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