Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-30 08:56:54

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_BinnedStorage_H
0007 #define YODA_BinnedStorage_H
0008 
0009 #include "YODA/BinnedAxis.h"
0010 #include "YODA/Binning.h"
0011 #include "YODA/Bin.h"
0012 
0013 #include <memory>
0014 #include <vector>
0015 
0016 namespace YODA {
0017 
0018 
0019   /// @brief Vector wrapper used to interact with bins vectors.
0020   /// Able to hide overflow and hidden bins.
0021   template <class VecT>
0022   class BinsVecWrapper {
0023   public:
0024     /// @brief Iterator for range based for loops
0025     class myIt;
0026 
0027     using BinType = std::decay_t<decltype(*std::declval<VecT>().begin())>;
0028 
0029     BinsVecWrapper() = delete;
0030     /// @brief HiddenBins std::vector<size_t> must be sorted.
0031     BinsVecWrapper(VecT& bins, const std::vector<size_t>& hiddenBins)
0032       : _bins(bins), _hiddenBins(hiddenBins) {}
0033 
0034     BinsVecWrapper(BinsVecWrapper&& other)
0035       : _bins(std::move(other._bins)), _hiddenBins(std::move(other._hiddenBins)) {}
0036 
0037     myIt begin() const  { return myIt(_bins, _hiddenBins); }
0038     myIt end()   const  { return myIt(_bins); }
0039 
0040     size_t size() const { return _bins.size() - _hiddenBins.size(); }
0041 
0042     const BinType& operator[](size_t index) const { return _bins[index]; }
0043 
0044     BinType& operator[](size_t index) { return _bins[index]; }
0045 
0046   private:
0047 
0048     VecT& _bins;
0049     std::vector<size_t> _hiddenBins;
0050   };
0051 
0052   template<typename VecT>
0053   class BinsVecWrapper<VecT>::myIt {
0054   private:
0055     using IterT = std::conditional_t<std::is_const<VecT>::value,
0056                                         typename VecT::const_iterator,
0057                                         typename VecT::iterator>;
0058   public:
0059 
0060     myIt(VecT& bins,
0061          const std::vector<size_t>& hiddenBins)
0062         : _ptr(bins.begin()),
0063           hiddenCurrPtr(hiddenBins.begin()),
0064           hiddenEndPtr(hiddenBins.end()),
0065           binsEndPtr(bins.end()) {
0066 
0067         if (hiddenCurrPtr != hiddenEndPtr && 0 == *hiddenCurrPtr){
0068 
0069           ++hiddenCurrPtr; /// Reveal next hidden bin.
0070           ++(*this);       /// Advance iterator manually
0071 
0072         }
0073       }
0074 
0075     myIt(VecT& bins)
0076         :  _ptr(bins.end()),
0077            //hiddenCurrPtr(std::vector<size_t>::const_iterator()),
0078            //hiddenEndPtr(std::vector<size_t>::const_iterator()),
0079            binsEndPtr(bins.end()) {}
0080 
0081     myIt operator++() noexcept {
0082       /// @brief Increment iterator. If iterator points on hidden element, skip until
0083       /// non hidden element or end of the vector.
0084       ++_ptr;
0085       ++currBinIdx;
0086 
0087       while (_ptr != binsEndPtr &&
0088             hiddenCurrPtr != hiddenEndPtr &&
0089             currBinIdx == *hiddenCurrPtr) {
0090 
0091         ++currBinIdx;
0092         ++hiddenCurrPtr;
0093         ++_ptr;
0094       }
0095       return *this;
0096     }
0097 
0098     bool operator!=(const myIt& other) const noexcept {
0099       return _ptr != other._ptr;
0100     }
0101 
0102     typename std::iterator_traits<IterT>::reference operator*() noexcept { return *_ptr; }
0103 
0104   private:
0105     IterT _ptr;
0106     std::vector<size_t>::const_iterator hiddenCurrPtr;
0107     std::vector<size_t>::const_iterator hiddenEndPtr;
0108     IterT binsEndPtr;
0109     size_t currBinIdx = 0;
0110   };
0111 
0112 
0113   /// @brief BinnedStorage, stores the bins and coordinates access to them
0114   template <typename BinContentT, typename... AxisT>
0115   class BinnedStorage {
0116   protected:
0117 
0118     /// @brief Convenience alias to be used in constructor
0119     using BinningT = Binning<std::decay_t<decltype(std::declval<Axis<AxisT>>())>...>;
0120     using BinT = Bin<sizeof...(AxisT), BinContentT, BinningT>;
0121     using BinsVecT = std::vector<BinT>;
0122     using BaseT = BinnedStorage<BinContentT, AxisT...>;
0123 
0124   public:
0125 
0126     using BinningType = BinningT;
0127     using BinType = BinT;
0128     using BinDimension = std::integral_constant<size_t, sizeof...(AxisT)>;
0129 
0130 
0131     /// @name Constructors
0132     /// @{
0133 
0134     /// @brief Nullary constructor for unique pointers etc.
0135     BinnedStorage() : _binning(std::vector<AxisT>{}...) {
0136       fillBins();
0137     }
0138 
0139     /// @brief Constructs BinnedStorage from Binning.
0140     BinnedStorage(const BinningT& binning) : _binning(binning) {
0141       fillBins();
0142     }
0143 
0144     /// @brief Constructs BinnedStorage from Binning. Rvalue.
0145     BinnedStorage(BinningT&& binning) : _binning(std::move(binning)) {
0146       fillBins();
0147     }
0148 
0149     /// @brief Constructs binning from an adapter and vectors of axes' edges
0150     BinnedStorage(const std::vector<AxisT>&... edges) : _binning(edges...) {
0151       fillBins();
0152     }
0153 
0154     /// @brief Constructs binning from an adapter and Rvalue vectors of axes' edges
0155     BinnedStorage(std::vector<AxisT>&&... edges) : _binning(std::move(edges)...) {
0156       fillBins();
0157     }
0158 
0159     /// @brief Constructs binning from an adapter and Rvalue initializer lists of axes' edges
0160     BinnedStorage(std::initializer_list<AxisT>&&... edges) : _binning(std::vector<AxisT>{edges}...) {
0161       fillBins();
0162     }
0163 
0164     /// @brief Constructs binning from an adapter and a sequence of axes
0165     BinnedStorage(const Axis<AxisT>&... axes) : _binning(axes...) {
0166       fillBins();
0167     }
0168 
0169     /// @brief Constructs binning from an adapter and a sequence of Rvalue axes
0170     BinnedStorage(Axis<AxisT>&&... axes) : _binning(std::move(axes)...) {
0171       fillBins();
0172     }
0173 
0174     /// @brief Copy constructor.
0175     BinnedStorage(const BinnedStorage& other) : _binning(other._binning) {
0176       fillBins(other._bins);
0177     }
0178 
0179     /// @brief Move constructor.
0180     BinnedStorage(BinnedStorage&& other) : _binning(std::move(other._binning)) {
0181       fillBins(std::move(other._bins));
0182     }
0183 
0184     /// @}
0185 
0186     /// @name Methods
0187     /// @{
0188 
0189     /// @brief Total dimension of the object ( = number of axes + content)
0190     size_t dim() const noexcept {
0191       return sizeof...(AxisT) + 1;
0192     }
0193 
0194     /// @brief Returns reference to the bin at idx.
0195     /// @note Bin position is calculated using this pattern:
0196     ///     x + y*width + z*width*height + w*width*height*depth + ...
0197     /// where (x,y,z) are bin positions on three different axes, and
0198     /// width, height, and depth are length of these axes. Axes are
0199     /// queried for position (translating coordinates in positions)
0200     /// in the order of axes specification in Binning template instantiation.
0201     BinT& bin(size_t idx) noexcept {
0202       return _bins.at(idx);
0203     }
0204 
0205     /// @brief Returns Bin at idx.
0206     const BinT& bin(size_t idx) const noexcept {
0207       return _bins.at(idx);
0208     }
0209 
0210     /// @brief Bin access using local bin indices.
0211     BinT& bin(const std::array<size_t, sizeof...(AxisT)>& idxLocal) noexcept {
0212       return bin( _binning.localToGlobalIndex(idxLocal) );
0213     }
0214 
0215     /// @brief Bin access using local bin indices.
0216     const BinT& bin(const std::array<size_t, sizeof...(AxisT)>& idxLocal) const noexcept {
0217       return bin( _binning.localToGlobalIndex(idxLocal) );
0218     }
0219 
0220     /// @brief Returns reference to the bin at coordinates.
0221     BinT& binAt(typename BinningT::EdgeTypesTuple&& coords) noexcept {
0222       const size_t binIdx = _binning.globalIndexAt(coords);
0223       return bin(binIdx);
0224     }
0225 
0226     /// @brief Returns reference to the bin at coordinates (const version).
0227     const BinT& binAt(typename BinningT::EdgeTypesTuple&& coords) const noexcept {
0228       const size_t binIdx = _binning.globalIndexAt(coords);
0229       return bin(binIdx);
0230     }
0231 
0232     /// @brief Sets the bin corresponding to @a coords with an rvalue @a content.
0233     ///
0234     /// @note Cython is not a fan of perfext forwarding yet
0235     void set(typename BinningT::EdgeTypesTuple&& coords, BinContentT&& content) noexcept {
0236       const size_t binIdx = _binning.globalIndexAt(coords);
0237       _bins[binIdx] = std::move(content);
0238     }
0239 
0240     /// @brief Sets the bin corresponding to @a coords with @a content.
0241     void set(typename BinningT::EdgeTypesTuple&& coords, const BinContentT& content) noexcept {
0242       const size_t binIdx = _binning.globalIndexAt(coords);
0243       _bins[binIdx] = content;
0244     }
0245 
0246     /// @brief Sets the bin corresponding to @a binIndex with an rvalue @a content.
0247     ///
0248     /// @note Cython is not a fan of perfect forwarding yet
0249     void set(const size_t binIdx, BinContentT&& content) noexcept {
0250       _bins[binIdx] = std::move(content);
0251     }
0252 
0253     /// @brief Sets the bin corresponding to @a binIndex with @a content.
0254     void set(const size_t binIdx, const BinContentT& content) noexcept {
0255       _bins[binIdx] = content;
0256     }
0257 
0258     /// @brief Calculates indices of bins which are marked or located
0259     /// in the overflow.
0260     std::vector<size_t> calcIndicesToSkip(const bool includeOverflows, const bool includeMaskedBins) const noexcept {
0261 
0262       // if there are no bins, exit early
0263       if (!_binning.numBins(!includeOverflows, !includeMaskedBins))  return {};
0264 
0265       std::vector<size_t> indicesToSkip;
0266 
0267       // define a lambda
0268       auto appendIndicesVec = [&indicesToSkip](std::vector<size_t>&& indicesVec) {
0269         indicesToSkip.insert(std::end(indicesToSkip),
0270                              std::make_move_iterator(std::begin(indicesVec)),
0271                              std::make_move_iterator(std::end(indicesVec)));
0272       };
0273 
0274       // only calculate the masked indices when
0275       // the masked bins are to be skipped over
0276       if(!includeOverflows) {
0277         appendIndicesVec(_binning.calcOverflowBinsIndices());
0278       }
0279 
0280       if (!includeMaskedBins) {
0281         appendIndicesVec(_binning.maskedBins());
0282       }
0283 
0284       // sort and remove duplicates
0285       std::sort(indicesToSkip.begin(), indicesToSkip.end());
0286       indicesToSkip.erase( std::unique(indicesToSkip.begin(), indicesToSkip.end()),
0287                            indicesToSkip.end() );
0288 
0289       return indicesToSkip;
0290     }
0291 
0292 
0293     /// @brief Returns bins vector wrapper, which skips masked elements
0294     /// when iterated over.
0295     ///
0296     /// @note Here, @a includeoverflows refers to the return value,
0297     /// i.e. the default value false implies that calcIndicesToSkip
0298     /// should return an array of under/overflow indices
0299     BinsVecWrapper<BinsVecT> bins(const bool includeOverflows = false,
0300                                   const bool includeMaskedBins = false) noexcept {
0301       return BinsVecWrapper<BinsVecT>(_bins,
0302                 calcIndicesToSkip(includeOverflows, includeMaskedBins));
0303     }
0304 
0305     /// @brief Const version.
0306     ///
0307     /// @note Here, @a includeoverflows refers to the return value,
0308     /// i.e. the default value false implies that calcIndicesToSkip
0309     /// should return an array of under/overflow indices
0310     const BinsVecWrapper<const BinsVecT> bins(const bool includeOverflows = false,
0311                                               const bool includeMaskedBins = false) const noexcept {
0312       return BinsVecWrapper<const BinsVecT>(_bins,
0313                 calcIndicesToSkip(includeOverflows, includeMaskedBins));
0314     }
0315 
0316     /// @}
0317 
0318     /// @name Binning utilities
0319     /// @{
0320 
0321     /// @brief Returns dimension underlying binning object reference.
0322     const BinningT& binning() const noexcept {
0323       return _binning;
0324     }
0325 
0326     /// @brief Returns dimension of binning.
0327     size_t binDim() const noexcept {
0328       return _binning.dim();
0329     }
0330 
0331     /// @brief Number of bins in the BinnedStorage.
0332     size_t numBins(const bool includeOverflows = false, const bool includeMaskedBins = false) const noexcept {
0333       return _binning.numBins(includeOverflows, includeMaskedBins);
0334     }
0335 
0336     /// @brief Number of bins in the BinnedStorage.
0337     ///
0338     /// @note need "AtAxis" in function name since
0339     /// numBins(1) would be ambiguous otherwise
0340     size_t numBinsAt(const size_t axisN, const bool includeOverflows = false) const noexcept {
0341       size_t nOverflows = includeOverflows? 0 : _binning.countOverflowBins(axisN);
0342       return _binning.numBinsAt(axisN) - nOverflows;
0343     }
0344 
0345     /// @brief Reset the BinnedStorage.
0346     void reset() noexcept { clearBins(); }
0347 
0348     /// @brief Deletes all bins and creates empty new ones.
0349     ///
0350     /// @note Bins marked as masked will remain masked
0351     void clearBins() noexcept {
0352       _bins.clear();
0353       fillBins();
0354     }
0355 
0356     /// @brief Returns the edges of axis N by value.
0357     template <size_t I, typename E = typename BinningT::template getEdgeT<I>>
0358     std::vector<E> edges(const bool includeOverflows = false) const noexcept {
0359       return BaseT::_binning.template edges<I>(includeOverflows);
0360     }
0361 
0362     /// @brief Templated version to get bin widths of axis I by value.
0363     ///
0364     /// Overflows are included depending on @a includeOverflows
0365     /// Needed by axis-specific version from AxisMixin
0366     ///
0367     /// @note This version is only supported for continuous axes.
0368     template <size_t I, typename E = typename BinningT::template getEdgeT<I>>
0369     std::enable_if_t<std::is_floating_point<E>::value, std::vector<E>>
0370     widths(const bool includeOverflows = false) const noexcept {
0371       return BaseT::_binning.template widths<I>(includeOverflows);
0372     }
0373 
0374     /// @brief Templated version to get bin mins of axis I by value.
0375     ///
0376     /// Overflows are included depending on @a includeOverflows
0377     /// Needed by axis-specific version from AxisMixin
0378     ///
0379     /// @note This version is only supported for continuous axes.
0380     template <size_t I, typename E = typename BinningT::template getEdgeT<I>>
0381     std::enable_if_t<std::is_floating_point<E>::value, std::vector<E>>
0382     mins(const bool includeOverflows = false) const noexcept {
0383       return BaseT::_binning.template mins<I>(includeOverflows);
0384     }
0385 
0386     /// @brief Templated version to get bin maxs of axis I by value.
0387     ///
0388     /// Overflows are included depending on @a includeOverflows
0389     /// Needed by axis-specific version from AxisMixin
0390     ///
0391     /// @note This version is only supported for continuous axes.
0392     template <size_t I, typename E = typename BinningT::template getEdgeT<I>>
0393     std::enable_if_t<std::is_floating_point<E>::value, std::vector<E>>
0394     maxs(const bool includeOverflows = false) const noexcept {
0395       return BaseT::_binning.template maxs<I>(includeOverflows);
0396     }
0397 
0398     /// @brief Templated version to get bin mids of axis I by value.
0399     ///
0400     /// Overflows are included depending on @a includeOverflows
0401     /// Needed by axis-specific version from AxisMixin
0402     ///
0403     /// @note This version is only supported for continuous axes.
0404     template <size_t I, typename E = typename BinningT::template getEdgeT<I>>
0405     std::enable_if_t<std::is_floating_point<E>::value, std::vector<E>>
0406     mids(const bool includeOverflows = false) const noexcept {
0407       return BaseT::_binning.template mids<I>(includeOverflows);
0408     }
0409 
0410     /// @brief Get the lowest non-overflow edge of the axis
0411     ///
0412     /// @note This method is only supported for continuous axes
0413     template <size_t I, typename E = typename BinningT::template getEdgeT<I>>
0414     std::enable_if_t<std::is_floating_point<E>::value, E> min() const noexcept {
0415       return BaseT::_binning.template min<I>();
0416     }
0417 
0418     /// @brief Get the highest non-overflow edge of the axis
0419     ///
0420     /// @note This method is only supported for continuous axes
0421     template <size_t I, typename E = typename BinningT::template getEdgeT<I>>
0422     std::enable_if_t<std::is_floating_point<E>::value, E> max() const noexcept {
0423       return BaseT::_binning.template max<I>();
0424     }
0425 
0426     /// @}
0427 
0428 
0429     /// @name Bin-masking utilities
0430     /// @{
0431 
0432     /// @brief Mask a range of bins
0433     void maskBins(const std::vector<size_t>& indicesToMask, const bool status = true) noexcept {
0434       _binning.maskBins(indicesToMask, status);
0435     }
0436 
0437     /// @brief Mask a bin at a given index
0438     void maskBin(const size_t indexToMask, const bool status = true) noexcept {
0439       _binning.maskBin(indexToMask, status);
0440     }
0441 
0442     /// @brief Mask a slice of the binning at local bin index @a idx along axis dimesnion @a dim
0443     void maskSlice(const size_t dim, const size_t idx, const bool status = true) {
0444       _binning.maskSlice(dim, idx, status);
0445     }
0446 
0447     /// @brief Mask a bin at a given set of corrdinates
0448     void maskBinAt(typename BinningT::EdgeTypesTuple&& coords, const bool status = true) noexcept {
0449       _binning.maskBinAt(coords, status);
0450     }
0451 
0452     bool isMasked(const size_t binIndex) const noexcept {
0453       return _binning.isMasked(binIndex);
0454     }
0455 
0456     std::vector<size_t> maskedBins() const noexcept {
0457       return _binning.maskedBins();
0458     }
0459 
0460     bool isVisible(const size_t binIndex) const noexcept {
0461       return _binning.isVisible(binIndex);
0462     }
0463 
0464     /// @}
0465 
0466 
0467     /// @name Bin-merging and -slicing utilities
0468     /// @{
0469 
0470     /// @brief Merge bins from A to B at G axis.
0471     ///
0472     /// @param mergeRanges Call using following form mergeBins<1,2>({0, 5}, {3, 4}).
0473     ///
0474     /// @note Only works when the BinContentT has += operator. Otherwise merge operation
0475     /// would make little sense since there will be no effects on the binning except it's
0476     /// shrinking (no statistical strengthening).
0477     ///
0478     /// @note Merging unmasks previously masked bins.
0479     ///
0480     /// @note RetT to make enable_if work.
0481     template <size_t... AxisNs, class RetT = void>
0482     auto mergeBins(
0483       std::decay_t<decltype(AxisNs, std::declval<std::pair<size_t, size_t>>())>... mergeRanges)
0484       noexcept -> std::enable_if_t<MetaUtils::is_detected_v<MetaUtils::operatorTraits::addition_assignment_t, BinContentT>, RetT>
0485     {
0486       auto mergeStorageBins =
0487         [&binning = BaseT::_binning, &binStorage = BaseT::_bins](auto I, const auto& mergeRangePair){
0488           assert(mergeRangePair.first < mergeRangePair.second);
0489 
0490           auto append = [&binStorage](const auto& pivotBinsIndices, const auto& binsIndicesToMerge){
0491             assert(pivotBinsIndices.size() == binsIndicesToMerge.size());
0492             /*for (const auto& k : binsIndicesToMerge) { std::cout << " " << k; }
0493             std::cout << std::endl;
0494             for (const auto& k : pivotBinsIndices) { std::cout << " " << k; }
0495             std::cout << std::endl;*/
0496 
0497             // first merge the bins based on old set of indices
0498             // unless the bins are masked
0499             for (size_t i = 0; i < pivotBinsIndices.size(); ++i) {
0500               auto& pivotBin = binStorage[pivotBinsIndices[i]];
0501               pivotBin += std::move(binStorage[binsIndicesToMerge[i]]);
0502             }
0503             // then erase the bins (which will change the set of indices)
0504             binStorage.erase(
0505               std::remove_if(binStorage.begin(), binStorage.end(), [&](const auto& b) {
0506                 return std::find(binsIndicesToMerge.begin(), binsIndicesToMerge.end(), b.index()) != binsIndicesToMerge.end();
0507               }), binStorage.end());
0508           };
0509 
0510           ssize_t nBinRowsToBeMerged = mergeRangePair.second - mergeRangePair.first;
0511 
0512           size_t currBinRowIdx = mergeRangePair.first;
0513           size_t nextBinRowIdx = mergeRangePair.first + 1;
0514           //std::cout << nBinRowsToBeMerged << " " << currBinRowIdx << " " << nextBinRowIdx << std::endl;
0515 
0516           while (nBinRowsToBeMerged--) {
0517             /// @note Binning iteratively shrinks, so the next bin slice to merge
0518             /// will always be the next.
0519             append(binning.sliceIndices(I, mergeRangePair.first), binning.sliceIndices(I, nextBinRowIdx));
0520             binning.template mergeBins<I>({currBinRowIdx, nextBinRowIdx});
0521           }
0522         };
0523 
0524       ((void)mergeStorageBins(std::integral_constant<size_t, AxisNs>(), mergeRanges), ...);
0525 
0526     }
0527 
0528 
0529     /// @brief Split this BinnedStorage into a vector of BinnedStorages along @a axisN
0530     ///
0531     /// The binning dimension of the returned objects are reduced by one unit.
0532     /// @note Requires at least two binning dimensions.
0533     template<size_t axisN, template<typename...> typename BinnedT, typename Func,
0534              typename = std::enable_if_t< (axisN < sizeof...(AxisT) && sizeof...(AxisT)>=2) >>
0535     auto mkBinnedSlices(Func&& how2add, const bool includeOverflows=false) const {
0536 
0537       size_t vecN = BaseT::numBinsAt(axisN, includeOverflows);
0538       auto binnedSlice = _mkBinnedT<BinnedT>(_binning.template _getAxesExcept<axisN>());
0539       std::vector<decltype(binnedSlice)> rtn(vecN, binnedSlice);
0540       for (size_t i = 0; i < vecN; ++i) {
0541 
0542         auto mkSlice = [&oldBins = _bins, &how2add, &binnedSlice = rtn[i]](const auto& indicesToCopy) {
0543           assert(binnedSlice.numBins(true) == indicesToCopy.size());
0544 
0545           // for any given pivot, add the content
0546           // from the old slice to the new slice
0547           for (size_t i = 0; i < binnedSlice.numBins(true); ++i) {
0548             auto& pivotBin = binnedSlice.bin(i);
0549             auto& binToCopy = oldBins[indicesToCopy[i]];
0550             how2add(pivotBin, binToCopy);
0551           }
0552         };
0553 
0554         // get bin slice for any given bin i along the axis that is to be
0555         // sliced, then make the estimates for the new binning
0556         mkSlice(_binning.sliceIndices(axisN, i + !includeOverflows));
0557 
0558       }
0559       return rtn;
0560     }
0561 
0562     /// @}
0563 
0564 
0565     /// @name Assignment and Boolean operators
0566     /// @{
0567 
0568     /// @brief Copy assignment
0569     BinnedStorage& operator = (const BinnedStorage& other) noexcept {
0570       if (this != &other) {
0571         _binning = other._binning;
0572         fillBins(other._bins);
0573       }
0574       return *this;
0575     }
0576 
0577     /// @brief Move assignment
0578     BinnedStorage& operator = (BinnedStorage&& other) noexcept {
0579       if (this != &other) {
0580         _binning = std::move(other._binning);
0581         fillBins(std::move(other._bins));
0582       }
0583       return *this;
0584     }
0585 
0586     /// @brief Compares BinnedStorages for equality, e.g. dimensions of
0587     /// underlying binnings and all axes edges are equal.
0588     ///
0589     /// @note This only checks for compatible binning but not equal content.
0590     bool operator == (const BinnedStorage& other) const noexcept {
0591       return _binning.isCompatible(other._binning);
0592     }
0593 
0594     /// @brief Compares BinnedStorages for inequality.
0595     ///
0596     /// @note This only checks for compatible binning but not equal content.
0597     bool operator != (const BinnedStorage& other) const noexcept {
0598         return ! operator == (other);
0599     }
0600 
0601     /// @brief Helper function to simplify Cython wrapping
0602     std::vector<size_t> _global2local(size_t idx) const noexcept {
0603       const auto& indices = _binning.globalToLocalIndices(idx);
0604       return std::vector<size_t>(indices.begin(), indices.end());
0605     }
0606 
0607     /// @brief Helper function to simplify Cython wrapping
0608     size_t _local2global(const std::vector<size_t>& indices) const {
0609       assert(indices.size() == sizeof...(AxisT));
0610       std::array<size_t, sizeof...(AxisT)> arr;
0611       std::copy_n(std::make_move_iterator(indices.begin()), sizeof...(AxisT), arr.begin());
0612       return _binning.localToGlobalIndex(arr);
0613     }
0614 
0615     /// @}
0616 
0617 
0618     protected:
0619 
0620       /// @name Utilities
0621       /// @{
0622 
0623       /// @brief Fills bins with wrapped BinContent objects
0624       void fillBins() noexcept {
0625         _bins.reserve(_binning.numBins());
0626 
0627         for (size_t i = 0; i < _binning.numBins(); ++i) {
0628           _bins.emplace_back(i, _binning);
0629         }
0630       }
0631 
0632       void fillBins(const BinsVecT& bins) noexcept {
0633         _bins.clear();
0634         _bins.reserve(_binning.numBins());
0635         for (const auto& b : bins) {
0636           _bins.emplace_back(b, _binning);
0637         }
0638       }
0639 
0640       void fillBins(BinsVecT&& bins) noexcept {
0641         _bins.clear();
0642         _bins.reserve(_binning.numBins());
0643         for (auto&& b : bins) {
0644           _bins.emplace_back(std::move(b), _binning);
0645         }
0646       }
0647 
0648       /// @brief Helper function to unpack the tuple of new axes and make a new BinnedT
0649       template<template<typename...> typename BinnedT, class... newAxes, size_t... As>
0650       auto _mkBinnedT_aux(const std::tuple<newAxes...>& t, std::index_sequence<As...>) const {
0651         return BinnedT<typename newAxes::EdgeT...>(std::get<As>(t)...);
0652       }
0653 
0654       /// @brief Helper function to make a new BinnedT from a tuple of new axes
0655       template <template<typename...> typename BinnedT, class... newAxes>
0656       auto _mkBinnedT(const std::tuple<newAxes...>& t) const {
0657         return _mkBinnedT_aux<BinnedT>(t, std::make_index_sequence<sizeof...(newAxes)>{});
0658       }
0659 
0660       /// @}
0661 
0662       /// @brief 1-dim vector, which should be indexed by globalIndex of
0663       /// the underlying _binning object.
0664       BinsVecT _bins;
0665 
0666       BinningT _binning;
0667 
0668   };
0669 
0670 } // namespace YODA
0671 
0672 #endif