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 SnapshotTTreeHelper
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 SnapshotTTreeHelper
39#include "TH1.h"
40#include "TH3.h"
41#include "TGraph.h"
42#include "TGraphAsymmErrors.h"
43#include "TLeaf.h"
44#include "TObject.h"
45#include "TTree.h"
46#include "TTreeReader.h" // for SnapshotTTreeHelper
47#include "TStatistic.h"
51
52#include "ROOT/RNTupleDS.hxx"
53#include "ROOT/RNTupleWriter.hxx" // for SnapshotRNTupleHelper
54#include "ROOT/RTTreeDS.hxx"
55
56#include <algorithm>
57#include <functional>
58#include <limits>
59#include <memory>
60#include <mutex>
61#include <stdexcept>
62#include <string>
63#include <type_traits>
64#include <utility> // std::index_sequence
65#include <vector>
66#include <iomanip>
67#include <numeric> // std::accumulate in MeanHelper
68
69/// \cond HIDDEN_SYMBOLS
70
71namespace ROOT {
72namespace Internal {
73namespace RDF {
74using namespace ROOT::TypeTraits;
75using namespace ROOT::VecOps;
76using namespace ROOT::RDF;
77using namespace ROOT::Detail::RDF;
78
79using Hist_t = ::TH1D;
80
81class RBranchSet {
82 std::vector<TBranch *> fBranches;
83 std::vector<std::string> fNames;
84
85public:
86 TBranch *Get(const std::string &name) const
87 {
88 auto it = std::find(fNames.begin(), fNames.end(), name);
89 if (it == fNames.end())
90 return nullptr;
91 return fBranches[std::distance(fNames.begin(), it)];
92 }
93
94 void Insert(const std::string &name, TBranch *address)
95 {
96 if (address == nullptr) {
97 throw std::logic_error("Trying to insert a null branch address.");
98 }
99 if (std::find(fBranches.begin(), fBranches.end(), address) != fBranches.end()) {
100 throw std::logic_error("Trying to insert a branch address that's already present.");
101 }
102 if (std::find(fNames.begin(), fNames.end(), name) != fNames.end()) {
103 throw std::logic_error("Trying to insert a branch name that's already present.");
104 }
105 fNames.emplace_back(name);
106 fBranches.emplace_back(address);
107 }
108
109 void Clear()
110 {
111 fBranches.clear();
112 fNames.clear();
113 }
114
116 {
117 std::vector<TBranch *> branchesWithNullAddress;
118 std::copy_if(fBranches.begin(), fBranches.end(), std::back_inserter(branchesWithNullAddress),
119 [](TBranch *b) { return b->GetAddress() == nullptr; });
120
121 if (branchesWithNullAddress.empty())
122 return;
123
124 // otherwise build error message and throw
125 std::vector<std::string> missingBranchNames;
127 std::back_inserter(missingBranchNames), [](TBranch *b) { return b->GetName(); });
128 std::string msg = "RDataFrame::Snapshot:";
129 if (missingBranchNames.size() == 1) {
130 msg += " branch " + missingBranchNames[0] +
131 " is needed as it provides the size for one or more branches containing dynamically sized arrays, but "
132 "it is";
133 } else {
134 msg += " branches ";
135 for (const auto &bName : missingBranchNames)
136 msg += bName + ", ";
137 msg.resize(msg.size() - 2); // remove last ", "
138 msg +=
139 " are needed as they provide the size of other branches containing dynamically sized arrays, but they are";
140 }
141 msg += " not part of the set of branches that are being written out.";
142 throw std::runtime_error(msg);
143 }
144};
145
146/// The container type for each thread's partial result in an action helper
147// We have to avoid to instantiate std::vector<bool> as that makes it impossible to return a reference to one of
148// the thread-local results. In addition, a common definition for the type of the container makes it easy to swap
149// the type of the underlying container if e.g. we see problems with false sharing of the thread-local results..
150template <typename T>
151using Results = std::conditional_t<std::is_same<T, bool>::value, std::deque<T>, std::vector<T>>;
152
153template <typename F>
154class R__CLING_PTRCHECK(off) ForeachSlotHelper : public RActionImpl<ForeachSlotHelper<F>> {
155 F fCallable;
156
157public:
159 ForeachSlotHelper(F &&f) : fCallable(f) {}
161 ForeachSlotHelper(const ForeachSlotHelper &) = delete;
162
163 void InitTask(TTreeReader *, unsigned int) {}
164
165 template <typename... Args>
166 void Exec(unsigned int slot, Args &&... args)
167 {
168 // check that the decayed types of Args are the same as the branch types
169 static_assert(std::is_same<TypeList<std::decay_t<Args>...>, ColumnTypes_t>::value, "");
170 fCallable(slot, std::forward<Args>(args)...);
171 }
172
173 void Initialize() { /* noop */}
174
175 void Finalize() { /* noop */}
176
177 std::string GetActionName() { return "ForeachSlot"; }
178};
179
180class R__CLING_PTRCHECK(off) CountHelper : public RActionImpl<CountHelper> {
181 std::shared_ptr<ULong64_t> fResultCount;
182 Results<ULong64_t> fCounts;
183
184public:
185 using ColumnTypes_t = TypeList<>;
186 CountHelper(const std::shared_ptr<ULong64_t> &resultCount, const unsigned int nSlots);
187 CountHelper(CountHelper &&) = default;
188 CountHelper(const CountHelper &) = delete;
189 void InitTask(TTreeReader *, unsigned int) {}
190 void Exec(unsigned int slot);
191 void Initialize() { /* noop */}
192 void Finalize();
193
194 // Helper functions for RMergeableValue
195 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
196 {
197 return std::make_unique<RMergeableCount>(*fResultCount);
198 }
199
200 ULong64_t &PartialUpdate(unsigned int slot);
201
202 std::string GetActionName() { return "Count"; }
203
204 CountHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
205 {
206 auto &result = *static_cast<std::shared_ptr<ULong64_t> *>(newResult);
207 return CountHelper(result, fCounts.size());
208 }
209};
210
211template <typename RNode_t>
212class R__CLING_PTRCHECK(off) ReportHelper : public RActionImpl<ReportHelper<RNode_t>> {
213 std::shared_ptr<RCutFlowReport> fReport;
214 /// Non-owning pointer, never null. As usual, the node is owned by its children nodes (and therefore indirectly by
215 /// the RAction corresponding to this action helper).
216 RNode_t *fNode;
218
219public:
220 using ColumnTypes_t = TypeList<>;
221 ReportHelper(const std::shared_ptr<RCutFlowReport> &report, RNode_t *node, bool emptyRep)
222 : fReport(report), fNode(node), fReturnEmptyReport(emptyRep){};
223 ReportHelper(ReportHelper &&) = default;
224 ReportHelper(const ReportHelper &) = delete;
225 void InitTask(TTreeReader *, unsigned int) {}
226 void Exec(unsigned int /* slot */) {}
227 void Initialize() { /* noop */}
228 void Finalize()
229 {
231 fNode->Report(*fReport);
232 }
233
234 std::string GetActionName() { return "Report"; }
235
236 ReportHelper MakeNew(void *newResult, std::string_view variation = "nominal")
237 {
238 auto &&result = *static_cast<std::shared_ptr<RCutFlowReport> *>(newResult);
239 return ReportHelper{result,
240 std::static_pointer_cast<RNode_t>(fNode->GetVariedFilter(std::string(variation))).get(),
242 }
243};
244
245/// This helper fills TH1Ds for which no axes were specified by buffering the fill values to pick good axes limits.
246///
247/// TH1Ds have an automatic mechanism to pick good limits based on the first N entries they were filled with, but
248/// that does not work in multi-thread event loops as it might yield histograms with incompatible binning in each
249/// thread, making it impossible to merge the per-thread results.
250/// Instead, this helper delays the decision on the axes limits until all threads have done processing, synchronizing
251/// the decision on the limits as part of the merge operation.
252class R__CLING_PTRCHECK(off) BufferedFillHelper : public RActionImpl<BufferedFillHelper> {
253 // this sets a total initial size of 16 MB for the buffers (can increase)
254 static constexpr unsigned int fgTotalBufSize = 2097152;
255 using BufEl_t = double;
256 using Buf_t = std::vector<BufEl_t>;
257
258 std::vector<Buf_t> fBuffers;
259 std::vector<Buf_t> fWBuffers;
260 std::shared_ptr<Hist_t> fResultHist;
261 unsigned int fNSlots;
262 unsigned int fBufSize;
263 /// Histograms containing "snapshots" of partial results. Non-null only if a registered callback requires it.
265 Buf_t fMin;
266 Buf_t fMax;
267
268 void UpdateMinMax(unsigned int slot, double v);
269
270public:
271 BufferedFillHelper(const std::shared_ptr<Hist_t> &h, const unsigned int nSlots);
273 BufferedFillHelper(const BufferedFillHelper &) = delete;
274 void InitTask(TTreeReader *, unsigned int) {}
275 void Exec(unsigned int slot, double v);
276 void Exec(unsigned int slot, double v, double w);
277
279 void Exec(unsigned int slot, const T &vs)
280 {
281 auto &thisBuf = fBuffers[slot];
282 // range-based for results in warnings on some compilers due to vector<bool>'s custom reference type
283 for (auto v = vs.begin(); v != vs.end(); ++v) {
285 thisBuf.emplace_back(*v); // TODO: Can be optimised in case T == BufEl_t
286 }
287 }
288
290 void Exec(unsigned int slot, const T &vs, const W &ws)
291 {
292 auto &thisBuf = fBuffers[slot];
293
294 for (auto &v : vs) {
296 thisBuf.emplace_back(v);
297 }
298
299 auto &thisWBuf = fWBuffers[slot];
300 for (auto &w : ws) {
301 thisWBuf.emplace_back(w); // TODO: Can be optimised in case T == BufEl_t
302 }
303 }
304
306 void Exec(unsigned int slot, const T &vs, const W w)
307 {
308 auto &thisBuf = fBuffers[slot];
309 for (auto &v : vs) {
311 thisBuf.emplace_back(v); // TODO: Can be optimised in case T == BufEl_t
312 }
313
314 auto &thisWBuf = fWBuffers[slot];
315 thisWBuf.insert(thisWBuf.end(), vs.size(), w);
316 }
317
319 void Exec(unsigned int slot, const T v, const W &ws)
320 {
322 auto &thisBuf = fBuffers[slot];
323 thisBuf.insert(thisBuf.end(), ws.size(), v);
324
325 auto &thisWBuf = fWBuffers[slot];
326 thisWBuf.insert(thisWBuf.end(), ws.begin(), ws.end());
327 }
328
329 Hist_t &PartialUpdate(unsigned int);
330
331 void Initialize() { /* noop */}
332
333 void Finalize();
334
335 // Helper functions for RMergeableValue
336 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
337 {
338 return std::make_unique<RMergeableFill<Hist_t>>(*fResultHist);
339 }
340
341 std::string GetActionName()
342 {
343 return std::string(fResultHist->IsA()->GetName()) + "\\n" + std::string(fResultHist->GetName());
344 }
345
346 BufferedFillHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
347 {
348 auto &result = *static_cast<std::shared_ptr<Hist_t> *>(newResult);
349 result->Reset();
350 result->SetDirectory(nullptr);
351 return BufferedFillHelper(result, fNSlots);
352 }
353};
354
355// class which wraps a pointer and implements a no-op increment operator
356template <typename T>
358 const T *obj_;
359
360public:
361 using iterator_category = std::forward_iterator_tag;
362 using difference_type = std::ptrdiff_t;
363 using value_type = T;
364 using pointer = T *;
365 using reference = T &;
366 ScalarConstIterator(const T *obj) : obj_(obj) {}
367 const T &operator*() const { return *obj_; }
368 ScalarConstIterator<T> &operator++() { return *this; }
369};
370
371// return unchanged value for scalar
372template <typename T>
373auto MakeBegin(const T &val)
374{
375 if constexpr (IsDataContainer<T>::value) {
376 return std::begin(val);
377 } else {
378 return ScalarConstIterator<T>(&val);
379 }
380}
381
382// return container size for containers, and 1 for scalars
383template <typename T>
384std::size_t GetSize(const T &val)
385{
386 if constexpr (IsDataContainer<T>::value) {
387 return std::size(val);
388 } else {
389 return 1;
390 }
391}
392
393// Helpers for dealing with histograms and similar:
395void ResetIfPossible(H *h)
396{
397 h->Reset();
398}
399
401void ResetIfPossible(...);
402
405
406/// The generic Fill helper: it calls Fill on per-thread objects and then Merge to produce a final result.
407/// For one-dimensional histograms, if no axes are specified, RDataFrame uses BufferedFillHelper instead.
408template <typename HIST = Hist_t>
409class R__CLING_PTRCHECK(off) FillHelper : public RActionImpl<FillHelper<HIST>> {
410 std::vector<HIST *> fObjects;
411
412 // Merge overload for types with Merge(TCollection*), like TH1s
414 auto Merge(std::vector<H *> &objs, int /*toincreaseoverloadpriority*/)
415 -> decltype(objs[0]->Merge((TCollection *)nullptr), void())
416 {
417 TList l;
418 for (auto it = ++objs.begin(); it != objs.end(); ++it)
419 l.Add(*it);
420 objs[0]->Merge(&l);
421 }
422
423 // Merge overload for types with Merge(const std::vector&)
424 template <typename H>
425 auto Merge(std::vector<H *> &objs, double /*toloweroverloadpriority*/)
426 -> decltype(objs[0]->Merge(std::vector<HIST *>{}), void())
427 {
428 objs[0]->Merge({++objs.begin(), objs.end()});
429 }
430
431 // Merge overload to error out in case no valid HIST::Merge method was detected
432 template <typename T>
433 void Merge(T, ...)
434 {
435 static_assert(sizeof(T) < 0,
436 "The type passed to Fill does not provide a Merge(TCollection*) or Merge(const std::vector&) method.");
437 }
438
439 template <std::size_t ColIdx, typename End_t, typename... Its>
440 void ExecLoop(unsigned int slot, End_t end, Its... its)
441 {
442 for (auto *thisSlotH = fObjects[slot]; GetNthElement<ColIdx>(its...) != end; (std::advance(its, 1), ...)) {
443 thisSlotH->Fill(*its...);
444 }
445 }
446
447public:
448 FillHelper(FillHelper &&) = default;
449 FillHelper(const FillHelper &) = delete;
450
451 FillHelper(const std::shared_ptr<HIST> &h, const unsigned int nSlots) : fObjects(nSlots, nullptr)
452 {
453 fObjects[0] = h.get();
454 // Initialize all other slots
455 for (unsigned int i = 1; i < nSlots; ++i) {
456 fObjects[i] = new HIST(*fObjects[0]);
457 UnsetDirectoryIfPossible(fObjects[i]);
458 }
459 }
460
461 void InitTask(TTreeReader *, unsigned int) {}
462
463 // no container arguments
464 template <typename... ValTypes, std::enable_if_t<!Disjunction<IsDataContainer<ValTypes>...>::value, int> = 0>
465 auto Exec(unsigned int slot, const ValTypes &...x) -> decltype(fObjects[slot]->Fill(x...), void())
466 {
467 fObjects[slot]->Fill(x...);
468 }
469
470 // at least one container argument
471 template <typename... Xs, std::enable_if_t<Disjunction<IsDataContainer<Xs>...>::value, int> = 0>
472 auto Exec(unsigned int slot, const Xs &...xs) -> decltype(fObjects[slot]->Fill(*MakeBegin(xs)...), void())
473 {
474 // array of bools keeping track of which inputs are containers
475 constexpr std::array<bool, sizeof...(Xs)> isContainer{IsDataContainer<Xs>::value...};
476
477 // index of the first container input
478 constexpr std::size_t colidx = FindIdxTrue(isContainer);
479 // if this happens, there is a bug in the implementation
480 static_assert(colidx < sizeof...(Xs), "Error: index of collection-type argument not found.");
481
482 // get the end iterator to the first container
483 auto const xrefend = std::end(GetNthElement<colidx>(xs...));
484
485 // array of container sizes (1 for scalars)
486 std::array<std::size_t, sizeof...(xs)> sizes = {{GetSize(xs)...}};
487
488 for (std::size_t i = 0; i < sizeof...(xs); ++i) {
489 if (isContainer[i] && sizes[i] != sizes[colidx]) {
490 throw std::runtime_error("Cannot fill histogram with values in containers of different sizes.");
491 }
492 }
493
495 }
496
497 template <typename T = HIST>
498 void Exec(...)
499 {
500 static_assert(sizeof(T) < 0,
501 "When filling an object with RDataFrame (e.g. via a Fill action) the number or types of the "
502 "columns passed did not match the signature of the object's `Fill` method.");
503 }
504
505 void Initialize() { /* noop */}
506
507 void Finalize()
508 {
509 if (fObjects.size() == 1)
510 return;
511
512 Merge(fObjects, /*toselectcorrectoverload=*/0);
513
514 // delete the copies we created for the slots other than the first
515 for (auto it = ++fObjects.begin(); it != fObjects.end(); ++it)
516 delete *it;
517 }
518
519 HIST &PartialUpdate(unsigned int slot) { return *fObjects[slot]; }
520
521 // Helper functions for RMergeableValue
522 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
523 {
524 return std::make_unique<RMergeableFill<HIST>>(*fObjects[0]);
525 }
526
527 // if the fObjects vector type is derived from TObject, return the name of the object
529 std::string GetActionName()
530 {
531 return std::string(fObjects[0]->IsA()->GetName()) + "\\n" + std::string(fObjects[0]->GetName());
532 }
533
534 // if fObjects is not derived from TObject, indicate it is some other object
536 std::string GetActionName()
537 {
538 return "Fill custom object";
539 }
540
541 template <typename H = HIST>
542 FillHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
543 {
544 auto &result = *static_cast<std::shared_ptr<H> *>(newResult);
545 ResetIfPossible(result.get());
547 return FillHelper(result, fObjects.size());
548 }
549};
550
552public:
553 using Result_t = ::TGraph;
554
555private:
556 std::vector<::TGraph *> fGraphs;
557
558public:
560 FillTGraphHelper(const FillTGraphHelper &) = delete;
561
562 FillTGraphHelper(const std::shared_ptr<::TGraph> &g, const unsigned int nSlots) : fGraphs(nSlots, nullptr)
563 {
564 fGraphs[0] = g.get();
565 // Initialize all other slots
566 for (unsigned int i = 1; i < nSlots; ++i) {
567 fGraphs[i] = new TGraph(*fGraphs[0]);
568 }
569 }
570
571 void Initialize() {}
572 void InitTask(TTreeReader *, unsigned int) {}
573
574 // case: both types are container types
575 template <typename X0, typename X1,
576 std::enable_if_t<IsDataContainer<X0>::value && IsDataContainer<X1>::value, int> = 0>
577 void Exec(unsigned int slot, const X0 &x0s, const X1 &x1s)
578 {
579 if (x0s.size() != x1s.size()) {
580 throw std::runtime_error("Cannot fill Graph with values in containers of different sizes.");
581 }
582 auto *thisSlotG = fGraphs[slot];
583 auto x0sIt = std::begin(x0s);
584 const auto x0sEnd = std::end(x0s);
585 auto x1sIt = std::begin(x1s);
586 for (; x0sIt != x0sEnd; x0sIt++, x1sIt++) {
587 thisSlotG->SetPoint(thisSlotG->GetN(), *x0sIt, *x1sIt);
588 }
589 }
590
591 // case: both types are non-container types, e.g. scalars
592 template <typename X0, typename X1,
593 std::enable_if_t<!IsDataContainer<X0>::value && !IsDataContainer<X1>::value, int> = 0>
594 void Exec(unsigned int slot, X0 x0, X1 x1)
595 {
596 auto thisSlotG = fGraphs[slot];
597 thisSlotG->SetPoint(thisSlotG->GetN(), x0, x1);
598 }
599
600 // case: types are combination of containers and non-containers
601 // this is not supported, error out
602 template <typename X0, typename X1, typename... ExtraArgsToLowerPriority>
603 void Exec(unsigned int, X0, X1, ExtraArgsToLowerPriority...)
604 {
605 throw std::runtime_error("Graph was applied to a mix of scalar values and collections. This is not supported.");
606 }
607
608 void Finalize()
609 {
610 const auto nSlots = fGraphs.size();
611 auto resGraph = fGraphs[0];
612 TList l;
613 l.SetOwner(); // The list will free the memory associated to its elements upon destruction
614 for (unsigned int slot = 1; slot < nSlots; ++slot) {
615 l.Add(fGraphs[slot]);
616 }
617 resGraph->Merge(&l);
618 }
619
620 // Helper functions for RMergeableValue
621 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
622 {
623 return std::make_unique<RMergeableFill<Result_t>>(*fGraphs[0]);
624 }
625
626 std::string GetActionName() { return "Graph"; }
627
628 Result_t &PartialUpdate(unsigned int slot) { return *fGraphs[slot]; }
629
630 FillTGraphHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
631 {
632 auto &result = *static_cast<std::shared_ptr<TGraph> *>(newResult);
633 result->Set(0);
634 return FillTGraphHelper(result, fGraphs.size());
635 }
636};
637
639 : public ROOT::Detail::RDF::RActionImpl<FillTGraphAsymmErrorsHelper> {
640public:
641 using Result_t = ::TGraphAsymmErrors;
642
643private:
644 std::vector<::TGraphAsymmErrors *> fGraphAsymmErrors;
645
646public:
649
650 FillTGraphAsymmErrorsHelper(const std::shared_ptr<::TGraphAsymmErrors> &g, const unsigned int nSlots)
651 : fGraphAsymmErrors(nSlots, nullptr)
652 {
653 fGraphAsymmErrors[0] = g.get();
654 // Initialize all other slots
655 for (unsigned int i = 1; i < nSlots; ++i) {
657 }
658 }
659
660 void Initialize() {}
661 void InitTask(TTreeReader *, unsigned int) {}
662
663 // case: all types are container types
664 template <
665 typename X, typename Y, typename EXL, typename EXH, typename EYL, typename EYH,
666 std::enable_if_t<IsDataContainer<X>::value && IsDataContainer<Y>::value && IsDataContainer<EXL>::value &&
667 IsDataContainer<EXH>::value && IsDataContainer<EYL>::value && IsDataContainer<EYH>::value,
668 int> = 0>
669 void
670 Exec(unsigned int slot, const X &xs, const Y &ys, const EXL &exls, const EXH &exhs, const EYL &eyls, const EYH &eyhs)
671 {
672 if ((xs.size() != ys.size()) || (xs.size() != exls.size()) || (xs.size() != exhs.size()) ||
673 (xs.size() != eyls.size()) || (xs.size() != eyhs.size())) {
674 throw std::runtime_error("Cannot fill GraphAsymmErrors with values in containers of different sizes.");
675 }
677 auto xsIt = std::begin(xs);
678 auto ysIt = std::begin(ys);
679 auto exlsIt = std::begin(exls);
680 auto exhsIt = std::begin(exhs);
681 auto eylsIt = std::begin(eyls);
682 auto eyhsIt = std::begin(eyhs);
683 while (xsIt != std::end(xs)) {
684 const auto n = thisSlotG->GetN(); // must use the same `n` for SetPoint and SetPointError
685 thisSlotG->SetPoint(n, *xsIt++, *ysIt++);
686 thisSlotG->SetPointError(n, *exlsIt++, *exhsIt++, *eylsIt++, *eyhsIt++);
687 }
688 }
689
690 // case: all types are non-container types, e.g. scalars
691 template <
692 typename X, typename Y, typename EXL, typename EXH, typename EYL, typename EYH,
693 std::enable_if_t<!IsDataContainer<X>::value && !IsDataContainer<Y>::value && !IsDataContainer<EXL>::value &&
694 !IsDataContainer<EXH>::value && !IsDataContainer<EYL>::value && !IsDataContainer<EYH>::value,
695 int> = 0>
696 void Exec(unsigned int slot, X x, Y y, EXL exl, EXH exh, EYL eyl, EYH eyh)
697 {
699 const auto n = thisSlotG->GetN();
700 thisSlotG->SetPoint(n, x, y);
701 thisSlotG->SetPointError(n, exl, exh, eyl, eyh);
702 }
703
704 // case: types are combination of containers and non-containers
705 // this is not supported, error out
706 template <typename X, typename Y, typename EXL, typename EXH, typename EYL, typename EYH,
707 typename... ExtraArgsToLowerPriority>
708 void Exec(unsigned int, X, Y, EXL, EXH, EYL, EYH, ExtraArgsToLowerPriority...)
709 {
710 throw std::runtime_error(
711 "GraphAsymmErrors was applied to a mix of scalar values and collections. This is not supported.");
712 }
713
714 void Finalize()
715 {
716 const auto nSlots = fGraphAsymmErrors.size();
718 TList l;
719 l.SetOwner(); // The list will free the memory associated to its elements upon destruction
720 for (unsigned int slot = 1; slot < nSlots; ++slot) {
722 }
723 resGraphAsymmErrors->Merge(&l);
724 }
725
726 // Helper functions for RMergeableValue
727 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
728 {
729 return std::make_unique<RMergeableFill<Result_t>>(*fGraphAsymmErrors[0]);
730 }
731
732 std::string GetActionName() { return "GraphAsymmErrors"; }
733
734 Result_t &PartialUpdate(unsigned int slot) { return *fGraphAsymmErrors[slot]; }
735
736 FillTGraphAsymmErrorsHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
737 {
738 auto &result = *static_cast<std::shared_ptr<TGraphAsymmErrors> *>(newResult);
739 result->Set(0);
741 }
742};
743
744/// A FillHelper for classes supporting the FillThreadSafe function.
745template <typename HIST>
746class R__CLING_PTRCHECK(off) ThreadSafeFillHelper : public RActionImpl<ThreadSafeFillHelper<HIST>> {
747 std::vector<std::shared_ptr<HIST>> fObjects;
748 std::vector<std::unique_ptr<std::mutex>> fMutexPtrs;
749
750 // This overload matches if the function exists:
751 template <typename T, typename... Args>
752 auto TryCallFillThreadSafe(T &object, std::mutex &, int /*dummy*/, Args... args)
753 -> decltype(ROOT::Internal::FillThreadSafe(object, args...), void())
754 {
755 ROOT::Internal::FillThreadSafe(object, args...);
756 }
757 // This one has lower precedence because of the dummy argument, and uses a lock
758 template <typename T, typename... Args>
759 auto TryCallFillThreadSafe(T &object, std::mutex &mutex, char /*dummy*/, Args... args)
760 {
761 std::scoped_lock lock{mutex};
762 object.Fill(args...);
763 }
764
765 template <std::size_t ColIdx, typename End_t, typename... Its>
766 void ExecLoop(unsigned int slot, End_t end, Its... its)
767 {
768 const auto localSlot = slot % fObjects.size();
769 for (; GetNthElement<ColIdx>(its...) != end; (std::advance(its, 1), ...)) {
771 }
772 }
773
774public:
777
778 ThreadSafeFillHelper(const std::shared_ptr<HIST> &h, const unsigned int nSlots)
779 {
780 fObjects.resize(nSlots);
781 fObjects.front() = h;
782
783 std::generate(fObjects.begin() + 1, fObjects.end(), [h]() {
784 auto hist = std::make_shared<HIST>(*h);
785 UnsetDirectoryIfPossible(hist.get());
786 return hist;
787 });
788 fMutexPtrs.resize(nSlots);
789 std::generate(fMutexPtrs.begin(), fMutexPtrs.end(), []() { return std::make_unique<std::mutex>(); });
790 }
791
792 void InitTask(TTreeReader *, unsigned int) {}
793
794 // no container arguments
795 template <typename... ValTypes, std::enable_if_t<!Disjunction<IsDataContainer<ValTypes>...>::value, int> = 0>
796 void Exec(unsigned int slot, const ValTypes &...x)
797 {
798 const auto localSlot = slot % fObjects.size();
800 }
801
802 // at least one container argument
803 template <typename... Xs, std::enable_if_t<Disjunction<IsDataContainer<Xs>...>::value, int> = 0>
804 void Exec(unsigned int slot, const Xs &...xs)
805 {
806 // array of bools keeping track of which inputs are containers
807 constexpr std::array<bool, sizeof...(Xs)> isContainer{IsDataContainer<Xs>::value...};
808
809 // index of the first container input
810 constexpr std::size_t colidx = FindIdxTrue(isContainer);
811 // if this happens, there is a bug in the implementation
812 static_assert(colidx < sizeof...(Xs), "Error: index of collection-type argument not found.");
813
814 // get the end iterator to the first container
815 auto const xrefend = std::end(GetNthElement<colidx>(xs...));
816
817 // array of container sizes (1 for scalars)
818 std::array<std::size_t, sizeof...(xs)> sizes = {{GetSize(xs)...}};
819
820 for (std::size_t i = 0; i < sizeof...(xs); ++i) {
821 if (isContainer[i] && sizes[i] != sizes[colidx]) {
822 throw std::runtime_error("Cannot fill histogram with values in containers of different sizes.");
823 }
824 }
825
827 }
828
829 template <typename T = HIST>
830 void Exec(...)
831 {
832 static_assert(sizeof(T) < 0,
833 "When filling an object with RDataFrame (e.g. via a Fill action) the number or types of the "
834 "columns passed did not match the signature of the object's `FillThreadSafe` method.");
835 }
836
837 void Initialize() { /* noop */ }
838
839 void Finalize()
840 {
841 if (fObjects.size() > 1) {
842 TList list;
843 for (auto it = fObjects.cbegin() + 1; it != fObjects.end(); ++it) {
844 list.Add(it->get());
845 }
846 fObjects[0]->Merge(&list);
847 }
848
849 fObjects.resize(1);
850 fMutexPtrs.clear();
851 }
852
853 // Helper function for RMergeableValue
854 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
855 {
856 return std::make_unique<RMergeableFill<HIST>>(*fObjects[0]);
857 }
858
859 // if the fObjects vector type is derived from TObject, return the name of the object
861 std::string GetActionName()
862 {
863 return std::string(fObjects[0]->IsA()->GetName()) + "\\n" + std::string(fObjects[0]->GetName());
864 }
865
866 template <typename H = HIST>
867 ThreadSafeFillHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
868 {
869 auto &result = *static_cast<std::shared_ptr<H> *>(newResult);
870 ResetIfPossible(result.get());
872 return ThreadSafeFillHelper(result, fObjects.size());
873 }
874};
875
876// In case of the take helper we have 4 cases:
877// 1. The column is not an RVec, the collection is not a vector
878// 2. The column is not an RVec, the collection is a vector
879// 3. The column is an RVec, the collection is not a vector
880// 4. The column is an RVec, the collection is a vector
881
882template <typename V, typename COLL>
883void FillColl(V&& v, COLL& c) {
884 c.emplace_back(v);
885}
886
887// Use push_back for bool since some compilers do not support emplace_back.
888template <typename COLL>
889void FillColl(bool v, COLL& c) {
890 c.push_back(v);
891}
892
893// Case 1.: The column is not an RVec, the collection is not a vector
894// No optimisations, no transformations: just copies.
895template <typename RealT_t, typename T, typename COLL>
896class R__CLING_PTRCHECK(off) TakeHelper : public RActionImpl<TakeHelper<RealT_t, T, COLL>> {
898
899public:
900 using ColumnTypes_t = TypeList<T>;
901 TakeHelper(const std::shared_ptr<COLL> &resultColl, const unsigned int nSlots)
902 {
903 fColls.emplace_back(resultColl);
904 for (unsigned int i = 1; i < nSlots; ++i)
905 fColls.emplace_back(std::make_shared<COLL>());
906 }
908 TakeHelper(const TakeHelper &) = delete;
909
910 void InitTask(TTreeReader *, unsigned int) {}
911
912 void Exec(unsigned int slot, T &v) { FillColl(v, *fColls[slot]); }
913
914 void Initialize() { /* noop */}
915
916 void Finalize()
917 {
918 auto rColl = fColls[0];
919 for (unsigned int i = 1; i < fColls.size(); ++i) {
920 const auto &coll = fColls[i];
921 const auto end = coll->end();
922 // Use an explicit loop here to prevent compiler warnings introduced by
923 // clang's range-based loop analysis and vector<bool> references.
924 for (auto j = coll->begin(); j != end; j++) {
925 FillColl(*j, *rColl);
926 }
927 }
928 }
929
930 COLL &PartialUpdate(unsigned int slot) { return *fColls[slot].get(); }
931
932 std::string GetActionName() { return "Take"; }
933
934 TakeHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
935 {
936 auto &result = *static_cast<std::shared_ptr<COLL> *>(newResult);
937 result->clear();
938 return TakeHelper(result, fColls.size());
939 }
940};
941
942// Case 2.: The column is not an RVec, the collection is a vector
943// Optimisations, no transformations: just copies.
944template <typename RealT_t, typename T>
945class R__CLING_PTRCHECK(off) TakeHelper<RealT_t, T, std::vector<T>>
946 : public RActionImpl<TakeHelper<RealT_t, T, std::vector<T>>> {
948
949public:
950 using ColumnTypes_t = TypeList<T>;
951 TakeHelper(const std::shared_ptr<std::vector<T>> &resultColl, const unsigned int nSlots)
952 {
953 fColls.emplace_back(resultColl);
954 for (unsigned int i = 1; i < nSlots; ++i) {
955 auto v = std::make_shared<std::vector<T>>();
956 v->reserve(1024);
957 fColls.emplace_back(v);
958 }
959 }
961 TakeHelper(const TakeHelper &) = delete;
962
963 void InitTask(TTreeReader *, unsigned int) {}
964
965 void Exec(unsigned int slot, T &v) { FillColl(v, *fColls[slot]); }
966
967 void Initialize() { /* noop */}
968
969 // This is optimised to treat vectors
970 void Finalize()
971 {
972 ULong64_t totSize = 0;
973 for (auto &coll : fColls)
974 totSize += coll->size();
975 auto rColl = fColls[0];
976 rColl->reserve(totSize);
977 for (unsigned int i = 1; i < fColls.size(); ++i) {
978 auto &coll = fColls[i];
979 rColl->insert(rColl->end(), coll->begin(), coll->end());
980 }
981 }
982
983 std::vector<T> &PartialUpdate(unsigned int slot) { return *fColls[slot]; }
984
985 std::string GetActionName() { return "Take"; }
986
987 TakeHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
988 {
989 auto &result = *static_cast<std::shared_ptr<std::vector<T>> *>(newResult);
990 result->clear();
991 return TakeHelper(result, fColls.size());
992 }
993};
994
995// Case 3.: The column is a RVec, the collection is not a vector
996// No optimisations, transformations from RVecs to vectors
997template <typename RealT_t, typename COLL>
999 : public RActionImpl<TakeHelper<RealT_t, RVec<RealT_t>, COLL>> {
1001
1002public:
1003 using ColumnTypes_t = TypeList<RVec<RealT_t>>;
1004 TakeHelper(const std::shared_ptr<COLL> &resultColl, const unsigned int nSlots)
1005 {
1006 fColls.emplace_back(resultColl);
1007 for (unsigned int i = 1; i < nSlots; ++i)
1008 fColls.emplace_back(std::make_shared<COLL>());
1009 }
1011 TakeHelper(const TakeHelper &) = delete;
1012
1013 void InitTask(TTreeReader *, unsigned int) {}
1014
1015 void Exec(unsigned int slot, RVec<RealT_t> av) { fColls[slot]->emplace_back(av.begin(), av.end()); }
1016
1017 void Initialize() { /* noop */}
1018
1019 void Finalize()
1020 {
1021 auto rColl = fColls[0];
1022 for (unsigned int i = 1; i < fColls.size(); ++i) {
1023 auto &coll = fColls[i];
1024 for (auto &v : *coll) {
1025 rColl->emplace_back(v);
1026 }
1027 }
1028 }
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 4.: The column is an RVec, the collection is a vector
1041// Optimisations, transformations from RVecs to vectors
1042template <typename RealT_t>
1043class R__CLING_PTRCHECK(off) TakeHelper<RealT_t, RVec<RealT_t>, std::vector<RealT_t>>
1044 : public RActionImpl<TakeHelper<RealT_t, RVec<RealT_t>, std::vector<RealT_t>>> {
1045
1047
1048public:
1049 using ColumnTypes_t = TypeList<RVec<RealT_t>>;
1050 TakeHelper(const std::shared_ptr<std::vector<std::vector<RealT_t>>> &resultColl, const unsigned int nSlots)
1051 {
1052 fColls.emplace_back(resultColl);
1053 for (unsigned int i = 1; i < nSlots; ++i) {
1054 auto v = std::make_shared<std::vector<RealT_t>>();
1055 v->reserve(1024);
1056 fColls.emplace_back(v);
1057 }
1058 }
1060 TakeHelper(const TakeHelper &) = delete;
1061
1062 void InitTask(TTreeReader *, unsigned int) {}
1063
1064 void Exec(unsigned int slot, RVec<RealT_t> av) { fColls[slot]->emplace_back(av.begin(), av.end()); }
1065
1066 void Initialize() { /* noop */}
1067
1068 // This is optimised to treat vectors
1069 void Finalize()
1070 {
1071 ULong64_t totSize = 0;
1072 for (auto &coll : fColls)
1073 totSize += coll->size();
1074 auto rColl = fColls[0];
1075 rColl->reserve(totSize);
1076 for (unsigned int i = 1; i < fColls.size(); ++i) {
1077 auto &coll = fColls[i];
1078 rColl->insert(rColl->end(), coll->begin(), coll->end());
1079 }
1080 }
1081
1082 std::string GetActionName() { return "Take"; }
1083
1084 TakeHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1085 {
1086 auto &result = *static_cast<typename decltype(fColls)::value_type *>(newResult);
1087 result->clear();
1088 return TakeHelper(result, fColls.size());
1089 }
1090};
1091
1092// Extern templates for TakeHelper
1093// NOTE: The move-constructor of specializations declared as extern templates
1094// must be defined out of line, otherwise cling fails to find its symbol.
1095template <typename RealT_t, typename T, typename COLL>
1097template <typename RealT_t, typename T>
1099template <typename RealT_t, typename COLL>
1101template <typename RealT_t>
1102TakeHelper<RealT_t, RVec<RealT_t>, std::vector<RealT_t>>::TakeHelper(TakeHelper<RealT_t, RVec<RealT_t>, std::vector<RealT_t>> &&) = default;
1103
1104// External templates are disabled for gcc5 since this version wrongly omits the C++11 ABI attribute
1105#if __GNUC__ > 5
1106extern template class TakeHelper<bool, bool, std::vector<bool>>;
1110extern template class TakeHelper<int, int, std::vector<int>>;
1111extern template class TakeHelper<long, long, std::vector<long>>;
1113extern template class TakeHelper<float, float, std::vector<float>>;
1115#endif
1116
1117template <typename ResultType>
1118class R__CLING_PTRCHECK(off) MinHelper : public RActionImpl<MinHelper<ResultType>> {
1119 std::shared_ptr<ResultType> fResultMin;
1121
1122public:
1123 MinHelper(MinHelper &&) = default;
1124 MinHelper(const std::shared_ptr<ResultType> &minVPtr, const unsigned int nSlots)
1125 : fResultMin(minVPtr), fMins(nSlots, std::numeric_limits<ResultType>::max())
1126 {
1127 }
1128
1129 void Exec(unsigned int slot, ResultType v) { fMins[slot] = std::min(v, fMins[slot]); }
1130
1131 void InitTask(TTreeReader *, unsigned int) {}
1132
1134 void Exec(unsigned int slot, const T &vs)
1135 {
1136 for (auto &&v : vs)
1137 fMins[slot] = std::min(static_cast<ResultType>(v), fMins[slot]);
1138 }
1139
1140 void Initialize() { /* noop */}
1141
1142 void Finalize()
1143 {
1144 *fResultMin = std::numeric_limits<ResultType>::max();
1145 for (auto &m : fMins)
1146 *fResultMin = std::min(m, *fResultMin);
1147 }
1148
1149 // Helper functions for RMergeableValue
1150 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1151 {
1152 return std::make_unique<RMergeableMin<ResultType>>(*fResultMin);
1153 }
1154
1155 ResultType &PartialUpdate(unsigned int slot) { return fMins[slot]; }
1156
1157 std::string GetActionName() { return "Min"; }
1158
1159 MinHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1160 {
1161 auto &result = *static_cast<std::shared_ptr<ResultType> *>(newResult);
1162 return MinHelper(result, fMins.size());
1163 }
1164};
1165
1166template <typename ResultType>
1167class R__CLING_PTRCHECK(off) MaxHelper : public RActionImpl<MaxHelper<ResultType>> {
1168 std::shared_ptr<ResultType> fResultMax;
1170
1171public:
1172 MaxHelper(MaxHelper &&) = default;
1173 MaxHelper(const MaxHelper &) = delete;
1174 MaxHelper(const std::shared_ptr<ResultType> &maxVPtr, const unsigned int nSlots)
1175 : fResultMax(maxVPtr), fMaxs(nSlots, std::numeric_limits<ResultType>::lowest())
1176 {
1177 }
1178
1179 void InitTask(TTreeReader *, unsigned int) {}
1180 void Exec(unsigned int slot, ResultType v) { fMaxs[slot] = std::max(v, fMaxs[slot]); }
1181
1183 void Exec(unsigned int slot, const T &vs)
1184 {
1185 for (auto &&v : vs)
1186 fMaxs[slot] = std::max(static_cast<ResultType>(v), fMaxs[slot]);
1187 }
1188
1189 void Initialize() { /* noop */}
1190
1191 void Finalize()
1192 {
1193 *fResultMax = std::numeric_limits<ResultType>::lowest();
1194 for (auto &m : fMaxs) {
1195 *fResultMax = std::max(m, *fResultMax);
1196 }
1197 }
1198
1199 // Helper functions for RMergeableValue
1200 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1201 {
1202 return std::make_unique<RMergeableMax<ResultType>>(*fResultMax);
1203 }
1204
1205 ResultType &PartialUpdate(unsigned int slot) { return fMaxs[slot]; }
1206
1207 std::string GetActionName() { return "Max"; }
1208
1209 MaxHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1210 {
1211 auto &result = *static_cast<std::shared_ptr<ResultType> *>(newResult);
1212 return MaxHelper(result, fMaxs.size());
1213 }
1214};
1215
1216template <typename ResultType>
1217class R__CLING_PTRCHECK(off) SumHelper : public RActionImpl<SumHelper<ResultType>> {
1218 std::shared_ptr<ResultType> fResultSum;
1221
1222 /// Evaluate neutral element for this type and the sum operation.
1223 /// This is assumed to be any_value - any_value if operator- is defined
1224 /// for the type, otherwise a default-constructed ResultType{} is used.
1225 template <typename T = ResultType>
1226 auto NeutralElement(const T &v, int /*overloadresolver*/) -> decltype(v - v)
1227 {
1228 return v - v;
1229 }
1230
1231 template <typename T = ResultType, typename Dummy = int>
1232 ResultType NeutralElement(const T &, Dummy) // this overload has lower priority thanks to the template arg
1233 {
1234 return ResultType{};
1235 }
1236
1237public:
1238 SumHelper(SumHelper &&) = default;
1239 SumHelper(const SumHelper &) = delete;
1240 SumHelper(const std::shared_ptr<ResultType> &sumVPtr, const unsigned int nSlots)
1243 {
1244 }
1245 void InitTask(TTreeReader *, unsigned int) {}
1246
1247 void Exec(unsigned int slot, ResultType x)
1248 {
1249 // Kahan Sum:
1251 ResultType t = fSums[slot] + y;
1252 fCompensations[slot] = (t - fSums[slot]) - y;
1253 fSums[slot] = t;
1254 }
1255
1257 void Exec(unsigned int slot, const T &vs)
1258 {
1259 for (auto &&v : vs) {
1260 Exec(slot, v);
1261 }
1262 }
1263
1264 void Initialize() { /* noop */}
1265
1266 void Finalize()
1267 {
1272 for (auto &m : fSums) {
1273 // Kahan Sum:
1274 y = m - compensation;
1275 t = sum + y;
1276 compensation = (t - sum) - y;
1277 sum = t;
1278 }
1279 *fResultSum += sum;
1280 }
1281
1282 // Helper functions for RMergeableValue
1283 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1284 {
1285 return std::make_unique<RMergeableSum<ResultType>>(*fResultSum);
1286 }
1287
1288 ResultType &PartialUpdate(unsigned int slot) { return fSums[slot]; }
1289
1290 std::string GetActionName() { return "Sum"; }
1291
1292 SumHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1293 {
1294 auto &result = *static_cast<std::shared_ptr<ResultType> *>(newResult);
1295 *result = NeutralElement(*result, -1);
1296 return SumHelper(result, fSums.size());
1297 }
1298};
1299
1300class R__CLING_PTRCHECK(off) MeanHelper : public RActionImpl<MeanHelper> {
1301 std::shared_ptr<double> fResultMean;
1302 std::vector<ULong64_t> fCounts;
1303 std::vector<double> fSums;
1304 std::vector<double> fPartialMeans;
1305 std::vector<double> fCompensations;
1306
1307public:
1308 MeanHelper(const std::shared_ptr<double> &meanVPtr, const unsigned int nSlots);
1309 MeanHelper(MeanHelper &&) = default;
1310 MeanHelper(const MeanHelper &) = delete;
1311 void InitTask(TTreeReader *, unsigned int) {}
1312 void Exec(unsigned int slot, double v);
1313
1315 void Exec(unsigned int slot, const T &vs)
1316 {
1317 for (auto &&v : vs) {
1318
1319 fCounts[slot]++;
1320 // Kahan Sum:
1321 double y = v - fCompensations[slot];
1322 double t = fSums[slot] + y;
1323 fCompensations[slot] = (t - fSums[slot]) - y;
1324 fSums[slot] = t;
1325 }
1326 }
1327
1328 void Initialize() { /* noop */}
1329
1330 void Finalize();
1331
1332 // Helper functions for RMergeableValue
1333 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1334 {
1335 const ULong64_t counts = std::accumulate(fCounts.begin(), fCounts.end(), 0ull);
1336 return std::make_unique<RMergeableMean>(*fResultMean, counts);
1337 }
1338
1339 double &PartialUpdate(unsigned int slot);
1340
1341 std::string GetActionName() { return "Mean"; }
1342
1343 MeanHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1344 {
1345 auto &result = *static_cast<std::shared_ptr<double> *>(newResult);
1346 return MeanHelper(result, fSums.size());
1347 }
1348};
1349
1350class R__CLING_PTRCHECK(off) StdDevHelper : public RActionImpl<StdDevHelper> {
1351 // Number of subsets of data
1352 unsigned int fNSlots;
1353 std::shared_ptr<double> fResultStdDev;
1354 // Number of element for each slot
1355 std::vector<ULong64_t> fCounts;
1356 // Mean of each slot
1357 std::vector<double> fMeans;
1358 // Squared distance from the mean
1359 std::vector<double> fDistancesfromMean;
1360
1361public:
1362 StdDevHelper(const std::shared_ptr<double> &meanVPtr, const unsigned int nSlots);
1363 StdDevHelper(StdDevHelper &&) = default;
1364 StdDevHelper(const StdDevHelper &) = delete;
1365 void InitTask(TTreeReader *, unsigned int) {}
1366 void Exec(unsigned int slot, double v);
1367
1369 void Exec(unsigned int slot, const T &vs)
1370 {
1371 for (auto &&v : vs) {
1372 Exec(slot, v);
1373 }
1374 }
1375
1376 void Initialize() { /* noop */}
1377
1378 void Finalize();
1379
1380 // Helper functions for RMergeableValue
1381 std::unique_ptr<RMergeableValueBase> GetMergeableValue() const final
1382 {
1383 const ULong64_t counts = std::accumulate(fCounts.begin(), fCounts.end(), 0ull);
1384 const Double_t mean =
1385 std::inner_product(fMeans.begin(), fMeans.end(), fCounts.begin(), 0.) / static_cast<Double_t>(counts);
1386 return std::make_unique<RMergeableStdDev>(*fResultStdDev, counts, mean);
1387 }
1388
1389 std::string GetActionName() { return "StdDev"; }
1390
1391 StdDevHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
1392 {
1393 auto &result = *static_cast<std::shared_ptr<double> *>(newResult);
1394 return StdDevHelper(result, fCounts.size());
1395 }
1396};
1397
1398template <typename PrevNodeType>
1399class R__CLING_PTRCHECK(off) DisplayHelper : public RActionImpl<DisplayHelper<PrevNodeType>> {
1400private:
1402 std::shared_ptr<Display_t> fDisplayerHelper;
1403 std::shared_ptr<PrevNodeType> fPrevNode;
1404 size_t fEntriesToProcess;
1405
1406public:
1407 DisplayHelper(size_t nRows, const std::shared_ptr<Display_t> &d, const std::shared_ptr<PrevNodeType> &prevNode)
1408 : fDisplayerHelper(d), fPrevNode(prevNode), fEntriesToProcess(nRows)
1409 {
1410 }
1411 DisplayHelper(DisplayHelper &&) = default;
1412 DisplayHelper(const DisplayHelper &) = delete;
1413 void InitTask(TTreeReader *, unsigned int) {}
1414
1415 template <typename... Columns>
1416 void Exec(unsigned int, Columns &... columns)
1417 {
1418 if (fEntriesToProcess == 0)
1419 return;
1420
1421 fDisplayerHelper->AddRow(columns...);
1422 --fEntriesToProcess;
1423
1424 if (fEntriesToProcess == 0) {
1425 // No more entries to process. Send a one-time signal that this node
1426 // of the graph is done. It is important that the 'StopProcessing'
1427 // method is only called once from this helper, otherwise it would seem
1428 // like more than one operation has completed its work.
1429 fPrevNode->StopProcessing();
1430 }
1431 }
1432
1433 void Initialize() {}
1434
1435 void Finalize() {}
1436
1437 std::string GetActionName() { return "Display"; }
1438};
1439
1440template <typename T>
1441void *GetData(ROOT::VecOps::RVec<T> &v)
1442{
1443 return v.data();
1444}
1445
1446template <typename T>
1447void *GetData(T & /*v*/)
1448{
1449 return nullptr;
1450}
1451
1452template <typename T>
1453void SetBranchesHelper(TTree *inputTree, TTree &outputTree, const std::string &inName, const std::string &name,
1454 TBranch *&branch, void *&branchAddress, T *address, RBranchSet &outputBranches,
1455 bool /*isDefine*/, int basketSize)
1456{
1457 static TClassRef TBOClRef("TBranchObject");
1458
1459 TBranch *inputBranch = nullptr;
1460 if (inputTree) {
1461 inputBranch = inputTree->GetBranch(inName.c_str());
1462 if (!inputBranch) // try harder
1463 inputBranch = inputTree->FindBranch(inName.c_str());
1464 }
1465
1466 auto *outputBranch = outputBranches.Get(name);
1467 if (outputBranch) {
1468 // the output branch was already created, we just need to (re)set its address
1469 if (inputBranch && inputBranch->IsA() == TBOClRef) {
1470 outputBranch->SetAddress(reinterpret_cast<T **>(inputBranch->GetAddress()));
1471 } else if (outputBranch->IsA() != TBranch::Class()) {
1472 branchAddress = address;
1473 outputBranch->SetAddress(&branchAddress);
1474 } else {
1475 outputBranch->SetAddress(address);
1476 branchAddress = address;
1477 }
1478 return;
1479 }
1480
1481 if (inputBranch) {
1482 // Respect the original bufsize and splitlevel arguments
1483 // In particular, by keeping splitlevel equal to 0 if this was the case for `inputBranch`, we avoid
1484 // writing garbage when unsplit objects cannot be written as split objects (e.g. in case of a polymorphic
1485 // TObject branch, see https://bit.ly/2EjLMId ).
1486 // A user-provided basket size value takes precedence.
1487 const auto bufSize = (basketSize > 0) ? basketSize : inputBranch->GetBasketSize();
1488 const auto splitLevel = inputBranch->GetSplitLevel();
1489
1490 if (inputBranch->IsA() == TBOClRef) {
1491 // Need to pass a pointer to pointer
1492 outputBranch =
1493 outputTree.Branch(name.c_str(), reinterpret_cast<T **>(inputBranch->GetAddress()), bufSize, splitLevel);
1494 } else {
1495 outputBranch = outputTree.Branch(name.c_str(), address, bufSize, splitLevel);
1496 }
1497 } else {
1498 // Set Custom basket size for new branches.
1499 const auto buffSize = (basketSize > 0) ? basketSize : (inputBranch ? inputBranch->GetBasketSize() : 32000);
1500 outputBranch = outputTree.Branch(name.c_str(), address, buffSize);
1501 }
1503 // This is not an array branch, so we don't register the address of the output branch here
1504 branch = nullptr;
1505 branchAddress = nullptr;
1506}
1507
1508/// Helper function for SnapshotTTreeHelper and SnapshotTTreeHelperMT. It creates new branches for the output TTree of a
1509/// Snapshot. This overload is called for columns of type `RVec<T>`. For RDF, these can represent:
1510/// 1. c-style arrays in ROOT files, so we are sure that there are input trees to which we can ask the correct branch
1511/// title
1512/// 2. RVecs coming from a custom column or the input file/data-source
1513/// 3. vectors coming from ROOT files that are being read as RVecs
1514/// 4. TClonesArray
1515///
1516/// In case of 1., we keep aside the pointer to the branch and the pointer to the input value (in `branch` and
1517/// `branchAddress`) so we can intercept changes in the address of the input branch and tell the output branch.
1518template <typename T>
1519void SetBranchesHelper(TTree *inputTree, TTree &outputTree, const std::string &inName, const std::string &outName,
1521 int basketSize)
1522{
1523 TBranch *inputBranch = nullptr;
1524 if (inputTree) {
1525 inputBranch = inputTree->GetBranch(inName.c_str());
1526 if (!inputBranch) // try harder
1527 inputBranch = inputTree->FindBranch(inName.c_str());
1528 }
1529 auto *outputBranch = outputBranches.Get(outName);
1530
1531 // if no backing input branch, we must write out an RVec
1532 bool mustWriteRVec = (inputBranch == nullptr || isDefine);
1533 // otherwise, if input branch is TClonesArray, must write out an RVec
1534 if (!mustWriteRVec && std::string_view(inputBranch->GetClassName()) == "TClonesArray") {
1535 mustWriteRVec = true;
1536 Warning("Snapshot",
1537 "Branch \"%s\" contains TClonesArrays but the type specified to Snapshot was RVec<T>. The branch will "
1538 "be written out as a RVec instead of a TClonesArray. Specify that the type of the branch is "
1539 "TClonesArray as a Snapshot template parameter to write out a TClonesArray instead.",
1540 inName.c_str());
1541 }
1542 // otherwise, if input branch is a std::vector or RVec, must write out an RVec
1543 if (!mustWriteRVec) {
1544 const auto STLKind = TClassEdit::IsSTLCont(inputBranch->GetClassName());
1545 if (STLKind == ROOT::ESTLType::kSTLvector || STLKind == ROOT::ESTLType::kROOTRVec)
1546 mustWriteRVec = true;
1547 }
1548
1549 if (mustWriteRVec) {
1550 // Treat:
1551 // 2. RVec coming from a custom column or a source
1552 // 3. RVec coming from a column on disk of type vector (the RVec is adopting the data of that vector)
1553 // 4. TClonesArray written out as RVec<T>
1554 if (outputBranch) {
1555 // needs to be SetObject (not SetAddress) to mimic what happens when this TBranchElement is constructed
1556 outputBranch->SetObject(ab);
1557 } else {
1558 // Set Custom basket size for new branches if specified, otherwise get basket size from input branches
1559 const auto buffSize = (basketSize > 0) ? basketSize : (inputBranch ? inputBranch->GetBasketSize() : 32000);
1560 auto *b = outputTree.Branch(outName.c_str(), ab, buffSize);
1561 outputBranches.Insert(outName, b);
1562 }
1563 return;
1564 }
1565
1566 // else this must be a C-array, aka case 1.
1567 auto dataPtr = ab->data();
1568
1569 if (outputBranch) {
1570 if (outputBranch->IsA() != TBranch::Class()) {
1572 outputBranch->SetAddress(&branchAddress);
1573 } else {
1574 outputBranch->SetAddress(dataPtr);
1575 }
1576 } else {
1577 // must construct the leaflist for the output branch and create the branch in the output tree
1578 auto *const leaf = static_cast<TLeaf *>(inputBranch->GetListOfLeaves()->UncheckedAt(0));
1579 const auto bname = leaf->GetName();
1580 auto *sizeLeaf = leaf->GetLeafCount();
1581 const auto sizeLeafName = sizeLeaf ? std::string(sizeLeaf->GetName()) : std::to_string(leaf->GetLenStatic());
1582
1583 if (sizeLeaf && !outputBranches.Get(sizeLeafName)) {
1584 // The output array branch `bname` has dynamic size stored in leaf `sizeLeafName`, but that leaf has not been
1585 // added to the output tree yet. However, the size leaf has to be available for the creation of the array
1586 // branch to be successful. So we create the size leaf here.
1587 const auto sizeTypeStr = TypeName2ROOTTypeName(sizeLeaf->GetTypeName());
1588 // Use Original basket size for Existing Branches otherwise use Custom basket Size.
1589 const auto sizeBufSize = (basketSize > 0) ? basketSize : sizeLeaf->GetBranch()->GetBasketSize();
1590 // The null branch address is a placeholder. It will be set when SetBranchesHelper is called for `sizeLeafName`
1591 auto *sizeBranch = outputTree.Branch(sizeLeafName.c_str(), (void *)nullptr,
1592 (sizeLeafName + '/' + sizeTypeStr).c_str(), sizeBufSize);
1594 }
1595
1596 const auto btype = leaf->GetTypeName();
1597 const auto rootbtype = TypeName2ROOTTypeName(btype);
1598 if (rootbtype == ' ') {
1599 Warning("Snapshot",
1600 "RDataFrame::Snapshot: could not correctly construct a leaflist for C-style array in column %s. This "
1601 "column will not be written out.",
1602 bname);
1603 } else {
1604 const auto leaflist = std::string(bname) + "[" + sizeLeafName + "]/" + rootbtype;
1605 // Use original basket size for existing branches and new basket size for new branches
1606 const auto branchBufSize = (basketSize > 0) ? basketSize : inputBranch->GetBasketSize();
1607 outputBranch = outputTree.Branch(outName.c_str(), dataPtr, leaflist.c_str(), branchBufSize);
1608 outputBranch->SetTitle(inputBranch->GetTitle());
1611 branchAddress = ab->data();
1612 }
1613 }
1614}
1615
1617 const std::string &inputBranchName, const std::string &outputBranchName,
1618 const std::type_info &typeInfo, int basketSize);
1619
1620/// Ensure that the TTree with the resulting snapshot can be written to the target TFile. This means checking that the
1621/// TFile can be opened in the mode specified in `opts`, deleting any existing TTrees in case
1622/// `opts.fOverwriteIfExists = true`, or throwing an error otherwise.
1623void EnsureValidSnapshotTTreeOutput(const RSnapshotOptions &opts, const std::string &treeName,
1624 const std::string &fileName);
1625
1626/// Helper object for a single-thread TTree-based Snapshot action
1627template <typename... ColTypes>
1628class R__CLING_PTRCHECK(off) SnapshotTTreeHelper : public RActionImpl<SnapshotTTreeHelper<ColTypes...>> {
1629 std::string fFileName;
1630 std::string fDirName;
1631 std::string fTreeName;
1632 RSnapshotOptions fOptions;
1633 std::unique_ptr<TFile> fOutputFile;
1634 std::unique_ptr<TTree> fOutputTree; // must be a ptr because TTrees are not copy/move constructible
1635 bool fBranchAddressesNeedReset{true};
1636 ColumnNames_t fInputBranchNames; // This contains the resolved aliases
1637 ColumnNames_t fOutputBranchNames;
1638 TTree *fInputTree = nullptr; // Current input tree. Set at initialization time (`InitTask`)
1639 // TODO we might be able to unify fBranches, fBranchAddresses and fOutputBranches
1640 std::vector<TBranch *> fBranches; // Addresses of branches in output, non-null only for the ones holding C arrays
1641 std::vector<void *> fBranchAddresses; // Addresses of objects associated to output branches
1643 std::vector<bool> fIsDefine;
1646
1647public:
1648 using ColumnTypes_t = TypeList<ColTypes...>;
1649 SnapshotTTreeHelper(std::string_view filename, std::string_view dirname, std::string_view treename,
1650 const ColumnNames_t &vbnames, const ColumnNames_t &bnames, const RSnapshotOptions &options,
1653 : fFileName(filename),
1654 fDirName(dirname),
1655 fTreeName(treename),
1656 fOptions(options),
1659 fBranches(vbnames.size(), nullptr),
1660 fBranchAddresses(vbnames.size(), nullptr),
1661 fIsDefine(std::move(isDefine)),
1664 {
1665 EnsureValidSnapshotTTreeOutput(fOptions, fTreeName, fFileName);
1666 }
1667
1668 SnapshotTTreeHelper(const SnapshotTTreeHelper &) = delete;
1671 {
1672 if (!fTreeName.empty() /*not moved from*/ && !fOutputFile /* did not run */ && fOptions.fLazy) {
1673 const auto fileOpenMode = [&]() {
1674 TString checkupdate = fOptions.fMode;
1675 checkupdate.ToLower();
1676 return checkupdate == "update" ? "updated" : "created";
1677 }();
1678 Warning("Snapshot",
1679 "A lazy Snapshot action was booked but never triggered. The tree '%s' in output file '%s' was not %s. "
1680 "In case it was desired instead, remember to trigger the Snapshot operation, by storing "
1681 "its result in a variable and for example calling the GetValue() method on it.",
1682 fTreeName.c_str(), fFileName.c_str(), fileOpenMode);
1683 }
1684 }
1685
1686 void InitTask(TTreeReader * /*treeReader*/, unsigned int /* slot */)
1687 {
1688 // We ask the input RLoopManager if it has a TTree. We cannot rely on getting this information when constructing
1689 // this action helper, since the TTree might change e.g. when ChangeSpec is called in-between distributed tasks.
1690 fInputTree = fInputLoopManager->GetTree();
1692 }
1693
1694 void Exec(unsigned int /* slot */, ColTypes &... values)
1695 {
1696 using ind_t = std::index_sequence_for<ColTypes...>;
1698 UpdateCArraysPtrs(values..., ind_t{});
1699 } else {
1700 SetBranches(values..., ind_t{});
1702 }
1703 fOutputTree->Fill();
1704 }
1705
1706 template <std::size_t... S>
1707 void UpdateCArraysPtrs(ColTypes &... values, std::index_sequence<S...> /*dummy*/)
1708 {
1709 // This code deals with branches which hold C arrays of variable size. It can happen that the buffers
1710 // associated to those is re-allocated. As a result the value of the pointer can change therewith
1711 // leaving associated to the branch of the output tree an invalid pointer.
1712 // With this code, we set the value of the pointer in the output branch anew when needed.
1713 // Nota bene: the extra ",0" after the invocation of SetAddress, is because that method returns void and
1714 // we need an int for the expander list.
1715 int expander[] = {(fBranches[S] && fBranchAddresses[S] != GetData(values)
1716 ? fBranches[S]->SetAddress(GetData(values)),
1717 fBranchAddresses[S] = GetData(values), 0 : 0, 0)...,
1718 0};
1719 (void)expander; // avoid unused variable warnings for older compilers such as gcc 4.9
1720 }
1721
1722 template <std::size_t... S>
1723 void SetBranches(ColTypes &... values, std::index_sequence<S...> /*dummy*/)
1724 {
1725 // create branches in output tree
1726 int expander[] = {
1728 fBranchAddresses[S], &values, fOutputBranches, fIsDefine[S], fOptions.fBasketSize),
1729 0)...,
1730 0};
1731 fOutputBranches.AssertNoNullBranchAddresses();
1732 (void)expander; // avoid unused variable warnings for older compilers such as gcc 4.9
1733 }
1734
1735 template <std::size_t... S>
1736 void SetEmptyBranches(TTree *inputTree, TTree &outputTree, std::index_sequence<S...>)
1737 {
1739 // We use the expander trick rather than a fold expression to avoid incurring in the bracket depth limit of clang
1741 fOutputBranchNames[S], typeid(ColTypes), fOptions.fBasketSize),
1742 0)...,
1743 0};
1744 (void)expander;
1745 }
1746
1747 void Initialize()
1748 {
1749 fOutputFile.reset(
1750 TFile::Open(fFileName.c_str(), fOptions.fMode.c_str(), /*ftitle=*/"",
1752 if(!fOutputFile)
1753 throw std::runtime_error("Snapshot: could not create output file " + fFileName);
1754
1755 TDirectory *outputDir = fOutputFile.get();
1756 if (!fDirName.empty()) {
1757 TString checkupdate = fOptions.fMode;
1758 checkupdate.ToLower();
1759 if (checkupdate == "update")
1760 outputDir = fOutputFile->mkdir(fDirName.c_str(), "", true); // do not overwrite existing directory
1761 else
1762 outputDir = fOutputFile->mkdir(fDirName.c_str());
1763 }
1764
1765 fOutputTree =
1766 std::make_unique<TTree>(fTreeName.c_str(), fTreeName.c_str(), fOptions.fSplitLevel, /*dir=*/outputDir);
1767
1768 if (fOptions.fAutoFlush)
1769 fOutputTree->SetAutoFlush(fOptions.fAutoFlush);
1770 }
1771
1772 void Finalize()
1773 {
1774 assert(fOutputTree != nullptr);
1775 assert(fOutputFile != nullptr);
1776
1777 // There were no entries to fill the TTree with (either the input TTree was empty or no event passed after
1778 // filtering). We have already created an empty TTree, now also create the branches to preserve the schema
1779 if (fOutputTree->GetEntries() == 0) {
1780 using ind_t = std::index_sequence_for<ColTypes...>;
1782 }
1783 // use AutoSave to flush TTree contents because TTree::Write writes in gDirectory, not in fDirectory
1784 fOutputTree->AutoSave("flushbaskets");
1785 // must destroy the TTree first, otherwise TFile will delete it too leading to a double delete
1786 fOutputTree.reset();
1787 fOutputFile->Close();
1788
1789 // Now connect the data source to the loop manager so it can be used for further processing
1790 auto fullTreeName = fDirName.empty() ? fTreeName : fDirName + '/' + fTreeName;
1791 fOutputLoopManager->SetDataSource(std::make_unique<ROOT::Internal::RDF::RTTreeDS>(fullTreeName, fFileName));
1792 }
1793
1794 std::string GetActionName() { return "Snapshot"; }
1795
1796 ROOT::RDF::SampleCallback_t GetSampleCallback() final
1797 {
1798 return [this](unsigned int, const RSampleInfo &) mutable { fBranchAddressesNeedReset = true; };
1799 }
1800
1801 /**
1802 * @brief Create a new SnapshotTTreeHelper with a different output file name
1803 *
1804 * @param newName A type-erased string with the output file name
1805 * @return SnapshotTTreeHelper
1806 *
1807 * This MakeNew implementation is tied to the cloning feature of actions
1808 * of the computation graph. In particular, cloning a Snapshot node usually
1809 * also involves changing the name of the output file, otherwise the cloned
1810 * Snapshot would overwrite the same file.
1811 */
1812 SnapshotTTreeHelper MakeNew(void *newName, std::string_view /*variation*/ = "nominal")
1813 {
1814 const std::string finalName = *reinterpret_cast<const std::string *>(newName);
1816 fDirName,
1817 fTreeName,
1820 fOptions,
1821 std::vector<bool>(fIsDefine),
1824 }
1825};
1826
1827/// Helper object for a multi-thread TTree-based Snapshot action
1828template <typename... ColTypes>
1829class R__CLING_PTRCHECK(off) SnapshotTTreeHelperMT : public RActionImpl<SnapshotTTreeHelperMT<ColTypes...>> {
1830 unsigned int fNSlots;
1831 std::unique_ptr<ROOT::TBufferMerger> fMerger; // must use a ptr because TBufferMerger is not movable
1832 std::vector<std::shared_ptr<ROOT::TBufferMergerFile>> fOutputFiles;
1833 std::vector<std::unique_ptr<TTree>> fOutputTrees;
1834 std::vector<int> fBranchAddressesNeedReset; // vector<bool> does not allow concurrent writing of different elements
1835 std::string fFileName; // name of the output file name
1836 std::string fDirName; // name of TFile subdirectory in which output must be written (possibly empty)
1837 std::string fTreeName; // name of output tree
1838 RSnapshotOptions fOptions; // struct holding options to pass down to TFile and TTree in this action
1839 ColumnNames_t fInputBranchNames; // This contains the resolved aliases
1840 ColumnNames_t fOutputBranchNames;
1841 std::vector<TTree *> fInputTrees; // Current input trees. Set at initialization time (`InitTask`)
1842 // Addresses of branches in output per slot, non-null only for the ones holding C arrays
1843 std::vector<std::vector<TBranch *>> fBranches;
1844 // Addresses associated to output branches per slot, non-null only for the ones holding C arrays
1845 std::vector<std::vector<void *>> fBranchAddresses;
1846 std::vector<RBranchSet> fOutputBranches;
1847 std::vector<bool> fIsDefine;
1850 TFile *fOutputFile; // Non-owning view on the output file
1851
1852public:
1853 using ColumnTypes_t = TypeList<ColTypes...>;
1854
1855 SnapshotTTreeHelperMT(const unsigned int nSlots, std::string_view filename, std::string_view dirname,
1856 std::string_view treename, const ColumnNames_t &vbnames, const ColumnNames_t &bnames,
1857 const RSnapshotOptions &options, std::vector<bool> &&isDefine,
1859 : fNSlots(nSlots),
1860 fOutputFiles(fNSlots),
1861 fOutputTrees(fNSlots),
1862 fBranchAddressesNeedReset(fNSlots, 1),
1863 fFileName(filename),
1864 fDirName(dirname),
1865 fTreeName(treename),
1866 fOptions(options),
1869 fInputTrees(fNSlots),
1870 fBranches(fNSlots, std::vector<TBranch *>(vbnames.size(), nullptr)),
1871 fBranchAddresses(fNSlots, std::vector<void *>(vbnames.size(), nullptr)),
1872 fOutputBranches(fNSlots),
1873 fIsDefine(std::move(isDefine)),
1876 {
1877 EnsureValidSnapshotTTreeOutput(fOptions, fTreeName, fFileName);
1878 }
1882 {
1883 if (!fTreeName.empty() /*not moved from*/ && fOptions.fLazy && !fOutputFiles.empty() &&
1884 std::all_of(fOutputFiles.begin(), fOutputFiles.end(), [](const auto &f) { return !f; }) /* never run */) {
1885 const auto fileOpenMode = [&]() {
1886 TString checkupdate = fOptions.fMode;
1887 checkupdate.ToLower();
1888 return checkupdate == "update" ? "updated" : "created";
1889 }();
1890 Warning("Snapshot",
1891 "A lazy Snapshot action was booked but never triggered. The tree '%s' in output file '%s' was not %s. "
1892 "In case it was desired instead, remember to trigger the Snapshot operation, by storing "
1893 "its result in a variable and for example calling the GetValue() method on it.",
1894 fTreeName.c_str(), fFileName.c_str(), fileOpenMode);
1895 }
1896 }
1897
1898 void InitTask(TTreeReader *r, unsigned int slot)
1899 {
1900 ::TDirectory::TContext c; // do not let tasks change the thread-local gDirectory
1901 if (!fOutputFiles[slot]) {
1902 // first time this thread executes something, let's create a TBufferMerger output directory
1903 fOutputFiles[slot] = fMerger->GetFile();
1904 }
1906 if (!fDirName.empty()) {
1907 // call returnExistingDirectory=true since MT can end up making this call multiple times
1908 treeDirectory = fOutputFiles[slot]->mkdir(fDirName.c_str(), "", true);
1909 }
1910 // re-create output tree as we need to create its branches again, with new input variables
1911 // TODO we could instead create the output tree and its branches, change addresses of input variables in each task
1913 std::make_unique<TTree>(fTreeName.c_str(), fTreeName.c_str(), fOptions.fSplitLevel, /*dir=*/treeDirectory);
1915 // TODO can be removed when RDF supports interleaved TBB task execution properly, see ROOT-10269
1916 fOutputTrees[slot]->SetImplicitMT(false);
1917 if (fOptions.fAutoFlush)
1918 fOutputTrees[slot]->SetAutoFlush(fOptions.fAutoFlush);
1919 if (r) {
1920 // We could be getting a task-local TTreeReader from the TTreeProcessorMT.
1921 fInputTrees[slot] = r->GetTree();
1922 } else {
1923 fInputTrees[slot] = fInputLoopManager->GetTree();
1924 }
1925 fBranchAddressesNeedReset[slot] = 1; // reset first event flag for this slot
1926 }
1927
1928 void FinalizeTask(unsigned int slot)
1929 {
1930 if (fOutputTrees[slot]->GetEntries() > 0)
1931 fOutputFiles[slot]->Write();
1932 // clear now to avoid concurrent destruction of output trees and input tree (which has them listed as fClones)
1933 fOutputTrees[slot].reset(nullptr);
1934 fOutputBranches[slot].Clear();
1935 }
1936
1937 void Exec(unsigned int slot, ColTypes &... values)
1938 {
1939 using ind_t = std::index_sequence_for<ColTypes...>;
1940 if (fBranchAddressesNeedReset[slot] == 0) {
1941 UpdateCArraysPtrs(slot, values..., ind_t{});
1942 } else {
1943 SetBranches(slot, values..., ind_t{});
1945 }
1946 fOutputTrees[slot]->Fill();
1947 auto entries = fOutputTrees[slot]->GetEntries();
1948 auto autoFlush = fOutputTrees[slot]->GetAutoFlush();
1949 if ((autoFlush > 0) && (entries % autoFlush == 0))
1950 fOutputFiles[slot]->Write();
1951 }
1952
1953 template <std::size_t... S>
1954 void UpdateCArraysPtrs(unsigned int slot, ColTypes &... values, std::index_sequence<S...> /*dummy*/)
1955 {
1956 // This code deals with branches which hold C arrays of variable size. It can happen that the buffers
1957 // associated to those is re-allocated. As a result the value of the pointer can change therewith
1958 // leaving associated to the branch of the output tree an invalid pointer.
1959 // With this code, we set the value of the pointer in the output branch anew when needed.
1960 // Nota bene: the extra ",0" after the invocation of SetAddress, is because that method returns void and
1961 // we need an int for the expander list.
1962 int expander[] = {(fBranches[slot][S] && fBranchAddresses[slot][S] != GetData(values)
1963 ? fBranches[slot][S]->SetAddress(GetData(values)),
1964 fBranchAddresses[slot][S] = GetData(values), 0 : 0, 0)...,
1965 0};
1966 (void)expander; // avoid unused parameter warnings (gcc 12.1)
1967 (void)slot; // Also "slot" might be unused, in case "values" is empty
1968 }
1969
1970 template <std::size_t... S>
1971 void SetBranches(unsigned int slot, ColTypes &... values, std::index_sequence<S...> /*dummy*/)
1972 {
1973 // hack to call TTree::Branch on all variadic template arguments
1974 int expander[] = {(SetBranchesHelper(fInputTrees[slot], *fOutputTrees[slot], fInputBranchNames[S],
1975 fOutputBranchNames[S], fBranches[slot][S], fBranchAddresses[slot][S],
1976 &values, fOutputBranches[slot], fIsDefine[S], fOptions.fBasketSize),
1977 0)...,
1978 0};
1979 fOutputBranches[slot].AssertNoNullBranchAddresses();
1980 (void)expander; // avoid unused parameter warnings (gcc 12.1)
1981 }
1982
1983 template <std::size_t... S>
1984 void SetEmptyBranches(TTree *inputTree, TTree &outputTree, std::index_sequence<S...>)
1985 {
1987 // We use the expander trick rather than a fold expression to avoid incurring in the bracket depth limit of clang
1989 fOutputBranchNames[S], typeid(ColTypes), fOptions.fBasketSize),
1990 0)...,
1991 0};
1992 (void)expander;
1993 }
1994
1995 void Initialize()
1996 {
1998 auto outFile = std::unique_ptr<TFile>{
1999 TFile::Open(fFileName.c_str(), fOptions.fMode.c_str(), /*ftitle=*/fFileName.c_str(), cs)};
2000 if (!outFile)
2001 throw std::runtime_error("Snapshot: could not create output file " + fFileName);
2002 fOutputFile = outFile.get();
2003 fMerger = std::make_unique<ROOT::TBufferMerger>(std::move(outFile));
2004 }
2005
2006 void Finalize()
2007 {
2008 assert(std::any_of(fOutputFiles.begin(), fOutputFiles.end(), [](const auto &ptr) { return ptr != nullptr; }));
2009
2010 for (auto &file : fOutputFiles) {
2011 if (file) {
2012 file->Write();
2013 file->Close();
2014 }
2015 }
2016
2017 // If there were no entries to fill the TTree with (either the input TTree was empty or no event passed after
2018 // filtering), create an empty TTree in the output file and create the branches to preserve the schema
2019 auto fullTreeName = fDirName.empty() ? fTreeName : fDirName + '/' + fTreeName;
2020 assert(fOutputFile && "Missing output file in Snapshot finalization.");
2021 if (!fOutputFile->Get(fullTreeName.c_str())) {
2022
2023 // First find in which directory we need to write the output TTree
2024 TDirectory *treeDirectory = fOutputFile;
2025 if (!fDirName.empty()) {
2026 treeDirectory = fOutputFile->mkdir(fDirName.c_str(), "", true);
2027 }
2029
2030 // Create the output TTree and create the user-requested branches
2031 auto outTree =
2032 std::make_unique<TTree>(fTreeName.c_str(), fTreeName.c_str(), fOptions.fSplitLevel, /*dir=*/treeDirectory);
2033 using ind_t = std::index_sequence_for<ColTypes...>;
2035
2036 fOutputFile->Write();
2037 }
2038
2039 // flush all buffers to disk by destroying the TBufferMerger
2040 fOutputFiles.clear();
2041 fMerger.reset();
2042
2043 // Now connect the data source to the loop manager so it can be used for further processing
2044 fOutputLoopManager->SetDataSource(std::make_unique<ROOT::Internal::RDF::RTTreeDS>(fullTreeName, fFileName));
2045 }
2046
2047 std::string GetActionName() { return "Snapshot"; }
2048
2049 ROOT::RDF::SampleCallback_t GetSampleCallback() final
2050 {
2051 return [this](unsigned int slot, const RSampleInfo &) mutable { fBranchAddressesNeedReset[slot] = 1; };
2052 }
2053
2054 /**
2055 * @brief Create a new SnapshotTTreeHelperMT with a different output file name
2056 *
2057 * @param newName A type-erased string with the output file name
2058 * @return SnapshotTTreeHelperMT
2059 *
2060 * This MakeNew implementation is tied to the cloning feature of actions
2061 * of the computation graph. In particular, cloning a Snapshot node usually
2062 * also involves changing the name of the output file, otherwise the cloned
2063 * Snapshot would overwrite the same file.
2064 */
2065 SnapshotTTreeHelperMT MakeNew(void *newName, std::string_view /*variation*/ = "nominal")
2066 {
2067 const std::string finalName = *reinterpret_cast<const std::string *>(newName);
2068 return SnapshotTTreeHelperMT{fNSlots,
2069 finalName,
2070 fDirName,
2071 fTreeName,
2074 fOptions,
2075 std::vector<bool>(fIsDefine),
2078 }
2079};
2080
2081/// Ensure that the RNTuple with the resulting snapshot can be written to the target TFile. This means checking that the
2082/// TFile can be opened in the mode specified in `opts`, deleting any existing RNTuples in case
2083/// `opts.fOverwriteIfExists = true`, or throwing an error otherwise.
2085 const std::string &fileName);
2086
2087/// Helper function to update the value of an RNTuple's field in the provided entry.
2088template <typename T>
2089void SetFieldsHelper(T &value, std::string_view fieldName, ROOT::REntry *entry)
2090{
2091 entry->BindRawPtr(fieldName, &value);
2092}
2093
2094/// Helper object for a single-thread RNTuple-based Snapshot action
2095template <typename... ColTypes>
2096class R__CLING_PTRCHECK(off) SnapshotRNTupleHelper : public RActionImpl<SnapshotRNTupleHelper<ColTypes...>> {
2097 std::string fFileName;
2098 std::string fDirName;
2099 std::string fNTupleName;
2100
2101 std::unique_ptr<TFile> fOutputFile{nullptr};
2102
2103 RSnapshotOptions fOptions;
2105 ColumnNames_t fInputFieldNames; // This contains the resolved aliases
2106 ColumnNames_t fOutputFieldNames;
2107 std::unique_ptr<ROOT::RNTupleWriter> fWriter{nullptr};
2108
2110
2111 std::vector<bool> fIsDefine;
2112
2113public:
2114 using ColumnTypes_t = TypeList<ColTypes...>;
2115 SnapshotRNTupleHelper(std::string_view filename, std::string_view dirname, std::string_view ntuplename,
2116 const ColumnNames_t &vfnames, const ColumnNames_t &fnames, const RSnapshotOptions &options,
2117 ROOT::Detail::RDF::RLoopManager *lm, std::vector<bool> &&isDefine)
2118 : fFileName(filename),
2119 fDirName(dirname),
2120 fNTupleName(ntuplename),
2121 fOptions(options),
2125 fIsDefine(std::move(isDefine))
2126 {
2127 EnsureValidSnapshotRNTupleOutput(fOptions, fNTupleName, fFileName);
2128 }
2129
2135 {
2136 if (!fNTupleName.empty() && !fOutputLoopManager->GetDataSource() && fOptions.fLazy)
2137 Warning("Snapshot", "A lazy Snapshot action was booked but never triggered.");
2138 }
2139
2140 void InitTask(TTreeReader *, unsigned int /* slot */) {}
2141
2142 void Exec(unsigned int /* slot */, ColTypes &...values)
2143 {
2144 using ind_t = std::index_sequence_for<ColTypes...>;
2145
2146 SetFields(values..., ind_t{});
2147 fWriter->Fill();
2148 }
2149
2150 template <std::size_t... S>
2151 void SetFields(ColTypes &...values, std::index_sequence<S...> /*dummy*/)
2152 {
2153 int expander[] = {(SetFieldsHelper(values, fOutputFieldNames[S], fOutputEntry), 0)..., 0};
2154 (void)expander; // avoid unused variable warnings for older compilers (gcc 14.1)
2155 }
2156
2157 void Initialize()
2158 {
2159 using ind_t = std::index_sequence_for<ColTypes...>;
2160
2161 auto model = ROOT::RNTupleModel::Create();
2162 MakeFields(*model, ind_t{});
2163 fOutputEntry = &model->GetDefaultEntry();
2164
2166 writeOptions.SetCompression(fOptions.fCompressionAlgorithm, fOptions.fCompressionLevel);
2167
2168 fOutputFile.reset(TFile::Open(fFileName.c_str(), fOptions.fMode.c_str()));
2169 if (!fOutputFile)
2170 throw std::runtime_error("Snapshot: could not create output file " + fFileName);
2171
2172 TDirectory *outputDir = fOutputFile.get();
2173 if (!fDirName.empty()) {
2174 TString checkupdate = fOptions.fMode;
2175 checkupdate.ToLower();
2176 if (checkupdate == "update")
2177 outputDir = fOutputFile->mkdir(fDirName.c_str(), "", true); // do not overwrite existing directory
2178 else
2179 outputDir = fOutputFile->mkdir(fDirName.c_str());
2180 }
2181
2182 fWriter = ROOT::RNTupleWriter::Append(std::move(model), fNTupleName, *outputDir, writeOptions);
2183 }
2184
2185 template <std::size_t... S>
2186 void MakeFields(ROOT::RNTupleModel &model, std::index_sequence<S...> /*dummy*/)
2187 {
2188 int expander[] = {(model.MakeField<ColTypes>(fOutputFieldNames[S]), 0)..., 0};
2189 (void)expander; // avoid unused variable warnings for older compilers (gcc 14.1)
2190 }
2191
2192 void Finalize()
2193 {
2194 fWriter.reset();
2195 // We can now set the data source of the loop manager for the RDataFrame that is returned by the Snapshot call.
2196 fOutputLoopManager->SetDataSource(
2197 std::make_unique<ROOT::RDF::RNTupleDS>(fDirName + "/" + fNTupleName, fFileName));
2198 }
2199
2200 std::string GetActionName() { return "Snapshot"; }
2201
2202 ROOT::RDF::SampleCallback_t GetSampleCallback() final
2203 {
2204 return [](unsigned int, const RSampleInfo &) mutable {};
2205 }
2206
2207 /**
2208 * @brief Create a new SnapshotRNTupleHelper with a different output file name
2209 *
2210 * @param newName A type-erased string with the output file name
2211 * @return SnapshotRNTupleHelper
2212 *
2213 * This MakeNew implementation is tied to the cloning feature of actions
2214 * of the computation graph. In particular, cloning a Snapshot node usually
2215 * also involves changing the name of the output file, otherwise the cloned
2216 * Snapshot would overwrite the same file.
2217 */
2219 {
2220 const std::string finalName = *reinterpret_cast<const std::string *>(newName);
2222 fNTupleName,
2225 fOptions,
2227 std::vector<bool>(fIsDefine)};
2228 }
2229};
2230
2231template <typename Acc, typename Merge, typename R, typename T, typename U,
2232 bool MustCopyAssign = std::is_same<R, U>::value>
2234 : public RActionImpl<AggregateHelper<Acc, Merge, R, T, U, MustCopyAssign>> {
2236 Merge fMerge;
2237 std::shared_ptr<U> fResult;
2239
2240public:
2241 using ColumnTypes_t = TypeList<T>;
2242
2243 AggregateHelper(Acc &&f, Merge &&m, const std::shared_ptr<U> &result, const unsigned int nSlots)
2244 : fAggregate(std::move(f)), fMerge(std::move(m)), fResult(result), fAggregators(nSlots, *result)
2245 {
2246 }
2247
2248 AggregateHelper(Acc &f, Merge &m, const std::shared_ptr<U> &result, const unsigned int nSlots)
2249 : fAggregate(f), fMerge(m), fResult(result), fAggregators(nSlots, *result)
2250 {
2251 }
2252
2253 AggregateHelper(AggregateHelper &&) = default;
2254 AggregateHelper(const AggregateHelper &) = delete;
2255
2256 void InitTask(TTreeReader *, unsigned int) {}
2257
2258 template <bool MustCopyAssign_ = MustCopyAssign, std::enable_if_t<MustCopyAssign_, int> = 0>
2259 void Exec(unsigned int slot, const T &value)
2260 {
2262 }
2263
2264 template <bool MustCopyAssign_ = MustCopyAssign, std::enable_if_t<!MustCopyAssign_, int> = 0>
2265 void Exec(unsigned int slot, const T &value)
2266 {
2268 }
2269
2270 void Initialize() { /* noop */}
2271
2273 bool MergeAll = std::is_same<void, MergeRet>::value>
2274 std::enable_if_t<MergeAll, void> Finalize()
2275 {
2276 fMerge(fAggregators);
2277 *fResult = fAggregators[0];
2278 }
2279
2281 bool MergeTwoByTwo = std::is_same<U, MergeRet>::value>
2282 std::enable_if_t<MergeTwoByTwo, void> Finalize(...) // ... needed to let compiler distinguish overloads
2283 {
2284 for (const auto &acc : fAggregators)
2285 *fResult = fMerge(*fResult, acc);
2286 }
2287
2288 U &PartialUpdate(unsigned int slot) { return fAggregators[slot]; }
2289
2290 std::string GetActionName() { return "Aggregate"; }
2291
2292 AggregateHelper MakeNew(void *newResult, std::string_view /*variation*/ = "nominal")
2293 {
2294 auto &result = *static_cast<std::shared_ptr<U> *>(newResult);
2295 return AggregateHelper(fAggregate, fMerge, result, fAggregators.size());
2296 }
2297};
2298
2299} // end of NS RDF
2300} // end of NS Internal
2301} // end of NS ROOT
2302
2303/// \endcond
2304
2305#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
void operator=(const TProof &)
Int_t Exec(const char *cmd, ESlaves list, Bool_t plusMaster)
Long64_t Finalize(Int_t query=-1, Bool_t force=kFALSE)
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.
The head node of a RDF computation graph.
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 ...
The REntry is a collection of values in an RNTuple corresponding to a complete row in the data set.
Definition REntry.hxx:54
The RNTupleModel encapulates the schema of an RNTuple.
static std::unique_ptr< RNTupleModel > Create()
std::shared_ptr< T > MakeField(std::string_view name, std::string_view description="")
Creates a new field given a name or {name, description} pair and a corresponding, default-constructed...
Common user-tunable settings for storing RNTuples.
static std::unique_ptr< RNTupleWriter > Append(std::unique_ptr< ROOT::RNTupleModel > model, std::string_view ntupleName, TDirectory &fileOrDirectory, const ROOT::RNTupleWriteOptions &options=ROOT::RNTupleWriteOptions())
Throws an exception if the model is null.
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:29
Collection abstract base class.
Definition TCollection.h:65
TDirectory * mkdir(const char *name, const char *title="", Bool_t returnExistingDirectory=kFALSE) override
Create a sub-directory "a" or a hierarchy of sub-directories "a/b/c/...".
TObject * Get(const char *namecycle) override
Return pointer to object identified by namecycle.
TDirectory::TContext keeps track and restore the current directory.
Definition TDirectory.h:89
Describe directory structure in memory.
Definition TDirectory.h:45
A ROOT file is an on-disk file, usually with extension .root, that stores objects in a file-system-li...
Definition TFile.h:131
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:4112
Int_t Write(const char *name=nullptr, Int_t opt=0, Int_t bufsiz=0) override
Write memory objects to this file.
Definition TFile.cxx:2458
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:698
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
void Add(TObject *obj) override
Definition TList.h:81
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:84
@ kEntriesReshuffled
If set, signals that this TTree is the output of the processing of another TTree, and the entries are...
Definition TTree.h:273
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)
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:324
char TypeName2ROOTTypeName(const std::string &b)
Convert type name (e.g.
Definition RDFUtils.cxx:269
void ResetIfPossible(TStatistic *h)
void EnsureValidSnapshotRNTupleOutput(const RSnapshotOptions &opts, const std::string &ntupleName, const std::string &fileName)
void EnsureValidSnapshotTTreeOutput(const RSnapshotOptions &opts, const std::string &treeName, const std::string &fileName)
constexpr std::size_t FindIdxTrue(const T &arr)
Definition Utils.hxx:241
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:34
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.
int fBasketSize
Set a custom basket size option.
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