Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-03-30 07:45:48

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #pragma once
0010 
0011 #include "Acts/Utilities/AxisDefinitions.hpp"
0012 #include "Acts/Utilities/IAxis.hpp"
0013 
0014 #include <algorithm>
0015 #include <cmath>
0016 #include <vector>
0017 
0018 namespace Acts {
0019 
0020 // This object can be iterated to produce up to two sequences of integer
0021 // indices, corresponding to the half-open integer ranges [begin1, end1[ and
0022 // [begin2, end2[.
0023 //
0024 // The goal is to emulate the effect of enumerating a range of neighbor
0025 // indices on an axis (which may go out of bounds and wrap around since we
0026 // have AxisBoundaryType::Closed), inserting them into an std::vector, and
0027 // discarding duplicates, without paying the price of duplicate removal
0028 // and dynamic memory allocation in hot magnetic field interpolation code.
0029 //
0030 /// Iterable indices for neighborhood lookups with optional wrap-around.
0031 class NeighborHoodIndices {
0032  public:
0033   NeighborHoodIndices() = default;
0034 
0035   /// Constructor for continuous range
0036   /// @param begin Start index
0037   /// @param end End index (exclusive)
0038   NeighborHoodIndices(std::size_t begin, std::size_t end)
0039       : m_begin1(begin), m_end1(end), m_begin2(end), m_end2(end) {}
0040 
0041   /// Constructor for wrapped range (two segments)
0042   /// @param begin1 Start of first segment
0043   /// @param end1 End of first segment (exclusive)
0044   /// @param begin2 Start of second segment
0045   /// @param end2 End of second segment (exclusive)
0046   NeighborHoodIndices(std::size_t begin1, std::size_t end1, std::size_t begin2,
0047                       std::size_t end2)
0048       : m_begin1(begin1), m_end1(end1), m_begin2(begin2), m_end2(end2) {}
0049 
0050   /// Iterator over the neighborhood index sequence.
0051   class iterator {
0052    public:
0053     iterator() = default;
0054 
0055     /// Constructor for end iterator
0056     /// @param current End position
0057     explicit iterator(std::size_t current)
0058         : m_current(current), m_wrapped(true) {}
0059 
0060     /// Constructor for begin iterator
0061     /// @param begin1 Start of first segment
0062     /// @param end1 End of first segment
0063     /// @param begin2 Start of second segment
0064     iterator(std::size_t begin1, std::size_t end1, std::size_t begin2)
0065         : m_current(begin1),
0066           m_end1(end1),
0067           m_begin2(begin2),
0068           m_wrapped(begin1 == begin2) {}
0069 
0070     /// Dereference operator
0071     /// @return Current index
0072     std::size_t operator*() const { return m_current; }
0073 
0074     /// Pre-increment operator
0075     /// @return Reference to this iterator
0076     iterator& operator++() {
0077       ++m_current;
0078       if (m_current == m_end1) {
0079         m_current = m_begin2;
0080         m_wrapped = true;
0081       }
0082       return *this;
0083     }
0084 
0085     /// Equality comparison operator
0086     /// @param it Other iterator
0087     /// @return True if iterators are equal
0088     bool operator==(const iterator& it) const {
0089       return (m_current == it.m_current) && (m_wrapped == it.m_wrapped);
0090     }
0091 
0092    private:
0093     std::size_t m_current = 0, m_end1 = 0, m_begin2 = 0;
0094     bool m_wrapped = false;
0095   };
0096 
0097   /// Get begin iterator
0098   /// @return Iterator to first index
0099   iterator begin() const { return iterator(m_begin1, m_end1, m_begin2); }
0100 
0101   /// Get end iterator
0102   /// @return Iterator past last index
0103   iterator end() const { return iterator(m_end2); }
0104 
0105   /// Get total number of indices in the sequence
0106   /// @return Number of indices
0107   std::size_t size() const { return (m_end1 - m_begin1) + (m_end2 - m_begin2); }
0108 
0109   /// Collect all indices into a vector
0110   /// @return Vector containing all indices
0111   std::vector<std::size_t> collect() const {
0112     std::vector<std::size_t> result;
0113     result.reserve(this->size());
0114     for (std::size_t idx : *this) {
0115       result.push_back(idx);
0116     }
0117     return result;
0118   }
0119 
0120  private:
0121   std::size_t m_begin1 = 0, m_end1 = 0, m_begin2 = 0, m_end2 = 0;
0122 };
0123 
0124 /// @brief calculate bin indices for an equidistant binning
0125 ///
0126 /// This class provides some basic functionality for calculating bin indices
0127 /// for a given equidistant binning.
0128 template <AxisBoundaryType bdt>
0129 class Axis<AxisType::Equidistant, bdt> : public IAxis {
0130  public:
0131   /// Static type identifier for this equidistant axis specialization
0132   static constexpr AxisType type = AxisType::Equidistant;
0133 
0134   /// @brief default constructor
0135   ///
0136   /// @param [in] xmin lower boundary of axis range
0137   /// @param [in] xmax upper boundary of axis range
0138   /// @param [in] nBins number of bins to divide the axis range into
0139   ///
0140   /// Divide the range \f$[\text{xmin},\text{xmax})\f$ into \f$\text{nBins}\f$
0141   /// equidistant bins.
0142   Axis(double xmin, double xmax, std::size_t nBins)
0143       : m_min(xmin),
0144         m_max(xmax),
0145         m_width((xmax - xmin) / nBins),
0146         m_bins(nBins) {}
0147 
0148   /// Constructor with a tag for the boundary type
0149   ///
0150   /// @param [in] typeTag boundary type tag
0151   /// @param [in] xmin lower boundary of axis range
0152   /// @param [in] xmax upper boundary of axis range
0153   /// @param [in] nBins number of bins to divide the axis range into
0154   ///
0155   /// Divide the range \f$[\text{xmin},\text{xmax})\f$ into \f$\text{nBins}\f$
0156   /// equidistant bins.
0157   Axis(AxisBoundaryTypeTag<bdt> typeTag, double xmin, double xmax,
0158        std::size_t nBins)
0159       : Axis(xmin, xmax, nBins) {
0160     static_cast<void>(typeTag);
0161   }
0162 
0163   /// @brief returns whether the axis is equidistant
0164   ///
0165   /// @return bool is equidistant
0166   bool isEquidistant() const final { return true; }
0167 
0168   /// @brief returns whether the axis is variable
0169   ///
0170   /// @return bool is variable
0171   bool isVariable() const final { return false; }
0172 
0173   /// @brief returns the type of the axis
0174   /// @return @c AxisType of this axis
0175   AxisType getType() const final { return type; }
0176 
0177   /// @brief returns the boundary type set in the template param
0178   ///
0179   /// @return @c AxisBoundaryType of this axis
0180   AxisBoundaryType getBoundaryType() const final { return bdt; }
0181 
0182   /// @brief Get #size bins which neighbor the one given
0183   ///
0184   /// Generic overload with symmetric size
0185   ///
0186   /// @param [in] idx requested bin index
0187   /// @param [in] size how many neighboring bins (up/down)
0188   /// @return Set of neighboring bin indices (global)
0189   NeighborHoodIndices neighborHoodIndices(std::size_t idx,
0190                                           std::size_t size = 1) const {
0191     return neighborHoodIndices(idx,
0192                                std::make_pair(-static_cast<int>(size), size));
0193   }
0194 
0195   /// @brief Get #size bins which neighbor the one given
0196   ///
0197   /// This is the version for Open
0198   ///
0199   /// @param [in] idx requested bin index
0200   /// @param [in] sizes how many neighboring bins (up/down)
0201   /// @return Set of neighboring bin indices (global)
0202   /// @note Open varies given bin and allows 0 and NBins+1 (underflow,
0203   /// overflow)
0204   ///       as neighbors
0205   NeighborHoodIndices neighborHoodIndices(std::size_t idx,
0206                                           std::pair<int, int> sizes = {-1,
0207                                                                        1}) const
0208     requires(bdt == AxisBoundaryType::Open)
0209   {
0210     constexpr int min = 0;
0211     const int max = getNBins() + 1;
0212     const int itmin = std::clamp(static_cast<int>(idx + sizes.first), min, max);
0213     const int itmax =
0214         std::clamp(static_cast<int>(idx + sizes.second), min, max);
0215     return NeighborHoodIndices(itmin, itmax + 1);
0216   }
0217 
0218   /// @brief Get #size bins which neighbor the one given
0219   ///
0220   /// This is the version for Bound
0221   ///
0222   /// @param [in] idx requested bin index
0223   /// @param [in] sizes how many neighboring bins (up/down)
0224   /// @return Set of neighboring bin indices (global)
0225   /// @note Bound varies given bin and allows 1 and NBins (regular bins)
0226   ///       as neighbors
0227   NeighborHoodIndices neighborHoodIndices(std::size_t idx,
0228                                           std::pair<int, int> sizes = {-1,
0229                                                                        1}) const
0230     requires(bdt == AxisBoundaryType::Bound)
0231   {
0232     if (idx <= 0 || idx >= (getNBins() + 1)) {
0233       return NeighborHoodIndices();
0234     }
0235     constexpr int min = 1;
0236     const int max = getNBins();
0237     const int itmin = std::clamp(static_cast<int>(idx) + sizes.first, min, max);
0238     const int itmax =
0239         std::clamp(static_cast<int>(idx) + sizes.second, min, max);
0240     return NeighborHoodIndices(itmin, itmax + 1);
0241   }
0242 
0243   /// @brief Get #size bins which neighbor the one given
0244   ///
0245   /// This is the version for Closed (i.e. Wrapping)
0246   ///
0247   /// @param [in] idx requested bin index
0248   /// @param [in] sizes how many neighboring bins (up/down)
0249   /// @return Set of neighboring bin indices (global)
0250   /// @note Closed varies given bin and allows bins on the opposite
0251   ///       side of the axis as neighbors. (excludes underflow / overflow)
0252   NeighborHoodIndices neighborHoodIndices(std::size_t idx,
0253                                           std::pair<int, int> sizes = {-1,
0254                                                                        1}) const
0255     requires(bdt == AxisBoundaryType::Closed)
0256   {
0257     // Handle invalid indices
0258     if (idx <= 0 || idx >= (getNBins() + 1)) {
0259       return NeighborHoodIndices();
0260     }
0261 
0262     // Handle corner case where user requests more neighbours than the number
0263     // of bins on the axis. All bins are returned in this case.
0264 
0265     const int max = getNBins();
0266     sizes.first = std::clamp(sizes.first, -max, max);
0267     sizes.second = std::clamp(sizes.second, -max, max);
0268     if (std::abs(sizes.first - sizes.second) >= max) {
0269       sizes.first = 1 - idx;
0270       sizes.second = max - idx;
0271     }
0272 
0273     // If the entire index range is not covered, we must wrap the range of
0274     // targeted neighbor indices into the range of valid bin indices. This may
0275     // split the range of neighbor indices in two parts:
0276     //
0277     // Before wraparound - [        XXXXX]XXX
0278     // After wraparound  - [ XXXX   XXXX ]
0279     //
0280     const int itmin = idx + sizes.first;
0281     const int itmax = idx + sizes.second;
0282     const std::size_t itfirst = wrapBin(itmin);
0283     const std::size_t itlast = wrapBin(itmax);
0284     if (itfirst <= itlast) {
0285       return NeighborHoodIndices(itfirst, itlast + 1);
0286     } else {
0287       return NeighborHoodIndices(itfirst, max + 1, 1, itlast + 1);
0288     }
0289   }
0290 
0291   /// @brief Converts bin index into a valid one for this axis.
0292   ///
0293   /// @note Open: bin index is clamped to [0, nBins+1]
0294   ///
0295   /// @param [in] bin The bin to wrap
0296   /// @return valid bin index
0297   std::size_t wrapBin(int bin) const
0298     requires(bdt == AxisBoundaryType::Open)
0299   {
0300     return std::max(std::min(bin, static_cast<int>(getNBins()) + 1), 0);
0301   }
0302 
0303   /// @brief Converts bin index into a valid one for this axis.
0304   ///
0305   /// @note Bound: bin index is clamped to [1, nBins]
0306   ///
0307   /// @param [in] bin The bin to wrap
0308   /// @return valid bin index
0309   std::size_t wrapBin(int bin) const
0310     requires(bdt == AxisBoundaryType::Bound)
0311   {
0312     return std::max(std::min(bin, static_cast<int>(getNBins())), 1);
0313   }
0314 
0315   /// @brief Converts bin index into a valid one for this axis.
0316   ///
0317   /// @note Closed: bin index wraps around to other side
0318   ///
0319   /// @param [in] bin The bin to wrap
0320   /// @return valid bin index
0321   std::size_t wrapBin(int bin) const
0322     requires(bdt == AxisBoundaryType::Closed)
0323   {
0324     const int w = getNBins();
0325     return 1 + (w + ((bin - 1) % w)) % w;
0326     // return int(bin<1)*w - int(bin>w)*w + bin;
0327   }
0328 
0329   /// @brief get corresponding bin index for given coordinate
0330   ///
0331   /// @param  [in] x input coordinate
0332   /// @return index of bin containing the given value
0333   ///
0334   /// @note Bin intervals are defined with closed lower bounds and open upper
0335   ///       bounds, that is \f$l <= x < u\f$ if the value @c x lies within a
0336   ///       bin with lower bound @c l and upper bound @c u.
0337   /// @note Bin indices start at @c 1. The underflow bin has the index @c 0
0338   ///       while the index <tt>nBins + 1</tt> indicates the overflow bin .
0339   std::size_t getBin(double x) const final {
0340     return wrapBin(
0341         static_cast<int>(std::floor((x - getMin()) / getBinWidth()) + 1));
0342   }
0343 
0344   /// @brief get bin width
0345   ///
0346   /// @return constant width for all bins
0347   double getBinWidth(std::size_t /*bin*/ = 0) const { return m_width; }
0348 
0349   /// @brief get lower bound of bin
0350   ///
0351   /// @param  [in] bin index of bin
0352   /// @return lower bin boundary
0353   ///
0354   /// @pre @c bin must be a valid bin index (excluding the underflow bin),
0355   ///      i.e. \f$1 \le \text{bin} \le \text{nBins} + 1\f$
0356   ///
0357   /// @note Bin intervals have a closed lower bound, i.e. the lower boundary
0358   ///       belongs to the bin with the given bin index.
0359   double getBinLowerBound(std::size_t bin) const {
0360     return getMin() + (bin - 1) * getBinWidth();
0361   }
0362 
0363   /// @brief get upper bound of bin
0364   ///
0365   /// @param  [in] bin index of bin
0366   /// @return upper bin boundary
0367   ///
0368   /// @pre @c bin must be a valid bin index (excluding the overflow bin),
0369   ///      i.e. \f$0 \le \text{bin} \le \text{nBins}\f$
0370   ///
0371   /// @note Bin intervals have an open upper bound, i.e. the upper boundary
0372   ///       does @b not belong to the bin with the given bin index.
0373   double getBinUpperBound(std::size_t bin) const {
0374     return getMin() + bin * getBinWidth();
0375   }
0376 
0377   /// @brief get bin center
0378   ///
0379   /// @param  [in] bin index of bin
0380   /// @return bin center position
0381   ///
0382   /// @pre @c bin must be a valid bin index (excluding under-/overflow bins),
0383   ///      i.e. \f$1 \le \text{bin} \le \text{nBins}\f$
0384   double getBinCenter(std::size_t bin) const {
0385     return getMin() + (bin - 0.5) * getBinWidth();
0386   }
0387 
0388   /// @brief get maximum of binning range
0389   ///
0390   /// @return maximum of binning range
0391   double getMax() const final { return m_max; }
0392 
0393   /// @brief get minimum of binning range
0394   ///
0395   /// @return minimum of binning range
0396   double getMin() const final { return m_min; }
0397 
0398   /// @brief get total number of bins
0399   ///
0400   /// @return total number of bins (excluding under-/overflow bins)
0401   std::size_t getNBins() const final { return m_bins; }
0402 
0403   /// @brief check whether value is inside axis limits
0404   /// @param x The value to check
0405   ///
0406   /// @return @c true if \f$\text{xmin} \le x < \text{xmax}\f$, otherwise
0407   ///         @c false
0408   ///
0409   /// @post If @c true is returned, the bin containing the given value is a
0410   ///       valid bin, i.e. it is neither the underflow nor the overflow bin.
0411   bool isInside(double x) const { return (m_min <= x) && (x < m_max); }
0412 
0413   /// @brief Return a vector of bin edges
0414   /// @return Vector which contains the bin edges
0415   std::vector<double> getBinEdges() const final {
0416     std::vector<double> binEdges;
0417     for (std::size_t i = 1; i <= m_bins; i++) {
0418       binEdges.push_back(getBinLowerBound(i));
0419     }
0420     binEdges.push_back(getBinUpperBound(m_bins));
0421     return binEdges;
0422   }
0423 
0424   friend std::ostream& operator<<(std::ostream& os, const Axis& axis) {
0425     os << "Axis<Equidistant, " << bdt << ">(";
0426     os << axis.m_min << ", ";
0427     os << axis.m_max << ", ";
0428     os << axis.m_bins << ")";
0429     return os;
0430   }
0431 
0432  protected:
0433   void toStream(std::ostream& os) const final { os << *this; }
0434 
0435  private:
0436   /// minimum of binning range
0437   double m_min{};
0438   /// maximum of binning range
0439   double m_max{};
0440   /// constant bin width
0441   double m_width{};
0442   /// number of bins (excluding under-/overflow bins)
0443   std::size_t m_bins{};
0444 };
0445 
0446 /// @brief calculate bin indices for a variable binning
0447 ///
0448 /// This class provides some basic functionality for calculating bin indices
0449 /// for a given binning with variable bin sizes.
0450 template <AxisBoundaryType bdt>
0451 class Axis<AxisType::Variable, bdt> : public IAxis {
0452  public:
0453   /// Static type identifier for this variable-width axis specialization
0454   static constexpr AxisType type = AxisType::Variable;
0455 
0456   /// @param [in] binEdges vector of bin edges
0457   /// @pre @c binEdges must be strictly sorted in ascending order.
0458   /// @pre @c binEdges must contain at least two entries.
0459   ///
0460   /// Create a binning structure with @c nBins variable-sized bins from the
0461   /// given bin boundaries. @c nBins is given by the number of bin edges
0462   /// reduced by one.
0463   explicit Axis(std::vector<double> binEdges)
0464       : m_binEdges(std::move(binEdges)) {}
0465 
0466   /// @param [in] typeTag boundary type tag
0467   /// @param [in] binEdges vector of bin edges
0468   /// @pre @c binEdges must be strictly sorted in ascending order.
0469   /// @pre @c binEdges must contain at least two entries.
0470   ///
0471   /// Create a binning structure with @c nBins variable-sized bins from the
0472   /// given bin boundaries. @c nBins is given by the number of bin edges
0473   /// reduced by one.
0474   Axis(AxisBoundaryTypeTag<bdt> typeTag, std::vector<double> binEdges)
0475       : Axis(std::move(binEdges)) {
0476     static_cast<void>(typeTag);
0477   }
0478 
0479   /// @brief returns whether the axis is equidistante
0480   ///
0481   /// @return bool is equidistant
0482   bool isEquidistant() const final { return false; }
0483 
0484   /// @brief returns whether the axis is variable
0485   ///
0486   /// @return bool is variable
0487   bool isVariable() const final { return true; }
0488 
0489   /// @brief returns the type of the axis
0490   /// @return @c AxisType of this axis
0491   AxisType getType() const final { return type; }
0492 
0493   /// @brief returns the boundary type set in the template param
0494   ///
0495   /// @return @c AxisBoundaryType of this axis
0496   AxisBoundaryType getBoundaryType() const final { return bdt; }
0497 
0498   /// @brief Get #size bins which neighbor the one given
0499   ///
0500   /// Generic overload with symmetric size
0501   ///
0502   /// @param [in] idx requested bin index
0503   /// @param [in] size how many neighboring bins
0504   /// @return Set of neighboring bin indices (global)
0505   NeighborHoodIndices neighborHoodIndices(std::size_t idx,
0506                                           std::size_t size = 1) const {
0507     return neighborHoodIndices(idx,
0508                                std::make_pair(-static_cast<int>(size), size));
0509   }
0510 
0511   /// @brief Get #size bins which neighbor the one given
0512   ///
0513   /// This is the version for Open
0514   ///
0515   /// @param [in] idx requested bin index
0516   /// @param [in] sizes how many neighboring bins (up/down)
0517   /// @return Set of neighboring bin indices (global)
0518   /// @note Open varies given bin and allows 0 and NBins+1 (underflow,
0519   /// overflow)
0520   ///       as neighbors
0521   NeighborHoodIndices neighborHoodIndices(std::size_t idx,
0522                                           std::pair<int, int> sizes = {-1,
0523                                                                        1}) const
0524     requires(bdt == AxisBoundaryType::Open)
0525   {
0526     constexpr int min = 0;
0527     const int max = getNBins() + 1;
0528     const int itmin = std::max(min, static_cast<int>(idx) + sizes.first);
0529     const int itmax = std::min(max, static_cast<int>(idx) + sizes.second);
0530     return NeighborHoodIndices(itmin, itmax + 1);
0531   }
0532 
0533   /// @brief Get #size bins which neighbor the one given
0534   ///
0535   /// This is the version for Bound
0536   ///
0537   /// @param [in] idx requested bin index
0538   /// @param [in] sizes how many neighboring bins (up/down)
0539   /// @return Set of neighboring bin indices (global)
0540   /// @note Bound varies given bin and allows 1 and NBins (regular bins)
0541   ///       as neighbors
0542   NeighborHoodIndices neighborHoodIndices(std::size_t idx,
0543                                           std::pair<int, int> sizes = {-1,
0544                                                                        1}) const
0545     requires(bdt == AxisBoundaryType::Bound)
0546   {
0547     if (idx <= 0 || idx >= (getNBins() + 1)) {
0548       return NeighborHoodIndices();
0549     }
0550     constexpr int min = 1;
0551     const int max = getNBins();
0552     const int itmin = std::max(min, static_cast<int>(idx) + sizes.first);
0553     const int itmax = std::min(max, static_cast<int>(idx) + sizes.second);
0554     return NeighborHoodIndices(itmin, itmax + 1);
0555   }
0556 
0557   /// @brief Get #size bins which neighbor the one given
0558   ///
0559   /// This is the version for Closed
0560   ///
0561   /// @param [in] idx requested bin index
0562   /// @param [in] sizes how many neighboring bins (up/down)
0563   /// @return Set of neighboring bin indices (global)
0564   /// @note Closed varies given bin and allows bins on the opposite
0565   ///       side of the axis as neighbors. (excludes underflow / overflow)
0566   NeighborHoodIndices neighborHoodIndices(std::size_t idx,
0567                                           std::pair<int, int> sizes = {-1,
0568                                                                        1}) const
0569     requires(bdt == AxisBoundaryType::Closed)
0570   {
0571     // Handle invalid indices
0572     if (idx <= 0 || idx >= (getNBins() + 1)) {
0573       return NeighborHoodIndices();
0574     }
0575 
0576     // Handle corner case where user requests more neighbours than the number
0577     // of bins on the axis. All bins are returned in this case
0578 
0579     const int max = getNBins();
0580     sizes.first = std::clamp(sizes.first, -max, max);
0581     sizes.second = std::clamp(sizes.second, -max, max);
0582     if (std::abs(sizes.first - sizes.second) >= max) {
0583       sizes.first = 1 - idx;
0584       sizes.second = max - idx;
0585     }
0586 
0587     // If the entire index range is not covered, we must wrap the range of
0588     // targeted neighbor indices into the range of valid bin indices. This may
0589     // split the range of neighbor indices in two parts:
0590     //
0591     // Before wraparound - [        XXXXX]XXX
0592     // After wraparound  - [ XXXX   XXXX ]
0593     //
0594     const int itmin = idx + sizes.first;
0595     const int itmax = idx + sizes.second;
0596     const std::size_t itfirst = wrapBin(itmin);
0597     const std::size_t itlast = wrapBin(itmax);
0598     if (itfirst <= itlast) {
0599       return NeighborHoodIndices(itfirst, itlast + 1);
0600     } else {
0601       return NeighborHoodIndices(itfirst, max + 1, 1, itlast + 1);
0602     }
0603   }
0604 
0605   /// @brief Converts bin index into a valid one for this axis.
0606   ///
0607   /// @note Open: bin index is clamped to [0, nBins+1]
0608   ///
0609   /// @param [in] bin The bin to wrap
0610   /// @return valid bin index
0611   std::size_t wrapBin(int bin) const
0612     requires(bdt == AxisBoundaryType::Open)
0613   {
0614     return std::max(std::min(bin, static_cast<int>(getNBins()) + 1), 0);
0615   }
0616 
0617   /// @brief Converts bin index into a valid one for this axis.
0618   ///
0619   /// @note Bound: bin index is clamped to [1, nBins]
0620   ///
0621   /// @param [in] bin The bin to wrap
0622   /// @return valid bin index
0623   std::size_t wrapBin(int bin) const
0624     requires(bdt == AxisBoundaryType::Bound)
0625   {
0626     return std::max(std::min(bin, static_cast<int>(getNBins())), 1);
0627   }
0628 
0629   /// @brief Converts bin index into a valid one for this axis.
0630   ///
0631   /// @note Closed: bin index wraps around to other side
0632   ///
0633   /// @param [in] bin The bin to wrap
0634   /// @return valid bin index
0635   std::size_t wrapBin(int bin) const
0636     requires(bdt == AxisBoundaryType::Closed)
0637   {
0638     const int w = getNBins();
0639     return 1 + (w + ((bin - 1) % w)) % w;
0640     // return int(bin<1)*w - int(bin>w)*w + bin;
0641   }
0642 
0643   /// @brief get corresponding bin index for given coordinate
0644   ///
0645   /// @param  [in] x input coordinate
0646   /// @return index of bin containing the given value
0647   ///
0648   /// @note Bin intervals are defined with closed lower bounds and open upper
0649   ///       bounds, that is \f$l <= x < u\f$ if the value @c x lies within a
0650   ///       bin with lower bound @c l and upper bound @c u.
0651   /// @note Bin indices start at @c 1. The underflow bin has the index @c 0
0652   ///       while the index <tt>nBins + 1</tt> indicates the overflow bin .
0653   std::size_t getBin(double x) const final {
0654     const auto it =
0655         std::upper_bound(std::begin(m_binEdges), std::end(m_binEdges), x);
0656     return wrapBin(std::distance(std::begin(m_binEdges), it));
0657   }
0658 
0659   /// @brief get bin width
0660   ///
0661   /// @param  [in] bin index of bin
0662   /// @return width of given bin
0663   ///
0664   /// @pre @c bin must be a valid bin index (excluding under-/overflow bins),
0665   ///      i.e. \f$1 \le \text{bin} \le \text{nBins}\f$
0666   double getBinWidth(std::size_t bin) const {
0667     return m_binEdges.at(bin) - m_binEdges.at(bin - 1);
0668   }
0669 
0670   /// @brief get lower bound of bin
0671   ///
0672   /// @param  [in] bin index of bin
0673   /// @return lower bin boundary
0674   ///
0675   /// @pre @c bin must be a valid bin index (excluding the underflow bin),
0676   ///      i.e. \f$1 \le \text{bin} \le \text{nBins} + 1\f$
0677   ///
0678   /// @note Bin intervals have a closed lower bound, i.e. the lower boundary
0679   ///       belongs to the bin with the given bin index.
0680   double getBinLowerBound(std::size_t bin) const {
0681     return m_binEdges.at(bin - 1);
0682   }
0683 
0684   /// @brief get upper bound of bin
0685   ///
0686   /// @param  [in] bin index of bin
0687   /// @return upper bin boundary
0688   ///
0689   /// @pre @c bin must be a valid bin index (excluding the overflow bin),
0690   ///      i.e. \f$0 \le \text{bin} \le \text{nBins}\f$
0691   ///
0692   /// @note Bin intervals have an open upper bound, i.e. the upper boundary
0693   ///       does @b not belong to the bin with the given bin index.
0694   double getBinUpperBound(std::size_t bin) const { return m_binEdges.at(bin); }
0695 
0696   /// @brief get bin center
0697   ///
0698   /// @param  [in] bin index of bin
0699   /// @return bin center position
0700   ///
0701   /// @pre @c bin must be a valid bin index (excluding under-/overflow bins),
0702   ///      i.e. \f$1 \le \text{bin} \le \text{nBins}\f$
0703   double getBinCenter(std::size_t bin) const {
0704     return 0.5 * (getBinLowerBound(bin) + getBinUpperBound(bin));
0705   }
0706 
0707   /// @brief get maximum of binning range
0708   ///
0709   /// @return maximum of binning range
0710   double getMax() const final { return m_binEdges.back(); }
0711 
0712   /// @brief get minimum of binning range
0713   ///
0714   /// @return minimum of binning range
0715   double getMin() const final { return m_binEdges.front(); }
0716 
0717   /// @brief get total number of bins
0718   ///
0719   /// @return total number of bins (excluding under-/overflow bins)
0720   std::size_t getNBins() const final { return m_binEdges.size() - 1; }
0721 
0722   /// @brief check whether value is inside axis limits
0723   /// @param x The value to check
0724   ///
0725   /// @return @c true if \f$\text{xmin} \le x < \text{xmax}\f$, otherwise
0726   ///         @c false
0727   ///
0728   /// @post If @c true is returned, the bin containing the given value is a
0729   ///       valid bin, i.e. it is neither the underflow nor the overflow bin.
0730   bool isInside(double x) const {
0731     return (m_binEdges.front() <= x) && (x < m_binEdges.back());
0732   }
0733 
0734   /// @brief Return a vector of bin edges
0735   /// @return Vector which contains the bin edges
0736   std::vector<double> getBinEdges() const final { return m_binEdges; }
0737 
0738   friend std::ostream& operator<<(std::ostream& os, const Axis& axis) {
0739     os << "Axis<Variable, " << bdt << ">(";
0740     os << axis.m_binEdges.front();
0741     for (std::size_t i = 1; i < axis.m_binEdges.size(); i++) {
0742       os << ", " << axis.m_binEdges[i];
0743     }
0744     os << ")";
0745     return os;
0746   }
0747 
0748  protected:
0749   void toStream(std::ostream& os) const final { os << *this; }
0750 
0751  private:
0752   /// vector of bin edges (sorted in ascending order)
0753   std::vector<double> m_binEdges;
0754 };
0755 }  // namespace Acts