Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-16 09:21:18

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_RHistAutoAxisFiller
0006 #define ROOT_RHistAutoAxisFiller
0007 
0008 #include "RHist.hxx"
0009 #include "RHistEngine.hxx"
0010 #include "RWeight.hxx"
0011 
0012 #include <algorithm>
0013 #include <cassert>
0014 #include <cmath>
0015 #include <cstddef>
0016 #include <cstdint>
0017 #include <limits>
0018 #include <optional>
0019 #include <stdexcept>
0020 #include <type_traits> // for std::conditional_t
0021 #include <utility>
0022 #include <vector>
0023 
0024 namespace ROOT {
0025 namespace Experimental {
0026 
0027 /**
0028 A histogram filler that automatically determines the axis interval.
0029 
0030 This class allows filling a regular one-dimensional histogram without specifying an axis interval during construction.
0031 After a configurable number of buffered entries, or upon request, a RRegularAxis is constructed using the minimum and
0032 maximum values until that point. This ensures all initial entries are filled into normal bins. Note that this cannot be
0033 guaranteed for further calls to Fill.
0034 
0035 \code
0036 ROOT::Experimental::RHistAutoAxisFiller<int> filler(20);
0037 filler.Fill(1.0);
0038 filler.Fill(1.5);
0039 filler.Fill(2.0);
0040 
0041 // The following will implicitly trigger the histogram creation
0042 auto &hist = filler.GetHist();
0043 // hist.GetNEntries() will return 3
0044 \endcode
0045 
0046 \warning This is part of the %ROOT 7 prototype! It will change without notice. It might trigger earthquakes.
0047 Feedback is welcome!
0048 */
0049 template <typename BinContentType>
0050 class RHistAutoAxisFiller final {
0051 public:
0052    static constexpr bool SupportsWeightedFilling = RHistEngine<BinContentType>::SupportsWeightedFilling;
0053 
0054 private:
0055    /// The filled histogram, after it has been constructed
0056    std::optional<RHist<BinContentType>> fHist;
0057 
0058    /// The number of normal bins
0059    std::uint64_t fNNormalBins;
0060    /// The maximum buffer size until Flush() is automatically called
0061    std::size_t fMaxBufferSize;
0062    /// The fraction of the axis interval to use as margin
0063    double fMarginFraction;
0064 
0065    using BufferElement = std::conditional_t<SupportsWeightedFilling, std::pair<double, RWeight>, double>;
0066 
0067    /// The buffer of filled entries
0068    std::vector<BufferElement> fBuffer;
0069    /// The minimum of the filled entries
0070    double fMinimum = std::numeric_limits<double>::infinity();
0071    /// The maximum of the filled entries
0072    double fMaximum = -std::numeric_limits<double>::infinity();
0073 
0074 public:
0075    /// Create a filler object.
0076    ///
0077    /// \param[in] nNormalBins the number of normal bins, must be > 0
0078    /// \param[in] maxBufferSize the maximum buffer size, must be > 0
0079    /// \param[in] marginFraction the fraction of the axis interval to use as margin, must be > 0
0080    explicit RHistAutoAxisFiller(std::uint64_t nNormalBins, std::size_t maxBufferSize = 1024,
0081                                 double marginFraction = 0.05)
0082       : fNNormalBins(nNormalBins), fMaxBufferSize(maxBufferSize), fMarginFraction(marginFraction)
0083    {
0084       if (nNormalBins == 0) {
0085          throw std::invalid_argument("nNormalBins must be > 0");
0086       }
0087       if (maxBufferSize == 0) {
0088          throw std::invalid_argument("maxBufferSize must be > 0");
0089       }
0090       if (marginFraction <= 0) {
0091          throw std::invalid_argument("marginFraction must be > 0");
0092       }
0093    }
0094 
0095    std::uint64_t GetNNormalBins() const { return fNNormalBins; }
0096    std::size_t GetMaxBufferSize() const { return fMaxBufferSize; }
0097    double GetMarginFraction() const { return fMarginFraction; }
0098 
0099 private:
0100    void BufferImpl(double x, RWeight weight)
0101    {
0102       if constexpr (SupportsWeightedFilling) {
0103          fBuffer.emplace_back(x, weight);
0104       } else {
0105          assert(weight.fValue == 1.0);
0106          // Silence compiler warning about unused parameter
0107          (void)weight;
0108          fBuffer.push_back(x);
0109       }
0110       fMinimum = std::min(fMinimum, x);
0111       fMaximum = std::max(fMaximum, x);
0112 
0113       if (fBuffer.size() >= fMaxBufferSize) {
0114          Flush();
0115       }
0116    }
0117 
0118 public:
0119    /// Fill an entry into the histogram.
0120    ///
0121    /// \param[in] x the argument
0122    /// \par See also
0123    /// the \ref Fill(double x, RWeight weight) "overload for weighted filling"
0124    void Fill(double x)
0125    {
0126       // If the histogram exists, forward the Fill call.
0127       if (fHist) {
0128          fHist->Fill(x);
0129          return;
0130       }
0131       BufferImpl(x, RWeight(1.0));
0132    }
0133 
0134    /// Fill an entry into the histogram with a weight.
0135    ///
0136    /// This overload is only available for floating-point bin content types (see
0137    /// \ref RHistEngine::SupportsWeightedFilling).
0138    ///
0139    /// \param[in] x the argument
0140    /// \param[in] weight the weight for this entry
0141    /// \par See also
0142    /// the \ref Fill(double x) "overload for unweighted filling"
0143    void Fill(double x, RWeight weight)
0144    {
0145       // If the histogram exists, forward the Fill call.
0146       if (fHist) {
0147          fHist->Fill(x, weight);
0148          return;
0149       }
0150       BufferImpl(x, weight);
0151    }
0152 
0153    /// Flush the buffer of entries and construct the histogram.
0154    ///
0155    /// Throws an exception if the buffer is empty, the axis interval cannot be determined, or if it would be empty
0156    /// because the minimum equals the maximum.
0157    void Flush()
0158    {
0159       if (fHist) {
0160          assert(fBuffer.empty() && "buffer should have been emptied");
0161          return;
0162       }
0163 
0164       if (fBuffer.empty()) {
0165          throw std::runtime_error("buffer is empty, cannot create histogram");
0166       }
0167       if (!std::isfinite(fMinimum) || !std::isfinite(fMaximum)) {
0168          throw std::runtime_error("could not determine axis interval");
0169       }
0170       if (fMinimum == fMaximum) {
0171          throw std::runtime_error("axis interval is empty");
0172       }
0173 
0174       // Add some margin to the axis interval to make sure the maximum is included in the last bin, but also to
0175       // accommodate closeby values.
0176       const auto margin = fMarginFraction * (fMaximum - fMinimum);
0177       const auto high = fMaximum + margin;
0178       const auto low = fMinimum - margin;
0179       assert(high > low);
0180       fHist.emplace(fNNormalBins, std::make_pair(low, high));
0181 
0182       for (auto &&x : fBuffer) {
0183          if constexpr (SupportsWeightedFilling) {
0184             fHist->Fill(x.first, x.second);
0185          } else {
0186             fHist->Fill(x);
0187          }
0188       }
0189       fBuffer.clear();
0190    }
0191 
0192    /// Return the constructed histogram.
0193    ///
0194    /// \see Flush()
0195    RHist<BinContentType> &GetHist()
0196    {
0197       Flush();
0198       assert(fHist.has_value());
0199       return *fHist;
0200    }
0201 };
0202 
0203 } // namespace Experimental
0204 } // namespace ROOT
0205 
0206 #endif