Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RDFHelpers.hxx
Go to the documentation of this file.
1// Author: Enrico Guiraud, Danilo Piparo CERN 02/2018
2
3/*************************************************************************
4 * Copyright (C) 1995-2018, Rene Brun and Fons Rademakers. *
5 * All rights reserved. *
6 * *
7 * For the licensing terms see $ROOTSYS/LICENSE. *
8 * For the list of contributors see $ROOTSYS/README/CREDITS. *
9 *************************************************************************/
10
11// This header contains helper free functions that slim down RDataFrame's programming model
12
13#ifndef ROOT_RDF_HELPERS
14#define ROOT_RDF_HELPERS
15
19#include <ROOT/RResultHandle.hxx> // users of RunGraphs might rely on this transitive include
20#include <ROOT/TypeTraits.hxx>
21
22#include "RConfigure.h" // for R__HAS_ROOT7
23#ifdef R__HAS_ROOT7
24#include <ROOT/RHist.hxx>
25#include <ROOT/RHistEngine.hxx>
26#include <ROOT/RProfile.hxx>
27#endif
28
29#include <array>
30#include <chrono>
31#include <fstream>
32#include <functional>
33#include <map>
34#include <memory>
35#include <mutex>
36#include <type_traits>
37#include <utility> // std::index_sequence
38#include <vector>
39
40namespace ROOT {
41namespace Internal {
42namespace RDF {
43template <typename... ArgTypes, typename F>
45{
46 return std::function<bool(ArgTypes...)>([=](ArgTypes... args) mutable { return !f(args...); });
47}
48
49template <typename... ArgTypes, typename Ret, typename... Args>
50std::function<bool(ArgTypes...)> NotHelper(ROOT::TypeTraits::TypeList<ArgTypes...>, Ret (*f)(Args...))
51{
52 return std::function<bool(ArgTypes...)>([=](ArgTypes... args) mutable { return !f(args...); });
53}
54
55template <typename I, typename T, typename F>
57
58template <std::size_t... N, typename T, typename F>
59class PassAsVecHelper<std::index_sequence<N...>, T, F> {
60 template <std::size_t Idx>
61 using AlwaysT = T;
62 std::decay_t<F> fFunc;
63
64public:
65 PassAsVecHelper(F &&f) : fFunc(std::forward<F>(f)) {}
66 auto operator()(AlwaysT<N>... args) -> decltype(fFunc({args...})) { return fFunc({args...}); }
67};
68
69template <std::size_t N, typename T, typename F>
71{
72 return PassAsVecHelper<std::make_index_sequence<N>, T, F>(std::forward<F>(f));
73}
74
75/**
76 * \brief Helper function to add a copy of an object to a vector of shared_ptrs, used in the implementation of
77 * VariationsFor.
78 * \tparam T An object that is used as result of a RDataFrame action, e.g. a histogram
79 * \param obj The object to be copied and wrapped by a new std::shared_ptr.
80 *
81 * The default implementation of this function template uses copy constructor, which should work for most objects types
82 * since they are copied for each slot.
83 */
84template <typename T>
85std::shared_ptr<T> CopyForVariations(const T &obj)
86{
87 return std::make_shared<T>(obj);
88}
89
90#ifdef R__HAS_ROOT7
91/// \brief Specialization of CopyForVariations for ROOT::Experimental::RHist objects, which are not copyable but
92/// clonable.
93template <typename B>
94std::shared_ptr<ROOT::Experimental::RHist<B>> CopyForVariations(const ROOT::Experimental::RHist<B> &obj)
95{
96 return std::make_shared<ROOT::Experimental::RHist<B>>(obj.Clone());
97}
98
99/// \brief Specialization of CopyForVariations for ROOT::Experimental::RHistEngine objects, which are not copyable but
100/// clonable.
101template <typename B>
102std::shared_ptr<ROOT::Experimental::RHistEngine<B>> CopyForVariations(const ROOT::Experimental::RHistEngine<B> &obj)
103{
104 return std::make_shared<ROOT::Experimental::RHistEngine<B>>(obj.Clone());
105}
106
107/// \brief Specialization of CopyForVariations for ROOT::Experimental::RProfile objects, which are not copyable but
108/// clonable.
109inline std::shared_ptr<ROOT::Experimental::RProfile> CopyForVariations(const ROOT::Experimental::RProfile &obj)
110{
111 return std::make_shared<ROOT::Experimental::RProfile>(obj.Clone());
112}
113#endif
114
115} // namespace RDF
116} // namespace Internal
117
118namespace RDF {
120
121// clang-format off
122/// Given a callable with signature bool(T1, T2, ...) return a callable with same signature that returns the negated result
123///
124/// The callable must have one single non-template definition of operator(). This is a limitation with respect to
125/// std::not_fn, required for interoperability with RDataFrame.
126// clang-format on
127template <typename F,
128 typename Args = typename ROOT::TypeTraits::CallableTraits<std::decay_t<F>>::arg_types_nodecay,
129 typename Ret = typename ROOT::TypeTraits::CallableTraits<std::decay_t<F>>::ret_type>
130auto Not(F &&f) -> decltype(RDFInternal::NotHelper(Args(), std::forward<F>(f)))
131{
132 static_assert(std::is_same<Ret, bool>::value, "RDF::Not requires a callable that returns a bool.");
133 return RDFInternal::NotHelper(Args(), std::forward<F>(f));
134}
135
136// clang-format off
137/// PassAsVec is a callable generator that allows passing N variables of type T to a function as a single collection.
138///
139/// PassAsVec<N, T>(func) returns a callable that takes N arguments of type T, passes them down to function `func` as
140/// an initializer list `{t1, t2, t3,..., tN}` and returns whatever f({t1, t2, t3, ..., tN}) returns.
141///
142/// Note that for this to work with RDataFrame the type of all columns that the callable is applied to must be exactly T.
143/// Example usage together with RDataFrame ("varX" columns must all be `float` variables):
144/// \code
145/// bool myVecFunc(std::vector<float> args);
146/// df.Filter(PassAsVec<3, float>(myVecFunc), {"var1", "var2", "var3"});
147/// \endcode
148// clang-format on
149template <std::size_t N, typename T, typename F>
151{
152 return RDFInternal::PassAsVecHelper<std::make_index_sequence<N>, T, F>(std::forward<F>(f));
153}
154
155// clang-format off
156/// Create a graphviz representation of the dataframe computation graph, return it as a string.
157/// \param[in] node any node of the graph. Called on the head (first) node, it prints the entire graph. Otherwise, only the branch the node belongs to.
158///
159/// The output can be displayed with a command akin to `dot -Tpng output.dot > output.png && open output.png`.
160///
161/// Note that "hanging" Defines, i.e. Defines without downstream nodes, will not be displayed by SaveGraph as they are
162/// effectively optimized away from the computation graph.
163///
164/// Note that SaveGraph is not thread-safe and must not be called concurrently from different threads.
165// clang-format on
166template <typename NodeType>
167std::string SaveGraph(NodeType node)
168{
170 return helper.RepresentGraph(node);
171}
172
173// clang-format off
174/// Create a graphviz representation of the dataframe computation graph, write it to the specified file.
175/// \param[in] node any node of the graph. Called on the head (first) node, it prints the entire graph. Otherwise, only the branch the node belongs to.
176/// \param[in] outputFile file where to save the representation.
177///
178/// The output can be displayed with a command akin to `dot -Tpng output.dot > output.png && open output.png`.
179///
180/// Note that "hanging" Defines, i.e. Defines without downstream nodes, will not be displayed by SaveGraph as they are
181/// effectively optimized away from the computation graph.
182///
183/// Note that SaveGraph is not thread-safe and must not be called concurrently from different threads.
184// clang-format on
185template <typename NodeType>
186void SaveGraph(NodeType node, const std::string &outputFile)
187{
189 std::string dotGraph = helper.RepresentGraph(node);
190
191 std::ofstream out(outputFile);
192 if (!out.is_open()) {
193 throw std::runtime_error("Could not open output file \"" + outputFile + "\"for reading");
194 }
195
196 out << dotGraph;
197 out.close();
198}
199
200// clang-format off
201/// Cast a RDataFrame node to the common type ROOT::RDF::RNode
202/// \param[in] node Any node of a RDataFrame graph
203// clang-format on
204template <typename NodeType>
205RNode AsRNode(NodeType node)
206{
207 return node;
208}
209
210// clang-format off
211/// Run the event loops of multiple RDataFrames concurrently.
212/// \param[in] handles A vector of RResultHandles whose event loops should be run.
213/// \return The number of distinct computation graphs that have been processed.
214///
215/// This function triggers the event loop of all computation graphs which relate to the
216/// given RResultHandles. The advantage compared to running the event loop implicitly by accessing the
217/// RResultPtr is that the event loops will run concurrently. Therefore, the overall
218/// computation of all results can be scheduled more efficiently.
219/// It should be noted that user-defined operations (e.g., Filters and Defines) of the different RDataFrame graphs are assumed to be safe to call concurrently.
220/// RDataFrame will pass slot numbers in the range [0, NThread-1] to all helpers used in nodes such as DefineSlot. NThread is the number of threads ROOT was
221/// configured with in EnableImplicitMT().
222/// Slot numbers are unique across all graphs, so no two tasks with the same slot number will run concurrently. Note that it is not guaranteed that each slot
223/// number will be reached in every graph.
224///
225/// ~~~{.cpp}
226/// ROOT::RDataFrame df1("tree1", "file1.root");
227/// auto r1 = df1.Histo1D("var1");
228///
229/// ROOT::RDataFrame df2("tree2", "file2.root");
230/// auto r2 = df2.Sum("var2");
231///
232/// // RResultPtr -> RResultHandle conversion is automatic
233/// ROOT::RDF::RunGraphs({r1, r2});
234/// ~~~
235// clang-format on
236unsigned int RunGraphs(std::vector<RResultHandle> handles);
237
238namespace Experimental {
239
240/// \brief Produce all required systematic variations for the given result.
241/// \param[in] resPtr The result for which variations should be produced.
242/// \return A \ref ROOT::RDF::Experimental::RResultMap "RResultMap" object with full variation names as strings
243/// (e.g. "pt:down") and the corresponding varied results as values.
244///
245/// A given input RResultPtr<T> produces a corresponding RResultMap<T> with a "nominal"
246/// key that will return a value identical to the one contained in the original RResultPtr.
247/// Other keys correspond to the varied values of this result, one for each variation
248/// that the result depends on.
249/// VariationsFor does not trigger the event loop. The event loop is only triggered
250/// upon first access to a valid key, similarly to what happens with RResultPtr.
251///
252/// If the result does not depend, directly or indirectly, from any registered systematic variation, the
253/// returned RResultMap will contain only the "nominal" key.
254///
255/// See RDataFrame's \ref ROOT::RDF::RInterface::Vary() "Vary" method for more information and example usages.
256///
257/// \note Currently, producing variations for the results of \ref ROOT::RDF::RInterface::Display() "Display",
258/// \ref ROOT::RDF::RInterface::Report() "Report" and \ref ROOT::RDF::RInterface::Snapshot() "Snapshot"
259/// actions is not supported.
260///
261/// \note The varied values are constructed by copying the value held by the RResultPtr \p resPtr.
262//
263// An overview of how systematic variations work internally. Given N variations (including the nominal):
264//
265// RResultMap owns RVariedAction
266// N results N action helpers
267// N previous filters
268// N*#input_cols column readers
269//
270// ...and each RFilter and RDefine knows for what universe it needs to construct column readers ("nominal" by default).
271template <typename T>
273{
275 static_assert(!std::is_same_v<T, SnapshotResult_t>,
276 "Snapshot with variations can only be enabled via RSnapshotOptions.");
277
278 R__ASSERT(resPtr != nullptr && "Calling VariationsFor on an empty RResultPtr");
279
280 // populate parts of the computation graph for which we only have "empty shells", e.g. RJittedActions and
281 // RJittedFilters
282 resPtr.fLoopManager->Jit();
283
284 std::unique_ptr<RDFInternal::RActionBase> variedAction;
285 std::vector<std::shared_ptr<T>> variedResults;
286
287 std::shared_ptr<RDFInternal::RActionBase> nominalAction = resPtr.fActionPtr;
288 std::vector<std::string> variations = nominalAction->GetVariations();
289 const auto nVariations = variations.size();
290
291 if (nVariations > 0) {
292 // clone the result once for each variation
293 variedResults.reserve(nVariations);
294 for (auto i = 0u; i < nVariations; ++i){
295
296 // Make a copy of the result object for this variation
297 variedResults.push_back(ROOT::Internal::RDF::CopyForVariations(*resPtr.fObjPtr));
298
299 // Check if the result's type T inherits from TNamed
300 if constexpr (std::is_base_of<TNamed, T>::value) {
301 // Get the current variation name
302 std::string variationName = variations[i];
303 // Replace the colon with an underscore
304 std::replace(variationName.begin(), variationName.end(), ':', '_');
305 // Get a pointer to the corresponding varied result
306 auto &variedResult = variedResults.back();
307 // Set the varied result's name to NOMINALNAME_VARIATIONAME
308 variedResult->SetName((std::string(variedResult->GetName()) + "_" + variationName).c_str());
309 }
310 }
311
312 std::vector<void *> typeErasedResults;
313 typeErasedResults.reserve(variedResults.size());
314 for (auto &res : variedResults)
315 typeErasedResults.emplace_back(&res);
316
317 // Create the RVariedAction and inject it in the computation graph.
318 // This recursively creates all the required varied column readers and upstream nodes of the computation graph.
319 variedAction = nominalAction->MakeVariedAction(std::move(typeErasedResults));
320 }
321
322 return RDFInternal::MakeResultMap<T>(resPtr.fObjPtr, std::move(variedResults), std::move(variations),
323 *resPtr.fLoopManager, std::move(nominalAction), std::move(variedAction));
324}
325
326/// \brief Add ProgressBar to a ROOT::RDF::RNode
327/// \param[in] df RDataFrame node at which ProgressBar is called.
328///
329/// The ProgressBar can be added not only at the RDataFrame head node, but also at any any computational node,
330/// such as Filter or Define.
331/// ###Example usage:
332/// ~~~{.cpp}
333/// ROOT::RDataFrame df("tree", "file.root");
334/// auto df_1 = ROOT::RDF::RNode(df.Filter("x>1"));
335/// ROOT::RDF::Experimental::AddProgressBar(df_1);
336/// ~~~
338
339/// \brief Add ProgressBar to an RDataFrame
340/// \param[in] df RDataFrame for which ProgressBar is called.
341///
342/// This function adds a ProgressBar to display the event statistics in the terminal every
343/// \b m events and every \b n seconds, including elapsed time, currently processed file,
344/// currently processed events, the rate of event processing
345/// and an estimated remaining time (per file being processed).
346/// ProgressBar should be added after the dataframe object (df) is created first:
347/// ~~~{.cpp}
348/// ROOT::RDataFrame df("tree", "file.root");
349/// ROOT::RDF::Experimental::AddProgressBar(df);
350/// ~~~
351/// For more details see ROOT::RDF::Experimental::ProgressHelper Class.
353
354/// @brief Set the number of threads sharing one TH3 in RDataFrame.
355/// When RDF runs multi-threaded, each thread typically clones every histogram in the computation graph.
356/// If this consumes too much memory, N threads can share one clone.
357/// Higher values might slow down RDF because they lead to higher contention on the TH3Ds, but save memory.
358/// Lower values run faster with less contention at the cost of higher memory usage.
359/// @param nThread Number of threads that share a TH3D.
360void ThreadsPerTH3(unsigned int nThread = 1);
361
362/// RDF progress helper.
363/// This class provides callback functions to the RDataFrame. The event statistics
364/// (including elapsed time, currently processed file, currently processed events, the rate of event processing
365/// and an estimated remaining time (per file being processed))
366/// are recorded and printed in the terminal every m events and every n seconds.
367/// ProgressHelper::operator()(unsigned int, T&) is thread safe, and can be used as a callback in MT mode.
368/// ProgressBar should be added after creating the dataframe object (df):
369/// ~~~{.cpp}
370/// ROOT::RDataFrame df("tree", "file.root");
371/// ROOT::RDF::Experimental::AddProgressBar(df);
372/// ~~~
373/// alternatively RDataFrame can be cast to an RNode first giving it more flexibility.
374/// For example, it can be called at any computational node, such as Filter or Define, not only the head node,
375/// with no change to the ProgressBar function itself:
376/// ~~~{.cpp}
377/// ROOT::RDataFrame df("tree", "file.root");
378/// auto df_1 = ROOT::RDF::RNode(df.Filter("x>1"));
379/// ROOT::RDF::Experimental::AddProgressBar(df_1);
380/// ~~~
382private:
383 std::size_t ComputeTotalEvents() const;
384 double EvtPerSec() const;
385 void PrintProgressAndStats(std::ostream &stream, std::size_t currentEventCount,
386 std::chrono::seconds totalElapsedSeconds) const;
387 std::pair<std::size_t, std::chrono::seconds> RecordEvtCountAndTime();
388 void Update();
389
390 bool const fIsTTY;
392
393 std::atomic<std::size_t> fProcessedEvents{0};
394 std::size_t fLastProcessedEvents{0};
395 std::size_t const fIncrement;
396 unsigned int const fNColumns;
397 unsigned int const fTotalFiles;
398
399 std::array<double, 10> fEventsPerSecondStatistics;
401
402 std::chrono::time_point<std::chrono::system_clock> const fBeginTime = std::chrono::system_clock::now();
403 std::chrono::time_point<std::chrono::system_clock> fLastPrintTime = fBeginTime;
404 std::chrono::seconds const fPrintInterval;
405
406 // Mutex to ensure that only one thread updates the progress bar.
407 // Lock this mutex to update any of the members above:
408 std::mutex fUpdateMutex;
409
410 mutable std::mutex fSampleNameToEventEntriesMutex; // Mutex to protect access to the below map
411 std::map<std::string, ULong64_t> fSampleNameToEventEntries; // Filename, events in the file
412
413public:
414 /// Create a progress helper.
415 /// \param increment RDF callbacks are called every `n` events. Pass this `n` here.
416 /// \param totalFiles number of files read in the RDF.
417 /// \param printInterval Update stats every `n` seconds.
418 /// \param useColors Use shell colour codes to colour the output. Automatically disabled when
419 /// we are not writing to a tty.
420 ProgressHelper(std::size_t increment, unsigned int totalFiles, unsigned int printInterval = 0,
421 bool useColors = true);
422 ProgressHelper(ProgressHelper const &) = delete; // The mutexes and atomics won't allow copy/move
424 ~ProgressHelper() = default;
427
428 void RegisterNewSample(unsigned int /*slot*/, const ROOT::RDF::RSampleInfo &id);
429
430 /// Thread-safe callback for RDataFrame.
431 /// It will record elapsed times and event statistics, and print a progress bar every n seconds (set by the
432 /// fPrintInterval). The function arguments are ignored.
433 template <typename T>
434 void operator()(unsigned int /*slot*/, T & /*value*/)
435 {
436 Update();
437 }
438 void PrintStatsFinal(std::size_t exactTotalEvents) const;
439};
440} // namespace Experimental
441} // namespace RDF
442} // namespace ROOT
443#endif
#define f(i)
Definition RSha256.hxx:104
#define R__ASSERT(e)
Checks condition e and reports a fatal error if it's false.
Definition TError.h:139
#define N
void Jit()
Add RDF nodes that require just-in-time compilation to the computation graph.
A histogram data structure to bin data along multiple dimensions.
RHistEngine Clone() const
Clone this histogram engine.
A histogram for aggregation of data along multiple dimensions.
Definition RHist.hxx:66
RHist Clone() const
Clone this histogram.
Definition RHist.hxx:440
A profile histogram, computing statistical quantities of an additional variable per bin.
Definition RProfile.hxx:58
RProfile Clone() const
Clone this profile histogram.
Definition RProfile.hxx:537
std::string RepresentGraph(ROOT::RDataFrame &rDataFrame)
Starting from the root node, prints the entire graph.
std::size_t ComputeTotalEvents() const
Compute total events in all open files.
ProgressHelper(std::size_t increment, unsigned int totalFiles, unsigned int printInterval=0, bool useColors=true)
Create a progress helper.
void PrintStatsFinal(std::size_t exactTotalEvents) const
std::pair< std::size_t, std::chrono::seconds > RecordEvtCountAndTime()
Record current event counts and time stamp, populate evts/s statistics array.
void RegisterNewSample(unsigned int, const ROOT::RDF::RSampleInfo &id)
Register a new sample for completion statistics.
std::chrono::time_point< std::chrono::system_clock > const fBeginTime
void Update()
Record number of events processed and update progress bar.
std::map< std::string, ULong64_t > fSampleNameToEventEntries
ProgressHelper(ProgressHelper const &)=delete
ProgressHelper & operator=(ProgressHelper &&)=delete
ProgressHelper(ProgressHelper &&)=delete
std::chrono::seconds const fPrintInterval
double EvtPerSec() const
Compute a running mean of events/s.
std::atomic< std::size_t > fProcessedEvents
std::array< double, 10 > fEventsPerSecondStatistics
std::chrono::time_point< std::chrono::system_clock > fLastPrintTime
void operator()(unsigned int, T &)
Thread-safe callback for RDataFrame.
void PrintProgressAndStats(std::ostream &stream, std::size_t currentEventCount, std::chrono::seconds totalElapsedSeconds) const
Print event and time statistics.
ProgressHelper & operator=(ProgressHelper const &)=delete
The public interface to the RDataFrame federation of classes.
Smart pointer for the return type of actions.
std::shared_ptr< T > fObjPtr
Shared pointer encapsulating the wrapped result.
RDFDetail::RLoopManager * fLoopManager
Non-owning pointer to the RLoopManager at the root of this computation graph.
std::shared_ptr< RDFInternal::RActionBase > fActionPtr
Owning pointer to the action that will produce this result.
This type represents a sample identifier, to be used in conjunction with RDataFrame features such as ...
ROOT's RDataFrame offers a modern, high-level interface for analysis of data stored in TTree ,...
std::function< bool(ArgTypes...)> NotHelper(ROOT::TypeTraits::TypeList< ArgTypes... >, F &&f)
auto PassAsVec(F &&f) -> PassAsVecHelper< std::make_index_sequence< N >, T, F >
std::shared_ptr< T > CopyForVariations(const T &obj)
Helper function to add a copy of an object to a vector of shared_ptrs, used in the implementation of ...
void ThreadsPerTH3(unsigned int nThread=1)
Set the number of threads sharing one TH3 in RDataFrame.
RResultMap< T > VariationsFor(RResultPtr< T > resPtr)
Produce all required systematic variations for the given result.
void AddProgressBar(ROOT::RDF::RNode df)
Add ProgressBar to a ROOT::RDF::RNode.
auto Not(F &&f) -> decltype(RDFInternal::NotHelper(Args(), std::forward< F >(f)))
Given a callable with signature bool(T1, T2, ...) return a callable with same signature that returns ...
std::string SaveGraph(NodeType node)
Create a graphviz representation of the dataframe computation graph, return it as a string.
RNode AsRNode(NodeType node)
Cast a RDataFrame node to the common type ROOT::RDF::RNode.
Lightweight storage for a collection of types.