Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions hist/histv7/inc/ROOT/RHistEngine.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,36 @@ public:
}
}

/// Fill an entry into the histogram with a user-defined weight.
///
/// This overload is only available for user-defined bin content types.
///
/// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
/// discarded.
///
/// Throws an exception if the number of arguments does not match the axis configuration, or if an argument cannot be
/// converted for the axis type at run-time.
///
/// \param[in] args the arguments for each axis
/// \param[in] weight the weight for this entry
template <typename... A, typename W>
void Fill(const std::tuple<A...> &args, const W &weight)
{
static_assert(std::is_class_v<BinContentType>,
"user-defined weight types are only supported for user-defined bin content types");

// We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
// be confusing for users.
if (sizeof...(A) != GetNDimensions()) {
throw std::invalid_argument("invalid number of arguments to Fill");
}
RLinearizedIndex index = fAxes.ComputeGlobalIndexImpl<sizeof...(A)>(args);
if (index.fValid) {
assert(index.fIndex < fBinContents.size());
fBinContents[index.fIndex] += weight;
}
}

/// Fill an entry into the histogram.
///
/// \code
Expand Down Expand Up @@ -395,6 +425,31 @@ public:
}
}

/// Fill an entry into the histogram with a user-defined weight using atomic instructions.
///
/// This overload is only available for user-defined bin content types.
///
/// \param[in] args the arguments for each axis
/// \param[in] weight the weight for this entry
/// \see Fill(const std::tuple<A...> &args, const W &weight)
template <typename... A, typename W>
void FillAtomic(const std::tuple<A...> &args, const W &weight)
{
static_assert(std::is_class_v<BinContentType>,
"user-defined weight types are only supported for user-defined bin content types");

// We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
// be confusing for users.
if (sizeof...(A) != GetNDimensions()) {
throw std::invalid_argument("invalid number of arguments to Fill");
}
RLinearizedIndex index = fAxes.ComputeGlobalIndexImpl<sizeof...(A)>(args);
if (index.fValid) {
assert(index.fIndex < fBinContents.size());
Internal::AtomicAdd(&fBinContents[index.fIndex], weight);
}
}

/// Fill an entry into the histogram using atomic instructions.
///
/// \param[in] args the arguments for each axis
Expand Down
59 changes: 58 additions & 1 deletion hist/histv7/test/hist_user.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

#include <type_traits>

// User-defined weight type with a single double, but can be much more complicated
struct UserWeight {
double fWeight = 0;
};

// User-defined bin content type consisting of a single double, but can be much more complicated.
struct User {
double fValue = 0;
Expand All @@ -25,6 +30,12 @@ struct User {
return *this;
}

User &operator+=(const UserWeight &w)
{
fValue += w.fWeight;
return *this;
}

User &operator+=(const User &rhs)
{
fValue += rhs.fValue;
Expand All @@ -41,6 +52,8 @@ struct User {

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

void AtomicAdd(const UserWeight &w) { ROOT::Experimental::Internal::AtomicAdd(&fValue, w.fWeight); }

void AtomicAdd(const User &rhs) { ROOT::Experimental::Internal::AtomicAdd(&fValue, rhs.fValue); }
};

Expand Down Expand Up @@ -149,6 +162,28 @@ TEST(RHistEngineUser, FillWeight)
EXPECT_EQ(engine.GetBinContent(indices).fValue, 0.9);
}

TEST(RHistEngineUser, FillUserWeight)
{
// Weighted filling with user-defined weight uses operator+=(const UserWeight &)
static constexpr std::size_t Bins = 20;
const RRegularAxis axis(Bins, {0, Bins});
RHistEngine<User> engine({axis});

// Must use overload accepting std::tuple
engine.Fill(std::make_tuple(9.5), UserWeight{0.9});

EXPECT_EQ(engine.GetBinContent(RBinIndex(9)).fValue, 0.9);
}

TEST(RHistEngineUser, FillUserWeightInvalidNumberOfArguments)
{
static constexpr std::size_t Bins = 20;
const RRegularAxis axis(Bins, {0, Bins});
RHistEngine<User> engine({axis});

EXPECT_THROW(engine.Fill(std::make_tuple(8.5, 9.5), UserWeight{0.9}), std::invalid_argument);
}

TEST(RHistEngineUser, FillAtomic)
{
// Unweighted filling with atomic instructions uses AtomicInc
Expand All @@ -166,7 +201,7 @@ TEST(RHistEngineUser, FillAtomic)

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

TEST(RHistEngineUser, FillAtomicUserWeight)
{
// Weighted filling with user-defined weight and atomic instructions uses AtomicAdd(const UserWeight &)
static constexpr std::size_t Bins = 20;
const RRegularAxis axis(Bins, {0, Bins});
RHistEngine<User> engine({axis});

// Must use overload accepting std::tuple
engine.FillAtomic(std::make_tuple(9.5), UserWeight{0.9});

EXPECT_EQ(engine.GetBinContent(RBinIndex(9)).fValue, 0.9);
}

TEST(RHistEngineUser, FillAtomicUserWeightInvalidNumberOfArguments)
{
static constexpr std::size_t Bins = 20;
const RRegularAxis axis(Bins, {0, Bins});
RHistEngine<User> engine({axis});

EXPECT_THROW(engine.FillAtomic(std::make_tuple(8.5, 9.5), UserWeight{0.9}), std::invalid_argument);
}

TEST(RHistEngineUser, Scale)
{
// Scaling uses operator+=(double)
Expand Down
Loading