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