Skip to content

Commit fc16b1c

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

File tree

6 files changed

+367
-5
lines changed

6 files changed

+367
-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: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
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 <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.
51+
Feedback is 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 \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+
// RAxes::ComputeGlobalIndex will check the number of indices.
113+
RLinearizedIndex index = fAxes.ComputeGlobalIndex(indices);
114+
if (!index.fValid) {
115+
throw std::invalid_argument("bin not found");
116+
}
117+
assert(index.fIndex < fBinContents.size());
118+
return fBinContents[index.fIndex];
119+
}
120+
121+
/// Get the content of a single bin.
122+
///
123+
/// \code
124+
/// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
125+
/// int content = hist.GetBinContent(ROOT::Experimental::RBinIndex(3), ROOT::Experimental::RBinIndex(5));
126+
/// \endcode
127+
///
128+
/// Throws an exception if the number of arguments does not match the axis configuration or the bin is not found.
129+
///
130+
/// \param[in] args the arguments for each axis
131+
/// \return the bin content
132+
/// \sa the \ref GetBinContent(const std::array<RBinIndex, N> &indices) const "function overload" accepting
133+
/// `std::array`
134+
template <typename... A>
135+
const BinContentType &GetBinContent(const A &...args) const
136+
{
137+
std::array<RBinIndex, sizeof...(A)> indices{args...};
138+
return GetBinContent(indices);
139+
}
140+
141+
/// Fill an entry into the histogram.
142+
///
143+
/// \code
144+
/// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
145+
/// auto args = std::make_tuple(8.5, 10.5);
146+
/// hist.Fill(args);
147+
/// \endcode
148+
///
149+
/// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
150+
/// discarded.
151+
///
152+
/// Throws an exception if the number of arguments does not match the axis configuration.
153+
///
154+
/// \param[in] args the arguments for each axis
155+
/// \sa the \ref Fill(const A &... args) "variadic function template overload" accepting arguments directly
156+
template <typename... A>
157+
void Fill(const std::tuple<A...> &args)
158+
{
159+
// We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
160+
// be confusing for users.
161+
if (sizeof...(A) != GetNDimensions()) {
162+
throw std::invalid_argument("invalid number of arguments to Fill");
163+
}
164+
RLinearizedIndex index = fAxes.ComputeGlobalIndex(args);
165+
if (index.fValid) {
166+
assert(index.fIndex < fBinContents.size());
167+
fBinContents[index.fIndex]++;
168+
}
169+
}
170+
171+
/// Fill an entry into the histogram.
172+
///
173+
/// \code
174+
/// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
175+
/// hist.Fill(8.5, 10.5);
176+
/// \endcode
177+
///
178+
/// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
179+
/// discarded.
180+
///
181+
/// Throws an exception if the number of arguments does not match the axis configuration.
182+
///
183+
/// \param[in] args the arguments for each axis
184+
/// \sa the \ref Fill(const std::tuple<A...> &args) "function overload" accepting `std::tuple`
185+
template <typename... A>
186+
void Fill(const A &...args)
187+
{
188+
Fill(std::forward_as_tuple(args...));
189+
}
190+
191+
/// %ROOT Streamer function to throw when trying to store an object of this class.
192+
void Streamer(TBuffer &) { throw std::runtime_error("unable to store RHistEngine"); }
193+
};
194+
195+
} // namespace Experimental
196+
} // namespace ROOT
197+
198+
#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)