Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-27 08:30:43

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 #include "Acts/Utilities/NeighborHoodIndices.hpp"
0014 
0015 #include <algorithm>
0016 #include <cmath>
0017 #include <iostream>
0018 #include <stdexcept>
0019 #include <vector>
0020 
0021 namespace Acts {
0022 
0023 /// @brief calculate bin indices for an equidistant binning
0024 ///
0025 /// This class provides some basic functionality for calculating bin indices
0026 /// for a given equidistant binning.
0027 template <AxisBoundaryType bdt>
0028 class Axis<AxisType::Equidistant, bdt> : public IAxis {
0029  public:
0030   /// Static type identifier for this equidistant axis specialization
0031   static constexpr AxisType type = AxisType::Equidistant;
0032 
0033   /// Divide the range \f$[\text{xmin},\text{xmax})\f$ into \f$\text{nBins}\f$
0034   /// equidistant bins.
0035   ///
0036   /// @param xmin lower boundary of axis range
0037   /// @param xmax upper boundary of axis range
0038   /// @param nBins number of bins to divide the axis range into
0039   /// @param direction optional direction of the axis
0040   Axis(double xmin, double xmax, std::size_t nBins,
0041        std::optional<AxisDirection> direction = std::nullopt)
0042       : IAxis(direction),
0043         m_min(xmin),
0044         m_max(xmax),
0045         m_width((xmax - xmin) / static_cast<double>(nBins)),
0046         m_bins(nBins) {
0047     if (m_min >= m_max) {
0048       std::string msg = "Axis: Invalid axis range'";
0049       msg += "', min edge (" + std::to_string(m_min) + ") ";
0050       msg += " needs to be smaller than max edge (";
0051       msg += std::to_string(m_max) + ").";
0052       throw std::invalid_argument(msg);
0053     }
0054     if (m_bins < 1u) {
0055       throw std::invalid_argument(
0056           "Axis: Invalid binning, at least one bin is needed.");
0057     }
0058   }
0059 
0060   /// Divide the range \f$[\text{xmin},\text{xmax})\f$ into \f$\text{nBins}\f$
0061   /// equidistant bins.
0062   ///
0063   /// @param typeTag boundary type tag
0064   /// @param xmin lower boundary of axis range
0065   /// @param xmax upper boundary of axis range
0066   /// @param nBins number of bins to divide the axis range into
0067   /// @param direction optional direction of the axis
0068   Axis(AxisBoundaryTypeTag<bdt> typeTag, double xmin, double xmax,
0069        std::size_t nBins, std::optional<AxisDirection> direction = std::nullopt)
0070       : Axis(xmin, xmax, nBins, direction) {
0071     static_cast<void>(typeTag);
0072   }
0073 
0074   /// returns whether the axis is equidistant
0075   /// @return bool is equidistant
0076   bool isEquidistant() const final { return true; }
0077 
0078   /// returns whether the axis is variable
0079   /// @return bool is variable
0080   bool isVariable() const final { return false; }
0081 
0082   /// returns the type of the axis
0083   /// @return @c AxisType of this axis
0084   AxisType getType() const final { return type; }
0085 
0086   /// returns the boundary type set in the template param
0087   /// @return @c AxisBoundaryType of this axis
0088   AxisBoundaryType getBoundaryType() const final { return bdt; }
0089 
0090   /// Get #size bins which neighbor the one given. Generic overload with
0091   /// symmetric size.
0092   /// @param idx requested bin index
0093   /// @param size how many neighboring bins (up/down)
0094   /// @return Set of neighboring bin indices (global)
0095   NeighborHoodIndices neighborHoodIndices(std::size_t idx,
0096                                           std::size_t size = 1) const {
0097     return neighborHoodIndices(idx,
0098                                std::make_pair(-static_cast<int>(size), size));
0099   }
0100 
0101   /// Get #size bins which neighbor the one given. This is the version for Open.
0102   /// @param idx requested bin index
0103   /// @param sizes how many neighboring bins (up/down)
0104   /// @return Set of neighboring bin indices (global)
0105   /// @note Open varies given bin and allows 0 and NBins+1 (underflow, overflow) as neighbors
0106   NeighborHoodIndices neighborHoodIndices(std::size_t idx,
0107                                           std::pair<int, int> sizes = {-1,
0108                                                                        1}) const
0109     requires(bdt == AxisBoundaryType::Open)
0110   {
0111     constexpr int min = 0;
0112     const int max = static_cast<int>(getNBins()) + 1;
0113     const int itmin = std::clamp(static_cast<int>(idx) + sizes.first, min, max);
0114     const int itmax =
0115         std::clamp(static_cast<int>(idx) + sizes.second, min, max);
0116     return NeighborHoodIndices(static_cast<std::size_t>(itmin),
0117                                static_cast<std::size_t>(itmax + 1));
0118   }
0119 
0120   /// Get #size bins which neighbor the one given. This is the version for
0121   /// Bound.
0122   /// @param idx requested bin index
0123   /// @param sizes how many neighboring bins (up/down)
0124   /// @return Set of neighboring bin indices (global)
0125   /// @note Bound varies given bin and allows 1 and NBins (regular bins) as neighbors
0126   NeighborHoodIndices neighborHoodIndices(std::size_t idx,
0127                                           std::pair<int, int> sizes = {-1,
0128                                                                        1}) const
0129     requires(bdt == AxisBoundaryType::Bound)
0130   {
0131     if (idx <= 0 || idx >= (getNBins() + 1)) {
0132       return NeighborHoodIndices();
0133     }
0134     constexpr int min = 1;
0135     const int max = static_cast<int>(getNBins());
0136     const int itmin = std::clamp(static_cast<int>(idx) + sizes.first, min, max);
0137     const int itmax =
0138         std::clamp(static_cast<int>(idx) + sizes.second, min, max);
0139     return NeighborHoodIndices(static_cast<std::size_t>(itmin),
0140                                static_cast<std::size_t>(itmax + 1));
0141   }
0142 
0143   /// Get #size bins which neighbor the one given. This is the version for
0144   /// Closed (i.e. Wrapping).
0145   /// @param idx requested bin index
0146   /// @param sizes how many neighboring bins (up/down)
0147   /// @return Set of neighboring bin indices (global)
0148   /// @note Closed varies given bin and allows bins on the opposite
0149   ///       side of the axis as neighbors. (excludes underflow / overflow)
0150   NeighborHoodIndices neighborHoodIndices(std::size_t idx,
0151                                           std::pair<int, int> sizes = {-1,
0152                                                                        1}) const
0153     requires(bdt == AxisBoundaryType::Closed)
0154   {
0155     // Handle invalid indices
0156     if (idx <= 0 || idx >= (getNBins() + 1)) {
0157       return NeighborHoodIndices();
0158     }
0159 
0160     // Handle corner case where user requests more neighbours than the number
0161     // of bins on the axis. All bins are returned in this case.
0162 
0163     const int max = static_cast<int>(getNBins());
0164     sizes.first = std::clamp(sizes.first, -max, max);
0165     sizes.second = std::clamp(sizes.second, -max, max);
0166     if (std::abs(sizes.first - sizes.second) >= max) {
0167       sizes.first = 1 - static_cast<int>(idx);
0168       sizes.second = max - static_cast<int>(idx);
0169     }
0170 
0171     // If the entire index range is not covered, we must wrap the range of
0172     // targeted neighbor indices into the range of valid bin indices. This may
0173     // split the range of neighbor indices in two parts:
0174     //
0175     // Before wraparound - [        XXXXX]XXX
0176     // After wraparound  - [ XXXX   XXXX ]
0177     //
0178     const int itmin = static_cast<int>(idx) + sizes.first;
0179     const int itmax = static_cast<int>(idx) + sizes.second;
0180     const std::size_t itfirst = wrapBin(itmin);
0181     const std::size_t itlast = wrapBin(itmax);
0182     if (itfirst <= itlast) {
0183       return NeighborHoodIndices(itfirst, itlast + 1);
0184     } else {
0185       return NeighborHoodIndices(itfirst, static_cast<std::size_t>(max + 1), 1,
0186                                  itlast + 1);
0187     }
0188   }
0189 
0190   /// Converts bin index into a valid one for this axis.
0191   /// @note Open: bin index is clamped to [0, nBins+1]
0192   /// @param bin The bin to wrap
0193   /// @return valid bin index
0194   std::size_t wrapBin(int bin) const
0195     requires(bdt == AxisBoundaryType::Open)
0196   {
0197     return static_cast<std::size_t>(
0198         std::max(std::min(bin, static_cast<int>(getNBins()) + 1), 0));
0199   }
0200 
0201   /// Converts bin index into a valid one for this axis.
0202   /// @note Bound: bin index is clamped to [1, nBins]
0203   /// @param bin The bin to wrap
0204   /// @return valid bin index
0205   std::size_t wrapBin(int bin) const
0206     requires(bdt == AxisBoundaryType::Bound)
0207   {
0208     return static_cast<std::size_t>(
0209         std::max(std::min(bin, static_cast<int>(getNBins())), 1));
0210   }
0211 
0212   /// Converts bin index into a valid one for this axis.
0213   /// @note Closed: bin index wraps around to other side
0214   /// @param bin The bin to wrap
0215   /// @return valid bin index
0216   std::size_t wrapBin(int bin) const
0217     requires(bdt == AxisBoundaryType::Closed)
0218   {
0219     const int w = static_cast<int>(getNBins());
0220     return static_cast<std::size_t>(1 + (w + ((bin - 1) % w)) % w);
0221     // return int(bin<1)*w - int(bin>w)*w + bin;
0222   }
0223 
0224   /// get corresponding bin index for given coordinate
0225   /// @param x input coordinate
0226   /// @return index of bin containing the given value
0227   /// @note Bin intervals are defined with closed lower bounds and open upper
0228   ///       bounds, that is \f$l <= x < u\f$ if the value @c x lies within a
0229   ///       bin with lower bound @c l and upper bound @c u.
0230   /// @note Bin indices start at @c 1. The underflow bin has the index @c 0
0231   ///       while the index <tt>nBins + 1</tt> indicates the overflow bin.
0232   std::size_t getBin(double x) const final {
0233     return wrapBin(
0234         static_cast<int>(std::floor((x - getMin()) / getBinWidth()) + 1));
0235   }
0236 
0237   /// get bin width
0238   /// @return constant width for all bins
0239   double getBinWidth(std::size_t /*bin*/) const final { return m_width; }
0240 
0241   /// get bin width
0242   /// @return constant width for all bins
0243   double getBinWidth() const { return getBinWidth(0); }
0244 
0245   /// get lower bound of bin
0246   /// @param bin index of bin
0247   /// @return lower bin boundary
0248   ///
0249   /// @pre @c bin must be a valid bin index (excluding the underflow bin),
0250   ///      i.e. \f$1 \le \text{bin} \le \text{nBins} + 1\f$
0251   ///
0252   /// @note Bin intervals have a closed lower bound, i.e. the lower boundary
0253   ///       belongs to the bin with the given bin index.
0254   double getBinLowerBound(std::size_t bin) const final {
0255     return getMin() + static_cast<double>(bin - 1) * getBinWidth();
0256   }
0257 
0258   /// get upper bound of bin
0259   /// @param bin index of bin
0260   /// @return upper bin boundary
0261   /// @pre @c bin must be a valid bin index (excluding the overflow bin),
0262   ///      i.e. \f$0 \le \text{bin} \le \text{nBins}\f$
0263   /// @note Bin intervals have an open upper bound, i.e. the upper boundary
0264   ///       does @b not belong to the bin with the given bin index.
0265   double getBinUpperBound(std::size_t bin) const final {
0266     return getMin() + static_cast<double>(bin) * getBinWidth();
0267   }
0268 
0269   /// get bin center
0270   /// @param bin index of bin
0271   /// @return bin center position
0272   /// @pre @c bin must be a valid bin index (excluding under-/overflow bins),
0273   ///      i.e. \f$1 \le \text{bin} \le \text{nBins}\f$
0274   double getBinCenter(std::size_t bin) const final {
0275     return getMin() + (static_cast<double>(bin) - 0.5) * getBinWidth();
0276   }
0277 
0278   /// get maximum of binning range
0279   /// @return maximum of binning range
0280   double getMax() const final { return m_max; }
0281 
0282   /// get minimum of binning range
0283   /// @return minimum of binning range
0284   double getMin() const final { return m_min; }
0285 
0286   /// get total number of bins
0287   /// @return total number of bins (excluding under-/overflow bins)
0288   std::size_t getNBins() const final { return m_bins; }
0289 
0290   /// check whether value is inside axis limits
0291   /// @param x The value to check
0292   /// @return @c true if \f$\text{xmin} \le x < \text{xmax}\f$, otherwise
0293   ///         @c false
0294   /// @post If @c true is returned, the bin containing the given value is a
0295   ///       valid bin, i.e. it is neither the underflow nor the overflow bin.
0296   bool isInside(double x) const final { return (m_min <= x) && (x < m_max); }
0297 
0298   /// Return a vector of bin edges
0299   /// @return Vector which contains the bin edges
0300   std::vector<double> getBinEdges() const final {
0301     std::vector<double> binEdges;
0302     for (std::size_t i = 1; i <= m_bins; i++) {
0303       binEdges.push_back(getBinLowerBound(i));
0304     }
0305     binEdges.push_back(getBinUpperBound(m_bins));
0306     return binEdges;
0307   }
0308 
0309   friend std::ostream& operator<<(std::ostream& os, const Axis& axis) {
0310     os << "Axis<Equidistant, " << bdt << ">(";
0311     os << axis.m_min << ", ";
0312     os << axis.m_max << ", ";
0313     os << axis.m_bins << ", ";
0314     if (axis.getDirection().has_value()) {
0315       os << *axis.getDirection();
0316     } else {
0317       os << "Undefined";
0318     }
0319     os << ")";
0320     return os;
0321   }
0322 
0323  protected:
0324   void toStream(std::ostream& os) const final { os << *this; }
0325 
0326  private:
0327   /// minimum of binning range
0328   double m_min{};
0329   /// maximum of binning range
0330   double m_max{};
0331   /// constant bin width
0332   double m_width{};
0333   /// number of bins (excluding under-/overflow bins)
0334   std::size_t m_bins{};
0335 };
0336 
0337 /// calculate bin indices for a variable binning
0338 ///
0339 /// This class provides some basic functionality for calculating bin indices
0340 /// for a given binning with variable bin sizes.
0341 template <AxisBoundaryType bdt>
0342 class Axis<AxisType::Variable, bdt> : public IAxis {
0343  public:
0344   /// Static type identifier for this variable-width axis specialization
0345   static constexpr AxisType type = AxisType::Variable;
0346 
0347   /// Create a binning structure with @c nBins variable-sized bins from the
0348   /// given bin boundaries. @c nBins is given by the number of bin edges
0349   /// reduced by one.
0350   /// @param binEdges vector of bin edges
0351   /// @param direction optional direction of the axis
0352   /// @pre @c binEdges must be strictly sorted in ascending order.
0353   /// @pre @c binEdges must contain at least two entries.
0354   explicit Axis(std::vector<double> binEdges,
0355                 std::optional<AxisDirection> direction = std::nullopt)
0356       : IAxis(direction), m_binEdges(std::move(binEdges)) {
0357     if (m_binEdges.size() < 2) {
0358       throw std::invalid_argument(
0359           "Axis: Invalid binning, at least two bin edges are needed.");
0360     }
0361     if (!std::ranges::is_sorted(m_binEdges)) {
0362       throw std::invalid_argument(
0363           "Axis: Invalid binning, bin edges are not sorted.");
0364     }
0365   }
0366 
0367   /// Create a binning structure with @c nBins variable-sized bins from the
0368   /// given bin boundaries. @c nBins is given by the number of bin edges
0369   /// reduced by one.
0370   /// @param typeTag boundary type tag
0371   /// @param binEdges vector of bin edges
0372   /// @param direction optional direction of the axis
0373   /// @pre @c binEdges must be strictly sorted in ascending order.
0374   /// @pre @c binEdges must contain at least two entries.
0375   Axis(AxisBoundaryTypeTag<bdt> typeTag, std::vector<double> binEdges,
0376        std::optional<AxisDirection> direction = std::nullopt)
0377       : Axis(std::move(binEdges), direction) {
0378     static_cast<void>(typeTag);
0379   }
0380 
0381   /// returns whether the axis is equidistante
0382   /// @return bool is equidistant
0383   bool isEquidistant() const final { return false; }
0384 
0385   /// returns whether the axis is variable
0386   /// @return bool is variable
0387   bool isVariable() const final { return true; }
0388 
0389   /// returns the type of the axis
0390   /// @return @c AxisType of this axis
0391   AxisType getType() const final { return type; }
0392 
0393   /// returns the boundary type set in the template param
0394   /// @return @c AxisBoundaryType of this axis
0395   AxisBoundaryType getBoundaryType() const final { return bdt; }
0396 
0397   /// Get #size bins which neighbor the one given. Generic overload with
0398   /// symmetric size.
0399   /// @param idx requested bin index
0400   /// @param size how many neighboring bins
0401   /// @return Set of neighboring bin indices (global)
0402   NeighborHoodIndices neighborHoodIndices(std::size_t idx,
0403                                           std::size_t size = 1) const {
0404     return neighborHoodIndices(idx,
0405                                std::make_pair(-static_cast<int>(size), size));
0406   }
0407 
0408   /// Get #size bins which neighbor the one given. This is the version for Open.
0409   /// @param idx requested bin index
0410   /// @param sizes how many neighboring bins (up/down)
0411   /// @return Set of neighboring bin indices (global)
0412   /// @note Open varies given bin and allows 0 and NBins+1 (underflow, overflow) as neighbors
0413   NeighborHoodIndices neighborHoodIndices(std::size_t idx,
0414                                           std::pair<int, int> sizes = {-1,
0415                                                                        1}) const
0416     requires(bdt == AxisBoundaryType::Open)
0417   {
0418     constexpr int min = 0;
0419     const int max = getNBins() + 1;
0420     const int itmin = std::max(min, static_cast<int>(idx) + sizes.first);
0421     const int itmax = std::min(max, static_cast<int>(idx) + sizes.second);
0422     return NeighborHoodIndices(itmin, itmax + 1);
0423   }
0424 
0425   /// Get #size bins which neighbor the one given. This is the version for
0426   /// Bound.
0427   /// @param idx requested bin index
0428   /// @param sizes how many neighboring bins (up/down)
0429   /// @return Set of neighboring bin indices (global)
0430   /// @note Bound varies given bin and allows 1 and NBins (regular bins) as neighbors
0431   NeighborHoodIndices neighborHoodIndices(std::size_t idx,
0432                                           std::pair<int, int> sizes = {-1,
0433                                                                        1}) const
0434     requires(bdt == AxisBoundaryType::Bound)
0435   {
0436     if (idx <= 0 || idx >= (getNBins() + 1)) {
0437       return NeighborHoodIndices();
0438     }
0439     constexpr int min = 1;
0440     const int max = getNBins();
0441     const int itmin = std::max(min, static_cast<int>(idx) + sizes.first);
0442     const int itmax = std::min(max, static_cast<int>(idx) + sizes.second);
0443     return NeighborHoodIndices(itmin, itmax + 1);
0444   }
0445 
0446   /// Get #size bins which neighbor the one given. This is the version for
0447   /// Closed.
0448   /// @param idx requested bin index
0449   /// @param sizes how many neighboring bins (up/down)
0450   /// @return Set of neighboring bin indices (global)
0451   /// @note Closed varies given bin and allows bins on the opposite
0452   ///       side of the axis as neighbors. (excludes underflow / overflow)
0453   NeighborHoodIndices neighborHoodIndices(std::size_t idx,
0454                                           std::pair<int, int> sizes = {-1,
0455                                                                        1}) const
0456     requires(bdt == AxisBoundaryType::Closed)
0457   {
0458     // Handle invalid indices
0459     if (idx <= 0 || idx >= (getNBins() + 1)) {
0460       return NeighborHoodIndices();
0461     }
0462 
0463     // Handle corner case where user requests more neighbours than the number
0464     // of bins on the axis. All bins are returned in this case
0465 
0466     const int max = static_cast<int>(getNBins());
0467     sizes.first = std::clamp(sizes.first, -max, max);
0468     sizes.second = std::clamp(sizes.second, -max, max);
0469     if (std::abs(sizes.first - sizes.second) >= max) {
0470       sizes.first = 1 - static_cast<int>(idx);
0471       sizes.second = max - static_cast<int>(idx);
0472     }
0473 
0474     // If the entire index range is not covered, we must wrap the range of
0475     // targeted neighbor indices into the range of valid bin indices. This may
0476     // split the range of neighbor indices in two parts:
0477     //
0478     // Before wraparound - [        XXXXX]XXX
0479     // After wraparound  - [ XXXX   XXXX ]
0480     //
0481     const int itmin = static_cast<int>(idx) + sizes.first;
0482     const int itmax = static_cast<int>(idx) + sizes.second;
0483     const std::size_t itfirst = wrapBin(itmin);
0484     const std::size_t itlast = wrapBin(itmax);
0485     if (itfirst <= itlast) {
0486       return NeighborHoodIndices(itfirst, itlast + 1);
0487     } else {
0488       return NeighborHoodIndices(itfirst, static_cast<std::size_t>(max + 1), 1,
0489                                  itlast + 1);
0490     }
0491   }
0492 
0493   /// Converts bin index into a valid one for this axis.
0494   /// @note Open: bin index is clamped to [0, nBins+1]
0495   /// @param bin The bin to wrap
0496   /// @return valid bin index
0497   std::size_t wrapBin(int bin) const
0498     requires(bdt == AxisBoundaryType::Open)
0499   {
0500     return static_cast<std::size_t>(
0501         std::max(std::min(bin, static_cast<int>(getNBins()) + 1), 0));
0502   }
0503 
0504   /// Converts bin index into a valid one for this axis.
0505   /// @note Bound: bin index is clamped to [1, nBins]
0506   /// @param bin The bin to wrap
0507   /// @return valid bin index
0508   std::size_t wrapBin(int bin) const
0509     requires(bdt == AxisBoundaryType::Bound)
0510   {
0511     return static_cast<std::size_t>(
0512         std::max(std::min(bin, static_cast<int>(getNBins())), 1));
0513   }
0514 
0515   /// Converts bin index into a valid one for this axis.
0516   /// @note Closed: bin index wraps around to other side
0517   /// @param bin The bin to wrap
0518   /// @return valid bin index
0519   std::size_t wrapBin(int bin) const
0520     requires(bdt == AxisBoundaryType::Closed)
0521   {
0522     const int w = static_cast<int>(getNBins());
0523     return static_cast<std::size_t>(1 + (w + ((bin - 1) % w)) % w);
0524     // return int(bin<1)*w - int(bin>w)*w + bin;
0525   }
0526 
0527   /// get corresponding bin index for given coordinate
0528   /// @param x input coordinate
0529   /// @return index of bin containing the given value
0530   /// @note Bin intervals are defined with closed lower bounds and open upper
0531   ///       bounds, that is \f$l <= x < u\f$ if the value @c x lies within a
0532   ///       bin with lower bound @c l and upper bound @c u.
0533   /// @note Bin indices start at @c 1. The underflow bin has the index @c 0
0534   ///       while the index <tt>nBins + 1</tt> indicates the overflow bin.
0535   std::size_t getBin(double x) const final {
0536     const auto it = std::ranges::upper_bound(m_binEdges, x);
0537     return wrapBin(
0538         static_cast<int>(std::ranges::distance(m_binEdges.begin(), it)));
0539   }
0540 
0541   /// get bin width
0542   /// @param bin index of bin
0543   /// @return width of given bin
0544   /// @pre @c bin must be a valid bin index (excluding under-/overflow bins),
0545   ///      i.e. \f$1 \le \text{bin} \le \text{nBins}\f$
0546   double getBinWidth(std::size_t bin) const final {
0547     return m_binEdges.at(bin) - m_binEdges.at(bin - 1);
0548   }
0549 
0550   /// get lower bound of bin
0551   /// @param bin index of bin
0552   /// @return lower bin boundary
0553   /// @pre @c bin must be a valid bin index (excluding the underflow bin),
0554   ///      i.e. \f$1 \le \text{bin} \le \text{nBins} + 1\f$
0555   /// @note Bin intervals have a closed lower bound, i.e. the lower boundary
0556   ///       belongs to the bin with the given bin index.
0557   double getBinLowerBound(std::size_t bin) const final {
0558     return m_binEdges.at(bin - 1);
0559   }
0560 
0561   /// get upper bound of bin
0562   /// @param bin index of bin
0563   /// @return upper bin boundary
0564   /// @pre @c bin must be a valid bin index (excluding the overflow bin),
0565   ///      i.e. \f$0 \le \text{bin} \le \text{nBins}\f$
0566   /// @note Bin intervals have an open upper bound, i.e. the upper boundary
0567   ///       does @b not belong to the bin with the given bin index.
0568   double getBinUpperBound(std::size_t bin) const final {
0569     return m_binEdges.at(bin);
0570   }
0571 
0572   /// get bin center
0573   /// @param bin index of bin
0574   /// @return bin center position
0575   /// @pre @c bin must be a valid bin index (excluding under-/overflow bins),
0576   ///      i.e. \f$1 \le \text{bin} \le \text{nBins}\f$
0577   double getBinCenter(std::size_t bin) const final {
0578     return 0.5 * (getBinLowerBound(bin) + getBinUpperBound(bin));
0579   }
0580 
0581   /// get maximum of binning range
0582   /// @return maximum of binning range
0583   double getMax() const final { return m_binEdges.back(); }
0584 
0585   /// get minimum of binning range
0586   /// @return minimum of binning range
0587   double getMin() const final { return m_binEdges.front(); }
0588 
0589   /// get total number of bins
0590   /// @return total number of bins (excluding under-/overflow bins)
0591   std::size_t getNBins() const final { return m_binEdges.size() - 1; }
0592 
0593   /// check whether value is inside axis limits
0594   /// @param x The value to check
0595   /// @return @c true if \f$\text{xmin} \le x < \text{xmax}\f$, otherwise @c false
0596   /// @post If @c true is returned, the bin containing the given value is a
0597   ///       valid bin, i.e. it is neither the underflow nor the overflow bin.
0598   bool isInside(double x) const final {
0599     return (m_binEdges.front() <= x) && (x < m_binEdges.back());
0600   }
0601 
0602   /// Return a vector of bin edges
0603   /// @return Vector which contains the bin edges
0604   std::vector<double> getBinEdges() const final { return m_binEdges; }
0605 
0606   friend std::ostream& operator<<(std::ostream& os, const Axis& axis) {
0607     os << "Axis<Variable, " << bdt << ">({";
0608     os << axis.m_binEdges.front();
0609     for (std::size_t i = 1; i < axis.m_binEdges.size(); ++i) {
0610       os << ", " << axis.m_binEdges.at(i);
0611     }
0612     os << "}, ";
0613     if (axis.getDirection().has_value()) {
0614       os << *axis.getDirection();
0615     } else {
0616       os << "Undefined";
0617     }
0618     os << ")";
0619     return os;
0620   }
0621 
0622  protected:
0623   void toStream(std::ostream& os) const final { os << *this; }
0624 
0625  private:
0626   /// vector of bin edges (sorted in ascending order)
0627   std::vector<double> m_binEdges;
0628 };
0629 
0630 }  // namespace Acts