Back to home page

EIC code displayed by LXR

 
 

    


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

0001 /// \file ROOT/RAxis.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_RAxis
0017 #define ROOT7_RAxis
0018 
0019 #include <algorithm>
0020 #include <cmath>
0021 #include <limits>
0022 #include <string>
0023 #include <unordered_map>
0024 #include <vector>
0025 
0026 #include "ROOT/RAxisConfig.hxx"
0027 #include <string_view>
0028 #include "ROOT/RLogger.hxx"
0029 
0030 namespace ROOT {
0031 namespace Experimental {
0032 
0033 /**
0034  \class RAxisBase
0035  Histogram axis base class. Keeps track of the number of bins and overflow
0036  handling. Offers bin iteration.
0037 
0038  Regular bin indices are starting from 1, up to N + 1 for an axis with N bins.
0039  Index -1 is for the underflow bin, representing values that are lower than
0040  the axis range. Index -2 is the overflow bin for values larger than the axis
0041  range.
0042  Growable axes do not have underflow or overflow bins, as they don't need them.
0043  */
0044 class RAxisBase {
0045 protected:
0046    ///\name Inaccessible copy, assignment
0047    /// The copy and move constructors and assignment operators are protected to
0048    /// prevent slicing.
0049    ///\{
0050    RAxisBase(const RAxisBase &) = default;
0051    RAxisBase(RAxisBase &&) = default;
0052    RAxisBase &operator=(const RAxisBase &) = default;
0053    RAxisBase &operator=(RAxisBase &&) = default;
0054    ///\}
0055 
0056    /// Default construct a RAxisBase (for use by derived classes for I/O)
0057    RAxisBase() noexcept(noexcept(std::string())) = default;
0058 
0059    /// Virtual destructor needed in this inheritance-based design
0060    virtual ~RAxisBase();
0061 
0062    /// Construct a RAxisBase.
0063    ///
0064    ///\param[in] title - axis title used for graphics and text representation.
0065    RAxisBase(std::string_view title) noexcept: fTitle(title) {}
0066 
0067    /// Given rawbin (`<0` for underflow, `>=GetNBinsNoOver()` for overflow),
0068    /// determine the bin number taking into account how over/underflow
0069    /// should be handled.
0070    ///
0071    /// \param[in] rawbin for which to determine the bin number.
0072    /// \return Returns the bin number adjusted for potential over- and underflow
0073    /// bins. Returns `kInvalidBin` if the axis cannot handle the over- / underflow.
0074    ///
0075    int AdjustOverflowBinNumber(double rawbin) const
0076    {
0077       ++rawbin;
0078 
0079       // Underflow: Put in underflow bin if any, otherwise ignore
0080       if (rawbin < GetFirstBin())
0081          return CanGrow() ? kInvalidBin : GetUnderflowBin();
0082 
0083       // Overflow: Put in overflow bin if any, otherwise ignore
0084       // `rawbin` is not an integer, cannot compare `rawbin > GetLastBin()`.
0085       if (rawbin >= GetLastBin() + 1)
0086          return CanGrow() ? kInvalidBin : GetOverflowBin();
0087 
0088       // Bin index is in range and has been corrected for over/underflow
0089       return (int)rawbin;
0090    }
0091 
0092    /// Check if two axis have the same bin borders
0093    ///
0094    /// Default implementation should work for any RAxis type, but is quite
0095    /// inefficient as it does virtual GetBinFrom calls in a loop. RAxis
0096    /// implementations are encouraged to provide optimized overrides for common
0097    /// axis binning comparison scenarios.
0098    virtual bool HasSameBinBordersAs(const RAxisBase& other) const {
0099       // Axis growability (and thus under/overflow bin existence) must match
0100       if (CanGrow() != other.CanGrow())
0101          return false;
0102 
0103       // Number of normal bins must match
0104       if (GetNBinsNoOver() != other.GetNBinsNoOver())
0105          return false;
0106 
0107       // Left borders of normal bins must match
0108       for (int bin: *this)
0109          if (GetBinFrom(bin) != other.GetBinFrom(bin))
0110             return false;
0111 
0112       // Right border of the last normal bin (aka maximum) must also match
0113       if (GetMaximum() != other.GetMaximum())
0114          return false;
0115 
0116       // If all of these checks passed, the two axes have the same bin borders
0117       return true;
0118    }
0119 
0120 public:
0121    /**
0122     \class const_iterator
0123     Random const_iterator through bins. Represents the bin index, not a bin
0124     content: the axis has no notion of any content.
0125     */
0126    class const_iterator {
0127       int fCursor = 0; ///< Current iteration position
0128 
0129    public:
0130       using iterator_category = std::random_access_iterator_tag;
0131       using value_type = int;
0132       using difference_type = int;
0133       using pointer = const int *;
0134       using reference = const int &;
0135 
0136       const_iterator() = default;
0137 
0138       /// Initialize a const_iterator with its position
0139       explicit const_iterator(int cursor) noexcept: fCursor(cursor) {}
0140 
0141       /// ++i
0142       const_iterator &operator++() noexcept
0143       {
0144          // Could check whether fCursor < fEnd - but what for?
0145          ++fCursor;
0146          return *this;
0147       }
0148 
0149       /// --i
0150       const_iterator &operator--() noexcept
0151       {
0152          // Could check whether fCursor > fBegin - but what for?
0153          --fCursor;
0154          return *this;
0155       }
0156 
0157       /// i++
0158       const_iterator operator++(int)noexcept
0159       {
0160          const_iterator old(*this);
0161          ++(*this);
0162          return old;
0163       }
0164 
0165       // i--
0166       const_iterator operator--(int)noexcept
0167       {
0168          const_iterator old(*this);
0169          --(*this);
0170          return old;
0171       }
0172 
0173       // i += 2
0174       const_iterator &operator+=(int d) noexcept
0175       {
0176          fCursor += d;
0177          return *this;
0178       }
0179 
0180       // i -= 2
0181       const_iterator &operator-=(int d) noexcept
0182       {
0183          fCursor -= d;
0184          return *this;
0185       }
0186 
0187       // i + 2
0188       const_iterator operator+(int d) noexcept
0189       {
0190          const_iterator ret(*this);
0191          ret += d;
0192          return ret;
0193       }
0194       friend const_iterator operator+(int d, const_iterator rhs) noexcept;
0195 
0196       // i - 2
0197       const_iterator operator-(int d) noexcept
0198       {
0199          const_iterator ret(*this);
0200          ret -= d;
0201          return ret;
0202       }
0203 
0204       // i - j
0205       int operator-(const const_iterator& j) noexcept
0206       {
0207          return fCursor - j.fCursor;
0208       }
0209 
0210       // i[2]
0211       int operator[](int d) noexcept
0212       {
0213          return fCursor + d;
0214       }
0215 
0216       // *i
0217       int operator*() const noexcept { return fCursor; }
0218 
0219       // i->
0220       const int *operator->() const noexcept { return &fCursor; }
0221 
0222       friend bool operator<(const_iterator lhs, const_iterator rhs) noexcept;
0223       friend bool operator>(const_iterator lhs, const_iterator rhs) noexcept;
0224       friend bool operator<=(const_iterator lhs, const_iterator rhs) noexcept;
0225       friend bool operator>=(const_iterator lhs, const_iterator rhs) noexcept;
0226       friend bool operator==(const_iterator lhs, const_iterator rhs) noexcept;
0227       friend bool operator!=(const_iterator lhs, const_iterator rhs) noexcept;
0228    };
0229 
0230    /// Special bin index returned to signify that no bin matches a request.
0231    constexpr static const int kInvalidBin = 0;
0232 
0233    /// Index of the underflow bin, if any.
0234    constexpr static const int kUnderflowBin = -1;
0235 
0236    /// Index of the overflow bin, if any.
0237    constexpr static const int kOverflowBin = -2;
0238 
0239    /// Get the axis's title
0240    const std::string &GetTitle() const { return fTitle; }
0241 
0242    /// Whether this axis can grow (and thus has no overflow bins).
0243    virtual bool CanGrow() const noexcept = 0;
0244 
0245    /// Get the number of bins, excluding under- and overflow.
0246    virtual int GetNBinsNoOver() const noexcept = 0;
0247 
0248    /// Get the number of bins, including under- and overflow.
0249    int GetNBins() const noexcept { return GetNBinsNoOver() + GetNOverflowBins(); }
0250 
0251    /// Get the number of over- and underflow bins: 0 for growable axes, 2 otherwise.
0252    int GetNOverflowBins() const noexcept
0253    {
0254       if (CanGrow())
0255          return 0;
0256       else
0257          return 2;
0258    };
0259 
0260    /// Get the bin index for the underflow bin (or `kInvalidBin`
0261    /// if CanGrow()).
0262    int GetUnderflowBin() const noexcept {
0263       if (CanGrow())
0264          return kInvalidBin;
0265       else
0266          return kUnderflowBin;
0267    }
0268 
0269    /// Get the bin index for the overflow bin (or `kInvalidBin`
0270    /// if CanGrow()).
0271    int GetOverflowBin() const noexcept {
0272       if (CanGrow())
0273          return kInvalidBin;
0274       else
0275          return kOverflowBin;
0276    }
0277 
0278    /// Get the bin index for the first bin of the axis
0279    int GetFirstBin() const noexcept { return 1; }
0280 
0281    /// Get the bin index for the last bin of the axis
0282    int GetLastBin() const noexcept { return GetNBinsNoOver(); }
0283 
0284    ///\name Iterator interfaces
0285    ///\{
0286 
0287    /// Get a const_iterator pointing to the first regular bin.
0288    const_iterator begin() const noexcept { return const_iterator{GetFirstBin()}; }
0289 
0290    /// Get a const_iterator pointing beyond the last regular bin
0291    const_iterator end() const noexcept { return const_iterator{GetLastBin() + 1}; }
0292    ///\}
0293 
0294    /// Find the adjusted bin index (returning `kUnderflowBin` for underflow and `kOverflowBin`
0295    /// for overflow) for the given coordinate.
0296    /// \note Passing a bin border coordinate can either return the bin above or
0297    /// below the bin border. I.e. don't do that for reliable results!
0298    virtual int FindBin(double x) const noexcept = 0;
0299 
0300    /// Get the bin center for the given bin index.
0301    /// The result of this method on an overflow or underflow bin is unspecified.
0302    virtual double GetBinCenter(int bin) const = 0;
0303 
0304    /// Get the low bin border ("left edge") for the given bin index.
0305    /// The result of this method on an underflow bin is unspecified.
0306    virtual double GetBinFrom(int bin) const = 0;
0307 
0308    /// Get the high bin border ("right edge") for the given bin index.
0309    /// The result of this method on an overflow bin is unspecified.
0310    double GetBinTo(int bin) const {
0311       const double result = (bin == kUnderflowBin) ? GetMinimum() : GetBinFrom(bin + 1);
0312       return result;
0313    }
0314 
0315    /// Get the low end of the axis range.
0316    double GetMinimum() const { return GetBinFrom(GetFirstBin()); }
0317 
0318    /// Get the high end of the axis range.
0319    double GetMaximum() const { return GetBinTo(GetLastBin()); }
0320 
0321    /// Check if two axes use the same binning convention, i.e.
0322    ///
0323    /// - Either they are both growable or neither of them is growable.
0324    /// - Minimum, maximum, and all bin borders in the middle are the same.
0325    /// - Bin labels must match (exactly including order, for now).
0326    bool HasSameBinningAs(const RAxisBase& other) const;
0327 
0328    /// If the coordinate `x` is within 10 ULPs of a bin low edge coordinate,
0329    /// return the bin for which this is a low edge. If it's not a bin edge,
0330    /// return `kInvalidBin`.
0331    virtual int GetBinIndexForLowEdge(double x) const noexcept = 0;
0332 
0333 private:
0334    std::string fTitle;    ///< Title of this axis, used for graphics / text.
0335 };
0336 
0337 ///\name RAxisBase::const_iterator external operators
0338 ///\{
0339 
0340 /// 2 + i
0341 inline RAxisBase::const_iterator operator+(int d, RAxisBase::const_iterator rhs) noexcept
0342 {
0343    return rhs + d;
0344 }
0345 
0346 /// i < j
0347 inline bool operator<(RAxisBase::const_iterator lhs, RAxisBase::const_iterator rhs) noexcept
0348 {
0349    return lhs.fCursor < rhs.fCursor;
0350 }
0351 
0352 /// i > j
0353 inline bool operator>(RAxisBase::const_iterator lhs, RAxisBase::const_iterator rhs) noexcept
0354 {
0355    return lhs.fCursor > rhs.fCursor;
0356 }
0357 
0358 /// i <= j
0359 inline bool operator<=(RAxisBase::const_iterator lhs, RAxisBase::const_iterator rhs) noexcept
0360 {
0361    return lhs.fCursor <= rhs.fCursor;
0362 }
0363 
0364 /// i >= j
0365 inline bool operator>=(RAxisBase::const_iterator lhs, RAxisBase::const_iterator rhs) noexcept
0366 {
0367    return lhs.fCursor >= rhs.fCursor;
0368 }
0369 
0370 /// i == j
0371 inline bool operator==(RAxisBase::const_iterator lhs, RAxisBase::const_iterator rhs) noexcept
0372 {
0373    return lhs.fCursor == rhs.fCursor;
0374 }
0375 
0376 /// i != j
0377 inline bool operator!=(RAxisBase::const_iterator lhs, RAxisBase::const_iterator rhs) noexcept
0378 {
0379    return lhs.fCursor != rhs.fCursor;
0380 }
0381 ///\}
0382 
0383 /**
0384  Axis with equidistant bin borders. Defined by lower l and upper u limit and
0385  the number of bins n. All bins have the same width (u-l)/n.
0386 
0387  This axis cannot grow; use `RAxisGrow` for that.
0388  */
0389 class RAxisEquidistant: public RAxisBase {
0390 protected:
0391    double fLow = 0.;          ///< The lower limit of the axis
0392    double fInvBinWidth = 0.;  ///< The inverse of the bin width
0393    unsigned int fNBinsNoOver; ///< Number of bins excluding under- and overflow.
0394 
0395    /// Determine the inverse bin width.
0396    /// \param nbinsNoOver - number of bins without unter-/overflow
0397    /// \param lowOrHigh - first axis boundary
0398    /// \param highOrLow - second axis boundary
0399    static double GetInvBinWidth(int nbinsNoOver, double lowOrHigh, double highOrLow)
0400    {
0401       return nbinsNoOver / std::fabs(highOrLow - lowOrHigh);
0402    }
0403 
0404    /// See RAxisBase::HasSameBinBordersAs
0405    bool HasSameBinBordersAs(const RAxisBase& other) const override;
0406 
0407    /// Find the raw bin index (not adjusted) for the given coordinate.
0408    /// The resulting raw bin is 0-based.
0409    /// \note Passing a bin border coordinate can either return the bin above or
0410    /// below the bin border. I.e. don't do that for reliable results!
0411    double FindBinRaw(double x) const noexcept
0412    {
0413       return (x - fLow) * fInvBinWidth;
0414    }
0415 
0416 public:
0417    RAxisEquidistant() = default;
0418 
0419    /// Initialize a RAxisEquidistant.
0420    /// \param[in] title - axis title used for graphics and text representation.
0421    /// \param nbinsNoOver - number of bins in the axis, excluding under- and overflow
0422    ///   bins.
0423    /// \param low - the low axis range. Any coordinate below that is considered
0424    ///   as underflow. The first bin's lower edge is at this value.
0425    /// \param high - the high axis range. Any coordinate above that is considered
0426    ///   as overflow. The last bin's higher edge is at this value.
0427    explicit RAxisEquidistant(std::string_view title, int nbinsNoOver, double low, double high) noexcept
0428       : RAxisBase(title)
0429       , fLow(low)
0430       , fInvBinWidth(GetInvBinWidth(nbinsNoOver, low, high))
0431       , fNBinsNoOver(nbinsNoOver)
0432    {}
0433 
0434    /// Initialize a RAxisEquidistant.
0435    /// \param nbinsNoOver - number of bins in the axis, excluding under- and overflow
0436    ///   bins.
0437    /// \param low - the low axis range. Any coordinate below that is considered
0438    ///   as underflow. The first bin's lower edge is at this value.
0439    /// \param high - the high axis range. Any coordinate above that is considered
0440    ///   as overflow. The last bin's higher edge is at this value.
0441    explicit RAxisEquidistant(int nbinsNoOver, double low, double high) noexcept
0442       : RAxisEquidistant("", nbinsNoOver, low, high)
0443    {}
0444 
0445    /// Convert to RAxisConfig.
0446    operator RAxisConfig() const { return RAxisConfig(GetTitle(), GetNBinsNoOver(), GetMinimum(), GetMaximum()); }
0447 
0448    /// Get the number of bins, excluding under- and overflow.
0449    int GetNBinsNoOver() const noexcept final { return fNBinsNoOver; }
0450 
0451    /// Find the adjusted bin index (returning `kUnderflowBin` for underflow and
0452    /// `kOverflowBin` for overflow) for the given coordinate.
0453    /// \note Passing a bin border coordinate can either return the bin above or
0454    /// below the bin border. I.e. don't do that for reliable results!
0455    int FindBin(double x) const noexcept final 
0456    {
0457       double rawbin = FindBinRaw(x);
0458       return AdjustOverflowBinNumber(rawbin);
0459    }
0460 
0461    /// This axis cannot grow.
0462    bool CanGrow() const noexcept override { return false; }
0463 
0464    /// Get the width of the bins.
0465    double GetBinWidth() const noexcept { return 1. / fInvBinWidth; }
0466 
0467    /// Get the inverse of the width of the bins.
0468    double GetInverseBinWidth() const noexcept { return fInvBinWidth; }
0469 
0470    /// Get the bin center for the given bin index.
0471    /// For the bin == 1 (the first bin) of 2 bins for an axis (0., 1.), this
0472    /// returns 0.25.
0473    /// The result of this method on an overflow or underflow bin is unspecified.
0474    double GetBinCenter(int bin) const final { return fLow + (bin - GetFirstBin() + 0.5) / fInvBinWidth; }
0475 
0476    /// Get the low bin border for the given bin index.
0477    /// For the bin == 1 (the first bin) of 2 bins for an axis (0., 1.), this
0478    /// returns 0.
0479    /// The result of this method on an underflow bin is unspecified.
0480    double GetBinFrom(int bin) const final {
0481       const double result = (bin == kOverflowBin) ? GetMaximum() : fLow + (bin - GetFirstBin()) / fInvBinWidth;
0482       return result;
0483    }
0484 
0485    /// If the coordinate `x` is within 10 ULPs of a bin low edge coordinate,
0486    /// return the bin for which this is a low edge. If it's not a bin edge,
0487    /// return `kInvalidBin`.
0488    int GetBinIndexForLowEdge(double x) const noexcept final ;
0489 };
0490 
0491 namespace Internal {
0492 
0493 template <>
0494 struct AxisConfigToType<RAxisConfig::kEquidistant> {
0495    using Axis_t = RAxisEquidistant;
0496 
0497    Axis_t operator()(const RAxisConfig &cfg) noexcept
0498    {
0499       return RAxisEquidistant(cfg.GetTitle(), cfg.GetNBinsNoOver(), cfg.GetBinBorders()[0], cfg.GetBinBorders()[1]);
0500    }
0501 };
0502 
0503 } // namespace Internal
0504 
0505 /** An axis that can extend its range, keeping the number of its bins unchanged.
0506  The axis is constructed with an initial range. Apart from its ability to
0507  grow, this axis behaves like a RAxisEquidistant.
0508  */
0509 class RAxisGrow: public RAxisEquidistant {
0510 public:
0511    /// Initialize a RAxisGrow.
0512    /// \param[in] title - axis title used for graphics and text representation.
0513    /// \param nbins - number of bins in the axis, excluding under- and overflow
0514    ///   bins. This value is fixed over the lifetime of the object.
0515    /// \param low - the initial value for the low axis range. Any coordinate
0516    ///   below that is considered as underflow. To trigger the growing of the
0517    ///   axis call `Grow()`.
0518    /// \param high - the initial value for the high axis range. Any coordinate
0519    ///   above that is considered as overflow. To trigger the growing of the
0520    ///   axis call `Grow()`.
0521    explicit RAxisGrow(std::string_view title, int nbins, double low, double high) noexcept
0522       : RAxisEquidistant(title, nbins, low, high)
0523    {}
0524 
0525    /// Initialize a RAxisGrow.
0526    /// \param nbins - number of bins in the axis, excluding under- and overflow
0527    ///   bins. This value is fixed over the lifetime of the object.
0528    /// \param low - the initial value for the low axis range. Any coordinate
0529    ///   below that is considered as underflow. To trigger the growing of the
0530    ///   axis call `Grow()`.
0531    /// \param high - the initial value for the high axis range. Any coordinate
0532    ///   above that is considered as overflow. To trigger the growing of the
0533    ///   axis call `Grow()`.
0534    explicit RAxisGrow(int nbins, double low, double high) noexcept: RAxisGrow("", nbins, low, high) {}
0535 
0536    /// Convert to RAxisConfig.
0537    operator RAxisConfig() const { return RAxisConfig(GetTitle(), RAxisConfig::Grow, GetNBinsNoOver(), GetMinimum(), GetMaximum()); }
0538 
0539    /// Grow this axis to make the "virtual bin" toBin in-range. This keeps the
0540    /// non-affected axis limit unchanged, and extends the other axis limit such
0541    /// that a number of consecutive bins are merged.
0542    ///
0543    /// Example, assuming an initial RAxisGrow with 10 bins from 0. to 1.:
0544    ///   - `Grow(0)`: that (virtual) bin spans from -0.1 to 0. To include it
0545    ///     in the axis range, the lower limit must be shifted. The minimal number
0546    ///     of bins that can be merged is 2, thus the new axis will span from
0547    ///     -1. to 1.
0548    ///   - `Grow(-1)`: that (virtual) bin spans from -0.2 to 0.1. To include it
0549    ///     in the axis range, the lower limit must be shifted. The minimal number
0550    ///     of bins that can be merged is 2, thus the new axis will span from
0551    ///     -1. to 1.
0552    ///   - `Grow(50)`: that (virtual) bin spans from 4.9 to 5.0. To include it
0553    ///     in the axis range, the higher limit must be shifted. Five bins need to
0554    ///     be merged, making the new axis range 0. to 5.0.
0555    ///
0556    /// \param toBin - the "virtual" bin number, as if the axis had an infinite
0557    ///   number of bins with the current bin width. For instance, for an axis
0558    ///   with ten bins in the range 0. to 1., the coordinate 2.05 has the virtual
0559    ///   bin index 20.
0560    /// \return Returns the number of bins that were merged to reach the value.
0561    ///   A value of 1 means that no bins were merged (toBin was in the original
0562    ///   axis range).
0563    int Grow(int toBin);
0564 
0565    /// This axis kind can increase its range.
0566    bool CanGrow() const noexcept final { return true; }
0567 };
0568 
0569 namespace Internal {
0570 
0571 template <>
0572 struct AxisConfigToType<RAxisConfig::kGrow> {
0573    using Axis_t = RAxisGrow;
0574 
0575    Axis_t operator()(const RAxisConfig &cfg) noexcept
0576    {
0577       return RAxisGrow(cfg.GetTitle(), cfg.GetNBinsNoOver(), cfg.GetBinBorders()[0], cfg.GetBinBorders()[1]);
0578    }
0579 };
0580 
0581 } // namespace Internal
0582 
0583 /**
0584  An axis with non-equidistant bins (also known as "variable binning"). It is
0585  defined by an array of bin borders - one more than the number of
0586  (non-overflow-) bins it has! As an example, an axis with two bin needs three
0587  bin borders:
0588    - lower edge of the first bin;
0589    - higher edge of the first bin, identical to the lower edge of the second
0590      bin;
0591    - higher edge of the second bin
0592 
0593  This axis cannot grow; the size of new bins would not be well defined.
0594  */
0595 class RAxisIrregular: public RAxisBase {
0596 private:
0597    /// Bin borders, one more than the number of regular bins.
0598    std::vector<double> fBinBorders;
0599 
0600 protected:
0601    /// See RAxisBase::HasSameBinBordersAs
0602    bool HasSameBinBordersAs(const RAxisBase& other) const override;
0603 
0604    /// Find the raw bin index (not adjusted) for the given coordinate `x`.
0605    /// The resulting raw bin is 1-based.
0606    /// \note Passing a bin border coordinate can either return the bin above or
0607    /// below the bin border. I.e. don't do that for reliable results!
0608    double FindBinRaw(double x) const noexcept
0609    {
0610       const auto bBegin = fBinBorders.begin();
0611       const auto bEnd = fBinBorders.end();
0612       // lower_bound finds the first bin border that is >= x.
0613       auto iNotLess = std::lower_bound(bBegin, bEnd, x);
0614       return iNotLess - bBegin;
0615    }
0616 
0617 public:
0618    RAxisIrregular() = default;
0619 
0620    /// Construct a RAxisIrregular from a vector of bin borders.
0621    /// \note The bin borders must be sorted in increasing order!
0622    explicit RAxisIrregular(const std::vector<double> &binborders)
0623       : RAxisBase(), fBinBorders(binborders)
0624    {
0625 #ifdef R__DO_RANGE_CHECKS
0626       if (!std::is_sorted(fBinBorders.begin(), fBinBorders.end()))
0627          R__LOG_ERROR("HIST") << "Bin borders must be sorted!";
0628 #endif // R__DO_RANGE_CHECKS
0629    }
0630 
0631    /// Construct a RAxisIrregular from a vector of bin borders.
0632    /// \note The bin borders must be sorted in increasing order!
0633    /// Faster, noexcept version taking an rvalue of binborders. The compiler will
0634    /// know when it can take this one.
0635    explicit RAxisIrregular(std::vector<double> &&binborders) noexcept
0636       : RAxisBase(), fBinBorders(std::move(binborders))
0637    {
0638 #ifdef R__DO_RANGE_CHECKS
0639       if (!std::is_sorted(fBinBorders.begin(), fBinBorders.end()))
0640          R__LOG_ERROR("HIST") << "Bin borders must be sorted!";
0641 #endif // R__DO_RANGE_CHECKS
0642    }
0643 
0644    /// Construct a RAxisIrregular from a vector of bin borders.
0645    /// \note The bin borders must be sorted in increasing order!
0646    explicit RAxisIrregular(std::string_view title, const std::vector<double> &binborders)
0647       : RAxisBase(title), fBinBorders(binborders)
0648    {
0649 #ifdef R__DO_RANGE_CHECKS
0650       if (!std::is_sorted(fBinBorders.begin(), fBinBorders.end()))
0651          R__LOG_ERROR("HIST") << "Bin borders must be sorted!";
0652 #endif // R__DO_RANGE_CHECKS
0653    }
0654 
0655    /// Construct a RAxisIrregular from a vector of bin borders.
0656    /// \note The bin borders must be sorted in increasing order!
0657    /// Faster, noexcept version taking an rvalue of binborders. The compiler will
0658    /// know when it can take this one.
0659    explicit RAxisIrregular(std::string_view title, std::vector<double> &&binborders) noexcept
0660       : RAxisBase(title), fBinBorders(std::move(binborders))
0661    {
0662 #ifdef R__DO_RANGE_CHECKS
0663       if (!std::is_sorted(fBinBorders.begin(), fBinBorders.end()))
0664          R__LOG_ERROR("HIST") << "Bin borders must be sorted!";
0665 #endif // R__DO_RANGE_CHECKS
0666    }
0667 
0668    /// Convert to RAxisConfig.
0669    operator RAxisConfig() const { return RAxisConfig(GetTitle(), GetBinBorders()); }
0670 
0671    /// Get the number of bins, excluding under- and overflow.
0672    int GetNBinsNoOver() const noexcept final { return fBinBorders.size() - 1; }
0673 
0674    /// Find the bin index (adjusted with under- and overflow) for the given coordinate `x`.
0675    /// \note Passing a bin border coordinate can either return the bin above or
0676    /// below the bin border. I.e. don't do that for reliable results!
0677    int FindBin(double x) const noexcept final 
0678    {
0679       int rawbin = FindBinRaw(x);
0680       // No need for AdjustOverflowBinNumber(rawbin) here; lower_bound() is the
0681       // answer: e.g. for x < *bBegin, rawbin is -1.
0682       if (rawbin < GetFirstBin())
0683          return kUnderflowBin;
0684       if (rawbin >= GetLastBin() + 1)
0685          return kOverflowBin;
0686       return rawbin;
0687    }
0688 
0689    /// Get the bin center of the bin with the given index.
0690    /// The result of this method on an overflow or underflow bin is unspecified.
0691    double GetBinCenter(int bin) const final { return 0.5 * (fBinBorders[bin - 1] + fBinBorders[bin]); }
0692 
0693    /// Get the lower bin border for a given bin index.
0694    /// The result of this method on an underflow bin is unspecified.
0695    double GetBinFrom(int bin) const final 
0696    {
0697       if (bin == kOverflowBin)
0698          return fBinBorders[GetLastBin()];
0699       return fBinBorders[bin - 1];
0700    }
0701 
0702    /// If the coordinate `x` is within 10 ULPs of a bin low edge coordinate,
0703    /// return the bin for which this is a low edge. If it's not a bin edge,
0704    /// return `kInvalidBin`.
0705    int GetBinIndexForLowEdge(double x) const noexcept final ;
0706 
0707    /// This axis cannot be extended.
0708    bool CanGrow() const noexcept final { return false; }
0709 
0710    /// Access to the bin borders used by this axis.
0711    const std::vector<double> &GetBinBorders() const noexcept { return fBinBorders; }
0712 };
0713 
0714 namespace Internal {
0715 
0716 template <>
0717 struct AxisConfigToType<RAxisConfig::kIrregular> {
0718    using Axis_t = RAxisIrregular;
0719 
0720    Axis_t operator()(const RAxisConfig &cfg) { return RAxisIrregular(cfg.GetTitle(), cfg.GetBinBorders()); }
0721 };
0722 
0723 } // namespace Internal
0724 
0725 /**
0726  \class RAxisLabels
0727  A RAxisGrow that has a label assigned to each bin and a bin width of 1.
0728 
0729  While filling still works through coordinates (i.e. arrays of doubles),
0730  RAxisLabels allows to convert a string to a bin number or the bin's coordinate
0731  center. The number of labels and the number of bins reported by RAxisGrow might
0732  differ: the RAxisGrow will only grow when seeing a Fill(), while the RAxisLabels
0733  will add a new label whenever `GetBinCenter()` is called.
0734 
0735  Implementation details:
0736  Filling happens often; `GetBinCenter()` needs to be fast. Thus the unordered_map.
0737  The painter needs the reverse: it wants the label for bin 0, bin 1 etc. The axis
0738  should only store the bin labels once; referencing them is (due to re-allocation,
0739  hashing etc) non-trivial. So instead, build a `vector<string_view>` for the few
0740  times the axis needs to be painted.
0741  */
0742 class RAxisLabels: public RAxisGrow {
0743 private:
0744    /// Map of label (view on `fLabels`'s elements) to bin index
0745    std::unordered_map<std::string, int /*bin number*/> fLabelsIndex;
0746 
0747 public:
0748    /// Construct a RAxisLables from a `vector` of `string_view`s, with title.
0749    explicit RAxisLabels(std::string_view title, const std::vector<std::string_view> &labels)
0750       : RAxisGrow(title, labels.size(), 0., static_cast<double>(labels.size()))
0751    {
0752       for (size_t i = 0, n = labels.size(); i < n; ++i)
0753          fLabelsIndex[std::string(labels[i])] = i;
0754    }
0755 
0756    /// Construct a RAxisLables from a `vector` of `string`s, with title.
0757    explicit RAxisLabels(std::string_view title, const std::vector<std::string> &labels)
0758       : RAxisGrow(title, labels.size(), 0., static_cast<double>(labels.size()))
0759    {
0760       for (size_t i = 0, n = labels.size(); i < n; ++i)
0761          fLabelsIndex[labels[i]] = i;
0762    }
0763 
0764    /// Construct a RAxisLables from a `vector` of `string_view`s
0765    explicit RAxisLabels(const std::vector<std::string_view> &labels): RAxisLabels("", labels) {}
0766 
0767    /// Construct a RAxisLables from a `vector` of `string`s
0768    explicit RAxisLabels(const std::vector<std::string> &labels): RAxisLabels("", labels) {}
0769 
0770    /// Convert to RAxisConfig.
0771    operator RAxisConfig() const { return RAxisConfig(GetTitle(), GetBinLabels()); }
0772 
0773    /// Get the bin index with label.
0774    int FindBinByName(const std::string &label)
0775    {
0776       auto insertResult = fLabelsIndex.insert({label, -1});
0777       if (insertResult.second) {
0778          // we have created a new label
0779          int idx = fLabelsIndex.size() - 1;
0780          insertResult.first->second = idx;
0781          return idx;
0782       }
0783       return insertResult.first->second;
0784    }
0785 
0786    /// Get the center of the bin with label.
0787    double GetBinCenterByName(const std::string &label)
0788    {
0789       return FindBinByName(label) + 0.5; // bin *center*
0790    }
0791 
0792    /// Build a vector of labels. The position in the vector defines the label's bin.
0793    std::vector<std::string_view> GetBinLabels() const
0794    {
0795       std::vector<std::string_view> vec(fLabelsIndex.size());
0796       for (const auto &kv: fLabelsIndex)
0797          vec.at(kv.second) = kv.first;
0798       return vec;
0799    }
0800 
0801    /// Result of an RAxisLabels label set comparison
0802    enum LabelsCmpFlags {
0803       /// Both axes have the same labels, mapping to the same bins
0804       kLabelsCmpSame = 0,
0805 
0806       /// The other axis doesn't have some labels from this axis
0807       kLabelsCmpSubset = 0b1,
0808 
0809       /// The other axis has some labels which this axis doesn't have
0810       kLabelsCmpSuperset = 0b10,
0811 
0812       /// The labels shared by both axes do not map into the same bins
0813       kLabelsCmpDisordered = 0b100,
0814    };
0815 
0816    /// Compare the labels of this axis with those of another axis
0817    LabelsCmpFlags CompareBinLabels(const RAxisLabels& other) const noexcept {
0818       // This will eventually contain the results of the labels comparison
0819       LabelsCmpFlags result = kLabelsCmpSame;
0820       size_t missing_in_other = 0;
0821 
0822       // First, check how this axis' labels map into the other axis
0823       for (const auto &kv: fLabelsIndex) {
0824          auto iter = other.fLabelsIndex.find(kv.first);
0825          if (iter == other.fLabelsIndex.cend()) {
0826             ++missing_in_other;
0827          } else if (iter->second != kv.second) {
0828             result = LabelsCmpFlags(result | kLabelsCmpDisordered);
0829          }
0830       }
0831       if (missing_in_other > 0)
0832          result = LabelsCmpFlags(result | kLabelsCmpSubset);
0833 
0834       // If this covered all labels in the other axis, we're done
0835       if (fLabelsIndex.size() == other.fLabelsIndex.size() + missing_in_other)
0836          return result;
0837 
0838       // Otherwise, we must check the labels of the other axis too
0839       for (const auto &kv: other.fLabelsIndex)
0840          if (fLabelsIndex.find(kv.first) == fLabelsIndex.cend())
0841             return LabelsCmpFlags(result | kLabelsCmpSuperset);
0842       return result;
0843    }
0844 };
0845 
0846 namespace Internal {
0847 
0848 template <>
0849 struct AxisConfigToType<RAxisConfig::kLabels> {
0850    using Axis_t = RAxisLabels;
0851 
0852    Axis_t operator()(const RAxisConfig &cfg) { return RAxisLabels(cfg.GetTitle(), cfg.GetBinLabels()); }
0853 };
0854 
0855 } // namespace Internal
0856 
0857 ///\name Axis Compatibility
0858 ///\{
0859 enum class EAxisCompatibility {
0860    kIdentical, ///< Source and target axes are identical
0861 
0862    kContains, ///< The source is a subset of bins of the target axis
0863 
0864    /// The bins of the source axis have finer granularity, but the bin borders
0865    /// are compatible. Example:
0866    /// source: 0., 1., 2., 3., 4., 5., 6.; target: 0., 2., 5., 6.
0867    /// Note that this is *not* a symmetrical property: only one of
0868    /// CanMerge(source, target), CanMap(target, source) can return kContains.
0869    kSampling,
0870 
0871    /// The source axis and target axis have different binning. Example:
0872    /// source: 0., 1., 2., 3., 4., target: 0., 0.1, 0.2, 0.3, 0.4
0873    kIncompatible
0874 };
0875 
0876 /// Whether (and how) the source axis can be merged into the target axis.
0877 EAxisCompatibility CanMap(const RAxisEquidistant &target, const RAxisEquidistant &source) noexcept;
0878 ///\}
0879 
0880 } // namespace Experimental
0881 } // namespace ROOT
0882 
0883 #endif // ROOT7_RAxis header guard