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