Back to home page

EIC code displayed by LXR

 
 

    


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

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_MathUtils_H
0007 #define YODA_MathUtils_H
0008 
0009 /// @todo Add SFINAE math type stuff (see Rivet) and add inrange() and inrange_closed_closed() etc. aliases cf. MCUtils
0010 
0011 #include "YODA/Exceptions.h"
0012 #include "YODA/Config/BuildConfig.h"
0013 
0014 #include <algorithm>
0015 #include <functional>
0016 #include <numeric>
0017 #include <cassert>
0018 #include <cfloat>
0019 #include <climits>
0020 #include <cmath>
0021 #include <functional>
0022 #include <iostream>
0023 #include <limits>
0024 #include <map>
0025 #include <numeric>
0026 #include <ostream>
0027 #include <sstream>
0028 #include <stdexcept>
0029 #include <string>
0030 #include <utility>
0031 #include <vector>
0032 
0033 namespace YODA {
0034 
0035 
0036   /// Pre-defined numeric type limits
0037   /// @deprecated Prefer the standard DBL/INT_MAX
0038   const static double MAXDOUBLE = DBL_MAX; // was std::numeric_limits<double>::max(); -- warns in GCC5
0039   const static double MAXINT = INT_MAX; // was std::numeric_limits<int>::max(); -- warns in GCC5
0040 
0041   /// A pre-defined value of \f$ \pi \f$.
0042   static const double PI = M_PI;
0043 
0044   /// A pre-defined value of \f$ 2\pi \f$.
0045   static const double TWOPI = 2*M_PI;
0046 
0047   /// A pre-defined value of \f$ \pi/2 \f$.
0048   static const double HALFPI = M_PI_2;
0049 
0050   /// Enum for signs of numbers.
0051   enum Sign { MINUS = -1, ZERO = 0, PLUS = 1 };
0052 
0053 
0054   /// @name Comparison functions for safe (floating point) equality tests
0055   //@{
0056 
0057   /// @brief Compare a number to zero
0058   ///
0059   /// This version for floating point types has a degree of fuzziness expressed
0060   /// by the absolute @a tolerance parameter, for floating point safety.
0061   template <typename NUM>
0062   inline typename std::enable_if_t<std::is_floating_point_v<NUM>, bool>
0063   isZero(NUM val, double tolerance=1e-8) {
0064     return fabs(val) < tolerance;
0065   }
0066 
0067   /// @brief Compare a number to zero
0068   ///
0069   /// SFINAE template specialisation for integers, since there is no FP
0070   /// precision issue.
0071   template <typename NUM>
0072   inline typename std::enable_if_t<std::is_integral_v<NUM>, bool>
0073   isZero(NUM val, double = 1e-5) {
0074     return val==0;
0075   }
0076 
0077   /// @brief Check if a number is NaN
0078   template <typename NUM>
0079   inline typename std::enable_if_t<std::is_floating_point_v<NUM>, bool>
0080   isNaN(NUM val) { return std::isnan(val); }
0081 
0082   /// @brief Check if a number is non-NaN
0083   template <typename NUM>
0084   inline typename std::enable_if_t<std::is_floating_point_v<NUM>, bool>
0085   notNaN(NUM val) { return !std::isnan(val); }
0086 
0087   /// @brief Compare two numbers for equality with a degree of fuzziness
0088   ///
0089   /// This version for floating point types (if any argument is FP) has a degree
0090   /// of fuzziness expressed by the fractional @a tolerance parameter, for
0091   /// floating point safety.
0092   template <typename N1, typename N2>
0093   inline typename std::enable_if_t<
0094     std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2> &&
0095    (std::is_floating_point_v<N1> || std::is_floating_point_v<N2>), bool>
0096   fuzzyEquals(N1 a, N2 b, double tolerance=1e-5) {
0097     const double absavg = (std::abs(a) + std::abs(b))/2.0;
0098     const double absdiff = std::abs(a - b);
0099     const bool rtn = (isZero(a) && isZero(b)) || absdiff < tolerance*absavg;
0100     return rtn;
0101   }
0102 
0103   /// @brief Compare two numbers for equality with a degree of fuzziness
0104   ///
0105   /// Simpler SFINAE template specialisation for integers, since there is no FP
0106   /// precision issue.
0107   template <typename N1, typename N2>
0108   inline typename std::enable_if_t<
0109     std::is_integral_v<N1> && std::is_integral_v<N2>, bool>
0110     fuzzyEquals(N1 a, N2 b, double) { //< NB. unused tolerance parameter for ints, still needs a default value!
0111     return a == b;
0112   }
0113 
0114   /// @brief Comparator wrapper to use with STL algorithms, e.g. std::equal etc.
0115   static std::function<bool(const double, const double)> fuzzyEqComp =
0116     [](const double& lhs, const double& rhs) { return fuzzyEquals(lhs, rhs); };
0117 
0118 
0119   /// @brief Compare two numbers for >= with a degree of fuzziness
0120   ///
0121   /// The @a tolerance parameter on the equality test is as for @c fuzzyEquals.
0122   template <typename N1, typename N2>
0123   inline typename std::enable_if_t<
0124     std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2>, bool>
0125   fuzzyGtrEquals(N1 a, N2 b, double tolerance=1e-5) {
0126     return a > b || fuzzyEquals(a, b, tolerance);
0127   }
0128 
0129   /// @brief Compare two floating point numbers for <= with a degree of fuzziness
0130   ///
0131   /// The @a tolerance parameter on the equality test is as for @c fuzzyEquals.
0132   template <typename N1, typename N2>
0133   inline typename std::enable_if_t<
0134     std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2>, bool>
0135   fuzzyLessEquals(N1 a, N2 b, double tolerance=1e-5) {
0136     return a < b || fuzzyEquals(a, b, tolerance);
0137   }
0138 
0139   /// Returns a number floored at the nth decimal place.
0140   inline double approx(double a, int n = 5) {
0141     double roundTo = pow(10.0,n);
0142     a *= roundTo;
0143     a = floor(a);
0144     return a/roundTo;
0145   }
0146   //@}
0147 
0148 
0149   /// @name Ranges and intervals
0150   //@{
0151 
0152   /// Represents whether an interval is open (non-inclusive) or closed (inclusive).
0153   ///
0154   /// For example, the interval \f$ [0, \pi) \f$ is closed (an inclusive
0155   /// boundary) at 0, and open (a non-inclusive boundary) at \f$ \pi \f$.
0156   enum RangeBoundary { OPEN=0, SOFT=0, CLOSED=1, HARD=1 };
0157 
0158 
0159   /// @brief Determine if @a value is in the range @a low to @a high, for floating point numbers
0160   ///
0161   /// Interval boundary types are defined by @a lowbound and @a highbound.
0162   /// @todo Optimise to one-line at compile time?
0163   template<typename NUM>
0164   inline bool inRange(NUM value, NUM low, NUM high,
0165                       RangeBoundary lowbound=CLOSED, RangeBoundary highbound=OPEN) {
0166     if (lowbound == OPEN && highbound == OPEN) {
0167       return (value > low && value < high);
0168     } else if (lowbound == OPEN && highbound == CLOSED) {
0169       return (value > low && value <= high);
0170     } else if (lowbound == CLOSED && highbound == OPEN) {
0171       return (value >= low && value < high);
0172     } else { // if (lowbound == CLOSED && highbound == CLOSED) {
0173       return (value >= low && value <= high);
0174     }
0175   }
0176 
0177   /// Alternative version of inRange for doubles, which accepts a pair for the range arguments.
0178   template<typename NUM>
0179   inline bool inRange(NUM value, std::pair<NUM, NUM> lowhigh,
0180                       RangeBoundary lowbound=CLOSED, RangeBoundary highbound=OPEN) {
0181     return inRange(value, lowhigh.first, lowhigh.second, lowbound, highbound);
0182   }
0183 
0184 
0185   /// @brief Determine if @a value is in the range @a low to @a high, for integer types
0186   ///
0187   /// Interval boundary types are defined by @a lowbound and @a highbound.
0188   /// @todo Optimise to one-line at compile time?
0189   inline bool inRange(int value, int low, int high,
0190                       RangeBoundary lowbound=CLOSED, RangeBoundary highbound=CLOSED) {
0191     if (lowbound == OPEN && highbound == OPEN) {
0192       return (value > low && value < high);
0193     } else if (lowbound == OPEN && highbound == CLOSED) {
0194       return (value > low && value <= high);
0195     } else if (lowbound == CLOSED && highbound == OPEN) {
0196       return (value >= low && value < high);
0197     } else { // if (lowbound == CLOSED && highbound == CLOSED) {
0198       return (value >= low && value <= high);
0199     }
0200   }
0201 
0202   /// Alternative version of @c inRange for ints, which accepts a pair for the range arguments.
0203   inline bool inRange(int value, std::pair<int, int> lowhigh,
0204                       RangeBoundary lowbound=CLOSED, RangeBoundary highbound=OPEN) {
0205     return inRange(value, lowhigh.first, lowhigh.second, lowbound, highbound);
0206   }
0207 
0208   //@}
0209 
0210 
0211   /// @name Miscellaneous numerical helpers
0212   //@{
0213 
0214   /// Named number-type squaring operation.
0215   template <typename NUM>
0216   inline NUM sqr(NUM a) {
0217     return a*a;
0218   }
0219 
0220   /// Named number-type addition in quadrature operation.
0221   template <typename Num>
0222   inline Num add_quad(Num a, Num b) {
0223     return sqrt(a*a + b*b);
0224   }
0225 
0226   /// Named number-type addition in quadrature operation.
0227   template <typename Num>
0228   inline Num add_quad(Num a, Num b, Num c) {
0229     return sqrt(a*a + b*b + c*c);
0230   }
0231 
0232   /// Find the sign of a number
0233   inline int sign(double val) {
0234     if (isZero(val)) return ZERO;
0235     const int valsign = (val > 0) ? PLUS : MINUS;
0236     return valsign;
0237   }
0238 
0239   /// Find the sign of a number
0240   inline int sign(int val) {
0241     if (val == 0) return ZERO;
0242     return (val > 0) ? PLUS : MINUS;
0243   }
0244 
0245   /// Find the sign of a number
0246   inline int sign(long val) {
0247     if (val == 0) return ZERO;
0248     return (val > 0) ? PLUS : MINUS;
0249   }
0250 
0251   //@}
0252 
0253 
0254   /// @name Binning helper functions
0255   //@{
0256 
0257   /// @brief Make a list of @a nbins + 1 values uniformly spaced between @a xmin and @a xmax inclusive.
0258   ///
0259   /// @note The arg ordering and the meaning of the nbins variable is "histogram-like",
0260   /// as opposed to the Numpy/Matlab version.
0261   inline std::vector<double> linspace(size_t nbins, double xmin, double xmax, bool include_end=true) {
0262     if (xmax < xmin)  throw RangeError("xmax should not be smaller than xmin!");
0263     if (nbins == 0)   throw RangeError("Requested number of bins is 0!");
0264     std::vector<double> rtn;
0265     const double interval = (xmax-xmin)/static_cast<double>(nbins);
0266     for (size_t i = 0; i < nbins; ++i) {
0267       rtn.push_back(xmin + i*interval);
0268     }
0269     assert(rtn.size() == nbins);
0270     if (include_end) rtn.push_back(xmax); // exact xmax, not result of n * interval
0271     return rtn;
0272   }
0273 
0274 
0275   /// @brief Make a list of @a nbins + 1 values uniformly spaced in log(x) between @a xmin and @a xmax inclusive.
0276   ///
0277   /// @note The arg ordering and the meaning of the nbins variable is "histogram-like",
0278   /// as opposed to the Numpy/Matlab version, and the xmin and xmax arguments are expressed
0279   /// in "normal" space, rather than as the logarithms of the xmin/xmax values as in Numpy/Matlab.
0280   inline std::vector<double> logspace(size_t nbins, double xmin, double xmax, bool include_end=true) {
0281     if (xmax < xmin)  throw RangeError("xmax should not be smaller than xmin!");
0282     if (xmin < 0)     throw RangeError("xmin should not be negative!");
0283     if (nbins == 0)   throw RangeError("Requested number of bins is 0!");
0284     const double logxmin = std::log(xmin);
0285     const double logxmax = std::log(xmax);
0286     const std::vector<double> logvals = linspace(nbins, logxmin, logxmax);
0287     assert(logvals.size() == nbins+1);
0288     std::vector<double> rtn; rtn.reserve(logvals.size());
0289     rtn.push_back(xmin);
0290     for (size_t i = 1; i < logvals.size()-1; ++i) {
0291       rtn.push_back(std::exp(logvals[i]));
0292     }
0293     assert(rtn.size() == nbins);
0294     if (include_end) rtn.push_back(xmax);
0295     return rtn;
0296   }
0297 
0298 
0299   /// @todo fspace() for uniform sampling from f(x); requires ability to invert fn... how, in general?
0300   //inline std::vector<double> fspace(size_t nbins, double xmin, double xmax, std::function<double(double)>& fn) {
0301 
0302 
0303   /// @brief Make a list of @a nbins + 1 values spaced with *density* ~ f(x) between @a xmin and @a end inclusive.
0304   ///
0305   /// The density function @a fn will be evaluated at @a nsample uniformly
0306   /// distributed points between @a xmin and @a xmax, its integral approximated
0307   /// via the Trapezium Rule and used to normalize the distribution, and @a
0308   /// nbins + 1 edges then selected to (approximately) divide into bins each
0309   /// containing fraction 1/@a nbins of the integral.
0310   ///
0311   /// @note The function @a fn does not need to be a normalized pdf, but it should be non-negative.
0312   /// Any negative values returned by @a fn(x) will be truncated to zero and contribute nothing to
0313   /// the estimated integral and binning density.
0314   ///
0315   /// @note The arg ordering and the meaning of the nbins variable is "histogram-like",
0316   /// as opposed to the Numpy/Matlab version, and the xmin and xmax arguments are expressed
0317   /// in "normal" space, rather than in the function-mapped space as with Numpy/Matlab.
0318   ///
0319   /// @note The naming of this function differs from the other, Matlab-inspired ones: the bin spacing is
0320   /// uniform in the CDF of the density function given, rather than in the function itself. For
0321   /// most use-cases this is more intuitive.
0322   inline std::vector<double> pdfspace(size_t nbins, double xmin, double xmax, std::function<double(double)>& fn, size_t nsample=10000) {
0323     const double dx = (xmax-xmin)/(double)nsample;
0324     const std::vector<double> xs = linspace(nsample, xmin, xmax);
0325     std::vector<double> ys(0, nsample);
0326     auto posfn = [&](double x){return std::max(fn(x), 0.0);};
0327     std::transform(xs.begin(), xs.end(), ys.begin(), posfn);
0328     std::vector<double> areas; areas.reserve(nsample);
0329     double areasum = 0;
0330     for (size_t i = 0; i < ys.size()-1; ++i) {
0331       const double area = (ys[i] + ys[i+1])*dx/2.0;
0332       areas[i] = area;
0333       areasum += area;
0334     }
0335     const double df = areasum/(double)nbins;
0336     std::vector<double> xedges{xmin}; xedges.reserve(nbins+1);
0337     double fsum = 0;
0338     for (size_t i = 0; i < nsample-1; ++i) {
0339       fsum += areas[i];
0340       if (fsum > df) {
0341         fsum = 0;
0342         xedges.push_back(xs[i+1]);
0343       }
0344     }
0345     xedges.push_back(xmax);
0346     assert(xedges.size() == nbins+1);
0347     return xedges;
0348   }
0349 
0350 
0351   /// @brief Return the bin index of the given value, @a val, given a vector of bin edges
0352   ///
0353   /// NB. The @a binedges vector must be sorted
0354   template <typename NUM>
0355   inline int index_between(const NUM& val, const std::vector<NUM>& binedges) {
0356     if (!inRange(val, binedges.front(), binedges.back())) return -1; //< Out of histo range
0357     int index = -1;
0358     for (size_t i = 1; i < binedges.size(); ++i) {
0359       if (val < binedges[i]) {
0360         index = i-1;
0361         break;
0362       }
0363     }
0364     assert(inRange(index, -1, binedges.size()-1));
0365     return index;
0366   }
0367 
0368   //@}
0369 
0370 
0371   /// @name Statistics functions
0372   //@{
0373 
0374   /// @brief Calculate the effective number of entries of a sample
0375   inline double effNumEntries(const double sumW, const double sumW2) {
0376     if (isZero(sumW2))  return 0;
0377     return sqr(sumW) / sumW2;
0378   }
0379 
0380   /// @brief Calculate the effective number of entries of a sample
0381   inline double effNumEntries(const std::vector<double>& weights) {
0382     double sumW = 0.0, sumW2 = 0.0;
0383     for (size_t i = 0; i < weights.size(); ++i) {
0384       sumW += weights[i];
0385       sumW2 += sqr(weights[i]);
0386     }
0387     return effNumEntries(sumW, sumW2);
0388   }
0389 
0390   /// @brief Calculate the mean of a sample
0391   inline double mean(const std::vector<int>& sample) {
0392     double mean = 0.0;
0393     for (size_t i=0; i<sample.size(); ++i) {
0394       mean += sample[i];
0395     }
0396     return mean/sample.size();
0397   }
0398 
0399   /// @brief Calculate the weighted mean of a sample
0400   inline double mean(const double sumWX, const double sumW) {
0401     return sumW? sumWX / sumW : std::numeric_limits<double>::quiet_NaN();
0402   }
0403 
0404   /// @brief Calculate the weighted mean of a sample
0405   inline double mean(const std::vector<double>& sample,
0406                      const std::vector<double>& weights) {
0407     if (sample.size() != weights.size())  throw RangeError("Inputs should have equal length!");
0408     double sumWX = 0., sumW = 0.;
0409     for (size_t i = 0; i < sample.size(); ++i) {
0410       sumW  += weights[i];
0411       sumWX += weights[i]*sample[i];
0412     }
0413     return mean(sumWX, sumW);
0414   }
0415 
0416   /// @brief Calculate the weighted variance of a sample
0417   ///
0418   /// Weighted variance defined as
0419   /// sig2 = ( sum(wx**2) * sum(w) - sum(wx)**2 ) / ( sum(w)**2 - sum(w**2) )
0420   /// see http://en.wikipedia.org/wiki/Weighted_mean
0421   inline double variance(const double sumWX, const double sumW,
0422                          const double sumWX2, const double sumW2) {
0423     const double num = sumWX2*sumW - sqr(sumWX);
0424     const double den = sqr(sumW) - sumW2;
0425     /// @todo Isn't this sensitive to the overall scale of the weights?
0426     /// Shouldn't it check if den is bigger then num by a set number of
0427     /// orders of magnitude and vice versa?
0428     // if (fabs(num) < 1e-10 && fabs(den) < 1e-10) {
0429     //   return std::numeric_limits<double>::quiet_NaN();
0430     // }
0431     /// We take the modulus of the weighted variance
0432     /// since the ratio can be negative with weighted means
0433     /// @todo Is this the correct approach? There is no information
0434     /// online other than "weights are non-negative"...
0435     return den? fabs(num/den): std::numeric_limits<double>::quiet_NaN();
0436   }
0437 
0438   /// @brief Calculate the weighted variance of a sample
0439   inline double variance(const std::vector<double>& sample,
0440                          const std::vector<double>& weights) {
0441     if (sample.size() != weights.size())  throw RangeError("Inputs should have equal length!");
0442     if (fuzzyLessEquals(effNumEntries(weights), 1.0)) {
0443        //throw LowStatsError("Requested variance of a distribution with only one effective entry");
0444        return std::numeric_limits<double>::quiet_NaN();
0445     }
0446     double sumWX = 0., sumW = 0.;
0447     double sumWX2 = 0., sumW2 = 0.;
0448     for (size_t i = 0; i < sample.size(); ++i) {
0449       sumW   += weights[i];
0450       sumWX  += weights[i]*sample[i];
0451       sumW2  += sqr(weights[i]);
0452       sumWX2 += weights[i]*sqr(sample[i]);
0453     }
0454     return variance(sumWX, sumW, sumWX2, sumW2);
0455   }
0456 
0457   /// @brief Calculate the weighted standard deviation of a sample
0458   inline double stdDev(const double sumWX, const double sumW,
0459                        const double sumWX2, const double sumW2) {
0460     return std::sqrt(variance(sumWX, sumW, sumWX2, sumW2));
0461   }
0462 
0463   /// @brief Calculate the weighted variance of a sample
0464   inline double stdDev(const std::vector<double>& sample,
0465                        const std::vector<double>& weights) {
0466     return std::sqrt(variance(sample, weights));
0467   }
0468 
0469   /// @brief Calculate the weighted standard error of a sample
0470   inline double stdErr(const double sumWX, const double sumW,
0471                        const double sumWX2, const double sumW2) {
0472     const double effN = effNumEntries(sumW, sumW2);
0473     if (effN == 0)  return std::numeric_limits<double>::quiet_NaN();
0474     const double var = variance(sumWX, sumW, sumWX2, sumW2);
0475     return std::sqrt(var / effN);
0476   }
0477 
0478   /// @brief Calculate the weighted variance of a sample
0479   inline double stdErr(const std::vector<double>& sample,
0480                         const std::vector<double>& weights) {
0481     if (sample.size() != weights.size())  throw RangeError("Inputs should have equal length!");
0482     const double effN = effNumEntries(weights);
0483     if (effN == 0)  return std::numeric_limits<double>::quiet_NaN();
0484     const double var = variance(sample, weights);
0485     return std::sqrt(var / effN);
0486   }
0487 
0488   /// @brief Calculate the weighted RMS of a sample
0489   inline double RMS(const double sumWX2, const double sumW, const double sumW2) {
0490     // Weighted RMS defined as
0491     // rms = sqrt(sum{w x^2} / sum{w})
0492     const double effN = effNumEntries(sumW, sumW2);
0493     if (effN == 0)  return std::numeric_limits<double>::quiet_NaN();
0494     const double meanSq = sumWX2 / sumW;
0495     return std::sqrt(meanSq);
0496   }
0497 
0498   /// @brief Calculate the weighted RMS of a sample
0499   inline double RMS(const std::vector<double>& sample,
0500                     const std::vector<double>& weights) {
0501     if (sample.size() != weights.size())  throw RangeError("Inputs should have equal length!");
0502     double sumWX2 = 0., sumW = 0., sumW2 = 0.;
0503     for (size_t i = 0; i < sample.size(); ++i) {
0504       sumW   += weights[i];
0505       sumW2  += sqr(weights[i]);
0506       sumWX2 += weights[i]*sqr(sample[i]);
0507     }
0508     return RMS(sumWX2, sumW, sumW2);
0509   }
0510 
0511   /// @brief Calculate the covariance (variance) between two samples
0512   inline double covariance(const std::vector<int>& sample1, const std::vector<int>& sample2) {
0513     const double mean1 = mean(sample1);
0514     const double mean2 = mean(sample2);
0515     const size_t N = sample1.size();
0516     double cov = 0.0;
0517     for (size_t i = 0; i < N; i++) {
0518       const double cov_i = (sample1[i] - mean1)*(sample2[i] - mean2);
0519       cov += cov_i;
0520     }
0521     if (N > 1) return cov/(N-1);
0522     else return 0.0;
0523   }
0524 
0525 
0526   /// @brief Calculate the correlation strength between two samples
0527   inline double correlation(const std::vector<int>& sample1, const std::vector<int>& sample2) {
0528     const double cov = covariance(sample1, sample2);
0529     const double var1 = covariance(sample1, sample1);
0530     const double var2 = covariance(sample2, sample2);
0531     const double correlation = cov/sqrt(var1*var2);
0532     const double corr_strength = correlation*sqrt(var2/var1);
0533     return corr_strength;
0534   }
0535 
0536 
0537   /// @brief Calculate the error-weighted chi2 statistic between two samples
0538   ///
0539   /// @note This calculation is rather naive as it neglects the error breakdowns that
0540   /// may be available for the curves being compared. More sophisticated comparisons
0541   /// will require a separate analysis based on the Estimate objects utilising the full
0542   /// covariance information.
0543   inline double naiveChi2(const std::vector<double>& sample1, const std::vector<double>& sample2,
0544                      const std::vector<double>& s1errors = std::vector<double>{},
0545                      const std::vector<double>& s2errors = std::vector<double>{}) {
0546     if (sample1.size() != sample2.size()) {
0547       throw RangeError("Inputs should have equal length!");
0548     }
0549     if (s1errors.size() && sample1.size() != s1errors.size()) {
0550       throw RangeError("Inputs should have equal length!");
0551     }
0552     if (s2errors.size() && sample2.size() != s2errors.size()) {
0553       throw RangeError("Inputs should have equal length!");
0554     }
0555     const size_t N = sample1.size();
0556     double chi2 = 0.0;
0557     for (size_t i = 0; i < N; ++i) {
0558       double temp = sqr(sample1[i] - sample2[i]);
0559       if (s1errors.size()) {
0560         temp /= sqr(s1errors[i]) + sqr(s2errors[i]);
0561       }
0562       chi2 += temp;
0563     }
0564     return chi2;
0565   }
0566 
0567   /// @brief Calculate the error-weighted reduced chi2 statistic between two samples
0568   ///
0569   /// @note This calculation is rather naive as it neglects the error breakdowns that
0570   /// may be available for the curves being compared. More sophisticated comparisons
0571   /// will require a separate analysis based on the Estimate objects utilising the full
0572   /// covariance information.
0573   inline double naiveChi2reduced(const std::vector<double>& sample1, const std::vector<double>& sample2,
0574                         const std::vector<double>& s1errors = std::vector<double>{},
0575                         const std::vector<double>& s2errors = std::vector<double>{}) {
0576     if (sample1.empty()) throw RangeError("Inputs should not have 0 length!");
0577     return naiveChi2(sample1, sample2, s1errors, s2errors)/sample1.size();
0578   }
0579 
0580   //@}
0581 
0582 
0583 }
0584 
0585 #endif