Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-16 09:13:06

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_BinnedEstimate_h
0007 #define YODA_BinnedEstimate_h
0008 
0009 #include "YODA/AnalysisObject.h"
0010 #include "YODA/BinnedStorage.h"
0011 #include "YODA/Estimate.h"
0012 #include "YODA/Scatter.h"
0013 #include "YODA/Transformation.h"
0014 #include "YODA/Utils/BinnedUtils.h"
0015 #include <regex>
0016 
0017 #ifdef HAVE_HDF5
0018 #include "YODA/Utils/H5Utils.h"
0019 #endif
0020 
0021 
0022 namespace YODA {
0023 
0024   /// @note Anonymous namespace to limit visibility to this file
0025   namespace {
0026     /// @brief Nullify bin uncertainty for discrete axes
0027     template<size_t axisN, typename BinT>
0028     std::pair<double, double> nullifyIfDisc(const BinT& /* b */, const double /* val */, std::false_type, const double null = 0.0) {
0029       return { null, null };
0030     }
0031 
0032     /// @brief Work out bin uncertainty with respect to
0033     /// central value for continuous axes
0034     template<size_t axisN, typename BinT>
0035     std::pair<double, double> nullifyIfDisc(const BinT& b, const double val, std::true_type, const double /* null */ = 0.0) {
0036       return { val - b.template min<axisN>(), b.template max<axisN>() - val };
0037     }
0038 
0039     /// @brief Unify bin interval for discrete axes
0040     template<size_t axisN, typename BinT>
0041     double unifyIfDisc(const BinT& /* b */, std::false_type, const double unity = 1.0) {
0042       return unity;
0043     }
0044 
0045     /// @brief Get bin width for continuous axes
0046     template<size_t axisN, typename BinT>
0047     double unifyIfDisc(const BinT& b, std::true_type, const double /* unity */ = 1.0) {
0048       return b.template width<axisN>();
0049     }
0050 
0051     /// @brief Return bin index for non-integral discrete axes
0052     template<size_t axisN, typename BinT>
0053     double coordPicker(const BinT& b, std::false_type, std::false_type) {
0054       return b.index();
0055     }
0056 
0057     /// @brief Return bin edge for integral discrete axes
0058     template<size_t axisN, typename BinT>
0059     double coordPicker(const BinT& b, std::true_type, std::false_type) {
0060       return b.template edge<axisN>();
0061     }
0062     /// @brief Return bin mid point for continuous axes
0063     template<size_t axisN, typename BinT>
0064     double coordPicker(const BinT& b, std::true_type, std::true_type) {
0065       return b.template mid<axisN>();
0066     }
0067   }
0068 
0069 
0070   /// Forward declaration
0071   template <typename... AxisT>
0072   class BinnedEstimate;
0073 
0074   /// @brief EstimateStorage convenience class based on BinnedStorage.
0075   ///
0076   /// @note We use this abstraction layer to implement the bulk once and only once.
0077   /// The user-facing BinnedEstimate type will inherit all its methods from this
0078   /// base class along with a few axis-specifc mixins.
0079   template<typename... AxisT>
0080   class EstimateStorage
0081       : public BinnedStorage<Estimate, AxisT...>,
0082         public AnalysisObject {
0083   public:
0084 
0085     using BaseT = BinnedStorage<Estimate, AxisT...>;
0086     using BinningT = typename BaseT::BinningT;
0087     using BinT = typename BaseT::BinT;
0088     using BinType = BinT;
0089     using AnalysisObject::operator =;
0090 
0091     /// @name Constructors
0092     /// @{
0093 
0094     /// @brief Nullary constructor for unique pointers etc.
0095     EstimateStorage(const std::string& path = "", const std::string& title = "")
0096         : BaseT(), AnalysisObject(mkTypeString<AxisT...>(), path, title) { }
0097 
0098     /// @brief Constructor giving explicit bin edges as rvalue reference.
0099     ///
0100     /// Discrete axes have as many edges as bins.
0101     /// Continuous axes have bins.size()+1 edges, the last one
0102     /// being the upper bound of the last bin.
0103     EstimateStorage(std::vector<AxisT>&&... binsEdges, const std::string& path = "", const std::string& title = "")
0104         : BaseT(Axis<AxisT>(std::move(binsEdges))...), AnalysisObject(mkTypeString<AxisT...>(), path, title) { }
0105 
0106     /// @brief Constructor giving explicit bin edges as lvalue reference.
0107     ///
0108     /// Discrete axes have as many edges as bins.
0109     /// Continuous axes have bins.size()+1 edges, the last one
0110     /// being the upper bound of the last bin.
0111     EstimateStorage(const std::vector<AxisT>&... binsEdges, const std::string& path = "", const std::string& title = "")
0112         : BaseT(Axis<AxisT>(binsEdges)...), AnalysisObject(mkTypeString<AxisT...>(), path, title) { }
0113 
0114     /// @brief Constructor giving explicit bin edges as initializer list.
0115     ///
0116     /// Discrete axes have as many edges as bins.
0117     /// Continuous axes have bins.size()+1 edges, the last one
0118     /// being the upper bound of the last bin.
0119     EstimateStorage(std::initializer_list<AxisT>&&... binsEdges, const std::string& path = "", const std::string& title = "")
0120         : BaseT(Axis<AxisT>(std::move(binsEdges))...), AnalysisObject(mkTypeString<AxisT...>(), path, title) { }
0121 
0122     /// @brief Constructor giving range and number of bins.
0123     template <typename EdgeT = double, typename = enable_if_all_CAxisT<EdgeT, AxisT...>>
0124     EstimateStorage(const std::vector<size_t>& nBins,
0125                     const std::vector<std::pair<EdgeT, EdgeT>>& limitsLowUp,
0126                     const std::string& path = "", const std::string& title = "")
0127         //: BaseT( BinningT(nBins, limitsLowUp, std::make_index_sequence<sizeof...(AxisT)>{}) ),
0128         : BaseT( _mkBinning(nBins, limitsLowUp, std::make_index_sequence<sizeof...(AxisT)>{}) ),
0129           AnalysisObject(mkTypeString<AxisT...>(), path, title) { }
0130 
0131     /// @brief Constructor given a binning type
0132     EstimateStorage(const BinningT& binning, const std::string& path = "", const std::string& title = "")
0133         : BaseT(binning), AnalysisObject(mkTypeString<AxisT...>(), path, title) { }
0134 
0135     /// @brief Constructor given an rvalue BinningT
0136     EstimateStorage(BinningT&& binning, const std::string& path = "", const std::string& title = "")
0137         : BaseT(std::move(binning)), AnalysisObject(mkTypeString<AxisT...>(), path, title) { }
0138 
0139     /// @brief Constructor given a scatter
0140     template <typename EdgeT = double, typename = enable_if_all_CAxisT<EdgeT, AxisT...>>
0141     EstimateStorage(const ScatterND<sizeof...(AxisT)+1>& s, const std::string& path = "", const std::string& title = "")
0142          : BaseT(_mkBinning(s, std::make_index_sequence<sizeof...(AxisT)>{})),
0143            AnalysisObject(mkTypeString<AxisT...>(), path, title) { }
0144 
0145     /// @brief Copy constructor
0146     ///
0147     /// @todo Also allow title setting from the constructor?
0148     EstimateStorage(const EstimateStorage& other, const std::string& path = "") : BaseT(other),
0149         AnalysisObject(mkTypeString<AxisT...>(), path!=""? path : other.path(), other, other.title()) { }
0150 
0151     /// @brief Move constructor
0152     ///
0153     /// @todo Also allow title setting from the constructor?
0154     EstimateStorage(EstimateStorage&& other, const std::string& path = "") : BaseT(std::move(other)),
0155         AnalysisObject(mkTypeString<AxisT...>(), path!=""? path : other.path(), other, other.title()) { }
0156 
0157     /// @brief Make a copy on the stack
0158     EstimateStorage clone() const noexcept {
0159       return EstimateStorage(*this);
0160     }
0161 
0162     /// @brief Make a copy on the heap
0163     EstimateStorage* newclone() const noexcept {
0164       return new EstimateStorage(*this);
0165     }
0166 
0167     /// @}
0168 
0169 
0170     /// @name I/O
0171     /// @{
0172 
0173   private:
0174 
0175     // @brief Render information about this AO (private implementation)
0176     template<size_t... Is>
0177     void _renderYODA_aux(std::ostream& os, const int width, std::index_sequence<Is...>) const noexcept {
0178 
0179       // print bin edges
0180       BaseT::_binning._renderYODA(os);
0181 
0182       // Assemble union of error sources, as it's not guaranteed
0183       // that every bin has the same error breakdown
0184       const std::vector<std::string> labels = this->sources();
0185       if (labels.size()) {
0186         os << "ErrorLabels: [";
0187         for (size_t i = 0; i < labels.size(); ++i) {
0188           const std::string& src = labels[i];
0189           if (i)  os << ", ";
0190           os << std::quoted(src);
0191         }
0192         os << "]\n";
0193       }
0194 
0195       // column header: content types
0196       os << std::setw(width) << std::left << "# value" << "\t";
0197       const int errwidth = std::max(int(std::to_string(labels.size()).size()+7), width); // "errDn(" + src + ")"
0198       for (size_t i = 0; i < labels.size(); ++i) {
0199         const std::string& src = labels[i];
0200         if (src.empty()) {
0201           os << std::setw(errwidth) << std::left << "totalDn" << "\t"
0202              << std::setw(errwidth) << std::left << "totalUp" << "\t";
0203         }
0204         else {
0205           os << std::setw(errwidth) << std::left << ("errDn(" + std::to_string(i+1) + ")") << "\t"
0206              << std::setw(errwidth) << std::left << ("errUp(" + std::to_string(i+1) + ")") << "\t";
0207         }
0208       }
0209       os << "\n";
0210 
0211       for (const auto& b : BaseT::bins(true, true)) {
0212         os << std::setw(width) << std::left << b.val() << "\t"; // print value
0213         // print systs if available
0214         for (const std::string& src : labels) {
0215           if (!b.hasSource(src)) {
0216             os << std::setw(errwidth) << std::left << "---" << "\t"
0217                << std::setw(errwidth) << std::left << "---" << "\t";
0218             continue;
0219           }
0220           const auto& err = b.err(src);
0221           os << std::setw(errwidth) << std::left << err.first << "\t"
0222              << std::setw(errwidth) << std::left << err.second << "\t";
0223         }
0224         os << "\n";
0225       }
0226     }
0227 
0228   public:
0229 
0230     /// @name I/O
0231     /// @{
0232 
0233     // @brief Render information about this AO
0234     void _renderYODA(std::ostream& os, const int width = 13) const noexcept {
0235       _renderYODA_aux(os, width, std::make_index_sequence<sizeof...(AxisT)>{});
0236     }
0237 
0238     // @brief Render scatter-like information about this AO
0239     void _renderFLAT(std::ostream& os, const int width = 13) const noexcept {
0240       const ScatterND<sizeof...(AxisT)+1> tmp = mkScatter();
0241       tmp._renderYODA(os, width);
0242     }
0243 
0244     #ifdef HAVE_HDF5
0245 
0246     // @brief Extract error labels of this AO
0247     void _extractLabels(std::vector<std::string>& labels,
0248                         std::vector<size_t>& labelsizes) const noexcept {
0249       size_t nlabels = 0;
0250       for (const auto& b : BaseT::bins(true, true)) {
0251         std::vector<std::string> keys = b.sources();
0252         nlabels += keys.size()+1; //< +1 for length info itself
0253         labels.insert(std::end(labels),
0254                       std::make_move_iterator(std::begin(keys)),
0255                       std::make_move_iterator(std::end(keys)));
0256       }
0257       std::sort(labels.begin(), labels.end());
0258       labels.erase( std::unique(labels.begin(), labels.end()), labels.end() );
0259       labelsizes.emplace_back(std::move(nlabels));
0260     };
0261 
0262     // @brief Extract axes edges of this AO into map of @a edges
0263     void _extractEdges(std::map<std::string, EdgeHandlerBasePtr>& edges,
0264                        const std::vector<std::string>& labels) const noexcept {
0265 
0266       using lenT = EdgeHandler<size_t>;
0267       using lenPtr = EdgeHandlerPtr<size_t>;
0268       const std::string lengthID("sizeinfo");
0269       lenPtr nedges = std::static_pointer_cast<lenT>(edges.find(lengthID)->second);
0270 
0271       auto extractEdges = [&edges, &binning = BaseT::_binning, &nedges](auto I) {
0272 
0273         using thisEdgeT = typename BinningT::template getEdgeT<I>;
0274         using thisHandlerT = EdgeHandler<thisEdgeT>;
0275         using thisHandlerPtr = EdgeHandlerPtr<thisEdgeT>;
0276 
0277         const std::string edgeID = std::string("edges_") + TypeID<thisEdgeT>::name();
0278         std::vector<thisEdgeT> tmp = binning.template edges<I>();
0279         nedges->extend({ tmp.size() });
0280 
0281         auto itr = edges.find(edgeID);
0282         if (itr != edges.cend()) {
0283           thisHandlerPtr edgeset = std::static_pointer_cast<thisHandlerT>(itr->second);
0284           edgeset->extend(std::move(tmp));
0285         }
0286         else {
0287           edges[edgeID] = std::make_shared<thisHandlerT>(std::move(tmp));
0288         }
0289       };
0290       MetaUtils::staticFor<sizeof...(AxisT)>(extractEdges);
0291 
0292       std::vector<size_t> masks = BaseT::_binning.maskedBins();
0293       nedges->extend({ masks.size() });
0294       nedges->extend(std::move(masks));
0295 
0296       const auto& itr = labels.cbegin();
0297       const auto& itrEnd = labels.cend();
0298       for (const auto& b : BaseT::bins(true, true)) {
0299         vector<string> keys = b.sources();
0300         nedges->extend({ keys.size() });
0301         for (const string& k : keys) {
0302           size_t dist = std::find(itr, itrEnd, k) - itr;
0303           nedges->extend({ dist });
0304         }
0305       }
0306     }
0307 
0308     #endif
0309 
0310     /// @}
0311 
0312 
0313     /// @name Transformations
0314     /// @{
0315 
0316     /// @brief Rescale as if all fill weights had been different by factor @a scalefactor.
0317     void scale(const double scalefactor) noexcept {
0318       setAnnotation("ScaledBy", annotation<double>("ScaledBy", 1.0) * scalefactor);
0319       for (auto& bin : BaseT::_bins) {
0320         bin.scale(scalefactor);
0321       }
0322     }
0323 
0324 
0325     /// @brief Merge every group of @a n bins, from start to end inclusive
0326     ///
0327     /// If the number of bins is not a multiple of @a n, the last @a m < @a n
0328     /// bins on the RHS will also be merged, as the closest possible approach to
0329     /// factor @n rebinning everywhere.
0330     ///
0331     /// @note Only visible bins are being rebinned. Underflow (index = 0) and
0332     /// overflow (index = numBins() + 1) are not included.
0333     template <size_t axisN>
0334     void rebinBy(unsigned int n, size_t begin=1, size_t end=UINT_MAX) {
0335       if (n < 1)  throw UserError("Rebinning requested in groups of 0!");
0336       if (!begin) throw UserError("Visible bins start with index 1!");
0337       if (end > BaseT::numBinsAt(axisN)+1)  end = BaseT::numBinsAt(axisN) + 1;
0338       for (size_t m = begin; m < end; ++m) {
0339         if (m > BaseT::numBinsAt(axisN)+1) break; // nothing to be done
0340         const size_t myend = (m+n-1 < BaseT::numBinsAt(axisN)+1) ? m+n-1 : BaseT::numBinsAt(axisN);
0341         if (myend > m) {
0342           BaseT::template mergeBins<axisN>({m, myend});
0343           end -= myend-m; //< reduce upper index by the number of removed bins
0344         }
0345       }
0346     }
0347 
0348     /// @brief Overloaded alias for rebinBy
0349     template <size_t axisN>
0350     void rebin(unsigned int n, size_t begin=1, size_t end=UINT_MAX) {
0351       rebinBy<axisN>(n, begin, end);
0352     }
0353 
0354     /// @brief Rebin to the given list of bin edges
0355     template <size_t axisN>
0356     void rebinTo(const std::vector<typename BinningT::template getAxisT<axisN>::EdgeT>& newedges) {
0357       if (newedges.size() < 2)
0358         throw UserError("Requested rebinning to an edge list which defines no bins");
0359       using thisAxisT = typename BinningT::template getAxisT<axisN>;
0360       using thisEdgeT = typename thisAxisT::EdgeT;
0361       // get list of shared edges
0362       thisAxisT& oldAxis = BaseT::_binning.template axis<axisN>();
0363       const thisAxisT newAxis(std::move(newedges));
0364       const std::vector<thisEdgeT> eshared = oldAxis.sharedEdges(newAxis);
0365       if (eshared.size() != newAxis.edges().size())
0366         throw BinningError("Requested rebinning to incompatible edges");
0367       // loop over new lower bin edges (= first bin index of merge range)
0368       for (size_t begin = 0; begin < eshared.size() - 1; ++begin) {
0369         // find index of upper edge along old axis
0370         // (subtracting 1 gives index of last bin to be merged)
0371         size_t end = oldAxis.index(eshared[begin+1]) - 1;
0372         // if the current edge is the last visible edge before the overflow
0373         // merge the remaining bins into the overflow
0374         if (begin == newAxis.numBins(true)-1)  end = oldAxis.numBins(true)-1;
0375         // merge this range
0376         if (end > begin)  BaseT::template mergeBins<axisN>({begin, end});
0377         if (eshared.size() == oldAxis.edges().size())  break; // we're done
0378       }
0379     }
0380 
0381     /// @brief Overloaded alias for rebinTo
0382     template <size_t axisN>
0383     void rebin(const std::vector<typename BinningT::template getAxisT<axisN>::EdgeT>& newedges) {
0384       rebinTo<axisN>(std::move(newedges));
0385     }
0386 
0387     /// @brief Reset the EstimateStorage
0388     ///
0389     /// Keep the binning but set all bin contents and related quantities to zero
0390     void reset() noexcept { BaseT::clearBins(); }
0391 
0392     /// @brief Copy assignment
0393     EstimateStorage& operator = (const EstimateStorage& est) noexcept {
0394       if (this != &est) {
0395         AnalysisObject::operator = (est);
0396         BaseT::operator = (est);
0397       }
0398       return *this;
0399     }
0400 
0401     /// Move assignment
0402     EstimateStorage& operator = (EstimateStorage&& est) noexcept {
0403       if (this != &est) {
0404         AnalysisObject::operator = (est);
0405         BaseT::operator = (std::move(est));
0406       }
0407       return *this;
0408     }
0409 
0410     /// @brief Add two EstimateStorages
0411     ///
0412     /// @note Adding EstimateStorages will unset any ScaledBy
0413     /// attribute from previous calls to scale or normalize.
0414     EstimateStorage& add(const EstimateStorage& est,
0415                          const std::string& pat_uncorr="^stat|^uncor" ) {
0416       if (*this != est)
0417         throw BinningError("Arithmetic operation requires compatible binning!");
0418       if (AO::hasAnnotation("ScaledBy")) AO::rmAnnotation("ScaledBy");
0419       for (size_t i = 0; i< BaseT::numBins(true, true); ++i) {
0420         BaseT::bin(i).add(est.bin(i), pat_uncorr);
0421       }
0422       BaseT::maskBins(est.maskedBins(), true);
0423       return *this;
0424     }
0425     //
0426     EstimateStorage& operator += (const EstimateStorage& est) {
0427       return add(est);
0428     }
0429 
0430     /// @brief Add two (rvalue) EstimateStorages
0431     ///
0432     /// @note Adding EstimateStorages will unset any ScaledBy
0433     /// attribute from previous calls to scale or normalize.
0434     EstimateStorage& add(EstimateStorage&& est,
0435                          const std::string& pat_uncorr="^stat|^uncor" ) {
0436       if (*this != est)
0437         throw BinningError("Arithmetic operation requires compatible binning!");
0438       if (AO::hasAnnotation("ScaledBy")) AO::rmAnnotation("ScaledBy");
0439       for (size_t i = 0; i< BaseT::numBins(true, true); ++i) {
0440         BaseT::bin(i).add(std::move(est.bin(i)), pat_uncorr);
0441       }
0442       BaseT::maskBins(est.maskedBins(), true);
0443       return *this;
0444     }
0445     //
0446     EstimateStorage& operator += (EstimateStorage&& est) {
0447       return add(std::move(est));
0448     }
0449 
0450 
0451     /// @brief Subtract one EstimateStorages from another one
0452     ///
0453     /// @note Subtracting EstimateStorages will unset any ScaledBy
0454     /// attribute from previous calls to scale or normalize.
0455     EstimateStorage& subtract(const EstimateStorage& est,
0456                               const std::string& pat_uncorr="^stat|^uncor" ) {
0457       if (*this != est)
0458         throw BinningError("Arithmetic operation requires compatible binning!");
0459       if (AO::hasAnnotation("ScaledBy")) AO::rmAnnotation("ScaledBy");
0460       for (size_t i = 0; i< BaseT::numBins(true, true); ++i) {
0461         BaseT::bin(i).subtract(est.bin(i), pat_uncorr);
0462       }
0463       BaseT::maskBins(est.maskedBins(), true);
0464       return *this;
0465     }
0466     //
0467     EstimateStorage& operator -= (const EstimateStorage& est) {
0468       return subtract(est);
0469     }
0470 
0471     /// @brief Subtract one (rvalue) EstimateStorages from another one
0472     EstimateStorage& subtract(EstimateStorage&& est,
0473                               const std::string& pat_uncorr="^stat|^uncor" ) {
0474       if (*this != est)
0475         throw BinningError("Arithmetic operation requires compatible binning!");
0476       if (AO::hasAnnotation("ScaledBy")) AO::rmAnnotation("ScaledBy");
0477       for (size_t i = 0; i< BaseT::numBins(true, true); ++i) {
0478         BaseT::bin(i) -= std::move(est.bin(i), pat_uncorr);
0479       }
0480       BaseT::maskBins(est.maskedBins(), true);
0481       return *this;
0482     }
0483     //
0484     EstimateStorage& operator -= (EstimateStorage&& est) {
0485       return subtract(std::move(est));
0486     }
0487 
0488     /// @}
0489 
0490 
0491     /// @name Binning info.
0492     /// @{
0493 
0494     /// @brief Total dimension of the object (number of axes + estimate)
0495     size_t dim() const noexcept { return sizeof...(AxisT) + 1; }
0496 
0497     /// @brief Returns the axis configuration
0498     std::string _config() const noexcept { return mkAxisConfig<AxisT...>(); }
0499 
0500     /// @brief Templated version to get edges of axis N by value.
0501     /// +-inf edges are included.
0502     ///
0503     /// @note Needed by axis-specific verison from AxisMixin
0504     template <size_t I, typename E = typename BinningT::template getEdgeT<I>>
0505     std::vector<E> edges(const bool includeOverflows = false) const noexcept {
0506       return BaseT::_binning.template edges<I>(includeOverflows);
0507     }
0508 
0509     /// @brief Templated version to get bin widths of axis N by value.
0510     ///
0511     /// Overflows are included depending on @a includeOverflows
0512     /// Needed by axis-specific version from AxisMixin
0513     ///
0514     /// @note This version is only supported for continuous axes.
0515     template <size_t I, typename E = typename BinningT::template getEdgeT<I>>
0516     std::enable_if_t<std::is_floating_point<E>::value, std::vector<E>>
0517     widths(const bool includeOverflows = false) const noexcept {
0518       return BaseT::_binning.template widths<I>(includeOverflows);
0519     }
0520 
0521     /// @brief Get the lowest non-overflow edge of the axis
0522     ///
0523     /// @note This method is only supported for continuous axes
0524     template <size_t I, typename E = typename BinningT::template getEdgeT<I>>
0525     std::enable_if_t<std::is_floating_point<E>::value, E> min() const noexcept {
0526       return BaseT::_binning.template min<I>();
0527     }
0528 
0529     /// @brief Get the highest non-overflow edge of the axis
0530     ///
0531     /// @note This method is only supported for continuous axes
0532     template <size_t I, typename E = typename BinningT::template getEdgeT<I>>
0533     std::enable_if_t<std::is_floating_point<E>::value, E> max() const noexcept {
0534       return BaseT::_binning.template max<I>();
0535     }
0536 
0537     /// @}
0538 
0539 
0540     /// @name Whole EstimateStorage data
0541     /// @{
0542 
0543     /// @brief Get list of central values
0544     std::vector<double> vals(const bool includeOverflows=false,
0545                              const bool includeMaskedBins=false) const {
0546       std::vector<double> rtn;
0547       rtn.reserve(BaseT::numBins(includeOverflows, includeMaskedBins));
0548       for (const auto& b : BaseT::bins(includeOverflows, includeMaskedBins)) {
0549         rtn.push_back(b.val());
0550       }
0551       return rtn;
0552     }
0553 
0554     /// @brief Get list of error sources
0555     std::vector<std::string> sources() const {
0556       // Assemble union of error sources, as it's not guaranteed
0557       // that every bin has the same error breakdown
0558       std::vector<std::string> rtn;
0559 
0560       for (const auto& b : BaseT::bins(true)) {
0561         std::vector<std::string> keys = b.sources();
0562         rtn.insert(std::end(rtn),
0563                    std::make_move_iterator(std::begin(keys)),
0564                    std::make_move_iterator(std::end(keys)));
0565       }
0566       std::sort(rtn.begin(), rtn.end());
0567       rtn.erase( std::unique(rtn.begin(), rtn.end()), rtn.end() );
0568 
0569       return rtn;
0570     }
0571 
0572     /// @brief Calculate the volume underneath the EstimateStorage
0573     ///
0574     /// @note overflow bins have infinite bin width
0575     double areaUnderCurve(const bool includeBinVol=true,
0576                           const bool includeOverflows=false,
0577                           const bool includeMaskedBins=false) const {
0578       double ret = 0.;
0579       for (const auto& b : BaseT::bins(includeOverflows, includeMaskedBins)) {
0580         const double val = fabs(b.val());
0581         const double vol = includeBinVol? b.dVol() : 1.0;
0582         if (std::isfinite(vol))  ret += val*vol; // aggregate bin volume
0583       }
0584       return ret;
0585     }
0586 
0587     /// @brief Convenient alias for areaUnderCurve()
0588     double auc(const bool includeBinVol=true,
0589                const bool includeOverflows=false,
0590                const bool includeMaskedBins=false) const {
0591       return areaUnderCurve(includeBinVol, includeOverflows, includeMaskedBins);
0592     }
0593 
0594 
0595 
0596     /// @brief Construct a covariance matrix from the error breakdown
0597     std::vector<std::vector<double> > covarianceMatrix(const bool ignoreOffDiagonalTerms=false,
0598                                                        const bool includeOverflows=false,
0599                                                        const bool includeMaskedBins=false,
0600                                                        const std::string& pat_uncorr="^stat|^uncor") const {
0601       const size_t nBins = BaseT::numBins(includeOverflows,includeMaskedBins);
0602       std::vector<std::vector<double> > covM(nBins);
0603 
0604       // initialise cov matrix to be the right shape
0605       for (size_t i = 0; i < nBins; ++i) {
0606         covM[i] = std::vector<double>(nBins, 0.0);
0607       }
0608 
0609       const std::vector<std::string> error_sources = sources();
0610 
0611       // nominal-only case, i.e. total uncertainty, labelled as empty string
0612       if (error_sources.size() == 1 && error_sources[0] == "") {
0613         size_t i = 0;
0614         for (const auto& b : BaseT::bins(includeOverflows,includeMaskedBins)) {
0615           covM[i][i] = b.hasSource("")? sqr(0.5*(b.err("").first+b.err("").second)) : 1.0;
0616           ++i;
0617         }
0618         return covM;
0619       }
0620 
0621       // more interesting case where we actually have some uncertainty breakdown!
0622       std::smatch match;
0623       const std::regex re(pat_uncorr, std::regex_constants::icase);
0624       for (const std::string& sname : error_sources) {
0625         if (sname == "")  continue;
0626         std::vector<double> systErrs(nBins, 0.0);
0627         size_t k = 0;
0628         for (const auto& b : BaseT::bins(includeOverflows,includeMaskedBins)) {
0629           if (b.hasSource(sname)) {
0630             const auto& errs = b.err(sname); // dn-up pair
0631             systErrs[k] = 0.5 *( fabs(errs.first)+fabs(errs.second));
0632           }
0633           ++k;
0634         }
0635         const bool skipOffDiag = (ignoreOffDiagonalTerms
0636                                   || std::regex_search(sname, match, re));
0637         for (size_t i = 0; i < nBins; ++i) {
0638           for (size_t j = 0; j < nBins; ++j) {
0639             if (skipOffDiag && i != j)  continue;
0640             covM[i][j] += systErrs[i]*systErrs[j];
0641           }
0642         }
0643       }
0644 
0645       return covM;
0646     }
0647 
0648     /// @}
0649 
0650     /// @name Type reductions
0651     /// @{
0652 
0653     /// @brief Produce a ScatterND from a EstimateStorage
0654     auto mkScatter(const std::string& path="", const std::string& pat_match = "",
0655                                                const bool includeOverflows=false,
0656                                                const bool includeMaskedBins=false) const {
0657 
0658       constexpr size_t N = sizeof...(AxisT);
0659 
0660       ScatterND<N+1> rtn;
0661       for (const std::string& a : annotations()) {
0662         if (a != "Type")  rtn.setAnnotation(a, annotation(a));
0663       }
0664       rtn.setAnnotation("Path", path);
0665 
0666       const bool incOF = all_CAxes<AxisT...>::value && includeOverflows;
0667       for (const auto& b : BaseT::bins(incOF, includeMaskedBins)) {
0668 
0669         // Create the vector of coordinates
0670         Utils::ndarray<double, N+1> vals;
0671         // first fill bin centres, use bin index if axis non-arithmetic
0672         auto indexIfDiscrete = [&vals, &b](auto I) {
0673           using isContinuous = typename BinningT::template is_CAxis<I>;
0674           using isArithmetic = typename BinningT::template is_Arithmetic<I>;
0675           vals[I] = coordPicker<I>(b, std::integral_constant<bool, isArithmetic::value>{},
0676                                       std::integral_constant<bool, isContinuous::value>{});
0677         };
0678         MetaUtils::staticFor<BinningT::Dimension::value>(indexIfDiscrete);
0679         // then fill bin content
0680         vals[N] = b.val();
0681 
0682         // Create the vector of error pairs, use 0 if axis not continuous
0683         Utils::ndarray<std::pair<double,double>, N+1> errs;
0684         auto nullifyDiscrete = [&errs, &vals, &b](auto I) {
0685           using isContinuous = typename BinningT::template is_CAxis<I>;
0686           errs[I] = nullifyIfDisc<I>(b, vals[I], std::integral_constant<bool, isContinuous::value>{});
0687         };
0688         MetaUtils::staticFor<BinningT::Dimension::value>(nullifyDiscrete);
0689         const double tot = fabs(b.totalErrPos(pat_match)); // use positive error component
0690         errs[N] = { tot, tot };
0691 
0692         // Add the PointND
0693         rtn.addPoint( PointND<N+1>(vals, errs) );
0694       }
0695 
0696       // Decorate output scatter with the discrete edges
0697       const BinningT& binning = BaseT::_binning;
0698       auto decorateEdges = [&rtn, &binning](auto I) {
0699         using isContinuous = typename BinningT::template is_CAxis<I>;
0700         if constexpr( !isContinuous::value ) {
0701           const auto& axis  = binning.template axis<I>();
0702           if (axis.numBins()) {
0703             std::stringstream ss;
0704             axis._renderYODA(ss);
0705             rtn.setAnnotation("EdgesA" + std::to_string(I+1), ss.str());
0706           }
0707         }
0708       };
0709       MetaUtils::staticFor<BinningT::Dimension::value>(decorateEdges);
0710 
0711       return rtn;
0712     }
0713 
0714     /// @brief Split into vector of BinnedEstimates along @a axisN
0715     ///
0716     /// The binning dimension of the returned objects are reduced by one unit.
0717     /// @note Requires at least two binning dimensions.
0718     template<size_t axisN, typename = std::enable_if_t< (axisN < sizeof...(AxisT) && sizeof...(AxisT)>=2) >>
0719     auto mkEstimates(const std::string& path="", const bool includeOverflows=false) const {
0720 
0721       // Need to provide a prescription for how to add the two bin contents
0722       auto how2add = [](auto& pivot, const BinType& toCopy) { pivot = toCopy; };
0723       auto rtn = BaseT::template mkBinnedSlices<axisN, BinnedEstimate>(how2add, includeOverflows);
0724       for (const std::string& a : annotations()) {
0725         if (a == "Type")  continue;
0726         for (size_t i = 0; i < rtn.size(); ++i) {
0727           rtn[i].setAnnotation(a, annotation(a));
0728         }
0729       }
0730       for (size_t i = 0; i < rtn.size(); ++i) {
0731         rtn[i].setAnnotation("Path", path);
0732       }
0733 
0734       return rtn;
0735     }
0736 
0737 
0738     /// @brief Method returns clone of the estimate with streamlined error source
0739     AnalysisObject* mkInert(const std::string& path = "",
0740                             const std::string& source = "") const noexcept {
0741       EstimateStorage* rtn = newclone();
0742       rtn->setPath(path);
0743       for (auto& b : rtn->bins(true, true)) {
0744         if (b.numErrs() == 1) {
0745           try {
0746             b.renameSource("", source);
0747           }
0748           catch (YODA::UserError& e) { }
0749         }
0750       }
0751       return rtn;
0752     }
0753 
0754     /// @}
0755 
0756     /// @name MPI (de-)serialisation
0757     //@{
0758 
0759     size_t lengthContent(bool fixed_length = false) const noexcept {
0760       size_t rtn = 0;
0761       for (const auto& bin : BaseT::bins(true, true)) {
0762         rtn += bin._lengthContent(fixed_length);
0763       }
0764       return rtn;
0765     }
0766 
0767     std::vector<double> serializeContent(bool fixed_length = false) const noexcept {
0768       std::vector<double> rtn;
0769       const size_t nBins = BaseT::numBins(true, true);
0770       rtn.reserve(nBins * 4);
0771       for (size_t i = 0; i < nBins; ++i) {
0772         const auto& b = BaseT::bin(i);
0773         std::vector<double> bdata = b._serializeContent(fixed_length);
0774         rtn.insert(std::end(rtn),
0775                    std::make_move_iterator(std::begin(bdata)),
0776                    std::make_move_iterator(std::end(bdata)));
0777       }
0778       return rtn;
0779     }
0780 
0781 
0782     void deserializeContent(const std::vector<double>& data) {
0783 
0784       const size_t nBins = BaseT::numBins(true, true);
0785       const size_t minLen = 2*nBins;
0786       if (data.size() < minLen)
0787         throw UserError("Length of serialized data should be at least " + std::to_string(minLen)+"!");
0788 
0789       size_t i = 0;
0790       auto itr = data.cbegin();
0791       const auto itrEnd = data.cend();
0792       const bool fixedLen = data.size() == 2*minLen;
0793       while (itr != itrEnd) {
0794         // for estimates, the first element represents the central,
0795         // the subsequent value represents the number of error pairs
0796         const size_t nErrs = fixedLen? 1 : (*(itr + 1) + 0.5); // add 0.5 to avoid rounding issues
0797         auto last = itr + 2*(nErrs+1); // last element + 1
0798         BaseT::bin(i)._deserializeContent(std::vector<double>{itr, last}, fixedLen);
0799         // update for next iteration
0800         itr = last;
0801         ++i;
0802       }
0803     }
0804 
0805     // @}
0806 
0807   private:
0808 
0809     /// @brief Helper function to create a BinningT from
0810     /// a given set @a nBins within a range @a limitsLowUp
0811     template<size_t... Is>
0812     BinningT _mkBinning(const std::vector<size_t>& nBins,
0813                         const std::vector<std::pair<double, double>>& limitsLowUp,
0814                         std::index_sequence<Is...>) const {
0815       return BinningT({((void)Is, Axis<AxisT>(nBins[Is], limitsLowUp[Is].first, limitsLowUp[Is].second))...});
0816     }
0817 
0818     /// @brief Helper function to create a BinningT from a scatter @a s
0819     template<size_t... Is>
0820     BinningT _mkBinning(const ScatterND<sizeof...(AxisT)+1>& s, std::index_sequence<Is...>) const {
0821       return BinningT({((void)Is, Axis<AxisT>(s.edges(Is)))...});
0822     }
0823 
0824   };
0825 
0826 
0827 
0828   /// @brief User-facing BinnedEstimate class in arbitrary dimensions
0829   template <typename... AxisT>
0830   class BinnedEstimate : public EstimateStorage<AxisT...> {
0831   public:
0832     using EstimateT = BinnedEstimate<AxisT...>;
0833     using BaseT = EstimateStorage<AxisT...>;
0834     using BinType = typename BaseT::BinT;
0835     using Ptr = std::shared_ptr<EstimateT>;
0836 
0837     /// @brief Inherit constructors.
0838     using BaseT::BaseT;
0839 
0840     BinnedEstimate(const EstimateT&) = default;
0841     BinnedEstimate(EstimateT&&) = default;
0842     BinnedEstimate& operator =(const EstimateT&) = default;
0843     BinnedEstimate& operator =(EstimateT&&) = default;
0844     using AnalysisObject::operator =;
0845 
0846     /// @brief Copy constructor (needed for clone functions).
0847     ///
0848     /// @note Compiler won't generate this constructor automatically.
0849     BinnedEstimate(const BaseT& other) : BaseT(other) {}
0850     //
0851     BinnedEstimate(const EstimateT& other, const std::string& path) : BaseT(other, path) {}
0852 
0853     /// @brief Move constructor.
0854     BinnedEstimate(BaseT&& other) : BaseT(std::move(other)) {}
0855     //
0856     BinnedEstimate(EstimateT&& other, const std::string& path) : BaseT(std::move(other), path) {}
0857 
0858   };
0859 
0860 
0861   /// @brief Specialisation of the BinnedEstimate for a 1D histogram
0862   template <typename AxisT>
0863   class BinnedEstimate<AxisT>
0864       : public EstimateStorage<AxisT>,
0865         public XAxisMixin<BinnedEstimate<AxisT>, AxisT> {
0866   public:
0867     using EstimateT = BinnedEstimate<AxisT>;
0868     using BaseT = EstimateStorage<AxisT>;
0869     using BinType = typename BaseT::BinT;
0870     using Ptr = std::shared_ptr<EstimateT>;
0871 
0872     /// @brief Inherit constructors.
0873     using BaseT::BaseT;
0874 
0875     BinnedEstimate(const EstimateT&) = default;
0876     BinnedEstimate(EstimateT&&) = default;
0877     BinnedEstimate& operator =(const EstimateT&) = default;
0878     BinnedEstimate& operator =(EstimateT&&) = default;
0879     using AnalysisObject::operator =;
0880 
0881     /// @brief Copy constructor (needed for clone functions).
0882     ///
0883     /// @note Compiler won't generate this constructor automatically.
0884     BinnedEstimate(const BaseT& other) : BaseT(other) {}
0885     //
0886     BinnedEstimate(const EstimateT& other, const std::string& path) : BaseT(other, path) {}
0887 
0888     /// @brief Move constructor.
0889     BinnedEstimate(BaseT&& other) : BaseT(std::move(other)) {}
0890     //
0891     BinnedEstimate(EstimateT&& other, const std::string& path) : BaseT(std::move(other), path) {}
0892 
0893 
0894     BinnedEstimate(std::vector<AxisT>&& edges, const std::string& path="", const std::string& title="")
0895               : BaseT(std::move(edges), path, title) {}
0896 
0897     BinnedEstimate(const std::vector<AxisT>& edges, const std::string& path="", const std::string& title="")
0898               : BaseT(edges, path, title) {}
0899 
0900     /// @brief Constructor with auto-setup of evenly spaced axes.
0901     ///
0902     /// The constructor argument uses double rather than EdgeT to
0903     /// allow for auto-conversion of int to double.
0904     ///
0905     /// @note This constructor is only supported when all axes are continuous.
0906     template <typename EdgeT = double, typename = enable_if_all_CAxisT<EdgeT, AxisT>>
0907     BinnedEstimate(size_t nbins, double lower, double upper, const std::string& path = "", const std::string& title = "")
0908               : BaseT({nbins}, {{lower, upper}}, path, title) {}
0909 
0910     /// @brief Make a copy on the stack
0911     EstimateT clone() const noexcept {
0912       return EstimateT(*this);
0913     }
0914 
0915     /// @brief Make a copy on the heap
0916     EstimateT* newclone() const noexcept {
0917       return new EstimateT(*this);
0918     }
0919 
0920     /// @brief Find bin index for given coordinates
0921     size_t indexAt(const AxisT xCoord) const noexcept {
0922       return BaseT::binAt( {xCoord} ).index();
0923     }
0924 
0925     /// @brief Mask/Unmask bin at given set of coordinates
0926     void maskBinAt(const AxisT xCoord, const bool status = true) noexcept {
0927       return BaseT::maskBin({xCoord}, status);
0928     }
0929 
0930   };
0931 
0932 
0933 
0934   /// @brief Specialisation of the BinnedEstimate for a 2D BinnedEstimate
0935   template <typename AxisT1, typename AxisT2>
0936   class BinnedEstimate<AxisT1, AxisT2>
0937       : public EstimateStorage<AxisT1, AxisT2>,
0938         public XAxisMixin<BinnedEstimate<AxisT1, AxisT2>, AxisT1>,
0939         public YAxisMixin<BinnedEstimate<AxisT1, AxisT2>, AxisT2> {
0940   public:
0941     using EstimateT = BinnedEstimate<AxisT1, AxisT2>;
0942     using BaseT = EstimateStorage<AxisT1, AxisT2>;
0943     using BinType = typename BaseT::BinT;
0944     using Ptr = std::shared_ptr<EstimateT>;
0945 
0946     /// @brief Inherit constructors.
0947     using BaseT::BaseT;
0948 
0949     BinnedEstimate(const EstimateT&) = default;
0950     BinnedEstimate(EstimateT&&) = default;
0951     BinnedEstimate& operator =(const EstimateT&) = default;
0952     BinnedEstimate& operator =(EstimateT&&) = default;
0953     using AnalysisObject::operator =;
0954 
0955     /// @brief Copy constructor (needed for clone functions).
0956     ///
0957     /// @note Compiler won't generate this constructor automatically.
0958     BinnedEstimate(const BaseT& other) : BaseT(std::move(other)) {}
0959     //
0960     BinnedEstimate(const EstimateT& other, const std::string& path) : BaseT(other, path) {}
0961 
0962     /// @brief Move constructor.
0963     BinnedEstimate(BaseT&& other) : BaseT(std::move(other)) {}
0964     //
0965     BinnedEstimate(EstimateT&& other, const std::string& path) : BaseT(std::move(other), path) {}
0966 
0967     BinnedEstimate(std::vector<AxisT1>&& xEdges, std::vector<AxisT2>&& yEdges,
0968                    const std::string& path="", const std::string& title="")
0969               : BaseT(std::move(xEdges), std::move(yEdges), path, title) {}
0970 
0971     BinnedEstimate(const std::vector<AxisT1>& xEdges, const std::vector<AxisT2>& yEdges,
0972                    const std::string& path="", const std::string& title="")
0973               : BaseT(xEdges, yEdges, path, title) {}
0974 
0975     /// @brief Constructor with auto-setup of evenly spaced axes.
0976     ///
0977     /// The constructor argument uses double rather than EdgeT to
0978     /// allow for auto-conversion of int to double.
0979     ///
0980     /// @note This constructor is only supported when all axes are continuous.
0981     template <typename EdgeT = double, typename = enable_if_all_CAxisT<EdgeT, AxisT1, AxisT2>>
0982     BinnedEstimate(size_t nbinsX, double lowerX, double upperX,
0983                    size_t nbinsY, double lowerY, double upperY,
0984                    const std::string& path = "", const std::string& title = "")
0985               : BaseT({nbinsX, nbinsY}, {{lowerX, upperX}, {lowerY, upperY}}, path, title) {}
0986 
0987     /// @brief Make a copy on the stack
0988     EstimateT clone() const noexcept {
0989       return EstimateT(*this);
0990     }
0991 
0992     /// @brief Make a copy on the heap
0993     EstimateT* newclone() const noexcept {
0994       return new EstimateT(*this);
0995     }
0996 
0997     /// @brief Bin access using global index
0998     BinType& bin(const size_t index) noexcept {
0999       return BaseT::bin(index);
1000     }
1001 
1002     /// @brief Bin access using global index (const version)
1003     const BinType& bin(const size_t index) const noexcept {
1004       return BaseT::bin(index);
1005     }
1006 
1007     /// @brief Bin access using local indices
1008     BinType& bin(const size_t localX, const size_t localY) noexcept {
1009       return BaseT::bin( {localX, localY} );
1010     }
1011 
1012     /// @brief Bin access using local indices (const version)
1013     const BinType& bin(const size_t localX, const size_t localY) const noexcept {
1014       return BaseT::bin( {localX, localY} );
1015     }
1016 
1017     /// @brief Bin access using coordinates
1018     BinType& binAt(const AxisT1 xCoord, const AxisT2 yCoord) noexcept {
1019       return BaseT::binAt( {xCoord, yCoord} );
1020     }
1021 
1022     /// @brief Bin access using coordinates (const version)
1023     const BinType& binAt(const AxisT1 xCoord, const AxisT2 yCoord) const noexcept {
1024       return BaseT::binAt( {xCoord, yCoord} );
1025     }
1026 
1027     /// @brief Find bin index for given coordinates
1028     size_t indexAt(const AxisT1 xCoord, const AxisT2 yCoord) const noexcept {
1029       return BaseT::binAt( {xCoord, yCoord} ).index();
1030     }
1031 
1032     /// @brief Mask/Unmask bin at given set of coordinates
1033     void maskBinAt(const AxisT1 xCoord, const AxisT2 yCoord, const bool status = true) noexcept {
1034       return BaseT::maskBin({xCoord, yCoord}, status);
1035     }
1036 
1037   };
1038 
1039 
1040   /// @brief Specialisation of the BinnedEstimate for a 3D BinnedEstimate
1041   template <typename AxisT1, typename AxisT2, typename AxisT3>
1042   class BinnedEstimate<AxisT1, AxisT2, AxisT3>
1043       : public EstimateStorage<AxisT1, AxisT2, AxisT3>,
1044         public XAxisMixin<BinnedEstimate<AxisT1, AxisT2, AxisT3>, AxisT1>,
1045         public YAxisMixin<BinnedEstimate<AxisT1, AxisT2, AxisT3>, AxisT2>,
1046         public ZAxisMixin<BinnedEstimate<AxisT1, AxisT2, AxisT3>, AxisT3> {
1047   public:
1048     using EstimateT = BinnedEstimate<AxisT1, AxisT2, AxisT3>;
1049     using BaseT = EstimateStorage<AxisT1, AxisT2, AxisT3>;
1050     using BinType = typename BaseT::BinT;
1051     using Ptr = std::shared_ptr<EstimateT>;
1052 
1053     /// @brief Inherit constructors.
1054     using BaseT::BaseT;
1055 
1056     BinnedEstimate(const EstimateT&) = default;
1057     BinnedEstimate(EstimateT&&) = default;
1058     BinnedEstimate& operator =(const EstimateT&) = default;
1059     BinnedEstimate& operator =(EstimateT&&) = default;
1060     using AnalysisObject::operator =;
1061 
1062     /// @brief Copy constructor (needed for clone functions).
1063     ///
1064     /// @note Compiler won't generate this constructor automatically.
1065     BinnedEstimate(const BaseT& other) : BaseT(other) {}
1066     //
1067     BinnedEstimate(const EstimateT& other, const std::string& path) : BaseT(other, path) {}
1068 
1069     /// @brief Move constructor.
1070     BinnedEstimate(BaseT&& other) : BaseT(std::move(other)) {}
1071     //
1072     BinnedEstimate(EstimateT&& other, const std::string& path) : BaseT(std::move(other), path) {}
1073 
1074     /// @brief Constructor with auto-setup of evenly spaced axes.
1075     ///
1076     /// The constructor argument uses double rather than EdgeT to
1077     /// allow for auto-conversion of int to double.
1078     ///
1079     /// @note This constructor is only supported when all axes are continuous.
1080     template <typename EdgeT = double, typename = enable_if_all_CAxisT<EdgeT, AxisT1, AxisT2, AxisT3>>
1081     BinnedEstimate(size_t nbinsX, double lowerX, double upperX,
1082                    size_t nbinsY, double lowerY, double upperY,
1083                    size_t nbinsZ, double lowerZ, double upperZ,
1084                    const std::string& path = "", const std::string& title = "")
1085               : BaseT({nbinsX, nbinsY, nbinsZ}, {{lowerX, upperX}, {lowerY, upperY},
1086                       {lowerZ, upperZ}}, path, title) {}
1087 
1088     /// @brief Make a copy on the stack
1089     EstimateT clone() const noexcept {
1090       return EstimateT(*this);
1091     }
1092 
1093     /// @brief Make a copy on the heap
1094     EstimateT* newclone() const noexcept {
1095       return new EstimateT(*this);
1096     }
1097 
1098     /// @brief Bin access using global index
1099     BinType& bin(const size_t index) noexcept {
1100       return BaseT::bin(index);
1101     }
1102 
1103     /// @brief Bin access using global index (const version)
1104     const BinType& bin(const size_t index) const noexcept {
1105       return BaseT::bin(index);
1106     }
1107 
1108     /// @brief Bin access using local indices
1109     BinType& bin(const size_t localX, const size_t localY, const size_t localZ) noexcept {
1110       return BaseT::bin( {localX, localY, localZ} );
1111     }
1112 
1113     /// @brief Bin access using local indices
1114     const BinType& bin(const size_t localX, const size_t localY, const size_t localZ) const noexcept {
1115       return BaseT::bin( {localX, localY, localZ} );
1116     }
1117 
1118     /// @brief Bin access using coordinates
1119     BinType& binAt(const AxisT1 xCoord, const AxisT2 yCoord, const AxisT3 zCoord) noexcept {
1120       return BaseT::binAt( {xCoord, yCoord, zCoord} );
1121     }
1122 
1123     /// @brief Bin access using coordinates (const version)
1124     const BinType& binAt(const AxisT1 xCoord, const AxisT2 yCoord, const AxisT3 zCoord) const noexcept {
1125       return BaseT::binAt( {xCoord, yCoord, zCoord} );
1126     }
1127 
1128     /// @brief Find bin index for given coordinates
1129     size_t indexAt(const AxisT1 xCoord, const AxisT2 yCoord, const AxisT3 zCoord) const noexcept {
1130       return BaseT::binAt( {xCoord, yCoord, zCoord} ).index();
1131     }
1132 
1133     /// @brief Mask/Unmask bin at given set of coordinates
1134     void maskBinAt(const AxisT1 xCoord, const AxisT2 yCoord, const AxisT3 zCoord, const bool status = true) noexcept {
1135       return BaseT::maskBin({xCoord, yCoord, zCoord}, status);
1136     }
1137 
1138   };
1139 
1140   /// @name Combining BinnedEstimates: global operators
1141   /// @{
1142 
1143   /// @brief Add two BinnedEstimates
1144   template <typename... AxisT>
1145   inline BinnedEstimate<AxisT...>
1146   operator + (BinnedEstimate<AxisT...> first, const BinnedEstimate<AxisT...>& second) {
1147     first += second;
1148     return first;
1149   }
1150 
1151 
1152   /// @brief Add two BinnedEstimates
1153   template <typename... AxisT>
1154   inline BinnedEstimate<AxisT...>
1155   operator + (BinnedEstimate<AxisT...> first, BinnedEstimate<AxisT...>&& second) {
1156     first += std::move(second);
1157     return first;
1158   }
1159 
1160 
1161   /// @brief Subtract two BinnedEstimates
1162   template <typename... AxisT>
1163   inline BinnedEstimate< AxisT...>
1164   operator - (BinnedEstimate<AxisT...> first, const BinnedEstimate<AxisT...>& second) {
1165     first -= second;
1166     return first;
1167   }
1168 
1169   /// @brief Subtract two BinnedEstimates
1170   template <typename... AxisT>
1171   inline BinnedEstimate< AxisT...>
1172   operator - (BinnedEstimate<AxisT...> first, BinnedEstimate<AxisT...>&& second) {
1173     first -= std::move(second);
1174     return first;
1175   }
1176 
1177   /// @brief Divide two BinnedEstimates
1178   template <typename... AxisT>
1179   inline BinnedEstimate<AxisT...>
1180   divide(const BinnedEstimate<AxisT...>& numer, const BinnedEstimate<AxisT...>& denom,
1181          const std::string& pat_uncorr="^stat|^uncor" ) {
1182     if (numer != denom) {
1183       throw BinningError("Arithmetic operation requires compatible binning!");
1184     }
1185 
1186     BinnedEstimate<AxisT...> rtn(numer.binning());
1187     if (numer.path() == denom.path())  rtn.setPath(numer.path());
1188     if (rtn.hasAnnotation("ScaledBy")) rtn.rmAnnotation("ScaledBy");
1189 
1190     for (const auto& b_num : numer.bins(true, true)) {
1191       const size_t idx = b_num.index();
1192       rtn.bin(idx) = divide(b_num, denom.bin(idx), pat_uncorr);
1193     }
1194     rtn.maskBins(denom.maskedBins(), true);
1195 
1196     return rtn;
1197   }
1198 
1199   /// @brief Divide two BinnedEstimates
1200   template <typename... AxisT>
1201   inline BinnedEstimate<AxisT...>
1202   operator / (const BinnedEstimate<AxisT...>& numer, const BinnedEstimate<AxisT...>& denom) {
1203     return divide(numer, denom);
1204   }
1205 
1206   /// @brief Divide two BinnedEstimates
1207   template <typename... AxisT>
1208   inline BinnedEstimate<AxisT...>
1209   operator / (BinnedEstimate<AxisT...>&& numer, const BinnedEstimate<AxisT...>& denom) {
1210     return divide(std::move(numer), denom);
1211   }
1212 
1213   /// @brief Divide two BinnedEstimates
1214   template <typename... AxisT>
1215   inline BinnedEstimate<AxisT...>
1216   operator / (const BinnedEstimate<AxisT...>& numer, BinnedEstimate<AxisT...>&& denom) {
1217     return divide(numer, std::move(denom));
1218   }
1219 
1220   /// @brief Divide two BinnedEstimates
1221   template <typename... AxisT>
1222   inline BinnedEstimate<AxisT...>
1223   operator / (BinnedEstimate<AxisT...>&& numer, BinnedEstimate<AxisT...>&& denom) {
1224     return divide(std::move(numer), std::move(denom));
1225   }
1226 
1227 
1228   /// @brief Calculate a binned efficiency ratio of two BinnedEstimate objects
1229   ///
1230   /// @note An efficiency is not the same thing as a standard division of two
1231   /// BinnedEstimate objects: the errors are treated as correlated via binomial statistics.
1232   template <typename... AxisT>
1233   inline BinnedEstimate<AxisT...>
1234   efficiency(const BinnedEstimate<AxisT...>& accepted, const BinnedEstimate<AxisT...>& total,
1235              const std::string& pat_uncorr="^stat|^uncor" ) {
1236 
1237     if (accepted != total) {
1238       throw BinningError("Arithmetic operation requires compatible binning!");
1239     }
1240 
1241     BinnedEstimate<AxisT...> rtn(accepted.binning());
1242 
1243     for (const auto& b_acc : accepted.bins(true, true)) {
1244       Estimate est;
1245       const size_t idx = b_acc.index();
1246       try {
1247         est = efficiency(b_acc, total.bin(idx), pat_uncorr);
1248       } catch (const UserError& e) {
1249         //
1250       }
1251       rtn.bin(idx).set(est);
1252     }
1253     return rtn;
1254   }
1255 
1256 
1257   /// @brief Calculate the asymmetry (a-b)/(a+b) of two BinnedDbn objects
1258   template <typename... AxisT>
1259   inline BinnedEstimate<AxisT...>
1260   asymm(const BinnedEstimate<AxisT...>& a,
1261         const BinnedEstimate<AxisT...>& b,
1262         const std::string& pat_uncorr="^stat|^uncor" ) {
1263     return divde(subtract(a-b, pat_uncorr), add(a+b, pat_uncorr), pat_uncorr);
1264   }
1265 
1266 
1267   /// @}
1268 
1269 
1270   /// @name Generalised transformations
1271   /// @{
1272 
1273   template <typename... AxisT>
1274   inline void transform(BinnedEstimate<AxisT...>& est, const Trf<1>& fn) {
1275     for (auto& b : est.bins(true, true)) {
1276       b.transform(fn);
1277     }
1278   }
1279 
1280   template <typename... AxisT, typename FN>
1281   inline void transform(BinnedEstimate<AxisT...>& est, const FN& fn) {
1282     transform(est, Trf<1>(fn));
1283   }
1284 
1285   /// @}
1286 
1287 
1288   /// @name Convenience aliases
1289   /// @{
1290 
1291   /// Define dimension-specific short-hands (Cython sugar)
1292   template<typename A1>
1293   using BinnedEstimate1D = BinnedEstimate<A1>;
1294 
1295   template <typename A1, typename A2>
1296   using BinnedEstimate2D = BinnedEstimate<A1, A2>;
1297 
1298   template <typename A1, typename A2, typename A3>
1299   using BinnedEstimate3D = BinnedEstimate<A1, A2, A3>;
1300 
1301   /// Anonymous namespace to limit visibility
1302   namespace {
1303     template <class T>
1304     struct EstimateMaker;
1305 
1306     template<size_t... Is>
1307     struct EstimateMaker<std::index_sequence<Is...>> {
1308       using type = BinnedEstimate< std::decay_t<decltype((void)Is, std::declval<double>())>... >;
1309     };
1310   }
1311 
1312   template<size_t N>
1313   using EstimateND = typename EstimateMaker<std::make_index_sequence<N>>::type;
1314 
1315 
1316   // User-friendly names (continuous axes only)
1317   using Estimate1D = BinnedEstimate<double>;
1318   using Estimate2D = BinnedEstimate<double,double>;
1319   using Estimate3D = BinnedEstimate<double,double,double>;
1320 
1321   /// @}
1322 
1323 }
1324 
1325 #endif