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).
This commit is contained in:
Hans Dembinski
2025-06-05 23:14:21 +02:00
committed by GitHub
parent c55b5290af
commit f859c9fe7e
8 changed files with 101 additions and 2 deletions

View File

@@ -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 ;

9
doc/README.md Normal file
View File

@@ -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)

View File

@@ -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]]

2
doc/doxygen_postprocessing.py Normal file → Executable file
View File

@@ -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

View File

@@ -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.

View File

@@ -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 ]

View File

@@ -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 <boost/histogram.hpp>
#include <cassert>
#include <vector>
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<double> {
using base_type = axis::regular<double>;
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<double>(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<fraction> a = {
{1, 5}, // 0.2
{3, 5}, // 0.6
};
h.fill(a);
std::vector<double> 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
}
//]

View File

@@ -7,6 +7,7 @@
//[ guide_fill_histogram
#include <boost/histogram.hpp>
#include <boost/core/span.hpp>
#include <cassert>
#include <functional>
#include <numeric>
@@ -29,7 +30,16 @@ int main() {
h(xy);
// chunk-wise filling is also supported and more efficient, make some data...
std::vector<double> xy2[2] = {{0, 2, 5}, {0.8, 0.4, 0.7}};
std::vector<double> x = {0, 2, 5};
std::vector<double> y = {0.8, 0.4, 0.7};
// fill accepts an iterable over iterables, to avoid copying data we use
// std::vector<boost::span<double>> and not std::vector<std::vector<double>>
std::vector<boost::span<double>> 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<boost::span<double>, 2> xy2 = {boost::make_span(x), boost::make_span(y)};
// ... and call fill method
h.fill(xy2);