Skip to content

Commit 36f7101

Browse files
committed
[hist] Implement initial RHistEngine
It combines an RAxes object and storage of bin contents.
1 parent 686f90d commit 36f7101

File tree

6 files changed

+378
-7
lines changed

6 files changed

+378
-7
lines changed

hist/histv7/doc/DesignImplementation.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This document describes key design decisions and implementation choices.
44

55
## Templating
66

7-
Classes are only templated if required for data members, in particular the bin content type `T`.
7+
Classes are only templated if required for data members, in particular the `BinContentType`.
88
We use member function templates to accept variable number of arguments (see also below).
99
Classes are **not** templated to improve performance, in particular not on the axis type(s).
1010
This avoids an explosion of types and simplifies serialization.
@@ -28,7 +28,7 @@ Many member functions have two overloads: one accepting a function parameter pac
2828
### Arguments with Different Types
2929

3030
Functions that take arguments with different types expect a `std::tuple`.
31-
An example is `template <typename A...> void Fill(const std::tuple<A...> &args)`.
31+
An example is `template <typename... A> void Fill(const std::tuple<A...> &args)`.
3232

3333
For user-convenience, a variadic function template forwards to the `std::tuple` overload:
3434
```cpp
@@ -41,13 +41,13 @@ This will forward the arguments as references, so no copy-constructors are calle
4141
### Arguments with Same Type
4242
4343
In this case, the function has a `std::size_t N` template argument and accepts a `std::array`.
44-
An example is `template <std::size_t N> const T &GetBinContent(const std::array<RBinIndex, N> &args)`
44+
An example is `template <std::size_t N> const BinContentType &GetBinContent(const std::array<RBinIndex, N> &indices)`
4545
4646
For user-convenience, a variadic function template forwards to the `std::array` overload:
4747
```cpp
48-
template <typename... A> const T &GetBinContent(const A &...args) {
49-
std::array<RBinIndex, sizeof...(A)> a{args...};
50-
return GetBinContent(a);
48+
template <typename... A> const BinContentType &GetBinContent(const A &...args) {
49+
std::array<RBinIndex, sizeof...(A)> indices{args...};
50+
return GetBinContent(indices);
5151
}
5252
```
5353
This will copy the arguments, which is fine in this case because `RBinIndex` is small (see below).
@@ -58,7 +58,7 @@ Special arguments are passed last.
5858
Examples include
5959
```cpp
6060
template <typename... A> void Fill(const std::tuple<A...> &args, RWeight w);
61-
template <std::size_t N> void SetBinContent(const std::array<RBinIndex, N> &args, const T &content);
61+
template <std::size_t N> void SetBinContent(const std::array<RBinIndex, N> &indices, const BinContentType &content);
6262
```
6363
The same works for the variadic function templates that will check the type of the last argument.
6464

hist/histv7/headers.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ set(histv7_headers
22
ROOT/RAxes.hxx
33
ROOT/RBinIndex.hxx
44
ROOT/RBinIndexRange.hxx
5+
ROOT/RHistEngine.hxx
56
ROOT/RLinearizedIndex.hxx
67
ROOT/RRegularAxis.hxx
78
ROOT/RVariableBinAxis.hxx

hist/histv7/inc/ROOT/RHistEngine.hxx

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
/// \file
2+
/// \warning This is part of the %ROOT 7 prototype! It will change without notice. It might trigger earthquakes.
3+
/// Feedback is welcome!
4+
5+
#ifndef ROOT_RHistEngine
6+
#define ROOT_RHistEngine
7+
8+
#include "RAxes.hxx"
9+
#include "RBinIndex.hxx"
10+
#include "RLinearizedIndex.hxx"
11+
#include "RRegularAxis.hxx"
12+
13+
#include <array>
14+
#include <cassert>
15+
#include <stdexcept>
16+
#include <tuple>
17+
#include <utility>
18+
#include <vector>
19+
20+
class TBuffer;
21+
22+
namespace ROOT {
23+
namespace Experimental {
24+
25+
/**
26+
A histogram data structure to bin data along multiple dimensions.
27+
28+
Every call to \ref Fill(const A &... args) "Fill" bins the data according to the axis configuration and increments the
29+
bin content:
30+
\code
31+
ROOT::Experimental::RHistEngine<int> hist(10, 5, 15);
32+
hist.Fill(8.5);
33+
// hist.GetBinContent(ROOT::Experimental::RBinIndex(3)) will return 1
34+
\endcode
35+
36+
The class is templated on the bin content type. For counting, as in the example above, it may be an integer type such as
37+
`int` or `long`. Narrower types such as `unsigned char` or `short` are supported, but may overflow due to their limited
38+
range and must be used with care. For weighted filling, the bin content type must be a floating-point type such as
39+
`float` or `double`. Note that `float` has a limited significant precision of 24 bits.
40+
41+
An object can have arbitrary dimensionality determined at run-time. The axis configuration is passed as a vector of
42+
RAxisVariant:
43+
\code
44+
std::vector<ROOT::Experimental::RAxisVariant> axes;
45+
axes.push_back(ROOT::Experimental::RRegularAxis(10, 5, 15));
46+
axes.push_back(ROOT::Experimental::RVariableBinAxis({1, 10, 100, 1000}));
47+
ROOT::Experimental::RHistEngine<int> hist(axes);
48+
// hist.GetNDimensions() will return 2
49+
\endcode
50+
51+
\warning This is part of the %ROOT 7 prototype! It will change without notice. It might trigger earthquakes.
52+
Feedback is welcome!
53+
*/
54+
template <typename BinContentType>
55+
class RHistEngine final {
56+
/// The axis configuration for this histogram. Relevant methods are forwarded from the public interface.
57+
Internal::RAxes fAxes;
58+
/// The bin contents for this histogram
59+
std::vector<BinContentType> fBinContents;
60+
61+
public:
62+
/// Construct a histogram engine.
63+
///
64+
/// \param[in] axes the axis objects, must have size > 0
65+
explicit RHistEngine(std::vector<RAxisVariant> axes) : fAxes(std::move(axes))
66+
{
67+
fBinContents.resize(fAxes.ComputeTotalNBins());
68+
}
69+
70+
/// Construct a one-dimensional histogram engine with a regular axis.
71+
///
72+
/// \param[in] nNormalBins the number of normal bins, must be > 0
73+
/// \param[in] low the lower end of the axis interval (inclusive)
74+
/// \param[in] high the upper end of the axis interval (exclusive), must be > low
75+
/// \sa the \ref RRegularAxis::RRegularAxis(std::size_t nNormalBins, double low, double high, bool enableFlowBins)
76+
/// "constructor of RRegularAxis"
77+
RHistEngine(std::size_t nNormalBins, double low, double high) : RHistEngine({RRegularAxis(nNormalBins, low, high)})
78+
{
79+
}
80+
81+
// Copy constructor and assignment operator are deleted to avoid surprises.
82+
RHistEngine(const RHistEngine<BinContentType> &) = delete;
83+
RHistEngine(RHistEngine<BinContentType> &&) = default;
84+
RHistEngine<BinContentType> &operator=(const RHistEngine<BinContentType> &) = delete;
85+
RHistEngine<BinContentType> &operator=(RHistEngine<BinContentType> &&) = default;
86+
~RHistEngine() = default;
87+
88+
const std::vector<RAxisVariant> &GetAxes() const { return fAxes.Get(); }
89+
std::size_t GetNDimensions() const { return fAxes.GetNDimensions(); }
90+
std::size_t GetTotalNBins() const { return fBinContents.size(); }
91+
92+
/// Get the content of a single bin.
93+
///
94+
/// \code
95+
/// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
96+
/// std::array<ROOT::Experimental::RBinIndex, 2> indices = {3, 5};
97+
/// int content = hist.GetBinContent(indices);
98+
/// \endcode
99+
///
100+
/// \note Compared to TH1 conventions, the first normal bin has index 0 and underflow and overflow bins are special
101+
/// values. See also the class documentation of RBinIndex.
102+
///
103+
/// Throws an exception if the number of indices does not match the axis configuration or the bin is not found.
104+
///
105+
/// \param[in] indices the array of indices for each axis
106+
/// \return the bin content
107+
/// \sa the \ref GetBinContent(const A &... args) const "variadic function template overload" accepting arguments
108+
/// directly
109+
template <std::size_t N>
110+
const BinContentType &GetBinContent(const std::array<RBinIndex, N> &indices) const
111+
{
112+
// We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
113+
// be confusing for users.
114+
if (N != GetNDimensions()) {
115+
throw std::invalid_argument("invalid number of arguments to GetBinContent");
116+
}
117+
RLinearizedIndex index = fAxes.ComputeGlobalIndex(indices);
118+
if (!index.fValid) {
119+
throw std::invalid_argument("bin not found");
120+
}
121+
assert(index.fIndex < fBinContents.size());
122+
return fBinContents[index.fIndex];
123+
}
124+
125+
/// Get the content of a single bin.
126+
///
127+
/// \code
128+
/// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
129+
/// int content = hist.GetBinContent(ROOT::Experimental::RBinIndex(3), ROOT::Experimental::RBinIndex(5));
130+
/// // ... or construct the RBinIndex arguments implicitly from integers:
131+
/// content = hist.GetBinContent(3, 5);
132+
/// \endcode
133+
///
134+
/// \note Compared to TH1 conventions, the first normal bin has index 0 and underflow and overflow bins are special
135+
/// values. See also the class documentation of RBinIndex.
136+
///
137+
/// Throws an exception if the number of arguments does not match the axis configuration or the bin is not found.
138+
///
139+
/// \param[in] args the arguments for each axis
140+
/// \return the bin content
141+
/// \sa the \ref GetBinContent(const std::array<RBinIndex, N> &indices) const "function overload" accepting
142+
/// `std::array`
143+
template <typename... A>
144+
const BinContentType &GetBinContent(const A &...args) const
145+
{
146+
std::array<RBinIndex, sizeof...(A)> indices{args...};
147+
return GetBinContent(indices);
148+
}
149+
150+
/// Fill an entry into the histogram.
151+
///
152+
/// \code
153+
/// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
154+
/// auto args = std::make_tuple(8.5, 10.5);
155+
/// hist.Fill(args);
156+
/// \endcode
157+
///
158+
/// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
159+
/// discarded.
160+
///
161+
/// Throws an exception if the number of arguments does not match the axis configuration.
162+
///
163+
/// \param[in] args the arguments for each axis
164+
/// \sa the \ref Fill(const A &... args) "variadic function template overload" accepting arguments directly
165+
template <typename... A>
166+
void Fill(const std::tuple<A...> &args)
167+
{
168+
// We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
169+
// be confusing for users.
170+
if (sizeof...(A) != GetNDimensions()) {
171+
throw std::invalid_argument("invalid number of arguments to Fill");
172+
}
173+
RLinearizedIndex index = fAxes.ComputeGlobalIndex(args);
174+
if (index.fValid) {
175+
assert(index.fIndex < fBinContents.size());
176+
fBinContents[index.fIndex]++;
177+
}
178+
}
179+
180+
/// Fill an entry into the histogram.
181+
///
182+
/// \code
183+
/// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
184+
/// hist.Fill(8.5, 10.5);
185+
/// \endcode
186+
///
187+
/// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
188+
/// discarded.
189+
///
190+
/// Throws an exception if the number of arguments does not match the axis configuration.
191+
///
192+
/// \param[in] args the arguments for each axis
193+
/// \sa the \ref Fill(const std::tuple<A...> &args) "function overload" accepting `std::tuple`
194+
template <typename... A>
195+
void Fill(const A &...args)
196+
{
197+
Fill(std::forward_as_tuple(args...));
198+
}
199+
200+
/// %ROOT Streamer function to throw when trying to store an object of this class.
201+
void Streamer(TBuffer &) { throw std::runtime_error("unable to store RHistEngine"); }
202+
};
203+
204+
} // namespace Experimental
205+
} // namespace ROOT
206+
207+
#endif

hist/histv7/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
HIST_ADD_GTEST(hist_axes hist_axes.cxx)
2+
HIST_ADD_GTEST(hist_engine hist_engine.cxx)
23
HIST_ADD_GTEST(hist_index hist_index.cxx)
34
HIST_ADD_GTEST(hist_regular hist_regular.cxx)
45
HIST_ADD_GTEST(hist_variable hist_variable.cxx)

0 commit comments

Comments
 (0)