From f529a5652ef02cf051777d5fef836180d318e4ed Mon Sep 17 00:00:00 2001 From: Massimo Tumolo <imaxoi@hotmail.it> Date: Tue, 17 Jul 2018 13:58:41 +0200 Subject: [PATCH] [DF] Introduced action for TGraph filling --- tree/dataframe/inc/ROOT/RDFActionHelpers.hxx | 63 +++++++++++++++++++ tree/dataframe/inc/ROOT/RDFInterface.hxx | 25 ++++++++ tree/dataframe/inc/ROOT/RDFInterfaceUtils.hxx | 13 ++++ tree/dataframe/test/dataframe_simple.cxx | 37 +++++++++++ tutorials/dataframe/df021_createTGraph.C | 42 +++++++++++++ 5 files changed, 180 insertions(+) create mode 100644 tutorials/dataframe/df021_createTGraph.C diff --git a/tree/dataframe/inc/ROOT/RDFActionHelpers.hxx b/tree/dataframe/inc/ROOT/RDFActionHelpers.hxx index 1c944d73016..450964a3509 100644 --- a/tree/dataframe/inc/ROOT/RDFActionHelpers.hxx +++ b/tree/dataframe/inc/ROOT/RDFActionHelpers.hxx @@ -37,6 +37,7 @@ #include "TDirectory.h" #include "TFile.h" // for SnapshotHelper #include "TH1.h" +#include "TGraph.h" #include "TLeaf.h" #include "TObjArray.h" #include "TObject.h" @@ -323,6 +324,66 @@ public: HIST &PartialUpdate(unsigned int slot) { return *fTo->GetAtSlotRaw(slot); } }; +class FillTGraphHelper : public ROOT::Detail::RDF::RActionImpl<FillTGraphHelper> { +public: + using Result_t = ::TGraph; + +private: + std::shared_ptr<::TGraph> fResultGraph; + std::unique_ptr<ROOT::TThreadedObject<::TGraph>> fTo; + +public: + FillTGraphHelper(FillTGraphHelper &&) = default; + FillTGraphHelper(const FillTGraphHelper &) = delete; + + // The last parameter is always false, as at the moment there is no way to propagate the parameter from the user to this method + FillTGraphHelper(const std::shared_ptr<::TGraph> &g, const unsigned int nSlots) + : fResultGraph(g), fTo(new ROOT::TThreadedObject<::TGraph>(*g)) + { + fTo->SetAtSlot(0, g); + // Initialise all other slots + for (unsigned int i = 1; i < nSlots; ++i) { + fTo->GetAtSlot(i); + } + } + + void Initialize() {} + void InitTask(TTreeReader *, unsigned int) {} + + template <typename X0, typename X1, + typename std::enable_if< + ROOT::TypeTraits::IsContainer<X0>::value && ROOT::TypeTraits::IsContainer<X1>::value, int>::type = 0> + void Exec(unsigned int slot, const X0 &x0s, const X1 &x1s) + { + if (x0s.size() != x1s.size()) { + throw std::runtime_error("Cannot fill Graph with values in containers of different sizes."); + } + auto thisSlotH = fTo->GetAtSlotRaw(slot); + auto x0sIt = std::begin(x0s); + const auto x0sEnd = std::end(x0s); + auto x1sIt = std::begin(x1s); + for (; x0sIt != x0sEnd; x0sIt++, x1sIt++) { + thisSlotH->SetPoint(thisSlotH->GetN(), *x0sIt, *x1sIt); + } + } + + template <typename X0, typename X1> + void Exec(unsigned int slot, X0 x0, X1 x1) + { + auto rawSlot = fTo->GetAtSlotRaw(slot); + rawSlot->SetPoint(rawSlot->GetN(), x0, x1); + } + + void Finalize() { + fTo->Merge(); + auto graph = fTo->Get(); + + *fResultGraph = *graph; + } + + ::TGraph &PartialUpdate(unsigned int slot) { return *fTo->GetAtSlotRaw(slot); } +}; + // In case of the take helper we have 4 cases: // 1. The column is not an RVec, the collection is not a vector // 2. The column is not an RVec, the collection is a vector @@ -801,6 +862,8 @@ public: } }; + + /// Helper object for a multi-thread Snapshot action template <typename... BranchTypes> class SnapshotHelperMT : public RActionImpl<SnapshotHelperMT<BranchTypes...>> { diff --git a/tree/dataframe/inc/ROOT/RDFInterface.hxx b/tree/dataframe/inc/ROOT/RDFInterface.hxx index 3e85a0163f7..d64e3f134ae 100644 --- a/tree/dataframe/inc/ROOT/RDFInterface.hxx +++ b/tree/dataframe/inc/ROOT/RDFInterface.hxx @@ -43,6 +43,7 @@ #include "TChain.h" #include "TDirectory.h" #include "TError.h" +#include "TGraph.h" // For Graph action #include "TH1.h" // For Histo actions #include "TH2.h" // For Histo actions #include "TH3.h" // For Histo actions @@ -992,6 +993,30 @@ public: return CreateAction<RDFInternal::ActionTags::Profile1D, V1, V2>(userColumns, h); } + //////////////////////////////////////////////////////////////////////////// + /// \brief Fill and return a graph (*lazy action*) + /// \tparam V1 The type of the column used to fill the x axis of the graph. + /// \tparam V2 The type of the column used to fill the y axis of the graph. + /// \param[in] v1Name The name of the column that will fill the x axis. + /// \param[in] v2Name The name of the column that will fill the y axis. + /// + /// Columns can be of a container type (e.g. std::vector<double>), in which case the graph + /// is filled with each one of the elements of the container. + /// If Multithreading is enabled, the order in which points are inserted can't be forseeen. + /// If the Graph has to be drawn, it is suggested to the user to sort it on the x before printing. + /// This action is *lazy*: upon invocation of this method the calculation is + /// booked but not executed. See RResultPtr documentation. + template <typename V1 = RDFDetail::TInferType, typename V2 = RDFDetail::TInferType> + RResultPtr<::TGraph> Graph(std::string_view v1Name = "", std::string_view v2Name = "") + { + auto graph = std::make_shared<::TGraph>(); + const std::vector<std::string_view> columnViews = {v1Name, v2Name}; + const auto userColumns = RDFInternal::AtLeastOneEmptyString(columnViews) + ? ColumnNames_t() + : ColumnNames_t(columnViews.begin(), columnViews.end()); + return CreateAction<RDFInternal::ActionTags::Graph, V1, V2>(userColumns, graph); + } + //////////////////////////////////////////////////////////////////////////// /// \brief Fill and return a one-dimensional profile (*lazy action*) /// \tparam V1 The type of the column the values of which are used to fill the profile. Inferred if not present. diff --git a/tree/dataframe/inc/ROOT/RDFInterfaceUtils.hxx b/tree/dataframe/inc/ROOT/RDFInterfaceUtils.hxx index 3be1da219e9..8a418290a29 100644 --- a/tree/dataframe/inc/ROOT/RDFInterfaceUtils.hxx +++ b/tree/dataframe/inc/ROOT/RDFInterfaceUtils.hxx @@ -74,6 +74,7 @@ namespace ActionTags { struct Histo1D{}; struct Histo2D{}; struct Histo3D{}; +struct Graph{}; struct Profile1D{}; struct Profile2D{}; struct Min{}; @@ -151,6 +152,18 @@ RActionBase *BuildAndBook(const ColumnNames_t &bl, const std::shared_ptr<::TH1D> return actionBase; } +template <typename... BranchTypes, typename PrevNodeType> +RActionBase * +BuildAndBook(const ColumnNames_t &bl, const std::shared_ptr<TGraph> &g, const unsigned int nSlots, + RLoopManager &loopManager, PrevNodeType &prevNode, ActionTags::Graph) +{ + using Helper_t = FillTGraphHelper; + using Action_t = RAction<Helper_t, PrevNodeType, TTraits::TypeList<BranchTypes...>>; + auto action = std::make_shared<Action_t>(Helper_t(g, nSlots), bl, prevNode); + loopManager.Book(action); + return action.get(); +} + // Min action template <typename BranchType, typename PrevNodeType, typename ActionResultType> RActionBase * diff --git a/tree/dataframe/test/dataframe_simple.cxx b/tree/dataframe/test/dataframe_simple.cxx index 67c1cbfb951..c7ceaa66132 100644 --- a/tree/dataframe/test/dataframe_simple.cxx +++ b/tree/dataframe/test/dataframe_simple.cxx @@ -459,6 +459,8 @@ TEST_P(RDFSimpleTests, Aggregate) EXPECT_EQ(*r2, 120); } + + TEST_P(RDFSimpleTests, AggregateGraph) { auto d = RDataFrame(20).DefineSlotEntry("x", [](unsigned int, ULong64_t e) { return static_cast<double>(e); }); @@ -487,6 +489,41 @@ TEST_P(RDFSimpleTests, AggregateGraph) } } +TEST_P(RDFSimpleTests, Graph) +{ + static const int NR_ELEMENTS = 20; + + // Define the source for the graph + std::vector<int> source(NR_ELEMENTS); + for (int i = 0; i < NR_ELEMENTS; ++i) + source[i] = i; + + // Create the graph from the Dataframe + ROOT::RDataFrame d(NR_ELEMENTS); + auto dd = d.DefineSlotEntry("x1", + [&source](unsigned int slot, ULong64_t entry) { + (void)slot; + return source[entry]; + }) + .DefineSlotEntry("x2", [&source](unsigned int slot, ULong64_t entry) { + (void)slot; + return source[entry]; + }); + + auto dfGraph = dd.Graph("x1", "x2"); + EXPECT_EQ(dfGraph->GetN(), NR_ELEMENTS); + + //To perform the test, it's easier to sort + dfGraph->Sort(); + + Double_t x, y; + for (int i = 0; i < NR_ELEMENTS; ++i) { + dfGraph->GetPoint(i, x, y); + EXPECT_EQ(i, x); + EXPECT_EQ(i, y); + } +} + class MaxSlotHelper : public ROOT::Detail::RDF::RActionImpl<MaxSlotHelper> { const std::shared_ptr<unsigned int> fMaxSlot; // final result std::vector<unsigned int> fMaxSlots; // per-thread partial results diff --git a/tutorials/dataframe/df021_createTGraph.C b/tutorials/dataframe/df021_createTGraph.C new file mode 100644 index 00000000000..830cc082b53 --- /dev/null +++ b/tutorials/dataframe/df021_createTGraph.C @@ -0,0 +1,42 @@ +/// \file +/// \ingroup tutorial_dataframe +/// \notebook +/// This tutorial shows how to fill a TGraph using the Dataframe. +/// +/// \macro_code +/// +/// \date July 2018 +/// \author Enrico Guiraud, Danilo Piparo, Massimo Tumolo + + + +void df021_createTGraph() +{ + ROOT::EnableImplicitMT(2); + + const unsigned int NR_ELEMENTS = 160; + std::vector<int> x(NR_ELEMENTS); + std::vector<int> y(NR_ELEMENTS); + + for (int i = 0; i < NR_ELEMENTS; ++i){ + y[i] = pow(i,2); + x[i] = i; + } + + ROOT::RDataFrame d(NR_ELEMENTS); + auto dd = d.DefineSlotEntry("x", + [&x](unsigned int slot, ULong64_t entry) { + (void)slot; + return x[entry]; + }) + .DefineSlotEntry("y", [&y](unsigned int slot, ULong64_t entry) { + (void)slot; + return y[entry]; + }); + + auto graph = dd.Graph("x", "y"); + + // This tutorial is ran with multithreading enabled. The order in which points are inserted is not known, so to have a meaningful representation points are sorted. + graph->Sort(); + graph->DrawClone("APL"); +} -- GitLab