Back to home page

EIC code displayed by LXR

 
 

    


Warning, file /include/root/ROOT/RDF/ActionHelpers.hxx was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /**
0002  \file ROOT/RDF/ActionHelpers.hxx
0003  \ingroup dataframe
0004  \author Enrico Guiraud, CERN
0005  \author Danilo Piparo, CERN
0006  \date 2016-12
0007  \author Vincenzo Eduardo Padulano
0008  \date 2020-06
0009 */
0010 
0011 /*************************************************************************
0012  * Copyright (C) 1995-2020, Rene Brun and Fons Rademakers.               *
0013  * All rights reserved.                                                  *
0014  *                                                                       *
0015  * For the licensing terms see $ROOTSYS/LICENSE.                         *
0016  * For the list of contributors see $ROOTSYS/README/CREDITS.             *
0017  *************************************************************************/
0018 
0019 #ifndef ROOT_RDFOPERATIONS
0020 #define ROOT_RDFOPERATIONS
0021 
0022 #include "ROOT/RVec.hxx"
0023 #include "ROOT/RDF/Utils.hxx"
0024 #include "ROOT/TypeTraits.hxx"
0025 #include "ROOT/RDF/RDisplay.hxx"
0026 #include "RtypesCore.h"
0027 #include "TH1.h"
0028 #include "TH3.h"
0029 #include "TGraph.h"
0030 #include "TGraphAsymmErrors.h"
0031 #include "TObject.h"
0032 #include "ROOT/RDF/RActionImpl.hxx"
0033 #include "ROOT/RDF/RMergeableValue.hxx"
0034 
0035 #include <algorithm>
0036 #include <array>
0037 #include <limits>
0038 #include <memory>
0039 #include <mutex>
0040 #include <stdexcept>
0041 #include <string>
0042 #include <string_view>
0043 #include <type_traits>
0044 #include <utility> // std::index_sequence
0045 #include <vector>
0046 #include <numeric> // std::accumulate in MeanHelper
0047 
0048 class TCollection;
0049 class TStatistic;
0050 class TTreeReader;
0051 namespace ROOT::RDF {
0052 class RCutFlowReport;
0053 } // namespace ROOT::RDF
0054 
0055 /// \cond HIDDEN_SYMBOLS
0056 
0057 namespace ROOT {
0058 namespace Internal {
0059 namespace RDF {
0060 using namespace ROOT::TypeTraits;
0061 using namespace ROOT::VecOps;
0062 using namespace ROOT::RDF;
0063 using namespace ROOT::Detail::RDF;
0064 
0065 using Hist_t = ::TH1D;
0066 
0067 /// The container type for each thread's partial result in an action helper
0068 // We have to avoid to instantiate std::vector<bool> as that makes it impossible to return a reference to one of
0069 // the thread-local results. In addition, a common definition for the type of the container makes it easy to swap
0070 // the type of the underlying container if e.g. we see problems with false sharing of the thread-local results..
0071 template <typename T>
0072 using Results = std::conditional_t<std::is_same<T, bool>::value, std::deque<T>, std::vector<T>>;
0073 
0074 template <typename F>
0075 class R__CLING_PTRCHECK(off) ForeachSlotHelper : public RActionImpl<ForeachSlotHelper<F>> {
0076    F fCallable;
0077 
0078 public:
0079    using ColumnTypes_t = RemoveFirstParameter_t<typename CallableTraits<F>::arg_types>;
0080    ForeachSlotHelper(F &&f) : fCallable(f) {}
0081    ForeachSlotHelper(ForeachSlotHelper &&) = default;
0082    ForeachSlotHelper(const ForeachSlotHelper &) = delete;
0083 
0084    void InitTask(TTreeReader *, unsigned int) {}
0085 
0086    template <typename... Args>
0087    void Exec(unsigned int slot, Args &&... args)
0088    {
0089       // check that the decayed types of Args are the same as the branch types
0090       static_assert(std::is_same<TypeList<std::decay_t<Args>...>, ColumnTypes_t>::value, "");
0091       fCallable(slot, std::forward<Args>(args)...);
0092    }
0093 
0094    void Initialize() { /* noop */}
0095 
0096    void Finalize() { /* noop */}
0097 
0098    std::string GetActionName() { return "ForeachSlot"; }
0099 };
0100 
0101 class R__CLING_PTRCHECK(off) CountHelper : public RActionImpl<CountHelper> {
0102    std::shared_ptr<ULong64_t> fResultCount;
0103    Results<ULong64_t> fCounts;
0104 
0105 public:
0106    using ColumnTypes_t = TypeList<>;
0107    CountHelper(const std::shared_ptr<ULong64_t> &resultCount, const unsigned int nSlots);
0108    CountHelper(CountHelper &&) = default;
0109    CountHelper(const CountHelper &) = delete;
0110    void InitTask(TTreeReader *, unsigned int) {}
0111    void Exec(unsigned int slot);
0112    void Initialize() { /* noop */}
0113    void Finalize();
0114 
0115    // Helper functions for RMergeableValue
0116    std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
0117    {
0118       return std::make_unique<RMergeableCount>(*fResultCount);
0119    }
0120 
0121    ULong64_t &PartialUpdate(unsigned int slot);
0122 
0123    std::string GetActionName() { return "Count"; }
0124 
0125    CountHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
0126    {
0127       auto &result = *static_cast<std::shared_ptr<ULong64_t> *>(newResult);
0128       return CountHelper(result, fCounts.size());
0129    }
0130 };
0131 
0132 template <typename RNode_t>
0133 class R__CLING_PTRCHECK(off) ReportHelper : public RActionImpl<ReportHelper<RNode_t>> {
0134    std::shared_ptr<RCutFlowReport> fReport;
0135    /// Non-owning pointer, never null. As usual, the node is owned by its children nodes (and therefore indirectly by
0136    /// the RAction corresponding to this action helper).
0137    RNode_t *fNode;
0138    bool fReturnEmptyReport;
0139 
0140 public:
0141    using ColumnTypes_t = TypeList<>;
0142    ReportHelper(const std::shared_ptr<RCutFlowReport> &report, RNode_t *node, bool emptyRep)
0143       : fReport(report), fNode(node), fReturnEmptyReport(emptyRep){};
0144    ReportHelper(ReportHelper &&) = default;
0145    ReportHelper(const ReportHelper &) = delete;
0146    void InitTask(TTreeReader *, unsigned int) {}
0147    void Exec(unsigned int /* slot */) {}
0148    void Initialize() { /* noop */}
0149    void Finalize()
0150    {
0151       if (!fReturnEmptyReport)
0152          fNode->Report(*fReport);
0153    }
0154 
0155    std::string GetActionName() { return "Report"; }
0156 
0157    ReportHelper MakeNew(void *newResult, std::string_view variation = "nominal")
0158    {
0159       auto &&result = *static_cast<std::shared_ptr<RCutFlowReport> *>(newResult);
0160       return ReportHelper{result,
0161                           std::static_pointer_cast<RNode_t>(fNode->GetVariedFilter(std::string(variation))).get(),
0162                           fReturnEmptyReport};
0163    }
0164 };
0165 
0166 /// This helper fills TH1Ds for which no axes were specified by buffering the fill values to pick good axes limits.
0167 ///
0168 /// TH1Ds have an automatic mechanism to pick good limits based on the first N entries they were filled with, but
0169 /// that does not work in multi-thread event loops as it might yield histograms with incompatible binning in each
0170 /// thread, making it impossible to merge the per-thread results.
0171 /// Instead, this helper delays the decision on the axes limits until all threads have done processing, synchronizing
0172 /// the decision on the limits as part of the merge operation.
0173 class R__CLING_PTRCHECK(off) BufferedFillHelper : public RActionImpl<BufferedFillHelper> {
0174    // this sets a total initial size of 16 MB for the buffers (can increase)
0175    static constexpr unsigned int fgTotalBufSize = 2097152;
0176    using BufEl_t = double;
0177    using Buf_t = std::vector<BufEl_t>;
0178 
0179    std::vector<Buf_t> fBuffers;
0180    std::vector<Buf_t> fWBuffers;
0181    std::shared_ptr<Hist_t> fResultHist;
0182    unsigned int fNSlots;
0183    unsigned int fBufSize;
0184    /// Histograms containing "snapshots" of partial results. Non-null only if a registered callback requires it.
0185    Results<std::unique_ptr<Hist_t>> fPartialHists;
0186    Buf_t fMin;
0187    Buf_t fMax;
0188 
0189    void UpdateMinMax(unsigned int slot, double v);
0190 
0191 public:
0192    BufferedFillHelper(const std::shared_ptr<Hist_t> &h, const unsigned int nSlots);
0193    BufferedFillHelper(BufferedFillHelper &&) = default;
0194    BufferedFillHelper(const BufferedFillHelper &) = delete;
0195    void InitTask(TTreeReader *, unsigned int) {}
0196    void Exec(unsigned int slot, double v);
0197    void Exec(unsigned int slot, double v, double w);
0198 
0199    template <typename T, std::enable_if_t<IsDataContainer<T>::value, int> = 0>
0200    void Exec(unsigned int slot, const T &vs)
0201    {
0202       auto &thisBuf = fBuffers[slot];
0203       // range-based for results in warnings on some compilers due to vector<bool>'s custom reference type
0204       for (auto v = vs.begin(); v != vs.end(); ++v) {
0205          UpdateMinMax(slot, *v);
0206          thisBuf.emplace_back(*v); // TODO: Can be optimised in case T == BufEl_t
0207       }
0208    }
0209 
0210    template <typename T, typename W, std::enable_if_t<IsDataContainer<T>::value && IsDataContainer<W>::value, int> = 0>
0211    void Exec(unsigned int slot, const T &vs, const W &ws)
0212    {
0213       auto &thisBuf = fBuffers[slot];
0214 
0215       for (auto &v : vs) {
0216          UpdateMinMax(slot, v);
0217          thisBuf.emplace_back(v);
0218       }
0219 
0220       auto &thisWBuf = fWBuffers[slot];
0221       for (auto &w : ws) {
0222          thisWBuf.emplace_back(w); // TODO: Can be optimised in case T == BufEl_t
0223       }
0224    }
0225 
0226    template <typename T, typename W, std::enable_if_t<IsDataContainer<T>::value && !IsDataContainer<W>::value, int> = 0>
0227    void Exec(unsigned int slot, const T &vs, const W w)
0228    {
0229       auto &thisBuf = fBuffers[slot];
0230       for (auto &v : vs) {
0231          UpdateMinMax(slot, v);
0232          thisBuf.emplace_back(v); // TODO: Can be optimised in case T == BufEl_t
0233       }
0234 
0235       auto &thisWBuf = fWBuffers[slot];
0236       thisWBuf.insert(thisWBuf.end(), vs.size(), w);
0237    }
0238 
0239    template <typename T, typename W, std::enable_if_t<IsDataContainer<W>::value && !IsDataContainer<T>::value, int> = 0>
0240    void Exec(unsigned int slot, const T v, const W &ws)
0241    {
0242       UpdateMinMax(slot, v);
0243       auto &thisBuf = fBuffers[slot];
0244       thisBuf.insert(thisBuf.end(), ws.size(), v);
0245 
0246       auto &thisWBuf = fWBuffers[slot];
0247       thisWBuf.insert(thisWBuf.end(), ws.begin(), ws.end());
0248    }
0249 
0250    Hist_t &PartialUpdate(unsigned int);
0251 
0252    void Initialize() { /* noop */}
0253 
0254    void Finalize();
0255 
0256    // Helper functions for RMergeableValue
0257    std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
0258    {
0259       return std::make_unique<RMergeableFill<Hist_t>>(*fResultHist);
0260    }
0261 
0262    std::string GetActionName()
0263    {
0264       return std::string(fResultHist->IsA()->GetName()) + "\\n" + std::string(fResultHist->GetName());
0265    }
0266 
0267    BufferedFillHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
0268    {
0269       auto &result = *static_cast<std::shared_ptr<Hist_t> *>(newResult);
0270       result->Reset();
0271       result->SetDirectory(nullptr);
0272       return BufferedFillHelper(result, fNSlots);
0273    }
0274 };
0275 
0276 // class which wraps a pointer and implements a no-op increment operator
0277 template <typename T>
0278 class ScalarConstIterator {
0279    const T *obj_;
0280 
0281 public:
0282    using iterator_category = std::forward_iterator_tag;
0283    using difference_type = std::ptrdiff_t;
0284    using value_type = T;
0285    using pointer = T *;
0286    using reference = T &;
0287    ScalarConstIterator(const T *obj) : obj_(obj) {}
0288    const T &operator*() const { return *obj_; }
0289    ScalarConstIterator<T> &operator++() { return *this; }
0290 };
0291 
0292 // return unchanged value for scalar
0293 template <typename T>
0294 auto MakeBegin(const T &val)
0295 {
0296    if constexpr (IsDataContainer<T>::value) {
0297       return std::begin(val);
0298    } else {
0299       return ScalarConstIterator<T>(&val);
0300    }
0301 }
0302 
0303 // return container size for containers, and 1 for scalars
0304 template <typename T>
0305 std::size_t GetSize(const T &val)
0306 {
0307    if constexpr (IsDataContainer<T>::value) {
0308       return std::size(val);
0309    } else {
0310       return 1;
0311    }
0312 }
0313 
0314 // Helpers for dealing with histograms and similar:
0315 template <typename H, typename = decltype(std::declval<H>().Reset())>
0316 void ResetIfPossible(H *h)
0317 {
0318    h->Reset();
0319 }
0320 
0321 void ResetIfPossible(TStatistic *h);
0322 void ResetIfPossible(...);
0323 
0324 void UnsetDirectoryIfPossible(TH1 *h);
0325 void UnsetDirectoryIfPossible(...);
0326 
0327 /// The generic Fill helper: it calls Fill on per-thread objects and then Merge to produce a final result.
0328 /// For one-dimensional histograms, if no axes are specified, RDataFrame uses BufferedFillHelper instead.
0329 template <typename HIST = Hist_t>
0330 class R__CLING_PTRCHECK(off) FillHelper : public RActionImpl<FillHelper<HIST>> {
0331    std::vector<HIST *> fObjects;
0332 
0333    // Merge overload for types with Merge(TCollection*), like TH1s
0334    template <typename H, typename = std::enable_if_t<std::is_base_of<TObject, H>::value, int>>
0335    auto Merge(std::vector<H *> &objs, int /*toincreaseoverloadpriority*/)
0336       -> decltype(objs[0]->Merge((TCollection *)nullptr), void())
0337    {
0338       TList l;
0339       for (auto it = ++objs.begin(); it != objs.end(); ++it)
0340          l.Add(*it);
0341       objs[0]->Merge(&l);
0342    }
0343 
0344    // Merge overload for types with Merge(const std::vector&)
0345    template <typename H>
0346    auto Merge(std::vector<H *> &objs, double /*toloweroverloadpriority*/)
0347       -> decltype(objs[0]->Merge(std::vector<HIST *>{}), void())
0348    {
0349       objs[0]->Merge({++objs.begin(), objs.end()});
0350    }
0351 
0352    // Merge overload to error out in case no valid HIST::Merge method was detected
0353    template <typename T>
0354    void Merge(T, ...)
0355    {
0356       static_assert(sizeof(T) < 0,
0357                     "The type passed to Fill does not provide a Merge(TCollection*) or Merge(const std::vector&) method.");
0358    }
0359 
0360    template <std::size_t ColIdx, typename End_t, typename... Its>
0361    void ExecLoop(unsigned int slot, End_t end, Its... its)
0362    {
0363       for (auto *thisSlotH = fObjects[slot]; GetNthElement<ColIdx>(its...) != end; (std::advance(its, 1), ...)) {
0364          thisSlotH->Fill(*its...);
0365       }
0366    }
0367 
0368 public:
0369    FillHelper(FillHelper &&) = default;
0370    FillHelper(const FillHelper &) = delete;
0371 
0372    FillHelper(const std::shared_ptr<HIST> &h, const unsigned int nSlots) : fObjects(nSlots, nullptr)
0373    {
0374       fObjects[0] = h.get();
0375       // Initialize all other slots
0376       for (unsigned int i = 1; i < nSlots; ++i) {
0377          fObjects[i] = new HIST(*fObjects[0]);
0378          UnsetDirectoryIfPossible(fObjects[i]);
0379       }
0380    }
0381 
0382    void InitTask(TTreeReader *, unsigned int) {}
0383 
0384    // no container arguments
0385    template <typename... ValTypes, std::enable_if_t<!Disjunction<IsDataContainer<ValTypes>...>::value, int> = 0>
0386    auto Exec(unsigned int slot, const ValTypes &...x) -> decltype(fObjects[slot]->Fill(x...), void())
0387    {
0388       fObjects[slot]->Fill(x...);
0389    }
0390 
0391    // at least one container argument
0392    template <typename... Xs, std::enable_if_t<Disjunction<IsDataContainer<Xs>...>::value, int> = 0>
0393    auto Exec(unsigned int slot, const Xs &...xs) -> decltype(fObjects[slot]->Fill(*MakeBegin(xs)...), void())
0394    {
0395       // array of bools keeping track of which inputs are containers
0396       constexpr std::array<bool, sizeof...(Xs)> isContainer{IsDataContainer<Xs>::value...};
0397 
0398       // index of the first container input
0399       constexpr std::size_t colidx = FindIdxTrue(isContainer);
0400       // if this happens, there is a bug in the implementation
0401       static_assert(colidx < sizeof...(Xs), "Error: index of collection-type argument not found.");
0402 
0403       // get the end iterator to the first container
0404       auto const xrefend = std::end(GetNthElement<colidx>(xs...));
0405 
0406       // array of container sizes (1 for scalars)
0407       std::array<std::size_t, sizeof...(xs)> sizes = {{GetSize(xs)...}};
0408 
0409       for (std::size_t i = 0; i < sizeof...(xs); ++i) {
0410          if (isContainer[i] && sizes[i] != sizes[colidx]) {
0411             throw std::runtime_error("Cannot fill histogram with values in containers of different sizes.");
0412          }
0413       }
0414 
0415       ExecLoop<colidx>(slot, xrefend, MakeBegin(xs)...);
0416    }
0417 
0418    template <typename T = HIST>
0419    void Exec(...)
0420    {
0421       static_assert(sizeof(T) < 0,
0422                     "When filling an object with RDataFrame (e.g. via a Fill action) the number or types of the "
0423                     "columns passed did not match the signature of the object's `Fill` method.");
0424    }
0425 
0426    void Initialize() { /* noop */}
0427 
0428    void Finalize()
0429    {
0430       if (fObjects.size() == 1)
0431          return;
0432 
0433       Merge(fObjects, /*toselectcorrectoverload=*/0);
0434 
0435       // delete the copies we created for the slots other than the first
0436       for (auto it = ++fObjects.begin(); it != fObjects.end(); ++it)
0437          delete *it;
0438    }
0439 
0440    HIST &PartialUpdate(unsigned int slot) { return *fObjects[slot]; }
0441 
0442    // Helper functions for RMergeableValue
0443    std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
0444    {
0445       return std::make_unique<RMergeableFill<HIST>>(*fObjects[0]);
0446    }
0447 
0448    // if the fObjects vector type is derived from TObject, return the name of the object
0449    template <typename T = HIST, std::enable_if_t<std::is_base_of<TObject, T>::value, int> = 0>
0450    std::string GetActionName()
0451    {
0452       return std::string(fObjects[0]->IsA()->GetName()) + "\\n" + std::string(fObjects[0]->GetName());
0453    }
0454 
0455    // if fObjects is not derived from TObject, indicate it is some other object
0456    template <typename T = HIST, std::enable_if_t<!std::is_base_of<TObject, T>::value, int> = 0>
0457    std::string GetActionName()
0458    {
0459       return "Fill custom object";
0460    }
0461 
0462    template <typename H = HIST>
0463    FillHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
0464    {
0465       auto &result = *static_cast<std::shared_ptr<H> *>(newResult);
0466       ResetIfPossible(result.get());
0467       UnsetDirectoryIfPossible(result.get());
0468       return FillHelper(result, fObjects.size());
0469    }
0470 };
0471 
0472 class R__CLING_PTRCHECK(off) FillTGraphHelper : public ROOT::Detail::RDF::RActionImpl<FillTGraphHelper> {
0473 public:
0474    using Result_t = ::TGraph;
0475 
0476 private:
0477    std::vector<::TGraph *> fGraphs;
0478 
0479 public:
0480    FillTGraphHelper(FillTGraphHelper &&) = default;
0481    FillTGraphHelper(const FillTGraphHelper &) = delete;
0482 
0483    FillTGraphHelper(const std::shared_ptr<::TGraph> &g, const unsigned int nSlots) : fGraphs(nSlots, nullptr)
0484    {
0485       fGraphs[0] = g.get();
0486       // Initialize all other slots
0487       for (unsigned int i = 1; i < nSlots; ++i) {
0488          fGraphs[i] = new TGraph(*fGraphs[0]);
0489       }
0490    }
0491 
0492    void Initialize() {}
0493    void InitTask(TTreeReader *, unsigned int) {}
0494 
0495    // case: both types are container types
0496    template <typename X0, typename X1,
0497              std::enable_if_t<IsDataContainer<X0>::value && IsDataContainer<X1>::value, int> = 0>
0498    void Exec(unsigned int slot, const X0 &x0s, const X1 &x1s)
0499    {
0500       if (x0s.size() != x1s.size()) {
0501          throw std::runtime_error("Cannot fill Graph with values in containers of different sizes.");
0502       }
0503       auto *thisSlotG = fGraphs[slot];
0504       auto x0sIt = std::begin(x0s);
0505       const auto x0sEnd = std::end(x0s);
0506       auto x1sIt = std::begin(x1s);
0507       for (; x0sIt != x0sEnd; x0sIt++, x1sIt++) {
0508          thisSlotG->SetPoint(thisSlotG->GetN(), *x0sIt, *x1sIt);
0509       }
0510    }
0511 
0512    // case: both types are non-container types, e.g. scalars
0513    template <typename X0, typename X1,
0514              std::enable_if_t<!IsDataContainer<X0>::value && !IsDataContainer<X1>::value, int> = 0>
0515    void Exec(unsigned int slot, X0 x0, X1 x1)
0516    {
0517       auto thisSlotG = fGraphs[slot];
0518       thisSlotG->SetPoint(thisSlotG->GetN(), x0, x1);
0519    }
0520 
0521    // case: types are combination of containers and non-containers
0522    // this is not supported, error out
0523    template <typename X0, typename X1, typename... ExtraArgsToLowerPriority>
0524    void Exec(unsigned int, X0, X1, ExtraArgsToLowerPriority...)
0525    {
0526       throw std::runtime_error("Graph was applied to a mix of scalar values and collections. This is not supported.");
0527    }
0528 
0529    void Finalize()
0530    {
0531       const auto nSlots = fGraphs.size();
0532       auto resGraph = fGraphs[0];
0533       TList l;
0534       l.SetOwner(); // The list will free the memory associated to its elements upon destruction
0535       for (unsigned int slot = 1; slot < nSlots; ++slot) {
0536          l.Add(fGraphs[slot]);
0537       }
0538       resGraph->Merge(&l);
0539    }
0540 
0541    // Helper functions for RMergeableValue
0542    std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
0543    {
0544       return std::make_unique<RMergeableFill<Result_t>>(*fGraphs[0]);
0545    }
0546 
0547    std::string GetActionName() { return "Graph"; }
0548 
0549    Result_t &PartialUpdate(unsigned int slot) { return *fGraphs[slot]; }
0550 
0551    FillTGraphHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
0552    {
0553       auto &result = *static_cast<std::shared_ptr<TGraph> *>(newResult);
0554       result->Set(0);
0555       return FillTGraphHelper(result, fGraphs.size());
0556    }
0557 };
0558 
0559 class R__CLING_PTRCHECK(off) FillTGraphAsymmErrorsHelper
0560    : public ROOT::Detail::RDF::RActionImpl<FillTGraphAsymmErrorsHelper> {
0561 public:
0562    using Result_t = ::TGraphAsymmErrors;
0563 
0564 private:
0565    std::vector<::TGraphAsymmErrors *> fGraphAsymmErrors;
0566 
0567 public:
0568    FillTGraphAsymmErrorsHelper(FillTGraphAsymmErrorsHelper &&) = default;
0569    FillTGraphAsymmErrorsHelper(const FillTGraphAsymmErrorsHelper &) = delete;
0570 
0571    FillTGraphAsymmErrorsHelper(const std::shared_ptr<::TGraphAsymmErrors> &g, const unsigned int nSlots)
0572       : fGraphAsymmErrors(nSlots, nullptr)
0573    {
0574       fGraphAsymmErrors[0] = g.get();
0575       // Initialize all other slots
0576       for (unsigned int i = 1; i < nSlots; ++i) {
0577          fGraphAsymmErrors[i] = new TGraphAsymmErrors(*fGraphAsymmErrors[0]);
0578       }
0579    }
0580 
0581    void Initialize() {}
0582    void InitTask(TTreeReader *, unsigned int) {}
0583 
0584    // case: all types are container types
0585    template <
0586       typename X, typename Y, typename EXL, typename EXH, typename EYL, typename EYH,
0587       std::enable_if_t<IsDataContainer<X>::value && IsDataContainer<Y>::value && IsDataContainer<EXL>::value &&
0588                           IsDataContainer<EXH>::value && IsDataContainer<EYL>::value && IsDataContainer<EYH>::value,
0589                        int> = 0>
0590    void
0591    Exec(unsigned int slot, const X &xs, const Y &ys, const EXL &exls, const EXH &exhs, const EYL &eyls, const EYH &eyhs)
0592    {
0593       if ((xs.size() != ys.size()) || (xs.size() != exls.size()) || (xs.size() != exhs.size()) ||
0594           (xs.size() != eyls.size()) || (xs.size() != eyhs.size())) {
0595          throw std::runtime_error("Cannot fill GraphAsymmErrors with values in containers of different sizes.");
0596       }
0597       auto *thisSlotG = fGraphAsymmErrors[slot];
0598       auto xsIt = std::begin(xs);
0599       auto ysIt = std::begin(ys);
0600       auto exlsIt = std::begin(exls);
0601       auto exhsIt = std::begin(exhs);
0602       auto eylsIt = std::begin(eyls);
0603       auto eyhsIt = std::begin(eyhs);
0604       while (xsIt != std::end(xs)) {
0605          const auto n = thisSlotG->GetN(); // must use the same `n` for SetPoint and SetPointError
0606          thisSlotG->SetPoint(n, *xsIt++, *ysIt++);
0607          thisSlotG->SetPointError(n, *exlsIt++, *exhsIt++, *eylsIt++, *eyhsIt++);
0608       }
0609    }
0610 
0611    // case: all types are non-container types, e.g. scalars
0612    template <
0613       typename X, typename Y, typename EXL, typename EXH, typename EYL, typename EYH,
0614       std::enable_if_t<!IsDataContainer<X>::value && !IsDataContainer<Y>::value && !IsDataContainer<EXL>::value &&
0615                           !IsDataContainer<EXH>::value && !IsDataContainer<EYL>::value && !IsDataContainer<EYH>::value,
0616                        int> = 0>
0617    void Exec(unsigned int slot, X x, Y y, EXL exl, EXH exh, EYL eyl, EYH eyh)
0618    {
0619       auto thisSlotG = fGraphAsymmErrors[slot];
0620       const auto n = thisSlotG->GetN();
0621       thisSlotG->SetPoint(n, x, y);
0622       thisSlotG->SetPointError(n, exl, exh, eyl, eyh);
0623    }
0624 
0625    // case: types are combination of containers and non-containers
0626    // this is not supported, error out
0627    template <typename X, typename Y, typename EXL, typename EXH, typename EYL, typename EYH,
0628              typename... ExtraArgsToLowerPriority>
0629    void Exec(unsigned int, X, Y, EXL, EXH, EYL, EYH, ExtraArgsToLowerPriority...)
0630    {
0631       throw std::runtime_error(
0632          "GraphAsymmErrors was applied to a mix of scalar values and collections. This is not supported.");
0633    }
0634 
0635    void Finalize()
0636    {
0637       const auto nSlots = fGraphAsymmErrors.size();
0638       auto resGraphAsymmErrors = fGraphAsymmErrors[0];
0639       TList l;
0640       l.SetOwner(); // The list will free the memory associated to its elements upon destruction
0641       for (unsigned int slot = 1; slot < nSlots; ++slot) {
0642          l.Add(fGraphAsymmErrors[slot]);
0643       }
0644       resGraphAsymmErrors->Merge(&l);
0645    }
0646 
0647    // Helper functions for RMergeableValue
0648    std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
0649    {
0650       return std::make_unique<RMergeableFill<Result_t>>(*fGraphAsymmErrors[0]);
0651    }
0652 
0653    std::string GetActionName() { return "GraphAsymmErrors"; }
0654 
0655    Result_t &PartialUpdate(unsigned int slot) { return *fGraphAsymmErrors[slot]; }
0656 
0657    FillTGraphAsymmErrorsHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
0658    {
0659       auto &result = *static_cast<std::shared_ptr<TGraphAsymmErrors> *>(newResult);
0660       result->Set(0);
0661       return FillTGraphAsymmErrorsHelper(result, fGraphAsymmErrors.size());
0662    }
0663 };
0664 
0665 /// A FillHelper for classes supporting the FillThreadSafe function.
0666 template <typename HIST>
0667 class R__CLING_PTRCHECK(off) ThreadSafeFillHelper : public RActionImpl<ThreadSafeFillHelper<HIST>> {
0668    std::vector<std::shared_ptr<HIST>> fObjects;
0669    std::vector<std::unique_ptr<std::mutex>> fMutexPtrs;
0670 
0671    // This overload matches if the function exists:
0672    template <typename T, typename... Args>
0673    auto TryCallFillThreadSafe(T &object, std::mutex &, int /*dummy*/, Args... args)
0674       -> decltype(ROOT::Internal::FillThreadSafe(object, args...), void())
0675    {
0676       ROOT::Internal::FillThreadSafe(object, args...);
0677    }
0678    // This one has lower precedence because of the dummy argument, and uses a lock
0679    template <typename T, typename... Args>
0680    auto TryCallFillThreadSafe(T &object, std::mutex &mutex, char /*dummy*/, Args... args)
0681    {
0682       std::scoped_lock lock{mutex};
0683       object.Fill(args...);
0684    }
0685 
0686    template <std::size_t ColIdx, typename End_t, typename... Its>
0687    void ExecLoop(unsigned int slot, End_t end, Its... its)
0688    {
0689       const auto localSlot = slot % fObjects.size();
0690       for (; GetNthElement<ColIdx>(its...) != end; (std::advance(its, 1), ...)) {
0691          TryCallFillThreadSafe(*fObjects[localSlot], *fMutexPtrs[localSlot], 0, *its...);
0692       }
0693    }
0694 
0695 public:
0696    ThreadSafeFillHelper(ThreadSafeFillHelper &&) = default;
0697    ThreadSafeFillHelper(const ThreadSafeFillHelper &) = delete;
0698 
0699    ThreadSafeFillHelper(const std::shared_ptr<HIST> &h, const unsigned int nSlots)
0700    {
0701       fObjects.resize(nSlots);
0702       fObjects.front() = h;
0703 
0704       std::generate(fObjects.begin() + 1, fObjects.end(), [h]() {
0705          auto hist = std::make_shared<HIST>(*h);
0706          UnsetDirectoryIfPossible(hist.get());
0707          return hist;
0708       });
0709       fMutexPtrs.resize(nSlots);
0710       std::generate(fMutexPtrs.begin(), fMutexPtrs.end(), []() { return std::make_unique<std::mutex>(); });
0711    }
0712 
0713    void InitTask(TTreeReader *, unsigned int) {}
0714 
0715    // no container arguments
0716    template <typename... ValTypes, std::enable_if_t<!Disjunction<IsDataContainer<ValTypes>...>::value, int> = 0>
0717    void Exec(unsigned int slot, const ValTypes &...x)
0718    {
0719       const auto localSlot = slot % fObjects.size();
0720       TryCallFillThreadSafe(*fObjects[localSlot], *fMutexPtrs[localSlot], 0, x...);
0721    }
0722 
0723    // at least one container argument
0724    template <typename... Xs, std::enable_if_t<Disjunction<IsDataContainer<Xs>...>::value, int> = 0>
0725    void Exec(unsigned int slot, const Xs &...xs)
0726    {
0727       // array of bools keeping track of which inputs are containers
0728       constexpr std::array<bool, sizeof...(Xs)> isContainer{IsDataContainer<Xs>::value...};
0729 
0730       // index of the first container input
0731       constexpr std::size_t colidx = FindIdxTrue(isContainer);
0732       // if this happens, there is a bug in the implementation
0733       static_assert(colidx < sizeof...(Xs), "Error: index of collection-type argument not found.");
0734 
0735       // get the end iterator to the first container
0736       auto const xrefend = std::end(GetNthElement<colidx>(xs...));
0737 
0738       // array of container sizes (1 for scalars)
0739       std::array<std::size_t, sizeof...(xs)> sizes = {{GetSize(xs)...}};
0740 
0741       for (std::size_t i = 0; i < sizeof...(xs); ++i) {
0742          if (isContainer[i] && sizes[i] != sizes[colidx]) {
0743             throw std::runtime_error("Cannot fill histogram with values in containers of different sizes.");
0744          }
0745       }
0746 
0747       ExecLoop<colidx>(slot, xrefend, MakeBegin(xs)...);
0748    }
0749 
0750    template <typename T = HIST>
0751    void Exec(...)
0752    {
0753       static_assert(sizeof(T) < 0,
0754                     "When filling an object with RDataFrame (e.g. via a Fill action) the number or types of the "
0755                     "columns passed did not match the signature of the object's `FillThreadSafe` method.");
0756    }
0757 
0758    void Initialize() { /* noop */ }
0759 
0760    void Finalize()
0761    {
0762       if (fObjects.size() > 1) {
0763          TList list;
0764          for (auto it = fObjects.cbegin() + 1; it != fObjects.end(); ++it) {
0765             list.Add(it->get());
0766          }
0767          fObjects[0]->Merge(&list);
0768       }
0769 
0770       fObjects.resize(1);
0771       fMutexPtrs.clear();
0772    }
0773 
0774    // Helper function for RMergeableValue
0775    std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
0776    {
0777       return std::make_unique<RMergeableFill<HIST>>(*fObjects[0]);
0778    }
0779 
0780    // if the fObjects vector type is derived from TObject, return the name of the object
0781    template <typename T = HIST, std::enable_if_t<std::is_base_of<TObject, T>::value, int> = 0>
0782    std::string GetActionName()
0783    {
0784       return std::string(fObjects[0]->IsA()->GetName()) + "\\n" + std::string(fObjects[0]->GetName());
0785    }
0786 
0787    template <typename H = HIST>
0788    ThreadSafeFillHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
0789    {
0790       auto &result = *static_cast<std::shared_ptr<H> *>(newResult);
0791       ResetIfPossible(result.get());
0792       UnsetDirectoryIfPossible(result.get());
0793       return ThreadSafeFillHelper(result, fObjects.size());
0794    }
0795 };
0796 
0797 // In case of the take helper we have 4 cases:
0798 // 1. The column is not an RVec, the collection is not a vector
0799 // 2. The column is not an RVec, the collection is a vector
0800 // 3. The column is an RVec, the collection is not a vector
0801 // 4. The column is an RVec, the collection is a vector
0802 
0803 template <typename V, typename COLL>
0804 void FillColl(V&& v, COLL& c) {
0805    c.emplace_back(v);
0806 }
0807 
0808 // Use push_back for bool since some compilers do not support emplace_back.
0809 template <typename COLL>
0810 void FillColl(bool v, COLL& c) {
0811    c.push_back(v);
0812 }
0813 
0814 // Case 1.: The column is not an RVec, the collection is not a vector
0815 // No optimisations, no transformations: just copies.
0816 template <typename RealT_t, typename T, typename COLL>
0817 class R__CLING_PTRCHECK(off) TakeHelper : public RActionImpl<TakeHelper<RealT_t, T, COLL>> {
0818    Results<std::shared_ptr<COLL>> fColls;
0819 
0820 public:
0821    using ColumnTypes_t = TypeList<T>;
0822    TakeHelper(const std::shared_ptr<COLL> &resultColl, const unsigned int nSlots)
0823    {
0824       fColls.emplace_back(resultColl);
0825       for (unsigned int i = 1; i < nSlots; ++i)
0826          fColls.emplace_back(std::make_shared<COLL>());
0827    }
0828    TakeHelper(TakeHelper &&);
0829    TakeHelper(const TakeHelper &) = delete;
0830 
0831    void InitTask(TTreeReader *, unsigned int) {}
0832 
0833    void Exec(unsigned int slot, T &v) { FillColl(v, *fColls[slot]); }
0834 
0835    void Initialize() { /* noop */}
0836 
0837    void Finalize()
0838    {
0839       auto rColl = fColls[0];
0840       for (unsigned int i = 1; i < fColls.size(); ++i) {
0841          const auto &coll = fColls[i];
0842          const auto end = coll->end();
0843          // Use an explicit loop here to prevent compiler warnings introduced by
0844          // clang's range-based loop analysis and vector<bool> references.
0845          for (auto j = coll->begin(); j != end; j++) {
0846             FillColl(*j, *rColl);
0847          }
0848       }
0849    }
0850 
0851    COLL &PartialUpdate(unsigned int slot) { return *fColls[slot].get(); }
0852 
0853    std::string GetActionName() { return "Take"; }
0854 
0855    TakeHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
0856    {
0857       auto &result = *static_cast<std::shared_ptr<COLL> *>(newResult);
0858       result->clear();
0859       return TakeHelper(result, fColls.size());
0860    }
0861 };
0862 
0863 // Case 2.: The column is not an RVec, the collection is a vector
0864 // Optimisations, no transformations: just copies.
0865 template <typename RealT_t, typename T>
0866 class R__CLING_PTRCHECK(off) TakeHelper<RealT_t, T, std::vector<T>>
0867    : public RActionImpl<TakeHelper<RealT_t, T, std::vector<T>>> {
0868    Results<std::shared_ptr<std::vector<T>>> fColls;
0869 
0870 public:
0871    using ColumnTypes_t = TypeList<T>;
0872    TakeHelper(const std::shared_ptr<std::vector<T>> &resultColl, const unsigned int nSlots)
0873    {
0874       fColls.emplace_back(resultColl);
0875       for (unsigned int i = 1; i < nSlots; ++i) {
0876          auto v = std::make_shared<std::vector<T>>();
0877          v->reserve(1024);
0878          fColls.emplace_back(v);
0879       }
0880    }
0881    TakeHelper(TakeHelper &&);
0882    TakeHelper(const TakeHelper &) = delete;
0883 
0884    void InitTask(TTreeReader *, unsigned int) {}
0885 
0886    void Exec(unsigned int slot, T &v) { FillColl(v, *fColls[slot]); }
0887 
0888    void Initialize() { /* noop */}
0889 
0890    // This is optimised to treat vectors
0891    void Finalize()
0892    {
0893       ULong64_t totSize = 0;
0894       for (auto &coll : fColls)
0895          totSize += coll->size();
0896       auto rColl = fColls[0];
0897       rColl->reserve(totSize);
0898       for (unsigned int i = 1; i < fColls.size(); ++i) {
0899          auto &coll = fColls[i];
0900          rColl->insert(rColl->end(), coll->begin(), coll->end());
0901       }
0902    }
0903 
0904    std::vector<T> &PartialUpdate(unsigned int slot) { return *fColls[slot]; }
0905 
0906    std::string GetActionName() { return "Take"; }
0907 
0908    TakeHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
0909    {
0910       auto &result = *static_cast<std::shared_ptr<std::vector<T>> *>(newResult);
0911       result->clear();
0912       return TakeHelper(result, fColls.size());
0913    }
0914 };
0915 
0916 // Case 3.: The column is a RVec, the collection is not a vector
0917 // No optimisations, transformations from RVecs to vectors
0918 template <typename RealT_t, typename COLL>
0919 class R__CLING_PTRCHECK(off) TakeHelper<RealT_t, RVec<RealT_t>, COLL>
0920    : public RActionImpl<TakeHelper<RealT_t, RVec<RealT_t>, COLL>> {
0921    Results<std::shared_ptr<COLL>> fColls;
0922 
0923 public:
0924    using ColumnTypes_t = TypeList<RVec<RealT_t>>;
0925    TakeHelper(const std::shared_ptr<COLL> &resultColl, const unsigned int nSlots)
0926    {
0927       fColls.emplace_back(resultColl);
0928       for (unsigned int i = 1; i < nSlots; ++i)
0929          fColls.emplace_back(std::make_shared<COLL>());
0930    }
0931    TakeHelper(TakeHelper &&);
0932    TakeHelper(const TakeHelper &) = delete;
0933 
0934    void InitTask(TTreeReader *, unsigned int) {}
0935 
0936    void Exec(unsigned int slot, RVec<RealT_t> av) { fColls[slot]->emplace_back(av.begin(), av.end()); }
0937 
0938    void Initialize() { /* noop */}
0939 
0940    void Finalize()
0941    {
0942       auto rColl = fColls[0];
0943       for (unsigned int i = 1; i < fColls.size(); ++i) {
0944          auto &coll = fColls[i];
0945          for (auto &v : *coll) {
0946             rColl->emplace_back(v);
0947          }
0948       }
0949    }
0950 
0951    std::string GetActionName() { return "Take"; }
0952 
0953    TakeHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
0954    {
0955       auto &result = *static_cast<std::shared_ptr<COLL> *>(newResult);
0956       result->clear();
0957       return TakeHelper(result, fColls.size());
0958    }
0959 };
0960 
0961 // Case 4.: The column is an RVec, the collection is a vector
0962 // Optimisations, transformations from RVecs to vectors
0963 template <typename RealT_t>
0964 class R__CLING_PTRCHECK(off) TakeHelper<RealT_t, RVec<RealT_t>, std::vector<RealT_t>>
0965    : public RActionImpl<TakeHelper<RealT_t, RVec<RealT_t>, std::vector<RealT_t>>> {
0966 
0967    Results<std::shared_ptr<std::vector<std::vector<RealT_t>>>> fColls;
0968 
0969 public:
0970    using ColumnTypes_t = TypeList<RVec<RealT_t>>;
0971    TakeHelper(const std::shared_ptr<std::vector<std::vector<RealT_t>>> &resultColl, const unsigned int nSlots)
0972    {
0973       fColls.emplace_back(resultColl);
0974       for (unsigned int i = 1; i < nSlots; ++i) {
0975          auto v = std::make_shared<std::vector<RealT_t>>();
0976          v->reserve(1024);
0977          fColls.emplace_back(v);
0978       }
0979    }
0980    TakeHelper(TakeHelper &&);
0981    TakeHelper(const TakeHelper &) = delete;
0982 
0983    void InitTask(TTreeReader *, unsigned int) {}
0984 
0985    void Exec(unsigned int slot, RVec<RealT_t> av) { fColls[slot]->emplace_back(av.begin(), av.end()); }
0986 
0987    void Initialize() { /* noop */}
0988 
0989    // This is optimised to treat vectors
0990    void Finalize()
0991    {
0992       ULong64_t totSize = 0;
0993       for (auto &coll : fColls)
0994          totSize += coll->size();
0995       auto rColl = fColls[0];
0996       rColl->reserve(totSize);
0997       for (unsigned int i = 1; i < fColls.size(); ++i) {
0998          auto &coll = fColls[i];
0999          rColl->insert(rColl->end(), coll->begin(), coll->end());
1000       }
1001    }
1002 
1003    std::string GetActionName() { return "Take"; }
1004 
1005    TakeHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1006    {
1007       auto &result = *static_cast<typename decltype(fColls)::value_type *>(newResult);
1008       result->clear();
1009       return TakeHelper(result, fColls.size());
1010    }
1011 };
1012 
1013 // Extern templates for TakeHelper
1014 // NOTE: The move-constructor of specializations declared as extern templates
1015 // must be defined out of line, otherwise cling fails to find its symbol.
1016 template <typename RealT_t, typename T, typename COLL>
1017 TakeHelper<RealT_t, T, COLL>::TakeHelper(TakeHelper<RealT_t, T, COLL> &&) = default;
1018 template <typename RealT_t, typename T>
1019 TakeHelper<RealT_t, T, std::vector<T>>::TakeHelper(TakeHelper<RealT_t, T, std::vector<T>> &&) = default;
1020 template <typename RealT_t, typename COLL>
1021 TakeHelper<RealT_t, RVec<RealT_t>, COLL>::TakeHelper(TakeHelper<RealT_t, RVec<RealT_t>, COLL> &&) = default;
1022 template <typename RealT_t>
1023 TakeHelper<RealT_t, RVec<RealT_t>, std::vector<RealT_t>>::TakeHelper(TakeHelper<RealT_t, RVec<RealT_t>, std::vector<RealT_t>> &&) = default;
1024 
1025 // External templates are disabled for gcc5 since this version wrongly omits the C++11 ABI attribute
1026 #if __GNUC__ > 5
1027 extern template class TakeHelper<bool, bool, std::vector<bool>>;
1028 extern template class TakeHelper<unsigned int, unsigned int, std::vector<unsigned int>>;
1029 extern template class TakeHelper<unsigned long, unsigned long, std::vector<unsigned long>>;
1030 extern template class TakeHelper<unsigned long long, unsigned long long, std::vector<unsigned long long>>;
1031 extern template class TakeHelper<int, int, std::vector<int>>;
1032 extern template class TakeHelper<long, long, std::vector<long>>;
1033 extern template class TakeHelper<long long, long long, std::vector<long long>>;
1034 extern template class TakeHelper<float, float, std::vector<float>>;
1035 extern template class TakeHelper<double, double, std::vector<double>>;
1036 #endif
1037 
1038 template <typename ResultType>
1039 class R__CLING_PTRCHECK(off) MinHelper : public RActionImpl<MinHelper<ResultType>> {
1040    std::shared_ptr<ResultType> fResultMin;
1041    Results<ResultType> fMins;
1042 
1043 public:
1044    MinHelper(MinHelper &&) = default;
1045    MinHelper(const std::shared_ptr<ResultType> &minVPtr, const unsigned int nSlots)
1046       : fResultMin(minVPtr), fMins(nSlots, std::numeric_limits<ResultType>::max())
1047    {
1048    }
1049 
1050    void Exec(unsigned int slot, ResultType v) { fMins[slot] = std::min(v, fMins[slot]); }
1051 
1052    void InitTask(TTreeReader *, unsigned int) {}
1053 
1054    template <typename T, std::enable_if_t<IsDataContainer<T>::value, int> = 0>
1055    void Exec(unsigned int slot, const T &vs)
1056    {
1057       for (auto &&v : vs)
1058          fMins[slot] = std::min(static_cast<ResultType>(v), fMins[slot]);
1059    }
1060 
1061    void Initialize() { /* noop */}
1062 
1063    void Finalize()
1064    {
1065       *fResultMin = std::numeric_limits<ResultType>::max();
1066       for (auto &m : fMins)
1067          *fResultMin = std::min(m, *fResultMin);
1068    }
1069 
1070    // Helper functions for RMergeableValue
1071    std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1072    {
1073       return std::make_unique<RMergeableMin<ResultType>>(*fResultMin);
1074    }
1075 
1076    ResultType &PartialUpdate(unsigned int slot) { return fMins[slot]; }
1077 
1078    std::string GetActionName() { return "Min"; }
1079 
1080    MinHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1081    {
1082       auto &result = *static_cast<std::shared_ptr<ResultType> *>(newResult);
1083       return MinHelper(result, fMins.size());
1084    }
1085 };
1086 
1087 template <typename ResultType>
1088 class R__CLING_PTRCHECK(off) MaxHelper : public RActionImpl<MaxHelper<ResultType>> {
1089    std::shared_ptr<ResultType> fResultMax;
1090    Results<ResultType> fMaxs;
1091 
1092 public:
1093    MaxHelper(MaxHelper &&) = default;
1094    MaxHelper(const MaxHelper &) = delete;
1095    MaxHelper(const std::shared_ptr<ResultType> &maxVPtr, const unsigned int nSlots)
1096       : fResultMax(maxVPtr), fMaxs(nSlots, std::numeric_limits<ResultType>::lowest())
1097    {
1098    }
1099 
1100    void InitTask(TTreeReader *, unsigned int) {}
1101    void Exec(unsigned int slot, ResultType v) { fMaxs[slot] = std::max(v, fMaxs[slot]); }
1102 
1103    template <typename T, std::enable_if_t<IsDataContainer<T>::value, int> = 0>
1104    void Exec(unsigned int slot, const T &vs)
1105    {
1106       for (auto &&v : vs)
1107          fMaxs[slot] = std::max(static_cast<ResultType>(v), fMaxs[slot]);
1108    }
1109 
1110    void Initialize() { /* noop */}
1111 
1112    void Finalize()
1113    {
1114       *fResultMax = std::numeric_limits<ResultType>::lowest();
1115       for (auto &m : fMaxs) {
1116          *fResultMax = std::max(m, *fResultMax);
1117       }
1118    }
1119 
1120    // Helper functions for RMergeableValue
1121    std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1122    {
1123       return std::make_unique<RMergeableMax<ResultType>>(*fResultMax);
1124    }
1125 
1126    ResultType &PartialUpdate(unsigned int slot) { return fMaxs[slot]; }
1127 
1128    std::string GetActionName() { return "Max"; }
1129 
1130    MaxHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1131    {
1132       auto &result = *static_cast<std::shared_ptr<ResultType> *>(newResult);
1133       return MaxHelper(result, fMaxs.size());
1134    }
1135 };
1136 
1137 template <typename ResultType>
1138 class R__CLING_PTRCHECK(off) SumHelper : public RActionImpl<SumHelper<ResultType>> {
1139    std::shared_ptr<ResultType> fResultSum;
1140    Results<ResultType> fSums;
1141    Results<ResultType> fCompensations;
1142 
1143    /// Evaluate neutral element for this type and the sum operation.
1144    /// This is assumed to be any_value - any_value if operator- is defined
1145    /// for the type, otherwise a default-constructed ResultType{} is used.
1146    template <typename T = ResultType>
1147    auto NeutralElement(const T &v, int /*overloadresolver*/) -> decltype(v - v)
1148    {
1149       return v - v;
1150    }
1151 
1152    template <typename T = ResultType, typename Dummy = int>
1153    ResultType NeutralElement(const T &, Dummy) // this overload has lower priority thanks to the template arg
1154    {
1155       return ResultType{};
1156    }
1157 
1158 public:
1159    SumHelper(SumHelper &&) = default;
1160    SumHelper(const SumHelper &) = delete;
1161    SumHelper(const std::shared_ptr<ResultType> &sumVPtr, const unsigned int nSlots)
1162       : fResultSum(sumVPtr), fSums(nSlots, NeutralElement(*sumVPtr, -1)),
1163         fCompensations(nSlots, NeutralElement(*sumVPtr, -1))
1164    {
1165    }
1166    void InitTask(TTreeReader *, unsigned int) {}
1167 
1168    void Exec(unsigned int slot, ResultType x)
1169    {
1170       // Kahan Sum:
1171       ResultType y = x - fCompensations[slot];
1172       ResultType t = fSums[slot] + y;
1173       fCompensations[slot] = (t - fSums[slot]) - y;
1174       fSums[slot] = t;
1175    }
1176 
1177    template <typename T, std::enable_if_t<IsDataContainer<T>::value, int> = 0>
1178    void Exec(unsigned int slot, const T &vs)
1179    {
1180       for (auto &&v : vs) {
1181          Exec(slot, v);
1182       }
1183    }
1184 
1185    void Initialize() { /* noop */}
1186 
1187    void Finalize()
1188    {
1189       ResultType sum(NeutralElement(ResultType{}, -1));
1190       ResultType compensation(NeutralElement(ResultType{}, -1));
1191       ResultType y(NeutralElement(ResultType{}, -1));
1192       ResultType t(NeutralElement(ResultType{}, -1));
1193       for (auto &m : fSums) {
1194          // Kahan Sum:
1195          y = m - compensation;
1196          t = sum + y;
1197          compensation = (t - sum) - y;
1198          sum = t;
1199       }
1200       *fResultSum += sum;
1201    }
1202 
1203    // Helper functions for RMergeableValue
1204    std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1205    {
1206       return std::make_unique<RMergeableSum<ResultType>>(*fResultSum);
1207    }
1208 
1209    ResultType &PartialUpdate(unsigned int slot) { return fSums[slot]; }
1210 
1211    std::string GetActionName() { return "Sum"; }
1212 
1213    SumHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1214    {
1215       auto &result = *static_cast<std::shared_ptr<ResultType> *>(newResult);
1216       *result = NeutralElement(*result, -1);
1217       return SumHelper(result, fSums.size());
1218    }
1219 };
1220 
1221 class R__CLING_PTRCHECK(off) MeanHelper : public RActionImpl<MeanHelper> {
1222    std::shared_ptr<double> fResultMean;
1223    std::vector<ULong64_t> fCounts;
1224    std::vector<double> fSums;
1225    std::vector<double> fPartialMeans;
1226    std::vector<double> fCompensations;
1227 
1228 public:
1229    MeanHelper(const std::shared_ptr<double> &meanVPtr, const unsigned int nSlots);
1230    MeanHelper(MeanHelper &&) = default;
1231    MeanHelper(const MeanHelper &) = delete;
1232    void InitTask(TTreeReader *, unsigned int) {}
1233    void Exec(unsigned int slot, double v);
1234 
1235    template <typename T, std::enable_if_t<IsDataContainer<T>::value, int> = 0>
1236    void Exec(unsigned int slot, const T &vs)
1237    {
1238       for (auto &&v : vs) {
1239 
1240          fCounts[slot]++;
1241          // Kahan Sum:
1242          double y = v - fCompensations[slot];
1243          double t = fSums[slot] + y;
1244          fCompensations[slot] = (t - fSums[slot]) - y;
1245          fSums[slot] = t;
1246       }
1247    }
1248 
1249    void Initialize() { /* noop */}
1250 
1251    void Finalize();
1252 
1253    // Helper functions for RMergeableValue
1254    std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1255    {
1256       const ULong64_t counts = std::accumulate(fCounts.begin(), fCounts.end(), 0ull);
1257       return std::make_unique<RMergeableMean>(*fResultMean, counts);
1258    }
1259 
1260    double &PartialUpdate(unsigned int slot);
1261 
1262    std::string GetActionName() { return "Mean"; }
1263 
1264    MeanHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1265    {
1266       auto &result = *static_cast<std::shared_ptr<double> *>(newResult);
1267       return MeanHelper(result, fSums.size());
1268    }
1269 };
1270 
1271 class R__CLING_PTRCHECK(off) StdDevHelper : public RActionImpl<StdDevHelper> {
1272    // Number of subsets of data
1273    unsigned int fNSlots;
1274    std::shared_ptr<double> fResultStdDev;
1275    // Number of element for each slot
1276    std::vector<ULong64_t> fCounts;
1277    // Mean of each slot
1278    std::vector<double> fMeans;
1279    // Squared distance from the mean
1280    std::vector<double> fDistancesfromMean;
1281 
1282 public:
1283    StdDevHelper(const std::shared_ptr<double> &meanVPtr, const unsigned int nSlots);
1284    StdDevHelper(StdDevHelper &&) = default;
1285    StdDevHelper(const StdDevHelper &) = delete;
1286    void InitTask(TTreeReader *, unsigned int) {}
1287    void Exec(unsigned int slot, double v);
1288 
1289    template <typename T, std::enable_if_t<IsDataContainer<T>::value, int> = 0>
1290    void Exec(unsigned int slot, const T &vs)
1291    {
1292       for (auto &&v : vs) {
1293          Exec(slot, v);
1294       }
1295    }
1296 
1297    void Initialize() { /* noop */}
1298 
1299    void Finalize();
1300 
1301    // Helper functions for RMergeableValue
1302    std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1303    {
1304       const ULong64_t counts = std::accumulate(fCounts.begin(), fCounts.end(), 0ull);
1305       const Double_t mean =
1306          std::inner_product(fMeans.begin(), fMeans.end(), fCounts.begin(), 0.) / static_cast<Double_t>(counts);
1307       return std::make_unique<RMergeableStdDev>(*fResultStdDev, counts, mean);
1308    }
1309 
1310    std::string GetActionName() { return "StdDev"; }
1311 
1312    StdDevHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1313    {
1314       auto &result = *static_cast<std::shared_ptr<double> *>(newResult);
1315       return StdDevHelper(result, fCounts.size());
1316    }
1317 };
1318 
1319 template <typename PrevNodeType>
1320 class R__CLING_PTRCHECK(off) DisplayHelper : public RActionImpl<DisplayHelper<PrevNodeType>> {
1321 private:
1322    using Display_t = ROOT::RDF::RDisplay;
1323    std::shared_ptr<Display_t> fDisplayerHelper;
1324    std::shared_ptr<PrevNodeType> fPrevNode;
1325    size_t fEntriesToProcess;
1326 
1327 public:
1328    DisplayHelper(size_t nRows, const std::shared_ptr<Display_t> &d, const std::shared_ptr<PrevNodeType> &prevNode)
1329       : fDisplayerHelper(d), fPrevNode(prevNode), fEntriesToProcess(nRows)
1330    {
1331    }
1332    DisplayHelper(DisplayHelper &&) = default;
1333    DisplayHelper(const DisplayHelper &) = delete;
1334    void InitTask(TTreeReader *, unsigned int) {}
1335 
1336    template <typename... Columns>
1337    void Exec(unsigned int, Columns &... columns)
1338    {
1339       if (fEntriesToProcess == 0)
1340          return;
1341 
1342       fDisplayerHelper->AddRow(columns...);
1343       --fEntriesToProcess;
1344 
1345       if (fEntriesToProcess == 0) {
1346          // No more entries to process. Send a one-time signal that this node
1347          // of the graph is done. It is important that the 'StopProcessing'
1348          // method is only called once from this helper, otherwise it would seem
1349          // like more than one operation has completed its work.
1350          fPrevNode->StopProcessing();
1351       }
1352    }
1353 
1354    void Initialize() {}
1355 
1356    void Finalize() {}
1357 
1358    std::string GetActionName() { return "Display"; }
1359 };
1360 
1361 template <typename Acc, typename Merge, typename R, typename T, typename U,
1362           bool MustCopyAssign = std::is_same<R, U>::value>
1363 class R__CLING_PTRCHECK(off) AggregateHelper
1364    : public RActionImpl<AggregateHelper<Acc, Merge, R, T, U, MustCopyAssign>> {
1365    Acc fAggregate;
1366    Merge fMerge;
1367    std::shared_ptr<U> fResult;
1368    Results<U> fAggregators;
1369 
1370 public:
1371    using ColumnTypes_t = TypeList<T>;
1372 
1373    AggregateHelper(Acc &&f, Merge &&m, const std::shared_ptr<U> &result, const unsigned int nSlots)
1374       : fAggregate(std::move(f)), fMerge(std::move(m)), fResult(result), fAggregators(nSlots, *result)
1375    {
1376    }
1377 
1378    AggregateHelper(Acc &f, Merge &m, const std::shared_ptr<U> &result, const unsigned int nSlots)
1379       : fAggregate(f), fMerge(m), fResult(result), fAggregators(nSlots, *result)
1380    {
1381    }
1382 
1383    AggregateHelper(AggregateHelper &&) = default;
1384    AggregateHelper(const AggregateHelper &) = delete;
1385 
1386    void InitTask(TTreeReader *, unsigned int) {}
1387 
1388    template <bool MustCopyAssign_ = MustCopyAssign, std::enable_if_t<MustCopyAssign_, int> = 0>
1389    void Exec(unsigned int slot, const T &value)
1390    {
1391       fAggregators[slot] = fAggregate(fAggregators[slot], value);
1392    }
1393 
1394    template <bool MustCopyAssign_ = MustCopyAssign, std::enable_if_t<!MustCopyAssign_, int> = 0>
1395    void Exec(unsigned int slot, const T &value)
1396    {
1397       fAggregate(fAggregators[slot], value);
1398    }
1399 
1400    void Initialize() { /* noop */}
1401 
1402    template <typename MergeRet = typename CallableTraits<Merge>::ret_type,
1403              bool MergeAll = std::is_same<void, MergeRet>::value>
1404    std::enable_if_t<MergeAll, void> Finalize()
1405    {
1406       fMerge(fAggregators);
1407       *fResult = fAggregators[0];
1408    }
1409 
1410    template <typename MergeRet = typename CallableTraits<Merge>::ret_type,
1411              bool MergeTwoByTwo = std::is_same<U, MergeRet>::value>
1412    std::enable_if_t<MergeTwoByTwo, void> Finalize(...) // ... needed to let compiler distinguish overloads
1413    {
1414       for (const auto &acc : fAggregators)
1415          *fResult = fMerge(*fResult, acc);
1416    }
1417 
1418    U &PartialUpdate(unsigned int slot) { return fAggregators[slot]; }
1419 
1420    std::string GetActionName() { return "Aggregate"; }
1421 
1422    AggregateHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1423    {
1424       auto &result = *static_cast<std::shared_ptr<U> *>(newResult);
1425       return AggregateHelper(fAggregate, fMerge, result, fAggregators.size());
1426    }
1427 };
1428 
1429 } // end of NS RDF
1430 } // end of NS Internal
1431 } // end of NS ROOT
1432 
1433 /// \endcond
1434 
1435 #endif