Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-13 10:32:13

0001 // -*- C++ -*-
0002 //
0003 // This file is part of YODA -- Yet more Objects for Data Analysis
0004 // Copyright (C) 2008-2025 The YODA collaboration (see AUTHORS for details)
0005 //
0006 #ifndef YODA_BINNING_H
0007 #define YODA_BINNING_H
0008 #include "YODA/BinnedAxis.h"
0009 #include "YODA/Utils/MetaUtils.h"
0010 #include <memory>
0011 #include <tuple>
0012 #include <vector>
0013 #include <array>
0014 
0015 namespace YODA {
0016 
0017   /// @brief Nullifies coordinate if it is discrete.
0018   template<typename CoordT>
0019   double nullifyIfDiscCoord(CoordT&& /* coord */, std::false_type, double null = 0.0) {
0020     return null;
0021   }
0022   //
0023   template<typename CoordT>
0024   double nullifyIfDiscCoord(CoordT&& coord, std::true_type, double /* null */ = 0.0) {
0025     return std::forward<CoordT>(coord);
0026   }
0027 
0028   /// @brief Checks if a coordinate tuple has a nan
0029   template<typename... Args>
0030   inline bool containsNan(const std::tuple<Args...>& coords) {
0031     std::array<size_t, sizeof...(Args)> hasNan{};
0032     auto checkCoords = [&hasNan, &coords](auto I) {
0033       using isContinuous = typename std::is_floating_point<typename std::tuple_element_t<I, std::tuple<Args...>>>;
0034 
0035       hasNan[I] = size_t(std::isnan(nullifyIfDiscCoord(std::move(std::get<I>(coords)),
0036                                                        std::integral_constant<bool,
0037                                                        isContinuous::value>{})));
0038     };
0039 
0040     MetaUtils::staticFor<sizeof...(Args)>(checkCoords);
0041 
0042     return std::any_of(hasNan.begin(), hasNan.end(), [ ](const bool i) { return i; } );
0043   }
0044 
0045 
0046 
0047   template <typename... Axes>
0048   class Binning {
0049   private:
0050 
0051     std::tuple<Axes...> _axes;
0052     size_t _dim = sizeof...(Axes);
0053     std::vector<size_t> _maskedIndices;
0054 
0055 
0056   protected:
0057 
0058     /// @name Utilities
0059     // @{
0060 
0061     using IndexArr = std::array<size_t, sizeof...(Axes)>;
0062 
0063     // @}
0064 
0065   public:
0066 
0067     /// @name Utilities
0068     // @{
0069 
0070     template <size_t I>
0071     using getAxisT = std::tuple_element_t<I, std::tuple<Axes...>>;
0072 
0073     template <size_t I>
0074     using getEdgeT = typename getAxisT<I>::EdgeT;
0075 
0076     template <size_t I>
0077     using is_CAxis = typename std::is_floating_point<getEdgeT<I>>;
0078 
0079     template<size_t I>
0080     using is_Arithmetic = typename std::is_arithmetic<getEdgeT<I>>;
0081 
0082     using all_CAxes = typename std::conjunction<std::is_floating_point<typename Axes::EdgeT>...>;
0083 
0084     using EdgeTypesTuple = decltype(
0085       std::tuple_cat(std::declval<std::tuple<typename Axes::EdgeT>>()...));
0086 
0087     using Dimension = std::integral_constant<size_t, sizeof...(Axes)>;
0088 
0089     // @}
0090 
0091     /// @name Constructors
0092     //@{
0093 
0094     /// @brief Nullary constructor for unique pointers etc.
0095     Binning() { }
0096 
0097     /// @brief Constructs binning from vectors of axes' edges
0098     Binning(const std::initializer_list<typename Axes::EdgeT>&... edges)
0099         : _axes(Axes(edges)...) {
0100       updateMaskedBins();
0101     }
0102 
0103     /// @brief Constructs binning from Rvalue vectors of axes' edges
0104     Binning(std::initializer_list<typename Axes::EdgeT>&&... edges)
0105         : _axes(Axes(std::move(edges))...) {
0106       updateMaskedBins();
0107     }
0108 
0109     /// @brief Constructs binning from a sequence of axes
0110     Binning(const Axes&... axes)
0111         : _axes(axes...) {
0112       updateMaskedBins();
0113     }
0114 
0115     /// @brief Constructs binning from a sequence of Rvalue axes
0116     Binning(Axes&&... axes)
0117         : _axes(std::move(axes)...) {
0118       updateMaskedBins();
0119     }
0120 
0121     /// @brief Copy constructor.
0122     Binning(const Binning& other) : _axes(other._axes), _maskedIndices(other._maskedIndices) {}
0123 
0124     /// @brief Move constructor.
0125     Binning(Binning&& other) : _axes(std::move(other._axes)), _maskedIndices(std::move(other._maskedIndices)) {}
0126 
0127     /// @brief Copy assignment
0128     Binning& operator=(const Binning& other) {
0129       if (this != &other) {
0130         _axes = other._axes;
0131         _maskedIndices = other._maskedIndices;
0132       }
0133       return *this;
0134     }
0135 
0136     /// @brief Move assignment
0137     Binning& operator=(Binning&& other) {
0138       if (this != &other) {
0139         _axes = std::move(other._axes);
0140         _maskedIndices = std::move(other._maskedIndices);
0141       }
0142       return *this;
0143     }
0144 
0145     //@}
0146 
0147     /// @name Indexing methods
0148     //@{
0149 
0150     /// @brief calculates global index from array of local indices
0151     size_t localToGlobalIndex(const IndexArr& localIndices) const;
0152 
0153     /// @brief calculates array of local indices from a global index
0154     IndexArr globalToLocalIndices(size_t globalIndex) const;
0155 
0156     /// @brief assembles array of local indices for an array of input values
0157     IndexArr localIndicesAt(const EdgeTypesTuple& coords) const;
0158 
0159     /// @brief calculates global index for a given array of input values
0160     size_t globalIndexAt(const EdgeTypesTuple& coords) const;
0161 
0162     /// @brief Calculates indices of overflow bins.
0163     std::vector<size_t> calcOverflowBinsIndices() const noexcept;
0164 
0165     /// @brief Count number of overflow bins.
0166     size_t countOverflowBins(const size_t axisN) const noexcept;
0167 
0168     /// @brief Calculates indices of masked bins.
0169     std::vector<size_t> maskedBins() const noexcept { return _maskedIndices; }
0170 
0171     /// @brief Clear masked bins
0172     void clearMaskedBins() noexcept { _maskedIndices.clear(); }
0173 
0174     /// @brief Mask/Unmask bin with global index @a index
0175     void maskBin(const size_t index, const bool status = true);
0176 
0177     /// @brief Mask/Unmask bin with local @a localIndices
0178     void maskBin(const IndexArr& localIndices, const bool status = true);
0179 
0180     /// @brief Mask/Unmask bin at set of @ coords
0181     void maskBinAt(const EdgeTypesTuple& coords, const bool status = true) noexcept;
0182 
0183     /// @brief Mask/Unmask bins in list of global @a indices
0184     void maskBins(const std::vector<size_t>& indices, const bool status = true);
0185 
0186     /// @brief Mask a slice of the binning at local bin index @a idx along axis dimesnion @a dim
0187     void maskSlice(const size_t dim, const size_t idx, const bool status = true);
0188 
0189     /// @brief Check if bin is masked
0190     bool isMasked(const size_t i) const noexcept;
0191 
0192     /// @brief Check if bin is in visible range
0193     bool isVisible(const size_t i) const noexcept;
0194 
0195     /// @brief Calculates size of a binning slice.
0196     size_t calcSliceSize(const size_t pivotAxisN) const noexcept;
0197 
0198     /// @brief Calculates indices of bins located in the specified slices.
0199     /// @note Accept vector of pairs where pair.first is index of axis,
0200     /// and pair.second is slice pivot bins on this axis.
0201     std::vector<size_t> sliceIndices(std::vector<std::pair<size_t, std::vector<size_t>>>) const noexcept;
0202 
0203     /// @brief Calculates indices of bins located in the slice corresponding to
0204     /// bin at nBin at axis N.
0205     std::vector<size_t> sliceIndices(size_t axisN, size_t binN) const noexcept;
0206 
0207     /// @brief Helper function to extract and mask index slices
0208     /// corresponding to masked bins along the axes
0209     void updateMaskedBins() noexcept {
0210 
0211       std::vector<std::pair<size_t, std::vector<size_t>>> slicePivots;
0212       auto extractMaskedBins = [&slicePivots, &axes = _axes](auto I) {
0213         using isContinuous = typename Binning::template is_CAxis<I>;
0214 
0215         if constexpr(isContinuous::value) {
0216           const auto& axis = std::get<I>(axes);
0217           slicePivots.push_back({I, {axis.maskedBins()} });
0218         }
0219       };
0220 
0221       // apply for all axes
0222       MetaUtils::staticFor<Dimension::value>(extractMaskedBins);
0223 
0224       // get indices along slice pivots and remove duplicates
0225       _maskedIndices = sliceIndices(slicePivots);
0226       std::sort(_maskedIndices.begin(), _maskedIndices.end());
0227       _maskedIndices.erase( std::unique(_maskedIndices.begin(), _maskedIndices.end()),
0228                             _maskedIndices.end() );
0229     }
0230 
0231     //@}
0232 
0233     /// @name Transformations
0234 
0235     // @{
0236     template <size_t... AxisNs>
0237     void mergeBins(
0238       std::decay_t<decltype(AxisNs, std::declval<std::pair<size_t, size_t>>())>... mergeRanges) noexcept;
0239     // @}
0240 
0241     /// @name Comparisons to other Binning objects
0242     //@{
0243 
0244     /// @brief checks if Binnings are compatible
0245     ///
0246     /// Binnings are compatible if they have the same number of axes,
0247     /// and those axes have the same bin edges
0248     bool isCompatible(const Binning<Axes...>& other) const noexcept;
0249 
0250     //@}
0251 
0252     /// @name Getters
0253     //@{
0254 
0255     /// @brief Extracts axis corresponding to dimension. Const version
0256     template <size_t I>
0257     const getAxisT<I>& axis() const noexcept;
0258 
0259     /// @brief Extracts axis corresponding to dimension. Non-const version
0260     template <size_t I>
0261     getAxisT<I>& axis() noexcept;
0262 
0263     template<size_t I>
0264     auto _getAxesExcept() const noexcept;
0265 
0266     /// @brief Constructs array of axes sizes
0267     IndexArr _getAxesSizes(const bool includeOverflows = true) const noexcept;
0268 
0269     /// @brief get dimension of Binning
0270     size_t dim() const noexcept;
0271 
0272     /// @brief get total number of bins in Binning
0273     size_t numBins(const bool includeOverflows = true, const bool includeMaskedBins = true) const noexcept;
0274 
0275     /// @brief get number of bins at axis
0276     size_t numBinsAt(const size_t axisN, const bool includeOverflows = true) const noexcept;
0277 
0278     /// @brief Return a coordinate tuple for bin @a index
0279     EdgeTypesTuple edgeTuple(const size_t index) const noexcept;
0280 
0281     /// @brief Templated version to get edges of axis N by value.
0282     ///
0283     /// +-inf edges are included, depending on the value of @a includeOverflows
0284     /// Needed by axis-specific version from StatsMixin
0285     template <size_t I, typename E = getEdgeT<I>>
0286     std::vector<E> edges(const bool includeOverflows = false) const noexcept {
0287       const auto& axis = std::get<I>(_axes);
0288       if (!includeOverflows && Binning::template is_CAxis<I>::value) {
0289         auto&& all_edges = axis.edges();
0290         typename std::vector<E> rtn;
0291         const size_t offset = all_edges.size() - 1;
0292         rtn.insert(rtn.end(), std::make_move_iterator(all_edges.begin()+1),
0293                               std::make_move_iterator(all_edges.begin()+offset));
0294         return rtn;
0295       }
0296       return axis.edges();
0297     }
0298 
0299     /// @brief Templated version to get bin widths of axis N by reference.
0300     ///
0301     /// Overflows are included depending on @a includeOverflows
0302     /// Needed by axis-specific version from AxisMixin
0303     ///
0304     /// @note This version is only supported for continuous axes.
0305     template <size_t I, typename E = getEdgeT<I>>
0306     std::enable_if_t<std::is_floating_point<E>::value, std::vector<E>>
0307     widths(const bool includeOverflows = false) const noexcept {
0308       const auto& axis = std::get<I>(_axes);
0309       return axis.widths(includeOverflows);
0310     }
0311 
0312     /// @brief Get the lowest non-overflow edge of the axis
0313     ///
0314     /// @note This method is only supported for continuous axes
0315     template <size_t I, typename E = getEdgeT<I>>
0316     std::enable_if_t<std::is_floating_point<E>::value, E> min() const noexcept {
0317       const auto& axis = std::get<I>(_axes);
0318       assert(axis.numBins(true) > 2); // More than 2 overflow bins.
0319       return axis.min(1); // Bin 0 is the underflow bin.
0320     }
0321 
0322     /// @brief Get the highest non-overflow edge of the axis
0323     ///
0324     /// @note This method is only supported for continuous axes
0325     template <size_t I, typename E = getEdgeT<I>>
0326     std::enable_if_t<std::is_floating_point<E>::value, E> max() const noexcept {
0327       const auto& axis = std::get<I>(_axes);
0328       assert(axis.numBins(true) > 2); // More than 2 overflow bins.
0329       return axis.min(axis.numBins(true)-1); // Bin 0 is the underflow bin.
0330     }
0331 
0332     /// @brief Return the differential volume for bin @a index
0333     double dVol(const size_t index) const;
0334 
0335     //@}
0336 
0337     /// @name I/O
0338     /// @{
0339 
0340     /// @brief Render axis setup for this binning
0341     void _renderYODA(std::ostream& os) const noexcept {
0342       // lambda that renders the axis edges
0343       auto edgePrinter = [&](auto I) {
0344         const auto& axis  = std::get<I>(_axes);
0345         if (axis.numBins()) {
0346           os << "Edges(A"+ std::to_string(I+1) + "): ";
0347           axis._renderYODA(os);
0348           os << "\n";
0349         }
0350       };
0351       // apply lambda to all axes
0352       MetaUtils::staticFor<Dimension::value>(edgePrinter);
0353 
0354       // render indices of masked bins
0355       if (_maskedIndices.size()) {
0356         std::vector<size_t> gaps(_maskedIndices.size());
0357         std::partial_sort_copy(begin(_maskedIndices), end(_maskedIndices), begin(gaps), end(gaps));
0358         //std::sort(_maskedIndices.begin(), _maskedIndices.end());
0359         os << "MaskedBins: [";
0360         for (size_t i = 0; i < gaps.size(); ++i) {
0361           if (i)  os << ", ";
0362           os << std::to_string(gaps[i]);
0363         }
0364         os << "]\n";
0365       }
0366     }
0367 
0368     /// @brief Render edges for this binning at index @a idx
0369     void _renderYODA(std::ostream& os, const size_t idx, const int width = 13) const {
0370       IndexArr localIndices = globalToLocalIndices(idx);
0371       // lambda that renders low/high edge or discrete value depending on the axis type
0372       auto edgePrinter = [&](auto I) {
0373         const auto& axis  = std::get<I>(_axes);
0374         axis._renderYODA(os, localIndices[I], width);
0375       };
0376       // apply lambda to all axes
0377       MetaUtils::staticFor<Dimension::value>(edgePrinter);
0378     }
0379 
0380     //@}
0381 
0382   };
0383 
0384 
0385   template<typename... Axes>
0386   size_t Binning<Axes...>::localToGlobalIndex(const IndexArr& localIndices) const {
0387     size_t gIndex = 0;
0388     // std::string st_indices = "";
0389     // for (size_t iIndex = 0; iIndex < localIndices.size(); ++iIndex) {
0390     //   st_indices += "|";
0391     //   st_indices += std::to_string(localIndices[iIndex]);
0392     // }
0393 
0394     /// x + y*width + z*width*height + w*width*height*depth + ...
0395     IndexArr axesSizes = _getAxesSizes();
0396     for (size_t iIndex = 0; iIndex < localIndices.size(); ++iIndex) {
0397       size_t productOfBinSizes = 1;
0398       for (ssize_t iBinnedAxis = iIndex - 1; iBinnedAxis >= 0; --iBinnedAxis) {
0399         productOfBinSizes *= (axesSizes[iBinnedAxis]);
0400       }
0401       gIndex += localIndices[iIndex] * productOfBinSizes;
0402     }
0403     return gIndex;
0404   }
0405 
0406   template <typename... Axes>
0407   typename Binning<Axes...>::IndexArr
0408   Binning<Axes...>::globalToLocalIndices(size_t globalIndex) const {
0409     if (globalIndex >= numBins(true, true))  throw RangeError("Global index outside bin range");
0410     IndexArr localIndices{};
0411 
0412     IndexArr axesSizes = _getAxesSizes();
0413 
0414     for (ssize_t iIndex = localIndices.size()-1 ; iIndex >= 0; --iIndex){
0415       size_t productOfNestedBinSizes = 1;
0416 
0417       for (ssize_t iBinnedAxis = iIndex - 1; iBinnedAxis >= 0; --iBinnedAxis) {
0418         productOfNestedBinSizes *= (axesSizes[iBinnedAxis]);
0419       }
0420 
0421       // integer division to get the multiplier
0422       localIndices[iIndex] = globalIndex / productOfNestedBinSizes;
0423       // then left with only the remainder
0424       globalIndex = globalIndex % productOfNestedBinSizes;
0425     }
0426     return localIndices;
0427   }
0428 
0429   template<typename... Axes>
0430   typename Binning<Axes...>::IndexArr
0431   Binning<Axes...>::localIndicesAt(const EdgeTypesTuple& coords) const {
0432     IndexArr localIndices{};
0433     auto extractIndices = [&localIndices, &coords, &axes = _axes](auto I) {
0434                                const auto& coord = std::get<I>(coords);
0435                                const auto& axis  = std::get<I>(axes);
0436 
0437                                localIndices[I] = axis.index(coord);
0438                              };
0439 
0440     MetaUtils::staticFor<Dimension::value>(extractIndices);
0441 
0442     return localIndices;
0443   }
0444 
0445   template<typename... Axes>
0446   size_t Binning<Axes...>::globalIndexAt(const EdgeTypesTuple& coords) const {
0447     return localToGlobalIndex(localIndicesAt(coords));
0448   }
0449 
0450   template<typename... Axes>
0451   std::vector<size_t> Binning<Axes...>::calcOverflowBinsIndices() const noexcept {
0452     IndexArr axesSizes = _getAxesSizes();
0453     std::vector<bool> isCAxis;
0454 
0455     auto getCAxisIndices = [&isCAxis](auto I){
0456       using isContinuous = typename Binning::template is_CAxis<I>;
0457       isCAxis.emplace_back(isContinuous::value);
0458     };
0459 
0460     MetaUtils::staticFor<Dimension::value>(getCAxisIndices);
0461 
0462     std::vector<std::pair<size_t, std::vector<size_t>>> slicePivots;
0463     slicePivots.reserve(isCAxis.size());
0464 
0465     for (size_t axisN = 0; axisN < isCAxis.size(); ++axisN) {
0466       if (isCAxis[axisN])
0467         slicePivots.push_back({axisN, {0, axesSizes[axisN]-1} });
0468       else
0469         slicePivots.push_back({axisN, {0} });
0470     }
0471 
0472     std::vector<size_t> res = sliceIndices(slicePivots);
0473     std::sort(res.begin(), res.end());
0474     res.erase( std::unique(res.begin(), res.end()), res.end() );
0475     return res;
0476   }
0477 
0478 
0479   /// @brief Helper function to count the number of under/other/overflow bins
0480   /// for a given axis @a axisN.
0481   ///
0482   /// Continuous axes have an underflow and an overflow bin (= 2).
0483   /// Discrete axes have an otherflow bin (= 1).
0484   template<typename... Axes>
0485   size_t Binning<Axes...>::countOverflowBins(const size_t axisN) const noexcept {
0486     std::vector<bool> CAxisIndices;
0487 
0488     auto getCAxisIndices = [&CAxisIndices](auto I){
0489       using isContinuous = typename Binning::template is_CAxis<I>;
0490       CAxisIndices.emplace_back(isContinuous::value);
0491     };
0492 
0493     MetaUtils::staticFor<Dimension::value>(getCAxisIndices);
0494     return CAxisIndices.at(axisN) + 1;
0495   }
0496 
0497   /// @brief Only visible in this translation unit.
0498   /*namespace {
0499     template<typename VecT, typename AxisT, typename IdxT>
0500     void appendIfCAxis(VecT& vec, AxisT& axis, IdxT I, std::true_type) {
0501       if(axis.maskedBins.size() != 0)
0502         vec.push_back({I, axis.maskedBins});
0503     }
0504 
0505     template<typename VecT, typename AxisT, typename IdxT>
0506     void appendIfCAxis(VecT& vec, AxisT& axis, IdxT I, std::false_type) {
0507       return;
0508     }
0509   }*/
0510 
0511   template<typename... Axes>
0512   void Binning<Axes...>::maskBin(const size_t index, const bool status) {
0513     Binning<Axes...>::maskBins({{index}}, status);
0514   }
0515 
0516   template<typename... Axes>
0517   void Binning<Axes...>::maskBin(const IndexArr& localIndices, const bool status) {
0518     const size_t gIndex = Binning<Axes...>::localToGlobalIndex(localIndices);
0519     Binning<Axes...>::maskBins({{gIndex}}, status);
0520   }
0521 
0522   template<typename... Axes>
0523   void Binning<Axes...>::maskBinAt(const EdgeTypesTuple& coords, const bool status) noexcept {
0524     const size_t gIndex = Binning<Axes...>::globalIndexAt(coords);
0525     Binning<Axes...>::maskBins({{gIndex}}, status);
0526   }
0527 
0528   template<typename... Axes>
0529   void Binning<Axes...>::maskBins(const std::vector<size_t>& indices, const bool status) {
0530     for (size_t i : indices) {
0531       const auto& itEnd = this->_maskedIndices.cend();
0532       const auto& res = std::find(this->_maskedIndices.cbegin(), itEnd, i);
0533       if (status && res == itEnd)  this->_maskedIndices.push_back(i);
0534       else if (!status && res != itEnd)  this->_maskedIndices.erase(res);
0535     }
0536   }
0537 
0538   template <typename... Axes>
0539   void Binning<Axes...>::maskSlice(const size_t dim, const size_t idx, const bool status) {
0540     //_binning.maskSlice(dim, idx, status);
0541     std::vector<std::pair<size_t, std::vector<size_t>>> slicePivot{{dim,{idx}}};
0542     std::vector<size_t> indices = Binning<Axes...>::sliceIndices(slicePivot);
0543     Binning<Axes...>::maskBins(indices, status);
0544   }
0545 
0546   template<typename... Axes>
0547   bool Binning<Axes...>::isMasked(const size_t i) const noexcept {
0548     const auto& itEnd = _maskedIndices.cend();
0549     return  std::find(_maskedIndices.cbegin(), itEnd, i) != itEnd;
0550   }
0551 
0552   // @todo Should this also check if the bin is masked?
0553   template<typename... Axes>
0554   bool Binning<Axes...>::isVisible(const size_t i) const noexcept {
0555     const std::vector<size_t> overflows = calcOverflowBinsIndices();
0556     const auto& itEnd = overflows.cend();
0557     return  std::find(overflows.cbegin(), itEnd, i) == itEnd;
0558   }
0559 
0560   template<typename... Axes>
0561   size_t Binning<Axes...>::calcSliceSize(const size_t pivotAxisN) const noexcept {
0562     const IndexArr axesSizes = _getAxesSizes();
0563     size_t sliceSize = 1;
0564     for (size_t iDim = 0; iDim < _dim; ++iDim) {
0565       if (iDim == pivotAxisN)
0566         continue;
0567       sliceSize *= (axesSizes[iDim]);
0568     }
0569     return sliceSize;
0570   }
0571 
0572   template<typename... Axes>
0573   std::vector<size_t>
0574   Binning<Axes...>::sliceIndices(std::vector<std::pair<size_t, std::vector<size_t>>> slicePivots) const noexcept {
0575 
0576     std::vector<size_t> slicesSizes;
0577     slicesSizes.reserve(slicePivots.size());
0578     size_t slicedIndicesVecSize = 0;
0579 
0580     for (const auto& slicePivot : slicePivots) {
0581       if(slicePivot.second.size() == 0)
0582         continue;
0583 
0584       const size_t& sliceSize = calcSliceSize(slicePivot.first);
0585 
0586       slicesSizes.emplace_back(sliceSize);
0587       slicedIndicesVecSize += sliceSize;
0588     }
0589 
0590     std::vector<size_t> slicedIndices;
0591     slicedIndices.reserve(slicedIndicesVecSize);
0592 
0593     auto appendSliceIndices = [&slicedIndices](std::vector<size_t>&& overflowSlice) {
0594       slicedIndices.insert(std::end(slicedIndices),
0595                            std::make_move_iterator(std::begin(overflowSlice)),
0596                            std::make_move_iterator(std::end(overflowSlice)));
0597     };
0598 
0599     for (const auto& slicePivot : slicePivots) {
0600       const size_t axisN = slicePivot.first;
0601       const auto& binPivots = slicePivot.second;
0602 
0603       for(const auto& binPivot : binPivots) {
0604         appendSliceIndices(sliceIndices(axisN, binPivot));
0605       }
0606     }
0607 
0608     return slicedIndices;
0609   }
0610 
0611   template<typename... Axes>
0612   std::vector<size_t> Binning<Axes...>::sliceIndices(const size_t axisN, const size_t binN) const noexcept {
0613     if(sizeof...(Axes) == 1) {
0614       return {binN};
0615     }
0616     /// x + y*width + (const value of pivot bin)*width*height + w*width*height*depth + ...
0617     const IndexArr axesSizes = _getAxesSizes();
0618     const size_t sliceSize = calcSliceSize(axisN);
0619 
0620     IndexArr multiIdx{};
0621     multiIdx[axisN] = binN;
0622     std::vector<size_t> slicedIndices;
0623 
0624     assert(axesSizes.size() != 0);
0625     slicedIndices.reserve(sliceSize);
0626 
0627     /* @note Iteratively generates permutations of multindex for fixed
0628     /// binN at axisN, e.g. (idx_1, idx_2, binN_axisN, idx_4)
0629     ///                      /        |          \         \
0630     ///       {0, axis1.size}  {0, axis2.size}   const    {0, axis4.size}
0631     ///
0632     /// Source: https://stackoverflow.com/a/30808351
0633     */
0634     size_t idxShift = axisN == 0 ? 1 : 0;
0635 
0636     size_t idx = idxShift;
0637 
0638     while (true) {
0639       slicedIndices.emplace_back(localToGlobalIndex(multiIdx));
0640 
0641       multiIdx[idx]++;
0642 
0643       while (multiIdx[idx] == axesSizes[idx] || idx == axisN) {
0644         // Overflow, we're done
0645         if (idx == axesSizes.size() - 1) {
0646           return slicedIndices;
0647         }
0648 
0649         if(idx != axisN)
0650           multiIdx[idx] = 0;
0651 
0652         idx++;
0653 
0654         if(idx != axisN)
0655           multiIdx[idx]++;
0656       }
0657 
0658       idx = idxShift;
0659     }
0660 
0661     return slicedIndices;
0662   }
0663 
0664   template <typename... Axes>
0665   template <size_t... AxisNs>
0666   void Binning<Axes...>::mergeBins(
0667     std::decay_t<decltype(AxisNs, std::declval<std::pair<size_t, size_t>>())>... mergeRanges) noexcept {
0668 
0669     ((void)axis<AxisNs>().mergeBins(mergeRanges), ...);
0670     updateMaskedBins();
0671 
0672   }
0673 
0674 
0675   template <typename... Axes>
0676   template <size_t I>
0677   const typename Binning<Axes...>::template getAxisT<I>&
0678   Binning<Axes...>::axis() const noexcept {
0679     return std::get<I>(_axes);
0680   }
0681 
0682   template <typename... Axes>
0683   template <size_t I>
0684   typename Binning<Axes...>::template getAxisT<I>&
0685   Binning<Axes...>::axis() noexcept {
0686     return std::get<I>(_axes);
0687   }
0688 
0689 
0690   template<typename... Axes>
0691   template<size_t I>
0692   auto Binning<Axes...>::_getAxesExcept() const noexcept {
0693     return MetaUtils::removeTupleElement<I>(_axes);
0694   }
0695 
0696   template<typename... Axes>
0697   typename Binning<Axes...>::IndexArr
0698   Binning<Axes...>::_getAxesSizes(const bool includeOverflows) const noexcept {
0699     IndexArr axesSizes{};
0700     auto extractSizes = [&axesSizes, &axes = _axes, &includeOverflows](auto I) {
0701                           const auto& axis = std::get<I>(axes);
0702                           axesSizes[I] = axis.numBins(includeOverflows);
0703                       };
0704 
0705     MetaUtils::staticFor<Dimension::value>(extractSizes);
0706 
0707     return axesSizes;
0708   }
0709 
0710   template<typename... Axes>
0711   size_t Binning<Axes...>::dim() const noexcept {
0712     return _dim;
0713   }
0714 
0715 
0716   template<typename... Axes>
0717   bool Binning<Axes...>::isCompatible(const Binning<Axes...>& other) const noexcept {
0718     // compatible if they have the same number of axes,
0719     // and those axes have the same bin edges
0720     if (_dim != other.dim())
0721       return false;
0722 
0723     std::array<bool, 1> isEqual{true};
0724     auto isEqAxes =
0725       [&isEqual, &axes1 = _axes, &axes2 = other._axes](auto I) {
0726         const auto& axis_lhs = std::get<I>(axes1);
0727         const auto& axis_rhs = std::get<I>(axes2);
0728 
0729         /// If one comparison fails, isEqal will stay false
0730         isEqual[0] &= axis_lhs.hasSameEdges(axis_rhs);
0731     };
0732 
0733     MetaUtils::staticFor<Dimension::value>(isEqAxes);
0734 
0735     return isEqual[0];
0736   }
0737 
0738   template<typename... Axes>
0739   size_t Binning<Axes...>::numBins(const bool includeOverflows, const bool includeMaskedBins) const noexcept {
0740     size_t nBins = 0;
0741     IndexArr axesSizes = _getAxesSizes(includeOverflows);
0742     assert(axesSizes.size() > 0);
0743     nBins = axesSizes[0];
0744     if constexpr (sizeof...(Axes) > 1) {
0745       for (size_t iDim = 1; iDim < _dim; ++iDim) {
0746         nBins *= (axesSizes[iDim]);
0747       }
0748     }
0749     const size_t maskedBins = includeMaskedBins? 0 : _maskedIndices.size();
0750     return nBins - maskedBins;
0751   }
0752 
0753   template<typename... Axes>
0754   size_t Binning<Axes...>::numBinsAt(const size_t axisN, const bool includeOverflows) const noexcept {
0755     IndexArr axesSizes = _getAxesSizes(includeOverflows);
0756     assert(axesSizes.size() > 0);
0757     return axesSizes[axisN];
0758   }
0759 
0760   template<typename... Axes>
0761   typename Binning<Axes...>::EdgeTypesTuple
0762   Binning<Axes...>::edgeTuple(const size_t index) const noexcept {
0763 
0764     EdgeTypesTuple coords;
0765     IndexArr indices = globalToLocalIndices(index);
0766 
0767     auto setCoords = [&coords, &indices, &axes = _axes](auto I) {
0768       using isContinuous = typename Binning::template is_CAxis<I>;
0769       const auto& axis = std::get<I>(axes);
0770       const size_t idx = std::get<I>(indices);
0771       if constexpr(isContinuous::value) {
0772         std::get<I>(coords) = axis.mid(idx);
0773       }
0774       else {
0775         std::get<I>(coords) = axis.edge(idx);
0776       }
0777     };
0778     MetaUtils::staticFor<Dimension::value>(setCoords);
0779 
0780     return coords;
0781   }
0782 
0783   template<typename... Axes>
0784   double Binning<Axes...>::dVol(const size_t index) const {
0785     double rtn = 1.0;
0786     IndexArr indices = globalToLocalIndices(index);
0787 
0788     auto getCAxisWidths = [&rtn, &indices, &axes = _axes](auto I){
0789       using isContinuous = typename Binning::template is_CAxis<I>;
0790       if constexpr (isContinuous::value) {
0791         const auto& axis = std::get<I>(axes);
0792         const size_t idx = std::get<I>(indices);
0793         rtn *= axis.width(idx);
0794       }
0795     };
0796     MetaUtils::staticFor<Binning::Dimension::value>(getCAxisWidths);
0797 
0798     return rtn;
0799   }
0800 
0801 }
0802 
0803 #endif