Skip to content

Commit a075a71

Browse files
committed
[hist] Support filling with user-defined weights
Only in RHistEngine because RHist needs conversion to double for global statistics (RHistStats).
1 parent 48ff83f commit a075a71

File tree

2 files changed

+113
-1
lines changed

2 files changed

+113
-1
lines changed

hist/histv7/inc/ROOT/RHistEngine.hxx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,36 @@ public:
307307
}
308308
}
309309

310+
/// Fill an entry into the histogram with a user-defined weight.
311+
///
312+
/// This overload is only available for user-defined bin content types.
313+
///
314+
/// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
315+
/// discarded.
316+
///
317+
/// Throws an exception if the number of arguments does not match the axis configuration, or if an argument cannot be
318+
/// converted for the axis type at run-time.
319+
///
320+
/// \param[in] args the arguments for each axis
321+
/// \param[in] weight the weight for this entry
322+
template <typename... A, typename W>
323+
void Fill(const std::tuple<A...> &args, const W &weight)
324+
{
325+
static_assert(std::is_class_v<BinContentType>,
326+
"user-defined weight types are only supported for user-defined bin content types");
327+
328+
// We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
329+
// be confusing for users.
330+
if (sizeof...(A) != GetNDimensions()) {
331+
throw std::invalid_argument("invalid number of arguments to Fill");
332+
}
333+
RLinearizedIndex index = fAxes.ComputeGlobalIndexImpl<sizeof...(A)>(args);
334+
if (index.fValid) {
335+
assert(index.fIndex < fBinContents.size());
336+
fBinContents[index.fIndex] += weight;
337+
}
338+
}
339+
310340
/// Fill an entry into the histogram.
311341
///
312342
/// \code
@@ -395,6 +425,31 @@ public:
395425
}
396426
}
397427

428+
/// Fill an entry into the histogram with a user-defined weight using atomic instructions.
429+
///
430+
/// This overload is only available for user-defined bin content types.
431+
///
432+
/// \param[in] args the arguments for each axis
433+
/// \param[in] weight the weight for this entry
434+
/// \see Fill(const std::tuple<A...> &args, const W &weight)
435+
template <typename... A, typename W>
436+
void FillAtomic(const std::tuple<A...> &args, const W &weight)
437+
{
438+
static_assert(std::is_class_v<BinContentType>,
439+
"user-defined weight types are only supported for user-defined bin content types");
440+
441+
// We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
442+
// be confusing for users.
443+
if (sizeof...(A) != GetNDimensions()) {
444+
throw std::invalid_argument("invalid number of arguments to Fill");
445+
}
446+
RLinearizedIndex index = fAxes.ComputeGlobalIndexImpl<sizeof...(A)>(args);
447+
if (index.fValid) {
448+
assert(index.fIndex < fBinContents.size());
449+
Internal::AtomicAdd(&fBinContents[index.fIndex], weight);
450+
}
451+
}
452+
398453
/// Fill an entry into the histogram using atomic instructions.
399454
///
400455
/// \param[in] args the arguments for each axis

hist/histv7/test/hist_user.cxx

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
#include <type_traits>
44

5+
// User-defined weight type with a single double, but can be much more complicated
6+
struct UserWeight {
7+
double fWeight = 0;
8+
};
9+
510
// User-defined bin content type consisting of a single double, but can be much more complicated.
611
struct User {
712
double fValue = 0;
@@ -25,6 +30,12 @@ struct User {
2530
return *this;
2631
}
2732

33+
User &operator+=(const UserWeight &w)
34+
{
35+
fValue += w.fWeight;
36+
return *this;
37+
}
38+
2839
User &operator+=(const User &rhs)
2940
{
3041
fValue += rhs.fValue;
@@ -41,6 +52,8 @@ struct User {
4152

4253
void AtomicAdd(double w) { ROOT::Experimental::Internal::AtomicAdd(&fValue, w); }
4354

55+
void AtomicAdd(const UserWeight &w) { ROOT::Experimental::Internal::AtomicAdd(&fValue, w.fWeight); }
56+
4457
void AtomicAdd(const User &rhs) { ROOT::Experimental::Internal::AtomicAdd(&fValue, rhs.fValue); }
4558
};
4659

@@ -149,6 +162,28 @@ TEST(RHistEngineUser, FillWeight)
149162
EXPECT_EQ(engine.GetBinContent(indices).fValue, 0.9);
150163
}
151164

165+
TEST(RHistEngineUser, FillUserWeight)
166+
{
167+
// Weighted filling with user-defined weight uses operator+=(const UserWeight &)
168+
static constexpr std::size_t Bins = 20;
169+
const RRegularAxis axis(Bins, {0, Bins});
170+
RHistEngine<User> engine({axis});
171+
172+
// Must use overload accepting std::tuple
173+
engine.Fill(std::make_tuple(9.5), UserWeight{0.9});
174+
175+
EXPECT_EQ(engine.GetBinContent(RBinIndex(9)).fValue, 0.9);
176+
}
177+
178+
TEST(RHistEngineUser, FillUserWeightInvalidNumberOfArguments)
179+
{
180+
static constexpr std::size_t Bins = 20;
181+
const RRegularAxis axis(Bins, {0, Bins});
182+
RHistEngine<User> engine({axis});
183+
184+
EXPECT_THROW(engine.Fill(std::make_tuple(8.5, 9.5), UserWeight{0.9}), std::invalid_argument);
185+
}
186+
152187
TEST(RHistEngineUser, FillAtomic)
153188
{
154189
// Unweighted filling with atomic instructions uses AtomicInc
@@ -166,7 +201,7 @@ TEST(RHistEngineUser, FillAtomic)
166201

167202
TEST(RHistEngineUser, FillAtomicWeight)
168203
{
169-
// Weighted filling with atomic instructions uses AtomicAdd
204+
// Weighted filling with atomic instructions uses AtomicAdd(double)
170205
static constexpr std::size_t Bins = 20;
171206
const RRegularAxis axis(Bins, {0, Bins});
172207
RHistEngine<User> engine({axis});
@@ -179,6 +214,28 @@ TEST(RHistEngineUser, FillAtomicWeight)
179214
EXPECT_EQ(engine.GetBinContent(indices).fValue, 0.9);
180215
}
181216

217+
TEST(RHistEngineUser, FillAtomicUserWeight)
218+
{
219+
// Weighted filling with user-defined weight and atomic instructions uses AtomicAdd(const UserWeight &)
220+
static constexpr std::size_t Bins = 20;
221+
const RRegularAxis axis(Bins, {0, Bins});
222+
RHistEngine<User> engine({axis});
223+
224+
// Must use overload accepting std::tuple
225+
engine.FillAtomic(std::make_tuple(9.5), UserWeight{0.9});
226+
227+
EXPECT_EQ(engine.GetBinContent(RBinIndex(9)).fValue, 0.9);
228+
}
229+
230+
TEST(RHistEngineUser, FillAtomicUserWeightInvalidNumberOfArguments)
231+
{
232+
static constexpr std::size_t Bins = 20;
233+
const RRegularAxis axis(Bins, {0, Bins});
234+
RHistEngine<User> engine({axis});
235+
236+
EXPECT_THROW(engine.FillAtomic(std::make_tuple(8.5, 9.5), UserWeight{0.9}), std::invalid_argument);
237+
}
238+
182239
TEST(RHistEngineUser, Scale)
183240
{
184241
// Scaling uses operator+=(double)

0 commit comments

Comments
 (0)