Logo ROOT  
Reference Guide
Loading...
Searching...
No Matches
ActionHelpers.hxx
Go to the documentation of this file.
1/**
2 \file ROOT/RDF/ActionHelpers.hxx
3 \author Enrico Guiraud, CERN
4 \author Danilo Piparo, CERN
5 \date 2016-12
6 \author Vincenzo Eduardo Padulano
7 \date 2020-06
8*/
9
10/*************************************************************************
11 * Copyright (C) 1995-2020, Rene Brun and Fons Rademakers. *
12 * All rights reserved. *
13 * *
14 * For the licensing terms see $ROOTSYS/LICENSE. *
15 * For the list of contributors see $ROOTSYS/README/CREDITS. *
16 *************************************************************************/
17
18#ifndef ROOT_RDFOPERATIONS
19#define ROOT_RDFOPERATIONS
20
21#include "ROOT/RVec.hxx"
22#include "ROOT/RDF/Utils.hxx"
23#include "ROOT/TypeTraits.hxx"
24#include "ROOT/RDF/RDisplay.hxx"
25#include "RtypesCore.h"
26#include "TH1.h"
27#include "TH3.h"
28#include "TGraph.h"
29#include "TGraphAsymmErrors.h"
30#include "TObject.h"
33
34#include "RConfigure.h" // for R__HAS_ROOT7
35#ifdef R__HAS_ROOT7
36#include <ROOT/RHist.hxx>
38#include <ROOT/RHistEngine.hxx>
39#include <ROOT/RWeight.hxx>
40#endif
41
42#include <algorithm>
43#include <array>
44#include <limits>
45#include <memory>
46#include <mutex>
47#include <stdexcept>
48#include <string>
49#include <string_view>
50#include <tuple>
51#include <type_traits>
52#include <utility> // std::index_sequence
53#include <vector>
54#include <numeric> // std::accumulate in MeanHelper
55
56class TCollection;
57class TStatistic;
58class TTreeReader;
59namespace ROOT::RDF {
60class RCutFlowReport;
61} // namespace ROOT::RDF
62
63/// \cond HIDDEN_SYMBOLS
64
65namespace ROOT {
66namespace Internal {
67namespace RDF {
68using namespace ROOT::TypeTraits;
69using namespace ROOT::VecOps;
70using namespace ROOT::RDF;
71using namespace ROOT::Detail::RDF;
72
73using Hist_t = ::TH1D;
74
75/// The container type for each thread's partial result in an action helper
76// We have to avoid to instantiate std::vector<bool> as that makes it impossible to return a reference to one of
77// the thread-local results. In addition, a common definition for the type of the container makes it easy to swap
78// the type of the underlying container if e.g. we see problems with false sharing of the thread-local results..
79template <typename T>
80using Results = std::conditional_t<std::is_same<T, bool>::value, std::deque<T>, std::vector<T>>;
81
82template <typename F>
83class R__CLING_PTRCHECK(off) ForeachSlotHelper : public RActionImpl<ForeachSlotHelper<F>> {
84 F fCallable;
85
86public:
87 using ColumnTypes_t = RemoveFirstParameter_t<typename CallableTraits<F>::arg_types>;
88 ForeachSlotHelper(F &&f) : fCallable(f) {}
89 ForeachSlotHelper(ForeachSlotHelper &&) = default;
90 ForeachSlotHelper(const ForeachSlotHelper &) = delete;
91
92 void InitTask(TTreeReader *, unsigned int) {}
93
94 template <typename... Args>
95 void Exec(unsigned int slot, Args &&... args)
96 {
97 // check that the decayed types of Args are the same as the branch types
98 static_assert(std::is_same<TypeList<std::decay_t<Args>...>, ColumnTypes_t>::value, "");
99 fCallable(slot, std::forward<Args>(args)...);
100 }
101
102 void Initialize() { /* noop */}
103
104 void Finalize() { /* noop */}
105
106 std::string GetActionName() { return "ForeachSlot"; }
107};
108
109class R__CLING_PTRCHECK(off) CountHelper : public RActionImpl<CountHelper> {
110 std::shared_ptr<ULong64_t> fResultCount;
111 Results<ULong64_t> fCounts;
112
113public:
114 using ColumnTypes_t = TypeList<>;
115 CountHelper(const std::shared_ptr<ULong64_t> &resultCount, const unsigned int nSlots);
116 CountHelper(CountHelper &&) = default;
117 CountHelper(const CountHelper &) = delete;
118 void InitTask(TTreeReader *, unsigned int) {}
119 void Exec(unsigned int slot);
120 void Initialize() { /* noop */}
121 void Finalize();
122
123 // Helper functions for RMergeableValue
124 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
125 {
126 return std::make_unique<RMergeableCount>(*fResultCount);
127 }
128
129 ULong64_t &PartialUpdate(unsigned int slot);
130
131 std::string GetActionName() { return "Count"; }
132
133 CountHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
134 {
135 auto &result = *static_cast<std::shared_ptr<ULong64_t> *>(newResult);
136 return CountHelper(result, fCounts.size());
137 }
138};
139
140template <typename RNode_t>
141class R__CLING_PTRCHECK(off) ReportHelper : public RActionImpl<ReportHelper<RNode_t>> {
142 std::shared_ptr<RCutFlowReport> fReport;
143 /// Non-owning pointer, never null. As usual, the node is owned by its children nodes (and therefore indirectly by
144 /// the RAction corresponding to this action helper).
145 RNode_t *fNode;
146 bool fReturnEmptyReport;
147
148public:
149 using ColumnTypes_t = TypeList<>;
150 ReportHelper(const std::shared_ptr<RCutFlowReport> &report, RNode_t *node, bool emptyRep)
151 : fReport(report), fNode(node), fReturnEmptyReport(emptyRep){};
152 ReportHelper(ReportHelper &&) = default;
153 ReportHelper(const ReportHelper &) = delete;
154 void InitTask(TTreeReader *, unsigned int) {}
155 void Exec(unsigned int /* slot */) {}
156 void Initialize() { /* noop */}
157 void Finalize()
158 {
159 if (!fReturnEmptyReport)
160 fNode->Report(*fReport);
161 }
162
163 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
164 {
165 auto cutinfo_vec = fReport->fCutInfos;
166 return std::make_unique<RMergeableReport>(*fReport, cutinfo_vec);
167 }
168
169 std::string GetActionName() { return "Report"; }
170
171 ReportHelper MakeNew(void *newResult, std::string_view variation = "nominal")
172 {
173 auto &&result = *static_cast<std::shared_ptr<RCutFlowReport> *>(newResult);
174 return ReportHelper{result,
175 std::static_pointer_cast<RNode_t>(fNode->GetVariedFilter(std::string(variation))).get(),
176 fReturnEmptyReport};
177 }
178};
179
180/// This helper fills TH1Ds for which no axes were specified by buffering the fill values to pick good axes limits.
181///
182/// TH1Ds have an automatic mechanism to pick good limits based on the first N entries they were filled with, but
183/// that does not work in multi-thread event loops as it might yield histograms with incompatible binning in each
184/// thread, making it impossible to merge the per-thread results.
185/// Instead, this helper delays the decision on the axes limits until all threads have done processing, synchronizing
186/// the decision on the limits as part of the merge operation.
187class R__CLING_PTRCHECK(off) BufferedFillHelper : public RActionImpl<BufferedFillHelper> {
188 // this sets a total initial size of 16 MB for the buffers (can increase)
189 static constexpr unsigned int fgTotalBufSize = 2097152;
190 using BufEl_t = double;
191 using Buf_t = std::vector<BufEl_t>;
192
193 std::vector<Buf_t> fBuffers;
194 std::vector<Buf_t> fWBuffers;
195 std::shared_ptr<Hist_t> fResultHist;
196 unsigned int fNSlots;
197 unsigned int fBufSize;
198 /// Histograms containing "snapshots" of partial results. Non-null only if a registered callback requires it.
199 Results<std::unique_ptr<Hist_t>> fPartialHists;
200 Buf_t fMin;
201 Buf_t fMax;
202
203 void UpdateMinMax(unsigned int slot, double v);
204
205public:
206 BufferedFillHelper(const std::shared_ptr<Hist_t> &h, const unsigned int nSlots);
207 BufferedFillHelper(BufferedFillHelper &&) = default;
208 BufferedFillHelper(const BufferedFillHelper &) = delete;
209 void InitTask(TTreeReader *, unsigned int) {}
210 void Exec(unsigned int slot, double v);
211 void Exec(unsigned int slot, double v, double w);
212
213 template <typename T, std::enable_if_t<IsDataContainer<T>::value, int> = 0>
214 void Exec(unsigned int slot, const T &vs)
215 {
216 auto &thisBuf = fBuffers[slot];
217 // range-based for results in warnings on some compilers due to vector<bool>'s custom reference type
218 for (auto v = vs.begin(); v != vs.end(); ++v) {
219 UpdateMinMax(slot, *v);
220 thisBuf.emplace_back(*v); // TODO: Can be optimised in case T == BufEl_t
221 }
222 }
223
224 template <typename T, typename W, std::enable_if_t<IsDataContainer<T>::value && IsDataContainer<W>::value, int> = 0>
225 void Exec(unsigned int slot, const T &vs, const W &ws)
226 {
227 auto &thisBuf = fBuffers[slot];
228
229 for (auto &v : vs) {
230 UpdateMinMax(slot, v);
231 thisBuf.emplace_back(v);
232 }
233
234 auto &thisWBuf = fWBuffers[slot];
235 for (auto &w : ws) {
236 thisWBuf.emplace_back(w); // TODO: Can be optimised in case T == BufEl_t
237 }
238 }
239
240 template <typename T, typename W, std::enable_if_t<IsDataContainer<T>::value && !IsDataContainer<W>::value, int> = 0>
241 void Exec(unsigned int slot, const T &vs, const W w)
242 {
243 auto &thisBuf = fBuffers[slot];
244 for (auto &v : vs) {
245 UpdateMinMax(slot, v);
246 thisBuf.emplace_back(v); // TODO: Can be optimised in case T == BufEl_t
247 }
248
249 auto &thisWBuf = fWBuffers[slot];
250 thisWBuf.insert(thisWBuf.end(), vs.size(), w);
251 }
252
253 template <typename T, typename W, std::enable_if_t<IsDataContainer<W>::value && !IsDataContainer<T>::value, int> = 0>
254 void Exec(unsigned int slot, const T v, const W &ws)
255 {
256 UpdateMinMax(slot, v);
257 auto &thisBuf = fBuffers[slot];
258 thisBuf.insert(thisBuf.end(), ws.size(), v);
259
260 auto &thisWBuf = fWBuffers[slot];
261 thisWBuf.insert(thisWBuf.end(), ws.begin(), ws.end());
262 }
263
264 Hist_t &PartialUpdate(unsigned int);
265
266 void Initialize() { /* noop */}
267
268 void Finalize();
269
270 // Helper functions for RMergeableValue
271 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
272 {
273 return std::make_unique<RMergeableFill<Hist_t>>(*fResultHist);
274 }
275
276 std::string GetActionName()
277 {
278 return std::string(fResultHist->IsA()->GetName()) + "\\n" + std::string(fResultHist->GetName());
279 }
280
281 BufferedFillHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
282 {
283 auto &result = *static_cast<std::shared_ptr<Hist_t> *>(newResult);
284 result->Reset();
285 result->SetDirectory(nullptr);
286 return BufferedFillHelper(result, fNSlots);
287 }
288};
289
290// class which wraps a pointer and implements a no-op increment operator
291template <typename T>
292class ScalarConstIterator {
293 const T *obj_;
294
295public:
296 using iterator_category = std::forward_iterator_tag;
297 using difference_type = std::ptrdiff_t;
298 using value_type = T;
299 using pointer = T *;
300 using reference = T &;
301 ScalarConstIterator(const T *obj) : obj_(obj) {}
302 const T &operator*() const { return *obj_; }
303 ScalarConstIterator<T> &operator++() { return *this; }
304};
305
306// return unchanged value for scalar
307template <typename T>
308auto MakeBegin(const T &val)
309{
310 if constexpr (IsDataContainer<T>::value) {
311 return std::begin(val);
312 } else {
313 return ScalarConstIterator<T>(&val);
314 }
315}
316
317// return container size for containers, and 1 for scalars
318template <typename T>
319std::size_t GetSize(const T &val)
320{
321 if constexpr (IsDataContainer<T>::value) {
322 return std::size(val);
323 } else {
324 return 1;
325 }
326}
327
328// Helpers for dealing with histograms and similar:
329template <typename H, typename = decltype(std::declval<H>().Reset())>
330void ResetIfPossible(H *h)
331{
332 h->Reset();
333}
334
335void ResetIfPossible(TStatistic *h);
336void ResetIfPossible(...);
337
338void UnsetDirectoryIfPossible(TH1 *h);
340
341/// The generic Fill helper: it calls Fill on per-thread objects and then Merge to produce a final result.
342/// For one-dimensional histograms, if no axes are specified, RDataFrame uses BufferedFillHelper instead.
343template <typename HIST = Hist_t>
344class R__CLING_PTRCHECK(off) FillHelper : public RActionImpl<FillHelper<HIST>> {
345 std::vector<HIST *> fObjects;
346
347 // Merge overload for types with Merge(TCollection*), like TH1s
348 template <typename H, typename = std::enable_if_t<std::is_base_of<TObject, H>::value, int>>
349 auto Merge(std::vector<H *> &objs, int /*toincreaseoverloadpriority*/)
350 -> decltype(objs[0]->Merge((TCollection *)nullptr), void())
351 {
352 TList l;
353 for (auto it = ++objs.begin(); it != objs.end(); ++it)
354 l.Add(*it);
355 objs[0]->Merge(&l);
356 }
357
358 // Merge overload for types with Merge(const std::vector&)
359 template <typename H>
360 auto Merge(std::vector<H *> &objs, double /*toloweroverloadpriority*/)
361 -> decltype(objs[0]->Merge(std::vector<HIST *>{}), void())
362 {
363 objs[0]->Merge({++objs.begin(), objs.end()});
364 }
365
366 // Merge overload to error out in case no valid HIST::Merge method was detected
367 template <typename T>
368 void Merge(T, ...)
369 {
370 static_assert(sizeof(T) < 0,
371 "The type passed to Fill does not provide a Merge(TCollection*) or Merge(const std::vector&) method.");
372 }
373
374 template <std::size_t ColIdx, typename End_t, typename... Its>
375 void ExecLoop(unsigned int slot, End_t end, Its... its)
376 {
377 for (auto *thisSlotH = fObjects[slot]; GetNthElement<ColIdx>(its...) != end; (std::advance(its, 1), ...)) {
378 thisSlotH->Fill(*its...);
379 }
380 }
381
382public:
383 FillHelper(FillHelper &&) = default;
384 FillHelper(const FillHelper &) = delete;
385
386 FillHelper(const std::shared_ptr<HIST> &h, const unsigned int nSlots) : fObjects(nSlots, nullptr)
387 {
388 fObjects[0] = h.get();
389 // Initialize all other slots
390 for (unsigned int i = 1; i < nSlots; ++i) {
391 fObjects[i] = new HIST(*fObjects[0]);
392 UnsetDirectoryIfPossible(fObjects[i]);
393 }
394 }
395
396 void InitTask(TTreeReader *, unsigned int) {}
397
398 // no container arguments
399 template <typename... ValTypes, std::enable_if_t<!Disjunction<IsDataContainer<ValTypes>...>::value, int> = 0>
400 auto Exec(unsigned int slot, const ValTypes &...x) -> decltype(fObjects[slot]->Fill(x...), void())
401 {
402 fObjects[slot]->Fill(x...);
403 }
404
405 // at least one container argument
406 template <typename... Xs, std::enable_if_t<Disjunction<IsDataContainer<Xs>...>::value, int> = 0>
407 auto Exec(unsigned int slot, const Xs &...xs) -> decltype(fObjects[slot]->Fill(*MakeBegin(xs)...), void())
408 {
409 // array of bools keeping track of which inputs are containers
410 constexpr std::array<bool, sizeof...(Xs)> isContainer{IsDataContainer<Xs>::value...};
411
412 // index of the first container input
413 constexpr std::size_t colidx = FindIdxTrue(isContainer);
414 // if this happens, there is a bug in the implementation
415 static_assert(colidx < sizeof...(Xs), "Error: index of collection-type argument not found.");
416
417 // get the end iterator to the first container
418 auto const xrefend = std::end(GetNthElement<colidx>(xs...));
419
420 // array of container sizes (1 for scalars)
421 std::array<std::size_t, sizeof...(xs)> sizes = {{GetSize(xs)...}};
422
423 for (std::size_t i = 0; i < sizeof...(xs); ++i) {
424 if (isContainer[i] && sizes[i] != sizes[colidx]) {
425 throw std::runtime_error("Cannot fill histogram with values in containers of different sizes.");
426 }
427 }
428
429 ExecLoop<colidx>(slot, xrefend, MakeBegin(xs)...);
430 }
431
432 template <typename T = HIST>
433 void Exec(...)
434 {
435 static_assert(sizeof(T) < 0,
436 "When filling an object with RDataFrame (e.g. via a Fill action) the number or types of the "
437 "columns passed did not match the signature of the object's `Fill` method.");
438 }
439
440 void Initialize() { /* noop */}
441
442 void Finalize()
443 {
444 if (fObjects.size() == 1)
445 return;
446
447 Merge(fObjects, /*toselectcorrectoverload=*/0);
448
449 // delete the copies we created for the slots other than the first
450 for (auto it = ++fObjects.begin(); it != fObjects.end(); ++it)
451 delete *it;
452 }
453
454 HIST &PartialUpdate(unsigned int slot) { return *fObjects[slot]; }
455
456 // Helper functions for RMergeableValue
457 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
458 {
459 return std::make_unique<RMergeableFill<HIST>>(*fObjects[0]);
460 }
461
462 // if the fObjects vector type is derived from TObject, return the name of the object
463 template <typename T = HIST, std::enable_if_t<std::is_base_of<TObject, T>::value, int> = 0>
464 std::string GetActionName()
465 {
466 return std::string(fObjects[0]->IsA()->GetName()) + "\\n" + std::string(fObjects[0]->GetName());
467 }
468
469 // if fObjects is not derived from TObject, indicate it is some other object
470 template <typename T = HIST, std::enable_if_t<!std::is_base_of<TObject, T>::value, int> = 0>
471 std::string GetActionName()
472 {
473 return "Fill custom object";
474 }
475
476 template <typename H = HIST>
477 FillHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
478 {
479 auto &result = *static_cast<std::shared_ptr<H> *>(newResult);
480 ResetIfPossible(result.get());
481 UnsetDirectoryIfPossible(result.get());
482 return FillHelper(result, fObjects.size());
483 }
484};
485
486#ifdef R__HAS_ROOT7
487template <typename BinContentType, bool WithWeight = false>
488class R__CLING_PTRCHECK(off) RHistFillHelper
489 : public ROOT::Detail::RDF::RActionImpl<RHistFillHelper<BinContentType, WithWeight>> {
490public:
491 using Result_t = ROOT::Experimental::RHist<BinContentType>;
492
493private:
494 std::unique_ptr<ROOT::Experimental::RHistConcurrentFiller<BinContentType>> fFiller;
495 std::vector<std::shared_ptr<ROOT::Experimental::RHistFillContext<BinContentType>>> fContexts;
496
497public:
498 RHistFillHelper(std::shared_ptr<ROOT::Experimental::RHist<BinContentType>> h, unsigned int nSlots)
499 : fFiller(new ROOT::Experimental::RHistConcurrentFiller<BinContentType>(h)), fContexts(nSlots)
500 {
501 for (unsigned int i = 0; i < nSlots; i++) {
502 fContexts[i] = fFiller->CreateFillContext();
503 }
504 }
505 RHistFillHelper(const RHistFillHelper &) = delete;
506 RHistFillHelper(RHistFillHelper &&) = default;
507 RHistFillHelper &operator=(const RHistFillHelper &) = delete;
508 RHistFillHelper &operator=(RHistFillHelper &&) = default;
509 ~RHistFillHelper() = default;
510
511 std::shared_ptr<Result_t> GetResultPtr() const { return fFiller.GetHist(); }
512
513 void Initialize() {}
514 void InitTask(TTreeReader *, unsigned int) {}
515
516 template <typename... ColumnTypes, const std::size_t... I>
517 void
518 ExecWithWeight(unsigned int slot, const std::tuple<const ColumnTypes &...> &columnValues, std::index_sequence<I...>)
519 {
520 // Build a tuple of const references with the actual arguments, stripping the weight and avoiding copies.
521 std::tuple<const std::tuple_element_t<I, std::tuple<ColumnTypes...>> &...> args(std::get<I>(columnValues)...);
522 ROOT::Experimental::RWeight weight(std::get<sizeof...(ColumnTypes) - 1>(columnValues));
523 fContexts[slot]->Fill(args, weight);
524 }
525
526 template <typename... ColumnTypes>
527 void Exec(unsigned int slot, const ColumnTypes &...columnValues)
528 {
529 if constexpr (WithWeight) {
530 auto t = std::forward_as_tuple(columnValues...);
531 ExecWithWeight(slot, t, std::make_index_sequence<sizeof...(ColumnTypes) - 1>());
532 } else {
533 fContexts[slot]->Fill(columnValues...);
534 }
535 }
536
537 void Finalize()
538 {
539 for (auto &&context : fContexts) {
540 context->Flush();
541 }
542 }
543
544 std::string GetActionName() { return "Hist"; }
545};
546
547template <typename BinContentType, bool WithWeight = false>
548class R__CLING_PTRCHECK(off) RHistEngineFillHelper
549 : public ROOT::Detail::RDF::RActionImpl<RHistEngineFillHelper<BinContentType, WithWeight>> {
550public:
551 using Result_t = ROOT::Experimental::RHistEngine<BinContentType>;
552
553private:
554 std::shared_ptr<Result_t> fHist;
555
556public:
557 RHistEngineFillHelper(std::shared_ptr<ROOT::Experimental::RHistEngine<BinContentType>> h) : fHist(h) {}
558 RHistEngineFillHelper(const RHistEngineFillHelper &) = delete;
559 RHistEngineFillHelper(RHistEngineFillHelper &&) = default;
560 RHistEngineFillHelper &operator=(const RHistEngineFillHelper &) = delete;
561 RHistEngineFillHelper &operator=(RHistEngineFillHelper &&) = default;
562 ~RHistEngineFillHelper() = default;
563
564 std::shared_ptr<Result_t> GetResultPtr() const { return fHist; }
565
566 void Initialize() {}
567 void InitTask(TTreeReader *, unsigned int) {}
568
569 template <typename... ColumnTypes, const std::size_t... I>
570 void ExecWithWeight(const std::tuple<const ColumnTypes &...> &columnValues, std::index_sequence<I...>)
571 {
572 // Build a tuple of const references with the actual arguments, stripping the weight and avoiding copies.
573 std::tuple<const std::tuple_element_t<I, std::tuple<ColumnTypes...>> &...> args(std::get<I>(columnValues)...);
574 ROOT::Experimental::RWeight weight(std::get<sizeof...(ColumnTypes) - 1>(columnValues));
575 fHist->FillAtomic(args, weight);
576 }
577
578 template <typename... ColumnTypes>
579 void Exec(unsigned int, const ColumnTypes &...columnValues)
580 {
581 if constexpr (WithWeight) {
582 auto t = std::forward_as_tuple(columnValues...);
583 ExecWithWeight(t, std::make_index_sequence<sizeof...(ColumnTypes) - 1>());
584 } else {
585 fHist->FillAtomic(columnValues...);
586 }
587 }
588
589 void Finalize() {}
590
591 std::string GetActionName() { return "Hist"; }
592};
593#endif
594
595class R__CLING_PTRCHECK(off) FillTGraphHelper : public ROOT::Detail::RDF::RActionImpl<FillTGraphHelper> {
596public:
597 using Result_t = ::TGraph;
598
599private:
600 std::vector<::TGraph *> fGraphs;
601
602public:
603 FillTGraphHelper(FillTGraphHelper &&) = default;
604 FillTGraphHelper(const FillTGraphHelper &) = delete;
605
606 FillTGraphHelper(const std::shared_ptr<::TGraph> &g, const unsigned int nSlots) : fGraphs(nSlots, nullptr)
607 {
608 fGraphs[0] = g.get();
609 // Initialize all other slots
610 for (unsigned int i = 1; i < nSlots; ++i) {
611 fGraphs[i] = new TGraph(*fGraphs[0]);
612 }
613 }
614
615 void Initialize() {}
616 void InitTask(TTreeReader *, unsigned int) {}
617
618 // case: both types are container types
619 template <typename X0, typename X1,
620 std::enable_if_t<IsDataContainer<X0>::value && IsDataContainer<X1>::value, int> = 0>
621 void Exec(unsigned int slot, const X0 &x0s, const X1 &x1s)
622 {
623 if (x0s.size() != x1s.size()) {
624 throw std::runtime_error("Cannot fill Graph with values in containers of different sizes.");
625 }
626 auto *thisSlotG = fGraphs[slot];
627 auto x0sIt = std::begin(x0s);
628 const auto x0sEnd = std::end(x0s);
629 auto x1sIt = std::begin(x1s);
630 for (; x0sIt != x0sEnd; x0sIt++, x1sIt++) {
631 thisSlotG->SetPoint(thisSlotG->GetN(), *x0sIt, *x1sIt);
632 }
633 }
634
635 // case: both types are non-container types, e.g. scalars
636 template <typename X0, typename X1,
637 std::enable_if_t<!IsDataContainer<X0>::value && !IsDataContainer<X1>::value, int> = 0>
638 void Exec(unsigned int slot, X0 x0, X1 x1)
639 {
640 auto thisSlotG = fGraphs[slot];
641 thisSlotG->SetPoint(thisSlotG->GetN(), x0, x1);
642 }
643
644 // case: types are combination of containers and non-containers
645 // this is not supported, error out
646 template <typename X0, typename X1, typename... ExtraArgsToLowerPriority>
647 void Exec(unsigned int, X0, X1, ExtraArgsToLowerPriority...)
648 {
649 throw std::runtime_error("Graph was applied to a mix of scalar values and collections. This is not supported.");
650 }
651
652 void Finalize()
653 {
654 const auto nSlots = fGraphs.size();
655 auto resGraph = fGraphs[0];
656 TList l;
657 l.SetOwner(); // The list will free the memory associated to its elements upon destruction
658 for (unsigned int slot = 1; slot < nSlots; ++slot) {
659 l.Add(fGraphs[slot]);
660 }
661 resGraph->Merge(&l);
662 }
663
664 // Helper functions for RMergeableValue
665 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
666 {
667 return std::make_unique<RMergeableFill<Result_t>>(*fGraphs[0]);
668 }
669
670 std::string GetActionName() { return "Graph"; }
671
672 Result_t &PartialUpdate(unsigned int slot) { return *fGraphs[slot]; }
673
674 FillTGraphHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
675 {
676 auto &result = *static_cast<std::shared_ptr<TGraph> *>(newResult);
677 result->Set(0);
678 return FillTGraphHelper(result, fGraphs.size());
679 }
680};
681
682class R__CLING_PTRCHECK(off) FillTGraphAsymmErrorsHelper
683 : public ROOT::Detail::RDF::RActionImpl<FillTGraphAsymmErrorsHelper> {
684public:
685 using Result_t = ::TGraphAsymmErrors;
686
687private:
688 std::vector<::TGraphAsymmErrors *> fGraphAsymmErrors;
689
690public:
691 FillTGraphAsymmErrorsHelper(FillTGraphAsymmErrorsHelper &&) = default;
692 FillTGraphAsymmErrorsHelper(const FillTGraphAsymmErrorsHelper &) = delete;
693
694 FillTGraphAsymmErrorsHelper(const std::shared_ptr<::TGraphAsymmErrors> &g, const unsigned int nSlots)
695 : fGraphAsymmErrors(nSlots, nullptr)
696 {
697 fGraphAsymmErrors[0] = g.get();
698 // Initialize all other slots
699 for (unsigned int i = 1; i < nSlots; ++i) {
700 fGraphAsymmErrors[i] = new TGraphAsymmErrors(*fGraphAsymmErrors[0]);
701 }
702 }
703
704 void Initialize() {}
705 void InitTask(TTreeReader *, unsigned int) {}
706
707 // case: all types are container types
708 template <
709 typename X, typename Y, typename EXL, typename EXH, typename EYL, typename EYH,
710 std::enable_if_t<IsDataContainer<X>::value && IsDataContainer<Y>::value && IsDataContainer<EXL>::value &&
711 IsDataContainer<EXH>::value && IsDataContainer<EYL>::value && IsDataContainer<EYH>::value,
712 int> = 0>
713 void
714 Exec(unsigned int slot, const X &xs, const Y &ys, const EXL &exls, const EXH &exhs, const EYL &eyls, const EYH &eyhs)
715 {
716 if ((xs.size() != ys.size()) || (xs.size() != exls.size()) || (xs.size() != exhs.size()) ||
717 (xs.size() != eyls.size()) || (xs.size() != eyhs.size())) {
718 throw std::runtime_error("Cannot fill GraphAsymmErrors with values in containers of different sizes.");
719 }
720 auto *thisSlotG = fGraphAsymmErrors[slot];
721 auto xsIt = std::begin(xs);
722 auto ysIt = std::begin(ys);
723 auto exlsIt = std::begin(exls);
724 auto exhsIt = std::begin(exhs);
725 auto eylsIt = std::begin(eyls);
726 auto eyhsIt = std::begin(eyhs);
727 while (xsIt != std::end(xs)) {
728 const auto n = thisSlotG->GetN(); // must use the same `n` for SetPoint and SetPointError
729 thisSlotG->SetPoint(n, *xsIt++, *ysIt++);
730 thisSlotG->SetPointError(n, *exlsIt++, *exhsIt++, *eylsIt++, *eyhsIt++);
731 }
732 }
733
734 // case: all types are non-container types, e.g. scalars
735 template <
736 typename X, typename Y, typename EXL, typename EXH, typename EYL, typename EYH,
737 std::enable_if_t<!IsDataContainer<X>::value && !IsDataContainer<Y>::value && !IsDataContainer<EXL>::value &&
738 !IsDataContainer<EXH>::value && !IsDataContainer<EYL>::value && !IsDataContainer<EYH>::value,
739 int> = 0>
740 void Exec(unsigned int slot, X x, Y y, EXL exl, EXH exh, EYL eyl, EYH eyh)
741 {
742 auto thisSlotG = fGraphAsymmErrors[slot];
743 const auto n = thisSlotG->GetN();
744 thisSlotG->SetPoint(n, x, y);
745 thisSlotG->SetPointError(n, exl, exh, eyl, eyh);
746 }
747
748 // case: types are combination of containers and non-containers
749 // this is not supported, error out
750 template <typename X, typename Y, typename EXL, typename EXH, typename EYL, typename EYH,
751 typename... ExtraArgsToLowerPriority>
752 void Exec(unsigned int, X, Y, EXL, EXH, EYL, EYH, ExtraArgsToLowerPriority...)
753 {
754 throw std::runtime_error(
755 "GraphAsymmErrors was applied to a mix of scalar values and collections. This is not supported.");
756 }
757
758 void Finalize()
759 {
760 const auto nSlots = fGraphAsymmErrors.size();
761 auto resGraphAsymmErrors = fGraphAsymmErrors[0];
762 TList l;
763 l.SetOwner(); // The list will free the memory associated to its elements upon destruction
764 for (unsigned int slot = 1; slot < nSlots; ++slot) {
765 l.Add(fGraphAsymmErrors[slot]);
766 }
767 resGraphAsymmErrors->Merge(&l);
768 }
769
770 // Helper functions for RMergeableValue
771 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
772 {
773 return std::make_unique<RMergeableFill<Result_t>>(*fGraphAsymmErrors[0]);
774 }
775
776 std::string GetActionName() { return "GraphAsymmErrors"; }
777
778 Result_t &PartialUpdate(unsigned int slot) { return *fGraphAsymmErrors[slot]; }
779
780 FillTGraphAsymmErrorsHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
781 {
782 auto &result = *static_cast<std::shared_ptr<TGraphAsymmErrors> *>(newResult);
783 result->Set(0);
784 return FillTGraphAsymmErrorsHelper(result, fGraphAsymmErrors.size());
785 }
786};
787
788/// A FillHelper for classes supporting the FillThreadSafe function.
789template <typename HIST>
790class R__CLING_PTRCHECK(off) ThreadSafeFillHelper : public RActionImpl<ThreadSafeFillHelper<HIST>> {
791 std::vector<std::shared_ptr<HIST>> fObjects;
792 std::vector<std::unique_ptr<std::mutex>> fMutexPtrs;
793
794 // This overload matches if the function exists:
795 template <typename T, typename... Args>
796 auto TryCallFillThreadSafe(T &object, std::mutex &, int /*dummy*/, Args... args)
797 -> decltype(ROOT::Internal::FillThreadSafe(object, args...), void())
798 {
799 ROOT::Internal::FillThreadSafe(object, args...);
800 }
801 // This one has lower precedence because of the dummy argument, and uses a lock
802 template <typename T, typename... Args>
803 auto TryCallFillThreadSafe(T &object, std::mutex &mutex, char /*dummy*/, Args... args)
804 {
805 std::scoped_lock lock{mutex};
806 object.Fill(args...);
807 }
808
809 template <std::size_t ColIdx, typename End_t, typename... Its>
810 void ExecLoop(unsigned int slot, End_t end, Its... its)
811 {
812 const auto localSlot = slot % fObjects.size();
813 for (; GetNthElement<ColIdx>(its...) != end; (std::advance(its, 1), ...)) {
814 TryCallFillThreadSafe(*fObjects[localSlot], *fMutexPtrs[localSlot], 0, *its...);
815 }
816 }
817
818public:
819 ThreadSafeFillHelper(ThreadSafeFillHelper &&) = default;
820 ThreadSafeFillHelper(const ThreadSafeFillHelper &) = delete;
821
822 ThreadSafeFillHelper(const std::shared_ptr<HIST> &h, const unsigned int nSlots)
823 {
824 fObjects.resize(nSlots);
825 fObjects.front() = h;
826
827 std::generate(fObjects.begin() + 1, fObjects.end(), [h]() {
828 auto hist = std::make_shared<HIST>(*h);
829 UnsetDirectoryIfPossible(hist.get());
830 return hist;
831 });
832 fMutexPtrs.resize(nSlots);
833 std::generate(fMutexPtrs.begin(), fMutexPtrs.end(), []() { return std::make_unique<std::mutex>(); });
834 }
835
836 void InitTask(TTreeReader *, unsigned int) {}
837
838 // no container arguments
839 template <typename... ValTypes, std::enable_if_t<!Disjunction<IsDataContainer<ValTypes>...>::value, int> = 0>
840 void Exec(unsigned int slot, const ValTypes &...x)
841 {
842 const auto localSlot = slot % fObjects.size();
843 TryCallFillThreadSafe(*fObjects[localSlot], *fMutexPtrs[localSlot], 0, x...);
844 }
845
846 // at least one container argument
847 template <typename... Xs, std::enable_if_t<Disjunction<IsDataContainer<Xs>...>::value, int> = 0>
848 void Exec(unsigned int slot, const Xs &...xs)
849 {
850 // array of bools keeping track of which inputs are containers
851 constexpr std::array<bool, sizeof...(Xs)> isContainer{IsDataContainer<Xs>::value...};
852
853 // index of the first container input
854 constexpr std::size_t colidx = FindIdxTrue(isContainer);
855 // if this happens, there is a bug in the implementation
856 static_assert(colidx < sizeof...(Xs), "Error: index of collection-type argument not found.");
857
858 // get the end iterator to the first container
859 auto const xrefend = std::end(GetNthElement<colidx>(xs...));
860
861 // array of container sizes (1 for scalars)
862 std::array<std::size_t, sizeof...(xs)> sizes = {{GetSize(xs)...}};
863
864 for (std::size_t i = 0; i < sizeof...(xs); ++i) {
865 if (isContainer[i] && sizes[i] != sizes[colidx]) {
866 throw std::runtime_error("Cannot fill histogram with values in containers of different sizes.");
867 }
868 }
869
870 ExecLoop<colidx>(slot, xrefend, MakeBegin(xs)...);
871 }
872
873 template <typename T = HIST>
874 void Exec(...)
875 {
876 static_assert(sizeof(T) < 0,
877 "When filling an object with RDataFrame (e.g. via a Fill action) the number or types of the "
878 "columns passed did not match the signature of the object's `FillThreadSafe` method.");
879 }
880
881 void Initialize() { /* noop */ }
882
883 void Finalize()
884 {
885 if (fObjects.size() > 1) {
886 TList list;
887 for (auto it = fObjects.cbegin() + 1; it != fObjects.end(); ++it) {
888 list.Add(it->get());
889 }
890 fObjects[0]->Merge(&list);
891 }
892
893 fObjects.resize(1);
894 fMutexPtrs.clear();
895 }
896
897 // Helper function for RMergeableValue
898 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
899 {
900 return std::make_unique<RMergeableFill<HIST>>(*fObjects[0]);
901 }
902
903 // if the fObjects vector type is derived from TObject, return the name of the object
904 template <typename T = HIST, std::enable_if_t<std::is_base_of<TObject, T>::value, int> = 0>
905 std::string GetActionName()
906 {
907 return std::string(fObjects[0]->IsA()->GetName()) + "\\n" + std::string(fObjects[0]->GetName());
908 }
909
910 template <typename H = HIST>
911 ThreadSafeFillHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
912 {
913 auto &result = *static_cast<std::shared_ptr<H> *>(newResult);
914 ResetIfPossible(result.get());
915 UnsetDirectoryIfPossible(result.get());
916 return ThreadSafeFillHelper(result, fObjects.size());
917 }
918};
919
920// In case of the take helper we have 4 cases:
921// 1. The column is not an RVec, the collection is not a vector
922// 2. The column is not an RVec, the collection is a vector
923// 3. The column is an RVec, the collection is not a vector
924// 4. The column is an RVec, the collection is a vector
925
926template <typename V, typename COLL>
927void FillColl(V&& v, COLL& c) {
928 c.emplace_back(v);
929}
930
931// Use push_back for bool since some compilers do not support emplace_back.
932template <typename COLL>
933void FillColl(bool v, COLL& c) {
934 c.push_back(v);
935}
936
937// Case 1.: The column is not an RVec, the collection is not a vector
938// No optimisations, no transformations: just copies.
939template <typename RealT_t, typename T, typename COLL>
940class R__CLING_PTRCHECK(off) TakeHelper : public RActionImpl<TakeHelper<RealT_t, T, COLL>> {
941 Results<std::shared_ptr<COLL>> fColls;
942
943public:
944 using ColumnTypes_t = TypeList<T>;
945 TakeHelper(const std::shared_ptr<COLL> &resultColl, const unsigned int nSlots)
946 {
947 fColls.emplace_back(resultColl);
948 for (unsigned int i = 1; i < nSlots; ++i)
949 fColls.emplace_back(std::make_shared<COLL>());
950 }
951 TakeHelper(TakeHelper &&);
952 TakeHelper(const TakeHelper &) = delete;
953
954 void InitTask(TTreeReader *, unsigned int) {}
955
956 void Exec(unsigned int slot, T &v) { FillColl(v, *fColls[slot]); }
957
958 void Initialize() { /* noop */}
959
960 void Finalize()
961 {
962 auto rColl = fColls[0];
963 for (unsigned int i = 1; i < fColls.size(); ++i) {
964 const auto &coll = fColls[i];
965 const auto end = coll->end();
966 // Use an explicit loop here to prevent compiler warnings introduced by
967 // clang's range-based loop analysis and vector<bool> references.
968 for (auto j = coll->begin(); j != end; j++) {
969 FillColl(*j, *rColl);
970 }
971 }
972 }
973
974 COLL &PartialUpdate(unsigned int slot) { return *fColls[slot].get(); }
975
976 std::string GetActionName() { return "Take"; }
977
978 TakeHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
979 {
980 auto &result = *static_cast<std::shared_ptr<COLL> *>(newResult);
981 result->clear();
982 return TakeHelper(result, fColls.size());
983 }
984};
985
986// Case 2.: The column is not an RVec, the collection is a vector
987// Optimisations, no transformations: just copies.
988template <typename RealT_t, typename T>
989class R__CLING_PTRCHECK(off) TakeHelper<RealT_t, T, std::vector<T>>
990 : public RActionImpl<TakeHelper<RealT_t, T, std::vector<T>>> {
991 Results<std::shared_ptr<std::vector<T>>> fColls;
992
993public:
994 using ColumnTypes_t = TypeList<T>;
995 TakeHelper(const std::shared_ptr<std::vector<T>> &resultColl, const unsigned int nSlots)
996 {
997 fColls.emplace_back(resultColl);
998 for (unsigned int i = 1; i < nSlots; ++i) {
999 auto v = std::make_shared<std::vector<T>>();
1000 v->reserve(1024);
1001 fColls.emplace_back(v);
1002 }
1003 }
1004 TakeHelper(TakeHelper &&);
1005 TakeHelper(const TakeHelper &) = delete;
1006
1007 void InitTask(TTreeReader *, unsigned int) {}
1008
1009 void Exec(unsigned int slot, T &v) { FillColl(v, *fColls[slot]); }
1010
1011 void Initialize() { /* noop */}
1012
1013 // This is optimised to treat vectors
1014 void Finalize()
1015 {
1016 ULong64_t totSize = 0;
1017 for (auto &coll : fColls)
1018 totSize += coll->size();
1019 auto rColl = fColls[0];
1020 rColl->reserve(totSize);
1021 for (unsigned int i = 1; i < fColls.size(); ++i) {
1022 auto &coll = fColls[i];
1023 rColl->insert(rColl->end(), coll->begin(), coll->end());
1024 }
1025 }
1026
1027 std::vector<T> &PartialUpdate(unsigned int slot) { return *fColls[slot]; }
1028
1029 std::string GetActionName() { return "Take"; }
1030
1031 TakeHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1032 {
1033 auto &result = *static_cast<std::shared_ptr<std::vector<T>> *>(newResult);
1034 result->clear();
1035 return TakeHelper(result, fColls.size());
1036 }
1037};
1038
1039// Case 3.: The column is a RVec, the collection is not a vector
1040// No optimisations, transformations from RVecs to vectors
1041template <typename RealT_t, typename COLL>
1042class R__CLING_PTRCHECK(off) TakeHelper<RealT_t, RVec<RealT_t>, COLL>
1043 : public RActionImpl<TakeHelper<RealT_t, RVec<RealT_t>, COLL>> {
1044 Results<std::shared_ptr<COLL>> fColls;
1045
1046public:
1047 using ColumnTypes_t = TypeList<RVec<RealT_t>>;
1048 TakeHelper(const std::shared_ptr<COLL> &resultColl, const unsigned int nSlots)
1049 {
1050 fColls.emplace_back(resultColl);
1051 for (unsigned int i = 1; i < nSlots; ++i)
1052 fColls.emplace_back(std::make_shared<COLL>());
1053 }
1054 TakeHelper(TakeHelper &&);
1055 TakeHelper(const TakeHelper &) = delete;
1056
1057 void InitTask(TTreeReader *, unsigned int) {}
1058
1059 void Exec(unsigned int slot, RVec<RealT_t> av) { fColls[slot]->emplace_back(av.begin(), av.end()); }
1060
1061 void Initialize() { /* noop */}
1062
1063 void Finalize()
1064 {
1065 auto rColl = fColls[0];
1066 for (unsigned int i = 1; i < fColls.size(); ++i) {
1067 auto &coll = fColls[i];
1068 for (auto &v : *coll) {
1069 rColl->emplace_back(v);
1070 }
1071 }
1072 }
1073
1074 std::string GetActionName() { return "Take"; }
1075
1076 TakeHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1077 {
1078 auto &result = *static_cast<std::shared_ptr<COLL> *>(newResult);
1079 result->clear();
1080 return TakeHelper(result, fColls.size());
1081 }
1082};
1083
1084// Case 4.: The column is an RVec, the collection is a vector
1085// Optimisations, transformations from RVecs to vectors
1086template <typename RealT_t>
1087class R__CLING_PTRCHECK(off) TakeHelper<RealT_t, RVec<RealT_t>, std::vector<RealT_t>>
1088 : public RActionImpl<TakeHelper<RealT_t, RVec<RealT_t>, std::vector<RealT_t>>> {
1089
1090 Results<std::shared_ptr<std::vector<std::vector<RealT_t>>>> fColls;
1091
1092public:
1093 using ColumnTypes_t = TypeList<RVec<RealT_t>>;
1094 TakeHelper(const std::shared_ptr<std::vector<std::vector<RealT_t>>> &resultColl, const unsigned int nSlots)
1095 {
1096 fColls.emplace_back(resultColl);
1097 for (unsigned int i = 1; i < nSlots; ++i) {
1098 auto v = std::make_shared<std::vector<RealT_t>>();
1099 v->reserve(1024);
1100 fColls.emplace_back(v);
1101 }
1102 }
1103 TakeHelper(TakeHelper &&);
1104 TakeHelper(const TakeHelper &) = delete;
1105
1106 void InitTask(TTreeReader *, unsigned int) {}
1107
1108 void Exec(unsigned int slot, RVec<RealT_t> av) { fColls[slot]->emplace_back(av.begin(), av.end()); }
1109
1110 void Initialize() { /* noop */}
1111
1112 // This is optimised to treat vectors
1113 void Finalize()
1114 {
1115 ULong64_t totSize = 0;
1116 for (auto &coll : fColls)
1117 totSize += coll->size();
1118 auto rColl = fColls[0];
1119 rColl->reserve(totSize);
1120 for (unsigned int i = 1; i < fColls.size(); ++i) {
1121 auto &coll = fColls[i];
1122 rColl->insert(rColl->end(), coll->begin(), coll->end());
1123 }
1124 }
1125
1126 std::string GetActionName() { return "Take"; }
1127
1128 TakeHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1129 {
1130 auto &result = *static_cast<typename decltype(fColls)::value_type *>(newResult);
1131 result->clear();
1132 return TakeHelper(result, fColls.size());
1133 }
1134};
1135
1136// Extern templates for TakeHelper
1137// NOTE: The move-constructor of specializations declared as extern templates
1138// must be defined out of line, otherwise cling fails to find its symbol.
1139template <typename RealT_t, typename T, typename COLL>
1140TakeHelper<RealT_t, T, COLL>::TakeHelper(TakeHelper<RealT_t, T, COLL> &&) = default;
1141template <typename RealT_t, typename T>
1142TakeHelper<RealT_t, T, std::vector<T>>::TakeHelper(TakeHelper<RealT_t, T, std::vector<T>> &&) = default;
1143template <typename RealT_t, typename COLL>
1144TakeHelper<RealT_t, RVec<RealT_t>, COLL>::TakeHelper(TakeHelper<RealT_t, RVec<RealT_t>, COLL> &&) = default;
1145template <typename RealT_t>
1146TakeHelper<RealT_t, RVec<RealT_t>, std::vector<RealT_t>>::TakeHelper(TakeHelper<RealT_t, RVec<RealT_t>, std::vector<RealT_t>> &&) = default;
1147
1148// External templates are disabled for gcc5 since this version wrongly omits the C++11 ABI attribute
1149#if __GNUC__ > 5
1150extern template class TakeHelper<bool, bool, std::vector<bool>>;
1151extern template class TakeHelper<unsigned int, unsigned int, std::vector<unsigned int>>;
1152extern template class TakeHelper<unsigned long, unsigned long, std::vector<unsigned long>>;
1153extern template class TakeHelper<unsigned long long, unsigned long long, std::vector<unsigned long long>>;
1154extern template class TakeHelper<int, int, std::vector<int>>;
1155extern template class TakeHelper<long, long, std::vector<long>>;
1156extern template class TakeHelper<long long, long long, std::vector<long long>>;
1157extern template class TakeHelper<float, float, std::vector<float>>;
1158extern template class TakeHelper<double, double, std::vector<double>>;
1159#endif
1160
1161template <typename ResultType>
1162class R__CLING_PTRCHECK(off) MinHelper : public RActionImpl<MinHelper<ResultType>> {
1163 std::shared_ptr<ResultType> fResultMin;
1164 Results<ResultType> fMins;
1165
1166public:
1167 MinHelper(MinHelper &&) = default;
1168 MinHelper(const std::shared_ptr<ResultType> &minVPtr, const unsigned int nSlots)
1169 : fResultMin(minVPtr), fMins(nSlots, std::numeric_limits<ResultType>::max())
1170 {
1171 }
1172
1173 void Exec(unsigned int slot, ResultType v) { fMins[slot] = std::min(v, fMins[slot]); }
1174
1175 void InitTask(TTreeReader *, unsigned int) {}
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 fMins[slot] = std::min(static_cast<ResultType>(v), fMins[slot]);
1182 }
1183
1184 void Initialize() { /* noop */}
1185
1186 void Finalize()
1187 {
1188 *fResultMin = std::numeric_limits<ResultType>::max();
1189 for (auto &m : fMins)
1190 *fResultMin = std::min(m, *fResultMin);
1191 }
1192
1193 // Helper functions for RMergeableValue
1194 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1195 {
1196 return std::make_unique<RMergeableMin<ResultType>>(*fResultMin);
1197 }
1198
1199 ResultType &PartialUpdate(unsigned int slot) { return fMins[slot]; }
1200
1201 std::string GetActionName() { return "Min"; }
1202
1203 MinHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1204 {
1205 auto &result = *static_cast<std::shared_ptr<ResultType> *>(newResult);
1206 return MinHelper(result, fMins.size());
1207 }
1208};
1209
1210template <typename ResultType>
1211class R__CLING_PTRCHECK(off) MaxHelper : public RActionImpl<MaxHelper<ResultType>> {
1212 std::shared_ptr<ResultType> fResultMax;
1213 Results<ResultType> fMaxs;
1214
1215public:
1216 MaxHelper(MaxHelper &&) = default;
1217 MaxHelper(const MaxHelper &) = delete;
1218 MaxHelper(const std::shared_ptr<ResultType> &maxVPtr, const unsigned int nSlots)
1219 : fResultMax(maxVPtr), fMaxs(nSlots, std::numeric_limits<ResultType>::lowest())
1220 {
1221 }
1222
1223 void InitTask(TTreeReader *, unsigned int) {}
1224 void Exec(unsigned int slot, ResultType v) { fMaxs[slot] = std::max(v, fMaxs[slot]); }
1225
1226 template <typename T, std::enable_if_t<IsDataContainer<T>::value, int> = 0>
1227 void Exec(unsigned int slot, const T &vs)
1228 {
1229 for (auto &&v : vs)
1230 fMaxs[slot] = std::max(static_cast<ResultType>(v), fMaxs[slot]);
1231 }
1232
1233 void Initialize() { /* noop */}
1234
1235 void Finalize()
1236 {
1237 *fResultMax = std::numeric_limits<ResultType>::lowest();
1238 for (auto &m : fMaxs) {
1239 *fResultMax = std::max(m, *fResultMax);
1240 }
1241 }
1242
1243 // Helper functions for RMergeableValue
1244 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1245 {
1246 return std::make_unique<RMergeableMax<ResultType>>(*fResultMax);
1247 }
1248
1249 ResultType &PartialUpdate(unsigned int slot) { return fMaxs[slot]; }
1250
1251 std::string GetActionName() { return "Max"; }
1252
1253 MaxHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1254 {
1255 auto &result = *static_cast<std::shared_ptr<ResultType> *>(newResult);
1256 return MaxHelper(result, fMaxs.size());
1257 }
1258};
1259
1260template <typename ResultType>
1261class R__CLING_PTRCHECK(off) SumHelper : public RActionImpl<SumHelper<ResultType>> {
1262 std::shared_ptr<ResultType> fResultSum;
1263 Results<ResultType> fSums;
1264 Results<ResultType> fCompensations;
1265
1266 /// Evaluate neutral element for this type and the sum operation.
1267 /// This is assumed to be any_value - any_value if operator- is defined
1268 /// for the type, otherwise a default-constructed ResultType{} is used.
1269 template <typename T = ResultType>
1270 auto NeutralElement(const T &v, int /*overloadresolver*/) -> decltype(v - v)
1271 {
1272 return v - v;
1273 }
1274
1275 template <typename T = ResultType, typename Dummy = int>
1276 ResultType NeutralElement(const T &, Dummy) // this overload has lower priority thanks to the template arg
1277 {
1278 return ResultType{};
1279 }
1280
1281public:
1282 SumHelper(SumHelper &&) = default;
1283 SumHelper(const SumHelper &) = delete;
1284 SumHelper(const std::shared_ptr<ResultType> &sumVPtr, const unsigned int nSlots)
1285 : fResultSum(sumVPtr), fSums(nSlots, NeutralElement(*sumVPtr, -1)),
1286 fCompensations(nSlots, NeutralElement(*sumVPtr, -1))
1287 {
1288 }
1289 void InitTask(TTreeReader *, unsigned int) {}
1290
1291 void Exec(unsigned int slot, ResultType x)
1292 {
1293 // Kahan Sum:
1294 ResultType y = x - fCompensations[slot];
1295 ResultType t = fSums[slot] + y;
1296 fCompensations[slot] = (t - fSums[slot]) - y;
1297 fSums[slot] = t;
1298 }
1299
1300 template <typename T, std::enable_if_t<IsDataContainer<T>::value, int> = 0>
1301 void Exec(unsigned int slot, const T &vs)
1302 {
1303 for (auto &&v : vs) {
1304 Exec(slot, v);
1305 }
1306 }
1307
1308 void Initialize() { /* noop */}
1309
1310 void Finalize()
1311 {
1312 ResultType sum(NeutralElement(ResultType{}, -1));
1313 ResultType compensation(NeutralElement(ResultType{}, -1));
1314 ResultType y(NeutralElement(ResultType{}, -1));
1315 ResultType t(NeutralElement(ResultType{}, -1));
1316 for (auto &m : fSums) {
1317 // Kahan Sum:
1318 y = m - compensation;
1319 t = sum + y;
1320 compensation = (t - sum) - y;
1321 sum = t;
1322 }
1323 *fResultSum += sum;
1324 }
1325
1326 // Helper functions for RMergeableValue
1327 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1328 {
1329 return std::make_unique<RMergeableSum<ResultType>>(*fResultSum);
1330 }
1331
1332 ResultType &PartialUpdate(unsigned int slot) { return fSums[slot]; }
1333
1334 std::string GetActionName() { return "Sum"; }
1335
1336 SumHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1337 {
1338 auto &result = *static_cast<std::shared_ptr<ResultType> *>(newResult);
1339 *result = NeutralElement(*result, -1);
1340 return SumHelper(result, fSums.size());
1341 }
1342};
1343
1344class R__CLING_PTRCHECK(off) MeanHelper : public RActionImpl<MeanHelper> {
1345 std::shared_ptr<double> fResultMean;
1346 std::vector<ULong64_t> fCounts;
1347 std::vector<double> fSums;
1348 std::vector<double> fPartialMeans;
1349 std::vector<double> fCompensations;
1350
1351public:
1352 MeanHelper(const std::shared_ptr<double> &meanVPtr, const unsigned int nSlots);
1353 MeanHelper(MeanHelper &&) = default;
1354 MeanHelper(const MeanHelper &) = delete;
1355 void InitTask(TTreeReader *, unsigned int) {}
1356 void Exec(unsigned int slot, double v);
1357
1358 template <typename T, std::enable_if_t<IsDataContainer<T>::value, int> = 0>
1359 void Exec(unsigned int slot, const T &vs)
1360 {
1361 for (auto &&v : vs) {
1362
1363 fCounts[slot]++;
1364 // Kahan Sum:
1365 double y = v - fCompensations[slot];
1366 double t = fSums[slot] + y;
1367 fCompensations[slot] = (t - fSums[slot]) - y;
1368 fSums[slot] = t;
1369 }
1370 }
1371
1372 void Initialize() { /* noop */}
1373
1374 void Finalize();
1375
1376 // Helper functions for RMergeableValue
1377 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1378 {
1379 const ULong64_t counts = std::accumulate(fCounts.begin(), fCounts.end(), 0ull);
1380 return std::make_unique<RMergeableMean>(*fResultMean, counts);
1381 }
1382
1383 double &PartialUpdate(unsigned int slot);
1384
1385 std::string GetActionName() { return "Mean"; }
1386
1387 MeanHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1388 {
1389 auto &result = *static_cast<std::shared_ptr<double> *>(newResult);
1390 return MeanHelper(result, fSums.size());
1391 }
1392};
1393
1394class R__CLING_PTRCHECK(off) StdDevHelper : public RActionImpl<StdDevHelper> {
1395 // Number of subsets of data
1396 unsigned int fNSlots;
1397 std::shared_ptr<double> fResultStdDev;
1398 // Number of element for each slot
1399 std::vector<ULong64_t> fCounts;
1400 // Mean of each slot
1401 std::vector<double> fMeans;
1402 // Squared distance from the mean
1403 std::vector<double> fDistancesfromMean;
1404
1405public:
1406 StdDevHelper(const std::shared_ptr<double> &meanVPtr, const unsigned int nSlots);
1407 StdDevHelper(StdDevHelper &&) = default;
1408 StdDevHelper(const StdDevHelper &) = delete;
1409 void InitTask(TTreeReader *, unsigned int) {}
1410 void Exec(unsigned int slot, double v);
1411
1412 template <typename T, std::enable_if_t<IsDataContainer<T>::value, int> = 0>
1413 void Exec(unsigned int slot, const T &vs)
1414 {
1415 for (auto &&v : vs) {
1416 Exec(slot, v);
1417 }
1418 }
1419
1420 void Initialize() { /* noop */}
1421
1422 void Finalize();
1423
1424 // Helper functions for RMergeableValue
1425 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1426 {
1427 const ULong64_t counts = std::accumulate(fCounts.begin(), fCounts.end(), 0ull);
1428 const Double_t mean =
1429 std::inner_product(fMeans.begin(), fMeans.end(), fCounts.begin(), 0.) / static_cast<Double_t>(counts);
1430 return std::make_unique<RMergeableStdDev>(*fResultStdDev, counts, mean);
1431 }
1432
1433 std::string GetActionName() { return "StdDev"; }
1434
1435 StdDevHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1436 {
1437 auto &result = *static_cast<std::shared_ptr<double> *>(newResult);
1438 return StdDevHelper(result, fCounts.size());
1439 }
1440};
1441
1442template <typename PrevNodeType>
1443class R__CLING_PTRCHECK(off) DisplayHelper : public RActionImpl<DisplayHelper<PrevNodeType>> {
1444private:
1445 using Display_t = ROOT::RDF::RDisplay;
1446 std::shared_ptr<Display_t> fDisplayerHelper;
1447 std::shared_ptr<PrevNodeType> fPrevNode;
1448 size_t fEntriesToProcess;
1449
1450public:
1451 DisplayHelper(size_t nRows, const std::shared_ptr<Display_t> &d, const std::shared_ptr<PrevNodeType> &prevNode)
1452 : fDisplayerHelper(d), fPrevNode(prevNode), fEntriesToProcess(nRows)
1453 {
1454 }
1455 DisplayHelper(DisplayHelper &&) = default;
1456 DisplayHelper(const DisplayHelper &) = delete;
1457 void InitTask(TTreeReader *, unsigned int) {}
1458
1459 template <typename... Columns>
1460 void Exec(unsigned int, Columns &... columns)
1461 {
1462 if (fEntriesToProcess == 0)
1463 return;
1464
1465 fDisplayerHelper->AddRow(columns...);
1466 --fEntriesToProcess;
1467
1468 if (fEntriesToProcess == 0) {
1469 // No more entries to process. Send a one-time signal that this node
1470 // of the graph is done. It is important that the 'StopProcessing'
1471 // method is only called once from this helper, otherwise it would seem
1472 // like more than one operation has completed its work.
1473 fPrevNode->StopProcessing();
1474 }
1475 }
1476
1477 void Initialize() {}
1478
1479 void Finalize() {}
1480
1481 std::string GetActionName() { return "Display"; }
1482};
1483
1484template <typename Acc, typename Merge, typename R, typename T, typename U,
1485 bool MustCopyAssign = std::is_same<R, U>::value>
1486class R__CLING_PTRCHECK(off) AggregateHelper
1487 : public RActionImpl<AggregateHelper<Acc, Merge, R, T, U, MustCopyAssign>> {
1488 Acc fAggregate;
1489 Merge fMerge;
1490 std::shared_ptr<U> fResult;
1491 Results<U> fAggregators;
1492
1493public:
1494 using ColumnTypes_t = TypeList<T>;
1495
1496 AggregateHelper(Acc &&f, Merge &&m, const std::shared_ptr<U> &result, const unsigned int nSlots)
1497 : fAggregate(std::move(f)), fMerge(std::move(m)), fResult(result), fAggregators(nSlots, *result)
1498 {
1499 }
1500
1501 AggregateHelper(Acc &f, Merge &m, const std::shared_ptr<U> &result, const unsigned int nSlots)
1502 : fAggregate(f), fMerge(m), fResult(result), fAggregators(nSlots, *result)
1503 {
1504 }
1505
1506 AggregateHelper(AggregateHelper &&) = default;
1507 AggregateHelper(const AggregateHelper &) = delete;
1508
1509 void InitTask(TTreeReader *, unsigned int) {}
1510
1511 template <bool MustCopyAssign_ = MustCopyAssign, std::enable_if_t<MustCopyAssign_, int> = 0>
1512 void Exec(unsigned int slot, const T &value)
1513 {
1514 fAggregators[slot] = fAggregate(fAggregators[slot], value);
1515 }
1516
1517 template <bool MustCopyAssign_ = MustCopyAssign, std::enable_if_t<!MustCopyAssign_, int> = 0>
1518 void Exec(unsigned int slot, const T &value)
1519 {
1520 fAggregate(fAggregators[slot], value);
1521 }
1522
1523 void Initialize() { /* noop */}
1524
1525 template <typename MergeRet = typename CallableTraits<Merge>::ret_type,
1526 bool MergeAll = std::is_same<void, MergeRet>::value>
1527 std::enable_if_t<MergeAll, void> Finalize()
1528 {
1529 fMerge(fAggregators);
1530 *fResult = fAggregators[0];
1531 }
1532
1533 template <typename MergeRet = typename CallableTraits<Merge>::ret_type,
1534 bool MergeTwoByTwo = std::is_same<U, MergeRet>::value>
1535 std::enable_if_t<MergeTwoByTwo, void> Finalize(...) // ... needed to let compiler distinguish overloads
1536 {
1537 for (const auto &acc : fAggregators)
1538 *fResult = fMerge(*fResult, acc);
1539 }
1540
1541 U &PartialUpdate(unsigned int slot) { return fAggregators[slot]; }
1542
1543 std::string GetActionName() { return "Aggregate"; }
1544
1545 AggregateHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1546 {
1547 auto &result = *static_cast<std::shared_ptr<U> *>(newResult);
1548 return AggregateHelper(fAggregate, fMerge, result, fAggregators.size());
1549 }
1550};
1551
1552} // end of NS RDF
1553} // end of NS Internal
1554} // end of NS ROOT
1555
1556/// \endcond
1557
1558#endif
Handle_t Display_t
Display handle.
Definition GuiTypes.h:27
#define d(i)
Definition RSha256.hxx:102
#define f(i)
Definition RSha256.hxx:104
#define c(i)
Definition RSha256.hxx:101
#define g(i)
Definition RSha256.hxx:105
#define h(i)
Definition RSha256.hxx:106
#define R(a, b, c, d, e, f, g, h, i)
Definition RSha256.hxx:110
TClass * IsA() const override
Basic types used by ROOT and required by TInterpreter.
double Double_t
Double 8 bytes.
Definition RtypesCore.h:73
unsigned long long ULong64_t
Portable unsigned long integer 8 bytes.
Definition RtypesCore.h:84
#define X(type, name)
Binding & operator=(OUT(*fun)(void))
TTime operator*(const TTime &t1, const TTime &t2)
Definition TTime.h:85
Collection abstract base class.
Definition TCollection.h:65
void Add(TObject *obj) override
Definition TList.h:81
Statistical variable, defined by its mean and variance (RMS).
Definition TStatistic.h:33
A simple, robust and fast interface to read values from ROOT columnar datasets such as TTree,...
Definition TTreeReader.h:46
RooCmdArg Columns(Int_t ncol)
Double_t y[n]
Definition legend1.C:17
Double_t x[n]
Definition legend1.C:17
const Int_t n
Definition legend1.C:16
#define F(x, y, z)
#define I(x, y, z)
#define H(x, y, z)
CPYCPPYY_EXTERN bool Exec(const std::string &cmd)
Definition API.cxx:435
std::unique_ptr< RMergeableVariations< T > > GetMergeableValue(ROOT::RDF::Experimental::RResultMap< T > &rmap)
Retrieve mergeable values after calling ROOT::RDF::VariationsFor .
void ResetIfPossible(TStatistic *h)
constexpr std::size_t FindIdxTrue(const T &arr)
Definition Utils.hxx:235
void UnsetDirectoryIfPossible(TH1 *h)
void(off) SmallVectorTemplateBase< T
ROOT::VecOps::RVec< T > RVec
Definition RVec.hxx:71
auto FillThreadSafe(T &histo, Args... args) -> decltype(histo.FillThreadSafe(args...), void())
Entrypoint for thread-safe filling from RDataFrame.
Definition TH3.h:39
double T(double x)
__device__ AFloat max(AFloat x, AFloat y)
Definition Kernels.cuh:207
void Initialize(Bool_t useTMVAStyle=kTRUE)
Definition tmvaglob.cxx:176
TMarker m
Definition textangle.C:8
TLine l
Definition textangle.C:4
static uint64_t sum(uint64_t i)
Definition Factory.cxx:2338