Skip to content

Commit 37f25cf

Browse files
committed
[hist] Implement initial RVariableBinAxis
As for the initial version of RRegularAxis, this first commit only exposes ComputeLinearizedIndex with a simple linear search.
1 parent ee5c12d commit 37f25cf

File tree

6 files changed

+203
-0
lines changed

6 files changed

+203
-0
lines changed

hist/histv7/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ ROOT_STANDARD_LIBRARY_PACKAGE(ROOTHist
22
HEADERS
33
ROOT/RLinearizedIndex.hxx
44
ROOT/RRegularAxis.hxx
5+
ROOT/RVariableBinAxis.hxx
56
NO_SOURCES
67
DEPENDENCIES
78
Core

hist/histv7/doc/DesignImplementation.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,6 @@ The implementation uses standard [C++17](https://en.cppreference.com/w/cpp/17.ht
7171
* No ROOT types, to make sure the histogram package can be compiled standalone.
7272
7373
Small objects are passed by value instead of by reference (`RBinIndex`, `RWeight`).
74+
75+
Complex objects, such as `std::vector`, that have to be copied (for example in a constructor) are also accepted by value.
76+
This allows a single overload that can efficiently take expiring ("moved") objects.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#ifndef ROOT_RVariableBinAxis
2+
#define ROOT_RVariableBinAxis
3+
4+
#include "RLinearizedIndex.hxx"
5+
6+
#include <cstddef>
7+
#include <utility>
8+
#include <vector>
9+
10+
namespace ROOT {
11+
namespace Experimental {
12+
13+
/**
14+
An axis with variable bins defined by their edges.
15+
16+
For example, the following creates an axis with 3 log-spaced bins:
17+
~~~ {.cxx}
18+
std::vector<double> binEdges = {1, 10, 100, 1000};
19+
ROOT::Experimental::RVariableBinAxis axis(binEdges);
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+
class RVariableBinAxis final {
26+
/// The (ordered) edges of the normal bins
27+
std::vector<double> fBinEdges;
28+
/// Whether underflow and overflow bins are enabled
29+
bool fEnableFlowBins;
30+
31+
public:
32+
/// Construct an axis object with variable bins.
33+
///
34+
/// \param[in] binEdges the (ordered) edges of the normal bins
35+
/// \param[in] enableFlowBins whether to enable underflow and overflow bins
36+
RVariableBinAxis(std::vector<double> binEdges, bool enableFlowBins = true)
37+
: fBinEdges(std::move(binEdges)), fEnableFlowBins(enableFlowBins)
38+
{
39+
// FIXME: should validate that fBinEdges is sorted
40+
}
41+
42+
std::size_t GetNumNormalBins() const { return fBinEdges.size() - 1; }
43+
std::size_t GetTotalNumBins() const { return fEnableFlowBins ? fBinEdges.size() + 1 : fBinEdges.size() - 1; }
44+
const std::vector<double> &GetBinEdges() const { return fBinEdges; }
45+
bool AreFlowBinsEnabled() const { return fEnableFlowBins; }
46+
47+
friend bool operator==(const RVariableBinAxis &lhs, const RVariableBinAxis &rhs)
48+
{
49+
return lhs.fBinEdges == rhs.fBinEdges && lhs.fEnableFlowBins == rhs.fEnableFlowBins;
50+
}
51+
52+
/// Compute the linarized index for a single argument.
53+
///
54+
/// The normal bins have indices \f$0\f$ to \f$fBinEdges.size() - 2\f$, the underflow bin has index
55+
/// \f$fBinEdges.size() - 1\f$, and the overflow bin has index \f$fBinEdges.size()\f$. If the argument is outside all
56+
/// bin edges and the flow bins are disabled, the return value is invalid.
57+
///
58+
/// \param[in] x the argument
59+
/// \return the linearized index that may be invalid
60+
RLinearizedIndex ComputeLinearizedIndex(double x) const
61+
{
62+
bool underflow = x < fBinEdges.front();
63+
// Put NaNs into overflow bin.
64+
bool overflow = !(x < fBinEdges.back());
65+
if (underflow) {
66+
return {fBinEdges.size() - 1, fEnableFlowBins};
67+
} else if (overflow) {
68+
return {fBinEdges.size(), fEnableFlowBins};
69+
}
70+
71+
// TODO (for later): The following can be optimized with binary search...
72+
for (std::size_t bin = 0; bin < fBinEdges.size() - 2; bin++) {
73+
if (x < fBinEdges[bin + 1]) {
74+
return {bin, true};
75+
}
76+
}
77+
std::size_t bin = fBinEdges.size() - 2;
78+
return {bin, true};
79+
}
80+
};
81+
82+
} // namespace Experimental
83+
} // namespace ROOT
84+
85+
#endif

hist/histv7/test/CMakeLists.txt

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

hist/histv7/test/hist_test.hxx

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

44
#include <ROOT/RRegularAxis.hxx>
5+
#include <ROOT/RVariableBinAxis.hxx>
56

67
#include "gtest/gtest.h"
78

89
using ROOT::Experimental::RRegularAxis;
10+
using ROOT::Experimental::RVariableBinAxis;
911

1012
#endif

hist/histv7/test/hist_variable.cxx

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

0 commit comments

Comments
 (0)