Skip to content
Open
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
14 changes: 14 additions & 0 deletions tree/dataframe/inc/ROOT/RDF/ActionHelpers.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,13 @@ public:
}
}

RHistFillHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
{
auto &result = *static_cast<std::shared_ptr<Result_t> *>(newResult);
result->Clear();
return RHistFillHelper(result, fContexts.size());
}

std::string GetActionName() { return "Hist"; }
};

Expand Down Expand Up @@ -588,6 +595,13 @@ public:

void Finalize() {}

RHistEngineFillHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
{
auto &result = *static_cast<std::shared_ptr<Result_t> *>(newResult);
result->Clear();
return RHistEngineFillHelper(result);
}

std::string GetActionName() { return "Hist"; }
};
#endif
Expand Down
36 changes: 33 additions & 3 deletions tree/dataframe/inc/ROOT/RDFHelpers.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,36 @@ namespace Experimental {
// N*#input_cols column readers
//
// ...and each RFilter and RDefine knows for what universe it needs to construct column readers ("nominal" by default).

namespace Internal {
/// @brief Helper function to add a copy of an object to a vector of shared_ptrs, used for variations.
/// Default implementation uses copy constructor, which should work for most objects types since they are copied for each slot.
/// @tparam T An object that is used as result of a RDataFrame action, e.g. a histogram
/// @param vec The vector of shared_ptrs to which a copy of obj will be added
/// @param obj The object to be copied and added to vec
template <typename T>
void addCopyForVariations(std::vector<std::shared_ptr<T>> &vec, const T & obj)
{
vec.emplace_back(std::make_shared<T>(obj));
}

/// @brief Specialization of addCopyForVariations for ROOT::Experimental::RHist objects, which are not copyable but clonable
template<typename B>
void addCopyForVariations(std::vector<std::shared_ptr<ROOT::Experimental::RHist<B>>> &vec, const ROOT::Experimental::RHist<B> & obj)
{
// Note that Clone() returns a value and not a pointer, so we call 'new' and pass that value to the RHist move constructor

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm misunderstanding the comment but I don't see new being called. I believe this is a valid use of the Clone method.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, when I wrote the comment I still had emplace_back(new T{x.Clone()}, then later I replaced the new with a make_shared and didn't update the comment.

vec.emplace_back(std::make_shared<ROOT::Experimental::RHist<B>>(obj.Clone()));
}

/// @brief Specialization of addCopyForVariations for ROOT::Experimental::RHistEngine objects, which are not copyable but clonable
template<typename B>
void addCopyForVariations(std::vector<std::shared_ptr<ROOT::Experimental::RHistEngine<B>>> &vec, const ROOT::Experimental::RHistEngine<B> & obj)
{
// Note that Clone() returns a value and not a pointer, so we call 'new' and pass that value to the RHist move constructor
vec.emplace_back(std::make_shared<ROOT::Experimental::RHistEngine<B>>(obj.Clone()));
}
}

template <typename T>
RResultMap<T> VariationsFor(RResultPtr<T> resPtr)
{
Expand All @@ -243,9 +273,9 @@ RResultMap<T> VariationsFor(RResultPtr<T> resPtr)
// clone the result once for each variation
variedResults.reserve(nVariations);
for (auto i = 0u; i < nVariations; ++i){
// implicitly assuming that T is copiable: this should be the case
// for all result types in use, as they are copied for each slot
variedResults.emplace_back(new T{*resPtr.fObjPtr});

// make a copy of the result object for this variation
Internal::addCopyForVariations(variedResults, *resPtr.fObjPtr);

// Check if the result's type T inherits from TNamed
if constexpr (std::is_base_of<TNamed, T>::value) {
Expand Down
72 changes: 72 additions & 0 deletions tree/dataframe/test/dataframe_hist.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,78 @@ TEST_P(RDFHist, WeightInvalidNumberOfArgumentsJit)
EXPECT_THROW(dfXW.Hist(engine, {"x", "x"}, "w"), std::invalid_argument);
}

TEST_P(RDFHist, Variations)
{
RDataFrame df(10);
auto dfX = df.Define("x", [](ULong64_t e) -> Float_t { return e + 5.5f; }, {"rdfentry_"})
.Vary("x", [](Float_t x) { return ROOT::RVecF{x - 1.0f, x + 1.0f}; }, {"x"}, 2);

const RRegularAxis axis(10, {5.0, 15.0});
auto hist = dfX.Hist</*BinContentType=*/double, Float_t>({axis}, {"x"});
auto vars = ROOT::RDF::Experimental::VariationsFor(hist);

// Check nominal
EXPECT_EQ(hist->GetNEntries(), 10);
for (auto index : axis.GetNormalRange()) {
EXPECT_EQ(hist->GetBinContent(index), 1.0);
}

// Check variations
EXPECT_EQ(vars.GetKeys().size(), 3); // nominal + 2 variations
for (const auto &key : vars.GetKeys()) {
auto &varHist = vars[key];
EXPECT_EQ(varHist.GetNEntries(), 10);
for (auto index : axis.GetNormalRange()) {
if (key == "nominal") {
EXPECT_EQ(varHist.GetBinContent(index), 1.0);
} else if (key == "x:0") {
EXPECT_EQ(varHist.GetBinContent(index), (index.GetIndex() == 9 ? 0.0 : 1.0));
} else if (key == "x:1") {
EXPECT_EQ(varHist.GetBinContent(index), (index.GetIndex() == 0 ? 0.0 : 1.0));
} else {
FAIL() << "Unexpected variation key: " << key;
}
}
}
}

TEST_P(RDFHist, VariationsEngine)
{
RDataFrame df(10);
auto dfX = df.Define("x", [](ULong64_t e) -> Float_t { return e + 5.5f; }, {"rdfentry_"})
.Vary("x", [](Float_t x) { return ROOT::RVecF{x - 1.0f, x + 1.0f}; }, {"x"}, 2);

const RRegularAxis axis(10, {5.0, 15.0});
auto hist = std::make_shared<RHistEngine<double>>(axis);
auto resPtr = dfX.Hist<Float_t>(hist, {"x"});
auto vars = ROOT::RDF::Experimental::VariationsFor(resPtr);

// Trigger the run
resPtr.GetValue();

// Check nominal
for (auto index : axis.GetNormalRange()) {
EXPECT_EQ(hist->GetBinContent(index), 1.0);
}

// Check variations
EXPECT_EQ(vars.GetKeys().size(), 3); // nominal + 2 variations
for (const auto &key : vars.GetKeys()) {
auto &varHist = vars[key];
for (auto index : axis.GetNormalRange()) {
if (key == "nominal") {
EXPECT_EQ(varHist.GetBinContent(index), 1.0);
} else if (key == "x:0") {
EXPECT_EQ(varHist.GetBinContent(index), (index.GetIndex() == 9 ? 0.0 : 1.0));
} else if (key == "x:1") {
EXPECT_EQ(varHist.GetBinContent(index), (index.GetIndex() == 0 ? 0.0 : 1.0));
} else {
FAIL() << "Unexpected variation key: " << key;
}
}
}
}

INSTANTIATE_TEST_SUITE_P(Seq, RDFHist, ::testing::Values(false));

#ifdef R__USE_IMT
Expand Down
Loading