Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-07-28 08:19:24

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