Back to home page

EIC code displayed by LXR

 
 

    


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

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_Estimate_h
0007 #define YODA_Estimate_h
0008 
0009 #include "YODA/Exceptions.h"
0010 #include "YODA/Dbn.h"
0011 #include "YODA/Transformation.h"
0012 #include "YODA/Utils/MathUtils.h"
0013 #include "YODA/Utils/StringUtils.h"
0014 #include <cmath>
0015 #include <map>
0016 #include <string>
0017 #include <regex>
0018 #include <utility>
0019 
0020 namespace YODA {
0021 
0022   /// @brief A point estimate (base class for the Estimate)
0023   ///
0024   /// This class is used internally by YODA to centralise the storage of
0025   /// static statistical results or estimators. Each estimate is made up of
0026   /// a value and an error map. The error map uses the source of uncertainty
0027   /// as the keys and the (possibly asymmetric) uncertainty pair as the values.
0028   /// The empty string is interpreted as the total uncertainty.
0029   class Estimate {
0030   public:
0031 
0032     /// @name Constructors
0033     //@{
0034 
0035     /// Default constructor of a new distribution.
0036     Estimate() { reset(); }
0037 
0038 
0039     /// @brief Constructor to set an Estimate with a pre-filled state.
0040     ///
0041     /// Principally designed for internal persistency use.
0042     Estimate(double v, std::map<std::string,std::pair<double,double>>& errors)
0043       : _value(v), _error(errors) { }
0044 
0045 
0046     /// @brief Alternative constructor to set an Estimate with value and uncertainty.
0047     Estimate(const double v, const std::pair<double,double>& e, const std::string& source = "")
0048       : _value(v) { setErr(e, source); }
0049 
0050     /// Copy constructor
0051     ///
0052     /// Sets all the parameters using the ones provided from an existing Estimate.
0053     Estimate(const Estimate& toCopy) {
0054       _value = toCopy._value;
0055       _error = toCopy._error;
0056     }
0057 
0058     /// Move constructor
0059     ///
0060     /// Sets all the parameters using the ones provided from an existing Estimate.
0061     Estimate(Estimate&& toMove) {
0062       _value = std::move(toMove._value);
0063       _error = std::move(toMove._error);
0064     }
0065 
0066     /// Copy assignment
0067     ///
0068     /// Sets all the parameters using the ones provided from an existing Estimate.
0069     Estimate& operator=(const Estimate& toCopy) noexcept {
0070       if (this != &toCopy) {
0071         _value = toCopy._value;
0072         _error = toCopy._error;
0073       }
0074       return *this;
0075     }
0076 
0077     /// Move assignment
0078     ///
0079     /// Sets all the parameters using the ones provided from an existing Estimate.
0080     Estimate& operator=(Estimate&& toMove) noexcept {
0081       if (this != &toMove) {
0082         _value = std::move(toMove._value);
0083         _error = std::move(toMove._error);
0084       }
0085       return *this;
0086     }
0087 
0088     //@}
0089 
0090 
0091     /// @name Operators
0092     //@{
0093 
0094     /// Add two Estimates
0095     Estimate& add(const Estimate& toAdd, const std::string& pat_uncorr="^stat|^uncor" ) {
0096       setVal(val() + toAdd.val());
0097       std::smatch match;
0098       const std::regex re(pat_uncorr, std::regex_constants::icase);
0099       for (const std::string& src : toAdd.sources()) {
0100         if (!hasSource(src))  setErr(toAdd.errDownUp(src), src);
0101         else if (std::regex_search(src, match, re)) {
0102           // treat as uncorrelated between AOs:
0103           // add in quadrature
0104           const auto [l_dn,l_up] = errDownUp(src);
0105           const auto [r_dn,r_up] = toAdd.errDownUp(src);
0106           const double new_dn = std::sqrt(l_dn*l_dn + r_dn*r_dn);
0107           const double new_up = std::sqrt(l_up*l_up + r_up*r_up);
0108           setErr({-new_dn,new_up}, src);
0109         }
0110         else {
0111           // treat as correlated between AOs: add linearly
0112           const auto [l_dn,l_up] = errDownUp(src);
0113           const auto [r_dn,r_up] = toAdd.errDownUp(src);
0114           setErr({l_dn+r_dn, l_up+r_up}, src);
0115         }
0116       }
0117       return *this;
0118     }
0119     //
0120     Estimate& operator+=(const Estimate& toAdd) {
0121       return add(toAdd);
0122     }
0123 
0124     /// Subtract one Estimate from another
0125     Estimate& subtract(const Estimate& toSubtract, const std::string& pat_uncorr="^stat|^uncor" ) {
0126       setVal(val() - toSubtract.val());
0127       std::smatch match;
0128       const std::regex re(pat_uncorr, std::regex_constants::icase);
0129       for (const std::string& src : toSubtract.sources()) {
0130         if (!hasSource(src))  setErr(toSubtract.errDownUp(src), src);
0131         else if (std::regex_search(src, match, re)) {
0132           // treat as uncorrelated between AOs: add in quadrature
0133           const auto [l_dn,l_up] = errDownUp(src);
0134           const auto [r_dn,r_up] = toSubtract.errDownUp(src);
0135           const double new_dn = std::sqrt(l_dn*l_dn + r_dn*r_dn);
0136           const double new_up = std::sqrt(l_up*l_up + r_up*r_up);
0137           setErr({-new_dn,new_up}, src);;
0138         }
0139         else {
0140           // treat as correlated between AOs: add linearly
0141           const auto [l_dn,l_up] = errDownUp(src);
0142           const auto [r_dn,r_up] = toSubtract.errDownUp(src);
0143           setErr({l_dn+r_dn, l_up+r_up}, src);
0144         }
0145       }
0146       return *this;
0147     }
0148     //
0149     Estimate& operator-=(const Estimate& toSubtract) {
0150       return subtract(toSubtract);
0151     }
0152 
0153     //@}
0154 
0155     /// @name Modifiers
0156     //@{
0157 
0158     /// @brief Set the central value of this estimator
0159     void setValue(const double value) noexcept { _value = value; }
0160 
0161     /// @brief Alias for setValue
0162     void setVal(const double val) noexcept { setValue(val); }
0163 
0164     /// @brief Set a signed uncertainty component.
0165     void setErr(const std::pair<double, double> &err, const std::string& source = "") {
0166       const std::string& s = Utils::toUpper(source);
0167       if (s == "TOTAL")
0168         throw UserError("Use empty string for the total uncertainty!");
0169       _error[source] = err;
0170     }
0171 
0172     /// @brief Set a symmetric uncertainty component
0173     ///
0174     /// @note The down-component will be set to -|err|
0175     /// and the up-component will be set to +|err|.
0176     void setErr(const double err, const std::string& source = "") {
0177       setErr({-fabs(err),fabs(err)}, source);
0178     }
0179 
0180     /// @brief Set both central value and uncertainty component
0181     void set(const double val, const std::pair<double, double> &err, const std::string source = "") {
0182       setVal(val);
0183       setErr(err, source);
0184     }
0185 
0186 
0187     /// @brief Set both central value and uncertainty component
0188     void set(const double val, const double err, const std::string& source = "") {
0189       setVal(val);
0190       setErr(err, source);
0191     }
0192 
0193     /// Reset the internal values
0194     void reset() noexcept {
0195       _value = std::numeric_limits<double>::quiet_NaN();
0196       _error.clear();
0197     }
0198 
0199 
0200     /// Rescale as if value and uncertainty had been different by factor @a scalefactor.
0201     void scale(const double scalefactor) noexcept {
0202       _value *= scalefactor;
0203       for (auto& item : _error) {
0204         item.second = { item.second.first * scalefactor, item.second.second * scalefactor };
0205       }
0206     }
0207 
0208     /// Generalised transformations with functors
0209     void scale(const Trf<1>& trf) {
0210       trf.transform(_value, _error);
0211     }
0212 
0213     /// Generalised transformations with functors
0214     void transform(const Trf<1>& trf) {
0215       scale(trf);
0216     }
0217 
0218     /// Replace a source label in the error breakdown
0219     void renameSource(const std::string& old_label, const std::string& new_label) {
0220       if (!hasSource(old_label)) {
0221         throw UserError("Error map has no such key: "+old_label);
0222       }
0223       auto entry = _error.extract(old_label);
0224       entry.key() = new_label;
0225       _error.insert(std::move(entry));
0226     }
0227 
0228     // Remove error source @a source
0229     void rmSource(const std::string& source) {
0230       if (hasSource(source)) {
0231         _error.erase(source);
0232       }
0233     }
0234 
0235     // Clear the error breakdown
0236     void rmErrs() {
0237       _error.clear();
0238     }
0239 
0240     //@}
0241 
0242   public:
0243 
0244 
0245     /// @name Estimate getters
0246     //@{
0247 
0248     /// The central value
0249     double val() const noexcept { return _value; }
0250 
0251     /// @brief The signed absolute error on the central value
0252     ///
0253     /// @note first/second element corresponds to
0254     /// systematic down/up variation
0255     std::pair<double,double> errDownUp(const std::string& source = "") const {
0256       const size_t count = _error.count(source);
0257       if (!count)
0258         throw RangeError("Error map has no such key: "+source);
0259       return _error.at(source);
0260     }
0261 
0262     /// @brief Convenience alias for errorDownUp(source)
0263     std::pair<double,double> err(const std::string& source = "") const {
0264       return errDownUp(source);
0265     }
0266 
0267     /// @brief The signed negative and positive uncertainty component
0268     std::pair<double,double> errNegPos(std::string source = "") const {
0269       return _downUp2NegPos( errDownUp(source) );
0270     }
0271 
0272     /// @brief The signed negative uncertainty component
0273     double errNeg(const std::string& source = "") const {
0274       return errNegPos(source).first;
0275     }
0276 
0277     /// @brief The signed positive uncertainty component
0278     double errPos(const std::string& source = "") const {
0279       return errNegPos(source).second;
0280     }
0281 
0282     /// @brief The signed error due to the systematic downward variation
0283     double errDown(const std::string& source = "") const {
0284       return errDownUp(source).first;
0285     }
0286 
0287     /// @brief The signed error due to the systematic upward variation
0288     double errUp(const std::string& source = "") const {
0289       return errDownUp(source).second;
0290     }
0291 
0292     /// @brief The unsigned error from an average of the downwards/upwards shifts
0293     double errAvg(const std::string& source = "") const {
0294       return _average(errDownUp(source));
0295     }
0296 
0297     /// @brief The unsigned symmetrised error from the largest component
0298     /// of the downwards/upwards shifts
0299     double errEnv(const std::string& source = "") const {
0300       return _envelope(errDownUp(source));
0301     }
0302 
0303     /// @brief The relative signed error pair with respect to the central value
0304     std::pair<double,double> relErrDownUp(const std::string& source = "") const {
0305       auto [dn,up] = errDownUp(source);
0306       dn = _value != 0 ? dn/fabs(_value) : std::numeric_limits<double>::quiet_NaN();
0307       up = _value != 0 ? up/fabs(_value) : std::numeric_limits<double>::quiet_NaN();
0308       return {dn,up};
0309     }
0310 
0311     /// @brief Convenience alias for relErrDownUp
0312     std::pair<double,double> relErr(const std::string& source = "") const {
0313       return relErrDownUp(source);
0314     }
0315 
0316     /// @brief The relative negative error with respect to the central value
0317     double relErrDown(const std::string& source = "") const {
0318       return relErr(source).first;
0319     }
0320 
0321     /// @brief The relative positive error with respect to the central value
0322     double relErrUp(const std::string& source = "") const {
0323       return relErr(source).second;
0324     }
0325 
0326     /// @brief The relative uncertainty from an average of the downward/upward shifts
0327     /// with respect to the central value
0328     double relErrAvg(const std::string& source = "") const {
0329       return _average(relErrDownUp(source));
0330     }
0331 
0332     /// @brief The relative unsigned symmetrised error from the largest component
0333     /// of the downwards/upwards shifts with respect to the central value
0334     double relErrEnv(const std::string& source = "") const {
0335       return _envelope(relErrDownUp(source));
0336     }
0337 
0338     /// @brief The maximal shift of the central value
0339     /// according to systematic variation @a source
0340     double valMax(const std::string& source = "") const {
0341       return val() + errPos(source);
0342     }
0343 
0344     /// @brief The minimal shift of the central value
0345     /// according to systematic variation @a source
0346     double valMin(const std::string& source = "") const {
0347       return val() + errNeg(source);
0348     }
0349 
0350     /// @brief The quadrature sum of uncertainty components
0351     ///
0352     /// @note The first element is for the negative error,
0353     /// the second element for the positive error.
0354     /// @note In case of one-sided uncertainties, a
0355     /// zero-deviation is used for the null component.
0356     /// @note if @a pat_match is supplied, only the subset
0357     /// of error sources matching the pattern are included.
0358     std::pair<double,double> quadSum(const std::string& pat_match = "") const noexcept {
0359       const bool check_match = pat_match != ""; std::smatch match;
0360       const std::regex re(pat_match, std::regex_constants::icase);
0361       // interpret as { neg , pos } quad sums
0362       std::pair<double, double> ret = { 0., 0. };
0363       for (const auto& item : _error) {
0364         if (item.first == "")  continue;
0365         if (check_match && !std::regex_search(item.first, match, re))  continue;
0366         const auto [dn,up] = _downUp2NegPos(item.second);
0367         ret.first += dn*dn;
0368         ret.second += up*up;
0369       }
0370       return { - std::sqrt(ret.first), std::sqrt(ret.second) };
0371     }
0372 
0373     /// The negative component of the quad-sum-based total uncertainty components
0374     double quadSumNeg(const std::string& pat_match = "") const noexcept {
0375       return quadSum(pat_match).first;
0376     }
0377 
0378     /// The positive component of the quad-sum-based total uncertainty components
0379     double quadSumPos(const std::string& pat_match = "") const noexcept {
0380       return quadSum(pat_match).second;
0381     }
0382 
0383     /// @brief The unsigned average of the two quad-sum-based total uncertainty components
0384     double quadSumAvg(const std::string& pat_match = "") const noexcept {
0385       return _average(quadSum(pat_match));
0386     }
0387 
0388     /// @brief The unsigned symmetrised uncertainty from the largest
0389     /// of the quad-sum-based total uncertainty components
0390     double quadSumEnv(const std::string& pat_match = "") const noexcept {
0391       return _envelope(quadSum(pat_match));
0392     }
0393 
0394     /// @brief The maximal shift of the central value
0395     /// from the quad-sum-based total uncertainty components
0396     double quadSumMax(const std::string& pat_match = "") const {
0397       return val() + quadSumPos(pat_match);
0398     }
0399 
0400     /// @brief The minimal shift of the central value
0401     /// from the quad-sum-based total uncertainty components
0402     double quadSumMin(const std::string& pat_match = "") const {
0403       return val() + quadSumNeg(pat_match);
0404     }
0405 
0406     /// @brief The total uncertainty
0407     ///
0408     /// @note If the user didn't specify a total uncertainty
0409     /// using the empty string, the sum in quadrature is returned.
0410     std::pair<double,double> totalErr(const std::string& pat_match = "") const noexcept {
0411       // check if the user specified the total uncertainty
0412       if (pat_match == "" && _error.count(""))  return _downUp2NegPos(_error.at(""));
0413       // otherwise return quadrature sum
0414       return quadSum(pat_match);
0415     }
0416 
0417     /// @brief The negative total uncertainty
0418     double totalErrNeg(const std::string& pat_match = "") const noexcept {
0419       return totalErr(pat_match).first;
0420     }
0421 
0422     /// @brief The positive total uncertainty
0423     double totalErrPos(const std::string& pat_match = "") const noexcept {
0424       return totalErr(pat_match).second;
0425     }
0426 
0427     /// @brief The average of the total downward/upward uncertainty shifts,
0428     /// which are taken to be the signed quadrature sums of the error sources
0429     double totalErrAvg(const std::string& pat_match = "") const {
0430       return _average(totalErr(pat_match));
0431     }
0432 
0433     /// @brief The unsigned symmetrised uncertainty from the largest of the
0434     /// downward/upward shifts of the quad-sum-based total uncertainty
0435     double totalErrEnv(const std::string& pat_match = "") const {
0436       return _envelope(totalErr(pat_match));
0437     }
0438 
0439     /// @brief The maximal shift of the central value
0440     /// from the total uncertainty components
0441     double totalErrMax(const std::string& pat_match = "") const {
0442       return val() + totalErrPos(pat_match);
0443     }
0444 
0445     /// @brief The minimal shift of the central value
0446     /// from the total uncertainty components
0447     double totalErrMin(const std::string& pat_match = "") const {
0448       return val() + totalErrNeg(pat_match);
0449     }
0450 
0451     /// @brief The unsigned average total uncertainty
0452     double valueErr(const std::string& pat_match = "") const {
0453       return fabs(totalErrAvg(pat_match));
0454     }
0455 
0456     /// @brief The relative negative/positive total uncertainty with respect to the central value
0457     std::pair<double,double> relTotalErr(const std::string& pat_match = "") const noexcept {
0458       auto [neg,pos] = totalErr(pat_match);
0459       neg = _value != 0 ? neg/fabs(_value) : std::numeric_limits<double>::quiet_NaN();
0460       pos = _value != 0 ? pos/fabs(_value) : std::numeric_limits<double>::quiet_NaN();
0461       return {neg,pos};
0462     }
0463 
0464     /// The relative negative total uncertainty on the central value
0465     double relTotalErrNeg(const std::string& pat_match = "") const noexcept {
0466       return relTotalErr(pat_match).first;
0467     }
0468 
0469     /// The relative positive total uncertainty on the central value
0470     double relTotalErrPos(const std::string& pat_match = "") const noexcept {
0471       return relTotalErr(pat_match).second;
0472     }
0473 
0474     /// @brief The relative average of the quad-sum-based total
0475     /// downward/upward uncertainty components
0476     double relTotalErrAvg(const std::string& pat_match = "") const noexcept {
0477       return _average(relTotalErr(pat_match));
0478     }
0479 
0480     /// @brief The unsigned symmetrised error from the largest downward/upward
0481     /// components of the relative total quad-sum-based uncertainty
0482     double relTotalErrEnv(const std::string& pat_match = "") const noexcept {
0483       return _envelope(relTotalErr(pat_match));
0484     }
0485 
0486     /// @brief The list of error source names
0487     std::vector<std::string> sources() const noexcept {
0488       std::vector<std::string> keys;
0489       for (const auto& item : _error)  keys.push_back(item.first);
0490       return keys;
0491     }
0492 
0493     /// @brief Returns true/false if the error map contains @a key
0494     bool hasSource(const std::string& key) const noexcept {
0495       return _error.count(key);
0496     }
0497 
0498     /// @brief The number of error sources in the error map
0499     size_t numErrs() const noexcept {
0500       return _error.size();
0501     }
0502 
0503     //@}
0504 
0505     /// @name MPI (de-)serialisation
0506     //@{
0507 
0508     size_t _lengthContent(bool fixed_length = false) const noexcept {
0509       const size_t nErrs = fixed_length? 1 : numErrs();
0510       return 2*(nErrs + 1);
0511     }
0512 
0513     std::vector<double> _serializeContent(bool fixed_length = false) const noexcept {
0514       std::vector<double> rtn;
0515       const size_t nErrs = fixed_length? 1 : numErrs();
0516       rtn.reserve(2*(nErrs + 1));
0517       rtn.push_back(_value);
0518       if (fixed_length) {
0519         rtn.push_back(1.0);
0520         rtn.push_back(totalErrNeg());
0521         rtn.push_back(totalErrPos());
0522         return rtn;
0523       }
0524       rtn.push_back((double)_error.size());
0525       for (const auto& item : _error) {
0526         rtn.push_back(item.second.first);
0527         rtn.push_back(item.second.second);
0528       }
0529       return rtn;
0530     }
0531 
0532     void _deserializeContent(const std::vector<double>& data, bool fixed_length = false) {
0533 
0534       if (data.size() < 2)
0535         throw UserError("Length of serialized data should be at least 2!");
0536 
0537       if (2*(fixed_length? 1 : data[1]) != (data.size() - 2))
0538         throw UserError("Expected "+std::to_string(data[1])+" error pairs!");
0539 
0540       reset();
0541       size_t idx = 0;
0542       auto itr = data.cbegin();
0543       const auto itrEnd = data.cend();
0544       while (itr != itrEnd) {
0545         if (!idx) {
0546           _value = *itr; ++itr; ++itr;
0547         }
0548         else {
0549           std::string name("source" + std::to_string(idx));
0550           const double dn = *itr; ++itr;
0551           const double up = *itr; ++itr;
0552           setErr({dn, up}, name);
0553         }
0554         ++idx;
0555       }
0556       if (numErrs() == 1)  renameSource("source1", "");
0557     }
0558 
0559     std::vector<std::string> serializeSources() const noexcept {
0560       return sources();
0561     }
0562 
0563     void deserializeSources(const std::vector<std::string>& data) {
0564 
0565       const size_t nErrs = numErrs();
0566       if (data.size() != nErrs)
0567         throw UserError("Expected " + std::to_string(nErrs) + " error source labels!");
0568 
0569       for (size_t i = 0; i < data.size(); ++i) {
0570         const std::string name("source" + std::to_string(i+1));
0571         if (!_error.count(name))
0572           throw UserError("Key names have already been updated!");
0573         renameSource(name, data[i]);
0574       }
0575 
0576     }
0577 
0578     // @}
0579 
0580   private:
0581 
0582     /// @name Utility methods
0583     //@{
0584 
0585     /// @brief Conversion from downward/upward shift to negative/positive error
0586     ///
0587     /// @todo Support strategies other than envelope
0588     std::pair<double,double> _downUp2NegPos(const std::pair<double,double> &e) const noexcept {
0589       const auto [dn,up] = e;
0590       if (dn < 0. && up < 0.) {
0591         // both negative
0592         const double env = std::min(dn, up);
0593         return {env, 0.};
0594       }
0595       else if (dn < 0.) {
0596         // dn negative, up positive
0597         return {dn,up};
0598       }
0599       else if (up < 0.) {
0600         // up negative, dn positive
0601         return {up, dn};
0602       }
0603       // else: both positive
0604       const double env = std::max(dn, up);
0605       return {0., env};
0606     }
0607 
0608     /// @brief Work out an average error from an error pair
0609     double _average(const std::pair<double,double> &err) const noexcept {
0610       return 0.5*(fabs(err.first) + fabs(err.second));
0611     }
0612 
0613     /// @brief Work out the largest symmetric error from an error pair
0614     double _envelope(const std::pair<double,double> &err) const noexcept {
0615       return std::max(fabs(err.first), fabs(err.second));
0616     }
0617 
0618     //@}
0619 
0620 
0621     /// @name Storage
0622     //@{
0623 
0624     /// @brief The central value
0625     double _value;
0626 
0627     /// @brief Error map for signed uncertainties.
0628     /// The first element of the pair is the effect on the central value
0629     /// from the systematic downward shift, the second element of the pair
0630     /// is the effect on the central value from the systematic upward shift.
0631     ///
0632     /// @note the empty string is interpreted as the total uncertainty
0633     std::map<std::string, std::pair<double,double>> _error;
0634 
0635     //@}
0636 
0637   public:
0638 
0639     /// @name Helper methods
0640     //@{
0641 
0642     std::string _toString() const noexcept {
0643       std::string res ="";
0644       res += ("value="+ std::to_string(_value));
0645       for (const auto& item : _error) {
0646         const std::string name = item.first == ""? "user-supplied total " : "";
0647         res += (", " + name + "error("+ item.first);
0648         res += (")={ "+ std::to_string(item.second.first));
0649         res += (", "+ std::to_string(item.second.second)+" }");
0650       }
0651       return res;
0652     }
0653 
0654     //@}
0655   };
0656 
0657 
0658   /// @name Global operators for Estimate objects
0659   //@{
0660 
0661   /// @brief Add two Estimate objects
0662   inline Estimate operator + (Estimate lhs, const Estimate& rhs) {
0663     lhs += rhs;
0664     return lhs;
0665   }
0666 
0667   /// @brief Add two Estimate objects
0668   inline Estimate operator + (Estimate lhs, Estimate&& rhs) {
0669     lhs += std::move(rhs);
0670     return lhs;
0671   }
0672 
0673   /// @brief Subtract two Estimate objects
0674   inline Estimate operator - (Estimate lhs, const Estimate& rhs) {
0675     lhs -= rhs;
0676     return lhs;
0677   }
0678 
0679   /// @brief Subtract two Estimate objects
0680   inline Estimate operator - (Estimate lhs, Estimate&& rhs) {
0681     lhs -= std::move(rhs);
0682     return lhs;
0683   }
0684 
0685   /// @brief Divide two Estimate objects
0686   inline Estimate divide(const Estimate& numer, const Estimate& denom,
0687                          const std::string& pat_uncorr="^stat|^uncor") {
0688 
0689     Estimate rtn;
0690     if (denom.val())  rtn.setVal(numer.val() / denom.val());
0691     const double newVal = rtn.val();
0692 
0693     // get error sources
0694     std::vector<std::string> sources = numer.sources();
0695     std::vector<std::string> tmp = denom.sources();
0696     sources.insert(std::end(sources),
0697                    std::make_move_iterator(std::begin(tmp)),
0698                    std::make_move_iterator(std::end(tmp)));
0699     std::sort(sources.begin(), sources.end());
0700     sources.erase( std::unique(sources.begin(), sources.end()), sources.end() );
0701 
0702     std::smatch match;
0703     const std::regex re(pat_uncorr, std::regex_constants::icase);
0704     for (const std::string& src : sources) {
0705       if (std::regex_search(src, match, re)) {
0706         // treat as uncorrelated between AOs:
0707         // add relative errors in quadrature
0708         double n_dn = 0.0, n_up = 0.0;
0709         if (numer.hasSource(src)) {
0710           n_dn = numer.relErrDown(src);
0711           n_up = numer.relErrUp(src);
0712         }
0713         double d_dn = 0.0, d_up = 0.0;
0714         if (denom.hasSource(src)) {
0715           d_dn = denom.relErrDown(src);
0716           d_up = denom.relErrUp(src);
0717         }
0718         const double new_dn = fabs(newVal) * std::sqrt(n_dn*n_dn + d_dn*d_dn);
0719         const double new_up = fabs(newVal) * std::sqrt(n_up*n_up + d_up*d_up);
0720         rtn.setErr({-new_dn,new_up}, src);
0721       }
0722       else {
0723         // treat as correlated between AOs:
0724         // work out correlated ratio: R+dR = (N+dN)/(D+dD)
0725         double n_dn = numer.val(), n_up = numer.val();
0726         if (numer.hasSource(src)) {
0727           n_dn += numer.errDown(src);
0728           n_up += numer.errUp(src);
0729         }
0730         double d_dn = denom.val(), d_up = denom.val();
0731         if (denom.hasSource(src)) {
0732           d_dn += denom.errDown(src);
0733           d_up += denom.errUp(src);
0734         }
0735         double new_dn = std::numeric_limits<double>::quiet_NaN();
0736         double new_up = std::numeric_limits<double>::quiet_NaN();
0737         if (d_dn)  new_dn = n_dn / d_dn - newVal;
0738         if (d_up)  new_up = n_up / d_up - newVal;
0739         rtn.setErr({new_dn, new_up}, src);
0740       }
0741     }
0742 
0743     return rtn;
0744   }
0745 
0746   /// @brief Divide two Estimate objects
0747   inline Estimate operator / (const Estimate& numer, const Estimate& denom) {
0748     return divide(numer, denom);
0749   }
0750 
0751   /// @brief Divide two Estimate objects
0752   inline Estimate operator / (Estimate&& numer, const Estimate& denom) {
0753     return divide(std::move(numer), denom);
0754   }
0755 
0756   /// @brief Divide two Estimate objects
0757   inline Estimate operator / (const Estimate& numer, Estimate&& denom) {
0758     return divide(numer, std::move(denom));
0759   }
0760 
0761   /// @brief Divide two Estimate objects
0762   inline Estimate operator / (Estimate&& numer, Estimate&& denom) {
0763     return divide(std::move(numer), std::move(denom));
0764   }
0765 
0766   /// @brief Divide two Estimate objects using binomial statistics
0767   inline Estimate efficiency(const Estimate& accepted, const Estimate& total,
0768                              const std::string& pat_uncorr="^stat|^uncor") {
0769 
0770     /// Check that the numerator is consistent with being a subset of the denominator
0771     ///
0772     /// @todo Need to check that bins are all positive? Integral could be zero due to large +ve/-ve in different bins :O
0773     const double acc_val = accepted.val();
0774     const double tot_val = total.val();
0775     if (acc_val > tot_val)
0776       throw UserError("Attempt to calculate an efficiency when the numerator is not a subset of the denominator: "
0777                       + Utils::toStr(acc_val) + " / " + Utils::toStr(tot_val));
0778 
0779     Estimate rtn = divide(accepted, total, pat_uncorr);
0780 
0781     // get error sources
0782     std::vector<std::string> sources = accepted.sources();
0783     std::vector<std::string> tmp = total.sources();
0784     sources.insert(std::end(sources),
0785                    std::make_move_iterator(std::begin(tmp)),
0786                    std::make_move_iterator(std::end(tmp)));
0787     std::sort(sources.begin(), sources.end());
0788     sources.erase( std::unique(sources.begin(), sources.end()), sources.end() );
0789 
0790     // set binomial error for uncorrelated error sources
0791     std::smatch match;
0792     const double eff = rtn.val();
0793     const std::regex re(pat_uncorr, std::regex_constants::icase);
0794     for (const std::string& src : sources) {
0795       double err = std::numeric_limits<double>::quiet_NaN();
0796       if (!tot_val) {
0797         rtn.setErr({-err,err}, src);
0798         continue;
0799       }
0800       else if (std::regex_search(src, match, re)) {
0801         const double acc_err = accepted.relTotalErrAvg();
0802         const double tot_err = total.totalErrAvg();
0803         err = sqrt(std::abs( ((1-2*eff)*sqr(acc_err) + sqr(eff)*sqr(tot_err)) / sqr(tot_val) ));
0804         rtn.setErr({-err,err}, src);
0805         continue;
0806       }
0807     }
0808 
0809     return rtn;
0810   }
0811 
0812   //@}
0813 
0814 }
0815 
0816 #endif