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