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