Skip to content

Commit 402d06f

Browse files
committed
[hist] Implement initial RHistEngine
It combines an RAxes object and storage of bin contents.
1 parent db766bb commit 402d06f

File tree

6 files changed

+362
-5
lines changed

6 files changed

+362
-5
lines changed

hist/histv7/doc/DesignImplementation.md

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