Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RResultPtr.hxx
Go to the documentation of this file.
1// Author: Enrico Guiraud, Danilo Piparo CERN 03/2017
2
3/*************************************************************************
4 * Copyright (C) 1995-2020, 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#ifndef ROOT_RRESULTPTR
12#define ROOT_RRESULTPTR
13
15#include "RtypesCore.h"
17#include "ROOT/TypeTraits.hxx"
18#include "TError.h" // Warning
19
20#include <memory>
21#include <functional>
22#include <type_traits> // std::is_constructible
23
24namespace ROOT {
25namespace RDF {
26template <typename T>
27class RResultPtr;
28
29namespace Experimental {
30template <typename T>
31class RResultMap;
32
33template <typename T>
34RResultMap<T> VariationsFor(RResultPtr<T> resPtr);
35} // namespace Experimental
36
37template <typename Proxied, typename DataSource>
38class RInterface;
39} // namespace RDF
40
41namespace Internal {
42namespace RDF {
44
45// no-op overload
46template <typename T>
48{
49}
50
51template <typename DS>
54{
55 if (!r.IsReady()) {
56 Warning("Snapshot", "A lazy Snapshot action was booked but never triggered.");
57 }
58}
59}
60} // namespace Internal
61
62namespace Detail {
63namespace RDF {
65// Fwd decl for RResultPtr
66template <typename T>
67RResultPtr<T> MakeResultPtr(const std::shared_ptr<T> &r, RLoopManager &df,
68 std::shared_ptr<ROOT::Internal::RDF::RActionBase> actionPtr);
69
70// Fwd decl for GetMergeableValue
71template <typename T>
72class RMergeableValue;
73
74template <typename T>
75std::unique_ptr<RMergeableValue<T>> GetMergeableValue(RResultPtr<T> &rptr);
76} // namespace RDF
77} // namespace Detail
78namespace RDF {
81namespace TTraits = ROOT::TypeTraits;
82
83/// Smart pointer for the return type of actions
84/**
85\class ROOT::RDF::RResultPtr
86\ingroup dataframe
87\brief A wrapper around the result of RDataFrame actions able to trigger calculations lazily.
88\tparam T Type of the action result
89
90A smart pointer which allows to access the result of a RDataFrame action. The
91methods of the encapsulated object can be accessed via the arrow operator.
92Upon invocation of the arrow operator or dereferencing (`operator*`), the
93loop on the events and calculations of all scheduled actions are executed
94if needed.
95It is possible to iterate on the result proxy if the proxied object is a collection.
96~~~{.cpp}
97for (auto& myItem : myResultProxy) { ... };
98~~~
99If iteration is not supported by the type of the proxied object, a compilation error is thrown.
100
101*/
102template <typename T>
104 // private using declarations
105 using SPT_t = std::shared_ptr<T>;
106
107 // friend declarations
108 template <typename T1>
109 friend class RResultPtr;
110
111 template <typename T1>
113 std::shared_ptr<RDFInternal::RActionBase>);
114
115 template <typename T1>
117
118 template <class T1, class T2>
119 friend bool operator==(const RResultPtr<T1> &lhs, const RResultPtr<T2> &rhs);
120 template <class T1, class T2>
121 friend bool operator!=(const RResultPtr<T1> &lhs, const RResultPtr<T2> &rhs);
122 template <class T1>
123 friend bool operator==(const RResultPtr<T1> &lhs, std::nullptr_t rhs);
124 template <class T1>
125 friend bool operator==(std::nullptr_t lhs, const RResultPtr<T1> &rhs);
126 template <class T1>
127 friend bool operator!=(const RResultPtr<T1> &lhs, std::nullptr_t rhs);
128 template <class T1>
129 friend bool operator!=(std::nullptr_t lhs, const RResultPtr<T1> &rhs);
130 friend std::unique_ptr<RDFDetail::RMergeableValue<T>> RDFDetail::GetMergeableValue<T>(RResultPtr<T> &rptr);
131
133
134 friend class RResultHandle;
135
136 /// \cond HIDDEN_SYMBOLS
137 template <typename V, bool hasBeginEnd = TTraits::HasBeginAndEnd<V>::value>
138 struct RIterationHelper {
139 using Iterator_t = void;
140 void GetBegin(const V &) { static_assert(sizeof(V) == 0, "It does not make sense to ask begin for this class."); }
141 void GetEnd(const V &) { static_assert(sizeof(V) == 0, "It does not make sense to ask end for this class."); }
142 };
143
144 template <typename V>
145 struct RIterationHelper<V, true> {
146 using Iterator_t = decltype(std::begin(std::declval<V>()));
147 static Iterator_t GetBegin(const V &v) { return std::begin(v); };
148 static Iterator_t GetEnd(const V &v) { return std::end(v); };
149 };
150 /// \endcond
151
152 /// Non-owning pointer to the RLoopManager at the root of this computation graph.
153 /// The RLoopManager is guaranteed to be always in scope if fLoopManager is not a nullptr.
155 SPT_t fObjPtr; ///< Shared pointer encapsulating the wrapped result
156 /// Owning pointer to the action that will produce this result.
157 /// Ownership is shared with other copies of this ResultPtr.
158 std::shared_ptr<RDFInternal::RActionBase> fActionPtr;
159
160 /// Triggers the event loop in the RLoopManager
161 void TriggerRun();
162
163 /// Get the pointer to the encapsulated result.
164 /// Ownership is not transferred to the caller.
165 /// Triggers event loop and execution of all actions booked in the associated RLoopManager.
166 T *Get()
167 {
168 if (fActionPtr != nullptr && !fActionPtr->HasRun())
169 TriggerRun();
170 return fObjPtr.get();
171 }
172
174 {
175 if (fObjPtr == nullptr)
176 throw std::runtime_error("Trying to access the contents of a null RResultPtr.");
177 }
178
179 RResultPtr(std::shared_ptr<T> objPtr, RDFDetail::RLoopManager *lm,
180 std::shared_ptr<RDFInternal::RActionBase> actionPtr)
181 : fLoopManager(lm), fObjPtr(std::move(objPtr)), fActionPtr(std::move(actionPtr))
182 {
183 }
184
185public:
186 using Value_t = T; ///< Convenience alias to simplify access to proxied type
187 static constexpr ULong64_t kOnce = 0ull; ///< Convenience definition to express a callback must be executed once
188
189 RResultPtr() = default;
190 RResultPtr(const RResultPtr &) = default;
191 RResultPtr(RResultPtr &&) = default;
192 RResultPtr &operator=(const RResultPtr &) = default;
194 explicit operator bool() const { return bool(fObjPtr); }
196 {
197 if (fObjPtr.use_count() == 1) {
199 }
200 }
201
202 /// Convert a RResultPtr<T2> to a RResultPtr<T>.
203 ///
204 /// Useful e.g. to store a number of RResultPtr<TH1D> and RResultPtr<TH2D> in a std::vector<RResultPtr<TH1>>.
205 /// The requirements on T2 and T are the same as for conversion between std::shared_ptr<T2> and std::shared_ptr<T>.
206 template <typename T2,
207 std::enable_if_t<std::is_constructible<std::shared_ptr<T>, std::shared_ptr<T2>>::value, int> = 0>
209 {
210 }
211
212 /// Get a const reference to the encapsulated object.
213 /// Triggers event loop and execution of all actions booked in the associated RLoopManager.
214 const T &GetValue()
215 {
216 ThrowIfNull();
217 return *Get();
218 }
219
220 /// Get the pointer to the encapsulated object.
221 /// Triggers event loop and execution of all actions booked in the associated RLoopManager.
222 T *GetPtr() { return Get(); }
223
224 /// Get a pointer to the encapsulated object.
225 /// Triggers event loop and execution of all actions booked in the associated RLoopManager.
227 {
228 ThrowIfNull();
229 return *Get();
230 }
231
232 /// Get a pointer to the encapsulated object.
233 /// Ownership is not transferred to the caller.
234 /// Triggers event loop and execution of all actions booked in the associated RLoopManager.
236 {
237 ThrowIfNull();
238 return Get();
239 }
240
241 /// Return an iterator to the beginning of the contained object if this makes
242 /// sense, throw a compilation error otherwise
243 typename RIterationHelper<T>::Iterator_t begin()
244 {
245 ThrowIfNull();
246 if (!fActionPtr->HasRun())
247 TriggerRun();
248 return RIterationHelper<T>::GetBegin(*fObjPtr);
249 }
250
251 /// Return an iterator to the end of the contained object if this makes
252 /// sense, throw a compilation error otherwise
253 typename RIterationHelper<T>::Iterator_t end()
254 {
255 ThrowIfNull();
256 if (!fActionPtr->HasRun())
257 TriggerRun();
258 return RIterationHelper<T>::GetEnd(*fObjPtr);
259 }
260
261 // clang-format off
262 /// Register a callback that RDataFrame will execute "everyNEvents" on a partial result.
263 ///
264 /// \param[in] everyNEvents Frequency at which the callback will be called, as a number of events processed
265 /// \param[in] callback a callable with signature `void(Value_t&)` where Value_t is the type of the value contained in this RResultPtr
266 /// \return this RResultPtr, to allow chaining of OnPartialResultSlot with other calls
267 ///
268 /// The callback must be a callable (lambda, function, functor class...) that takes a reference to the result type as
269 /// argument and returns nothing. RDataFrame will invoke registered callbacks passing partial action results as
270 /// arguments to them (e.g. a histogram filled with a part of the selected events, a counter incremented only up to a
271 /// certain point, a mean over a subset of the events and so forth).
272 ///
273 /// Callbacks can be used e.g. to inspect partial results of the analysis while the event loop is running. For
274 /// example one can draw an up-to-date version of a result histogram every 100 entries like this:
275 /// \code{.cpp}
276 /// auto h = tdf.Histo1D("x");
277 /// TCanvas c("c","x hist");
278 /// h.OnPartialResult(100, [&c](TH1D &h_) { c.cd(); h_.Draw(); c.Update(); });
279 /// h->Draw(); // event loop runs here, this `Draw` is executed after the event loop is finished
280 /// \endcode
281 ///
282 /// A value of 0 for everyNEvents indicates the callback must be executed only once, before running the event loop.
283 /// A conveniece definition `kOnce` is provided to make this fact more expressive in user code (see snippet below).
284 /// Multiple callbacks can be registered with the same RResultPtr (i.e. results of RDataFrame actions) and will
285 /// be executed sequentially. Callbacks are executed in the order they were registered.
286 /// The type of the value contained in a RResultPtr is also available as RResultPtr<T>::Value_t, e.g.
287 /// \code{.cpp}
288 /// auto h = tdf.Histo1D("x");
289 /// // h.kOnce is 0
290 /// // decltype(h)::Value_t is TH1D
291 /// \endcode
292 ///
293 /// When implicit multi-threading is enabled, the callback:
294 /// - will never be executed by multiple threads concurrently: it needs not be thread-safe. For example the snippet
295 /// above that draws the partial histogram on a canvas works seamlessly in multi-thread event loops.
296 /// - will always be executed "everyNEvents": partial results will "contain" that number of events more from
297 /// one call to the next
298 /// - might be executed by a different worker thread at different times: the value of `std::this_thread::get_id()`
299 /// might change between calls
300 ///
301 /// To register a callback that is called by _each_ worker thread (concurrently) every N events one can use
302 /// OnPartialResultSlot().
303 // clang-format on
304 RResultPtr<T> &OnPartialResult(ULong64_t everyNEvents, std::function<void(T &)> callback)
305 {
306 ThrowIfNull();
307 const auto nSlots = fLoopManager->GetNSlots();
308 auto actionPtr = fActionPtr;
309 auto c = [nSlots, actionPtr, callback](unsigned int slot) {
310 if (slot != nSlots - 1)
311 return;
312 auto partialResult = static_cast<Value_t *>(actionPtr->PartialUpdate(slot));
313 callback(*partialResult);
314 };
315 fLoopManager->RegisterCallback(everyNEvents, std::move(c));
316 return *this;
317 }
318
319 // clang-format off
320 /// Register a callback that RDataFrame will execute in each worker thread concurrently on that thread's partial result.
321 ///
322 /// \param[in] everyNEvents Frequency at which the callback will be called by each thread, as a number of events processed
323 /// \param[in] callback A callable with signature `void(unsigned int, Value_t&)` where Value_t is the type of the value contained in this RResultPtr
324 /// \return this RResultPtr, to allow chaining of OnPartialResultSlot with other calls
325 ///
326 /// See `OnPartialResult` for a generic explanation of the callback mechanism.
327 /// Compared to `OnPartialResult`, this method has two major differences:
328 /// - all worker threads invoke the callback once every specified number of events. The event count is per-thread,
329 /// and callback invocation might happen concurrently (i.e. the callback must be thread-safe)
330 /// - the callable must take an extra `unsigned int` parameter corresponding to a multi-thread "processing slot":
331 /// this is a "helper value" to simplify writing thread-safe callbacks: different worker threads might invoke the
332 /// callback concurrently but always with different `slot` numbers.
333 /// - a value of 0 for everyNEvents indicates the callback must be executed once _per slot_.
334 ///
335 /// For example, the following snippet prints out a thread-safe progress bar of the events processed by RDataFrame
336 /// \code
337 /// auto c = tdf.Count(); // any action would do, but `Count` is the most lightweight
338 /// std::string progress;
339 /// std::mutex bar_mutex;
340 /// c.OnPartialResultSlot(nEvents / 100, [&progress, &bar_mutex](unsigned int, ULong64_t &) {
341 /// std::lock_guard<std::mutex> lg(bar_mutex);
342 /// progress.push_back('#');
343 /// std::cout << "\r[" << std::left << std::setw(100) << progress << ']' << std::flush;
344 /// });
345 /// std::cout << "Analysis running..." << std::endl;
346 /// *c; // trigger the event loop by accessing an action's result
347 /// std::cout << "\nDone!" << std::endl;
348 /// \endcode
349 // clang-format on
350 RResultPtr<T> &OnPartialResultSlot(ULong64_t everyNEvents, std::function<void(unsigned int, T &)> callback)
351 {
352 ThrowIfNull();
353 auto actionPtr = fActionPtr;
354 auto c = [actionPtr, callback](unsigned int slot) {
355 auto partialResult = static_cast<Value_t *>(actionPtr->PartialUpdate(slot));
356 callback(slot, *partialResult);
357 };
358 fLoopManager->RegisterCallback(everyNEvents, std::move(c));
359 return *this;
360 }
361
362 // clang-format off
363 /// Check whether the result has already been computed
364 ///
365 /// ~~~{.cpp}
366 /// auto res = df.Count();
367 /// res.IsReady(); // false, access will trigger event loop
368 /// std::cout << *res << std::endl; // triggers event loop
369 /// res.IsReady(); // true
370 /// ~~~
371 // clang-format on
372 bool IsReady() const
373 {
374 if (fActionPtr == nullptr)
375 return false;
376 return fActionPtr->HasRun();
377 }
378};
379
380template <typename T>
382{
383 fLoopManager->Run();
384}
385
386template <class T1, class T2>
387bool operator==(const RResultPtr<T1> &lhs, const RResultPtr<T2> &rhs)
388{
389 return lhs.fObjPtr == rhs.fObjPtr;
390}
391
392template <class T1, class T2>
393bool operator!=(const RResultPtr<T1> &lhs, const RResultPtr<T2> &rhs)
394{
395 return lhs.fObjPtr != rhs.fObjPtr;
396}
397
398template <class T1>
399bool operator==(const RResultPtr<T1> &lhs, std::nullptr_t rhs)
400{
401 return lhs.fObjPtr == rhs;
402}
403
404template <class T1>
405bool operator==(std::nullptr_t lhs, const RResultPtr<T1> &rhs)
406{
407 return lhs == rhs.fObjPtr;
408}
409
410template <class T1>
411bool operator!=(const RResultPtr<T1> &lhs, std::nullptr_t rhs)
412{
413 return lhs.fObjPtr != rhs;
414}
415
416template <class T1>
417bool operator!=(std::nullptr_t lhs, const RResultPtr<T1> &rhs)
418{
419 return lhs != rhs.fObjPtr;
420}
421
422} // namespace RDF
423
424namespace Detail {
425namespace RDF {
426/// Create a RResultPtr and set its pointer to the corresponding RAction
427/// This overload is invoked by non-jitted actions, as they have access to RAction before constructing RResultPtr.
428template <typename T>
429RResultPtr<T>
430MakeResultPtr(const std::shared_ptr<T> &r, RLoopManager &lm, std::shared_ptr<RDFInternal::RActionBase> actionPtr)
431{
432 return RResultPtr<T>(r, &lm, std::move(actionPtr));
433}
434
435////////////////////////////////////////////////////////////////////////////////
436/// \brief Retrieve a mergeable value from an RDataFrame action.
437/// \param[in] rptr lvalue reference of an RResultPtr object.
438/// \returns An RMergeableValue holding the result of the action, wrapped in an
439/// `std::unique_ptr`.
440///
441/// This function triggers the execution of the RDataFrame computation graph.
442/// Then retrieves an RMergeableValue object created with the result wrapped by
443/// the RResultPtr argument. The user obtains ownership of the mergeable, which
444/// in turn holds a copy of the result of the action. The RResultPtr is not
445/// destroyed in the process and will still retain (shared) ownership of the
446/// original result.
447///
448/// Example usage:
449/// ~~~{.cpp}
450/// using namespace ROOT::Detail::RDF;
451/// ROOT::RDataFrame d("myTree", "file_*.root");
452/// auto h = d.Histo1D("Branch_A");
453/// auto mergeablehisto = GetMergeableValue(h);
454/// ~~~
455template <typename T>
456std::unique_ptr<RMergeableValue<T>> GetMergeableValue(RResultPtr<T> &rptr)
457{
458 rptr.ThrowIfNull();
459 if (!rptr.fActionPtr->HasRun())
460 rptr.TriggerRun(); // Prevents from using `const` specifier in parameter
461 return std::unique_ptr<RMergeableValue<T>>{
462 static_cast<RMergeableValue<T> *>(rptr.fActionPtr->GetMergeableValue().release())};
463}
464} // namespace RDF
465} // namespace Detail
466} // namespace ROOT
467
468#endif // ROOT_TRESULTPROXY
typedef void(GLAPIENTRYP _GLUfuncptr)(void)
ROOT::R::TRInterface & r
Definition Object.C:4
#define c(i)
Definition RSha256.hxx:101
unsigned long long ULong64_t
Definition RtypesCore.h:81
void Warning(const char *location, const char *msgfmt,...)
Use this function in warning situations.
Definition TError.cxx:231
The head node of a RDF computation graph.
void Run()
Start the event loop with a different mechanism depending on IMT/no IMT, data source/no data source.
void RegisterCallback(ULong64_t everyNEvents, std::function< void(unsigned int)> &&f)
A result of an RDataFrame execution, that knows how to merge with other results of the same type.
Helper class that provides the operation graph nodes.
The public interface to the RDataFrame federation of classes.
Smart pointer for the return type of actions.
bool IsReady() const
Check whether the result has already been computed.
void TriggerRun()
Triggers the event loop in the RLoopManager.
RResultPtr(RResultPtr &&)=default
T * GetPtr()
Get the pointer to the encapsulated object.
RResultPtr< T > & OnPartialResult(ULong64_t everyNEvents, std::function< void(T &)> callback)
Register a callback that RDataFrame will execute "everyNEvents" on a partial result.
RDFDetail::RLoopManager * fLoopManager
Non-owning pointer to the RLoopManager at the root of this computation graph.
friend bool operator!=(const RResultPtr< T1 > &lhs, const RResultPtr< T2 > &rhs)
RIterationHelper< T >::Iterator_t begin()
Return an iterator to the beginning of the contained object if this makes sense, throw a compilation ...
T Value_t
Convenience alias to simplify access to proxied type.
T * Get()
Get the pointer to the encapsulated result.
const T & GetValue()
Get a const reference to the encapsulated object.
RResultPtr(const RResultPtr &)=default
T & operator*()
Get a pointer to the encapsulated object.
RResultPtr & operator=(const RResultPtr &)=default
RResultPtr< T > & OnPartialResultSlot(ULong64_t everyNEvents, std::function< void(unsigned int, T &)> callback)
Register a callback that RDataFrame will execute in each worker thread concurrently on that thread's ...
RResultPtr & operator=(RResultPtr &&)=default
friend bool operator==(const RResultPtr< T1 > &lhs, const RResultPtr< T2 > &rhs)
RResultPtr(const RResultPtr< T2 > &r)
Convert a RResultPtr<T2> to a RResultPtr<T>.
std::shared_ptr< RDFInternal::RActionBase > fActionPtr
Owning pointer to the action that will produce this result.
std::shared_ptr< T > SPT_t
T * operator->()
Get a pointer to the encapsulated object.
SPT_t fObjPtr
Shared pointer encapsulating the wrapped result.
static constexpr ULong64_t kOnce
Convenience definition to express a callback must be executed once.
RResultPtr(std::shared_ptr< T > objPtr, RDFDetail::RLoopManager *lm, std::shared_ptr< RDFInternal::RActionBase > actionPtr)
RIterationHelper< T >::Iterator_t end()
Return an iterator to the end of the contained object if this makes sense, throw a compilation error ...
#define T2
Definition md5.inl:146
std::unique_ptr< RMergeableValue< T > > GetMergeableValue(RResultPtr< T > &rptr)
Retrieve a mergeable value from an RDataFrame action.
RResultPtr< T > MakeResultPtr(const std::shared_ptr< T > &r, RLoopManager &df, std::shared_ptr< ROOT::Internal::RDF::RActionBase > actionPtr)
Create a RResultPtr and set its pointer to the corresponding RAction This overload is invoked by non-...
void WarnOnLazySnapshotNotTriggered(const ROOT::RDF::RResultPtr< T > &)
RResultMap< T > VariationsFor(RResultPtr< T > resPtr)
Produce all required systematic variations for the given result.
bool operator!=(const RResultPtr< T1 > &lhs, const RResultPtr< T2 > &rhs)
bool operator==(const RResultPtr< T1 > &lhs, const RResultPtr< T2 > &rhs)
ROOT type_traits extensions.
tbb::task_arena is an alias of tbb::interface7::task_arena, which doesn't allow to forward declare tb...