Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-22 09:05:48

0001 // -*- C++ -*-
0002 #ifndef RIVET_MathUtils_HH
0003 #define RIVET_MathUtils_HH
0004 
0005 #include "Rivet/Math/MathConstants.hh"
0006 #include <type_traits>
0007 #include <cassert>
0008 
0009 namespace Rivet {
0010 
0011 
0012   /// @defgroup mathutils Maths utilities
0013 
0014 
0015   /// @name Comparison functions for safe (floating point) equality tests
0016   /// @{
0017 
0018   /// @brief Compare a number to zero
0019   ///
0020   /// This version for floating point types has a degree of fuzziness expressed
0021   /// by the absolute @a tolerance parameter, for floating point safety.
0022   template <typename NUM>
0023   inline typename std::enable_if_t<std::is_floating_point_v<NUM>, bool>
0024   isZero(NUM val, double tolerance=1e-8) {
0025     return fabs(val) < tolerance;
0026   }
0027 
0028   /// @brief Compare a number to zero
0029   ///
0030   /// SFINAE template specialisation for integers, since there is no FP
0031   /// precision issue.
0032   template <typename NUM>
0033   inline typename std::enable_if_t<std::is_integral_v<NUM>, bool>
0034   isZero(NUM val, double=1e-5) { //< NB. unused tolerance parameter for ints, still needs a default value!
0035     return val == 0;
0036   }
0037 
0038   /// @brief Check if a number is NaN
0039   template <typename NUM>
0040   inline typename std::enable_if_t<std::is_floating_point_v<NUM>, bool>
0041   isNaN(NUM val) { return std::isnan(val); }
0042 
0043   /// @brief Check if a number is non-NaN
0044   template <typename NUM>
0045   inline typename std::enable_if_t<std::is_floating_point_v<NUM>, bool>
0046   notNaN(NUM val) { return !std::isnan(val); }
0047 
0048   /// @brief Square root of the absolute value with the sign of the argument propagated
0049   template <typename NUM>
0050   inline typename std::enable_if<std::is_floating_point<NUM>::value, NUM>::type
0051   sqrt_signed(NUM val) { return std::copysign(sqrt(std::abs(val)), val); }
0052 
0053   /// @brief Compare two numbers for equality with a degree of fuzziness
0054   ///
0055   /// This version for floating point types (if any argument is FP) has a degree
0056   /// of fuzziness expressed by the fractional @a tolerance parameter, for
0057   /// floating point safety.
0058   template <typename N1, typename N2>
0059   inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2> &&
0060                                    (std::is_floating_point_v<N1> || std::is_floating_point_v<N2>), bool>
0061   fuzzyEquals(N1 a, N2 b, double tolerance=1e-5) {
0062     const double absavg = (std::abs(a) + std::abs(b))/2.0;
0063     const double absdiff = std::abs(a - b);
0064     const bool rtn = (isZero(a) && isZero(b)) || absdiff < tolerance*absavg;
0065     return rtn;
0066   }
0067 
0068   /// @brief Compare two numbers for equality with a degree of fuzziness
0069   ///
0070   /// Simpler SFINAE template specialisation for integers, since there is no FP
0071   /// precision issue.
0072   template <typename N1, typename N2>
0073   inline typename std::enable_if_t<std::is_integral_v<N1> && std::is_integral_v<N2>, bool>
0074     fuzzyEquals(N1 a, N2 b, double) { //< NB. unused tolerance parameter for ints, still needs a default value!
0075     return a == b;
0076   }
0077 
0078 
0079   /// @brief Compare two numbers for >= with a degree of fuzziness
0080   ///
0081   /// The @a tolerance parameter on the equality test is as for @c fuzzyEquals.
0082   template <typename N1, typename N2>
0083   inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2>, bool>
0084   fuzzyGtrEquals(N1 a, N2 b, double tolerance=1e-5) {
0085     return a > b || fuzzyEquals(a, b, tolerance);
0086   }
0087 
0088 
0089   /// @brief Compare two floating point numbers for <= with a degree of fuzziness
0090   ///
0091   /// The @a tolerance parameter on the equality test is as for @c fuzzyEquals.
0092   template <typename N1, typename N2>
0093   inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2>, bool>
0094   fuzzyLessEquals(N1 a, N2 b, double tolerance=1e-5) {
0095     return a < b || fuzzyEquals(a, b, tolerance);
0096   }
0097 
0098   /// @brief Get the minimum of two numbers
0099   ///
0100   /// @note unsigned integral types are cast to their integer equivalents first
0101   template <typename N1, typename N2>
0102   inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2>,
0103                                    signed_if_mixed_t<N1,N2> >
0104   min(N1 a, N2 b) {
0105     using rtnT = signed_if_mixed_t<N1,N2>;
0106     return ((rtnT)a > (rtnT)b)? b : a;
0107   }
0108 
0109   /// @brief Get the maximum of two numbers
0110   ///
0111   /// @note unsigned integral types are cast to their integer equivalents first
0112   template <typename N1, typename N2>
0113   inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2>,
0114                                    signed_if_mixed_t<N1,N2> >
0115   max(N1 a, N2 b) {
0116     using rtnT = signed_if_mixed_t<N1,N2>;
0117     return ((rtnT)a > (rtnT)b)? a : b;
0118   }
0119 
0120   /// @}
0121 
0122 
0123   /// @name Ranges and intervals
0124   /// @{
0125 
0126   /// Represents whether an interval is open (non-inclusive) or closed (inclusive).
0127   ///
0128   /// For example, the interval \f$ [0, \pi) \f$ is closed (an inclusive
0129   /// boundary) at 0, and open (a non-inclusive boundary) at \f$ \pi \f$.
0130   enum RangeBoundary { OPEN=0, SOFT=0, CLOSED=1, HARD=1 };
0131 
0132   /// @brief Determine if @a value is in the range @a low to @a high, for floating point numbers
0133   ///
0134   /// Interval boundary types are defined by @a lowbound and @a highbound.
0135   template <typename N1, typename N2, typename N3>
0136   inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2> && std::is_arithmetic_v<N3>, bool>
0137   inRange(N1 value, N2 low, N3 high,
0138           RangeBoundary lowbound=CLOSED, RangeBoundary highbound=OPEN) {
0139     if (lowbound == OPEN && highbound == OPEN) {
0140       return (value > low && value < high);
0141     } else if (lowbound == OPEN && highbound == CLOSED) {
0142       return (value > low && value <= high);
0143     } else if (lowbound == CLOSED && highbound == OPEN) {
0144       return (value >= low && value < high);
0145     } else { // if (lowbound == CLOSED && highbound == CLOSED) {
0146       return (value >= low && value <= high);
0147     }
0148   }
0149 
0150   /// @brief Determine if @a value is in the range @a low to @a high, for floating point numbers
0151   ///
0152   /// Interval boundary types are defined by @a lowbound and @a highbound.
0153   /// Closed intervals are compared fuzzily.
0154   template <typename N1, typename N2, typename N3>
0155   inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2> && std::is_arithmetic_v<N3>, bool>
0156   fuzzyInRange(N1 value, N2 low, N3 high,
0157                RangeBoundary lowbound=CLOSED, RangeBoundary highbound=OPEN) {
0158     if (lowbound == OPEN && highbound == OPEN) {
0159       return (value > low && value < high);
0160     } else if (lowbound == OPEN && highbound == CLOSED) {
0161       return (value > low && fuzzyLessEquals(value, high));
0162     } else if (lowbound == CLOSED && highbound == OPEN) {
0163       return (fuzzyGtrEquals(value, low) && value < high);
0164     } else { // if (lowbound == CLOSED && highbound == CLOSED) {
0165       return (fuzzyGtrEquals(value, low) && fuzzyLessEquals(value, high));
0166     }
0167   }
0168 
0169   /// Alternative version of inRange which accepts a pair for the range arguments.
0170   template <typename N1, typename N2, typename N3>
0171   inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2> && std::is_arithmetic_v<N3>, bool>
0172   inRange(N1 value, pair<N2, N3> lowhigh,
0173           RangeBoundary lowbound=CLOSED, RangeBoundary highbound=OPEN) {
0174     return inRange(value, lowhigh.first, lowhigh.second, lowbound, highbound);
0175   }
0176 
0177 
0178   // Alternative forms, with snake_case names and boundary types in names rather than as args -- from MCUtils
0179 
0180   /// @brief Boolean function to determine if @a value is within the given range
0181   ///
0182   /// @note The interval is closed (inclusive) at the low end, and open (exclusive) at the high end.
0183   template <typename N1, typename N2, typename N3>
0184   inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2> && std::is_arithmetic_v<N3>, bool>
0185   in_range(N1 val, N2 low, N3 high) {
0186     return inRange(val, low, high, CLOSED, OPEN);
0187   }
0188 
0189   /// @brief Boolean function to determine if @a value is within the given range
0190   ///
0191   /// @note The interval is closed at both ends.
0192   template <typename N1, typename N2, typename N3>
0193   inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2> && std::is_arithmetic_v<N3>, bool>
0194   in_closed_range(N1 val, N2 low, N3 high) {
0195     return inRange(val, low, high, CLOSED, CLOSED);
0196   }
0197 
0198   /// @brief Boolean function to determine if @a value is within the given range
0199   ///
0200   /// @note The interval is open at both ends.
0201   template <typename N1, typename N2, typename N3>
0202   inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2> && std::is_arithmetic_v<N3>, bool>
0203   in_open_range(N1 val, N2 low, N3 high) {
0204     return inRange(val, low, high, OPEN, OPEN);
0205   }
0206 
0207   /// @todo Add pair-based versions of the named range-boundary functions
0208 
0209   /// @}
0210 
0211 
0212   /// @name Miscellaneous numerical helpers
0213   /// @{
0214 
0215   /// Named number-type squaring operation.
0216   template <typename NUM>
0217   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0218   sqr(NUM a) {
0219     return a*a;
0220   }
0221 
0222   /// Subtract two numbers with FP fuzziness
0223   inline double subtract(double a, double b, double tolerance = 1e-5) {
0224     if (fuzzyEquals(a,b,tolerance))  return 0.;
0225     return a - b;
0226   }
0227 
0228   /// Add two numbers with FP fuzziness
0229   inline double add(double a, double b, double tolerance = 1e-5) {
0230     return subtract(a,-b,tolerance);
0231   }
0232 
0233   /// @brief Named number-type addition in quadrature operation.
0234   ///
0235   /// @note Result has the sqrt operation applied.
0236   /// @todo When std::common_type can be used, generalise to multiple numeric types with appropriate return type.
0237   // template <typename N1, typename N2>
0238   template <typename NUM>
0239   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0240   //std::common_type<N1, N2>::type
0241   add_quad(NUM a, NUM b) {
0242     return sqrt(a*a + b*b);
0243   }
0244 
0245   /// Named number-type addition in quadrature operation.
0246   ///
0247   /// @note Result has the sqrt operation applied.
0248   /// @todo When std::common_type can be used, generalise to multiple numeric types with appropriate return type.
0249   // template <typename N1, typename N2>
0250   template <typename NUM>
0251   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0252   //std::common_type<N1, N2, N3>::type
0253   add_quad(NUM a, NUM b, NUM c) {
0254     return sqrt(a*a + b*b + c*c);
0255   }
0256 
0257   /// Return a/b, or @a fail if b = 0
0258   /// @todo When std::common_type can be used, generalise to multiple numeric types with appropriate return type.
0259   inline double safediv(double num, double den, double fail=0.0) {
0260     return (!isZero(den)) ? num/den : fail;
0261   }
0262 
0263   /// A more efficient version of pow for raising numbers to integer powers.
0264   template <typename NUM>
0265   constexpr inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0266   intpow(NUM val, unsigned int exp) {
0267     if (exp == 0) return (NUM) 1;
0268     else if (exp == 1) return val;
0269     return val * intpow(val, exp-1);
0270   }
0271 
0272   /// Find the sign of a number
0273   template <typename NUM>
0274   constexpr inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, int>
0275   sign(NUM val) {
0276     if (isZero(val)) return ZERO;
0277     const int valsign = (val > 0) ? PLUS : MINUS;
0278     return valsign;
0279   }
0280 
0281   /// @}
0282 
0283 
0284   /// @name Physics statistical distributions
0285   /// @{
0286 
0287   /// @brief CDF for the Breit-Wigner distribution
0288   inline double cdfBW(double x, double mu, double gamma) {
0289     // normalize to (0;1) distribution
0290     const double xn = (x - mu)/gamma;
0291     return std::atan(xn)/M_PI + 0.5;
0292   }
0293 
0294   /// @brief Inverse CDF for the Breit-Wigner distribution
0295   inline double invcdfBW(double p, double mu, double gamma) {
0296     const double xn = std::tan(M_PI*(p-0.5));
0297     return gamma*xn + mu;
0298   }
0299 
0300   /// @}
0301 
0302 
0303   /// @name Binning helper functions
0304   /// @{
0305 
0306   /// @brief Make a list of @a nbins + 1 values equally spaced between @a start and @a end inclusive.
0307   ///
0308   /// @note The arg ordering and the meaning of the nbins variable is "histogram-like",
0309   /// as opposed to the Numpy/Matlab version.
0310   ///
0311   /// @todo Move to HEPUtils
0312   inline vector<double> linspace(size_t nbins, double start, double end, bool include_end=true) {
0313     assert(nbins > 0);
0314     vector<double> rtn;
0315     const double interval = (end-start)/static_cast<double>(nbins);
0316     for (size_t i = 0; i < nbins; ++i) {
0317       rtn.push_back(start + i*interval);
0318     }
0319     assert(rtn.size() == nbins);
0320     if (include_end) rtn.push_back(end); //< exact end, not result of n * interval
0321     return rtn;
0322   }
0323 
0324 
0325   /// @brief Make a list of values equally spaced by @a step between @a start and @a end inclusive.
0326   ///
0327   /// The values will start at @a start and be equally spaced up to the highest
0328   /// increment less than or equal to @a end. If @a include_end is given, the @a
0329   /// end value will be appended if distinct by @a tol times @a step.
0330   ///
0331   /// @note The arg ordering is "Rivet-like", cf. linspace() and logspace(),
0332   /// as opposed to the Numpy/Matlab arange() function (whose name inspired this,
0333   /// but we preferred to keep the "space" nomenclature for consistence.)
0334   ///
0335   /// @todo Move to HEPUtils
0336   inline vector<double> aspace(double step, double start, double end, bool include_end=true, double tol=1e-2) {
0337     assert( (end-start)*step > 0); //< ensure the step is going in the direction from start to end
0338     vector<double> rtn;
0339     double next = start;
0340     while (true) {
0341       if (next > end) break;
0342       rtn.push_back(next);
0343       next += step;
0344     }
0345     if (include_end) {
0346       if (end - rtn[rtn.size()-1] > tol*step) rtn.push_back(end);
0347     }
0348     return rtn;
0349   }
0350 
0351 
0352   /// Produce a vector of x values which are equally spaced in fn(x)
0353   ///
0354   /// @todo Move to HEPUtils
0355   inline vector<double> fnspace(size_t nbins, double start, double end,
0356                                 const std::function<double(double)>& fn, const std::function<double(double)>& invfn,
0357                                 bool include_end=true) {
0358     // assert(end >= start);
0359     assert(nbins > 0);
0360     const double pmin = fn(start);
0361     const double pmax = fn(end);
0362     const vector<double> edges = linspace(nbins, pmin, pmax, false);
0363     assert(edges.size() == nbins);
0364     vector<double> rtn; rtn.reserve(nbins+1);
0365     rtn.push_back(start); //< exact start, not round-tripped
0366     for (size_t i = 1; i < edges.size(); ++i) {
0367       rtn.push_back(invfn(edges[i]));
0368     }
0369     assert(rtn.size() == nbins);
0370     if (include_end) rtn.push_back(end); //< exact end
0371     return rtn;
0372   }
0373 
0374 
0375   /// @brief Make a list of @a nbins + 1 values exponentially spaced between @a start and @a end inclusive.
0376   ///
0377   /// The naming is because the values are uniformly spaced in log(x).
0378   ///
0379   /// @note The arg ordering and the meaning of the nbins variable is "histogram-like",
0380   /// as opposed to the Numpy/Matlab version, and the start and end arguments are expressed
0381   /// in "normal" space, rather than as the logarithms of the start/end values as in Numpy/Matlab.
0382   ///
0383   /// @todo Move to HEPUtils
0384   inline vector<double> logspace(size_t nbins, double start, double end, bool include_end=true) {
0385     return fnspace(nbins, start, end,
0386                    [](double x){ return std::log(x); },
0387                    [](double x){ return std::exp(x); },
0388                    include_end);
0389   }
0390 
0391 
0392   /// @brief Make a list of @a nbins + 1 values power-law spaced between @a start and @a end inclusive.
0393   ///
0394   /// The naming is because the values are uniformly spaced in x^n.
0395   ///
0396   /// @note The arg ordering and the meaning of the nbins variable is "histogram-like",
0397   /// as opposed to the Numpy/Matlab version, and the start and end arguments are expressed
0398   /// in terms of x rather than its transform.
0399   ///
0400   /// @todo Move to HEPUtils
0401   inline vector<double> powspace(size_t nbins, double start, double end, double npow, bool include_end=true) {
0402     assert(start >= 0); //< non-integer powers are complex for negative numbers... don't go there
0403     return fnspace(nbins, start, end,
0404                    [&](double x){ return std::pow(x, npow); },
0405                    [&](double x){ return std::pow(x, 1/npow); },
0406                    include_end);
0407   }
0408 
0409   /// @brief Make a list of @a nbins + 1 values equally spaced in the CDF of x^n between @a start and @a end inclusive.
0410   ///
0411   /// The naming is because the values are uniformly spaced in the integral x^n, implementing an inverse-CDF
0412   /// transform cf. random sampling from the power-law distribution. A histogram binned this way and filled
0413   /// with samples from x^n will asymptotically have equal populations and hence stat errors in each bin.
0414   ///
0415   /// @note The arg ordering and the meaning of the nbins variable is "histogram-like",
0416   /// as opposed to the Numpy/Matlab version, and the start and end arguments are expressed
0417   /// in terms of x rather than its transform.
0418   ///
0419   /// @todo Move to HEPUtils
0420   inline vector<double> powdbnspace(size_t nbins, double start, double end, double npow, bool include_end=true) {
0421     assert(start >= 0); //< non-integer powers are complex for negative numbers... don't go there
0422     return fnspace(nbins, start, end,
0423                    [&](double x){ return std::pow(x, npow+1) / (npow+1); },
0424                    [&](double x){ return std::pow((npow+1) * x, 1/(npow+1)); },
0425                    include_end);
0426   }
0427 
0428 
0429   /// @brief Make a list of @a nbins + 1 values spaced for equal area
0430   /// Breit-Wigner binning between @a start and @a end inclusive. @a
0431   /// mu and @a gamma are the Breit-Wigner parameters.
0432   ///
0433   /// @note The arg ordering and the meaning of the nbins variable is "histogram-like",
0434   /// as opposed to the Numpy/Matlab version, and the start and end arguments are expressed
0435   /// in terms of x rather than its transform.
0436   inline vector<double> bwdbnspace(size_t nbins, double start, double end, double mu, double gamma, bool include_end=true) {
0437     return fnspace(nbins, start, end,
0438                    [&](double x){ return cdfBW(x, mu, gamma); },
0439                    [&](double x){ return invcdfBW(x, mu, gamma); },
0440                    include_end);
0441   }
0442 
0443 
0444   /// Actual helper implementation of binIndex (so generic and specific overloading can work)
0445   template <typename NUM, typename CONTAINER>
0446   inline typename std::enable_if_t<std::is_arithmetic_v<NUM> && std::is_arithmetic_v<typename CONTAINER::value_type>, int>
0447   _binIndex(NUM val, const CONTAINER& binedges, bool allow_overflow=false) {
0448     if (val < *begin(binedges)) return -1; ///< Below/out of histo range
0449     // CONTAINER::iterator_type itend =
0450     if (val >= *(end(binedges)-1)) return allow_overflow ? int(binedges.size())-1 : -1; ///< Above/out of histo range
0451     auto it = std::upper_bound(begin(binedges), end(binedges), val);
0452     return std::distance(begin(binedges), --it);
0453   }
0454 
0455   /// @brief Return the bin index of the given value, @a val, given a vector of bin edges
0456   ///
0457   /// An underflow always returns -1. If allow_overflow is false (default) an overflow
0458   /// also returns -1, otherwise it returns the Nedge-1, the index of an inclusive bin
0459   /// starting at the last edge.
0460   ///
0461   /// @note The @a binedges vector must be sorted
0462   /// @todo Use std::common_type<NUM1, NUM2>::type x = val; ?
0463   template <typename NUM1, typename NUM2>
0464   inline typename std::enable_if_t<std::is_arithmetic_v<NUM1> && std::is_arithmetic_v<NUM2>, int>
0465   binIndex(NUM1 val, std::initializer_list<NUM2> binedges, bool allow_overflow=false) {
0466     return _binIndex(val, binedges, allow_overflow);
0467   }
0468 
0469   /// @brief Return the bin index of the given value, @a val, given a vector of bin edges
0470   ///
0471   /// An underflow always returns -1. If allow_overflow is false (default) an overflow
0472   /// also returns -1, otherwise it returns the Nedge-1, the index of an inclusive bin
0473   /// starting at the last edge.
0474   ///
0475   /// @note The @a binedges vector must be sorted
0476   /// @todo Use std::common_type<NUM1, NUM2>::type x = val; ?
0477   template <typename NUM, typename CONTAINER>
0478   inline typename std::enable_if_t<std::is_arithmetic_v<NUM> && std::is_arithmetic_v<typename CONTAINER::value_type>, int>
0479   binIndex(NUM val, const CONTAINER& binedges, bool allow_overflow=false) {
0480     return _binIndex(val, binedges, allow_overflow);
0481   }
0482 
0483   /// @}
0484 
0485 
0486   /// @name Discrete statistics functions
0487   /// @{
0488 
0489   /// Calculate the median of a sample
0490   /// @todo Support multiple container types via SFINAE
0491   template <typename NUM>
0492   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0493   median(const vector<NUM>& sample) {
0494     if (sample.empty()) throw RangeError("Can't compute median of an empty set");
0495     vector<NUM> tmp = sample;
0496     std::sort(tmp.begin(), tmp.end());
0497     const size_t imid = tmp.size()/2; // len1->idx0, len2->idx1, len3->idx1, len4->idx2, ...
0498     if (sample.size() % 2 == 0) return (tmp.at(imid-1) + tmp.at(imid)) / 2.0;
0499     else return tmp.at(imid);
0500   }
0501 
0502 
0503   /// Calculate the mean of a sample
0504   /// @todo Support multiple container types via SFINAE
0505   template <typename NUM>
0506   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0507   mean(const vector<NUM>& sample) {
0508     if (sample.empty()) throw RangeError("Can't compute mean of an empty set");
0509     double mean = 0.0;
0510     for (size_t i = 0; i < sample.size(); ++i) {
0511       mean += sample[i];
0512     }
0513     return mean/sample.size();
0514   }
0515 
0516   // Calculate the error on the mean, assuming Poissonian errors
0517   /// @todo Support multiple container types via SFINAE
0518   template <typename NUM>
0519   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0520   mean_err(const vector<NUM>& sample) {
0521     if (sample.empty()) throw RangeError("Can't compute mean_err of an empty set");
0522     double mean_e = 0.0;
0523     for (size_t i = 0; i < sample.size(); ++i) {
0524       mean_e += sqrt(sample[i]);
0525     }
0526     return mean_e/sample.size();
0527   }
0528 
0529 
0530   /// Calculate the covariance (variance) between two samples
0531   /// @todo Support multiple container types via SFINAE
0532   template <typename NUM>
0533   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0534   covariance(const vector<NUM>& sample1, const vector<NUM>& sample2) {
0535     if (sample1.empty() || sample2.empty()) throw RangeError("Can't compute covariance of an empty set");
0536     if (sample1.size() != sample2.size()) throw RangeError("Sizes of samples must be equal for covariance calculation");
0537     const double mean1 = mean(sample1);
0538     const double mean2 = mean(sample2);
0539     const size_t N = sample1.size();
0540     double cov = 0.0;
0541     for (size_t i = 0; i < N; i++) {
0542       const double cov_i = (sample1[i] - mean1)*(sample2[i] - mean2);
0543       cov += cov_i;
0544     }
0545     if (N > 1) return cov/(N-1);
0546     else return 0.0;
0547   }
0548 
0549   /// Calculate the error on the covariance (variance) of two samples, assuming poissonian errors
0550   /// @todo Support multiple container types via SFINAE
0551   template <typename NUM>
0552   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0553   covariance_err(const vector<NUM>& sample1, const vector<NUM>& sample2) {
0554     if (sample1.empty() || sample2.empty()) throw RangeError("Can't compute covariance_err of an empty set");
0555     if (sample1.size() != sample2.size()) throw RangeError("Sizes of samples must be equal for covariance_err calculation");
0556     const double mean1 = mean(sample1);
0557     const double mean2 = mean(sample2);
0558     const double mean1_e = mean_err(sample1);
0559     const double mean2_e = mean_err(sample2);
0560     const size_t N = sample1.size();
0561     double cov_e = 0.0;
0562     for (size_t i = 0; i < N; i++) {
0563       const double cov_i = (sqrt(sample1[i]) - mean1_e)*(sample2[i] - mean2) +
0564         (sample1[i] - mean1)*(sqrt(sample2[i]) - mean2_e);
0565       cov_e += cov_i;
0566     }
0567     if (N > 1) return cov_e/(N-1);
0568     else return 0.0;
0569   }
0570 
0571 
0572   /// Calculate the correlation strength between two samples
0573   /// @todo Support multiple container types via SFINAE
0574   template <typename NUM>
0575   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0576   correlation(const vector<NUM>& sample1, const vector<NUM>& sample2) {
0577     const double cov = covariance(sample1, sample2);
0578     const double var1 = covariance(sample1, sample1);
0579     const double var2 = covariance(sample2, sample2);
0580     const double correlation = cov/sqrt(var1*var2);
0581     const double corr_strength = correlation*sqrt(var2/var1);
0582     return corr_strength;
0583   }
0584 
0585   /// Calculate the error of the correlation strength between two samples assuming Poissonian errors
0586   /// @todo Support multiple container types via SFINAE
0587   template <typename NUM>
0588   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0589   correlation_err(const vector<NUM>& sample1, const vector<NUM>& sample2) {
0590     const double cov = covariance(sample1, sample2);
0591     const double var1 = covariance(sample1, sample1);
0592     const double var2 = covariance(sample2, sample2);
0593     const double cov_e = covariance_err(sample1, sample2);
0594     const double var1_e = covariance_err(sample1, sample1);
0595     const double var2_e = covariance_err(sample2, sample2);
0596 
0597     // Calculate the correlation
0598     const double correlation = cov/sqrt(var1*var2);
0599     // Calculate the error on the correlation
0600     const double correlation_err = cov_e/sqrt(var1*var2) -
0601       cov/(2*pow(3./2., var1*var2)) * (var1_e * var2 + var1 * var2_e);
0602 
0603     // Calculate the error on the correlation strength
0604     const double corr_strength_err = correlation_err*sqrt(var2/var1) +
0605       correlation/(2*sqrt(var2/var1)) * (var2_e/var1 - var2*var1_e/pow(2, var2));
0606 
0607     return corr_strength_err;
0608   }
0609 
0610   /// @}
0611 
0612 
0613   /// @name Angle range mappings
0614   /// @{
0615 
0616   /// @brief Reduce any number to the range [-2PI, 2PI]
0617   ///
0618   /// Achieved by repeated addition or subtraction of 2PI as required. Used to
0619   /// normalise angular measures.
0620   inline double _mapAngleM2PITo2Pi(double angle) {
0621     double rtn = fmod(angle, TWOPI);
0622     if (isZero(rtn)) return 0;
0623     assert(rtn >= -TWOPI && rtn <= TWOPI);
0624     return rtn;
0625   }
0626 
0627   /// Map an angle into the range (-PI, PI].
0628   inline double mapAngleMPiToPi(double angle) {
0629     double rtn = _mapAngleM2PITo2Pi(angle);
0630     if (isZero(rtn)) return 0;
0631     if (rtn > PI) rtn -= TWOPI;
0632     if (rtn <= -PI) rtn += TWOPI;
0633     assert(rtn > -PI && rtn <= PI);
0634     return rtn;
0635   }
0636 
0637   /// Map an angle into the range [0, 2PI).
0638   inline double mapAngle0To2Pi(double angle) {
0639     double rtn = _mapAngleM2PITo2Pi(angle);
0640     if (isZero(rtn)) return 0;
0641     if (rtn < 0) rtn += TWOPI;
0642     if (rtn == TWOPI) rtn = 0;
0643     assert(rtn >= 0 && rtn < TWOPI);
0644     return rtn;
0645   }
0646 
0647   /// Map an angle into the range [0, PI].
0648   inline double mapAngle0ToPi(double angle) {
0649     double rtn = fabs(mapAngleMPiToPi(angle));
0650     if (isZero(rtn)) return 0;
0651     assert(rtn > 0 && rtn <= PI);
0652     return rtn;
0653   }
0654 
0655   /// Map an angle into the enum-specified range.
0656   inline double mapAngle(double angle, PhiMapping mapping) {
0657     switch (mapping) {
0658     case MINUSPI_PLUSPI:
0659       return mapAngleMPiToPi(angle);
0660     case ZERO_2PI:
0661       return mapAngle0To2Pi(angle);
0662     case ZERO_PI:
0663       return mapAngle0ToPi(angle);
0664     default:
0665       throw Rivet::UserError("The specified phi mapping scheme is not implemented");
0666     }
0667   }
0668 
0669   /// @}
0670 
0671 
0672   /// @name Phase-space measure helpers
0673   /// @{
0674 
0675   /// @brief Calculate the difference between two angles in radians
0676   ///
0677   /// Returns in the range [0, PI].
0678   inline double deltaPhi(double phi1, double phi2, bool sign=false) {
0679     const double x = mapAngleMPiToPi(phi1 - phi2);
0680     return sign ? x : fabs(x);
0681   }
0682 
0683   /// Calculate the abs difference between two pseudorapidities
0684   ///
0685   /// @note Just a cosmetic name for analysis code clarity.
0686   inline double deltaEta(double eta1, double eta2, bool sign=false) {
0687     const double x = eta1 - eta2;
0688     return sign ? x : fabs(x);
0689   }
0690 
0691   /// Calculate the abs difference between two rapidities
0692   ///
0693   /// @note Just a cosmetic name for analysis code clarity.
0694   inline double deltaRap(double y1, double y2, bool sign=false) {
0695     const double x = y1 - y2;
0696     return sign? x : fabs(x);
0697   }
0698 
0699   /// Calculate the squared distance between two points in 2D rapidity-azimuthal
0700   /// ("\f$ \eta-\phi \f$") space. The phi values are given in radians.
0701   inline double deltaR2(double rap1, double phi1, double rap2, double phi2) {
0702     const double dphi = deltaPhi(phi1, phi2);
0703     return sqr(rap1-rap2) + sqr(dphi);
0704   }
0705 
0706   /// Calculate the distance between two points in 2D rapidity-azimuthal
0707   /// ("\f$ \eta-\phi \f$") space. The phi values are given in radians.
0708   inline double deltaR(double rap1, double phi1, double rap2, double phi2) {
0709     return sqrt(deltaR2(rap1, phi1, rap2, phi2));
0710   }
0711 
0712   /// Calculate a rapidity value from the supplied energy @a E and longitudinal momentum @a pz.
0713   inline double rapidity(double E, double pz) {
0714     if (isZero(E - pz)) {
0715       throw std::runtime_error("Divergent positive rapidity");
0716       return DBL_MAX;
0717     }
0718     if (isZero(E + pz)) {
0719       throw std::runtime_error("Divergent negative rapidity");
0720       return -DBL_MAX;
0721     }
0722     return 0.5*log((E+pz)/(E-pz));
0723   }
0724 
0725   /// @}
0726 
0727 
0728   /// Calculate transverse mass of two vectors from provided pT and deltaPhi
0729   ///
0730   /// @note Several versions taking two vectors are found in Vector4.hh
0731   inline double mT(double pT1, double pT2, double dphi) {
0732     return sqrt(2*pT1*pT2 * (1 - cos(dphi)) );
0733   }
0734 
0735 
0736 }
0737 
0738 
0739 #endif