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