Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-04-19 09:06:46

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   /// @brief Named number-type addition in quadrature operation.
0223   ///
0224   /// @note Result has the sqrt operation applied.
0225   /// @todo When std::common_type can be used, generalise to multiple numeric types with appropriate return type.
0226   // template <typename N1, typename N2>
0227   template <typename NUM>
0228   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0229   //std::common_type<N1, N2>::type
0230   add_quad(NUM a, NUM b) {
0231     return sqrt(a*a + b*b);
0232   }
0233 
0234   /// Named number-type addition in quadrature operation.
0235   ///
0236   /// @note Result has the sqrt operation applied.
0237   /// @todo When std::common_type can be used, generalise to multiple numeric types with appropriate return type.
0238   // template <typename N1, typename N2>
0239   template <typename NUM>
0240   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0241   //std::common_type<N1, N2, N3>::type
0242   add_quad(NUM a, NUM b, NUM c) {
0243     return sqrt(a*a + b*b + c*c);
0244   }
0245 
0246   /// Return a/b, or @a fail if b = 0
0247   /// @todo When std::common_type can be used, generalise to multiple numeric types with appropriate return type.
0248   inline double safediv(double num, double den, double fail=0.0) {
0249     return (!isZero(den)) ? num/den : fail;
0250   }
0251 
0252   /// A more efficient version of pow for raising numbers to integer powers.
0253   template <typename NUM>
0254   constexpr inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0255   intpow(NUM val, unsigned int exp) {
0256     if (exp == 0) return (NUM) 1;
0257     else if (exp == 1) return val;
0258     return val * intpow(val, exp-1);
0259   }
0260 
0261   /// Find the sign of a number
0262   template <typename NUM>
0263   constexpr inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, int>
0264   sign(NUM val) {
0265     if (isZero(val)) return ZERO;
0266     const int valsign = (val > 0) ? PLUS : MINUS;
0267     return valsign;
0268   }
0269 
0270   /// @}
0271 
0272 
0273   /// @name Physics statistical distributions
0274   /// @{
0275 
0276   /// @brief CDF for the Breit-Wigner distribution
0277   inline double cdfBW(double x, double mu, double gamma) {
0278     // normalize to (0;1) distribution
0279     const double xn = (x - mu)/gamma;
0280     return std::atan(xn)/M_PI + 0.5;
0281   }
0282 
0283   /// @brief Inverse CDF for the Breit-Wigner distribution
0284   inline double invcdfBW(double p, double mu, double gamma) {
0285     const double xn = std::tan(M_PI*(p-0.5));
0286     return gamma*xn + mu;
0287   }
0288 
0289   /// @}
0290 
0291 
0292   /// @name Binning helper functions
0293   /// @{
0294 
0295   /// @brief Make a list of @a nbins + 1 values equally spaced between @a start and @a end inclusive.
0296   ///
0297   /// @note The arg ordering and the meaning of the nbins variable is "histogram-like",
0298   /// as opposed to the Numpy/Matlab version.
0299   ///
0300   /// @todo Move to HEPUtils
0301   inline vector<double> linspace(size_t nbins, double start, double end, bool include_end=true) {
0302     assert(nbins > 0);
0303     vector<double> rtn;
0304     const double interval = (end-start)/static_cast<double>(nbins);
0305     for (size_t i = 0; i < nbins; ++i) {
0306       rtn.push_back(start + i*interval);
0307     }
0308     assert(rtn.size() == nbins);
0309     if (include_end) rtn.push_back(end); //< exact end, not result of n * interval
0310     return rtn;
0311   }
0312 
0313 
0314   /// @brief Make a list of values equally spaced by @a step between @a start and @a end inclusive.
0315   ///
0316   /// The values will start at @a start and be equally spaced up to the highest
0317   /// increment less than or equal to @a end. If @a include_end is given, the @a
0318   /// end value will be appended if distinct by @a tol times @a step.
0319   ///
0320   /// @note The arg ordering is "Rivet-like", cf. linspace() and logspace(),
0321   /// as opposed to the Numpy/Matlab arange() function (whose name inspired this,
0322   /// but we preferred to keep the "space" nomenclature for consistence.)
0323   ///
0324   /// @todo Move to HEPUtils
0325   inline vector<double> aspace(double step, double start, double end, bool include_end=true, double tol=1e-2) {
0326     assert( (end-start)*step > 0); //< ensure the step is going in the direction from start to end
0327     vector<double> rtn;
0328     double next = start;
0329     while (true) {
0330       if (next > end) break;
0331       rtn.push_back(next);
0332       next += step;
0333     }
0334     if (include_end) {
0335       if (end - rtn[rtn.size()-1] > tol*step) rtn.push_back(end);
0336     }
0337     return rtn;
0338   }
0339 
0340 
0341   /// Produce a vector of x values which are equally spaced in fn(x)
0342   ///
0343   /// @todo Move to HEPUtils
0344   inline vector<double> fnspace(size_t nbins, double start, double end,
0345                                 const std::function<double(double)>& fn, const std::function<double(double)>& invfn,
0346                                 bool include_end=true) {
0347     // assert(end >= start);
0348     assert(nbins > 0);
0349     const double pmin = fn(start);
0350     const double pmax = fn(end);
0351     const vector<double> edges = linspace(nbins, pmin, pmax, false);
0352     assert(edges.size() == nbins);
0353     vector<double> rtn; rtn.reserve(nbins+1);
0354     rtn.push_back(start); //< exact start, not round-tripped
0355     for (size_t i = 1; i < edges.size(); ++i) {
0356       rtn.push_back(invfn(edges[i]));
0357     }
0358     assert(rtn.size() == nbins);
0359     if (include_end) rtn.push_back(end); //< exact end
0360     return rtn;
0361   }
0362 
0363 
0364   /// @brief Make a list of @a nbins + 1 values exponentially spaced between @a start and @a end inclusive.
0365   ///
0366   /// The naming is because the values are uniformly spaced in log(x).
0367   ///
0368   /// @note The arg ordering and the meaning of the nbins variable is "histogram-like",
0369   /// as opposed to the Numpy/Matlab version, and the start and end arguments are expressed
0370   /// in "normal" space, rather than as the logarithms of the start/end values as in Numpy/Matlab.
0371   ///
0372   /// @todo Move to HEPUtils
0373   inline vector<double> logspace(size_t nbins, double start, double end, bool include_end=true) {
0374     return fnspace(nbins, start, end,
0375                    [](double x){ return std::log(x); },
0376                    [](double x){ return std::exp(x); },
0377                    include_end);
0378   }
0379 
0380 
0381   /// @brief Make a list of @a nbins + 1 values power-law spaced between @a start and @a end inclusive.
0382   ///
0383   /// The naming is because the values are uniformly spaced in x^n.
0384   ///
0385   /// @note The arg ordering and the meaning of the nbins variable is "histogram-like",
0386   /// as opposed to the Numpy/Matlab version, and the start and end arguments are expressed
0387   /// in terms of x rather than its transform.
0388   ///
0389   /// @todo Move to HEPUtils
0390   inline vector<double> powspace(size_t nbins, double start, double end, double npow, bool include_end=true) {
0391     assert(start >= 0); //< non-integer powers are complex for negative numbers... don't go there
0392     return fnspace(nbins, start, end,
0393                    [&](double x){ return std::pow(x, npow); },
0394                    [&](double x){ return std::pow(x, 1/npow); },
0395                    include_end);
0396   }
0397 
0398   /// @brief Make a list of @a nbins + 1 values equally spaced in the CDF of x^n between @a start and @a end inclusive.
0399   ///
0400   /// The naming is because the values are uniformly spaced in the integral x^n, implementing an inverse-CDF
0401   /// transform cf. random sampling from the power-law distribution. A histogram binned this way and filled
0402   /// with samples from x^n will asymptotically have equal populations and hence stat errors in each bin.
0403   ///
0404   /// @note The arg ordering and the meaning of the nbins variable is "histogram-like",
0405   /// as opposed to the Numpy/Matlab version, and the start and end arguments are expressed
0406   /// in terms of x rather than its transform.
0407   ///
0408   /// @todo Move to HEPUtils
0409   inline vector<double> powdbnspace(size_t nbins, double start, double end, double npow, bool include_end=true) {
0410     assert(start >= 0); //< non-integer powers are complex for negative numbers... don't go there
0411     return fnspace(nbins, start, end,
0412                    [&](double x){ return std::pow(x, npow+1) / (npow+1); },
0413                    [&](double x){ return std::pow((npow+1) * x, 1/(npow+1)); },
0414                    include_end);
0415   }
0416 
0417 
0418   /// @brief Make a list of @a nbins + 1 values spaced for equal area
0419   /// Breit-Wigner binning between @a start and @a end inclusive. @a
0420   /// mu and @a gamma are the Breit-Wigner parameters.
0421   ///
0422   /// @note The arg ordering and the meaning of the nbins variable is "histogram-like",
0423   /// as opposed to the Numpy/Matlab version, and the start and end arguments are expressed
0424   /// in terms of x rather than its transform.
0425   inline vector<double> bwdbnspace(size_t nbins, double start, double end, double mu, double gamma, bool include_end=true) {
0426     return fnspace(nbins, start, end,
0427                    [&](double x){ return cdfBW(x, mu, gamma); },
0428                    [&](double x){ return invcdfBW(x, mu, gamma); },
0429                    include_end);
0430   }
0431 
0432 
0433   /// Actual helper implementation of binIndex (so generic and specific overloading can work)
0434   template <typename NUM, typename CONTAINER>
0435   inline typename std::enable_if_t<std::is_arithmetic_v<NUM> && std::is_arithmetic_v<typename CONTAINER::value_type>, int>
0436   _binIndex(NUM val, const CONTAINER& binedges, bool allow_overflow=false) {
0437     if (val < *begin(binedges)) return -1; ///< Below/out of histo range
0438     // CONTAINER::iterator_type itend =
0439     if (val >= *(end(binedges)-1)) return allow_overflow ? int(binedges.size())-1 : -1; ///< Above/out of histo range
0440     auto it = std::upper_bound(begin(binedges), end(binedges), val);
0441     return std::distance(begin(binedges), --it);
0442   }
0443 
0444   /// @brief Return the bin index of the given value, @a val, given a vector of bin edges
0445   ///
0446   /// An underflow always returns -1. If allow_overflow is false (default) an overflow
0447   /// also returns -1, otherwise it returns the Nedge-1, the index of an inclusive bin
0448   /// starting at the last edge.
0449   ///
0450   /// @note The @a binedges vector must be sorted
0451   /// @todo Use std::common_type<NUM1, NUM2>::type x = val; ?
0452   template <typename NUM1, typename NUM2>
0453   inline typename std::enable_if_t<std::is_arithmetic_v<NUM1> && std::is_arithmetic_v<NUM2>, int>
0454   binIndex(NUM1 val, std::initializer_list<NUM2> binedges, bool allow_overflow=false) {
0455     return _binIndex(val, binedges, allow_overflow);
0456   }
0457 
0458   /// @brief Return the bin index of the given value, @a val, given a vector of bin edges
0459   ///
0460   /// An underflow always returns -1. If allow_overflow is false (default) an overflow
0461   /// also returns -1, otherwise it returns the Nedge-1, the index of an inclusive bin
0462   /// starting at the last edge.
0463   ///
0464   /// @note The @a binedges vector must be sorted
0465   /// @todo Use std::common_type<NUM1, NUM2>::type x = val; ?
0466   template <typename NUM, typename CONTAINER>
0467   inline typename std::enable_if_t<std::is_arithmetic_v<NUM> && std::is_arithmetic_v<typename CONTAINER::value_type>, int>
0468   binIndex(NUM val, const CONTAINER& binedges, bool allow_overflow=false) {
0469     return _binIndex(val, binedges, allow_overflow);
0470   }
0471 
0472   /// @}
0473 
0474 
0475   /// @name Discrete statistics functions
0476   /// @{
0477 
0478   /// Calculate the median of a sample
0479   /// @todo Support multiple container types via SFINAE
0480   template <typename NUM>
0481   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0482   median(const vector<NUM>& sample) {
0483     if (sample.empty()) throw RangeError("Can't compute median of an empty set");
0484     vector<NUM> tmp = sample;
0485     std::sort(tmp.begin(), tmp.end());
0486     const size_t imid = tmp.size()/2; // len1->idx0, len2->idx1, len3->idx1, len4->idx2, ...
0487     if (sample.size() % 2 == 0) return (tmp.at(imid-1) + tmp.at(imid)) / 2.0;
0488     else return tmp.at(imid);
0489   }
0490 
0491 
0492   /// Calculate the mean of a sample
0493   /// @todo Support multiple container types via SFINAE
0494   template <typename NUM>
0495   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0496   mean(const vector<NUM>& sample) {
0497     if (sample.empty()) throw RangeError("Can't compute mean of an empty set");
0498     double mean = 0.0;
0499     for (size_t i = 0; i < sample.size(); ++i) {
0500       mean += sample[i];
0501     }
0502     return mean/sample.size();
0503   }
0504 
0505   // Calculate the error on the mean, assuming Poissonian errors
0506   /// @todo Support multiple container types via SFINAE
0507   template <typename NUM>
0508   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0509   mean_err(const vector<NUM>& sample) {
0510     if (sample.empty()) throw RangeError("Can't compute mean_err of an empty set");
0511     double mean_e = 0.0;
0512     for (size_t i = 0; i < sample.size(); ++i) {
0513       mean_e += sqrt(sample[i]);
0514     }
0515     return mean_e/sample.size();
0516   }
0517 
0518 
0519   /// Calculate the covariance (variance) between two samples
0520   /// @todo Support multiple container types via SFINAE
0521   template <typename NUM>
0522   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0523   covariance(const vector<NUM>& sample1, const vector<NUM>& sample2) {
0524     if (sample1.empty() || sample2.empty()) throw RangeError("Can't compute covariance of an empty set");
0525     if (sample1.size() != sample2.size()) throw RangeError("Sizes of samples must be equal for covariance calculation");
0526     const double mean1 = mean(sample1);
0527     const double mean2 = mean(sample2);
0528     const size_t N = sample1.size();
0529     double cov = 0.0;
0530     for (size_t i = 0; i < N; i++) {
0531       const double cov_i = (sample1[i] - mean1)*(sample2[i] - mean2);
0532       cov += cov_i;
0533     }
0534     if (N > 1) return cov/(N-1);
0535     else return 0.0;
0536   }
0537 
0538   /// Calculate the error on the covariance (variance) of two samples, assuming poissonian errors
0539   /// @todo Support multiple container types via SFINAE
0540   template <typename NUM>
0541   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0542   covariance_err(const vector<NUM>& sample1, const vector<NUM>& sample2) {
0543     if (sample1.empty() || sample2.empty()) throw RangeError("Can't compute covariance_err of an empty set");
0544     if (sample1.size() != sample2.size()) throw RangeError("Sizes of samples must be equal for covariance_err calculation");
0545     const double mean1 = mean(sample1);
0546     const double mean2 = mean(sample2);
0547     const double mean1_e = mean_err(sample1);
0548     const double mean2_e = mean_err(sample2);
0549     const size_t N = sample1.size();
0550     double cov_e = 0.0;
0551     for (size_t i = 0; i < N; i++) {
0552       const double cov_i = (sqrt(sample1[i]) - mean1_e)*(sample2[i] - mean2) +
0553         (sample1[i] - mean1)*(sqrt(sample2[i]) - mean2_e);
0554       cov_e += cov_i;
0555     }
0556     if (N > 1) return cov_e/(N-1);
0557     else return 0.0;
0558   }
0559 
0560 
0561   /// Calculate the correlation strength between two samples
0562   /// @todo Support multiple container types via SFINAE
0563   template <typename NUM>
0564   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0565   correlation(const vector<NUM>& sample1, const vector<NUM>& sample2) {
0566     const double cov = covariance(sample1, sample2);
0567     const double var1 = covariance(sample1, sample1);
0568     const double var2 = covariance(sample2, sample2);
0569     const double correlation = cov/sqrt(var1*var2);
0570     const double corr_strength = correlation*sqrt(var2/var1);
0571     return corr_strength;
0572   }
0573 
0574   /// Calculate the error of the correlation strength between two samples assuming Poissonian errors
0575   /// @todo Support multiple container types via SFINAE
0576   template <typename NUM>
0577   inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0578   correlation_err(const vector<NUM>& sample1, const vector<NUM>& sample2) {
0579     const double cov = covariance(sample1, sample2);
0580     const double var1 = covariance(sample1, sample1);
0581     const double var2 = covariance(sample2, sample2);
0582     const double cov_e = covariance_err(sample1, sample2);
0583     const double var1_e = covariance_err(sample1, sample1);
0584     const double var2_e = covariance_err(sample2, sample2);
0585 
0586     // Calculate the correlation
0587     const double correlation = cov/sqrt(var1*var2);
0588     // Calculate the error on the correlation
0589     const double correlation_err = cov_e/sqrt(var1*var2) -
0590       cov/(2*pow(3./2., var1*var2)) * (var1_e * var2 + var1 * var2_e);
0591 
0592     // Calculate the error on the correlation strength
0593     const double corr_strength_err = correlation_err*sqrt(var2/var1) +
0594       correlation/(2*sqrt(var2/var1)) * (var2_e/var1 - var2*var1_e/pow(2, var2));
0595 
0596     return corr_strength_err;
0597   }
0598 
0599   /// @}
0600 
0601 
0602   /// @name Angle range mappings
0603   /// @{
0604 
0605   /// @brief Reduce any number to the range [-2PI, 2PI]
0606   ///
0607   /// Achieved by repeated addition or subtraction of 2PI as required. Used to
0608   /// normalise angular measures.
0609   inline double _mapAngleM2PITo2Pi(double angle) {
0610     double rtn = fmod(angle, TWOPI);
0611     if (isZero(rtn)) return 0;
0612     assert(rtn >= -TWOPI && rtn <= TWOPI);
0613     return rtn;
0614   }
0615 
0616   /// Map an angle into the range (-PI, PI].
0617   inline double mapAngleMPiToPi(double angle) {
0618     double rtn = _mapAngleM2PITo2Pi(angle);
0619     if (isZero(rtn)) return 0;
0620     if (rtn > PI) rtn -= TWOPI;
0621     if (rtn <= -PI) rtn += TWOPI;
0622     assert(rtn > -PI && rtn <= PI);
0623     return rtn;
0624   }
0625 
0626   /// Map an angle into the range [0, 2PI).
0627   inline double mapAngle0To2Pi(double angle) {
0628     double rtn = _mapAngleM2PITo2Pi(angle);
0629     if (isZero(rtn)) return 0;
0630     if (rtn < 0) rtn += TWOPI;
0631     if (rtn == TWOPI) rtn = 0;
0632     assert(rtn >= 0 && rtn < TWOPI);
0633     return rtn;
0634   }
0635 
0636   /// Map an angle into the range [0, PI].
0637   inline double mapAngle0ToPi(double angle) {
0638     double rtn = fabs(mapAngleMPiToPi(angle));
0639     if (isZero(rtn)) return 0;
0640     assert(rtn > 0 && rtn <= PI);
0641     return rtn;
0642   }
0643 
0644   /// Map an angle into the enum-specified range.
0645   inline double mapAngle(double angle, PhiMapping mapping) {
0646     switch (mapping) {
0647     case MINUSPI_PLUSPI:
0648       return mapAngleMPiToPi(angle);
0649     case ZERO_2PI:
0650       return mapAngle0To2Pi(angle);
0651     case ZERO_PI:
0652       return mapAngle0To2Pi(angle);
0653     default:
0654       throw Rivet::UserError("The specified phi mapping scheme is not implemented");
0655     }
0656   }
0657 
0658   /// @}
0659 
0660 
0661   /// @name Phase-space measure helpers
0662   /// @{
0663 
0664   /// @brief Calculate the difference between two angles in radians
0665   ///
0666   /// Returns in the range [0, PI].
0667   inline double deltaPhi(double phi1, double phi2, bool sign=false) {
0668     const double x = mapAngleMPiToPi(phi1 - phi2);
0669     return sign ? x : fabs(x);
0670   }
0671 
0672   /// Calculate the abs difference between two pseudorapidities
0673   ///
0674   /// @note Just a cosmetic name for analysis code clarity.
0675   inline double deltaEta(double eta1, double eta2, bool sign=false) {
0676     const double x = eta1 - eta2;
0677     return sign ? x : fabs(x);
0678   }
0679 
0680   /// Calculate the abs difference between two rapidities
0681   ///
0682   /// @note Just a cosmetic name for analysis code clarity.
0683   inline double deltaRap(double y1, double y2, bool sign=false) {
0684     const double x = y1 - y2;
0685     return sign? x : fabs(x);
0686   }
0687 
0688   /// Calculate the squared distance between two points in 2D rapidity-azimuthal
0689   /// ("\f$ \eta-\phi \f$") space. The phi values are given in radians.
0690   inline double deltaR2(double rap1, double phi1, double rap2, double phi2) {
0691     const double dphi = deltaPhi(phi1, phi2);
0692     return sqr(rap1-rap2) + sqr(dphi);
0693   }
0694 
0695   /// Calculate the distance between two points in 2D rapidity-azimuthal
0696   /// ("\f$ \eta-\phi \f$") space. The phi values are given in radians.
0697   inline double deltaR(double rap1, double phi1, double rap2, double phi2) {
0698     return sqrt(deltaR2(rap1, phi1, rap2, phi2));
0699   }
0700 
0701   /// Calculate a rapidity value from the supplied energy @a E and longitudinal momentum @a pz.
0702   inline double rapidity(double E, double pz) {
0703     if (isZero(E - pz)) {
0704       throw std::runtime_error("Divergent positive rapidity");
0705       return DBL_MAX;
0706     }
0707     if (isZero(E + pz)) {
0708       throw std::runtime_error("Divergent negative rapidity");
0709       return -DBL_MAX;
0710     }
0711     return 0.5*log((E+pz)/(E-pz));
0712   }
0713 
0714   /// @}
0715 
0716 
0717   /// Calculate transverse mass of two vectors from provided pT and deltaPhi
0718   /// @note Several versions taking two vectors are found in Vector4.hh
0719   inline double mT(double pT1, double pT2, double dphi) {
0720     return sqrt(2*pT1*pT2 * (1 - cos(dphi)) );
0721   }
0722 
0723 
0724 }
0725 
0726 
0727 #endif