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 \ingroup dataframe
4 \author Enrico Guiraud, CERN
5 \author Danilo Piparo, CERN
6 \date 2016-12
7 \author Vincenzo Eduardo Padulano
8 \date 2020-06
9*/
10
11/*************************************************************************
12 * Copyright (C) 1995-2020, Rene Brun and Fons Rademakers. *
13 * All rights reserved. *
14 * *
15 * For the licensing terms see $ROOTSYS/LICENSE. *
16 * For the list of contributors see $ROOTSYS/README/CREDITS. *
17 *************************************************************************/
18
19#ifndef ROOT_RDFOPERATIONS
20#define ROOT_RDFOPERATIONS
21
22#include "Compression.h"
23#include <string_view>
24#include "ROOT/RVec.hxx"
25#include "ROOT/TBufferMerger.hxx" // for SnapshotHelper
28#include "ROOT/RDF/Utils.hxx"
30#include "ROOT/TypeTraits.hxx"
31#include "ROOT/RDF/RDisplay.hxx"
32#include "RtypesCore.h"
33#include "TBranch.h"
34#include "TClassEdit.h"
35#include "TClassRef.h"
36#include "TDirectory.h"
37#include "TError.h" // for R__ASSERT, Warning
38#include "TFile.h" // for SnapshotHelper
39#include "TH1.h"
40#include "TGraph.h"
41#include "TGraphAsymmErrors.h"
42#include "TLeaf.h"
43#include "TObject.h"
44#include "TTree.h"
45#include "TTreeReader.h" // for SnapshotHelper
46#include "TStatistic.h"
49
50#include <algorithm>
51#include <functional>
52#include <limits>
53#include <memory>
54#include <stdexcept>
55#include <string>
56#include <type_traits>
57#include <utility> // std::index_sequence
58#include <vector>
59#include <iomanip>
60#include <numeric> // std::accumulate in MeanHelper
61
62/// \cond HIDDEN_SYMBOLS
63
64namespace ROOT {
65namespace Internal {
66namespace RDF {
67using namespace ROOT::TypeTraits;
68using namespace ROOT::VecOps;
69using namespace ROOT::RDF;
70using namespace ROOT::Detail::RDF;
71
72using Hist_t = ::TH1D;
73
74class RBranchSet {
75 std::vector<TBranch *> fBranches;
76 std::vector<std::string> fNames;
77
78public:
79 TBranch *Get(const std::string &name) const
80 {
81 auto it = std::find(fNames.begin(), fNames.end(), name);
82 if (it == fNames.end())
83 return nullptr;
84 return fBranches[std::distance(fNames.begin(), it)];
85 }
86
87 void Insert(const std::string &name, TBranch *address)
88 {
89 if (address == nullptr) {
90 throw std::logic_error("Trying to insert a null branch address.");
91 }
92 if (std::find(fBranches.begin(), fBranches.end(), address) != fBranches.end()) {
93 throw std::logic_error("Trying to insert a branch address that's already present.");
94 }
95 if (std::find(fNames.begin(), fNames.end(), name) != fNames.end()) {
96 throw std::logic_error("Trying to insert a branch name that's already present.");
97 }
98 fNames.emplace_back(name);
99 fBranches.emplace_back(address);
100 }
101
102 void Clear()
103 {
104 fBranches.clear();
105 fNames.clear();
106 }
107
109 {
110 std::vector<TBranch *> branchesWithNullAddress;
111 std::copy_if(fBranches.begin(), fBranches.end(), std::back_inserter(branchesWithNullAddress),
112 [](TBranch *b) { return b->GetAddress() == nullptr; });
113
114 if (branchesWithNullAddress.empty())
115 return;
116
117 // otherwise build error message and throw
118 std::vector<std::string> missingBranchNames;
120 std::back_inserter(missingBranchNames), [](TBranch *b) { return b->GetName(); });
121 std::string msg = "RDataFrame::Snapshot:";
122 if (missingBranchNames.size() == 1) {
123 msg += " branch " + missingBranchNames[0] +
124 " is needed as it provides the size for one or more branches containing dynamically sized arrays, but "
125 "it is";
126 } else {
127 msg += " branches ";
128 for (const auto &bName : missingBranchNames)
129 msg += bName + ", ";
130 msg.resize(msg.size() - 2); // remove last ", "
131 msg +=
132 " are needed as they provide the size of other branches containing dynamically sized arrays, but they are";
133 }
134 msg += " not part of the set of branches that are being written out.";
135 throw std::runtime_error(msg);
136 }
137};
138
139/// The container type for each thread's partial result in an action helper
140// We have to avoid to instantiate std::vector<bool> as that makes it impossible to return a reference to one of
141// the thread-local results. In addition, a common definition for the type of the container makes it easy to swap
142// the type of the underlying container if e.g. we see problems with false sharing of the thread-local results..
143template <typename T>
144using Results = std::conditional_t<std::is_same<T, bool>::value, std::deque<T>, std::vector<T>>;
145
146template <typename F>
147class R__CLING_PTRCHECK(off) ForeachSlotHelper : public RActionImpl<ForeachSlotHelper<F>> {
148 F fCallable;
149
150public:
152 ForeachSlotHelper(F &&f) : fCallable(f) {}
154 ForeachSlotHelper(const ForeachSlotHelper &) = delete;
155
156 void InitTask(TTreeReader *, unsigned int) {}
157
158 template <typename... Args>
159 void Exec(unsigned int slot, Args &&... args)
160 {
161 // check that the decayed types of Args are the same as the branch types
162 static_assert(std::is_same<TypeList<std::decay_t<Args>...>, ColumnTypes_t>::value, "");
163 fCallable(slot, std::forward<Args>(args)...);
164 }
165
166 void Initialize() { /* noop */}
167
168 void Finalize() { /* noop */}
169
170 std::string GetActionName() { return "ForeachSlot"; }
171};
172
173class R__CLING_PTRCHECK(off) CountHelper : public RActionImpl<CountHelper> {
174 std::shared_ptr<ULong64_t> fResultCount;
175 Results<ULong64_t> fCounts;
176
177public:
178 using ColumnTypes_t = TypeList<>;
179 CountHelper(const std::shared_ptr<ULong64_t> &resultCount, const unsigned int nSlots);
180 CountHelper(CountHelper &&) = default;
181 CountHelper(const CountHelper &) = delete;
182 void InitTask(TTreeReader *, unsigned int) {}
183 void Exec(unsigned int slot);
184 void Initialize() { /* noop */}
185 void Finalize();
186
187 // Helper functions for RMergeableValue
188 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
189 {
190 return std::make_unique<RMergeableCount>(*fResultCount);
191 }
192
193 ULong64_t &PartialUpdate(unsigned int slot);
194
195 std::string GetActionName() { return "Count"; }
196
197 CountHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
198 {
199 auto &result = *static_cast<std::shared_ptr<ULong64_t> *>(newResult);
200 return CountHelper(result, fCounts.size());
201 }
202};
203
204template <typename RNode_t>
205class R__CLING_PTRCHECK(off) ReportHelper : public RActionImpl<ReportHelper<RNode_t>> {
206 std::shared_ptr<RCutFlowReport> fReport;
207 /// Non-owning pointer, never null. As usual, the node is owned by its children nodes (and therefore indirectly by
208 /// the RAction corresponding to this action helper).
209 RNode_t *fNode;
211
212public:
213 using ColumnTypes_t = TypeList<>;
214 ReportHelper(const std::shared_ptr<RCutFlowReport> &report, RNode_t *node, bool emptyRep)
215 : fReport(report), fNode(node), fReturnEmptyReport(emptyRep){};
216 ReportHelper(ReportHelper &&) = default;
217 ReportHelper(const ReportHelper &) = delete;
218 void InitTask(TTreeReader *, unsigned int) {}
219 void Exec(unsigned int /* slot */) {}
220 void Initialize() { /* noop */}
221 void Finalize()
222 {
224 fNode->Report(*fReport);
225 }
226
227 std::string GetActionName() { return "Report"; }
228
229 ReportHelper MakeNew(void *newResult, std::string_view variation = "nominal")
230 {
231 auto &&result = *static_cast<std::shared_ptr<RCutFlowReport> *>(newResult);
232 return ReportHelper{result,
233 std::static_pointer_cast<RNode_t>(fNode->GetVariedFilter(std::string(variation))).get(),
235 }
236};
237
238/// This helper fills TH1Ds for which no axes were specified by buffering the fill values to pick good axes limits.
239///
240/// TH1Ds have an automatic mechanism to pick good limits based on the first N entries they were filled with, but
241/// that does not work in multi-thread event loops as it might yield histograms with incompatible binning in each
242/// thread, making it impossible to merge the per-thread results.
243/// Instead, this helper delays the decision on the axes limits until all threads have done processing, synchronizing
244/// the decision on the limits as part of the merge operation.
245class R__CLING_PTRCHECK(off) BufferedFillHelper : public RActionImpl<BufferedFillHelper> {
246 // this sets a total initial size of 16 MB for the buffers (can increase)
247 static constexpr unsigned int fgTotalBufSize = 2097152;
248 using BufEl_t = double;
249 using Buf_t = std::vector<BufEl_t>;
250
251 std::vector<Buf_t> fBuffers;
252 std::vector<Buf_t> fWBuffers;
253 std::shared_ptr<Hist_t> fResultHist;
254 unsigned int fNSlots;
255 unsigned int fBufSize;
256 /// Histograms containing "snapshots" of partial results. Non-null only if a registered callback requires it.
258 Buf_t fMin;
259 Buf_t fMax;
260
261 void UpdateMinMax(unsigned int slot, double v);
262
263public:
264 BufferedFillHelper(const std::shared_ptr<Hist_t> &h, const unsigned int nSlots);
266 BufferedFillHelper(const BufferedFillHelper &) = delete;
267 void InitTask(TTreeReader *, unsigned int) {}
268 void Exec(unsigned int slot, double v);
269 void Exec(unsigned int slot, double v, double w);
270
272 void Exec(unsigned int slot, const T &vs)
273 {
274 auto &thisBuf = fBuffers[slot];
275 // range-based for results in warnings on some compilers due to vector<bool>'s custom reference type
276 for (auto v = vs.begin(); v != vs.end(); ++v) {
278 thisBuf.emplace_back(*v); // TODO: Can be optimised in case T == BufEl_t
279 }
280 }
281
283 void Exec(unsigned int slot, const T &vs, const W &ws)
284 {
285 auto &thisBuf = fBuffers[slot];
286
287 for (auto &v : vs) {
289 thisBuf.emplace_back(v);
290 }
291
292 auto &thisWBuf = fWBuffers[slot];
293 for (auto &w : ws) {
294 thisWBuf.emplace_back(w); // TODO: Can be optimised in case T == BufEl_t
295 }
296 }
297
299 void Exec(unsigned int slot, const T &vs, const W w)
300 {
301 auto &thisBuf = fBuffers[slot];
302 for (auto &v : vs) {
304 thisBuf.emplace_back(v); // TODO: Can be optimised in case T == BufEl_t
305 }
306
307 auto &thisWBuf = fWBuffers[slot];
308 thisWBuf.insert(thisWBuf.end(), vs.size(), w);
309 }
310
312 void Exec(unsigned int slot, const T v, const W &ws)
313 {
315 auto &thisBuf = fBuffers[slot];
316 thisBuf.insert(thisBuf.end(), ws.size(), v);
317
318 auto &thisWBuf = fWBuffers[slot];
319 thisWBuf.insert(thisWBuf.end(), ws.begin(), ws.end());
320 }
321
322 Hist_t &PartialUpdate(unsigned int);
323
324 void Initialize() { /* noop */}
325
326 void Finalize();
327
328 // Helper functions for RMergeableValue
329 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
330 {
331 return std::make_unique<RMergeableFill<Hist_t>>(*fResultHist);
332 }
333
334 std::string GetActionName()
335 {
336 return std::string(fResultHist->IsA()->GetName()) + "\\n" + std::string(fResultHist->GetName());
337 }
338
339 BufferedFillHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
340 {
341 auto &result = *static_cast<std::shared_ptr<Hist_t> *>(newResult);
342 result->Reset();
343 result->SetDirectory(nullptr);
344 return BufferedFillHelper(result, fNSlots);
345 }
346};
347
348/// The generic Fill helper: it calls Fill on per-thread objects and then Merge to produce a final result.
349/// For one-dimensional histograms, if no axes are specified, RDataFrame uses BufferedFillHelper instead.
350template <typename HIST = Hist_t>
351class R__CLING_PTRCHECK(off) FillHelper : public RActionImpl<FillHelper<HIST>> {
352 std::vector<HIST *> fObjects;
353
355 void ResetIfPossible(H *h)
356 {
357 h->Reset();
358 }
359
360 void ResetIfPossible(TStatistic *h) { *h = TStatistic(); }
361
362 // cannot safely re-initialize variations of the result, hence error out
363 void ResetIfPossible(...)
364 {
365 throw std::runtime_error(
366 "A systematic variation was requested for a custom Fill action, but the type of the object to be filled does "
367 "not implement a Reset method, so we cannot safely re-initialize variations of the result. Aborting.");
368 }
369
371 h->SetDirectory(nullptr);
372 }
373
374 void UnsetDirectoryIfPossible(...) {}
375
376 // Merge overload for types with Merge(TCollection*), like TH1s
378 auto Merge(std::vector<H *> &objs, int /*toincreaseoverloadpriority*/)
379 -> decltype(objs[0]->Merge((TCollection *)nullptr), void())
380 {
381 TList l;
382 for (auto it = ++objs.begin(); it != objs.end(); ++it)
383 l.Add(*it);
384 objs[0]->Merge(&l);
385 }
386
387 // Merge overload for types with Merge(const std::vector&)
388 template <typename H>
389 auto Merge(std::vector<H *> &objs, double /*toloweroverloadpriority*/)
390 -> decltype(objs[0]->Merge(std::vector<HIST *>{}), void())
391 {
392 objs[0]->Merge({++objs.begin(), objs.end()});
393 }
394
395 // Merge overload to error out in case no valid HIST::Merge method was detected
396 template <typename T>
397 void Merge(T, ...)
398 {
399 static_assert(sizeof(T) < 0,
400 "The type passed to Fill does not provide a Merge(TCollection*) or Merge(const std::vector&) method.");
401 }
402
403 // class which wraps a pointer and implements a no-op increment operator
404 template <typename T>
405 class ScalarConstIterator {
406 const T *obj_;
407
408 public:
409 ScalarConstIterator(const T *obj) : obj_(obj) {}
410 const T &operator*() const { return *obj_; }
411 ScalarConstIterator<T> &operator++() { return *this; }
412 };
413
414 // helper functions which provide one implementation for scalar types and another for containers
415 // TODO these could probably all be replaced by inlined lambdas and/or constexpr if statements
416 // in c++17 or later
417
418 // return unchanged value for scalar
421 {
422 return ScalarConstIterator<T>(&val);
423 }
424
425 // return iterator to beginning of container
427 auto MakeBegin(const T &val)
428 {
429 return std::begin(val);
430 }
431
432 // return 1 for scalars
434 std::size_t GetSize(const T &)
435 {
436 return 1;
437 }
438
439 // return container size
441 std::size_t GetSize(const T &val)
442 {
443#if __cplusplus >= 201703L
444 return std::size(val);
445#else
446 return val.size();
447#endif
448 }
449
450 template <std::size_t ColIdx, typename End_t, typename... Its>
451 void ExecLoop(unsigned int slot, End_t end, Its... its)
452 {
453 auto *thisSlotH = fObjects[slot];
454 // loop increments all of the iterators while leaving scalars unmodified
455 // TODO this could be simplified with fold expressions or std::apply in C++17
456 auto nop = [](auto &&...) {};
457 for (; GetNthElement<ColIdx>(its...) != end; nop(++its...)) {
458 thisSlotH->Fill(*its...);
459 }
460 }
461
462public:
463 FillHelper(FillHelper &&) = default;
464 FillHelper(const FillHelper &) = delete;
465
466 FillHelper(const std::shared_ptr<HIST> &h, const unsigned int nSlots) : fObjects(nSlots, nullptr)
467 {
468 fObjects[0] = h.get();
469 // Initialize all other slots
470 for (unsigned int i = 1; i < nSlots; ++i) {
471 fObjects[i] = new HIST(*fObjects[0]);
472 UnsetDirectoryIfPossible(fObjects[i]);
473 }
474 }
475
476 void InitTask(TTreeReader *, unsigned int) {}
477
478 // no container arguments
479 template <typename... ValTypes, std::enable_if_t<!Disjunction<IsDataContainer<ValTypes>...>::value, int> = 0>
480 auto Exec(unsigned int slot, const ValTypes &...x) -> decltype(fObjects[slot]->Fill(x...), void())
481 {
482 fObjects[slot]->Fill(x...);
483 }
484
485 // at least one container argument
486 template <typename... Xs, std::enable_if_t<Disjunction<IsDataContainer<Xs>...>::value, int> = 0>
487 auto Exec(unsigned int slot, const Xs &...xs) -> decltype(fObjects[slot]->Fill(*MakeBegin(xs)...), void())
488 {
489 // array of bools keeping track of which inputs are containers
490 constexpr std::array<bool, sizeof...(Xs)> isContainer{IsDataContainer<Xs>::value...};
491
492 // index of the first container input
493 constexpr std::size_t colidx = FindIdxTrue(isContainer);
494 // if this happens, there is a bug in the implementation
495 static_assert(colidx < sizeof...(Xs), "Error: index of collection-type argument not found.");
496
497 // get the end iterator to the first container
498 auto const xrefend = std::end(GetNthElement<colidx>(xs...));
499
500 // array of container sizes (1 for scalars)
501 std::array<std::size_t, sizeof...(xs)> sizes = {{GetSize(xs)...}};
502
503 for (std::size_t i = 0; i < sizeof...(xs); ++i) {
504 if (isContainer[i] && sizes[i] != sizes[colidx]) {
505 throw std::runtime_error("Cannot fill histogram with values in containers of different sizes.");
506 }
507 }
508
510 }
511
512 template <typename T = HIST>
513 void Exec(...)
514 {
515 static_assert(sizeof(T) < 0,
516 "When filling an object with RDataFrame (e.g. via a Fill action) the number or types of the "
517 "columns passed did not match the signature of the object's `Fill` method.");
518 }
519
520 void Initialize() { /* noop */}
521
522 void Finalize()
523 {
524 if (fObjects.size() == 1)
525 return;
526
527 Merge(fObjects, /*toselectcorrectoverload=*/0);
528
529 // delete the copies we created for the slots other than the first
530 for (auto it = ++fObjects.begin(); it != fObjects.end(); ++it)
531 delete *it;
532 }
533
534 HIST &PartialUpdate(unsigned int slot) { return *fObjects[slot]; }
535
536 // Helper functions for RMergeableValue
537 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
538 {
539 return std::make_unique<RMergeableFill<HIST>>(*fObjects[0]);
540 }
541
542 // if the fObjects vector type is derived from TObject, return the name of the object
544 std::string GetActionName()
545 {
546 return std::string(fObjects[0]->IsA()->GetName()) + "\\n" + std::string(fObjects[0]->GetName());
547 }
548
549 // if fObjects is not derived from TObject, indicate it is some other object
551 std::string GetActionName()
552 {
553 return "Fill custom object";
554 }
555
556 template <typename H = HIST>
557 FillHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
558 {
559 auto &result = *static_cast<std::shared_ptr<H> *>(newResult);
560 ResetIfPossible(result.get());
562 return FillHelper(result, fObjects.size());
563 }
564};
565
567public:
568 using Result_t = ::TGraph;
569
570private:
571 std::vector<::TGraph *> fGraphs;
572
573public:
575 FillTGraphHelper(const FillTGraphHelper &) = delete;
576
577 FillTGraphHelper(const std::shared_ptr<::TGraph> &g, const unsigned int nSlots) : fGraphs(nSlots, nullptr)
578 {
579 fGraphs[0] = g.get();
580 // Initialize all other slots
581 for (unsigned int i = 1; i < nSlots; ++i) {
582 fGraphs[i] = new TGraph(*fGraphs[0]);
583 }
584 }
585
586 void Initialize() {}
587 void InitTask(TTreeReader *, unsigned int) {}
588
589 // case: both types are container types
590 template <typename X0, typename X1,
591 std::enable_if_t<IsDataContainer<X0>::value && IsDataContainer<X1>::value, int> = 0>
592 void Exec(unsigned int slot, const X0 &x0s, const X1 &x1s)
593 {
594 if (x0s.size() != x1s.size()) {
595 throw std::runtime_error("Cannot fill Graph with values in containers of different sizes.");
596 }
597 auto *thisSlotG = fGraphs[slot];
598 auto x0sIt = std::begin(x0s);
599 const auto x0sEnd = std::end(x0s);
600 auto x1sIt = std::begin(x1s);
601 for (; x0sIt != x0sEnd; x0sIt++, x1sIt++) {
602 thisSlotG->SetPoint(thisSlotG->GetN(), *x0sIt, *x1sIt);
603 }
604 }
605
606 // case: both types are non-container types, e.g. scalars
607 template <typename X0, typename X1,
608 std::enable_if_t<!IsDataContainer<X0>::value && !IsDataContainer<X1>::value, int> = 0>
609 void Exec(unsigned int slot, X0 x0, X1 x1)
610 {
611 auto thisSlotG = fGraphs[slot];
612 thisSlotG->SetPoint(thisSlotG->GetN(), x0, x1);
613 }
614
615 // case: types are combination of containers and non-containers
616 // this is not supported, error out
617 template <typename X0, typename X1, typename... ExtraArgsToLowerPriority>
618 void Exec(unsigned int, X0, X1, ExtraArgsToLowerPriority...)
619 {
620 throw std::runtime_error("Graph was applied to a mix of scalar values and collections. This is not supported.");
621 }
622
623 void Finalize()
624 {
625 const auto nSlots = fGraphs.size();
626 auto resGraph = fGraphs[0];
627 TList l;
628 l.SetOwner(); // The list will free the memory associated to its elements upon destruction
629 for (unsigned int slot = 1; slot < nSlots; ++slot) {
630 l.Add(fGraphs[slot]);
631 }
632 resGraph->Merge(&l);
633 }
634
635 // Helper functions for RMergeableValue
636 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
637 {
638 return std::make_unique<RMergeableFill<Result_t>>(*fGraphs[0]);
639 }
640
641 std::string GetActionName() { return "Graph"; }
642
643 Result_t &PartialUpdate(unsigned int slot) { return *fGraphs[slot]; }
644
645 FillTGraphHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
646 {
647 auto &result = *static_cast<std::shared_ptr<TGraph> *>(newResult);
648 result->Set(0);
649 return FillTGraphHelper(result, fGraphs.size());
650 }
651};
652
654 : public ROOT::Detail::RDF::RActionImpl<FillTGraphAsymmErrorsHelper> {
655public:
656 using Result_t = ::TGraphAsymmErrors;
657
658private:
659 std::vector<::TGraphAsymmErrors *> fGraphAsymmErrors;
660
661public:
664
665 FillTGraphAsymmErrorsHelper(const std::shared_ptr<::TGraphAsymmErrors> &g, const unsigned int nSlots)
666 : fGraphAsymmErrors(nSlots, nullptr)
667 {
668 fGraphAsymmErrors[0] = g.get();
669 // Initialize all other slots
670 for (unsigned int i = 1; i < nSlots; ++i) {
672 }
673 }
674
675 void Initialize() {}
676 void InitTask(TTreeReader *, unsigned int) {}
677
678 // case: all types are container types
679 template <
680 typename X, typename Y, typename EXL, typename EXH, typename EYL, typename EYH,
681 std::enable_if_t<IsDataContainer<X>::value && IsDataContainer<Y>::value && IsDataContainer<EXL>::value &&
682 IsDataContainer<EXH>::value && IsDataContainer<EYL>::value && IsDataContainer<EYH>::value,
683 int> = 0>
684 void
685 Exec(unsigned int slot, const X &xs, const Y &ys, const EXL &exls, const EXH &exhs, const EYL &eyls, const EYH &eyhs)
686 {
687 if ((xs.size() != ys.size()) || (xs.size() != exls.size()) || (xs.size() != exhs.size()) ||
688 (xs.size() != eyls.size()) || (xs.size() != eyhs.size())) {
689 throw std::runtime_error("Cannot fill GraphAsymmErrors with values in containers of different sizes.");
690 }
692 auto xsIt = std::begin(xs);
693 auto ysIt = std::begin(ys);
694 auto exlsIt = std::begin(exls);
695 auto exhsIt = std::begin(exhs);
696 auto eylsIt = std::begin(eyls);
697 auto eyhsIt = std::begin(eyhs);
698 while (xsIt != std::end(xs)) {
699 const auto n = thisSlotG->GetN(); // must use the same `n` for SetPoint and SetPointError
700 thisSlotG->SetPoint(n, *xsIt++, *ysIt++);
701 thisSlotG->SetPointError(n, *exlsIt++, *exhsIt++, *eylsIt++, *eyhsIt++);
702 }
703 }
704
705 // case: all types are non-container types, e.g. scalars
706 template <
707 typename X, typename Y, typename EXL, typename EXH, typename EYL, typename EYH,
708 std::enable_if_t<!IsDataContainer<X>::value && !IsDataContainer<Y>::value && !IsDataContainer<EXL>::value &&
709 !IsDataContainer<EXH>::value && !IsDataContainer<EYL>::value && !IsDataContainer<EYH>::value,
710 int> = 0>
711 void Exec(unsigned int slot, X x, Y y, EXL exl, EXH exh, EYL eyl, EYH eyh)
712 {
714 const auto n = thisSlotG->GetN();
715 thisSlotG->SetPoint(n, x, y);
716 thisSlotG->SetPointError(n, exl, exh, eyl, eyh);
717 }
718
719 // case: types are combination of containers and non-containers
720 // this is not supported, error out
721 template <typename X, typename Y, typename EXL, typename EXH, typename EYL, typename EYH,
722 typename... ExtraArgsToLowerPriority>
723 void Exec(unsigned int, X, Y, EXL, EXH, EYL, EYH, ExtraArgsToLowerPriority...)
724 {
725 throw std::runtime_error(
726 "GraphAsymmErrors was applied to a mix of scalar values and collections. This is not supported.");
727 }
728
729 void Finalize()
730 {
731 const auto nSlots = fGraphAsymmErrors.size();
733 TList l;
734 l.SetOwner(); // The list will free the memory associated to its elements upon destruction
735 for (unsigned int slot = 1; slot < nSlots; ++slot) {
737 }
738 resGraphAsymmErrors->Merge(&l);
739 }
740
741 // Helper functions for RMergeableValue
742 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
743 {
744 return std::make_unique<RMergeableFill<Result_t>>(*fGraphAsymmErrors[0]);
745 }
746
747 std::string GetActionName() { return "GraphAsymmErrors"; }
748
749 Result_t &PartialUpdate(unsigned int slot) { return *fGraphAsymmErrors[slot]; }
750
751 FillTGraphAsymmErrorsHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
752 {
753 auto &result = *static_cast<std::shared_ptr<TGraphAsymmErrors> *>(newResult);
754 result->Set(0);
756 }
757};
758
759// In case of the take helper we have 4 cases:
760// 1. The column is not an RVec, the collection is not a vector
761// 2. The column is not an RVec, the collection is a vector
762// 3. The column is an RVec, the collection is not a vector
763// 4. The column is an RVec, the collection is a vector
764
765template <typename V, typename COLL>
766void FillColl(V&& v, COLL& c) {
767 c.emplace_back(v);
768}
769
770// Use push_back for bool since some compilers do not support emplace_back.
771template <typename COLL>
772void FillColl(bool v, COLL& c) {
773 c.push_back(v);
774}
775
776// Case 1.: The column is not an RVec, the collection is not a vector
777// No optimisations, no transformations: just copies.
778template <typename RealT_t, typename T, typename COLL>
779class R__CLING_PTRCHECK(off) TakeHelper : public RActionImpl<TakeHelper<RealT_t, T, COLL>> {
781
782public:
783 using ColumnTypes_t = TypeList<T>;
784 TakeHelper(const std::shared_ptr<COLL> &resultColl, const unsigned int nSlots)
785 {
786 fColls.emplace_back(resultColl);
787 for (unsigned int i = 1; i < nSlots; ++i)
788 fColls.emplace_back(std::make_shared<COLL>());
789 }
791 TakeHelper(const TakeHelper &) = delete;
792
793 void InitTask(TTreeReader *, unsigned int) {}
794
795 void Exec(unsigned int slot, T &v) { FillColl(v, *fColls[slot]); }
796
797 void Initialize() { /* noop */}
798
799 void Finalize()
800 {
801 auto rColl = fColls[0];
802 for (unsigned int i = 1; i < fColls.size(); ++i) {
803 const auto &coll = fColls[i];
804 const auto end = coll->end();
805 // Use an explicit loop here to prevent compiler warnings introduced by
806 // clang's range-based loop analysis and vector<bool> references.
807 for (auto j = coll->begin(); j != end; j++) {
808 FillColl(*j, *rColl);
809 }
810 }
811 }
812
813 COLL &PartialUpdate(unsigned int slot) { return *fColls[slot].get(); }
814
815 std::string GetActionName() { return "Take"; }
816
817 TakeHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
818 {
819 auto &result = *static_cast<std::shared_ptr<COLL> *>(newResult);
820 result->clear();
821 return TakeHelper(result, fColls.size());
822 }
823};
824
825// Case 2.: The column is not an RVec, the collection is a vector
826// Optimisations, no transformations: just copies.
827template <typename RealT_t, typename T>
828class R__CLING_PTRCHECK(off) TakeHelper<RealT_t, T, std::vector<T>>
829 : public RActionImpl<TakeHelper<RealT_t, T, std::vector<T>>> {
831
832public:
833 using ColumnTypes_t = TypeList<T>;
834 TakeHelper(const std::shared_ptr<std::vector<T>> &resultColl, const unsigned int nSlots)
835 {
836 fColls.emplace_back(resultColl);
837 for (unsigned int i = 1; i < nSlots; ++i) {
838 auto v = std::make_shared<std::vector<T>>();
839 v->reserve(1024);
840 fColls.emplace_back(v);
841 }
842 }
844 TakeHelper(const TakeHelper &) = delete;
845
846 void InitTask(TTreeReader *, unsigned int) {}
847
848 void Exec(unsigned int slot, T &v) { FillColl(v, *fColls[slot]); }
849
850 void Initialize() { /* noop */}
851
852 // This is optimised to treat vectors
853 void Finalize()
854 {
855 ULong64_t totSize = 0;
856 for (auto &coll : fColls)
857 totSize += coll->size();
858 auto rColl = fColls[0];
859 rColl->reserve(totSize);
860 for (unsigned int i = 1; i < fColls.size(); ++i) {
861 auto &coll = fColls[i];
862 rColl->insert(rColl->end(), coll->begin(), coll->end());
863 }
864 }
865
866 std::vector<T> &PartialUpdate(unsigned int slot) { return *fColls[slot]; }
867
868 std::string GetActionName() { return "Take"; }
869
870 TakeHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
871 {
872 auto &result = *static_cast<std::shared_ptr<std::vector<T>> *>(newResult);
873 result->clear();
874 return TakeHelper(result, fColls.size());
875 }
876};
877
878// Case 3.: The column is a RVec, the collection is not a vector
879// No optimisations, transformations from RVecs to vectors
880template <typename RealT_t, typename COLL>
882 : public RActionImpl<TakeHelper<RealT_t, RVec<RealT_t>, COLL>> {
884
885public:
886 using ColumnTypes_t = TypeList<RVec<RealT_t>>;
887 TakeHelper(const std::shared_ptr<COLL> &resultColl, const unsigned int nSlots)
888 {
889 fColls.emplace_back(resultColl);
890 for (unsigned int i = 1; i < nSlots; ++i)
891 fColls.emplace_back(std::make_shared<COLL>());
892 }
894 TakeHelper(const TakeHelper &) = delete;
895
896 void InitTask(TTreeReader *, unsigned int) {}
897
898 void Exec(unsigned int slot, RVec<RealT_t> av) { fColls[slot]->emplace_back(av.begin(), av.end()); }
899
900 void Initialize() { /* noop */}
901
902 void Finalize()
903 {
904 auto rColl = fColls[0];
905 for (unsigned int i = 1; i < fColls.size(); ++i) {
906 auto &coll = fColls[i];
907 for (auto &v : *coll) {
908 rColl->emplace_back(v);
909 }
910 }
911 }
912
913 std::string GetActionName() { return "Take"; }
914
915 TakeHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
916 {
917 auto &result = *static_cast<std::shared_ptr<COLL> *>(newResult);
918 result->clear();
919 return TakeHelper(result, fColls.size());
920 }
921};
922
923// Case 4.: The column is an RVec, the collection is a vector
924// Optimisations, transformations from RVecs to vectors
925template <typename RealT_t>
926class R__CLING_PTRCHECK(off) TakeHelper<RealT_t, RVec<RealT_t>, std::vector<RealT_t>>
927 : public RActionImpl<TakeHelper<RealT_t, RVec<RealT_t>, std::vector<RealT_t>>> {
928
930
931public:
932 using ColumnTypes_t = TypeList<RVec<RealT_t>>;
933 TakeHelper(const std::shared_ptr<std::vector<std::vector<RealT_t>>> &resultColl, const unsigned int nSlots)
934 {
935 fColls.emplace_back(resultColl);
936 for (unsigned int i = 1; i < nSlots; ++i) {
937 auto v = std::make_shared<std::vector<RealT_t>>();
938 v->reserve(1024);
939 fColls.emplace_back(v);
940 }
941 }
943 TakeHelper(const TakeHelper &) = delete;
944
945 void InitTask(TTreeReader *, unsigned int) {}
946
947 void Exec(unsigned int slot, RVec<RealT_t> av) { fColls[slot]->emplace_back(av.begin(), av.end()); }
948
949 void Initialize() { /* noop */}
950
951 // This is optimised to treat vectors
952 void Finalize()
953 {
954 ULong64_t totSize = 0;
955 for (auto &coll : fColls)
956 totSize += coll->size();
957 auto rColl = fColls[0];
958 rColl->reserve(totSize);
959 for (unsigned int i = 1; i < fColls.size(); ++i) {
960 auto &coll = fColls[i];
961 rColl->insert(rColl->end(), coll->begin(), coll->end());
962 }
963 }
964
965 std::string GetActionName() { return "Take"; }
966
967 TakeHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
968 {
969 auto &result = *static_cast<typename decltype(fColls)::value_type *>(newResult);
970 result->clear();
971 return TakeHelper(result, fColls.size());
972 }
973};
974
975// Extern templates for TakeHelper
976// NOTE: The move-constructor of specializations declared as extern templates
977// must be defined out of line, otherwise cling fails to find its symbol.
978template <typename RealT_t, typename T, typename COLL>
980template <typename RealT_t, typename T>
982template <typename RealT_t, typename COLL>
984template <typename RealT_t>
985TakeHelper<RealT_t, RVec<RealT_t>, std::vector<RealT_t>>::TakeHelper(TakeHelper<RealT_t, RVec<RealT_t>, std::vector<RealT_t>> &&) = default;
986
987// External templates are disabled for gcc5 since this version wrongly omits the C++11 ABI attribute
988#if __GNUC__ > 5
989extern template class TakeHelper<bool, bool, std::vector<bool>>;
993extern template class TakeHelper<int, int, std::vector<int>>;
994extern template class TakeHelper<long, long, std::vector<long>>;
998#endif
999
1000template <typename ResultType>
1001class R__CLING_PTRCHECK(off) MinHelper : public RActionImpl<MinHelper<ResultType>> {
1002 std::shared_ptr<ResultType> fResultMin;
1004
1005public:
1006 MinHelper(MinHelper &&) = default;
1007 MinHelper(const std::shared_ptr<ResultType> &minVPtr, const unsigned int nSlots)
1008 : fResultMin(minVPtr), fMins(nSlots, std::numeric_limits<ResultType>::max())
1009 {
1010 }
1011
1012 void Exec(unsigned int slot, ResultType v) { fMins[slot] = std::min(v, fMins[slot]); }
1013
1014 void InitTask(TTreeReader *, unsigned int) {}
1015
1017 void Exec(unsigned int slot, const T &vs)
1018 {
1019 for (auto &&v : vs)
1020 fMins[slot] = std::min(static_cast<ResultType>(v), fMins[slot]);
1021 }
1022
1023 void Initialize() { /* noop */}
1024
1025 void Finalize()
1026 {
1027 *fResultMin = std::numeric_limits<ResultType>::max();
1028 for (auto &m : fMins)
1029 *fResultMin = std::min(m, *fResultMin);
1030 }
1031
1032 // Helper functions for RMergeableValue
1033 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1034 {
1035 return std::make_unique<RMergeableMin<ResultType>>(*fResultMin);
1036 }
1037
1038 ResultType &PartialUpdate(unsigned int slot) { return fMins[slot]; }
1039
1040 std::string GetActionName() { return "Min"; }
1041
1042 MinHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1043 {
1044 auto &result = *static_cast<std::shared_ptr<ResultType> *>(newResult);
1045 return MinHelper(result, fMins.size());
1046 }
1047};
1048
1049template <typename ResultType>
1050class R__CLING_PTRCHECK(off) MaxHelper : public RActionImpl<MaxHelper<ResultType>> {
1051 std::shared_ptr<ResultType> fResultMax;
1053
1054public:
1055 MaxHelper(MaxHelper &&) = default;
1056 MaxHelper(const MaxHelper &) = delete;
1057 MaxHelper(const std::shared_ptr<ResultType> &maxVPtr, const unsigned int nSlots)
1058 : fResultMax(maxVPtr), fMaxs(nSlots, std::numeric_limits<ResultType>::lowest())
1059 {
1060 }
1061
1062 void InitTask(TTreeReader *, unsigned int) {}
1063 void Exec(unsigned int slot, ResultType v) { fMaxs[slot] = std::max(v, fMaxs[slot]); }
1064
1066 void Exec(unsigned int slot, const T &vs)
1067 {
1068 for (auto &&v : vs)
1069 fMaxs[slot] = std::max(static_cast<ResultType>(v), fMaxs[slot]);
1070 }
1071
1072 void Initialize() { /* noop */}
1073
1074 void Finalize()
1075 {
1076 *fResultMax = std::numeric_limits<ResultType>::lowest();
1077 for (auto &m : fMaxs) {
1078 *fResultMax = std::max(m, *fResultMax);
1079 }
1080 }
1081
1082 // Helper functions for RMergeableValue
1083 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1084 {
1085 return std::make_unique<RMergeableMax<ResultType>>(*fResultMax);
1086 }
1087
1088 ResultType &PartialUpdate(unsigned int slot) { return fMaxs[slot]; }
1089
1090 std::string GetActionName() { return "Max"; }
1091
1092 MaxHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1093 {
1094 auto &result = *static_cast<std::shared_ptr<ResultType> *>(newResult);
1095 return MaxHelper(result, fMaxs.size());
1096 }
1097};
1098
1099template <typename ResultType>
1100class R__CLING_PTRCHECK(off) SumHelper : public RActionImpl<SumHelper<ResultType>> {
1101 std::shared_ptr<ResultType> fResultSum;
1104
1105 /// Evaluate neutral element for this type and the sum operation.
1106 /// This is assumed to be any_value - any_value if operator- is defined
1107 /// for the type, otherwise a default-constructed ResultType{} is used.
1108 template <typename T = ResultType>
1109 auto NeutralElement(const T &v, int /*overloadresolver*/) -> decltype(v - v)
1110 {
1111 return v - v;
1112 }
1113
1114 template <typename T = ResultType, typename Dummy = int>
1115 ResultType NeutralElement(const T &, Dummy) // this overload has lower priority thanks to the template arg
1116 {
1117 return ResultType{};
1118 }
1119
1120public:
1121 SumHelper(SumHelper &&) = default;
1122 SumHelper(const SumHelper &) = delete;
1123 SumHelper(const std::shared_ptr<ResultType> &sumVPtr, const unsigned int nSlots)
1126 {
1127 }
1128 void InitTask(TTreeReader *, unsigned int) {}
1129
1130 void Exec(unsigned int slot, ResultType x)
1131 {
1132 // Kahan Sum:
1134 ResultType t = fSums[slot] + y;
1135 fCompensations[slot] = (t - fSums[slot]) - y;
1136 fSums[slot] = t;
1137 }
1138
1140 void Exec(unsigned int slot, const T &vs)
1141 {
1142 for (auto &&v : vs) {
1143 Exec(slot, v);
1144 }
1145 }
1146
1147 void Initialize() { /* noop */}
1148
1149 void Finalize()
1150 {
1155 for (auto &m : fSums) {
1156 // Kahan Sum:
1157 y = m - compensation;
1158 t = sum + y;
1159 compensation = (t - sum) - y;
1160 sum = t;
1161 }
1162 *fResultSum += sum;
1163 }
1164
1165 // Helper functions for RMergeableValue
1166 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1167 {
1168 return std::make_unique<RMergeableSum<ResultType>>(*fResultSum);
1169 }
1170
1171 ResultType &PartialUpdate(unsigned int slot) { return fSums[slot]; }
1172
1173 std::string GetActionName() { return "Sum"; }
1174
1175 SumHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1176 {
1177 auto &result = *static_cast<std::shared_ptr<ResultType> *>(newResult);
1178 *result = NeutralElement(*result, -1);
1179 return SumHelper(result, fSums.size());
1180 }
1181};
1182
1183class R__CLING_PTRCHECK(off) MeanHelper : public RActionImpl<MeanHelper> {
1184 std::shared_ptr<double> fResultMean;
1185 std::vector<ULong64_t> fCounts;
1186 std::vector<double> fSums;
1187 std::vector<double> fPartialMeans;
1188 std::vector<double> fCompensations;
1189
1190public:
1191 MeanHelper(const std::shared_ptr<double> &meanVPtr, const unsigned int nSlots);
1192 MeanHelper(MeanHelper &&) = default;
1193 MeanHelper(const MeanHelper &) = delete;
1194 void InitTask(TTreeReader *, unsigned int) {}
1195 void Exec(unsigned int slot, double v);
1196
1198 void Exec(unsigned int slot, const T &vs)
1199 {
1200 for (auto &&v : vs) {
1201
1202 fCounts[slot]++;
1203 // Kahan Sum:
1204 double y = v - fCompensations[slot];
1205 double t = fSums[slot] + y;
1206 fCompensations[slot] = (t - fSums[slot]) - y;
1207 fSums[slot] = t;
1208 }
1209 }
1210
1211 void Initialize() { /* noop */}
1212
1213 void Finalize();
1214
1215 // Helper functions for RMergeableValue
1216 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1217 {
1218 const ULong64_t counts = std::accumulate(fCounts.begin(), fCounts.end(), 0ull);
1219 return std::make_unique<RMergeableMean>(*fResultMean, counts);
1220 }
1221
1222 double &PartialUpdate(unsigned int slot);
1223
1224 std::string GetActionName() { return "Mean"; }
1225
1226 MeanHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1227 {
1228 auto &result = *static_cast<std::shared_ptr<double> *>(newResult);
1229 return MeanHelper(result, fSums.size());
1230 }
1231};
1232
1233class R__CLING_PTRCHECK(off) StdDevHelper : public RActionImpl<StdDevHelper> {
1234 // Number of subsets of data
1235 unsigned int fNSlots;
1236 std::shared_ptr<double> fResultStdDev;
1237 // Number of element for each slot
1238 std::vector<ULong64_t> fCounts;
1239 // Mean of each slot
1240 std::vector<double> fMeans;
1241 // Squared distance from the mean
1242 std::vector<double> fDistancesfromMean;
1243
1244public:
1245 StdDevHelper(const std::shared_ptr<double> &meanVPtr, const unsigned int nSlots);
1246 StdDevHelper(StdDevHelper &&) = default;
1247 StdDevHelper(const StdDevHelper &) = delete;
1248 void InitTask(TTreeReader *, unsigned int) {}
1249 void Exec(unsigned int slot, double v);
1250
1252 void Exec(unsigned int slot, const T &vs)
1253 {
1254 for (auto &&v : vs) {
1255 Exec(slot, v);
1256 }
1257 }
1258
1259 void Initialize() { /* noop */}
1260
1261 void Finalize();
1262
1263 // Helper functions for RMergeableValue
1264 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1265 {
1266 const ULong64_t counts = std::accumulate(fCounts.begin(), fCounts.end(), 0ull);
1267 const Double_t mean =
1268 std::inner_product(fMeans.begin(), fMeans.end(), fCounts.begin(), 0.) / static_cast<Double_t>(counts);
1269 return std::make_unique<RMergeableStdDev>(*fResultStdDev, counts, mean);
1270 }
1271
1272 std::string GetActionName() { return "StdDev"; }
1273
1274 StdDevHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1275 {
1276 auto &result = *static_cast<std::shared_ptr<double> *>(newResult);
1277 return StdDevHelper(result, fCounts.size());
1278 }
1279};
1280
1281template <typename PrevNodeType>
1282class R__CLING_PTRCHECK(off) DisplayHelper : public RActionImpl<DisplayHelper<PrevNodeType>> {
1283private:
1285 std::shared_ptr<Display_t> fDisplayerHelper;
1286 std::shared_ptr<PrevNodeType> fPrevNode;
1287 size_t fEntriesToProcess;
1288
1289public:
1290 DisplayHelper(size_t nRows, const std::shared_ptr<Display_t> &d, const std::shared_ptr<PrevNodeType> &prevNode)
1291 : fDisplayerHelper(d), fPrevNode(prevNode), fEntriesToProcess(nRows)
1292 {
1293 }
1294 DisplayHelper(DisplayHelper &&) = default;
1295 DisplayHelper(const DisplayHelper &) = delete;
1296 void InitTask(TTreeReader *, unsigned int) {}
1297
1298 template <typename... Columns>
1299 void Exec(unsigned int, Columns &... columns)
1300 {
1301 if (fEntriesToProcess == 0)
1302 return;
1303
1304 fDisplayerHelper->AddRow(columns...);
1305 --fEntriesToProcess;
1306
1307 if (fEntriesToProcess == 0) {
1308 // No more entries to process. Send a one-time signal that this node
1309 // of the graph is done. It is important that the 'StopProcessing'
1310 // method is only called once from this helper, otherwise it would seem
1311 // like more than one operation has completed its work.
1312 fPrevNode->StopProcessing();
1313 }
1314 }
1315
1316 void Initialize() {}
1317
1318 void Finalize() {}
1319
1320 std::string GetActionName() { return "Display"; }
1321};
1322
1323template <typename T>
1324void *GetData(ROOT::VecOps::RVec<T> &v)
1325{
1326 return v.data();
1327}
1328
1329template <typename T>
1330void *GetData(T & /*v*/)
1331{
1332 return nullptr;
1333}
1334
1335template <typename T>
1336void SetBranchesHelper(TTree *inputTree, TTree &outputTree, const std::string &inName, const std::string &name,
1337 TBranch *&branch, void *&branchAddress, T *address, RBranchSet &outputBranches,
1338 bool /*isDefine*/)
1339{
1340 static TClassRef TBOClRef("TBranchObject");
1341
1342 TBranch *inputBranch = nullptr;
1343 if (inputTree) {
1344 inputBranch = inputTree->GetBranch(inName.c_str());
1345 if (!inputBranch) // try harder
1346 inputBranch = inputTree->FindBranch(inName.c_str());
1347 }
1348
1349 auto *outputBranch = outputBranches.Get(name);
1350 if (outputBranch) {
1351 // the output branch was already created, we just need to (re)set its address
1352 if (inputBranch && inputBranch->IsA() == TBOClRef) {
1353 outputBranch->SetAddress(reinterpret_cast<T **>(inputBranch->GetAddress()));
1354 } else if (outputBranch->IsA() != TBranch::Class()) {
1355 branchAddress = address;
1356 outputBranch->SetAddress(&branchAddress);
1357 } else {
1358 outputBranch->SetAddress(address);
1359 branchAddress = address;
1360 }
1361 return;
1362 }
1363
1364 if (inputBranch) {
1365 // Respect the original bufsize and splitlevel arguments
1366 // In particular, by keeping splitlevel equal to 0 if this was the case for `inputBranch`, we avoid
1367 // writing garbage when unsplit objects cannot be written as split objects (e.g. in case of a polymorphic
1368 // TObject branch, see https://bit.ly/2EjLMId ).
1369 const auto bufSize = inputBranch->GetBasketSize();
1370 const auto splitLevel = inputBranch->GetSplitLevel();
1371
1372 if (inputBranch->IsA() == TBOClRef) {
1373 // Need to pass a pointer to pointer
1374 outputBranch =
1375 outputTree.Branch(name.c_str(), reinterpret_cast<T **>(inputBranch->GetAddress()), bufSize, splitLevel);
1376 } else {
1377 outputBranch = outputTree.Branch(name.c_str(), address, bufSize, splitLevel);
1378 }
1379 } else {
1380 outputBranch = outputTree.Branch(name.c_str(), address);
1381 }
1383 // This is not an array branch, so we don't register the address of the output branch here
1384 branch = nullptr;
1385 branchAddress = nullptr;
1386}
1387
1388/// Helper function for SnapshotHelper and SnapshotHelperMT. It creates new branches for the output TTree of a Snapshot.
1389/// This overload is called for columns of type `RVec<T>`. For RDF, these can represent:
1390/// 1. c-style arrays in ROOT files, so we are sure that there are input trees to which we can ask the correct branch
1391/// title
1392/// 2. RVecs coming from a custom column or the input file/data-source
1393/// 3. vectors coming from ROOT files that are being read as RVecs
1394/// 4. TClonesArray
1395///
1396/// In case of 1., we keep aside the pointer to the branch and the pointer to the input value (in `branch` and
1397/// `branchAddress`) so we can intercept changes in the address of the input branch and tell the output branch.
1398template <typename T>
1399void SetBranchesHelper(TTree *inputTree, TTree &outputTree, const std::string &inName, const std::string &outName,
1401{
1402 TBranch *inputBranch = nullptr;
1403 if (inputTree) {
1404 inputBranch = inputTree->GetBranch(inName.c_str());
1405 if (!inputBranch) // try harder
1406 inputBranch = inputTree->FindBranch(inName.c_str());
1407 }
1408 auto *outputBranch = outputBranches.Get(outName);
1409
1410 // if no backing input branch, we must write out an RVec
1411 bool mustWriteRVec = (inputBranch == nullptr || isDefine);
1412 // otherwise, if input branch is TClonesArray, must write out an RVec
1413 if (!mustWriteRVec && std::string_view(inputBranch->GetClassName()) == "TClonesArray") {
1414 mustWriteRVec = true;
1415 Warning("Snapshot",
1416 "Branch \"%s\" contains TClonesArrays but the type specified to Snapshot was RVec<T>. The branch will "
1417 "be written out as a RVec instead of a TClonesArray. Specify that the type of the branch is "
1418 "TClonesArray as a Snapshot template parameter to write out a TClonesArray instead.",
1419 inName.c_str());
1420 }
1421 // otherwise, if input branch is a std::vector or RVec, must write out an RVec
1422 if (!mustWriteRVec) {
1423 const auto STLKind = TClassEdit::IsSTLCont(inputBranch->GetClassName());
1424 if (STLKind == ROOT::ESTLType::kSTLvector || STLKind == ROOT::ESTLType::kROOTRVec)
1425 mustWriteRVec = true;
1426 }
1427
1428 if (mustWriteRVec) {
1429 // Treat:
1430 // 2. RVec coming from a custom column or a source
1431 // 3. RVec coming from a column on disk of type vector (the RVec is adopting the data of that vector)
1432 // 4. TClonesArray written out as RVec<T>
1433 if (outputBranch) {
1434 // needs to be SetObject (not SetAddress) to mimic what happens when this TBranchElement is constructed
1435 outputBranch->SetObject(ab);
1436 } else {
1437 auto *b = outputTree.Branch(outName.c_str(), ab);
1438 outputBranches.Insert(outName, b);
1439 }
1440 return;
1441 }
1442
1443 // else this must be a C-array, aka case 1.
1444 auto dataPtr = ab->data();
1445
1446 if (outputBranch) {
1447 if (outputBranch->IsA() != TBranch::Class()) {
1449 outputBranch->SetAddress(&branchAddress);
1450 } else {
1451 outputBranch->SetAddress(dataPtr);
1452 }
1453 } else {
1454 // must construct the leaflist for the output branch and create the branch in the output tree
1455 auto *const leaf = static_cast<TLeaf *>(inputBranch->GetListOfLeaves()->UncheckedAt(0));
1456 const auto bname = leaf->GetName();
1457 auto *sizeLeaf = leaf->GetLeafCount();
1458 const auto sizeLeafName = sizeLeaf ? std::string(sizeLeaf->GetName()) : std::to_string(leaf->GetLenStatic());
1459
1460 if (sizeLeaf && !outputBranches.Get(sizeLeafName)) {
1461 // The output array branch `bname` has dynamic size stored in leaf `sizeLeafName`, but that leaf has not been
1462 // added to the output tree yet. However, the size leaf has to be available for the creation of the array
1463 // branch to be successful. So we create the size leaf here.
1464 const auto sizeTypeStr = TypeName2ROOTTypeName(sizeLeaf->GetTypeName());
1465 const auto sizeBufSize = sizeLeaf->GetBranch()->GetBasketSize();
1466 // The null branch address is a placeholder. It will be set when SetBranchesHelper is called for `sizeLeafName`
1467 auto *sizeBranch = outputTree.Branch(sizeLeafName.c_str(), (void *)nullptr,
1468 (sizeLeafName + '/' + sizeTypeStr).c_str(), sizeBufSize);
1470 }
1471
1472 const auto btype = leaf->GetTypeName();
1473 const auto rootbtype = TypeName2ROOTTypeName(btype);
1474 if (rootbtype == ' ') {
1475 Warning("Snapshot",
1476 "RDataFrame::Snapshot: could not correctly construct a leaflist for C-style array in column %s. This "
1477 "column will not be written out.",
1478 bname);
1479 } else {
1480 const auto leaflist = std::string(bname) + "[" + sizeLeafName + "]/" + rootbtype;
1481 outputBranch = outputTree.Branch(outName.c_str(), dataPtr, leaflist.c_str());
1482 outputBranch->SetTitle(inputBranch->GetTitle());
1485 branchAddress = ab->data();
1486 }
1487 }
1488}
1489
1490void ValidateSnapshotOutput(const RSnapshotOptions &opts, const std::string &treeName, const std::string &fileName);
1491
1492/// Helper object for a single-thread Snapshot action
1493template <typename... ColTypes>
1494class R__CLING_PTRCHECK(off) SnapshotHelper : public RActionImpl<SnapshotHelper<ColTypes...>> {
1495 std::string fFileName;
1496 std::string fDirName;
1497 std::string fTreeName;
1498 RSnapshotOptions fOptions;
1499 std::unique_ptr<TFile> fOutputFile;
1500 std::unique_ptr<TTree> fOutputTree; // must be a ptr because TTrees are not copy/move constructible
1501 bool fBranchAddressesNeedReset{true};
1502 ColumnNames_t fInputBranchNames; // This contains the resolved aliases
1503 ColumnNames_t fOutputBranchNames;
1504 TTree *fInputTree = nullptr; // Current input tree. Set at initialization time (`InitTask`)
1505 // TODO we might be able to unify fBranches, fBranchAddresses and fOutputBranches
1506 std::vector<TBranch *> fBranches; // Addresses of branches in output, non-null only for the ones holding C arrays
1507 std::vector<void *> fBranchAddresses; // Addresses of objects associated to output branches
1509 std::vector<bool> fIsDefine;
1510
1511public:
1512 using ColumnTypes_t = TypeList<ColTypes...>;
1513 SnapshotHelper(std::string_view filename, std::string_view dirname, std::string_view treename,
1514 const ColumnNames_t &vbnames, const ColumnNames_t &bnames, const RSnapshotOptions &options,
1515 std::vector<bool> &&isDefine)
1516 : fFileName(filename), fDirName(dirname), fTreeName(treename), fOptions(options), fInputBranchNames(vbnames),
1518 fBranchAddresses(vbnames.size(), nullptr), fIsDefine(std::move(isDefine))
1519 {
1520 ValidateSnapshotOutput(fOptions, fTreeName, fFileName);
1521 }
1522
1523 SnapshotHelper(const SnapshotHelper &) = delete;
1524 SnapshotHelper(SnapshotHelper &&) = default;
1526 {
1527 if (!fTreeName.empty() /*not moved from*/ && !fOutputFile /* did not run */ && fOptions.fLazy) {
1528 const auto fileOpenMode = [&]() {
1529 TString checkupdate = fOptions.fMode;
1530 checkupdate.ToLower();
1531 return checkupdate == "update" ? "updated" : "created";
1532 }();
1533 Warning("Snapshot",
1534 "A lazy Snapshot action was booked but never triggered. The tree '%s' in output file '%s' was not %s. "
1535 "In case it was desired instead, remember to trigger the Snapshot operation, by storing "
1536 "its result in a variable and for example calling the GetValue() method on it.",
1537 fTreeName.c_str(), fFileName.c_str(), fileOpenMode);
1538 }
1539 }
1540
1541 void InitTask(TTreeReader *r, unsigned int /* slot */)
1542 {
1543 if (r)
1544 fInputTree = r->GetTree();
1546 }
1547
1548 void Exec(unsigned int /* slot */, ColTypes &... values)
1549 {
1550 using ind_t = std::index_sequence_for<ColTypes...>;
1552 UpdateCArraysPtrs(values..., ind_t{});
1553 } else {
1554 SetBranches(values..., ind_t{});
1556 }
1557 fOutputTree->Fill();
1558 }
1559
1560 template <std::size_t... S>
1561 void UpdateCArraysPtrs(ColTypes &... values, std::index_sequence<S...> /*dummy*/)
1562 {
1563 // This code deals with branches which hold C arrays of variable size. It can happen that the buffers
1564 // associated to those is re-allocated. As a result the value of the pointer can change therewith
1565 // leaving associated to the branch of the output tree an invalid pointer.
1566 // With this code, we set the value of the pointer in the output branch anew when needed.
1567 // Nota bene: the extra ",0" after the invocation of SetAddress, is because that method returns void and
1568 // we need an int for the expander list.
1569 int expander[] = {(fBranches[S] && fBranchAddresses[S] != GetData(values)
1570 ? fBranches[S]->SetAddress(GetData(values)),
1571 fBranchAddresses[S] = GetData(values), 0 : 0, 0)...,
1572 0};
1573 (void)expander; // avoid unused variable warnings for older compilers such as gcc 4.9
1574 }
1575
1576 template <std::size_t... S>
1577 void SetBranches(ColTypes &... values, std::index_sequence<S...> /*dummy*/)
1578 {
1579 // create branches in output tree
1581 fBranches[S], fBranchAddresses[S], &values, fOutputBranches, fIsDefine[S]),
1582 0)...,
1583 0};
1584 fOutputBranches.AssertNoNullBranchAddresses();
1585 (void)expander; // avoid unused variable warnings for older compilers such as gcc 4.9
1586 }
1587
1588 void Initialize()
1589 {
1590 fOutputFile.reset(
1591 TFile::Open(fFileName.c_str(), fOptions.fMode.c_str(), /*ftitle=*/"",
1593 if(!fOutputFile)
1594 throw std::runtime_error("Snapshot: could not create output file " + fFileName);
1595
1596 TDirectory *outputDir = fOutputFile.get();
1597 if (!fDirName.empty()) {
1598 TString checkupdate = fOptions.fMode;
1599 checkupdate.ToLower();
1600 if (checkupdate == "update")
1601 outputDir = fOutputFile->mkdir(fDirName.c_str(), "", true); // do not overwrite existing directory
1602 else
1603 outputDir = fOutputFile->mkdir(fDirName.c_str());
1604 }
1605
1606 fOutputTree =
1607 std::make_unique<TTree>(fTreeName.c_str(), fTreeName.c_str(), fOptions.fSplitLevel, /*dir=*/outputDir);
1608
1609 if (fOptions.fAutoFlush)
1610 fOutputTree->SetAutoFlush(fOptions.fAutoFlush);
1611 }
1612
1613 void Finalize()
1614 {
1615 assert(fOutputTree != nullptr);
1616 assert(fOutputFile != nullptr);
1617
1618 // use AutoSave to flush TTree contents because TTree::Write writes in gDirectory, not in fDirectory
1619 fOutputTree->AutoSave("flushbaskets");
1620 // must destroy the TTree first, otherwise TFile will delete it too leading to a double delete
1621 fOutputTree.reset();
1622 fOutputFile->Close();
1623 }
1624
1625 std::string GetActionName() { return "Snapshot"; }
1626
1627 ROOT::RDF::SampleCallback_t GetSampleCallback() final
1628 {
1629 return [this](unsigned int, const RSampleInfo &) mutable { fBranchAddressesNeedReset = true; };
1630 }
1631
1632 /**
1633 * @brief Create a new SnapshotHelper with a different output file name
1634 *
1635 * @param newName A type-erased string with the output file name
1636 * @return SnapshotHelper
1637 *
1638 * This MakeNew implementation is tied to the cloning feature of actions
1639 * of the computation graph. In particular, cloning a Snapshot node usually
1640 * also involves changing the name of the output file, otherwise the cloned
1641 * Snapshot would overwrite the same file.
1642 */
1643 SnapshotHelper MakeNew(void *newName, std::string_view /*variation*/ = "nominal")
1644 {
1645 const std::string finalName = *reinterpret_cast<const std::string *>(newName);
1646 return SnapshotHelper{
1647 finalName, fDirName, fTreeName, fInputBranchNames, fOutputBranchNames, fOptions, std::vector<bool>(fIsDefine)};
1648 }
1649};
1650
1651/// Helper object for a multi-thread Snapshot action
1652template <typename... ColTypes>
1653class R__CLING_PTRCHECK(off) SnapshotHelperMT : public RActionImpl<SnapshotHelperMT<ColTypes...>> {
1654 unsigned int fNSlots;
1655 std::unique_ptr<ROOT::TBufferMerger> fMerger; // must use a ptr because TBufferMerger is not movable
1656 std::vector<std::shared_ptr<ROOT::TBufferMergerFile>> fOutputFiles;
1657 std::vector<std::unique_ptr<TTree>> fOutputTrees;
1658 std::vector<int> fBranchAddressesNeedReset; // vector<bool> does not allow concurrent writing of different elements
1659 std::string fFileName; // name of the output file name
1660 std::string fDirName; // name of TFile subdirectory in which output must be written (possibly empty)
1661 std::string fTreeName; // name of output tree
1662 RSnapshotOptions fOptions; // struct holding options to pass down to TFile and TTree in this action
1663 ColumnNames_t fInputBranchNames; // This contains the resolved aliases
1664 ColumnNames_t fOutputBranchNames;
1665 std::vector<TTree *> fInputTrees; // Current input trees. Set at initialization time (`InitTask`)
1666 // Addresses of branches in output per slot, non-null only for the ones holding C arrays
1667 std::vector<std::vector<TBranch *>> fBranches;
1668 // Addresses associated to output branches per slot, non-null only for the ones holding C arrays
1669 std::vector<std::vector<void *>> fBranchAddresses;
1670 std::vector<RBranchSet> fOutputBranches;
1671 std::vector<bool> fIsDefine;
1672
1673public:
1674 using ColumnTypes_t = TypeList<ColTypes...>;
1675 SnapshotHelperMT(const unsigned int nSlots, std::string_view filename, std::string_view dirname,
1676 std::string_view treename, const ColumnNames_t &vbnames, const ColumnNames_t &bnames,
1677 const RSnapshotOptions &options, std::vector<bool> &&isDefine)
1678 : fNSlots(nSlots), fOutputFiles(fNSlots), fOutputTrees(fNSlots), fBranchAddressesNeedReset(fNSlots, 1),
1679 fFileName(filename), fDirName(dirname), fTreeName(treename), fOptions(options), fInputBranchNames(vbnames),
1680 fOutputBranchNames(ReplaceDotWithUnderscore(bnames)), fInputTrees(fNSlots),
1681 fBranches(fNSlots, std::vector<TBranch *>(vbnames.size(), nullptr)),
1682 fBranchAddresses(fNSlots, std::vector<void *>(vbnames.size(), nullptr)), fOutputBranches(fNSlots),
1683 fIsDefine(std::move(isDefine))
1684 {
1685 ValidateSnapshotOutput(fOptions, fTreeName, fFileName);
1686 }
1687 SnapshotHelperMT(const SnapshotHelperMT &) = delete;
1688 SnapshotHelperMT(SnapshotHelperMT &&) = default;
1690 {
1691 if (!fTreeName.empty() /*not moved from*/ && fOptions.fLazy && !fOutputFiles.empty() &&
1692 std::all_of(fOutputFiles.begin(), fOutputFiles.end(), [](const auto &f) { return !f; }) /* never run */) {
1693 const auto fileOpenMode = [&]() {
1694 TString checkupdate = fOptions.fMode;
1695 checkupdate.ToLower();
1696 return checkupdate == "update" ? "updated" : "created";
1697 }();
1698 Warning("Snapshot",
1699 "A lazy Snapshot action was booked but never triggered. The tree '%s' in output file '%s' was not %s. "
1700 "In case it was desired instead, remember to trigger the Snapshot operation, by storing "
1701 "its result in a variable and for example calling the GetValue() method on it.",
1702 fTreeName.c_str(), fFileName.c_str(), fileOpenMode);
1703 }
1704 }
1705
1706 void InitTask(TTreeReader *r, unsigned int slot)
1707 {
1708 ::TDirectory::TContext c; // do not let tasks change the thread-local gDirectory
1709 if (!fOutputFiles[slot]) {
1710 // first time this thread executes something, let's create a TBufferMerger output directory
1711 fOutputFiles[slot] = fMerger->GetFile();
1712 }
1714 if (!fDirName.empty()) {
1715 // call returnExistingDirectory=true since MT can end up making this call multiple times
1716 treeDirectory = fOutputFiles[slot]->mkdir(fDirName.c_str(), "", true);
1717 }
1718 // re-create output tree as we need to create its branches again, with new input variables
1719 // TODO we could instead create the output tree and its branches, change addresses of input variables in each task
1721 std::make_unique<TTree>(fTreeName.c_str(), fTreeName.c_str(), fOptions.fSplitLevel, /*dir=*/treeDirectory);
1723 // TODO can be removed when RDF supports interleaved TBB task execution properly, see ROOT-10269
1724 fOutputTrees[slot]->SetImplicitMT(false);
1725 if (fOptions.fAutoFlush)
1726 fOutputTrees[slot]->SetAutoFlush(fOptions.fAutoFlush);
1727 if (r) {
1728 // not an empty-source RDF
1729 fInputTrees[slot] = r->GetTree();
1730 }
1731 fBranchAddressesNeedReset[slot] = 1; // reset first event flag for this slot
1732 }
1733
1734 void FinalizeTask(unsigned int slot)
1735 {
1736 if (fOutputTrees[slot]->GetEntries() > 0)
1737 fOutputFiles[slot]->Write();
1738 // clear now to avoid concurrent destruction of output trees and input tree (which has them listed as fClones)
1739 fOutputTrees[slot].reset(nullptr);
1740 fOutputBranches[slot].Clear();
1741 }
1742
1743 void Exec(unsigned int slot, ColTypes &... values)
1744 {
1745 using ind_t = std::index_sequence_for<ColTypes...>;
1746 if (fBranchAddressesNeedReset[slot] == 0) {
1747 UpdateCArraysPtrs(slot, values..., ind_t{});
1748 } else {
1749 SetBranches(slot, values..., ind_t{});
1751 }
1752 fOutputTrees[slot]->Fill();
1753 auto entries = fOutputTrees[slot]->GetEntries();
1754 auto autoFlush = fOutputTrees[slot]->GetAutoFlush();
1755 if ((autoFlush > 0) && (entries % autoFlush == 0))
1756 fOutputFiles[slot]->Write();
1757 }
1758
1759 template <std::size_t... S>
1760 void UpdateCArraysPtrs(unsigned int slot, ColTypes &... values, std::index_sequence<S...> /*dummy*/)
1761 {
1762 // This code deals with branches which hold C arrays of variable size. It can happen that the buffers
1763 // associated to those is re-allocated. As a result the value of the pointer can change therewith
1764 // leaving associated to the branch of the output tree an invalid pointer.
1765 // With this code, we set the value of the pointer in the output branch anew when needed.
1766 // Nota bene: the extra ",0" after the invocation of SetAddress, is because that method returns void and
1767 // we need an int for the expander list.
1768 int expander[] = {(fBranches[slot][S] && fBranchAddresses[slot][S] != GetData(values)
1769 ? fBranches[slot][S]->SetAddress(GetData(values)),
1770 fBranchAddresses[slot][S] = GetData(values), 0 : 0, 0)...,
1771 0};
1772 (void)expander; // avoid unused parameter warnings (gcc 12.1)
1773 (void)slot; // Also "slot" might be unused, in case "values" is empty
1774 }
1775
1776 template <std::size_t... S>
1777 void SetBranches(unsigned int slot, ColTypes &... values, std::index_sequence<S...> /*dummy*/)
1778 {
1779 // hack to call TTree::Branch on all variadic template arguments
1780 int expander[] = {(SetBranchesHelper(fInputTrees[slot], *fOutputTrees[slot], fInputBranchNames[S],
1781 fOutputBranchNames[S], fBranches[slot][S], fBranchAddresses[slot][S],
1782 &values, fOutputBranches[slot], fIsDefine[S]),
1783 0)...,
1784 0};
1785 fOutputBranches[slot].AssertNoNullBranchAddresses();
1786 (void)expander; // avoid unused parameter warnings (gcc 12.1)
1787 }
1788
1789 void Initialize()
1790 {
1792 auto out_file = TFile::Open(fFileName.c_str(), fOptions.fMode.c_str(), /*ftitle=*/fFileName.c_str(), cs);
1793 if(!out_file)
1794 throw std::runtime_error("Snapshot: could not create output file " + fFileName);
1795 fMerger = std::make_unique<ROOT::TBufferMerger>(std::unique_ptr<TFile>(out_file));
1796 }
1797
1798 void Finalize()
1799 {
1800 assert(std::any_of(fOutputFiles.begin(), fOutputFiles.end(), [](const auto &ptr) { return ptr != nullptr; }));
1801
1802 auto fileWritten = false;
1803 for (auto &file : fOutputFiles) {
1804 if (file) {
1805 file->Write();
1806 file->Close();
1807 fileWritten = true;
1808 }
1809 }
1810
1811 if (!fileWritten) {
1812 Warning("Snapshot",
1813 "No input entries (input TTree was empty or no entry passed the Filters). Output TTree is empty.");
1814 }
1815
1816 // flush all buffers to disk by destroying the TBufferMerger
1817 fOutputFiles.clear();
1818 fMerger.reset();
1819 }
1820
1821 std::string GetActionName() { return "Snapshot"; }
1822
1823 ROOT::RDF::SampleCallback_t GetSampleCallback() final
1824 {
1825 return [this](unsigned int slot, const RSampleInfo &) mutable { fBranchAddressesNeedReset[slot] = 1; };
1826 }
1827
1828 /**
1829 * @brief Create a new SnapshotHelperMT with a different output file name
1830 *
1831 * @param newName A type-erased string with the output file name
1832 * @return SnapshotHelperMT
1833 *
1834 * This MakeNew implementation is tied to the cloning feature of actions
1835 * of the computation graph. In particular, cloning a Snapshot node usually
1836 * also involves changing the name of the output file, otherwise the cloned
1837 * Snapshot would overwrite the same file.
1838 */
1839 SnapshotHelperMT MakeNew(void *newName, std::string_view /*variation*/ = "nominal")
1840 {
1841 const std::string finalName = *reinterpret_cast<const std::string *>(newName);
1842 return SnapshotHelperMT{fNSlots, finalName, fDirName, fTreeName,
1843 fInputBranchNames, fOutputBranchNames, fOptions, std::vector<bool>(fIsDefine)};
1844 }
1845};
1846
1847template <typename Acc, typename Merge, typename R, typename T, typename U,
1848 bool MustCopyAssign = std::is_same<R, U>::value>
1850 : public RActionImpl<AggregateHelper<Acc, Merge, R, T, U, MustCopyAssign>> {
1852 Merge fMerge;
1853 std::shared_ptr<U> fResult;
1855
1856public:
1857 using ColumnTypes_t = TypeList<T>;
1858
1859 AggregateHelper(Acc &&f, Merge &&m, const std::shared_ptr<U> &result, const unsigned int nSlots)
1860 : fAggregate(std::move(f)), fMerge(std::move(m)), fResult(result), fAggregators(nSlots, *result)
1861 {
1862 }
1863
1864 AggregateHelper(Acc &f, Merge &m, const std::shared_ptr<U> &result, const unsigned int nSlots)
1865 : fAggregate(f), fMerge(m), fResult(result), fAggregators(nSlots, *result)
1866 {
1867 }
1868
1869 AggregateHelper(AggregateHelper &&) = default;
1870 AggregateHelper(const AggregateHelper &) = delete;
1871
1872 void InitTask(TTreeReader *, unsigned int) {}
1873
1874 template <bool MustCopyAssign_ = MustCopyAssign, std::enable_if_t<MustCopyAssign_, int> = 0>
1875 void Exec(unsigned int slot, const T &value)
1876 {
1878 }
1879
1880 template <bool MustCopyAssign_ = MustCopyAssign, std::enable_if_t<!MustCopyAssign_, int> = 0>
1881 void Exec(unsigned int slot, const T &value)
1882 {
1884 }
1885
1886 void Initialize() { /* noop */}
1887
1889 bool MergeAll = std::is_same<void, MergeRet>::value>
1890 std::enable_if_t<MergeAll, void> Finalize()
1891 {
1892 fMerge(fAggregators);
1893 *fResult = fAggregators[0];
1894 }
1895
1897 bool MergeTwoByTwo = std::is_same<U, MergeRet>::value>
1898 std::enable_if_t<MergeTwoByTwo, void> Finalize(...) // ... needed to let compiler distinguish overloads
1899 {
1900 for (const auto &acc : fAggregators)
1901 *fResult = fMerge(*fResult, acc);
1902 }
1903
1904 U &PartialUpdate(unsigned int slot) { return fAggregators[slot]; }
1905
1906 std::string GetActionName() { return "Aggregate"; }
1907
1908 AggregateHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1909 {
1910 auto &result = *static_cast<std::shared_ptr<U> *>(newResult);
1911 return AggregateHelper(fAggregate, fMerge, result, fAggregators.size());
1912 }
1913};
1914
1915} // end of NS RDF
1916} // end of NS Internal
1917} // end of NS ROOT
1918
1919/// \endcond
1920
1921#endif
PyObject * fCallable
Handle_t Display_t
Display handle.
Definition GuiTypes.h:27
#define d(i)
Definition RSha256.hxx:102
#define b(i)
Definition RSha256.hxx:100
#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
double Double_t
Definition RtypesCore.h:59
unsigned long long ULong64_t
Definition RtypesCore.h:70
#define X(type, name)
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
void Warning(const char *location, const char *msgfmt,...)
Use this function in warning situations.
Definition TError.cxx:229
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 Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char filename
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 r
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
char name[80]
Definition TGX11.cxx:110
TClass * IsA() const override
Definition TStringLong.h:20
TTime operator*(const TTime &t1, const TTime &t2)
Definition TTime.h:85
Base class for action helpers, see RInterface::Book() for more information.
This class is the textual representation of the content of a columnar dataset.
Definition RDisplay.hxx:65
This type represents a sample identifier, to be used in conjunction with RDataFrame features such as ...
const_iterator begin() const
const_iterator end() const
A "std::vector"-like collection of values implementing handy operation to analyse them.
Definition RVec.hxx:1529
A TTree is a list of TBranches.
Definition TBranch.h:93
static TClass * Class()
TClassRef is used to implement a permanent reference to a TClass object.
Definition TClassRef.h:28
Collection abstract base class.
Definition TCollection.h:65
TDirectory::TContext keeps track and restore the current directory.
Definition TDirectory.h:89
Describe directory structure in memory.
Definition TDirectory.h:45
static TFile * Open(const char *name, Option_t *option="", const char *ftitle="", Int_t compress=ROOT::RCompressionSetting::EDefaults::kUseCompiledDefault, Int_t netopt=0)
Create / open a file.
Definition TFile.cxx:4088
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:693
TH1 is the base class of all histogram classes in ROOT.
Definition TH1.h:59
A TLeaf describes individual elements of a TBranch See TBranch structure in TTree.
Definition TLeaf.h:57
A doubly linked list.
Definition TList.h:38
Statistical variable, defined by its mean and variance (RMS).
Definition TStatistic.h:33
Basic string class.
Definition TString.h:139
A simple, robust and fast interface to read values from ROOT columnar datasets such as TTree,...
Definition TTreeReader.h:46
A TTree represents a columnar dataset.
Definition TTree.h:79
@ kEntriesReshuffled
If set, signals that this TTree is the output of the processing of another TTree, and the entries are...
Definition TTree.h:261
RooCmdArg Columns(Int_t ncol)
Double_t y[n]
Definition legend1.C:17
Double_t x[n]
Definition legend1.C:17
const Int_t n
Definition legend1.C:16
#define H(x, y, z)
CPYCPPYY_EXTERN bool Exec(const std::string &cmd)
Definition API.cxx:455
std::unique_ptr< RMergeableVariations< T > > GetMergeableValue(ROOT::RDF::Experimental::RResultMap< T > &rmap)
Retrieve mergeable values after calling ROOT::RDF::VariationsFor .
std::vector< std::string > ReplaceDotWithUnderscore(const std::vector< std::string > &columnNames)
Replace occurrences of '.
Definition RDFUtils.cxx:314
void ValidateSnapshotOutput(const RSnapshotOptions &opts, const std::string &treeName, const std::string &fileName)
char TypeName2ROOTTypeName(const std::string &b)
Convert type name (e.g.
Definition RDFUtils.cxx:259
constexpr std::size_t FindIdxTrue(const T &arr)
Definition Utils.hxx:228
std::function< void(unsigned int, const ROOT::RDF::RSampleInfo &)> SampleCallback_t
The type of a data-block callback, registered with an RDataFrame computation graph via e....
ROOT type_traits extensions.
tbb::task_arena is an alias of tbb::interface7::task_arena, which doesn't allow to forward declare tb...
@ kROOTRVec
Definition ESTLType.h:46
@ kSTLvector
Definition ESTLType.h:30
int CompressionSettings(RCompressionSetting::EAlgorithm::EValues algorithm, int compressionLevel)
RooArgSet S(Args_t &&... args)
Definition RooArgSet.h:200
ROOT::ESTLType STLKind(std::string_view type)
Converts STL container name to number.
ROOT::ESTLType IsSTLCont(std::string_view type)
type : type name: vector<list<classA,allocator>,allocator> result: 0 : not stl container code of cont...
void Initialize(Bool_t useTMVAStyle=kTRUE)
Definition tmvaglob.cxx:176
A collection of options to steer the creation of the dataset on file.
int fAutoFlush
AutoFlush value for output tree.
std::string fMode
Mode of creation of output file.
ECAlgo fCompressionAlgorithm
Compression algorithm of output file.
int fSplitLevel
Split level of output tree.
bool fLazy
Do not start the event loop when Snapshot is called.
int fCompressionLevel
Compression level of output file.
Lightweight storage for a collection of types.
TMarker m
Definition textangle.C:8
TLine l
Definition textangle.C:4
static uint64_t sum(uint64_t i)
Definition Factory.cxx:2345