Skip to content

Commit f816336

Browse files
committed
[hist] Implement initial RRegularAxis
The first version of this class exposes only ComputeLinearizedIndex with a simple index computation formula. It does not (yet) implement the recent changes to improve its precision available in TAxis. This commit also includes basic build system additions and setup for unit testing.
1 parent 06819bc commit f816336

File tree

7 files changed

+231
-0
lines changed

7 files changed

+231
-0
lines changed

hist/histv7/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
ROOT_STANDARD_LIBRARY_PACKAGE(ROOTHist
2+
HEADERS
3+
ROOT/RLinearizedIndex.hxx
4+
ROOT/RRegularAxis.hxx
5+
NO_SOURCES
6+
DEPENDENCIES
7+
Core
8+
)
9+
10+
ROOT_ADD_TEST_SUBDIRECTORY(test)

hist/histv7/inc/LinkDef.h

Whitespace-only changes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
2+
/// is welcome!
3+
4+
#ifndef ROOT_RLinearizedIndex
5+
#define ROOT_RLinearizedIndex
6+
7+
#include <cstddef>
8+
9+
namespace ROOT {
10+
namespace Experimental {
11+
12+
/**
13+
A linearized index that can be invalid.
14+
15+
For example, when an argument is outside the axis and underflow / overflow bins are disabled.
16+
17+
\warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback is
18+
welcome!
19+
*/
20+
struct RLinearizedIndex final {
21+
std::size_t fIndex;
22+
bool fValid;
23+
};
24+
25+
} // namespace Experimental
26+
} // namespace ROOT
27+
28+
#endif

hist/histv7/inc/ROOT/RRegularAxis.hxx

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
2+
/// is welcome!
3+
4+
#ifndef ROOT_RRegularAxis
5+
#define ROOT_RRegularAxis
6+
7+
#include "RLinearizedIndex.hxx"
8+
9+
#include <cstddef>
10+
11+
namespace ROOT {
12+
namespace Experimental {
13+
14+
/**
15+
A regular axis with equidistant bins in the interval \f$[fLow, fHigh)\f$.
16+
17+
For example, the following creates a regular axis with 10 normal bins between 5 and 15:
18+
~~~ {.cxx}
19+
ROOT::Experimental::RRegularAxis axis(10, 5, 15);
20+
~~~
21+
22+
It is possible to disable underflow and overflow bins by passing `enableFlowBins = false`. In that case, arguments
23+
outside the axis will be silently discarded.
24+
25+
\warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback is
26+
welcome!
27+
*/
28+
class RRegularAxis final {
29+
/// The number of normal bins
30+
std::size_t fNumNormalBins;
31+
/// The lower end of the axis interval
32+
double fLow;
33+
/// The upper end of the axis interval
34+
double fHigh;
35+
/// The cached inverse of the bin width to speed up ComputeLinearizedIndex
36+
double fInvBinWidth; //!
37+
/// Whether underflow and overflow bins are enabled
38+
bool fEnableFlowBins;
39+
40+
public:
41+
/// Construct a regular axis object.
42+
///
43+
/// \param[in] numNormalBins the number of normal bins
44+
/// \param[in] low the lower end of the axis interval (inclusive)
45+
/// \param[in] high the upper end of the axis interval (exclusive)
46+
/// \param[in] enableFlowBins whether to enable underflow and overflow bins
47+
RRegularAxis(std::size_t numNormalBins, double low, double high, bool enableFlowBins = true)
48+
: fNumNormalBins(numNormalBins), fLow(low), fHigh(high), fEnableFlowBins(enableFlowBins)
49+
{
50+
// FIXME: should validate numNormalBins > 0 and low < high
51+
fInvBinWidth = numNormalBins / (high - low);
52+
}
53+
54+
std::size_t GetNumNormalBins() const { return fNumNormalBins; }
55+
std::size_t GetTotalNumBins() const { return fEnableFlowBins ? fNumNormalBins + 2 : fNumNormalBins; }
56+
double GetLow() const { return fLow; }
57+
double GetHigh() const { return fHigh; }
58+
bool HasFlowBins() const { return fEnableFlowBins; }
59+
60+
friend bool operator==(const RRegularAxis &lhs, const RRegularAxis &rhs)
61+
{
62+
return lhs.fNumNormalBins == rhs.fNumNormalBins && lhs.fLow == rhs.fLow && lhs.fHigh == rhs.fHigh &&
63+
lhs.fEnableFlowBins == rhs.fEnableFlowBins;
64+
}
65+
66+
/// Compute the linarized index for a single argument.
67+
///
68+
/// The normal bins have indices \f$0\f$ to \f$fNumNormalBins - 1\f$, the underflow bin has index
69+
/// \f$fNumNormalBins\f$, and the overflow bin has index \f$fNumNormalBins + 1\f$. If the argument is outside the
70+
/// interval \f$[fLow, fHigh)\f$ and the flow bins are disabled, the return value is invalid.
71+
///
72+
/// \param[in] x the argument
73+
/// \return the linearized index that may be invalid
74+
RLinearizedIndex ComputeLinearizedIndex(double x) const
75+
{
76+
bool underflow = x < fLow;
77+
// Put NaNs into overflow bin.
78+
bool overflow = !(x < fHigh);
79+
if (underflow) {
80+
return {fNumNormalBins, fEnableFlowBins};
81+
} else if (overflow) {
82+
return {fNumNormalBins + 1, fEnableFlowBins};
83+
}
84+
85+
std::size_t bin = (x - fLow) * fInvBinWidth;
86+
return {bin, true};
87+
}
88+
};
89+
90+
} // namespace Experimental
91+
} // namespace ROOT
92+
93+
#endif

hist/histv7/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ROOT_ADD_GTEST(hist_regular hist_regular.cxx LIBRARIES ROOTHist)

hist/histv7/test/hist_regular.cxx

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#include "hist_test.hxx"
2+
3+
#include <limits>
4+
5+
TEST(RRegularAxis, Constructor)
6+
{
7+
static constexpr std::size_t Bins = 20;
8+
RRegularAxis axis(Bins, 0, Bins);
9+
EXPECT_EQ(axis.GetNumNormalBins(), Bins);
10+
EXPECT_EQ(axis.GetTotalNumBins(), Bins + 2);
11+
EXPECT_EQ(axis.GetLow(), 0);
12+
EXPECT_EQ(axis.GetHigh(), Bins);
13+
EXPECT_TRUE(axis.HasFlowBins());
14+
15+
axis = RRegularAxis(Bins, 0, Bins, /*enableFlowBins=*/false);
16+
EXPECT_EQ(axis.GetNumNormalBins(), Bins);
17+
EXPECT_EQ(axis.GetTotalNumBins(), Bins);
18+
EXPECT_FALSE(axis.HasFlowBins());
19+
}
20+
21+
TEST(RRegularAxis, Equality)
22+
{
23+
static constexpr std::size_t Bins = 20;
24+
const RRegularAxis axisA(Bins, 0, Bins);
25+
const RRegularAxis axisANoFlowBins(Bins, 0, Bins, /*enableFlowBins=*/false);
26+
const RRegularAxis axisA2(Bins, 0, Bins);
27+
const RRegularAxis axisB(Bins / 2, 0, Bins);
28+
const RRegularAxis axisC(Bins, 0, Bins / 2);
29+
const RRegularAxis axisD(Bins, Bins / 2, Bins);
30+
31+
EXPECT_TRUE(axisA == axisA);
32+
EXPECT_TRUE(axisA == axisA2);
33+
EXPECT_TRUE(axisA2 == axisA);
34+
35+
EXPECT_FALSE(axisA == axisANoFlowBins);
36+
37+
EXPECT_FALSE(axisA == axisB);
38+
EXPECT_FALSE(axisA == axisC);
39+
EXPECT_FALSE(axisA == axisD);
40+
41+
EXPECT_FALSE(axisB == axisC);
42+
EXPECT_FALSE(axisB == axisD);
43+
44+
EXPECT_FALSE(axisC == axisD);
45+
EXPECT_FALSE(axisD == axisC);
46+
}
47+
48+
TEST(RRegularAxis, ComputeLinearizedIndex)
49+
{
50+
static constexpr std::size_t Bins = 20;
51+
const RRegularAxis axis(Bins, 0, Bins);
52+
const RRegularAxis axisNoFlowBins(Bins, 0, Bins, /*enableFlowBins=*/false);
53+
54+
// Underflow
55+
static constexpr double NegativeInfinity = -std::numeric_limits<double>::infinity();
56+
static constexpr double UnderflowLarge = -static_cast<double>(Bins);
57+
static constexpr double UnderflowSmall = -0.1;
58+
for (double underflow : {NegativeInfinity, UnderflowLarge, UnderflowSmall}) {
59+
auto linIndex = axis.ComputeLinearizedIndex(underflow);
60+
EXPECT_EQ(linIndex.fIndex, Bins);
61+
EXPECT_TRUE(linIndex.fValid);
62+
linIndex = axisNoFlowBins.ComputeLinearizedIndex(underflow);
63+
EXPECT_EQ(linIndex.fIndex, Bins);
64+
EXPECT_FALSE(linIndex.fValid);
65+
}
66+
67+
for (std::size_t i = 0; i < Bins; i++) {
68+
auto linIndex = axis.ComputeLinearizedIndex(i + 0.5);
69+
EXPECT_EQ(linIndex.fIndex, i);
70+
EXPECT_TRUE(linIndex.fValid);
71+
linIndex = axisNoFlowBins.ComputeLinearizedIndex(i + 0.5);
72+
EXPECT_EQ(linIndex.fIndex, i);
73+
EXPECT_TRUE(linIndex.fValid);
74+
}
75+
76+
// Overflow
77+
static constexpr double PositiveInfinity = std::numeric_limits<double>::infinity();
78+
static constexpr double NaN = std::numeric_limits<double>::quiet_NaN();
79+
static constexpr double OverflowLarge = static_cast<double>(Bins * 2);
80+
static constexpr double OverflowSmall = Bins + 0.1;
81+
for (double overflow : {PositiveInfinity, NaN, OverflowLarge, OverflowSmall}) {
82+
auto linIndex = axis.ComputeLinearizedIndex(overflow);
83+
EXPECT_EQ(linIndex.fIndex, Bins + 1);
84+
EXPECT_TRUE(linIndex.fValid);
85+
linIndex = axisNoFlowBins.ComputeLinearizedIndex(overflow);
86+
EXPECT_EQ(linIndex.fIndex, Bins + 1);
87+
EXPECT_FALSE(linIndex.fValid);
88+
}
89+
}

hist/histv7/test/hist_test.hxx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#ifndef hist_test
2+
#define hist_test
3+
4+
#include <ROOT/RRegularAxis.hxx>
5+
6+
#include "gtest/gtest.h"
7+
8+
using ROOT::Experimental::RRegularAxis;
9+
10+
#endif

0 commit comments

Comments
 (0)