Skip to content

Commit 55d4eed

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 d5194a0 commit 55d4eed

File tree

6 files changed

+209
-0
lines changed

6 files changed

+209
-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: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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_RVariableBinAxis
5+
#define ROOT_RVariableBinAxis
6+
7+
#include "RLinearizedIndex.hxx"
8+
9+
#include <cstddef>
10+
#include <utility>
11+
#include <vector>
12+
13+
namespace ROOT {
14+
namespace Experimental {
15+
16+
/**
17+
An axis with variable bins defined by their edges.
18+
19+
For example, the following creates an axis with 3 log-spaced bins:
20+
~~~ {.cxx}
21+
std::vector<double> binEdges = {1, 10, 100, 1000};
22+
ROOT::Experimental::RVariableBinAxis axis(binEdges);
23+
~~~
24+
25+
It is possible to disable underflow and overflow bins by passing `enableFlowBins = false`. In that case, arguments
26+
outside the axis will be silently discarded.
27+
28+
\warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback is
29+
welcome!
30+
*/
31+
class RVariableBinAxis final {
32+
/// The (ordered) edges of the normal bins
33+
std::vector<double> fBinEdges;
34+
/// Whether underflow and overflow bins are enabled
35+
bool fEnableFlowBins;
36+
37+
public:
38+
/// Construct an axis object with variable bins.
39+
///
40+
/// \param[in] binEdges the (ordered) edges of the normal bins
41+
/// \param[in] enableFlowBins whether to enable underflow and overflow bins
42+
RVariableBinAxis(std::vector<double> binEdges, bool enableFlowBins = true)
43+
: fBinEdges(std::move(binEdges)), fEnableFlowBins(enableFlowBins)
44+
{
45+
// FIXME: should validate that fBinEdges is sorted
46+
}
47+
48+
std::size_t GetNumNormalBins() const { return fBinEdges.size() - 1; }
49+
std::size_t GetTotalNumBins() const { return fEnableFlowBins ? fBinEdges.size() + 1 : fBinEdges.size() - 1; }
50+
const std::vector<double> &GetBinEdges() const { return fBinEdges; }
51+
bool HasFlowBins() const { return fEnableFlowBins; }
52+
53+
friend bool operator==(const RVariableBinAxis &lhs, const RVariableBinAxis &rhs)
54+
{
55+
return lhs.fBinEdges == rhs.fBinEdges && lhs.fEnableFlowBins == rhs.fEnableFlowBins;
56+
}
57+
58+
/// Compute the linarized index for a single argument.
59+
///
60+
/// The normal bins have indices \f$0\f$ to \f$fBinEdges.size() - 2\f$, the underflow bin has index
61+
/// \f$fBinEdges.size() - 1\f$, and the overflow bin has index \f$fBinEdges.size()\f$. If the argument is outside all
62+
/// bin edges and the flow bins are disabled, the return value is invalid.
63+
///
64+
/// \param[in] x the argument
65+
/// \return the linearized index that may be invalid
66+
RLinearizedIndex ComputeLinearizedIndex(double x) const
67+
{
68+
bool underflow = x < fBinEdges.front();
69+
// Put NaNs into overflow bin.
70+
bool overflow = !(x < fBinEdges.back());
71+
if (underflow) {
72+
return {fBinEdges.size() - 1, fEnableFlowBins};
73+
} else if (overflow) {
74+
return {fBinEdges.size(), fEnableFlowBins};
75+
}
76+
77+
// TODO (for later): The following can be optimized with binary search...
78+
for (std::size_t bin = 0; bin < fBinEdges.size() - 2; bin++) {
79+
if (x < fBinEdges[bin + 1]) {
80+
return {bin, true};
81+
}
82+
}
83+
std::size_t bin = fBinEdges.size() - 2;
84+
return {bin, true};
85+
}
86+
};
87+
88+
} // namespace Experimental
89+
} // namespace ROOT
90+
91+
#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.HasFlowBins());
19+
20+
axis = RVariableBinAxis(bins, /*enableFlowBins=*/false);
21+
EXPECT_EQ(axis.GetNumNormalBins(), Bins);
22+
EXPECT_EQ(axis.GetTotalNumBins(), Bins);
23+
EXPECT_FALSE(axis.HasFlowBins());
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.fIndex, Bins);
83+
EXPECT_TRUE(linIndex.fValid);
84+
linIndex = axisNoFlowBins.ComputeLinearizedIndex(underflow);
85+
EXPECT_EQ(linIndex.fIndex, Bins);
86+
EXPECT_FALSE(linIndex.fValid);
87+
}
88+
89+
for (std::size_t i = 0; i < Bins; i++) {
90+
auto linIndex = axis.ComputeLinearizedIndex(i + 0.5);
91+
EXPECT_EQ(linIndex.fIndex, i);
92+
EXPECT_TRUE(linIndex.fValid);
93+
linIndex = axisNoFlowBins.ComputeLinearizedIndex(i + 0.5);
94+
EXPECT_EQ(linIndex.fIndex, i);
95+
EXPECT_TRUE(linIndex.fValid);
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.fIndex, Bins + 1);
106+
EXPECT_TRUE(linIndex.fValid);
107+
linIndex = axisNoFlowBins.ComputeLinearizedIndex(overflow);
108+
EXPECT_EQ(linIndex.fIndex, Bins + 1);
109+
EXPECT_FALSE(linIndex.fValid);
110+
}
111+
}

0 commit comments

Comments
 (0)