Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RHistEngine.hxx
Go to the documentation of this file.
1/// \file
2/// \warning This is part of the %ROOT 7 prototype! It will change without notice. It might trigger earthquakes.
3/// Feedback is welcome!
4
5#ifndef ROOT_RHistEngine
6#define ROOT_RHistEngine
7
8#include "RAxes.hxx"
9#include "RAxisVariant.hxx"
10#include "RBinIndex.hxx"
12#include "RHistUtils.hxx"
13#include "RLinearizedIndex.hxx"
14#include "RRegularAxis.hxx"
16#include "RSliceSpec.hxx"
17#include "RWeight.hxx"
18
19#include <array>
20#include <atomic>
21#include <cassert>
22#include <cstddef>
23#include <cstdint>
24#include <cstring>
25#include <initializer_list>
26#include <stdexcept>
27#include <tuple>
28#include <type_traits>
29#include <utility>
30#include <vector>
31
32class TBuffer;
33
34namespace ROOT {
35namespace Experimental {
36
37// forward declaration for friend declaration
38template <typename T>
39class RHist;
40
41/**
42A histogram data structure to bin data along multiple dimensions.
43
44Every call to \ref Fill(const A &... args) "Fill" bins the data according to the axis configuration and increments the
45bin content:
46\code
47ROOT::Experimental::RHistEngine<int> hist(10, {5, 15});
48hist.Fill(8.5);
49// hist.GetBinContent(ROOT::Experimental::RBinIndex(3)) will return 1
50\endcode
51
52The class is templated on the bin content type. For counting, as in the example above, it may be an integral type such
53as `int` or `long`. Narrower types such as `unsigned char` or `short` are supported, but may overflow due to their
54limited range and must be used with care. For weighted filling, the bin content type must not be an integral type, but
55a floating-point type such as `float` or `double`, or the special type RBinWithError. Note that `float` has a limited
56significand precision of 24 bits.
57
58An object can have arbitrary dimensionality determined at run-time. The axis configuration is passed as a vector of
59RAxisVariant:
60\code
61std::vector<ROOT::Experimental::RAxisVariant> axes;
62axes.push_back(ROOT::Experimental::RRegularAxis(10, {5, 15}));
63axes.push_back(ROOT::Experimental::RVariableBinAxis({1, 10, 100, 1000}));
64ROOT::Experimental::RHistEngine<int> hist(axes);
65// hist.GetNDimensions() will return 2
66\endcode
67
68\warning This is part of the %ROOT 7 prototype! It will change without notice. It might trigger earthquakes.
69Feedback is welcome!
70*/
71template <typename BinContentType>
72class RHistEngine final {
73 // For conversion, all other template instantiations must be a friend.
74 template <typename U>
75 friend class RHistEngine;
76
77 // For slicing, RHist needs to call SliceImpl.
78 friend class RHist<BinContentType>;
79
80 friend class RProfile;
81 friend class RProfileFillContext;
82
83 /// The axis configuration for this histogram. Relevant methods are forwarded from the public interface.
85 /// The bin contents for this histogram
86 std::vector<BinContentType> fBinContents;
87
88 /// Flag to pause filling while a snapshot is ongoing
89 mutable std::atomic<bool> fSnapshotInProgress{false}; //!
90
91public:
92 /// Construct a histogram engine.
93 ///
94 /// \param[in] axes the axis objects, must have size > 0
95 explicit RHistEngine(std::vector<RAxisVariant> axes) : fAxes(std::move(axes))
96 {
98 }
99
100 /// Construct a histogram engine.
101 ///
102 /// Note that there is no perfect forwarding of the axis objects. If that is needed, use the
103 /// \ref RHistEngine(std::vector<RAxisVariant> axes) "overload accepting a std::vector".
104 ///
105 /// \param[in] axes the axis objects, must have size > 0
106 explicit RHistEngine(std::initializer_list<RAxisVariant> axes) : RHistEngine(std::vector(axes)) {}
107
108 /// Construct a histogram engine.
109 ///
110 /// Note that there is no perfect forwarding of the axis objects. If that is needed, use the
111 /// \ref RHistEngine(std::vector<RAxisVariant> axes) "overload accepting a std::vector".
112 ///
113 /// \param[in] axis1 the first axis object
114 /// \param[in] axes the remaining axis objects
115 template <typename... Axes>
116 explicit RHistEngine(const RAxisVariant &axis1, const Axes &...axes)
117 : RHistEngine(std::vector<RAxisVariant>{axis1, axes...})
118 {
119 }
120
121 /// Construct a one-dimensional histogram engine with a regular axis.
122 ///
123 /// \param[in] nNormalBins the number of normal bins, must be > 0
124 /// \param[in] interval the axis interval (lower end inclusive, upper end exclusive)
125 /// \par See also
126 /// the \ref RRegularAxis::RRegularAxis(std::uint64_t nNormalBins, std::pair<double, double> interval, bool
127 /// enableFlowBins) "constructor of RRegularAxis"
128 RHistEngine(std::uint64_t nNormalBins, std::pair<double, double> interval)
129 : RHistEngine(std::vector<RAxisVariant>{RRegularAxis(nNormalBins, interval)})
130 {
131 }
132
133 /// The copy constructor is deleted.
134 ///
135 /// Copying all bin contents can be an expensive operation, depending on the number of bins. If required, users can
136 /// explicitly call Clone().
137 RHistEngine(const RHistEngine &) = delete;
138 /// Efficiently move construct a histogram engine.
139 ///
140 /// After this operation, the moved-from object is invalid.
141 RHistEngine(RHistEngine &&rhs) noexcept : fAxes(std::move(rhs.fAxes)), fBinContents(std::move(rhs.fBinContents)) {}
142
143 /// The copy assignment operator is deleted.
144 ///
145 /// Copying all bin contents can be an expensive operation, depending on the number of bins. If required, users can
146 /// explicitly call Clone().
148 /// Efficiently move a histogram engine.
149 ///
150 /// After this operation, the moved-from object is invalid.
152 {
153 std::swap(fAxes, rhs.fAxes);
154 std::swap(fBinContents, rhs.fBinContents);
155 return *this;
156 }
157
158 ~RHistEngine() = default;
159
160 /// \name Accessors
161 /// \{
162
163 const std::vector<RAxisVariant> &GetAxes() const { return fAxes.Get(); }
164 std::size_t GetNDimensions() const { return fAxes.GetNDimensions(); }
165 std::uint64_t GetTotalNBins() const { return fBinContents.size(); }
166
167 /// Get the content of a single bin.
168 ///
169 /// \code
170 /// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
171 /// std::array<ROOT::Experimental::RBinIndex, 2> indices = {3, 5};
172 /// int content = hist.GetBinContent(indices);
173 /// \endcode
174 ///
175 /// \note Compared to TH1 conventions, the first normal bin has index 0 and underflow and overflow bins are special
176 /// values. See also the class documentation of RBinIndex.
177 ///
178 /// Throws an exception if the number of indices does not match the axis configuration or the bin is not found.
179 ///
180 /// \param[in] indices the array of indices for each axis
181 /// \return the bin content
182 /// \par See also
183 /// the \ref GetBinContent(const A &... args) const "variadic function template overload" accepting arguments
184 /// directly
185 template <std::size_t N>
186 const BinContentType &GetBinContent(const std::array<RBinIndex, N> &indices) const
187 {
188 // We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
189 // be confusing for users.
190 if (N != GetNDimensions()) {
191 throw std::invalid_argument("invalid number of indices passed to GetBinContent");
192 }
194 if (!index.fValid) {
195 throw std::invalid_argument("bin not found in GetBinContent");
196 }
197 assert(index.fIndex < fBinContents.size());
198 return fBinContents[index.fIndex];
199 }
200
201 /// Get the content of a single bin.
202 ///
203 /// \code
204 /// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
205 /// std::vector<ROOT::Experimental::RBinIndex> indices = {3, 5};
206 /// int content = hist.GetBinContent(indices);
207 /// \endcode
208 ///
209 /// \note Compared to TH1 conventions, the first normal bin has index 0 and underflow and overflow bins are special
210 /// values. See also the class documentation of RBinIndex.
211 ///
212 /// Throws an exception if the number of indices does not match the axis configuration or the bin is not found.
213 ///
214 /// \param[in] indices the vector of indices for each axis
215 /// \return the bin content
216 /// \par See also
217 /// the \ref GetBinContent(const A &... args) const "variadic function template overload" accepting arguments
218 /// directly
219 const BinContentType &GetBinContent(const std::vector<RBinIndex> &indices) const
220 {
221 // We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
222 // be confusing for users.
223 if (indices.size() != GetNDimensions()) {
224 throw std::invalid_argument("invalid number of indices passed to GetBinContent");
225 }
227 if (!index.fValid) {
228 throw std::invalid_argument("bin not found in GetBinContent");
229 }
230 assert(index.fIndex < fBinContents.size());
231 return fBinContents[index.fIndex];
232 }
233
234 /// Get the content of a single bin.
235 ///
236 /// \code
237 /// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
238 /// int content = hist.GetBinContent(ROOT::Experimental::RBinIndex(3), ROOT::Experimental::RBinIndex(5));
239 /// // ... or construct the RBinIndex arguments implicitly from integers:
240 /// content = hist.GetBinContent(3, 5);
241 /// \endcode
242 ///
243 /// \note Compared to TH1 conventions, the first normal bin has index 0 and underflow and overflow bins are special
244 /// values. See also the class documentation of RBinIndex.
245 ///
246 /// Throws an exception if the number of arguments does not match the axis configuration or the bin is not found.
247 ///
248 /// \param[in] args the arguments for each axis
249 /// \return the bin content
250 /// \par See also
251 /// the function overloads accepting \ref GetBinContent(const std::array<RBinIndex, N> &indices) const "`std::array`"
252 /// or \ref GetBinContent(const std::vector<RBinIndex> &indices) const "`std::vector`"
253 template <typename... A>
254 const BinContentType &GetBinContent(const A &...args) const
255 {
256 std::array<RBinIndex, sizeof...(A)> indices{args...};
257 return GetBinContent(indices);
258 }
259
260 /// Get the multidimensional range of all bins.
261 ///
262 /// \return the multidimensional range
264
265 /// Set the content of a single bin.
266 ///
267 /// \code
268 /// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
269 /// std::array<ROOT::Experimental::RBinIndex, 2> indices = {3, 5};
270 /// int value = /* ... */;
271 /// hist.SetBinContent(indices, value);
272 /// \endcode
273 ///
274 /// \note Compared to TH1 conventions, the first normal bin has index 0 and underflow and overflow bins are special
275 /// values. See also the class documentation of RBinIndex.
276 ///
277 /// Throws an exception if the number of indices does not match the axis configuration or the bin is not found.
278 ///
279 /// \param[in] indices the array of indices for each axis
280 /// \param[in] value the new value of the bin content
281 /// \par See also
282 /// the \ref SetBinContent(const A &... args) "variadic function template overload" accepting arguments directly
283 template <std::size_t N, typename V>
284 void SetBinContent(const std::array<RBinIndex, N> &indices, const V &value)
285 {
286 // We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
287 // be confusing for users.
288 if (N != GetNDimensions()) {
289 throw std::invalid_argument("invalid number of indices passed to SetBinContent");
290 }
292 if (!index.fValid) {
293 throw std::invalid_argument("bin not found in SetBinContent");
294 }
295 assert(index.fIndex < fBinContents.size());
296 // To allow conversion, we have to accept value with a template type V to capture any argument. Otherwise it would
297 // select the variadic function template...
298 fBinContents[index.fIndex] = value;
299 }
300
301 /// \}
302 // End the group to ensure that all contained member functions are public.
303
304private:
305 template <typename... A, std::size_t... I>
306 void SetBinContentImpl(const std::tuple<A...> &args, std::index_sequence<I...>)
307 {
308 std::array<RBinIndex, sizeof...(A) - 1> indices{std::get<I>(args)...};
309 SetBinContent(indices, std::get<sizeof...(A) - 1>(args));
310 }
311
312public:
313 /// \name Accessors
314 /// \{
315
316 /// Set the content of a single bin.
317 ///
318 /// \code
319 /// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
320 /// int value = /* ... */;
321 /// hist.SetBinContent(ROOT::Experimental::RBinIndex(3), ROOT::Experimental::RBinIndex(5), value);
322 /// // ... or construct the RBinIndex arguments implicitly from integers:
323 /// hist.SetBinContent(3, 5, value);
324 /// \endcode
325 ///
326 /// \note Compared to TH1 conventions, the first normal bin has index 0 and underflow and overflow bins are special
327 /// values. See also the class documentation of RBinIndex.
328 ///
329 /// Throws an exception if the number of arguments does not match the axis configuration or the bin is not found.
330 ///
331 /// \param[in] args the arguments for each axis and the new value of the bin content
332 /// \par See also
333 /// the \ref SetBinContent(const std::array<RBinIndex, N> &indices, const V &value) "function overload" accepting
334 /// `std::array`
335 template <typename... A>
336 void SetBinContent(const A &...args)
337 {
338 auto t = std::forward_as_tuple(args...);
339 SetBinContentImpl(t, std::make_index_sequence<sizeof...(A) - 1>());
340 }
341
342 /// \}
343
344 /// Whether this histogram engine type supports weighted filling.
345 static constexpr bool SupportsWeightedFilling = !std::is_integral_v<BinContentType>;
346
347 // SupportsWeightedFilling is not included because it is static, which would mess up the subgrouping below "Public
348 // Member Functions".
349 /// \name Filling
350 /// \{
351
352 /// Fill an entry into the histogram.
353 ///
354 /// \code
355 /// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
356 /// auto args = std::make_tuple(8.5, 10.5);
357 /// hist.Fill(args);
358 /// \endcode
359 ///
360 /// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
361 /// discarded.
362 ///
363 /// Throws an exception if the number of arguments does not match the axis configuration, or if an argument cannot be
364 /// converted for the axis type at run-time.
365 ///
366 /// \param[in] args the arguments for each axis
367 /// \par See also
368 /// the \ref Fill(const A &... args) "variadic function template overload" accepting arguments directly and the
369 /// \ref Fill(const std::tuple<A...> &args, RWeight weight) "overload for weighted filling"
370 template <typename... A>
371 void Fill(const std::tuple<A...> &args)
372 {
373 // We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
374 // be confusing for users.
375 if (sizeof...(A) != GetNDimensions()) {
376 throw std::invalid_argument("invalid number of arguments to Fill");
377 }
379 if (index.fValid) {
380 assert(index.fIndex < fBinContents.size());
381 fBinContents[index.fIndex]++;
382 }
383 }
384
385 /// Fill an entry into the histogram with a weight.
386 ///
387 /// This overload is not available for integral bin content types (see \ref SupportsWeightedFilling).
388 ///
389 /// \code
390 /// ROOT::Experimental::RHistEngine<float> hist({/* two dimensions */});
391 /// auto args = std::make_tuple(8.5, 10.5);
392 /// hist.Fill(args, ROOT::Experimental::RWeight(0.8));
393 /// \endcode
394 ///
395 /// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
396 /// discarded.
397 ///
398 /// Throws an exception if the number of arguments does not match the axis configuration, or if an argument cannot be
399 /// converted for the axis type at run-time.
400 ///
401 /// \param[in] args the arguments for each axis
402 /// \param[in] weight the weight for this entry
403 /// \par See also
404 /// the \ref Fill(const A &... args) "variadic function template overload" accepting arguments directly and the
405 /// \ref Fill(const std::tuple<A...> &args) "overload for unweighted filling"
406 template <typename... A>
407 void Fill(const std::tuple<A...> &args, RWeight weight)
408 {
409 static_assert(SupportsWeightedFilling, "weighted filling is not supported for integral bin content types");
410
411 // We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
412 // be confusing for users.
413 if (sizeof...(A) != GetNDimensions()) {
414 throw std::invalid_argument("invalid number of arguments to Fill");
415 }
417 if (index.fValid) {
418 assert(index.fIndex < fBinContents.size());
419 fBinContents[index.fIndex] += weight.fValue;
420 }
421 }
422
423 /// \}
424 // End the group to ensure that all contained member functions are public.
425
426private:
427 // Also used by RProfile::Fill(const A &...args) - similar to the variadic RHistEngine::Fill(const A &...args) below,
428 // is has all arguments in the forwarded std::tuple and needs to explicitly specify how many of them should be used
429 // by RAxes::ComputeGlobalIndexImpl<N>(args).
430 template <std::size_t N, typename... A, typename W>
431 void FillImpl(const std::tuple<A...> &args, const W &weight)
432 {
434 if (index.fValid) {
435 assert(index.fIndex < fBinContents.size());
436 fBinContents[index.fIndex] += weight;
437 }
438 }
439
440public:
441 /// \name Filling
442 /// \{
443
444 /// Fill an entry into the histogram with a user-defined weight.
445 ///
446 /// This overload is only available for user-defined bin content types.
447 ///
448 /// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
449 /// discarded.
450 ///
451 /// Throws an exception if the number of arguments does not match the axis configuration, or if an argument cannot be
452 /// converted for the axis type at run-time.
453 ///
454 /// \param[in] args the arguments for each axis
455 /// \param[in] weight the weight for this entry
456 template <typename... A, typename W>
457 void Fill(const std::tuple<A...> &args, const W &weight)
458 {
459 static_assert(std::is_class_v<BinContentType>,
460 "user-defined weight types are only supported for user-defined bin content types");
461
462 // We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
463 // be confusing for users.
464 if (sizeof...(A) != GetNDimensions()) {
465 throw std::invalid_argument("invalid number of arguments to Fill");
466 }
467 FillImpl<sizeof...(A)>(args, weight);
468 }
469
470 /// Fill an entry into the histogram.
471 ///
472 /// \code
473 /// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
474 /// hist.Fill(8.5, 10.5);
475 /// \endcode
476 ///
477 /// For weighted filling, pass an RWeight as the last argument:
478 /// \code
479 /// ROOT::Experimental::RHistEngine<float> hist({/* two dimensions */});
480 /// hist.Fill(8.5, 10.5, ROOT::Experimental::RWeight(0.8));
481 /// \endcode
482 /// This is not available for integral bin content types (see \ref SupportsWeightedFilling).
483 ///
484 /// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
485 /// discarded.
486 ///
487 /// Throws an exception if the number of arguments does not match the axis configuration, or if an argument cannot be
488 /// converted for the axis type at run-time.
489 ///
490 /// \param[in] args the arguments for each axis
491 /// \par See also
492 /// the function overloads accepting `std::tuple` \ref Fill(const std::tuple<A...> &args) "for unweighted filling"
493 /// and \ref Fill(const std::tuple<A...> &args, RWeight) "for weighted filling"
494 template <typename... A>
495 void Fill(const A &...args)
496 {
497 static_assert(sizeof...(A) >= 1, "need at least one argument to Fill");
498 if constexpr (sizeof...(A) >= 1) {
499 auto t = std::forward_as_tuple(args...);
500 if constexpr (std::is_same_v<typename Internal::LastType<A...>::type, RWeight>) {
501 static_assert(SupportsWeightedFilling, "weighted filling is not supported for integral bin content types");
502 static constexpr std::size_t N = sizeof...(A) - 1;
503 if (N != GetNDimensions()) {
504 throw std::invalid_argument("invalid number of arguments to Fill");
505 }
506 RWeight weight = std::get<N>(t);
508 if (index.fValid) {
509 assert(index.fIndex < fBinContents.size());
510 fBinContents[index.fIndex] += weight.fValue;
511 }
512 } else {
513 Fill(t);
514 }
515 }
516 }
517
518 /// Fill an entry into the histogram using atomic instructions.
519 ///
520 /// \param[in] args the arguments for each axis
521 /// \see Fill(const std::tuple<A...> &args)
522 template <typename... A>
523 void FillAtomic(const std::tuple<A...> &args)
524 {
525 while (fSnapshotInProgress.load(std::memory_order_relaxed)) {
526 // Spin while a snapshot is running
527 }
528
529 // We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
530 // be confusing for users.
531 if (sizeof...(A) != GetNDimensions()) {
532 throw std::invalid_argument("invalid number of arguments to Fill");
533 }
535 if (index.fValid) {
536 assert(index.fIndex < fBinContents.size());
538 }
539 }
540
541 /// Fill an entry into the histogram with a weight using atomic instructions.
542 ///
543 /// This overload is not available for integral bin content types (see \ref SupportsWeightedFilling).
544 ///
545 /// \param[in] args the arguments for each axis
546 /// \param[in] weight the weight for this entry
547 /// \see Fill(const std::tuple<A...> &args, RWeight weight)
548 template <typename... A>
549 void FillAtomic(const std::tuple<A...> &args, RWeight weight)
550 {
551 static_assert(SupportsWeightedFilling, "weighted filling is not supported for integral bin content types");
552
553 while (fSnapshotInProgress.load(std::memory_order_relaxed)) {
554 // Spin while a snapshot is running
555 }
556
557 // We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
558 // be confusing for users.
559 if (sizeof...(A) != GetNDimensions()) {
560 throw std::invalid_argument("invalid number of arguments to Fill");
561 }
563 if (index.fValid) {
564 assert(index.fIndex < fBinContents.size());
566 }
567 }
568
569 /// \}
570 // End the group to ensure that all contained member functions are public.
571
572private:
573 // Also used by RProfileFillContext::Fill(const A &...args) - similar to the variadic
574 // RHistEngine::FillAtomic(const A &...args) below, is has all arguments in the forwarded std::tuple and needs to
575 // explicitly specify how many of them should be used by RAxes::ComputeGlobalIndexImpl<N>(args).
576 template <std::size_t N, typename... A, typename W>
577 void FillAtomicImpl(const std::tuple<A...> &args, const W &weight)
578 {
580 if (index.fValid) {
581 assert(index.fIndex < fBinContents.size());
583 }
584 }
585
586public:
587 /// \name Filling
588 /// \{
589
590 /// Fill an entry into the histogram with a user-defined weight using atomic instructions.
591 ///
592 /// This overload is only available for user-defined bin content types.
593 ///
594 /// \param[in] args the arguments for each axis
595 /// \param[in] weight the weight for this entry
596 /// \see Fill(const std::tuple<A...> &args, const W &weight)
597 template <typename... A, typename W>
598 void FillAtomic(const std::tuple<A...> &args, const W &weight)
599 {
600 static_assert(std::is_class_v<BinContentType>,
601 "user-defined weight types are only supported for user-defined bin content types");
602
603 while (fSnapshotInProgress.load(std::memory_order_relaxed)) {
604 // Spin while a snapshot is running
605 }
606
607 // We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
608 // be confusing for users.
609 if (sizeof...(A) != GetNDimensions()) {
610 throw std::invalid_argument("invalid number of arguments to Fill");
611 }
612 FillAtomicImpl<sizeof...(A)>(args, weight);
613 }
614
615 /// Fill an entry into the histogram using atomic instructions.
616 ///
617 /// \param[in] args the arguments for each axis
618 /// \see Fill(const A &...args)
619 template <typename... A>
620 void FillAtomic(const A &...args)
621 {
622 static_assert(sizeof...(A) >= 1, "need at least one argument to Fill");
623 if constexpr (sizeof...(A) >= 1) {
624 while (fSnapshotInProgress.load(std::memory_order_relaxed)) {
625 // Spin while a snapshot is running
626 }
627
628 auto t = std::forward_as_tuple(args...);
629 if constexpr (std::is_same_v<typename Internal::LastType<A...>::type, RWeight>) {
630 static_assert(SupportsWeightedFilling, "weighted filling is not supported for integral bin content types");
631 static constexpr std::size_t N = sizeof...(A) - 1;
632 if (N != GetNDimensions()) {
633 throw std::invalid_argument("invalid number of arguments to Fill");
634 }
635 RWeight weight = std::get<N>(t);
637 if (index.fValid) {
638 assert(index.fIndex < fBinContents.size());
640 }
641 } else {
642 FillAtomic(t);
643 }
644 }
645 }
646
647 /// \}
648 /// \name Operations
649 /// \{
650
651 /// Add all bin contents of another histogram.
652 ///
653 /// Throws an exception if the axes configurations are not identical.
654 ///
655 /// \param[in] other another histogram
656 void Add(const RHistEngine &other)
657 {
658 if (fAxes != other.fAxes) {
659 throw std::invalid_argument("axes configurations not identical in Add");
660 }
661 for (std::size_t i = 0; i < fBinContents.size(); i++) {
662 fBinContents[i] += other.fBinContents[i];
663 }
664 }
665
666 /// Add all bin contents of another histogram using atomic instructions.
667 ///
668 /// Throws an exception if the axes configurations are not identical.
669 ///
670 /// \param[in] other another histogram that must not be modified during the operation
671 void AddAtomic(const RHistEngine &other)
672 {
673 if (fAxes != other.fAxes) {
674 throw std::invalid_argument("axes configurations not identical in AddAtomic");
675 }
676 for (std::size_t i = 0; i < fBinContents.size(); i++) {
678 }
679 }
680
681 /// Clear all bin contents.
682 void Clear()
683 {
684 for (std::size_t i = 0; i < fBinContents.size(); i++) {
685 fBinContents[i] = {};
686 }
687 }
688
689 /// Clone this histogram engine.
690 ///
691 /// Copying all bin contents can be an expensive operation, depending on the number of bins.
692 ///
693 /// \return the cloned object
695 {
697 for (std::size_t i = 0; i < fBinContents.size(); i++) {
698 h.fBinContents[i] = fBinContents[i];
699 }
700 return h;
701 }
702
703 /// Convert this histogram engine to a different bin content type.
704 ///
705 /// There is no bounds checking to make sure that the converted values can be represented. Note that it is not
706 /// possible to convert to RBinWithError since the information about individual weights has been lost since filling.
707 ///
708 /// Converting all bin contents can be an expensive operation, depending on the number of bins.
709 ///
710 /// \return the converted object
711 template <typename U>
713 {
715 for (std::size_t i = 0; i < fBinContents.size(); i++) {
716 h.fBinContents[i] = static_cast<U>(fBinContents[i]);
717 }
718 return h;
719 }
720
721 /// Scale all histogram bin contents.
722 ///
723 /// This method is not available for integral bin content types.
724 ///
725 /// \param[in] factor the scale factor
726 void Scale(double factor)
727 {
728 static_assert(!std::is_integral_v<BinContentType>, "scaling is not supported for integral bin content types");
729 for (std::size_t i = 0; i < fBinContents.size(); i++) {
730 fBinContents[i] *= factor;
731 }
732 }
733
734 /// \}
735 // End the group to ensure that all contained member functions are public.
736
737private:
738 RHistEngine SliceImpl(const std::vector<RSliceSpec> &sliceSpecs, bool &dropped) const
739 {
740 if (sliceSpecs.size() != GetNDimensions()) {
741 throw std::invalid_argument("invalid number of specifications passed to Slice");
742 }
743
744 // Slice the axes.
745 std::vector<RAxisVariant> axes;
746 for (std::size_t i = 0; i < sliceSpecs.size(); i++) {
747 // A sum operation makes the dimension disappear.
748 if (sliceSpecs[i].GetOperationSum() == nullptr) {
749 axes.push_back(fAxes.Get()[i].Slice(sliceSpecs[i]));
750 }
751 }
752 if (axes.empty()) {
753 throw std::invalid_argument("summing across all dimensions is not supported");
754 }
755
756 RHistEngine sliced(std::move(axes));
757
758 // Create the helper objects to map the bin contents to the sliced histogram.
759 Internal::RSliceBinIndexMapper mapper(sliceSpecs);
760 assert(mapper.GetMappedDimensionality() == sliced.GetNDimensions());
761 std::vector<RBinIndex> mappedIndices(mapper.GetMappedDimensionality());
762
763 auto origRange = fAxes.GetFullMultiDimRange();
764 auto origRangeIt = origRange.begin();
765
766 for (std::size_t i = 0; i < fBinContents.size(); i++) {
767 const auto &origIndices = *origRangeIt;
768#ifndef NDEBUG
769 // Verify that the original indices correspond to the iteration variable.
770 RLinearizedIndex origIndex = fAxes.ComputeGlobalIndex(origIndices);
771 assert(origIndex.fValid);
772 assert(origIndex.fIndex == i);
773#endif
774
775 bool success = mapper.Map(origIndices, mappedIndices);
776 if (success) {
777 RLinearizedIndex mappedIndex = sliced.fAxes.ComputeGlobalIndex(mappedIndices);
778 assert(mappedIndex.fValid);
779 sliced.fBinContents[mappedIndex.fIndex] += fBinContents[i];
780 } else {
781 dropped = true;
782 }
783 ++origRangeIt;
784 }
785
786 return sliced;
787 }
788
789public:
790 /// \name Operations
791 /// \{
792
793 /// Slice this histogram with an RSliceSpec per dimension.
794 ///
795 /// With a range, only the specified bins are retained. All other bin contents are transferred to the underflow and
796 /// overflow bins:
797 /// \code
798 /// ROOT::Experimental::RHistEngine<int> hist(/* one dimension */);
799 /// // Fill the histogram with a number of entries...
800 /// auto sliced = hist.Slice({hist.GetAxes()[0].GetNormalRange(1, 5)});
801 /// // The returned histogram will have 4 normal bins, an underflow and an overflow bin.
802 /// \endcode
803 ///
804 /// Slicing can also perform operations per dimension, see RSliceSpec. RSliceSpec::ROperationRebin allows to rebin
805 /// the histogram axis, grouping a number of normal bins into a new one:
806 /// \code
807 /// ROOT::Experimental::RHistEngine<int> hist(/* one dimension */);
808 /// // Fill the histogram with a number of entries...
809 /// auto rebinned = hist.Slice(ROOT::Experimental::RSliceSpec::ROperationRebin(2));
810 /// // The returned histogram has groups of two normal bins merged.
811 /// \endcode
812 ///
813 /// RSliceSpec::ROperationSum sums the bin contents along that axis, which allows to project to a lower-dimensional
814 /// histogram:
815 /// \code
816 /// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
817 /// // Fill the histogram with a number of entries...
818 /// auto projected = hist.Slice(ROOT::Experimental::RSliceSpec{}, ROOT::Experimental::RSliceSpec::ROperationSum{});
819 /// // The returned histogram has one dimension, with bin contents summed along the second axis.
820 /// \endcode
821 /// Note that it is not allowed to sum along all histogram axes because the return value would be a scalar.
822 ///
823 /// Ranges and operations can be combined. In that case, the range is applied before the operation.
824 ///
825 /// \param[in] sliceSpecs the slice specifications for each axis
826 /// \return the sliced histogram
827 /// \par See also
828 /// the \ref Slice(const A &... args) const "variadic function template overload" accepting arguments directly
829 RHistEngine Slice(const std::vector<RSliceSpec> &sliceSpecs) const
830 {
831 bool dropped = false;
832 return SliceImpl(sliceSpecs, dropped);
833 }
834
835 /// Slice this histogram with an RSliceSpec per dimension.
836 ///
837 /// With a range, only the specified bins are retained. All other bin contents are transferred to the underflow and
838 /// overflow bins:
839 /// \code
840 /// ROOT::Experimental::RHistEngine<int> hist(/* one dimension */);
841 /// // Fill the histogram with a number of entries...
842 /// auto sliced = hist.Slice(hist.GetAxes()[0].GetNormalRange(1, 5));
843 /// // The returned histogram will have 4 normal bins, an underflow and an overflow bin.
844 /// \endcode
845 ///
846 /// Slicing can also perform operations per dimension, see RSliceSpec. RSliceSpec::ROperationRebin allows to rebin
847 /// the histogram axis, grouping a number of normal bins into a new one:
848 /// \code
849 /// ROOT::Experimental::RHistEngine<int> hist(/* one dimension */);
850 /// // Fill the histogram with a number of entries...
851 /// auto rebinned = hist.Slice(ROOT::Experimental::RSliceSpec::ROperationRebin(2));
852 /// // The returned histogram has groups of two normal bins merged.
853 /// \endcode
854 ///
855 /// RSliceSpec::ROperationSum sums the bin contents along that axis, which allows to project to a lower-dimensional
856 /// histogram:
857 /// \code
858 /// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
859 /// // Fill the histogram with a number of entries...
860 /// auto projected = hist.Slice(ROOT::Experimental::RSliceSpec{}, ROOT::Experimental::RSliceSpec::ROperationSum{});
861 /// // The returned histogram has one dimension, with bin contents summed along the second axis.
862 /// \endcode
863 /// Note that it is not allowed to sum along all histogram axes because the return value would be a scalar.
864 ///
865 /// Ranges and operations can be combined. In that case, the range is applied before the operation.
866 ///
867 /// \param[in] args the arguments for each axis
868 /// \return the sliced histogram
869 /// \par See also
870 /// the \ref Slice(const std::vector<RSliceSpec> &sliceSpecs) const "function overload" accepting `std::vector`
871 template <typename... A>
872 RHistEngine Slice(const A &...args) const
873 {
874 std::vector<RSliceSpec> sliceSpecs{args...};
875 return Slice(sliceSpecs);
876 }
877
878 /// Create an atomic snapshot of this histogram engine.
879 ///
880 /// A snapshot is a consistent copy of the histogram, during concurrent filling. It is guaranteed that the returned
881 /// copy represents a state between the begin and end of the snapshot operation.
882 ///
883 /// Snapshotting a histogram engine with many bins can be an expensive operation.
884 ///
885 /// \return the atomic snapshot
887 {
888 static_assert(std::is_trivially_copyable_v<BinContentType>,
889 "snapshotting requires a trivially copyable bin content type");
890
891 do {
892 while (fSnapshotInProgress.load(std::memory_order_relaxed)) {
893 // Spin while another snapshot is running
894 }
895 } while (fSnapshotInProgress.exchange(true, std::memory_order_relaxed));
896
897 RHistEngine snapshot(fAxes.Get());
898 // Do a first collect.
899 for (std::size_t i = 0; i < fBinContents.size(); i++) {
901 }
902
903 // Now do another collect. If no change is detected, the snapshot is consistent. Otherwise update the bin contents
904 // and try again.
905 BinContentType tmp;
906 bool changed;
907 do {
908 // To guarantee correctness, we let the release operation(s) in FillAtomic synchronize with this acquire fence.
909 // This ensures that all previous writes become visible side-effects and the atomic loads will see them.
910 std::atomic_thread_fence(std::memory_order_acquire);
911
912 changed = false;
913 for (std::size_t i = 0; i < fBinContents.size(); i++) {
915 if (std::memcmp(&tmp, &snapshot.fBinContents[i], sizeof(BinContentType))) {
916 std::memcpy(&snapshot.fBinContents[i], &tmp, sizeof(BinContentType));
917 changed = true;
918 }
919 }
920 } while (changed);
921
922 fSnapshotInProgress.store(false, std::memory_order_relaxed);
923
924 return snapshot;
925 }
926
927 /// \}
928
929 /// %ROOT Streamer function to throw when trying to store an object of this class.
930 void Streamer(TBuffer &) { throw std::runtime_error("unable to store RHistEngine"); }
931};
932
933} // namespace Experimental
934} // namespace ROOT
935
936#endif
#define h(i)
Definition RSha256.hxx:106
#define N
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t index
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 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 Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t type
Bin configurations for all dimensions of a histogram.
Definition RAxes.hxx:40
std::size_t GetNDimensions() const
Definition RAxes.hxx:55
RLinearizedIndex ComputeGlobalIndexImpl(std::size_t index, const std::tuple< A... > &args) const
Definition RAxes.hxx:77
RLinearizedIndex ComputeGlobalIndex(const std::tuple< A... > &args) const
Compute the global index for all axes.
Definition RAxes.hxx:131
RBinIndexMultiDimRange GetFullMultiDimRange() const
Get the multidimensional range of all bins.
Definition RAxes.hxx:195
std::uint64_t ComputeTotalNBins() const
Compute the total number of bins for all axes.
Definition RAxes.hxx:66
std::vector< RAxisVariant > fAxes
Definition RAxes.hxx:44
const std::vector< RAxisVariant > & Get() const
Definition RAxes.hxx:56
Mapper of bin indices for slice operations.
bool Map(const std::vector< RBinIndex > &original, std::vector< RBinIndex > &mapped) const
Map a vector of RBinIndex according to the slice specifications.
A variant of all supported axis types.
A multidimensional range of bin indices.
A bin index with special values for underflow and overflow bins.
Definition RBinIndex.hxx:23
A histogram data structure to bin data along multiple dimensions.
RHistEngine Slice(const std::vector< RSliceSpec > &sliceSpecs) const
Slice this histogram with an RSliceSpec per dimension.
RHistEngine & operator=(RHistEngine &&rhs) noexcept
Efficiently move a histogram engine.
RHistEngine(const RAxisVariant &axis1, const Axes &...axes)
Construct a histogram engine.
void Fill(const A &...args)
Fill an entry into the histogram.
const std::vector< RAxisVariant > & GetAxes() const
RHistEngine(RHistEngine &&rhs) noexcept
Efficiently move construct a histogram engine.
RBinIndexMultiDimRange GetFullMultiDimRange() const
Get the multidimensional range of all bins.
RHistEngine Clone() const
Clone this histogram engine.
void FillImpl(const std::tuple< A... > &args, const W &weight)
void SetBinContent(const A &...args)
Set the content of a single bin.
void Scale(double factor)
Scale all histogram bin contents.
RHistEngine SnapshotAtomic() const
Create an atomic snapshot of this histogram engine.
void Fill(const std::tuple< A... > &args)
Fill an entry into the histogram.
void FillAtomic(const std::tuple< A... > &args)
Fill an entry into the histogram using atomic instructions.
RHistEngine(std::uint64_t nNormalBins, std::pair< double, double > interval)
Construct a one-dimensional histogram engine with a regular axis.
RHistEngine & operator=(const RHistEngine &)=delete
The copy assignment operator is deleted.
const BinContentType & GetBinContent(const std::vector< RBinIndex > &indices) const
Get the content of a single bin.
RHistEngine(const RHistEngine &)=delete
The copy constructor is deleted.
const BinContentType & GetBinContent(const std::array< RBinIndex, N > &indices) const
Get the content of a single bin.
const BinContentType & GetBinContent(const A &...args) const
Get the content of a single bin.
RHistEngine SliceImpl(const std::vector< RSliceSpec > &sliceSpecs, bool &dropped) const
void SetBinContent(const std::array< RBinIndex, N > &indices, const V &value)
Set the content of a single bin.
void AddAtomic(const RHistEngine &other)
Add all bin contents of another histogram using atomic instructions.
std::size_t GetNDimensions() const
void Fill(const std::tuple< A... > &args, const W &weight)
Fill an entry into the histogram with a user-defined weight.
void Add(const RHistEngine &other)
Add all bin contents of another histogram.
void FillAtomic(const std::tuple< A... > &args, RWeight weight)
Fill an entry into the histogram with a weight using atomic instructions.
static constexpr bool SupportsWeightedFilling
Whether this histogram engine type supports weighted filling.
void Clear()
Clear all bin contents.
RHistEngine Slice(const A &...args) const
Slice this histogram with an RSliceSpec per dimension.
std::uint64_t GetTotalNBins() const
void FillAtomic(const std::tuple< A... > &args, const W &weight)
Fill an entry into the histogram with a user-defined weight using atomic instructions.
void SetBinContentImpl(const std::tuple< A... > &args, std::index_sequence< I... >)
RHistEngine(std::vector< RAxisVariant > axes)
Construct a histogram engine.
void FillAtomic(const A &...args)
Fill an entry into the histogram using atomic instructions.
Internal::RAxes fAxes
The axis configuration for this histogram. Relevant methods are forwarded from the public interface.
void Fill(const std::tuple< A... > &args, RWeight weight)
Fill an entry into the histogram with a weight.
void FillAtomicImpl(const std::tuple< A... > &args, const W &weight)
std::atomic< bool > fSnapshotInProgress
Flag to pause filling while a snapshot is ongoing.
RHistEngine(std::initializer_list< RAxisVariant > axes)
Construct a histogram engine.
void Streamer(TBuffer &)
ROOT Streamer function to throw when trying to store an object of this class.
std::vector< BinContentType > fBinContents
The bin contents for this histogram.
RHistEngine< U > Convert() const
Convert this histogram engine to a different bin content type.
A histogram for aggregation of data along multiple dimensions.
Definition RHist.hxx:66
A context to concurrently fill an RProfile.
A profile histogram, computing statistical quantities of an additional variable per bin.
Definition RProfile.hxx:58
A regular axis with equidistant bins in the interval .
Buffer base class used for serializing objects.
Definition TBuffer.h:43
std::enable_if_t< std::is_arithmetic_v< T > > AtomicIncRelease(T *ptr)
std::enable_if_t< std::is_arithmetic_v< T > > AtomicLoad(const T *ptr, T *ret)
std::enable_if_t< std::is_integral_v< T > > AtomicAdd(T *ptr, T val)
std::enable_if_t< std::is_integral_v< T > > AtomicAddRelease(T *ptr, T val)
A linearized index that can be invalid.
A weight for filling histograms.
Definition RWeight.hxx:17