Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-04-19 09:13:39

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