Skip to content

Commit 7169c32

Browse files
committed
[hist] Implement RHistEngine::FillAtomic
1 parent 7f4af21 commit 7169c32

File tree

3 files changed

+214
-0
lines changed

3 files changed

+214
-0
lines changed

hist/histv7/inc/ROOT/RHistEngine.hxx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,74 @@ public:
326326
}
327327
}
328328

329+
/// Fill an entry into the histogram using atomic instructions.
330+
///
331+
/// \param[in] args the arguments for each axis
332+
/// \see Fill(const std::tuple<A...> &args)
333+
template <typename... A>
334+
void FillAtomic(const std::tuple<A...> &args)
335+
{
336+
// We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
337+
// be confusing for users.
338+
if (sizeof...(A) != GetNDimensions()) {
339+
throw std::invalid_argument("invalid number of arguments to Fill");
340+
}
341+
RLinearizedIndex index = fAxes.ComputeGlobalIndexImpl<sizeof...(A)>(args);
342+
if (index.fValid) {
343+
assert(index.fIndex < fBinContents.size());
344+
Internal::AtomicInc(&fBinContents[index.fIndex]);
345+
}
346+
}
347+
348+
/// Fill an entry into the histogram with a weight using atomic instructions.
349+
///
350+
/// This overload is not available for integral bin content types (see \ref SupportsWeightedFilling).
351+
///
352+
/// \param[in] args the arguments for each axis
353+
/// \param[in] weight the weight for this entry
354+
/// \see Fill(const std::tuple<A...> &args, RWeight weight)
355+
template <typename... A>
356+
void FillAtomic(const std::tuple<A...> &args, RWeight weight)
357+
{
358+
static_assert(SupportsWeightedFilling, "weighted filling is not supported for integral bin content types");
359+
360+
// We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
361+
// be confusing for users.
362+
if (sizeof...(A) != GetNDimensions()) {
363+
throw std::invalid_argument("invalid number of arguments to Fill");
364+
}
365+
RLinearizedIndex index = fAxes.ComputeGlobalIndexImpl<sizeof...(A)>(args);
366+
if (index.fValid) {
367+
assert(index.fIndex < fBinContents.size());
368+
Internal::AtomicAdd(&fBinContents[index.fIndex], weight.fValue);
369+
}
370+
}
371+
372+
/// Fill an entry into the histogram using atomic instructions.
373+
///
374+
/// \param[in] args the arguments for each axis
375+
/// \see Fill(const A &...args)
376+
template <typename... A>
377+
void FillAtomic(const A &...args)
378+
{
379+
auto t = std::forward_as_tuple(args...);
380+
if constexpr (std::is_same_v<typename Internal::LastType<A...>::type, RWeight>) {
381+
static_assert(SupportsWeightedFilling, "weighted filling is not supported for integral bin content types");
382+
static constexpr std::size_t N = sizeof...(A) - 1;
383+
if (N != fAxes.GetNDimensions()) {
384+
throw std::invalid_argument("invalid number of arguments to Fill");
385+
}
386+
RWeight weight = std::get<N>(t);
387+
RLinearizedIndex index = fAxes.ComputeGlobalIndexImpl<N>(t);
388+
if (index.fValid) {
389+
assert(index.fIndex < fBinContents.size());
390+
Internal::AtomicAdd(&fBinContents[index.fIndex], weight.fValue);
391+
}
392+
} else {
393+
FillAtomic(t);
394+
}
395+
}
396+
329397
/// %ROOT Streamer function to throw when trying to store an object of this class.
330398
void Streamer(TBuffer &) { throw std::runtime_error("unable to store RHistEngine"); }
331399
};

hist/histv7/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ HIST_ADD_GTEST(hist_atomic hist_atomic.cxx)
22
HIST_ADD_GTEST(hist_axes hist_axes.cxx)
33
HIST_ADD_GTEST(hist_categorical hist_categorical.cxx)
44
HIST_ADD_GTEST(hist_engine hist_engine.cxx)
5+
HIST_ADD_GTEST(hist_engine_atomic hist_engine_atomic.cxx)
56
HIST_ADD_GTEST(hist_hist hist_hist.cxx)
67
HIST_ADD_GTEST(hist_index hist_index.cxx)
78
HIST_ADD_GTEST(hist_regular hist_regular.cxx)
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#include "hist_test.hxx"
2+
3+
TEST(RHistEngine, FillAtomic)
4+
{
5+
static constexpr std::size_t Bins = 20;
6+
const RRegularAxis axis(Bins, {0, Bins});
7+
RHistEngine<int> engine({axis});
8+
9+
engine.FillAtomic(-100);
10+
for (std::size_t i = 0; i < Bins; i++) {
11+
engine.FillAtomic(i);
12+
}
13+
engine.FillAtomic(100);
14+
15+
EXPECT_EQ(engine.GetBinContent(RBinIndex::Underflow()), 1);
16+
for (auto index : axis.GetNormalRange()) {
17+
EXPECT_EQ(engine.GetBinContent(index), 1);
18+
}
19+
EXPECT_EQ(engine.GetBinContent(RBinIndex::Overflow()), 1);
20+
21+
// Instantiate further bin content types to make sure they work.
22+
RHistEngine<long> engineL({axis});
23+
engineL.FillAtomic(1);
24+
25+
RHistEngine<long long> engineLL({axis});
26+
engineLL.FillAtomic(1);
27+
28+
RHistEngine<float> engineF({axis});
29+
engineF.FillAtomic(1);
30+
31+
RHistEngine<double> engineD({axis});
32+
engineD.FillAtomic(1);
33+
}
34+
35+
TEST(RHistEngine, FillAtomicTuple)
36+
{
37+
static constexpr std::size_t Bins = 20;
38+
const RRegularAxis axis(Bins, {0, Bins});
39+
RHistEngine<int> engine({axis});
40+
41+
engine.FillAtomic(std::make_tuple(-100));
42+
for (std::size_t i = 0; i < Bins; i++) {
43+
engine.FillAtomic(std::make_tuple(i));
44+
}
45+
engine.FillAtomic(std::make_tuple(100));
46+
47+
EXPECT_EQ(engine.GetBinContent(RBinIndex::Underflow()), 1);
48+
for (auto index : axis.GetNormalRange()) {
49+
EXPECT_EQ(engine.GetBinContent(index), 1);
50+
}
51+
EXPECT_EQ(engine.GetBinContent(RBinIndex::Overflow()), 1);
52+
}
53+
54+
TEST(RHistEngine, FillInvalidNumberOfArguments)
55+
{
56+
static constexpr std::size_t Bins = 20;
57+
const RRegularAxis axis(Bins, {0, Bins});
58+
RHistEngine<int> engine1({axis});
59+
ASSERT_EQ(engine1.GetNDimensions(), 1);
60+
RHistEngine<int> engine2({axis, axis});
61+
ASSERT_EQ(engine2.GetNDimensions(), 2);
62+
63+
EXPECT_NO_THROW(engine1.FillAtomic(1));
64+
EXPECT_THROW(engine1.FillAtomic(1, 2), std::invalid_argument);
65+
66+
EXPECT_THROW(engine2.FillAtomic(1), std::invalid_argument);
67+
EXPECT_NO_THROW(engine2.FillAtomic(1, 2));
68+
EXPECT_THROW(engine2.FillAtomic(1, 2, 3), std::invalid_argument);
69+
}
70+
71+
TEST(RHistEngine, FillAtomicWeight)
72+
{
73+
static constexpr std::size_t Bins = 20;
74+
const RRegularAxis axis(Bins, {0, Bins});
75+
RHistEngine<float> engine({axis});
76+
77+
engine.FillAtomic(-100, RWeight(0.25));
78+
for (std::size_t i = 0; i < Bins; i++) {
79+
engine.FillAtomic(i, RWeight(0.1 + i * 0.03));
80+
}
81+
engine.FillAtomic(100, RWeight(0.75));
82+
83+
EXPECT_FLOAT_EQ(engine.GetBinContent(RBinIndex::Underflow()), 0.25);
84+
for (auto index : axis.GetNormalRange()) {
85+
EXPECT_FLOAT_EQ(engine.GetBinContent(index), 0.1 + index.GetIndex() * 0.03);
86+
}
87+
EXPECT_EQ(engine.GetBinContent(RBinIndex::Overflow()), 0.75);
88+
89+
// Instantiate further bin content types to make sure they work.
90+
RHistEngine<double> engineD({axis});
91+
engineD.FillAtomic(1, RWeight(0.8));
92+
}
93+
94+
TEST(RHistEngine, FillAtomicTupleWeight)
95+
{
96+
static constexpr std::size_t Bins = 20;
97+
const RRegularAxis axis(Bins, {0, Bins});
98+
RHistEngine<float> engine({axis});
99+
100+
engine.FillAtomic(std::make_tuple(-100), RWeight(0.25));
101+
for (std::size_t i = 0; i < Bins; i++) {
102+
engine.FillAtomic(std::make_tuple(i), RWeight(0.1 + i * 0.03));
103+
}
104+
engine.FillAtomic(std::make_tuple(100), RWeight(0.75));
105+
106+
EXPECT_FLOAT_EQ(engine.GetBinContent(RBinIndex::Underflow()), 0.25);
107+
for (auto index : axis.GetNormalRange()) {
108+
EXPECT_FLOAT_EQ(engine.GetBinContent(index), 0.1 + index.GetIndex() * 0.03);
109+
}
110+
EXPECT_EQ(engine.GetBinContent(RBinIndex::Overflow()), 0.75);
111+
}
112+
113+
TEST(RHistEngine, FillWeightInvalidNumberOfArguments)
114+
{
115+
static constexpr std::size_t Bins = 20;
116+
const RRegularAxis axis(Bins, {0, Bins});
117+
RHistEngine<float> engine1({axis});
118+
ASSERT_EQ(engine1.GetNDimensions(), 1);
119+
RHistEngine<float> engine2({axis, axis});
120+
ASSERT_EQ(engine2.GetNDimensions(), 2);
121+
122+
EXPECT_NO_THROW(engine1.FillAtomic(1, RWeight(1)));
123+
EXPECT_THROW(engine1.FillAtomic(1, 2, RWeight(1)), std::invalid_argument);
124+
125+
EXPECT_THROW(engine2.FillAtomic(1, RWeight(1)), std::invalid_argument);
126+
EXPECT_NO_THROW(engine2.FillAtomic(1, 2, RWeight(1)));
127+
EXPECT_THROW(engine2.FillAtomic(1, 2, 3, RWeight(1)), std::invalid_argument);
128+
}
129+
130+
TEST(RHistEngine, FillTupleWeightInvalidNumberOfArguments)
131+
{
132+
static constexpr std::size_t Bins = 20;
133+
const RRegularAxis axis(Bins, {0, Bins});
134+
RHistEngine<float> engine1({axis});
135+
ASSERT_EQ(engine1.GetNDimensions(), 1);
136+
RHistEngine<float> engine2({axis, axis});
137+
ASSERT_EQ(engine2.GetNDimensions(), 2);
138+
139+
EXPECT_NO_THROW(engine1.FillAtomic(std::make_tuple(1), RWeight(1)));
140+
EXPECT_THROW(engine1.FillAtomic(std::make_tuple(1, 2), RWeight(1)), std::invalid_argument);
141+
142+
EXPECT_THROW(engine2.FillAtomic(std::make_tuple(1), RWeight(1)), std::invalid_argument);
143+
EXPECT_NO_THROW(engine2.FillAtomic(std::make_tuple(1, 2), RWeight(1)));
144+
EXPECT_THROW(engine2.FillAtomic(std::make_tuple(1, 2, 3), RWeight(1)), std::invalid_argument);
145+
}

0 commit comments

Comments
 (0)