Skip to content

Commit 7abbc0d

Browse files
committed
[hist] Implement initial RAxes
This first version supports RRegularAxis and RVariableBinAxis and exposes only ComputeTotalNumBins and ComputeGlobalIndex.
1 parent e07fbb2 commit 7abbc0d

File tree

7 files changed

+306
-0
lines changed

7 files changed

+306
-0
lines changed

hist/histv7/headers.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
set(histv7_headers
2+
ROOT/RAxes.hxx
23
ROOT/RLinearizedIndex.hxx
34
ROOT/RRegularAxis.hxx
45
ROOT/RVariableBinAxis.hxx

hist/histv7/inc/LinkDef.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
#pragma link C++ class ROOT::Experimental::RRegularAxis-;
22
#pragma link C++ class ROOT::Experimental::RVariableBinAxis-;
3+
#pragma link C++ class ROOT::Experimental::Internal::RAxes-;

hist/histv7/inc/ROOT/RAxes.hxx

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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_RAxes
6+
#define ROOT_RAxes
7+
8+
#include "RLinearizedIndex.hxx"
9+
#include "RRegularAxis.hxx"
10+
#include "RVariableBinAxis.hxx"
11+
12+
#include <stdexcept>
13+
#include <tuple>
14+
#include <utility>
15+
#include <variant>
16+
#include <vector>
17+
18+
class TBuffer;
19+
20+
namespace ROOT {
21+
namespace Experimental {
22+
namespace Internal {
23+
24+
/**
25+
Bin configurations for all dimensions of a histogram.
26+
27+
\warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback is
28+
welcome!
29+
*/
30+
class RAxes final {
31+
public:
32+
using AxisVariant = std::variant<RRegularAxis, RVariableBinAxis>;
33+
34+
private:
35+
std::vector<AxisVariant> fAxes;
36+
37+
public:
38+
/// \param[in] axes the axis objects, must have size > 0
39+
explicit RAxes(std::vector<AxisVariant> axes) : fAxes(std::move(axes))
40+
{
41+
if (fAxes.empty()) {
42+
throw std::invalid_argument("must have at least 1 axis object");
43+
}
44+
}
45+
46+
std::size_t GetNumDimensions() const { return fAxes.size(); }
47+
const std::vector<AxisVariant> &GetVector() const { return fAxes; }
48+
49+
friend bool operator==(const RAxes &lhs, const RAxes &rhs) { return lhs.fAxes == rhs.fAxes; }
50+
51+
/// Compute the total number of bins for all axes.
52+
///
53+
/// It is the product of each dimension's total number of bins.
54+
///
55+
/// \return the total number of bins
56+
std::size_t ComputeTotalNumBins() const
57+
{
58+
std::size_t totalNumBins = 1;
59+
for (auto &&axis : fAxes) {
60+
if (auto *regular = std::get_if<RRegularAxis>(&axis)) {
61+
totalNumBins *= regular->GetTotalNumBins();
62+
} else if (auto *variable = std::get_if<RVariableBinAxis>(&axis)) {
63+
totalNumBins *= variable->GetTotalNumBins();
64+
}
65+
}
66+
return totalNumBins;
67+
}
68+
69+
private:
70+
template <std::size_t I, typename... A>
71+
RLinearizedIndex ComputeGlobalIndex(std::size_t index, const std::tuple<A...> &args) const
72+
{
73+
const auto &axis = fAxes[I];
74+
RLinearizedIndex linIndex;
75+
if (auto *regular = std::get_if<RRegularAxis>(&axis)) {
76+
index *= regular->GetTotalNumBins();
77+
linIndex = regular->ComputeLinearizedIndex(std::get<I>(args));
78+
} else if (auto *variable = std::get_if<RVariableBinAxis>(&axis)) {
79+
index *= variable->GetTotalNumBins();
80+
linIndex = variable->ComputeLinearizedIndex(std::get<I>(args));
81+
}
82+
if (!linIndex.fValid) {
83+
return {0, false};
84+
}
85+
index += linIndex.fIndex;
86+
if constexpr (I + 1 < sizeof...(A)) {
87+
return ComputeGlobalIndex<I + 1>(index, args);
88+
}
89+
return {index, true};
90+
}
91+
92+
public:
93+
/// Compute the global index for all axes.
94+
///
95+
/// \param[in] args the arguments
96+
/// \return the global index that may be invalid
97+
template <typename... A>
98+
RLinearizedIndex ComputeGlobalIndex(const std::tuple<A...> &args) const
99+
{
100+
if (sizeof...(A) != fAxes.size()) {
101+
throw std::invalid_argument("invalid number of arguments to ComputeGlobalIndex");
102+
}
103+
return ComputeGlobalIndex<0, A...>(0, args);
104+
}
105+
106+
/// ROOT Streamer function to throw when trying to store an object of this class.
107+
void Streamer(TBuffer &) { throw std::runtime_error("unable to store RAxes"); }
108+
};
109+
110+
} // namespace Internal
111+
} // namespace Experimental
112+
} // namespace ROOT
113+
114+
#endif

hist/histv7/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
HIST_ADD_GTEST(hist_axes hist_axes.cxx)
12
HIST_ADD_GTEST(hist_regular hist_regular.cxx)
23
HIST_ADD_GTEST(hist_variable hist_variable.cxx)
34

hist/histv7/test/hist_axes.cxx

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#include "hist_test.hxx"
2+
3+
#include <ROOT/RAxes.hxx>
4+
5+
#include <stdexcept>
6+
#include <tuple>
7+
#include <variant>
8+
#include <vector>
9+
10+
TEST(RAxes, Constructor)
11+
{
12+
static constexpr std::size_t BinsX = 20;
13+
const RRegularAxis regularAxis(BinsX, 0, BinsX);
14+
static constexpr std::size_t BinsY = 30;
15+
std::vector<double> bins;
16+
for (std::size_t i = 0; i < BinsY; i++) {
17+
bins.push_back(i);
18+
}
19+
bins.push_back(BinsY);
20+
const RVariableBinAxis variableBinAxis(bins);
21+
22+
RAxes axes({regularAxis, variableBinAxis});
23+
EXPECT_EQ(axes.GetNumDimensions(), 2);
24+
const auto &v = axes.GetVector();
25+
ASSERT_EQ(v.size(), 2);
26+
EXPECT_EQ(v[0].index(), 0);
27+
EXPECT_EQ(v[1].index(), 1);
28+
EXPECT_TRUE(std::get_if<RRegularAxis>(&v[0]) != nullptr);
29+
EXPECT_TRUE(std::get_if<RVariableBinAxis>(&v[1]) != nullptr);
30+
31+
std::vector<RAxes::AxisVariant> newAxes{variableBinAxis, regularAxis};
32+
axes = RAxes(newAxes);
33+
EXPECT_EQ(axes.GetNumDimensions(), 2);
34+
35+
EXPECT_THROW(RAxes({}), std::invalid_argument);
36+
}
37+
38+
TEST(RAxes, Equality)
39+
{
40+
static constexpr std::size_t BinsX = 20;
41+
const RRegularAxis regularAxis(BinsX, 0, BinsX);
42+
static constexpr std::size_t BinsY = 30;
43+
std::vector<double> bins;
44+
for (std::size_t i = 0; i < BinsY; i++) {
45+
bins.push_back(i);
46+
}
47+
bins.push_back(BinsY);
48+
const RVariableBinAxis variableBinAxis(bins);
49+
50+
const RAxes axesA({regularAxis, variableBinAxis});
51+
const RAxes axesA2({regularAxis, variableBinAxis});
52+
const RAxes axesB({variableBinAxis, regularAxis});
53+
const RAxes axesC({regularAxis});
54+
const RAxes axesD({variableBinAxis});
55+
56+
EXPECT_TRUE(axesA == axesA);
57+
EXPECT_TRUE(axesA == axesA2);
58+
EXPECT_TRUE(axesA2 == axesA);
59+
60+
EXPECT_FALSE(axesA == axesB);
61+
EXPECT_FALSE(axesA == axesC);
62+
EXPECT_FALSE(axesA == axesD);
63+
64+
EXPECT_FALSE(axesB == axesC);
65+
EXPECT_FALSE(axesB == axesD);
66+
67+
EXPECT_FALSE(axesC == axesD);
68+
}
69+
70+
TEST(RAxes, ComputeTotalNumBins)
71+
{
72+
static constexpr std::size_t BinsX = 20;
73+
const RRegularAxis regularAxis(BinsX, 0, BinsX);
74+
static constexpr std::size_t BinsY = 30;
75+
std::vector<double> bins;
76+
for (std::size_t i = 0; i < BinsY; i++) {
77+
bins.push_back(i);
78+
}
79+
bins.push_back(BinsY);
80+
const RVariableBinAxis variableBinAxis(bins);
81+
const RAxes axes({regularAxis, variableBinAxis});
82+
83+
// Both axes include underflow and overflow bins.
84+
EXPECT_EQ(axes.ComputeTotalNumBins(), (BinsX + 2) * (BinsY + 2));
85+
}
86+
87+
TEST(RAxes, ComputeGlobalIndex)
88+
{
89+
static constexpr std::size_t BinsX = 20;
90+
const RRegularAxis regularAxis(BinsX, 0, BinsX);
91+
static constexpr std::size_t BinsY = 30;
92+
std::vector<double> bins;
93+
for (std::size_t i = 0; i < BinsY; i++) {
94+
bins.push_back(i);
95+
}
96+
bins.push_back(BinsY);
97+
const RVariableBinAxis variableBinAxis(bins);
98+
const RAxes axes({regularAxis, variableBinAxis});
99+
100+
{
101+
const auto globalIndex = axes.ComputeGlobalIndex(std::make_tuple(1.5, 2.5));
102+
EXPECT_EQ(globalIndex.fIndex, 1 * (BinsY + 2) + 2);
103+
EXPECT_TRUE(globalIndex.fValid);
104+
}
105+
106+
{
107+
// Underflow bin of the first axis.
108+
const auto globalIndex = axes.ComputeGlobalIndex(std::make_tuple(-1, 2.5));
109+
EXPECT_EQ(globalIndex.fIndex, BinsX * (BinsY + 2) + 2);
110+
EXPECT_TRUE(globalIndex.fValid);
111+
}
112+
113+
{
114+
// Overflow bin of the second axis.
115+
const auto globalIndex = axes.ComputeGlobalIndex(std::make_tuple(1.5, 42));
116+
EXPECT_EQ(globalIndex.fIndex, 1 * (BinsY + 2) + BinsY + 1);
117+
EXPECT_TRUE(globalIndex.fValid);
118+
}
119+
}
120+
121+
TEST(RAxes, ComputeGlobalIndexNoFlowBins)
122+
{
123+
static constexpr std::size_t BinsX = 20;
124+
const RRegularAxis regularAxis(BinsX, 0, BinsX, /*enableFlowBins=*/false);
125+
static constexpr std::size_t BinsY = 30;
126+
std::vector<double> bins;
127+
for (std::size_t i = 0; i < BinsY; i++) {
128+
bins.push_back(i);
129+
}
130+
bins.push_back(BinsY);
131+
const RVariableBinAxis variableBinAxis(bins, /*enableFlowBins=*/false);
132+
const RAxes axes({regularAxis, variableBinAxis});
133+
ASSERT_EQ(axes.ComputeTotalNumBins(), BinsX * BinsY);
134+
135+
{
136+
const auto globalIndex = axes.ComputeGlobalIndex(std::make_tuple(1.5, 2.5));
137+
EXPECT_EQ(globalIndex.fIndex, 1 * BinsY + 2);
138+
EXPECT_TRUE(globalIndex.fValid);
139+
}
140+
141+
{
142+
// Underflow bin of the first axis.
143+
const auto globalIndex = axes.ComputeGlobalIndex(std::make_tuple(-1, 2.5));
144+
EXPECT_EQ(globalIndex.fIndex, 0);
145+
EXPECT_FALSE(globalIndex.fValid);
146+
}
147+
148+
{
149+
// Overflow bin of the second axis.
150+
const auto globalIndex = axes.ComputeGlobalIndex(std::make_tuple(1.5, 42));
151+
EXPECT_EQ(globalIndex.fIndex, 0);
152+
EXPECT_FALSE(globalIndex.fValid);
153+
}
154+
}
155+
156+
TEST(RAxes, ComputeGlobalIndexInvalidNumberOfArguments)
157+
{
158+
static constexpr std::size_t Bins = 20;
159+
const RRegularAxis axis(Bins, 0, Bins);
160+
const RAxes axes1({axis});
161+
ASSERT_EQ(axes1.GetNumDimensions(), 1);
162+
const RAxes axes2({axis, axis});
163+
ASSERT_EQ(axes2.GetNumDimensions(), 2);
164+
165+
EXPECT_NO_THROW(axes1.ComputeGlobalIndex(std::make_tuple(1)));
166+
EXPECT_THROW(axes1.ComputeGlobalIndex(std::make_tuple(1, 2)), std::invalid_argument);
167+
168+
EXPECT_THROW(axes2.ComputeGlobalIndex(std::make_tuple(1)), std::invalid_argument);
169+
EXPECT_NO_THROW(axes2.ComputeGlobalIndex(std::make_tuple(1, 2)));
170+
EXPECT_THROW(axes2.ComputeGlobalIndex(std::make_tuple(1, 2, 3)), std::invalid_argument);
171+
}

hist/histv7/test/hist_io.cxx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,19 @@ TEST(RVariableBinAxis, Streamer)
3434
const RVariableBinAxis axis(bins);
3535
ExpectThrowOnWriteObject(axis);
3636
}
37+
38+
TEST(RAxes, Streamer)
39+
{
40+
static constexpr std::size_t BinsX = 20;
41+
const RRegularAxis regularAxis(BinsX, 0, BinsX);
42+
static constexpr std::size_t BinsY = 30;
43+
std::vector<double> bins;
44+
for (std::size_t i = 0; i < BinsY; i++) {
45+
bins.push_back(i);
46+
}
47+
bins.push_back(BinsY);
48+
const RVariableBinAxis variableBinAxis(bins);
49+
50+
const RAxes axes({regularAxis, variableBinAxis});
51+
ExpectThrowOnWriteObject(axes);
52+
}

hist/histv7/test/hist_test.hxx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
#ifndef hist_test
22
#define hist_test
33

4+
#include <ROOT/RAxes.hxx>
45
#include <ROOT/RRegularAxis.hxx>
56
#include <ROOT/RVariableBinAxis.hxx>
67

78
#include "gtest/gtest.h"
89

910
using ROOT::Experimental::RRegularAxis;
1011
using ROOT::Experimental::RVariableBinAxis;
12+
using ROOT::Experimental::Internal::RAxes;
1113

1214
#endif

0 commit comments

Comments
 (0)