From f859c9fe7ec0f55509e6ba51df2593e07c924863 Mon Sep 17 00:00:00 2001 From: Hans Dembinski Date: Thu, 5 Jun 2025 23:14:21 +0200 Subject: [PATCH] Expanded docs to discuss special cases (#412) This clarifies how to use histogram::fill efficiently with multi-dimensional histograms, and discusses a workaround for the design choice that Axis::index cannot be overloaded. And since I haven't build the documentation locally for a long time, and a new computer where I needed to set things up again from scratch, I also added a README.md on how to do that (which is not trivial for boostbook). --- doc/Jamfile | 2 +- doc/README.md | 9 +++ doc/concepts/Axis.qbk | 2 + doc/doxygen_postprocessing.py | 2 + doc/guide.qbk | 15 +++++ examples/Jamfile | 1 + ...guide_custom_axis_multiple_value_types.cpp | 60 +++++++++++++++++++ examples/guide_fill_histogram.cpp | 12 +++- 8 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 doc/README.md mode change 100644 => 100755 doc/doxygen_postprocessing.py create mode 100644 examples/guide_custom_axis_multiple_value_types.cpp diff --git a/doc/Jamfile b/doc/Jamfile index 61087d41..62297cd6 100644 --- a/doc/Jamfile +++ b/doc/Jamfile @@ -41,7 +41,7 @@ doxygen reference actions doxygen-postprocessing { - python $(THIS_PATH)/doxygen_postprocessing.py "$(>)" + $(THIS_PATH)/doxygen_postprocessing.py "$(>)" } notfile reference-pp : @doxygen-postprocessing : reference.xml ; diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 00000000..d1eb34b4 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,9 @@ +# Building the documentation + +For building the documentation, certain dependencies have to be installed and a user-config.jam has to be generated in the home directory. + +See [Quickbook documentation](https://www.boost.org/doc/libs/master/doc/html/quickbook/install.html) for details. + +In addition, b2 needs to be configured with Python support. + +See [b2 documentation](https://www.boost.org/doc/libs/1_76_0/tools/build/doc/html/index.html#bbv2.reference.tools.libraries.python) \ No newline at end of file diff --git a/doc/concepts/Axis.qbk b/doc/concepts/Axis.qbk index 9fc866df..dc13388c 100644 --- a/doc/concepts/Axis.qbk +++ b/doc/concepts/Axis.qbk @@ -9,6 +9,8 @@ An [*Axis] maps input values to indices. It holds state specific to that axis, like the number of bins and any metadata. Must be [@https://en.cppreference.com/w/cpp/named_req/CopyConstructible CopyConstructible], [@https://en.cppreference.com/w/cpp/named_req/CopyAssignable CopyAssignable], and *nothrow* [@https://en.cppreference.com/w/cpp/named_req/MoveAssignable MoveAssignable]. +[note `Axis::index` cannot be templated or overloaded. This is by design, but also a limitation of the internal code used to detect generic user-defined axes. A workaround for making an axis that accepts multiple value types is described in the [link histogram.guide.expert.axis_multiple_value_types [*Guide]].] + [heading Associated Types] * [link histogram.concepts.DiscreteAxis [*DiscreteAxis]] diff --git a/doc/doxygen_postprocessing.py b/doc/doxygen_postprocessing.py old mode 100644 new mode 100755 index c41aa6e2..89d28768 --- a/doc/doxygen_postprocessing.py +++ b/doc/doxygen_postprocessing.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + # Copyright Hans Dembinski 2018 - 2019. # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at diff --git a/doc/guide.qbk b/doc/guide.qbk index d91c5063..fdc23084 100644 --- a/doc/guide.qbk +++ b/doc/guide.qbk @@ -398,6 +398,21 @@ The library supports non-orthogonal grids by allowing axis types to accept a `st [endsect] +[section:axis_multiple_value_types Axis which accepts multiple value types] + +Can you make an axis that accepts more than one type of value? Sort of, with a workaround. + +What is the issue here? A basic assumption during the design of this library was that `Axis::index` only needs to handle a single value type, and thus the implementation does not allow `Axis::index` to be overloaded or templated. For example, data for one particular axis of the histogram may have type `double` or type `std::string`, but not a mix of the two. Value types which are implicitly convertible also work, for example, `double` and `int`. + +But what if you really need to accept more than one value type, and those types are not implicitly convertible? Then you can make it work with a special converter type, like in the following example. + +[import ../examples/guide_custom_axis_multiple_value_types.cpp] +[guide_custom_axis_multiple_value_types] + +In other words, the solution here was to make a type towards which the other value types are implicitly convertible. This is as efficient as making overloads, although formally a temporary object is created here, because the optimizer will inline the calls and never create that temporary object. + +[endsect] + [section User-defined storage class] Histograms which use a different storage class can easily created with the factory function [headerref boost/histogram/make_histogram.hpp make_histogram_with]. For convenience, this factory function accepts many standard containers as storage backends: vectors, arrays, and maps. These are automatically wrapped with a [classref boost::histogram::storage_adaptor] to provide the storage interface needed by the library. Users may also place custom accumulators in the vector, as described in the next section. diff --git a/examples/Jamfile b/examples/Jamfile index 367158c4..655c0228 100644 --- a/examples/Jamfile +++ b/examples/Jamfile @@ -42,6 +42,7 @@ alias cxx14 : [ run guide_custom_accumulators_ouroboros.cpp ] [ run guide_custom_minimal_axis.cpp ] [ run guide_custom_modified_axis.cpp ] + [ run guide_custom_axis_multiple_value_types.cpp ] [ run guide_custom_storage.cpp ] [ run guide_fill_histogram.cpp ] [ run guide_fill_profile.cpp ] diff --git a/examples/guide_custom_axis_multiple_value_types.cpp b/examples/guide_custom_axis_multiple_value_types.cpp new file mode 100644 index 00000000..74bd7f8e --- /dev/null +++ b/examples/guide_custom_axis_multiple_value_types.cpp @@ -0,0 +1,60 @@ +// Copyright 2025 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +//[ guide_custom_axis_multiple_value_types + +#include +#include +#include + +using namespace boost::histogram; + +struct fraction { + int numerator; + int denominator; +}; + +// We can use a converter type to accept multiple value types. +struct my_axis : axis::regular { + using base_type = axis::regular; + + using base_type::regular; // inherit constructors + + struct converter_type { + double val_; + // put overloads to handle multiple data types here or use a template + converter_type(const fraction& x) + : val_{static_cast(x.numerator) / x.denominator} {} + converter_type(double x) : val_{x} {} + }; + + axis::index_type index(converter_type x) const { + return base_type::index(x.val_); + } +}; + +int main() { + auto h = make_histogram(my_axis(4, 0.0, 1.0)); + + h(fraction{1, 3}); // 0.3333 + h(0.8); + + std::vector a = { + {1, 5}, // 0.2 + {3, 5}, // 0.6 + }; + h.fill(a); + + std::vector b = {0.2, 0.4}; + h.fill(b); + + assert(h.at(0) == 2); // 0.0 ... 0.25 + assert(h.at(1) == 2); // 0.25 ... 0.5 + assert(h.at(2) == 1); // 0.5 ... 0.75 + assert(h.at(3) == 1); // 0.75 ... 1.0 +} + +//] diff --git a/examples/guide_fill_histogram.cpp b/examples/guide_fill_histogram.cpp index 172be66e..f70b3b98 100644 --- a/examples/guide_fill_histogram.cpp +++ b/examples/guide_fill_histogram.cpp @@ -7,6 +7,7 @@ //[ guide_fill_histogram #include +#include #include #include #include @@ -29,7 +30,16 @@ int main() { h(xy); // chunk-wise filling is also supported and more efficient, make some data... - std::vector xy2[2] = {{0, 2, 5}, {0.8, 0.4, 0.7}}; + std::vector x = {0, 2, 5}; + std::vector y = {0.8, 0.4, 0.7}; + + // fill accepts an iterable over iterables, to avoid copying data we use + // std::vector> and not std::vector> + std::vector> xy2 = {boost::make_span(x), boost::make_span(y)}; + + // alternatively, if the number of axes is known at compile-time, + // it is better to use an array of spans, which avoids the heap allocation + // std::array, 2> xy2 = {boost::make_span(x), boost::make_span(y)}; // ... and call fill method h.fill(xy2);