Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-14 10:31:15

0001 /// \file
0002 /// \warning This is part of the %ROOT 7 prototype! It will change without notice. It might trigger earthquakes.
0003 /// Feedback is welcome!
0004 
0005 #ifndef ROOT_RHistEngine
0006 #define ROOT_RHistEngine
0007 
0008 #include "RAxes.hxx"
0009 #include "RBinIndex.hxx"
0010 #include "RHistUtils.hxx"
0011 #include "RLinearizedIndex.hxx"
0012 #include "RRegularAxis.hxx"
0013 #include "RWeight.hxx"
0014 
0015 #include <array>
0016 #include <cassert>
0017 #include <stdexcept>
0018 #include <tuple>
0019 #include <type_traits>
0020 #include <utility>
0021 #include <vector>
0022 
0023 class TBuffer;
0024 
0025 namespace ROOT {
0026 namespace Experimental {
0027 
0028 /**
0029 A histogram data structure to bin data along multiple dimensions.
0030 
0031 Every call to \ref Fill(const A &... args) "Fill" bins the data according to the axis configuration and increments the
0032 bin content:
0033 \code
0034 ROOT::Experimental::RHistEngine<int> hist(10, {5, 15});
0035 hist.Fill(8.5);
0036 // hist.GetBinContent(ROOT::Experimental::RBinIndex(3)) will return 1
0037 \endcode
0038 
0039 The class is templated on the bin content type. For counting, as in the example above, it may be an integral type such
0040 as `int` or `long`. Narrower types such as `unsigned char` or `short` are supported, but may overflow due to their
0041 limited range and must be used with care. For weighted filling, the bin content type must not be an integral type, but
0042 a floating-point type such as `float` or `double`, or the special type RBinWithError. Note that `float` has a limited
0043 significand precision of 24 bits.
0044 
0045 An object can have arbitrary dimensionality determined at run-time. The axis configuration is passed as a vector of
0046 RAxisVariant:
0047 \code
0048 std::vector<ROOT::Experimental::RAxisVariant> axes;
0049 axes.push_back(ROOT::Experimental::RRegularAxis(10, 5, 15));
0050 axes.push_back(ROOT::Experimental::RVariableBinAxis({1, 10, 100, 1000}));
0051 ROOT::Experimental::RHistEngine<int> hist(axes);
0052 // hist.GetNDimensions() will return 2
0053 \endcode
0054 
0055 \warning This is part of the %ROOT 7 prototype! It will change without notice. It might trigger earthquakes.
0056 Feedback is welcome!
0057 */
0058 template <typename BinContentType>
0059 class RHistEngine final {
0060    /// The axis configuration for this histogram. Relevant methods are forwarded from the public interface.
0061    Internal::RAxes fAxes;
0062    /// The bin contents for this histogram
0063    std::vector<BinContentType> fBinContents;
0064 
0065 public:
0066    /// Construct a histogram engine.
0067    ///
0068    /// \param[in] axes the axis objects, must have size > 0
0069    explicit RHistEngine(std::vector<RAxisVariant> axes) : fAxes(std::move(axes))
0070    {
0071       fBinContents.resize(fAxes.ComputeTotalNBins());
0072    }
0073 
0074    /// Construct a one-dimensional histogram engine with a regular axis.
0075    ///
0076    /// \param[in] nNormalBins the number of normal bins, must be > 0
0077    /// \param[in] interval the axis interval (lower end inclusive, upper end exclusive)
0078    /// \par See also
0079    /// the
0080    /// \ref RRegularAxis::RRegularAxis(std::size_t nNormalBins, std::pair<double, double> interval, bool enableFlowBins)
0081    /// "constructor of RRegularAxis"
0082    RHistEngine(std::size_t nNormalBins, std::pair<double, double> interval)
0083       : RHistEngine({RRegularAxis(nNormalBins, interval)})
0084    {
0085    }
0086 
0087    /// The copy constructor is deleted.
0088    ///
0089    /// Copying all bin contents can be an expensive operation, depending on the number of bins. If required, users can
0090    /// explicitly call Clone().
0091    RHistEngine(const RHistEngine<BinContentType> &) = delete;
0092    /// Efficiently move construct a histogram engine.
0093    ///
0094    /// After this operation, the moved-from object is invalid.
0095    RHistEngine(RHistEngine<BinContentType> &&) = default;
0096 
0097    /// The copy assignment operator is deleted.
0098    ///
0099    /// Copying all bin contents can be an expensive operation, depending on the number of bins. If required, users can
0100    /// explicitly call Clone().
0101    RHistEngine<BinContentType> &operator=(const RHistEngine<BinContentType> &) = delete;
0102    /// Efficiently move a histogram engine.
0103    ///
0104    /// After this operation, the moved-from object is invalid.
0105    RHistEngine<BinContentType> &operator=(RHistEngine<BinContentType> &&) = default;
0106 
0107    ~RHistEngine() = default;
0108 
0109    const std::vector<RAxisVariant> &GetAxes() const { return fAxes.Get(); }
0110    std::size_t GetNDimensions() const { return fAxes.GetNDimensions(); }
0111    std::size_t GetTotalNBins() const { return fBinContents.size(); }
0112 
0113    /// Get the content of a single bin.
0114    ///
0115    /// \code
0116    /// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
0117    /// std::array<ROOT::Experimental::RBinIndex, 2> indices = {3, 5};
0118    /// int content = hist.GetBinContent(indices);
0119    /// \endcode
0120    ///
0121    /// \note Compared to TH1 conventions, the first normal bin has index 0 and underflow and overflow bins are special
0122    /// values. See also the class documentation of RBinIndex.
0123    ///
0124    /// Throws an exception if the number of indices does not match the axis configuration or the bin is not found.
0125    ///
0126    /// \param[in] indices the array of indices for each axis
0127    /// \return the bin content
0128    /// \par See also
0129    /// the \ref GetBinContent(const A &... args) const "variadic function template overload" accepting arguments
0130    /// directly
0131    template <std::size_t N>
0132    const BinContentType &GetBinContent(const std::array<RBinIndex, N> &indices) const
0133    {
0134       // We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
0135       // be confusing for users.
0136       if (N != GetNDimensions()) {
0137          throw std::invalid_argument("invalid number of indices passed to GetBinContent");
0138       }
0139       RLinearizedIndex index = fAxes.ComputeGlobalIndex(indices);
0140       if (!index.fValid) {
0141          throw std::invalid_argument("bin not found in GetBinContent");
0142       }
0143       assert(index.fIndex < fBinContents.size());
0144       return fBinContents[index.fIndex];
0145    }
0146 
0147    /// Get the content of a single bin.
0148    ///
0149    /// \code
0150    /// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
0151    /// int content = hist.GetBinContent(ROOT::Experimental::RBinIndex(3), ROOT::Experimental::RBinIndex(5));
0152    /// // ... or construct the RBinIndex arguments implicitly from integers:
0153    /// content = hist.GetBinContent(3, 5);
0154    /// \endcode
0155    ///
0156    /// \note Compared to TH1 conventions, the first normal bin has index 0 and underflow and overflow bins are special
0157    /// values. See also the class documentation of RBinIndex.
0158    ///
0159    /// Throws an exception if the number of arguments does not match the axis configuration or the bin is not found.
0160    ///
0161    /// \param[in] args the arguments for each axis
0162    /// \return the bin content
0163    /// \par See also
0164    /// the \ref GetBinContent(const std::array<RBinIndex, N> &indices) const "function overload" accepting
0165    /// `std::array`
0166    template <typename... A>
0167    const BinContentType &GetBinContent(const A &...args) const
0168    {
0169       std::array<RBinIndex, sizeof...(A)> indices{args...};
0170       return GetBinContent(indices);
0171    }
0172 
0173    /// Add all bin contents of another histogram.
0174    ///
0175    /// Throws an exception if the axes configurations are not identical.
0176    ///
0177    /// \param[in] other another histogram
0178    void Add(const RHistEngine<BinContentType> &other)
0179    {
0180       if (fAxes != other.fAxes) {
0181          throw std::invalid_argument("axes configurations not identical in Add");
0182       }
0183       for (std::size_t i = 0; i < fBinContents.size(); i++) {
0184          fBinContents[i] += other.fBinContents[i];
0185       }
0186    }
0187 
0188    /// Clear all bin contents.
0189    void Clear()
0190    {
0191       for (std::size_t i = 0; i < fBinContents.size(); i++) {
0192          fBinContents[i] = {};
0193       }
0194    }
0195 
0196    /// Clone this histogram engine.
0197    ///
0198    /// Copying all bin contents can be an expensive operation, depending on the number of bins.
0199    ///
0200    /// \return the cloned object
0201    RHistEngine<BinContentType> Clone() const
0202    {
0203       RHistEngine<BinContentType> h(fAxes.Get());
0204       for (std::size_t i = 0; i < fBinContents.size(); i++) {
0205          h.fBinContents[i] = fBinContents[i];
0206       }
0207       return h;
0208    }
0209 
0210    /// Whether this histogram engine type supports weighted filling.
0211    static constexpr bool SupportsWeightedFilling = !std::is_integral_v<BinContentType>;
0212 
0213    /// Fill an entry into the histogram.
0214    ///
0215    /// \code
0216    /// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
0217    /// auto args = std::make_tuple(8.5, 10.5);
0218    /// hist.Fill(args);
0219    /// \endcode
0220    ///
0221    /// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
0222    /// discarded.
0223    ///
0224    /// Throws an exception if the number of arguments does not match the axis configuration, or if an argument cannot be
0225    /// converted for the axis type at run-time.
0226    ///
0227    /// \param[in] args the arguments for each axis
0228    /// \par See also
0229    /// the \ref Fill(const A &... args) "variadic function template overload" accepting arguments directly and the
0230    /// \ref Fill(const std::tuple<A...> &args, RWeight weight) "overload for weighted filling"
0231    template <typename... A>
0232    void Fill(const std::tuple<A...> &args)
0233    {
0234       // We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
0235       // be confusing for users.
0236       if (sizeof...(A) != GetNDimensions()) {
0237          throw std::invalid_argument("invalid number of arguments to Fill");
0238       }
0239       RLinearizedIndex index = fAxes.ComputeGlobalIndexImpl<sizeof...(A)>(args);
0240       if (index.fValid) {
0241          assert(index.fIndex < fBinContents.size());
0242          fBinContents[index.fIndex]++;
0243       }
0244    }
0245 
0246    /// Fill an entry into the histogram with a weight.
0247    ///
0248    /// This overload is not available for integral bin content types (see \ref SupportsWeightedFilling).
0249    ///
0250    /// \code
0251    /// ROOT::Experimental::RHistEngine<float> hist({/* two dimensions */});
0252    /// auto args = std::make_tuple(8.5, 10.5);
0253    /// hist.Fill(args, ROOT::Experimental::RWeight(0.8));
0254    /// \endcode
0255    ///
0256    /// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
0257    /// discarded.
0258    ///
0259    /// Throws an exception if the number of arguments does not match the axis configuration, or if an argument cannot be
0260    /// converted for the axis type at run-time.
0261    ///
0262    /// \param[in] args the arguments for each axis
0263    /// \param[in] weight the weight for this entry
0264    /// \par See also
0265    /// the \ref Fill(const A &... args) "variadic function template overload" accepting arguments directly and the
0266    /// \ref Fill(const std::tuple<A...> &args) "overload for unweighted filling"
0267    template <typename... A>
0268    void Fill(const std::tuple<A...> &args, RWeight weight)
0269    {
0270       static_assert(SupportsWeightedFilling, "weighted filling is not supported for integral bin content types");
0271 
0272       // We could rely on RAxes::ComputeGlobalIndex to check the number of arguments, but its exception message might
0273       // be confusing for users.
0274       if (sizeof...(A) != GetNDimensions()) {
0275          throw std::invalid_argument("invalid number of arguments to Fill");
0276       }
0277       RLinearizedIndex index = fAxes.ComputeGlobalIndexImpl<sizeof...(A)>(args);
0278       if (index.fValid) {
0279          assert(index.fIndex < fBinContents.size());
0280          fBinContents[index.fIndex] += weight.fValue;
0281       }
0282    }
0283 
0284    /// Fill an entry into the histogram.
0285    ///
0286    /// \code
0287    /// ROOT::Experimental::RHistEngine<int> hist({/* two dimensions */});
0288    /// hist.Fill(8.5, 10.5);
0289    /// \endcode
0290    ///
0291    /// For weighted filling, pass an RWeight as the last argument:
0292    /// \code
0293    /// ROOT::Experimental::RHistEngine<float> hist({/* two dimensions */});
0294    /// hist.Fill(8.5, 10.5, ROOT::Experimental::RWeight(0.8));
0295    /// \endcode
0296    /// This is not available for integral bin content types (see \ref SupportsWeightedFilling).
0297    ///
0298    /// If one of the arguments is outside the corresponding axis and flow bins are disabled, the entry will be silently
0299    /// discarded.
0300    ///
0301    /// Throws an exception if the number of arguments does not match the axis configuration, or if an argument cannot be
0302    /// converted for the axis type at run-time.
0303    ///
0304    /// \param[in] args the arguments for each axis
0305    /// \par See also
0306    /// the function overloads accepting `std::tuple` \ref Fill(const std::tuple<A...> &args) "for unweighted filling"
0307    /// and \ref Fill(const std::tuple<A...> &args, RWeight) "for weighted filling"
0308    template <typename... A>
0309    void Fill(const A &...args)
0310    {
0311       auto t = std::forward_as_tuple(args...);
0312       if constexpr (std::is_same_v<typename Internal::LastType<A...>::type, RWeight>) {
0313          static_assert(SupportsWeightedFilling, "weighted filling is not supported for integral bin content types");
0314          static constexpr std::size_t N = sizeof...(A) - 1;
0315          if (N != fAxes.GetNDimensions()) {
0316             throw std::invalid_argument("invalid number of arguments to Fill");
0317          }
0318          RWeight weight = std::get<N>(t);
0319          RLinearizedIndex index = fAxes.ComputeGlobalIndexImpl<N>(t);
0320          if (index.fValid) {
0321             assert(index.fIndex < fBinContents.size());
0322             fBinContents[index.fIndex] += weight.fValue;
0323          }
0324       } else {
0325          Fill(t);
0326       }
0327    }
0328 
0329    /// %ROOT Streamer function to throw when trying to store an object of this class.
0330    void Streamer(TBuffer &) { throw std::runtime_error("unable to store RHistEngine"); }
0331 };
0332 
0333 } // namespace Experimental
0334 } // namespace ROOT
0335 
0336 #endif