Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 10:10:44

0001 /// \file ROOT/RHistImpl.hxx
0002 /// \ingroup HistV7
0003 /// \author Axel Naumann <axel@cern.ch>
0004 /// \date 2015-03-23
0005 /// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
0006 /// is welcome!
0007 
0008 /*************************************************************************
0009  * Copyright (C) 1995-2015, Rene Brun and Fons Rademakers.               *
0010  * All rights reserved.                                                  *
0011  *                                                                       *
0012  * For the licensing terms see $ROOTSYS/LICENSE.                         *
0013  * For the list of contributors see $ROOTSYS/README/CREDITS.             *
0014  *************************************************************************/
0015 
0016 #ifndef ROOT7_RHistImpl
0017 #define ROOT7_RHistImpl
0018 
0019 #include <cassert>
0020 #include <cctype>
0021 #include <functional>
0022 #include <tuple>
0023 #include "ROOT/RSpan.hxx"
0024 
0025 #include "ROOT/RAxis.hxx"
0026 #include "ROOT/RHistBinIter.hxx"
0027 #include "ROOT/RHistUtils.hxx"
0028 #include "ROOT/RLogger.hxx"
0029 
0030 class TRootIOCtor;
0031 
0032 namespace ROOT {
0033 namespace Experimental {
0034 
0035 template <int DIMENSIONS, class PRECISION, template <int D_, class P_> class... STAT>
0036 class RHist;
0037 
0038 namespace Hist {
0039 /// Iterator over n dimensional axes - an array of n axis iterators.
0040 template <int NDIMS>
0041 using AxisIter_t = std::array<RAxisBase::const_iterator, NDIMS>;
0042 /// Range over n dimensional axes - a pair of arrays of n axis iterators.
0043 template <int NDIMS>
0044 using AxisIterRange_t = std::array<AxisIter_t<NDIMS>, 2>;
0045 
0046 /// Kinds of under- and overflow handling.
0047 enum class EOverflow {
0048    kNoOverflow = 0x0, ///< Exclude under- and overflows
0049    kUnderflow = 0x1,  ///< Include underflows
0050    kOverflow = 0x2,   ///< Include overflows
0051    kUnderOver = 0x3,  ///< Include both under- and overflows
0052 };
0053 
0054 inline bool operator&(EOverflow a, EOverflow b)
0055 {
0056    return static_cast<int>(a) & static_cast<int>(b);
0057 }
0058 } // namespace Hist
0059 
0060 namespace Detail {
0061 
0062 /**
0063  \class RHistImplPrecisionAgnosticBase
0064  Base class for `RHistImplBase` that abstracts out the histogram's `PRECISION`.
0065 
0066  For operations such as painting a histogram, the `PRECISION` (type of the bin
0067  content) is not relevant; painting will cast the underlying bin type to double.
0068  To facilitate this, `RHistImplBase` itself inherits from the
0069  `RHistImplPrecisionAgnosticBase` interface.
0070  */
0071 template <int DIMENSIONS>
0072 class RHistImplPrecisionAgnosticBase {
0073 public:
0074    /// Type of the coordinates.
0075    using CoordArray_t = Hist::CoordArray_t<DIMENSIONS>;
0076    /// Type of the local per-axis bin indices.
0077    using BinArray_t = std::array<int, DIMENSIONS>;
0078    /// Range type.
0079    using AxisIterRange_t = Hist::AxisIterRange_t<DIMENSIONS>;
0080 
0081    RHistImplPrecisionAgnosticBase() = default;
0082    RHistImplPrecisionAgnosticBase(const RHistImplPrecisionAgnosticBase &) = default;
0083    RHistImplPrecisionAgnosticBase(RHistImplPrecisionAgnosticBase &&) = default;
0084    RHistImplPrecisionAgnosticBase(std::string_view title): fTitle(title) {}
0085    virtual ~RHistImplPrecisionAgnosticBase() {}
0086 
0087    /// Number of dimensions of the coordinates.
0088    static constexpr int GetNDim() { return DIMENSIONS; }
0089    /// Number of bins of this histogram, including all overflow and underflow
0090    /// bins. Simply the product of all axes' total number of bins.
0091    virtual int GetNBins() const noexcept = 0;
0092    /// Number of bins of this histogram, excluding all overflow and underflow
0093    /// bins. Simply the product of all axes' number of regular bins.
0094    virtual int GetNBinsNoOver() const noexcept = 0;
0095    /// Number of under- and overflow bins of this histogram, excluding all
0096    /// regular bins.
0097    virtual int GetNOverflowBins() const noexcept = 0;
0098 
0099    /// Get the histogram title.
0100    const std::string &GetTitle() const { return fTitle; }
0101 
0102    /// Given the coordinate `x`, determine the index of the bin.
0103    virtual int GetBinIndex(const CoordArray_t &x) const = 0;
0104    /// Given the coordinate `x`, determine the index of the bin, possibly
0105    /// growing axes for which `x` is out of range.
0106    virtual int GetBinIndexAndGrow(const CoordArray_t &x) const = 0;
0107 
0108    /// Given the local per-axis bins `x`, determine the index of the bin.
0109    virtual int GetBinIndexFromLocalBins(const BinArray_t &x) const = 0;
0110    /// Given the index of the bin, determine the local per-axis bins `x`.
0111    virtual BinArray_t GetLocalBins(int binidx) const = 0;
0112 
0113    /// Get the center in all dimensions of the bin with index `binidx`.
0114    virtual CoordArray_t GetBinCenter(int binidx) const = 0;
0115    /// Get the lower edge in all dimensions of the bin with index `binidx`.
0116    virtual CoordArray_t GetBinFrom(int binidx) const = 0;
0117    /// Get the upper edge in all dimensions of the bin with index `binidx`.
0118    virtual CoordArray_t GetBinTo(int binidx) const = 0;
0119 
0120    /// Get the uncertainty of the bin with index `binidx`.
0121    virtual double GetBinUncertainty(int binidx) const = 0;
0122 
0123    /// Whether this histogram's statistics provide storage for uncertainties, or
0124    /// whether uncertainties are determined as poisson uncertainty of the content.
0125    virtual bool HasBinUncertainty() const = 0;
0126 
0127    /// The bin content, cast to double.
0128    virtual double GetBinContentAsDouble(int binidx) const = 0;
0129 
0130    /// Get a base-class view on axis with index `iAxis`.
0131    ///
0132    /// \param iAxis - index of the axis, must be `0 <= iAxis < DIMENSION`.
0133    virtual const RAxisBase &GetAxis(int iAxis) const = 0;
0134 
0135    /// Get an `AxisIterRange_t` for the whole histogram,
0136    /// excluding under- and overflow.
0137    virtual AxisIterRange_t GetRange() const = 0;
0138 
0139 private:
0140    std::string fTitle; ///< The histogram's title.
0141 };
0142 
0143 /**
0144  \class RHistImplBase
0145  Interface class for `RHistImpl`.
0146 
0147  `RHistImpl` is templated for a specific configuration of axes. To enable access
0148  through `RHist`, `RHistImpl` inherits from `RHistImplBase`, exposing only dimension
0149  (`DIMENSION`) and bin type (`PRECISION`).
0150  */
0151 template <class DATA>
0152 class RHistImplBase: public RHistImplPrecisionAgnosticBase<DATA::GetNDim()> {
0153 public:
0154    /// Type of the statistics (bin content, uncertainties etc).
0155    using Stat_t = DATA;
0156    /// Type of the coordinates.
0157    using CoordArray_t = Hist::CoordArray_t<DATA::GetNDim()>;
0158    /// Type of the local per-axis bin indices.
0159    using BinArray_t = std::array<int, DATA::GetNDim()>;
0160    /// Type of the bin content (and thus weights).
0161    using Weight_t = typename DATA::Weight_t;
0162 
0163    /// Type of the `Fill(x, w)` function
0164    using FillFunc_t = void (RHistImplBase::*)(const CoordArray_t &x, Weight_t w);
0165 
0166 private:
0167    /// The histogram's bin content, uncertainties etc.
0168    Stat_t fStatistics;
0169 
0170 public:
0171    RHistImplBase() = default;
0172    RHistImplBase(size_t numBins, size_t numOverflowBins): fStatistics(numBins, numOverflowBins) {}
0173    RHistImplBase(std::string_view title, size_t numBins, size_t numOverflowBins)
0174       : RHistImplPrecisionAgnosticBase<DATA::GetNDim()>(title), fStatistics(numBins, numOverflowBins)
0175    {}
0176    RHistImplBase(const RHistImplBase &) = default;
0177    RHistImplBase(RHistImplBase &&) = default;
0178 
0179    virtual std::unique_ptr<RHistImplBase> Clone() const = 0;
0180 
0181    /// Interface function to fill a vector or array of coordinates with
0182    /// corresponding weights.
0183    /// \note the size of `xN` and `weightN` must be the same!
0184    virtual void FillN(const std::span<const CoordArray_t> xN, const std::span<const Weight_t> weightN) = 0;
0185 
0186    /// Interface function to fill a vector or array of coordinates.
0187    virtual void FillN(const std::span<const CoordArray_t> xN) = 0;
0188 
0189    /// Retrieve the pointer to the overridden `Fill(x, w)` function.
0190    virtual FillFunc_t GetFillFunc() const = 0;
0191 
0192    /// Get the bin content (sum of weights) for the bin at coordinate `x`.
0193    virtual Weight_t GetBinContent(const CoordArray_t &x) const = 0;
0194 
0195    using RHistImplPrecisionAgnosticBase<DATA::GetNDim()>::GetBinUncertainty;
0196 
0197    /// Get the bin uncertainty for the bin at coordinate x.
0198    virtual double GetBinUncertainty(const CoordArray_t &x) const = 0;
0199 
0200    /// Get the number of bins in this histogram, including possible under- and
0201    /// overflow bins.
0202    int GetNBins() const noexcept final { return fStatistics.size(); }
0203 
0204    /// Get the number of bins in this histogram, excluding possible under- and
0205    /// overflow bins.
0206    int GetNBinsNoOver() const noexcept final { return fStatistics.sizeNoOver(); }
0207 
0208    /// Get the number of under- and overflow bins of this histogram, excluding all
0209    /// regular bins.
0210    int GetNOverflowBins() const noexcept final { return fStatistics.sizeUnderOver(); }
0211 
0212    /// Get the bin content (sum of weights) for bin index `binidx`.
0213    Weight_t GetBinContent(int binidx) const
0214    {
0215       assert(binidx != 0);
0216       return fStatistics[binidx];
0217    }
0218 
0219    /// Get the bin content (sum of weights) for bin index `binidx` (non-const).
0220    Weight_t &GetBinContent(int binidx)
0221    {
0222       assert(binidx != 0);
0223       return fStatistics[binidx];
0224    }
0225 
0226    /// Const access to statistics.
0227    const Stat_t &GetStat() const noexcept { return fStatistics; }
0228 
0229    /// Non-const access to statistics.
0230    Stat_t &GetStat() noexcept { return fStatistics; }
0231 
0232    /// Get the bin content (sum of weights) for bin index `binidx`, cast to
0233    /// `double`.
0234    double GetBinContentAsDouble(int binidx) const final { return (double)GetBinContent(binidx); }
0235 
0236    /// Add `w` to the bin at index `bin`.
0237    void AddBinContent(int binidx, Weight_t w)
0238    {
0239       assert(binidx != 0);
0240       fStatistics[binidx] += w;
0241    }
0242 };
0243 } // namespace Detail
0244 
0245 namespace Internal {
0246 /** \name Histogram traits
0247     Helper traits for histogram operations.
0248  */
0249 ///\{
0250 
0251 /// Specifies if the wanted result is the bin's lower edge, center or higher edge.
0252 enum class EBinCoord {
0253    kBinFrom,   ///< Get the lower bin edge
0254    kBinCenter, ///< Get the bin center
0255    kBinTo      ///< Get the bin high edge
0256 };
0257 
0258 /// Status of FindBin(x) and FindAdjustedBin(x)
0259 enum class EFindStatus {
0260    kCanGrow, ///< The coordinate could fit after growing the axis
0261    kValid    ///< The returned bin index is valid
0262 };
0263 
0264 /// \name Axis tuple operations
0265 /// Template operations on axis tuple.
0266 ///@{
0267 
0268 /// Recursively gets the total number of bins in whole hist, excluding under- and overflow.
0269 /// Each call gets the current axis' number of bins (excluding under- and overflow) multiplied
0270 /// by that of the next axis.
0271 template <int IDX, class AXISTUPLE>
0272 struct RGetNBinsNoOverCount;
0273 
0274 template <class AXES>
0275 struct RGetNBinsNoOverCount<0, AXES> {
0276    int operator()(const AXES &axes) const { return std::get<0>(axes).GetNBinsNoOver(); }
0277 };
0278 
0279 template <int I, class AXES>
0280 struct RGetNBinsNoOverCount {
0281    int operator()(const AXES &axes) const { return std::get<I>(axes).GetNBinsNoOver() * RGetNBinsNoOverCount<I - 1, AXES>()(axes); }
0282 };
0283 
0284 /// Get the number of bins in whole hist, excluding under- and overflow.
0285 template <class... AXISCONFIG>
0286 int GetNBinsNoOverFromAxes(AXISCONFIG... axisArgs)
0287 {
0288    using axesTuple = std::tuple<AXISCONFIG...>;
0289    return RGetNBinsNoOverCount<sizeof...(AXISCONFIG) - 1, axesTuple>()(axesTuple{axisArgs...});
0290 }
0291 
0292 /// Recursively gets the total number of bins in whole hist, including under- and overflow.
0293 /// Each call gets the current axis' number of bins (including under- and overflow) multiplied
0294 /// by that of the next axis.
0295 template <int IDX, class AXISTUPLE>
0296 struct RGetNBinsCount;
0297 
0298 template <class AXES>
0299 struct RGetNBinsCount<0, AXES> {
0300    int operator()(const AXES &axes) const { return std::get<0>(axes).GetNBins(); }
0301 };
0302 
0303 template <int I, class AXES>
0304 struct RGetNBinsCount {
0305    int operator()(const AXES &axes) const { return std::get<I>(axes).GetNBins() * RGetNBinsCount<I - 1, AXES>()(axes); }
0306 };
0307 
0308 /// Get the number of bins in whole hist, including under- and overflow.
0309 template <class... AXISCONFIG>
0310 int GetNBinsFromAxes(AXISCONFIG... axisArgs)
0311 {
0312    using axesTuple = std::tuple<AXISCONFIG...>;
0313    return RGetNBinsCount<sizeof...(AXISCONFIG) - 1, axesTuple>()(axesTuple{axisArgs...});
0314 }
0315 
0316 /// Get the number of under- and overflow bins in whole hist, excluding regular bins.
0317 template <class... AXISCONFIG>
0318 int GetNOverflowBinsFromAxes(AXISCONFIG... axisArgs)
0319 {
0320    using axesTuple = std::tuple<AXISCONFIG...>;
0321    return RGetNBinsCount<sizeof...(AXISCONFIG) - 1, axesTuple>()(axesTuple{axisArgs...}) - RGetNBinsNoOverCount<sizeof...(AXISCONFIG) - 1, axesTuple>()(axesTuple{axisArgs...});
0322 }
0323 
0324 /// Recursively fills the ranges of all axes, excluding under- and overflow.
0325 /// Each call fills `range` with `begin()` and `end()` of the current axis, excluding
0326 /// under- and overflow.
0327 template <int I, class AXES>
0328 struct RFillIterRange;
0329 
0330 template <class AXES>
0331 struct RFillIterRange<-1, AXES> {
0332    void operator()(Hist::AxisIterRange_t<std::tuple_size<AXES>::value> & /*range*/, const AXES & /*axes*/) const
0333    {}
0334 };
0335 
0336 template <int I, class AXES>
0337 struct RFillIterRange {
0338    void operator()(Hist::AxisIterRange_t<std::tuple_size<AXES>::value> &range, const AXES &axes) const
0339    {
0340       range[0][I] = std::get<I>(axes).begin();
0341       range[1][I] = std::get<I>(axes).end();
0342       RFillIterRange<I - 1, AXES>()(range, axes);
0343    }
0344 };
0345 
0346 /// Recursively gets the number of regular bins just before the current dimension.
0347 /// Each call gets the previous axis' number of regular bins multiplied
0348 /// by the number of regular bins before the previous axis.
0349 template <int I, int NDIMS, typename BINS, class AXES>
0350 struct RGetNRegularBinsBefore;
0351 
0352 template <int NDIMS, typename BINS, class AXES>
0353 struct RGetNRegularBinsBefore<-1, NDIMS, BINS, AXES> {
0354    void operator()(BINS &/*binSizes*/, const AXES &/*axes*/) const
0355    {}
0356 };
0357 
0358 template <int I, int NDIMS, typename BINS, class AXES>
0359 struct RGetNRegularBinsBefore {
0360    void operator()(BINS &binSizes, const AXES &axes) const
0361    {
0362       constexpr const int thisAxis = NDIMS - I - 1;
0363       binSizes[thisAxis] = binSizes[thisAxis-1] * std::get<thisAxis-1>(axes).GetNBinsNoOver();
0364       RGetNRegularBinsBefore<I - 1, NDIMS, BINS, AXES>()(binSizes, axes);
0365    }
0366 };
0367 
0368 /// Recursively gets the total number of regular bins before the current dimension,
0369 /// when computing a global bin that is in under- or overflow in at least one
0370 /// dimension. That global bin's local per-axis bin indices are passed through
0371 /// the `localBins` parameter. These `localBins` were translated to 0-based bins,
0372 /// which is more convenient for some operations and which are the `virtualBins`
0373 /// parameter.
0374 /// Each call gets the current axis' number of regular bins before the global_bin
0375 /// in the current dimension multiplied by the number of regular bins before the
0376 /// current axis.
0377 /// If the global_bin is in under- or overflow in the current dimension (local bin),
0378 /// there is no need to process further.
0379 
0380 //  - We want to know how many regular bins lie before the current overflow bin in the
0381 // histogram's global binning order (which so far I thought was row-major, but now I'm
0382 // not sure, maybe it's actually column-major... it doesn't matter, we don't need to spell out what is the global binning order anyway).
0383 
0384 template <int I, int NDIMS, typename BINS, class AXES>
0385 struct RComputeGlobalBin;
0386 
0387 template <int NDIMS, typename BINS, class AXES>
0388 struct RComputeGlobalBin<-1, NDIMS, BINS, AXES> {
0389    int operator()(int total_regular_bins_before, const AXES &/*axes*/, const BINS &/*virtualBins*/, const BINS &/*binSizes*/, const BINS &/*localBins*/) const
0390    {
0391       return total_regular_bins_before;
0392    }
0393 };
0394 
0395 template <int I, int NDIMS, typename BINS, class AXES>
0396 struct RComputeGlobalBin {
0397    int operator()(int total_regular_bins_before, const AXES &axes, const BINS &virtualBins, const BINS &binSizes, const BINS &localBins) const
0398    {
0399       // We can tell how many regular bins lie before us on this axis,
0400       // accounting for the underflow bin of this axis if it has one.
0401       const int num_underflow_bins = static_cast<int>(!std::get<I>(axes).CanGrow());
0402       const int num_regular_bins_before =
0403          std::max(virtualBins[I] - num_underflow_bins, 0);
0404       total_regular_bins_before += num_regular_bins_before * binSizes[I];
0405 
0406       // If we are on an overflow or underflow bin on this axis, we know that
0407       // we don't need to look at the remaining axes. Projecting on those
0408       // dimensions would only take us into an hyperplane of over/underflow
0409       // bins for the current axis, and we know that by construction there
0410       // will be no regular bins in there.
0411       if (localBins[I] < 1)
0412          return total_regular_bins_before;
0413 
0414       return RComputeGlobalBin<I - 1, NDIMS, BINS, AXES>()(total_regular_bins_before, axes, virtualBins, binSizes, localBins);
0415    }
0416 };
0417 
0418 /// Recursively compute some quantities needed for `ComputeLocalBins`, namely
0419 /// the total number of bins per hyperplane (overflow and regular) and the
0420 /// number of regular bins per hyperplane on the hyperplanes that have them.
0421 template <int I, int NDIMS, class AXES>
0422 struct RComputeLocalBinsInitialisation;
0423 
0424 template <int NDIMS, class AXES>
0425 struct RComputeLocalBinsInitialisation<0, NDIMS, AXES> {
0426    void operator()(std::array<int, NDIMS-1> /* bins_per_hyperplane */, std::array<int, NDIMS-1> /* regular_bins_per_hyperplane */, const AXES & /*axes*/) const
0427    {}
0428 };
0429 
0430 template <int I, int NDIMS, class AXES>
0431 struct RComputeLocalBinsInitialisation {
0432    void operator()(std::array<int, NDIMS-1>& bins_per_hyperplane, std::array<int, NDIMS-1>& regular_bins_per_hyperplane, const AXES &axes) const
0433    {
0434       constexpr const int thisAxis = NDIMS - I - 1;
0435       bins_per_hyperplane[thisAxis] = Internal::RGetNBinsCount<thisAxis, AXES>()(axes);
0436       regular_bins_per_hyperplane[thisAxis] = Internal::RGetNBinsNoOverCount<thisAxis, AXES>()(axes);
0437       RComputeLocalBinsInitialisation<I - 1, NDIMS, AXES>()(bins_per_hyperplane, regular_bins_per_hyperplane, axes);
0438    }
0439 };
0440 
0441 /// Recursively computes the number of regular bins before the current dimension,
0442 /// as well as the number of under- and overflow bins left to account for, after
0443 /// the current dimension. If the latter is equal to 0, there is no need to process
0444 /// further.
0445 /// It is computing local bins that are in under- or overflow in at least one
0446 /// dimension.
0447 /// Starting at the highest dimension, it examines how many full hyperplanes of
0448 /// regular bins lie before, then projects on the remaining dimensions.
0449 
0450 template <int I, int NDIMS, class AXES>
0451 struct RComputeLocalBins;
0452 
0453 template <int NDIMS, class AXES>
0454 struct RComputeLocalBins<0, NDIMS, AXES> {
0455    void operator()(const AXES &/*axes*/, int &/*unprocessed_previous_overflow_bin*/,
0456          int &/*num_regular_bins_before*/, std::array<int, NDIMS-1> /* bins_per_hyperplane */,
0457          std::array<int, NDIMS-1> /* regular_bins_per_hyperplane */, int /* curr_bins_per_hyperplane */,
0458          int /* curr_regular_bins_per_hyperplane */) const
0459    {}
0460 };
0461 
0462 template <int I, int NDIMS, class AXES>
0463 struct RComputeLocalBins {
0464    void operator()(const AXES &axes, int &unprocessed_previous_overflow_bin,
0465          int &num_regular_bins_before, std::array<int, NDIMS-1> bins_per_hyperplane,
0466          std::array<int, NDIMS-1> regular_bins_per_hyperplane, int curr_bins_per_hyperplane,
0467          int curr_regular_bins_per_hyperplane) const
0468    {
0469       // Let's start by computing the contribution of the underflow
0470       // hyperplane (if any), in which we know there will be no regular bins
0471       const int num_underflow_hyperplanes =
0472             static_cast<int>(!std::get<I>(axes).CanGrow());
0473       const int bins_in_underflow_hyperplane =
0474             num_underflow_hyperplanes * bins_per_hyperplane[I-1];
0475 
0476       // Next, from the total number of bins per hyperplane and the number of
0477       // regular bins per hyperplane that has them, we deduce the number of
0478       // overflow bins per hyperplane that has regular bins.
0479       const int overflow_bins_per_regular_hyperplane =
0480             bins_per_hyperplane[I-1] - regular_bins_per_hyperplane[I-1];
0481 
0482       // This allows us to answer a key question: are there any under/overflow
0483       // bins on the hyperplanes that have regular bins? It may not be the
0484       // case if all of their axes are growable, and thus don't have overflow bins.
0485       if (overflow_bins_per_regular_hyperplane != 0) {
0486             // If so, we start by cutting off the contribution of the underflow
0487             // and overflow hyperplanes, to focus specifically on regular bins.
0488             const int overflow_bins_in_regular_hyperplanes =
0489                std::min(
0490                   std::max(
0491                      unprocessed_previous_overflow_bin
0492                            - bins_in_underflow_hyperplane,
0493                      0
0494                   ),
0495                   overflow_bins_per_regular_hyperplane
0496                         * std::get<I>(axes).GetNBinsNoOver()
0497                );
0498 
0499             // We count how many _complete_ "regular" hyperplanes that leaves
0500             // before us, and account for those in our regular bin count.
0501             const int num_regular_hyperplanes_before =
0502                overflow_bins_in_regular_hyperplanes
0503                   / overflow_bins_per_regular_hyperplane;
0504             num_regular_bins_before +=
0505                num_regular_hyperplanes_before
0506                   * regular_bins_per_hyperplane[I-1];
0507 
0508             // This only leaves the _current_ hyperplane as a possible source of
0509             // more regular bins that we haven't accounted for yet. We'll take
0510             // those into account while processing previous dimensions.
0511             unprocessed_previous_overflow_bin =
0512                overflow_bins_in_regular_hyperplanes
0513                   % overflow_bins_per_regular_hyperplane;
0514       } else {
0515             // If there are no overflow bins in regular hyperplane, then the
0516             // rule changes: observing _one_ overflow bin after the underflow
0517             // hyperplane means that _all_ regular hyperplanes on this axis are
0518             // already before us.
0519             if (unprocessed_previous_overflow_bin >= bins_in_underflow_hyperplane) {
0520                num_regular_bins_before +=
0521                   std::get<I>(axes).GetNBinsNoOver()
0522                         * regular_bins_per_hyperplane[I-1];
0523             }
0524 
0525             // In this case, we're done, because the current bin may only lie
0526             // in the underflow or underflow hyperplane of this axis. Which
0527             // means that there are no further regular bins to be accounted for
0528             // in the current hyperplane.
0529             unprocessed_previous_overflow_bin = 0;
0530       }
0531 
0532       // No need to continue this loop if we've taken into account all
0533       // overflow bins that were associated with regular bins.
0534       if (unprocessed_previous_overflow_bin == 0)
0535          return;
0536 
0537       return Internal::RComputeLocalBins<I - 1, NDIMS, AXES>()
0538                                  (axes, unprocessed_previous_overflow_bin, num_regular_bins_before, bins_per_hyperplane,
0539                                  regular_bins_per_hyperplane, curr_bins_per_hyperplane, curr_regular_bins_per_hyperplane);
0540    }
0541 };
0542 
0543 /// Recursively computes zero-based local bin indices, given...
0544 ///
0545 /// - A zero-based global bin index
0546 /// - The number of considered bins on each axis (can be either `GetNBinsNoOver`
0547 ///   or `GetNBins` depending on what you are trying to do)
0548 /// - A policy of treating all bins as regular (i.e. no negative indices)
0549 template <int I, int NDIMS, typename BINS, class AXES, class BINTYPE>
0550 struct RComputeLocalBinsRaw;
0551 
0552 template <int NDIMS, typename BINS, class AXES, class BINTYPE>
0553 struct RComputeLocalBinsRaw<-1, NDIMS, BINS, AXES, BINTYPE> {
0554    void operator()(BINS & /*virtualBins*/, const AXES & /*axes*/, int /*zeroBasedGlobalBin*/, BINTYPE /*GetNBinType*/) const
0555    {}
0556 };
0557 
0558 template <int I, int NDIMS, typename BINS, class AXES, class BINTYPE>
0559 struct RComputeLocalBinsRaw {
0560    void operator()(BINS &virtualBins, const AXES &axes, int zeroBasedGlobalBin, BINTYPE GetNBinType) const
0561    {
0562       constexpr const int thisAxis = NDIMS - I - 1;
0563       virtualBins[thisAxis] = zeroBasedGlobalBin % (std::get<thisAxis>(axes).*GetNBinType)();
0564       RComputeLocalBinsRaw<I - 1, NDIMS, BINS, AXES, BINTYPE>()(virtualBins, axes, zeroBasedGlobalBin / (std::get<thisAxis>(axes).*GetNBinType)(), GetNBinType);
0565    }
0566 };
0567 
0568 /// Recursively computes a zero-based global bin index, given...
0569 ///
0570 /// - A set of zero-based per-axis bin indices
0571 /// - The number of considered bins on each axis (can be either `GetNBinsNoOver`
0572 ///   or `GetNBins` depending on what you are trying to do)
0573 /// - A policy of treating all bins qs regular (i.e. no negative indices)
0574 template <int I, int NDIMS, typename BINS, class AXES, class BINTYPE>
0575 struct RComputeGlobalBinRaw;
0576 
0577 template <int NDIMS, typename BINS, class AXES, class BINTYPE>
0578 struct RComputeGlobalBinRaw<-1, NDIMS, BINS, AXES, BINTYPE> {
0579    int operator()(int globalVirtualBin, const AXES & /*axes*/, const BINS & /*zeroBasedLocalBins*/, int /*binSize*/, BINTYPE /*GetNBinType*/) const
0580    {
0581       return globalVirtualBin;
0582    }
0583 };
0584 
0585 template <int I, int NDIMS, typename BINS, class AXES, class BINTYPE>
0586 struct RComputeGlobalBinRaw {
0587    int operator()(int globalVirtualBin, const AXES &axes, const BINS &zeroBasedLocalBins, int binSize, BINTYPE GetNBinType) const
0588    {
0589       constexpr const int thisAxis = NDIMS - I - 1;
0590       globalVirtualBin += zeroBasedLocalBins[thisAxis] * binSize;
0591       binSize *= (std::get<thisAxis>(axes).*GetNBinType)();
0592       return Internal::RComputeGlobalBinRaw<I - 1, NDIMS, BINS, AXES, BINTYPE>()(globalVirtualBin, axes, zeroBasedLocalBins, binSize, GetNBinType);
0593    }
0594 };
0595 
0596 /// Recursively converts zero-based virtual bins where the underflow bin
0597 /// has index `0` and the overflow bin has index `N+1` where `N` is the axis'
0598 /// number of regular bins, to the standard `kUnderflowBin`/`kOverflowBin` for under/overflow
0599 /// bin indexing convention.
0600 ///
0601 /// For growable axes, must add 1 to go back to standard indices as their virtual
0602 /// indexing convention is also 0-based, with zero designating the first regular bin.
0603 template <int I, int NDIMS, typename BINS, class AXES>
0604 struct RVirtualBinsToLocalBins;
0605 
0606 template <int NDIMS, typename BINS, class AXES>
0607 struct RVirtualBinsToLocalBins<-1, NDIMS, BINS, AXES> {
0608    void operator()(BINS & /*localBins*/, const AXES & /*axes*/, const BINS & /*virtualBins*/) const
0609    {}
0610 };
0611 
0612 template <int I, int NDIMS, typename BINS, class AXES>
0613 struct RVirtualBinsToLocalBins {
0614    void operator()(BINS &localBins, const AXES &axes, const BINS &virtualBins) const
0615    {
0616       constexpr const int thisAxis = NDIMS - I - 1;
0617       if ((!std::get<thisAxis>(axes).CanGrow()) && (virtualBins[thisAxis] == 0)) {
0618          localBins[thisAxis] = RAxisBase::kUnderflowBin;
0619       } else if ((!std::get<thisAxis>(axes).CanGrow()) && (virtualBins[thisAxis] == (std::get<thisAxis>(axes).GetNBins() - 1))) {
0620          localBins[thisAxis] = RAxisBase::kOverflowBin;
0621       } else {
0622          const int regular_bin_offset = -static_cast<int>(std::get<thisAxis>(axes).CanGrow());
0623          localBins[thisAxis] = virtualBins[thisAxis] - regular_bin_offset;
0624       }
0625       RVirtualBinsToLocalBins<I - 1, NDIMS, BINS, AXES>()(localBins, axes, virtualBins);
0626    }
0627 };
0628 
0629 /// Recursively converts local axis bins from the standard `kUnderflowBin`/`kOverflowBin` for under/overflow
0630 /// bin indexing convention, to a "virtual bin" convention where the underflow bin
0631 /// has index `0` and the overflow bin has index `N+1` where `N` is the axis'
0632 /// number of regular bins.
0633 ///
0634 /// For growable axes, subtract 1 from regular indices so that the indexing
0635 /// convention remains zero-based (this means that there will be no "holes" in
0636 /// global binning, which matters more than the choice of regular index base)
0637 template <int I, int NDIMS, typename BINS, class AXES>
0638 struct RLocalBinsToVirtualBins;
0639 
0640 template <int NDIMS, typename BINS, class AXES>
0641 struct RLocalBinsToVirtualBins<-1, NDIMS, BINS, AXES> {
0642    void operator()(BINS & /*virtualBins*/, const AXES & /*axes*/, const BINS & /*localBins*/) const
0643    {}
0644 };
0645 
0646 template <int I, int NDIMS, typename BINS, class AXES>
0647 struct RLocalBinsToVirtualBins {
0648    void operator()(BINS &virtualBins, const AXES &axes, const BINS &localBins) const
0649    {
0650       constexpr const int thisAxis = NDIMS - I - 1;
0651       switch (localBins[thisAxis]) {
0652          case RAxisBase::kUnderflowBin:
0653                virtualBins[thisAxis] = 0; break;
0654          case RAxisBase::kOverflowBin:
0655                virtualBins[thisAxis] = std::get<thisAxis>(axes).GetNBins() - 1; break;
0656          default:
0657                virtualBins[thisAxis] = localBins[thisAxis] - static_cast<int>(std::get<thisAxis>(axes).CanGrow());
0658       }
0659       RLocalBinsToVirtualBins<I - 1, NDIMS, BINS, AXES>()(virtualBins, axes, localBins);
0660    }
0661 };
0662 
0663 /// Find the per-axis local bin indices associated with a certain set of coordinates.
0664 template <int I, int NDIMS, typename BINS, typename COORD, class AXES>
0665 struct RFindLocalBins;
0666 
0667 template <int NDIMS, typename BINS, typename COORD, class AXES>
0668 struct RFindLocalBins<-1, NDIMS, BINS, COORD, AXES> {
0669    void operator()(BINS & /*localBins*/, const AXES & /*axes*/, const COORD & /*coords*/) const
0670    {}
0671 };
0672 
0673 template <int I, int NDIMS, typename BINS, typename COORD, class AXES>
0674 struct RFindLocalBins {
0675    void operator()(BINS &localBins, const AXES &axes, const COORD &coords) const
0676    {
0677       constexpr const int thisAxis = NDIMS - I - 1;
0678       localBins[thisAxis] = std::get<thisAxis>(axes).FindBin(coords[thisAxis]);
0679       RFindLocalBins<I - 1, NDIMS, BINS, COORD, AXES>()(localBins, axes, coords);
0680    }
0681 };
0682 
0683 /// Recursively converts local axis bins from the standard `kUnderflowBin`/`kOverflowBin` for
0684 /// under/overflow bin indexing convention, to the corresponding bin coordinates.
0685 template <int I, int NDIMS, typename BINS, typename COORD, class AXES>
0686 struct RLocalBinsToCoords;
0687 
0688 template <int NDIMS, typename BINS, typename COORD, class AXES>
0689 struct RLocalBinsToCoords<-1, NDIMS, BINS, COORD, AXES> {
0690    void operator()(COORD & /*coords*/, const AXES & /*axes*/, const BINS & /*localBins*/, EBinCoord /*kind*/) const
0691    {}
0692 };
0693 
0694 template <int I, int NDIMS, typename BINS, typename COORD, class AXES>
0695 struct RLocalBinsToCoords {
0696    void operator()(COORD &coords, const AXES &axes, const BINS &localBins, EBinCoord kind) const
0697    {
0698       constexpr const int thisAxis = NDIMS - I - 1;
0699       int axisbin = localBins[thisAxis];
0700       switch (kind) {
0701          case EBinCoord::kBinFrom: coords[thisAxis] = std::get<thisAxis>(axes).GetBinFrom(axisbin); break;
0702          case EBinCoord::kBinCenter: coords[thisAxis] = std::get<thisAxis>(axes).GetBinCenter(axisbin); break;
0703          case EBinCoord::kBinTo: coords[thisAxis] = std::get<thisAxis>(axes).GetBinTo(axisbin); break;
0704       }
0705       RLocalBinsToCoords<I - 1, NDIMS, BINS, COORD, AXES>()(coords, axes, localBins, kind);
0706    }
0707 };
0708 
0709 template <class... AXISCONFIG>
0710 static std::array<const RAxisBase *, sizeof...(AXISCONFIG)> GetAxisView(const AXISCONFIG &... axes) noexcept
0711 {
0712    std::array<const RAxisBase *, sizeof...(AXISCONFIG)> axisViews{{&axes...}};
0713    return axisViews;
0714 }
0715 
0716 ///\}
0717 } // namespace Internal
0718 
0719 namespace Detail {
0720 
0721 template <class DATA, class... AXISCONFIG>
0722 class RHistImpl final: public RHistImplBase<DATA> {
0723    static_assert(sizeof...(AXISCONFIG) == DATA::GetNDim(), "Number of axes must equal histogram dimension");
0724 
0725    friend typename DATA::Hist_t;
0726 
0727 public:
0728    using ImplBase_t = RHistImplBase<DATA>;
0729    using CoordArray_t = typename ImplBase_t::CoordArray_t;
0730    using BinArray_t = typename ImplBase_t::BinArray_t;
0731    using Weight_t = typename ImplBase_t::Weight_t;
0732    using typename ImplBase_t::FillFunc_t;
0733    template <int NDIMS = DATA::GetNDim()>
0734    using AxisIterRange_t = typename Hist::AxisIterRange_t<NDIMS>;
0735 
0736 private:
0737    std::tuple<AXISCONFIG...> fAxes; ///< The histogram's axes
0738 
0739 public:
0740    RHistImpl(TRootIOCtor *);
0741    RHistImpl(AXISCONFIG... axisArgs);
0742    RHistImpl(std::string_view title, AXISCONFIG... axisArgs);
0743 
0744    std::unique_ptr<ImplBase_t> Clone() const override {
0745       return std::unique_ptr<ImplBase_t>(new RHistImpl(*this));
0746    }
0747 
0748    /// Retrieve the fill function for this histogram implementation, to prevent
0749    /// the virtual function call for high-frequency fills.
0750    FillFunc_t GetFillFunc() const final {
0751       return (FillFunc_t)&RHistImpl::Fill;
0752    }
0753 
0754    /// Get the axes of this histogram.
0755    const std::tuple<AXISCONFIG...> &GetAxes() const { return fAxes; }
0756 
0757    /// Normalized axes access, converting from actual axis type to base class.
0758    const RAxisBase &GetAxis(int iAxis) const final { return *std::apply(Internal::GetAxisView<AXISCONFIG...>, fAxes)[iAxis]; }
0759 
0760    /// Computes a zero-based global bin index, given...
0761    ///
0762    /// - A set of zero-based per-axis bin indices
0763    /// - The number of considered bins on each axis (can be either `GetNBinsNoOver`
0764    ///   or `GetNBins` depending on what you are trying to do)
0765    /// - A policy of treating all bins qs regular (i.e. no negative indices)
0766    template <int NDIMS, typename BINTYPE>
0767    int ComputeGlobalBinRaw(const BinArray_t& zeroBasedLocalBins, BINTYPE GetNBinType) const {
0768       int result = 0;
0769       int binSize = 1;
0770       return Internal::RComputeGlobalBinRaw<NDIMS - 1, NDIMS, BinArray_t, decltype(fAxes), BINTYPE>()(result, fAxes, zeroBasedLocalBins, binSize, GetNBinType);
0771    }
0772 
0773    /// Computes zero-based local bin indices, given...
0774    ///
0775    /// - A zero-based global bin index
0776    /// - The number of considered bins on each axis (can be either `GetNBinsNoOver`
0777    ///   or `GetNBins` depending on what you are trying to do)
0778    /// - A policy of treating all bins as regular (i.e. no negative indices)
0779    template <int NDIMS, typename BINTYPE>
0780    BinArray_t ComputeLocalBinsRaw(int zeroBasedGlobalBin, BINTYPE GetNBinType) const {
0781       BinArray_t result;
0782       Internal::RComputeLocalBinsRaw<NDIMS - 1, NDIMS, BinArray_t, decltype(fAxes), BINTYPE>()(result, fAxes, zeroBasedGlobalBin, GetNBinType);
0783       return result;
0784    }
0785 
0786    /// Converts local axis bins from the standard `kUnderflowBin`/`kOverflowBin` for under/overflow
0787    /// bin indexing convention, to a "virtual bin" convention where the underflow bin
0788    /// has index `0` and the overflow bin has index `N+1` where `N` is the axis'
0789    /// number of regular bins.
0790    template <int NDIMS>
0791    BinArray_t LocalBinsToVirtualBins(const BinArray_t& localBins) const {
0792       BinArray_t virtualBins;
0793       Internal::RLocalBinsToVirtualBins<NDIMS - 1, NDIMS, BinArray_t, decltype(fAxes)>()(virtualBins, fAxes, localBins);
0794       return virtualBins;
0795    }
0796 
0797    /// Converts zero-based virtual bins where the underflow bin has
0798    /// index `0` and the overflow bin has index `N+1` where `N` is the axis'
0799    /// number of regular bins, to the standard `kUnderflowBin`/`kOverflowBin` for under/overflow
0800    /// bin indexing convention.
0801    template <int NDIMS>
0802    BinArray_t VirtualBinsToLocalBins(const BinArray_t& virtualBins) const {
0803       BinArray_t localBins = {};
0804       Internal::RVirtualBinsToLocalBins<NDIMS - 1, NDIMS, BinArray_t, decltype(fAxes)>()(localBins, fAxes, virtualBins);
0805       return localBins;
0806    }
0807 
0808    /// Computes the global index of a certain bin on an `NDIMS`-dimensional histogram,
0809    /// knowing the local per-axis bin indices as returned by calling `FindBin()` on each axis.
0810    template <int NDIMS>
0811    int ComputeGlobalBin(BinArray_t& local_bins) const {
0812       // Get regular bins out of the way
0813       if (std::all_of(local_bins.cbegin(), local_bins.cend(),
0814                      [](int bin) { return bin >= 1; })) {
0815          for (int bin = 0; bin < NDIMS; bin++)
0816             local_bins[bin] -= 1;
0817          return ComputeGlobalBinRaw<NDIMS>(local_bins, &ROOT::Experimental::RAxisBase::GetNBinsNoOver) + 1;
0818       }
0819 
0820       // Convert bin indices to a zero-based coordinate system where the underflow
0821       // bin (if any) has coordinate 0 and the overflow bin (if any) has
0822       // coordinate N-1, where N is the axis' total number of bins.
0823       BinArray_t virtual_bins = LocalBinsToVirtualBins<NDIMS>(local_bins);
0824 
0825       // Deduce what the global bin index would be in this coordinate system that
0826       // unifies regular and overflow bins.
0827       const int global_virtual_bin = ComputeGlobalBinRaw<NDIMS>(virtual_bins, &ROOT::Experimental::RAxisBase::GetNBins);
0828 
0829       // Move to 1-based and negative indexing
0830       const int neg_1based_virtual_bin = -global_virtual_bin - 1;
0831 
0832       // At this point, we have an index that represents a count of all bins, both
0833       // regular and overflow, that are located before the current bin when
0834       // enumerating histogram bins in row-major order.
0835       //
0836       // We will next count the number of _regular_ bins which are located before
0837       // the current bin, and by removing this offset from the above index, we
0838       // will get a count of overflow bins that are located before the current bin
0839       // in row-major order. Which is what we want as our overflow bin index.
0840       //
0841       int total_regular_bins_before = 0;
0842 
0843       // First, we need to know how many regular bins we leave behind us for each
0844       // step on each axis, assuming that the bin from which we come was regular.
0845       //
0846       // If mathematically inclined, you can also think of this as the size of an
0847       // hyperplane of regular bins when projecting on lower-numbered dimensions.
0848       // See the docs of ComputeLocalBins for more on this worldview.
0849       //
0850       BinArray_t bin_sizes;
0851       bin_sizes[0] = 1;
0852       Internal::RGetNRegularBinsBefore<NDIMS - 2, NDIMS, BinArray_t, decltype(fAxes)>()(bin_sizes, fAxes);
0853 
0854       // With that, we can deduce how many regular bins lie before us.
0855       total_regular_bins_before = Internal::RComputeGlobalBin<NDIMS - 1, NDIMS, BinArray_t, decltype(fAxes)>()
0856          (total_regular_bins_before, fAxes, virtual_bins, bin_sizes, local_bins);
0857 
0858       // Now that we know how many bins lie before us, and how many of those are
0859       // regular bins, we can trivially deduce how many overflow bins lie before
0860       // us, and emit that as our global overflow bin index.
0861       return neg_1based_virtual_bin + total_regular_bins_before;
0862    }
0863 
0864    /// Computes the local per-axis bin indices of a certain bin on an `NDIMS`-dimensional histogram,
0865    /// knowing the global histogram bin index.
0866    template <int NDIMS>
0867    BinArray_t ComputeLocalBins(int global_bin) const {
0868       // Get regular bins out of the way
0869       if (global_bin >= 1) {
0870          BinArray_t computed_bins = ComputeLocalBinsRaw<NDIMS>(global_bin - 1, &ROOT::Experimental::RAxisBase::GetNBinsNoOver);
0871          for (int bin = 0; bin < NDIMS; ++bin)
0872             computed_bins[bin] += 1;
0873          return computed_bins;
0874       }
0875 
0876       // Convert our negative index to something positive and 0-based, as that is
0877       // more convenient to work with. Note, however, that this is _not_
0878       // equivalent to the virtual_bin that we had before, because what we have
0879       // here is a count of overflow bins, not of all bins...
0880       const int corrected_virtual_overflow_bin = -global_bin - 1;
0881 
0882       // ...so we need to retrieve and bring back the regular bin count, and this
0883       // is where the fun begins.
0884       //
0885       // The main difficulty is that the number of regular bins is not fixed as
0886       // one slides along a histogram axis. Using a 2D binning case as a simple
0887       // motivating example...
0888       //
0889       //    -1   -2   -3   -4   <- No regular bins on the underflow line of axis 1
0890       //    -5    1    2   -6   <- Some of them on middle lines of axis 1
0891       //    -7    3    4   -8
0892       //    -9   -10  -11  -12  <- No regular bins on the overflow line of axis 1
0893       //
0894       // As we go to higher dimensions, the geometry becomes more complex, but
0895       // if we replace "line" with "plane", we get a similar picture in 3D when we
0896       // slide along axis 2:
0897       //
0898       //  No regular bins on the    Some of them on the     No regular bins again
0899       //    UF plane of axis 2    regular planes of ax.2   on the OF plane of ax.2
0900       //
0901       //    -1   -2   -3   -4       -17  -18  -19  -20      -29  -30  -31  -32
0902       //    -5   -6   -7   -8       -21   1    2   -22      -33  -34  -35  -36
0903       //    -9   -10  -11  -12      -23   3    4   -24      -37  -37  -39  -40
0904       //    -13  -14  -15  -16      -25  -26  -27  -28      -41  -42  -43  -44
0905       //
0906       // We can generalize this to N dimensions by saying that as we slide along
0907       // the last axis of an N-d histogram, we see an hyperplane full of overflow
0908       // bins, then some hyperplanes with regular bins in the "middle" surrounded
0909       // by overflow bins, then a last hyperplane full of overflow bins.
0910       //
0911       // From this, we can devise a recursive algorithm to recover the number of
0912       // regular bins before the overflow bin we're currently looking at:
0913       //
0914       // - Start by processing the last histogram axis.
0915       // - Ignore the first and last hyperplane on this axis, which only contain
0916       //   underflow and overflow bins respectively.
0917       // - Count how many complete hyperplanes of regular bins lie before us on
0918       //   this axis, which we can do indirectly in our overflow bin based
0919       //   reasoning by computing the perimeter of the regular region and dividing
0920       //   our "regular" overflow bin count by that amount.
0921       // - Now we counted previous hyperplanes on this last histogram axis, but
0922       //   we need to process the hyperplane that our bin is located in, if any.
0923       //      * For this, we reduce our overflow bin count to a count of
0924       //        _unaccounted_ overflow bins in the current hyperplane...
0925       //      * ...which allows us to recursively continue the computation by
0926       //        processing the next (well, previous) histogram axis in the context
0927       //        of this hyperplane, in the same manner as above.
0928       //
0929       // Alright, now that the general plan is sorted out, let's compute some
0930       // quantities that we are going to need, namely the total number of bins per
0931       // hyperplane (overflow and regular) and the number of regular bins per
0932       // hyperplane on the hyperplanes that have them.
0933       //
0934       std::array<int, NDIMS - 1> bins_per_hyperplane{};
0935       std::array<int, NDIMS - 1> regular_bins_per_hyperplane{};
0936       Internal::RComputeLocalBinsInitialisation<NDIMS - 1, NDIMS, decltype(fAxes)>()(bins_per_hyperplane, regular_bins_per_hyperplane, fAxes);
0937 
0938       int curr_bins_per_hyperplane = Internal::RGetNBinsCount<NDIMS - 1, decltype(fAxes)>()(fAxes);
0939       int curr_regular_bins_per_hyperplane = Internal::RGetNBinsNoOverCount<NDIMS - 1, decltype(fAxes)>()(fAxes);
0940 
0941       // Given that, we examine each axis, starting from the last one.
0942       int unprocessed_previous_overflow_bin = corrected_virtual_overflow_bin;
0943       int num_regular_bins_before = 0;
0944       Internal::RComputeLocalBins<NDIMS - 1, NDIMS, decltype(fAxes)>()
0945                                  (fAxes, unprocessed_previous_overflow_bin, num_regular_bins_before, bins_per_hyperplane,
0946                                  regular_bins_per_hyperplane, curr_bins_per_hyperplane, curr_regular_bins_per_hyperplane);
0947 
0948       // By the time we reach the first axis, there should only be at most one
0949       // full row of regular bins before us:
0950       //
0951       //    -1  1  2  3  -2
0952       //     ^            ^
0953       //     |            |
0954       //     |        Option 2: one overflow bin before us
0955       //     |
0956       // Option 1: no overflow bin before us
0957       //
0958       num_regular_bins_before +=
0959          unprocessed_previous_overflow_bin * std::get<0>(fAxes).GetNBinsNoOver();
0960 
0961       // Now that we know the number of regular bins before us, we can add this to
0962       // to the zero-based overflow bin index that we started with to get a global
0963       // zero-based bin index accounting for both under/overflow bins and regular
0964       // bins, just like what we had in the ComputeGlobalBin<DATA::GetNDim()>() implementation.
0965       const int global_virtual_bin =
0966          corrected_virtual_overflow_bin + num_regular_bins_before;
0967 
0968       // We can then easily go back to zero-based "virtual" bin indices...
0969       const BinArray_t virtual_bins = ComputeLocalBinsRaw<NDIMS>(global_virtual_bin, &ROOT::Experimental::RAxisBase::GetNBins);
0970 
0971       // ...and from that go back to the -1/-2 overflow bin indexing convention.
0972       return VirtualBinsToLocalBins<NDIMS>(virtual_bins);
0973    }
0974 
0975    /// Get the bin index for the given coordinates `x`. The use of `RFindLocalBins`
0976    /// allows to convert the coordinates to local per-axis bin indices before using
0977    /// `ComputeGlobalBin()`.
0978    int GetBinIndex(const CoordArray_t &x) const final
0979    {
0980       BinArray_t localBins = {};
0981       Internal::RFindLocalBins<DATA::GetNDim() - 1, DATA::GetNDim(), BinArray_t, CoordArray_t, decltype(fAxes)>()(localBins, fAxes, x);
0982       int result = ComputeGlobalBin<DATA::GetNDim()>(localBins);
0983       return result;
0984    }
0985 
0986    /// Get the bin index for the given coordinates `x`, growing the axes as needed.
0987    /// The use of `RFindLocalBins` allows to convert the coordinates to local
0988    /// per-axis bin indices before using `ComputeGlobalBin()`.
0989    ///
0990    /// TODO: implement growable behavior
0991    int GetBinIndexAndGrow(const CoordArray_t &x) const final
0992    {
0993       Internal::EFindStatus status = Internal::EFindStatus::kCanGrow;
0994       int ret = 0;
0995       BinArray_t localBins = {};
0996       while (status == Internal::EFindStatus::kCanGrow) {
0997          Internal::RFindLocalBins<DATA::GetNDim() - 1, DATA::GetNDim(), BinArray_t, CoordArray_t, decltype(fAxes)>()(localBins, fAxes, x);
0998          ret = ComputeGlobalBin<DATA::GetNDim()>(localBins);
0999          status = Internal::EFindStatus::kValid;
1000       }
1001       return ret;
1002    }
1003 
1004    /// Get the bin index for the given local per-axis bin indices `x`, using
1005    /// `ComputeGlobalBin()`.
1006    int GetBinIndexFromLocalBins(const BinArray_t &x) const final
1007    {
1008       BinArray_t localBins = x;
1009       int result = ComputeGlobalBin<DATA::GetNDim()>(localBins);
1010       return result;
1011    }
1012 
1013    /// Get the local per-axis bin indices `x` for the given bin index, using
1014    /// `ComputeLocalBins()`.
1015    BinArray_t GetLocalBins(int binidx) const final
1016    {
1017       BinArray_t localBins = ComputeLocalBins<DATA::GetNDim()>(binidx);
1018       return localBins;
1019    }
1020 
1021    /// Get the center coordinates of the bin with index `binidx`.
1022    CoordArray_t GetBinCenter(int binidx) const final
1023    {
1024       BinArray_t localBins = ComputeLocalBins<DATA::GetNDim()>(binidx);
1025       CoordArray_t coords;
1026       Internal::RLocalBinsToCoords<DATA::GetNDim() - 1, DATA::GetNDim(), BinArray_t, CoordArray_t, decltype(fAxes)>()(coords, fAxes, localBins, Internal::EBinCoord::kBinCenter);
1027       return coords;
1028    }
1029 
1030    /// Get the coordinates of the low limit of the bin with index `binidx`.
1031    CoordArray_t GetBinFrom(int binidx) const final
1032    {
1033       BinArray_t localBins = ComputeLocalBins<DATA::GetNDim()>(binidx);
1034       CoordArray_t coords;
1035       Internal::RLocalBinsToCoords<DATA::GetNDim() - 1, DATA::GetNDim(), BinArray_t, CoordArray_t, decltype(fAxes)>()(coords, fAxes, localBins, Internal::EBinCoord::kBinFrom);
1036       return coords;
1037    }
1038 
1039    /// Get the coordinates of the high limit of the bin with index `binidx`.
1040    CoordArray_t GetBinTo(int binidx) const final
1041    {
1042       BinArray_t localBins = ComputeLocalBins<DATA::GetNDim()>(binidx);
1043       CoordArray_t coords;
1044       Internal::RLocalBinsToCoords<DATA::GetNDim() - 1, DATA::GetNDim(), BinArray_t, CoordArray_t, decltype(fAxes)>()(coords, fAxes, localBins, Internal::EBinCoord::kBinTo);
1045       return coords;
1046    }
1047 
1048    /// Fill an array of `weightN` to the bins specified by coordinates `xN`.
1049    /// For each element `i`, the weight `weightN[i]` will be added to the bin
1050    /// at the coordinate `xN[i]`
1051    /// \note `xN` and `weightN` must have the same size!
1052    void FillN(const std::span<const CoordArray_t> xN, const std::span<const Weight_t> weightN) final
1053    {
1054 #ifndef NDEBUG
1055       if (xN.size() != weightN.size()) {
1056          R__LOG_ERROR(HistLog()) << "Not the same number of points and weights!";
1057          return;
1058       }
1059 #endif
1060 
1061       for (size_t i = 0; i < xN.size(); ++i) {
1062          Fill(xN[i], weightN[i]);
1063       }
1064    }
1065 
1066    /// Fill an array of `weightN` to the bins specified by coordinates `xN`.
1067    /// For each element `i`, the weight `weightN[i]` will be added to the bin
1068    /// at the coordinate `xN[i]`
1069    void FillN(const std::span<const CoordArray_t> xN) final
1070    {
1071       for (auto &&x: xN) {
1072          Fill(x);
1073       }
1074    }
1075 
1076    /// Add a single weight `w` to the bin at coordinate `x`.
1077    void Fill(const CoordArray_t &x, Weight_t w = 1.)
1078    {
1079       int bin = GetBinIndexAndGrow(x);
1080       this->GetStat().Fill(x, bin, w);
1081    }
1082 
1083    /// Get the content of the bin at position `x`.
1084    Weight_t GetBinContent(const CoordArray_t &x) const final
1085    {
1086       int bin = GetBinIndex(x);
1087       return ImplBase_t::GetBinContent(bin);
1088    }
1089 
1090    /// Return the uncertainties for the given bin index.
1091    double GetBinUncertainty(int binidx) const final { return this->GetStat().GetBinUncertainty(binidx); }
1092 
1093    /// Get the bin uncertainty for the bin at coordinate `x`.
1094    double GetBinUncertainty(const CoordArray_t &x) const final
1095    {
1096       const int bin = GetBinIndex(x);
1097       return this->GetBinUncertainty(bin);
1098    }
1099 
1100    /// Whether this histogram's statistics provide storage for uncertainties, or
1101    /// whether uncertainties are determined as poisson uncertainty of the content.
1102    bool HasBinUncertainty() const final { return this->GetStat().HasBinUncertainty(); }
1103 
1104    /// Get the begin() and end() for each axis.
1105    AxisIterRange_t<DATA::GetNDim()>
1106    GetRange() const final
1107    {
1108       std::array<std::array<RAxisBase::const_iterator, DATA::GetNDim()>, 2> ret;
1109       Internal::RFillIterRange<DATA::GetNDim() - 1, decltype(fAxes)>()(ret, fAxes);
1110       return ret;
1111    }
1112 
1113    /// Grow the axis number `iAxis` to fit the coordinate `x`.
1114    ///
1115    /// The histogram (conceptually) combines pairs of bins along this axis until
1116    /// `x` is within the range of the axis.
1117    /// The axis must support growing for this to work (e.g. a `RAxisGrow`).
1118    void GrowAxis(int /*iAxis*/, double /*x*/)
1119    {
1120       // TODO: Implement GrowAxis()
1121    }
1122 
1123    /// \{
1124    /// \name Iterator interface
1125    using const_iterator = RHistBinIter<const ImplBase_t>;
1126    using iterator = RHistBinIter<ImplBase_t>;
1127    iterator begin() noexcept { return iterator(*this); }
1128    const_iterator begin() const noexcept { return const_iterator(*this); }
1129    iterator end() noexcept { return iterator(*this, this->GetNBinsNoOver()); }
1130    const_iterator end() const noexcept { return const_iterator(*this, this->GetNBinsNoOver()); }
1131    /// \}
1132 };
1133 
1134 template <class DATA, class... AXISCONFIG>
1135 RHistImpl<DATA, AXISCONFIG...>::RHistImpl(TRootIOCtor *)
1136 {}
1137 
1138 template <class DATA, class... AXISCONFIG>
1139 RHistImpl<DATA, AXISCONFIG...>::RHistImpl(AXISCONFIG... axisArgs)
1140    : ImplBase_t(Internal::GetNBinsNoOverFromAxes(axisArgs...), Internal::GetNOverflowBinsFromAxes(axisArgs...)), fAxes{axisArgs...}
1141 {}
1142 
1143 template <class DATA, class... AXISCONFIG>
1144 RHistImpl<DATA, AXISCONFIG...>::RHistImpl(std::string_view title, AXISCONFIG... axisArgs)
1145    : ImplBase_t(title, Internal::GetNBinsNoOverFromAxes(axisArgs...), Internal::GetNOverflowBinsFromAxes(axisArgs...)), fAxes{axisArgs...}
1146 {}
1147 
1148 #if 0
1149 // In principle we can also have a runtime version of RHistImpl, that does not
1150 // contain a tuple of concrete axis types but a vector of `RAxisConfig`.
1151 template <class DATA>
1152 class RHistImplRuntime: public RHistImplBase<DATA> {
1153 public:
1154   RHistImplRuntime(std::array<RAxisConfig, DATA::GetNDim()>&& axisCfg);
1155 };
1156 #endif
1157 
1158 } // namespace Detail
1159 
1160 } // namespace Experimental
1161 } // namespace ROOT
1162 
1163 #endif