new docu section about histogram usage in APIs

This commit is contained in:
Hans Dembinski 2020-03-10 13:34:44 +01:00 committed by GitHub
parent 48226f606e
commit 82a22250aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 130 additions and 30 deletions

View File

@ -44,15 +44,15 @@ The library provides a number of useful axis types. The builtin axis types can b
[variablelist
[
[
[classref boost::histogram::axis::regular]
[classref boost::histogram::axis::regular regular]
]
[
Axis over intervals on the real line which have equal width. Value-to-index conversion is O(1) and very fast. The axis does not allocate memory dynamically. The axis is very flexible thanks to transforms (see below). Due to finite precision of floating point calculations, bin edges may not be exactly at expected values, though. If you need bin edges at exactly defined floating point values, use the [classref boost::histogram::axis::variable variable axis]. If you need bins at exact consecutive integral values, use the [classref boost::histogram::axis::integer integer axis].
Axis over intervals on the real line which have equal width. Value-to-index conversion is O(1) and very fast. The axis does not allocate memory dynamically. The axis is very flexible thanks to transforms (see below). Due to finite precision of floating point calculations, bin edges may not be exactly at expected values, though. If you need bin edges at exactly defined floating point values, use the [classref boost::histogram::axis::variable variable] axis. If you need bins at exact consecutive integral values, use the [classref boost::histogram::axis::integer integer] axis.
]
]
[
[
[classref boost::histogram::axis::variable]
[classref boost::histogram::axis::variable variable]
]
[
Axis over intervals on the real line of variable width. Value-to-index conversion is O(log(N)). The axis allocates memory dynamically to store the bin edges. Use this if the regular axis with transforms cannot represent the binning you want. If you need bin edges at exactly defined floating point values, use this axis.
@ -60,18 +60,18 @@ The library provides a number of useful axis types. The builtin axis types can b
]
[
[
[classref boost::histogram::axis::integer]
[classref boost::histogram::axis::integer integer]
]
[
Axis over an integer sequence [i, i+1, i+2, ...]. It can be configured to handle real input values, too, and then acts like a fast regular axis with a fixed bin width of 1. Value-to-index conversion is O(1) and faster than for the [classref boost::histogram::axis::regular regular axis]. Does not allocate memory dynamically. Use this when your input consists of an integer range or pre-digitized values with low dynamic range, like pixel values for individual colors in an image.
Axis over an integer sequence [i, i+1, i+2, ...]. It can be configured to handle real input values, too, and then acts like a fast regular axis with a fixed bin width of 1. Value-to-index conversion is O(1) and faster than for the [classref boost::histogram::axis::regular regular] axis. Does not allocate memory dynamically. Use this when your input consists of an integer range or pre-digitized values with low dynamic range, like pixel values for individual colours in an image.
]
]
[
[
[classref boost::histogram::axis::category]
[classref boost::histogram::axis::category category]
]
[
Axis over a set of unique values of an arbitrary equal-comparable type. Value-to-index conversion is O(N), but still faster than the O(logN) complexity of the [classref boost::histogram::axis::variable variable axis] for N < 10, the typical use case. The axis allocates memory from the heap to store the values.
Axis over a set of unique values of an arbitrary equal-comparable type. Value-to-index conversion is O(N), but still faster than the O(logN) complexity of the [classref boost::histogram::axis::variable variable] axis for N < 10, the typical use case. The axis allocates memory from the heap to store the values.
]
]
]
@ -81,7 +81,7 @@ Here is an example which shows the basic use case for each axis type.
[import ../examples/guide_axis_basic_demo.cpp]
[guide_axis_basic_demo]
[note All builtin axes over the real-line use semi-open bin intervals by convention. As a mnemonic, think of iterator ranges from `begin` to `end`, where `end` is also not included.]
[note All builtin axes over a continuous value range use semi-open intervals in their constructors as a convention. As a mnemonic, think of iterator ranges from `begin` to `end`, where `end` is also never included.]
As mentioned in the previous example, you can assign an optional label to any axis to keep track of what the axis is about. Assume you have census data and you want to investigate how yearly income correlates with age, you could do:
@ -96,7 +96,7 @@ By default, strings are used to store the metadata, but any type compatible with
[section Axis configuration]
All builtin axis types have template arguments for customization. All arguments have reasonable defaults so you can use empty brackets. If your compiler supports C++17, you can drop the brackets altogether. Suitable arguments are then deduced from the constructor call. The template arguments are in order:
All builtin axis types have template arguments for customisation. All arguments have reasonable defaults so you can use empty brackets. If your compiler supports C++17, you can drop the brackets altogether. Suitable arguments are then deduced from the constructor call. The template arguments are in order:
[variablelist
[
@ -135,14 +135,14 @@ You can use the `boost::histogram::use_default` tag type for any of these option
[section Transforms]
Transforms are a way to customize a [classref boost::histogram::axis::regular regular axis]. The default is the identity transform which forwards the value. Transforms allow you to chose the faster stack-allocated regular axis over the generic [classref boost::histogram::axis::variable variable axis] in some cases. A common need is a regular binning in the logarithm of the input value. This can be achieved with a [classref boost::histogram::axis::transform::log log transform]. The follow example shows the builtin transforms.
Transforms are a way to customize a [classref boost::histogram::axis::regular regular] axis. Transforms allow you to chose the faster stack-allocated regular axis over the generic [classref boost::histogram::axis::variable variable axis] in some cases. The default is the identity transform which does nothing. A common need is a regular binning in the logarithm of the input value. This can be achieved with a [classref boost::histogram::axis::transform::log log] transform. The follow example shows the builtin transforms.
[import ../examples/guide_axis_with_transform.cpp]
[guide_axis_with_transform]
Due to the finite precision of floating point calculations, the bin edges of a transformed regular axis may not be exactly at the expected values. If you need exact correspondence, use a [classref boost::histogram::axis::variable variable axis].
Due to the finite precision of floating point calculations, the bin edges of a transformed regular axis may not be exactly at the expected values. If you need exact correspondence, use a [classref boost::histogram::axis::variable variable] axis.
Users may write their own transforms and use them with the builtin [classref boost::histogram::axis::regular regular axis], by implementing a type that matches the [link histogram.concepts.Transform [*Transform] concept].
Users may write their own transforms and use them with the builtin [classref boost::histogram::axis::regular regular] axis, by implementing a type that matches the [link histogram.concepts.Transform [*Transform] concept].
[endsect]
@ -157,7 +157,7 @@ Under- and overflow bins are added automatically for most axis types. If you cre
[import ../examples/guide_axis_with_uoflow_off.cpp]
[guide_axis_with_uoflow_off]
The [classref boost::histogram::axis::category category axis] by default comes only with an overflow bin, which counts all input values that are not part of the initial set.
The [classref boost::histogram::axis::category category] axis by default comes only with an overflow bin, which counts all input values that are not part of the initial set.
[*Circular axes]
@ -170,7 +170,7 @@ A circular axis cannot have an underflow bin, passing both options together gene
[*Growing axes]
Each builtin axis has an option to make it grow beyond its initial range when a value outside of that range is passed to it, while the default behavior is to count this value in the under- or overflow bins (or to discard it). Example:
Each builtin axis has an option to make it grow beyond its initial range when a value outside of that range is passed to it, while the default behaviour is to count this value in the under- or overflow bins (or to discard it). Example:
[import ../examples/guide_axis_growing.cpp]
[guide_axis_growing]
@ -192,7 +192,7 @@ A growing axis can have under- and overflow bins, but these only count the speci
[section Filling histograms and accessing cells]
A histogram has been created and now you want to insert values. This is done with the flexible [memberref boost::histogram::histogram::operator() call operator] or the [memberref boost::histogram::histogram::fill fill method], which you typically call in a loop. [memberref boost::histogram::histogram::operator() The call operator] accepts `N` arguments or a `std::tuple` with `N` elements, where `N` is equal to the number of axes of the histogram. It finds the corresponding bin for the input and increments the bin counter by one. The [memberref boost::histogram::histogram::fill fill method] accepts a single iterable over other iterables (which must have have elements contiguous in memory) or values, see the method documentation for details.
A histogram has been created and now you want to insert values. This is done with the flexible [memberref boost::histogram::histogram::operator() call operator] or the [memberref boost::histogram::histogram::fill fill method], which you typically call in a loop. The [memberref boost::histogram::histogram::operator() call operator] accepts `N` arguments or a `std::tuple` with `N` elements, where `N` is equal to the number of axes of the histogram. It finds the corresponding bin for the input and increments the bin counter by one. The [memberref boost::histogram::histogram::fill fill method] accepts a single iterable over other iterables (which must have have elements contiguous in memory) or values, see the method documentation for details.
After the histogram has been filled, use the [memberref boost::histogram::histogram::at at method] (in analogy to `std::vector::at`) to access the cell values. It accepts integer indices, one for each axis of the histogram.
@ -285,11 +285,11 @@ The library provides the [funcref boost::histogram::algorithm::project] function
[section Reduction]
A projection removes an axis completely. A less drastic way to obtain a smaller histogram is the [funcref boost::histogram::algorithm::reduce] function, which allows one to /slice/, /shrink/ or /rebin/ individual axes.
A projection removes an axis completely. A less drastic way to obtain a smaller histogram is the [funcref boost::histogram::algorithm::reduce reduce] function, which allows one to /slice/, /shrink/ or /rebin/ individual axes.
Shrinking means that the value range of an axis is reduced and the number of bins along that axis. Slicing does the same, but is based on axis indices while shrinking is based on the axis values. To rebin means that adjacent bins are merged into larger bins, the histogram is made coarser. For N adjacent bins, a new bin is formed which covers the common interval of the merged bins and has their added content. These two operations can be combined and applied to several axes at once. Doing it in one step is much more efficient than doing it in several steps.
Shrinking means that the value range of an axis is reduced and the number of bins along that axis. Slicing does the same, but is based on axis indices while shrinking is based on the axis values. To /rebin/ means that adjacent bins are merged into larger bins, the histogram is made coarser. For N adjacent bins, a new bin is formed which covers the common interval of the merged bins and has their added content. These two operations can be combined and applied to several axes at once. Doing it in one step is much more efficient than doing it in several steps.
[funcref boost::histogram::algorithm::reduce] does not change the total count if all modified axes in the histogram have underflow/overflow bins. Counts in removed bins are added to the corresponding under- and overflow bins. As in case of [funcref boost::histogram::algorithm::project], such a reduced histogram is guaranteed to be identical to one obtained from filling the original data.
The [funcref boost::histogram::algorithm::reduce reduce] function does not change the total count if all modified axes in the histogram have underflow and overflow bins. Counts in removed bins are added to the corresponding under- and overflow bins. As in case of the [funcref boost::histogram::algorithm::project project] function, such a histogram is guaranteed to be identical to one obtained from filling the original data.
[import ../examples/guide_histogram_reduction.cpp]
[guide_histogram_reduction]
@ -316,6 +316,45 @@ The library supports serialization via [@boost:/libs/serialization/index.html Bo
[endsect]
[section Using histograms in APIs]
Letting the compiler deduce the histogram type is recommended, because the templated type is tedious to write down explicitly. Functions or methods which accept or return histograms should be templated to work with all kinds of histograms. It is also possible to write templated versions which accept only histograms with dynamic axes or only histograms with static axes. The following example demonstrates all this.
[import ../examples/guide_histogram_in_api.cpp]
[guide_histogram_in_api]
If the histogram type has to be written down explicitly, the types are constructed as follows. In all cases, the `default_storage` type argument may be replaced by any other storage type or omitted entirely.
* Histogram with fixed axis types:
```
boost::histogram::histogram<
std::tuple<axis_type_1, axis_type_2, ..., axis_type_N>
, boost::histogram::default_storage
>
```
* Histogram with a variable number of the same axis type:
```
boost::histogram::histogram<
std::vector<
axis_type_1
>
, boost::histogram::default_storage
>
```
* Histogram with variable axis types:
```
boost::histogram::histogram<
std::vector<
boost::histogram::axis::variant<
axis_type_1, axis_type_2, ..., axis_type_N
>
>
, boost::histogram::default_storage
>
```
[endsect] [/Using histograms in APIs]
[section:expert Advanced usage]
The library is customisable and extensible by users. Users can create new axis types and use them with the histogram, or implement a custom storage policy, or use a builtin storage policy with a custom counter type. The library was designed to make this very easy. This section shows how to do this.

View File

@ -44,6 +44,7 @@ alias cxx14 :
[ run guide_histogram_reduction.cpp ]
[ run guide_histogram_projection.cpp ]
[ run guide_histogram_streaming.cpp ]
[ run guide_histogram_in_api.cpp ]
[ run guide_indexed_access.cpp ]
[ run guide_make_dynamic_histogram.cpp ]
[ run guide_make_static_histogram.cpp ]

View File

@ -0,0 +1,57 @@
// Copyright 2020 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_histogram_in_api
#include <boost/histogram.hpp>
#include <cassert>
// function accepts any histogram and returns a copy
template <class Axes, class Storage>
boost::histogram::histogram<Axes, Storage> any_histogram(
boost::histogram::histogram<Axes, Storage>& h) {
return h;
}
// function only accepts histograms with fixed axis types and returns a copy
template <class Storage, class... Axes>
boost::histogram::histogram<std::tuple<Axes...>, Storage> only_static_histogram(
boost::histogram::histogram<std::tuple<Axes...>, Storage>& h) {
return h;
}
// function only accepts histograms with dynamic axis types and returns a copy
template <class Storage, class... Axes>
boost::histogram::histogram<std::vector<boost::histogram::axis::variant<Axes...>>,
Storage>
only_dynamic_histogram(
boost::histogram::histogram<std::vector<boost::histogram::axis::variant<Axes...>>,
Storage>& h) {
return h;
}
int main() {
using namespace boost::histogram;
auto histogram_with_static_axes = make_histogram(axis::regular<>(10, 0, 1));
using axis_variant = axis::variant<axis::regular<>, axis::integer<>>;
std::vector<axis_variant> axes;
axes.emplace_back(axis::regular<>(5, 0, 1));
axes.emplace_back(axis::integer<>(0, 1));
auto histogram_with_dynamic_axes = make_histogram(axes);
assert(any_histogram(histogram_with_static_axes) == histogram_with_static_axes);
assert(any_histogram(histogram_with_dynamic_axes) == histogram_with_dynamic_axes);
assert(only_static_histogram(histogram_with_static_axes) == histogram_with_static_axes);
assert(only_dynamic_histogram(histogram_with_dynamic_axes) ==
histogram_with_dynamic_axes);
// does not compile: only_static_histogram(histogram_with_dynamic_axes)
// does not compile: only_dynamic_histogram(histogram_with_static_axes)
}
//]

View File

@ -155,6 +155,8 @@ public:
/** Fill histogram with values, an optional weight, and/or a sample.
Returns iterator to located cell.
Arguments are passed in order to the axis objects. Passing an argument type that is
not convertible to the value type accepted by the axis or passing the wrong number
of arguments causes a throw of `std::invalid_argument`.
@ -176,14 +178,14 @@ public:
__Axis with multiple arguments__
If the histogram contains an axis which accepts a `std::tuple` of arguments, the
arguments for that axis need to passed as a `std::tuple`, for example,
arguments for that axis need to be passed as a `std::tuple`, for example,
`std::make_tuple(1.2, 2.3)`. If the histogram contains only this axis and no other,
the arguments can be passed directly.
*/
template <class Arg0, class... Args>
std::enable_if_t<(detail::is_tuple<Arg0>::value == false || sizeof...(Args) > 0),
iterator>
operator()(const Arg0& arg0, const Args&... args) {
template <class T0, class... Ts,
class = std::enable_if_t<(detail::is_tuple<T0>::value == false ||
sizeof...(Ts) > 0)>>
iterator operator()(const T0& arg0, const Ts&... args) {
return operator()(std::forward_as_tuple(arg0, args...));
}
@ -623,23 +625,24 @@ auto operator/(const histogram<A, S>& h, double x) {
#if __cpp_deduction_guides >= 201606
template <class... Axes, class = detail::requires_axes<std::tuple<std::decay_t<Axes>...>>>
histogram(Axes...) -> histogram<std::tuple<std::decay_t<Axes>...>>;
histogram(Axes...)->histogram<std::tuple<std::decay_t<Axes>...>>;
template <class... Axes, class S, class = detail::requires_storage_or_adaptible<S>>
histogram(std::tuple<Axes...>, S)
-> histogram<std::tuple<Axes...>, std::conditional_t<detail::is_adaptible<S>::value,
storage_adaptor<S>, S>>;
->histogram<std::tuple<Axes...>, std::conditional_t<detail::is_adaptible<S>::value,
storage_adaptor<S>, S>>;
template <class Iterable, class = detail::requires_iterable<Iterable>,
class = detail::requires_any_axis<typename Iterable::value_type>>
histogram(Iterable) -> histogram<std::vector<typename Iterable::value_type>>;
histogram(Iterable)->histogram<std::vector<typename Iterable::value_type>>;
template <class Iterable, class S, class = detail::requires_iterable<Iterable>,
class = detail::requires_any_axis<typename Iterable::value_type>,
class = detail::requires_storage_or_adaptible<S>>
histogram(Iterable, S) -> histogram<
std::vector<typename Iterable::value_type>,
std::conditional_t<detail::is_adaptible<S>::value, storage_adaptor<S>, S>>;
histogram(Iterable, S)
->histogram<
std::vector<typename Iterable::value_type>,
std::conditional_t<detail::is_adaptible<S>::value, storage_adaptor<S>, S>>;
#endif