Skip to content

Commit ee5c12d

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 349085b commit ee5c12d

File tree

7 files changed

+219
-0
lines changed

7 files changed

+219
-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: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#ifndef ROOT_RLinearizedIndex
2+
#define ROOT_RLinearizedIndex
3+
4+
#include <cstddef>
5+
6+
namespace ROOT {
7+
namespace Experimental {
8+
9+
/**
10+
A linearized index that can be invalid.
11+
12+
For example, when an argument is outside the axis and underflow / overflow bins are disabled.
13+
*/
14+
struct RLinearizedIndex final {
15+
std::size_t index;
16+
bool valid;
17+
};
18+
19+
} // namespace Experimental
20+
} // namespace ROOT
21+
22+
#endif

hist/histv7/inc/ROOT/RRegularAxis.hxx

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