rivet is hosted by Hepforge, IPPP Durham
MathUtils.hh
Go to the documentation of this file.
00001 // -*- C++ -*-
00002 #ifndef RIVET_MathUtils_HH
00003 #define RIVET_MathUtils_HH
00004 
00005 #include "Rivet/Math/MathHeader.hh"
00006 #include "Rivet/Tools/RivetBoost.hh"
00007 #include <cassert>
00008 
00009 namespace Rivet {
00010 
00011 
00012   /// @name Comparison functions for safe (floating point) equality tests
00013   //@{
00014 
00015   /// @brief Compare a number to zero
00016   ///
00017   /// This version for floating point types has a degree of fuzziness expressed
00018   /// by the absolute @a tolerance parameter, for floating point safety.
00019   template <typename NUM>
00020   inline typename boost::enable_if_c<boost::is_floating_point<NUM>::value, bool>::type
00021   isZero(NUM val, double tolerance=1e-8) {
00022     return fabs(val) < tolerance;
00023   }
00024 
00025   /// @brief Compare a number to zero
00026   ///
00027   /// SFINAE template specialisation for integers, since there is no FP
00028   /// precision issue.
00029   template <typename NUM>
00030   inline typename boost::enable_if_c<boost::is_integral<NUM>::value, bool>::type
00031     isZero(NUM val, double UNUSED(tolerance)=1e-8) {
00032     return val == 0;
00033   }
00034 
00035 
00036   /// @brief Compare two numbers for equality with a degree of fuzziness
00037   ///
00038   /// This version for floating point types (if any argument is FP) has a degree
00039   /// of fuzziness expressed by the fractional @a tolerance parameter, for
00040   /// floating point safety.
00041   template <typename N1, typename N2>
00042   inline typename boost::enable_if_c<
00043     boost::is_arithmetic<N1>::value && boost::is_arithmetic<N2>::value &&
00044    (boost::is_floating_point<N1>::value || boost::is_floating_point<N2>::value), bool>::type
00045   fuzzyEquals(N1 a, N2 b, double tolerance=1e-5) {
00046     const double absavg = (fabs(a) + fabs(b))/2.0;
00047     const double absdiff = fabs(a - b);
00048     const bool rtn = (isZero(a) && isZero(b)) || absdiff < tolerance*absavg;
00049     return rtn;
00050   }
00051 
00052   /// @brief Compare two numbers for equality with a degree of fuzziness
00053   ///
00054   /// Simpler SFINAE template specialisation for integers, since there is no FP
00055   /// precision issue.
00056   template <typename N1, typename N2>
00057   inline typename boost::enable_if_c<
00058     boost::is_integral<N1>::value && boost::is_integral<N2>::value, bool>::type
00059   fuzzyEquals(N1 a, N2 b, double UNUSED(tolerance)=1e-5) {
00060     return a == b;
00061   }
00062 
00063 
00064   /// @brief Compare two numbers for >= with a degree of fuzziness
00065   ///
00066   /// The @a tolerance parameter on the equality test is as for @c fuzzyEquals.
00067   template <typename N1, typename N2>
00068   inline typename boost::enable_if_c<
00069     boost::is_arithmetic<N1>::value && boost::is_arithmetic<N2>::value, bool>::type
00070   fuzzyGtrEquals(N1 a, N2 b, double tolerance=1e-5) {
00071     return a > b || fuzzyEquals(a, b, tolerance);
00072   }
00073 
00074 
00075   /// @brief Compare two floating point numbers for <= with a degree of fuzziness
00076   ///
00077   /// The @a tolerance parameter on the equality test is as for @c fuzzyEquals.
00078   template <typename N1, typename N2>
00079   inline typename boost::enable_if_c<
00080     boost::is_arithmetic<N1>::value && boost::is_arithmetic<N2>::value, bool>::type
00081   fuzzyLessEquals(N1 a, N2 b, double tolerance=1e-5) {
00082     return a < b || fuzzyEquals(a, b, tolerance);
00083   }
00084 
00085   //@}
00086 
00087 
00088   /// @name Ranges and intervals
00089   //@{
00090 
00091   /// Represents whether an interval is open (non-inclusive) or closed (inclusive).
00092   ///
00093   /// For example, the interval \f$ [0, \pi) \f$ is closed (an inclusive
00094   /// boundary) at 0, and open (a non-inclusive boundary) at \f$ \pi \f$.
00095   enum RangeBoundary { OPEN=0, SOFT=0, CLOSED=1, HARD=1 };
00096 
00097 
00098   /// @brief Determine if @a value is in the range @a low to @a high, for floating point numbers
00099   ///
00100   /// Interval boundary types are defined by @a lowbound and @a highbound.
00101   template <typename N1, typename N2, typename N3>
00102   inline typename boost::enable_if_c<
00103     boost::is_arithmetic<N1>::value && boost::is_arithmetic<N2>::value && boost::is_arithmetic<N3>::value, bool>::type
00104   inRange(N1 value, N2 low, N3 high,
00105           RangeBoundary lowbound=CLOSED, RangeBoundary highbound=OPEN) {
00106     if (lowbound == OPEN && highbound == OPEN) {
00107       return (value > low && value < high);
00108     } else if (lowbound == OPEN && highbound == CLOSED) {
00109       return (value > low && fuzzyLessEquals(value, high));
00110     } else if (lowbound == CLOSED && highbound == OPEN) {
00111       return (fuzzyGtrEquals(value, low) && value < high);
00112     } else { // if (lowbound == CLOSED && highbound == CLOSED) {
00113       return (fuzzyGtrEquals(value, low) && fuzzyLessEquals(value, high));
00114     }
00115   }
00116 
00117   /// Alternative version of inRange which accepts a pair for the range arguments.
00118   template <typename N1, typename N2, typename N3>
00119   inline typename boost::enable_if_c<
00120     boost::is_arithmetic<N1>::value && boost::is_arithmetic<N2>::value && boost::is_arithmetic<N3>::value, bool>::type
00121   inRange(N1 value, pair<N2, N3> lowhigh,
00122           RangeBoundary lowbound=CLOSED, RangeBoundary highbound=OPEN) {
00123     return inRange(value, lowhigh.first, lowhigh.second, lowbound, highbound);
00124   }
00125 
00126   //@}
00127 
00128 
00129   /// @name Miscellaneous numerical helpers
00130   //@{
00131 
00132   /// Named number-type squaring operation.
00133   template <typename NUM>
00134   inline typename boost::enable_if_c<boost::is_arithmetic<NUM>::value, NUM>::type
00135   sqr(NUM a) {
00136     return a*a;
00137   }
00138 
00139   /// @brief Named number-type addition in quadrature operation.
00140   ///
00141   /// @note Result has the sqrt operation applied.
00142   /// @todo When std::common_type can be used, generalise to multiple numeric types with appropriate return type.
00143   // template <typename N1, typename N2>
00144   template <typename NUM>
00145   inline typename boost::enable_if_c<boost::is_arithmetic<NUM>::value, NUM>::type
00146   //std::common_type<N1, N2>::type
00147   add_quad(NUM a, NUM b) {
00148     return sqrt(a*a + b*b);
00149   }
00150 
00151   /// Named number-type addition in quadrature operation.
00152   ///
00153   /// @note Result has the sqrt operation applied.
00154   /// @todo When std::common_type can be used, generalise to multiple numeric types with appropriate return type.
00155   // template <typename N1, typename N2>
00156   template <typename NUM>
00157   inline typename boost::enable_if_c<boost::is_arithmetic<NUM>::value, NUM>::type
00158   //std::common_type<N1, N2, N3>::type
00159   add_quad(NUM a, NUM b, NUM c) {
00160     return sqrt(a*a + b*b + c*c);
00161   }
00162 
00163   /// Return a/b, or @a fail if b = 0
00164   /// @todo When std::common_type can be used, generalise to multiple numeric types with appropriate return type.
00165   inline double safediv(double num, double den, double fail=0.0) {
00166     return (!isZero(den)) ? num/den : fail;
00167   }
00168 
00169   /// A more efficient version of pow for raising numbers to integer powers.
00170   template <typename NUM>
00171   inline typename boost::enable_if_c<boost::is_arithmetic<NUM>::value, NUM>::type
00172   intpow(NUM val, unsigned int exp) {
00173     assert(exp >= 0);
00174     if (exp == 0) return (NUM) 1;
00175     else if (exp == 1) return val;
00176     return val * intpow(val, exp-1);
00177   }
00178 
00179   /// Find the sign of a number
00180   template <typename NUM>
00181   inline typename boost::enable_if_c<boost::is_arithmetic<NUM>::value, int>::type
00182   sign(NUM val) {
00183     if (isZero(val)) return ZERO;
00184     const int valsign = (val > 0) ? PLUS : MINUS;
00185     return valsign;
00186   }
00187 
00188   //@}
00189 
00190 
00191   /// @name Physics statistical distributions
00192   //@{
00193 
00194   /// @brief CDF for the Breit-Wigner distribution
00195   inline double cdfBW(double x, double mu, double gamma) {
00196     // normalize to (0;1) distribution
00197     const double xn = (x - mu)/gamma;
00198     return std::atan(xn)/M_PI + 0.5;
00199   }
00200 
00201   /// @brief Inverse CDF for the Breit-Wigner distribution
00202   inline double invcdfBW(double p, double mu, double gamma) {
00203     const double xn = std::tan(M_PI*(p-0.5));
00204     return gamma*xn + mu;
00205   }
00206 
00207   //@}
00208 
00209 
00210   /// @name Binning helper functions
00211   //@{
00212 
00213   /// @brief Make a list of @a nbins + 1 values equally spaced between @a start and @a end inclusive.
00214   ///
00215   /// NB. The arg ordering and the meaning of the nbins variable is "histogram-like",
00216   /// as opposed to the Numpy/Matlab version.
00217   inline vector<double> linspace(size_t nbins, double start, double end, bool include_end=true) {
00218     assert(end >= start);
00219     assert(nbins > 0);
00220     vector<double> rtn;
00221     const double interval = (end-start)/static_cast<double>(nbins);
00222     double edge = start;
00223     while (inRange(edge, start, end, CLOSED, CLOSED)) {
00224       rtn.push_back(edge);
00225       edge += interval;
00226     }
00227     assert(rtn.size() == nbins+1);
00228     if (!include_end) rtn.pop_back();
00229     return rtn;
00230   }
00231 
00232 
00233   /// @brief Make a list of @a nbins + 1 values exponentially spaced between @a start and @a end inclusive.
00234   ///
00235   /// NB. The arg ordering and the meaning of the nbins variable is "histogram-like",
00236   /// as opposed to the Numpy/Matlab version, and the start and end arguments are expressed
00237   /// in "normal" space, rather than as the logarithms of the start/end values as in Numpy/Matlab.
00238   inline vector<double> logspace(size_t nbins, double start, double end, bool include_end=true) {
00239     assert(end >= start);
00240     assert(start > 0);
00241     assert(nbins > 0);
00242     const double logstart = std::log(start);
00243     const double logend = std::log(end);
00244     const vector<double> logvals = linspace(nbins, logstart, logend);
00245     assert(logvals.size() == nbins+1);
00246     vector<double> rtn;
00247     foreach (double logval, logvals) {
00248       rtn.push_back(std::exp(logval));
00249     }
00250     assert(rtn.size() == nbins+1);
00251     if (!include_end) rtn.pop_back();
00252     return rtn;
00253   }
00254 
00255 
00256   /// @brief Make a list of @a nbins + 1 values spaced for equal area
00257   /// Breit-Wigner binning between @a start and @a end inclusive. @a
00258   /// mu and @a gamma are the Breit-Wigner parameters.
00259   ///
00260   /// @note The arg ordering and the meaning of the nbins variable is "histogram-like",
00261   /// as opposed to the Numpy/Matlab version, and the start and end arguments are expressed
00262   /// in "normal" space.
00263   inline vector<double> bwspace(size_t nbins, double start, double end, double mu, double gamma) {
00264     assert(end >= start);
00265     assert(nbins > 0);
00266     const double pmin = cdfBW(start, mu, gamma);
00267     const double pmax = cdfBW(end,   mu, gamma);
00268     const vector<double> edges = linspace(nbins, pmin, pmax);
00269     assert(edges.size() == nbins+1);
00270     vector<double> rtn;
00271     foreach (double edge, edges) {
00272       rtn.push_back(invcdfBW(edge, mu, gamma));
00273     }
00274     assert(rtn.size() == nbins+1);
00275     return rtn;
00276   }
00277 
00278 
00279   /// @brief Return the bin index of the given value, @a val, given a vector of bin edges
00280   ///
00281   /// NB. The @a binedges vector must be sorted
00282   template <typename NUM1, typename NUM2>
00283   inline typename boost::enable_if_c<boost::is_arithmetic<NUM1>::value && boost::is_floating_point<NUM2>::value, int>::type
00284   binIndex(NUM1 val, const vector<NUM2>& binedges) {
00285     /// @todo Use std::common_type<NUM1, NUM2>::type x = val; ?
00286     /// @todo Add linear & log guesses, and binary split via upper_bound for large binnings
00287     if (!inRange(val, binedges.front(), binedges.back())) return -1; //< Out of histo range
00288     int index = -1;
00289     for (size_t i = 1; i < binedges.size(); ++i) {
00290       if (val < binedges[i]) {
00291         index = i-1;
00292         break;
00293       }
00294     }
00295     assert(inRange(index, -1, binedges.size()-1));
00296     return index;
00297   }
00298 
00299   //@}
00300 
00301 
00302   /// @name Discrete statistics functions
00303   //@{
00304 
00305   /// Calculate the mean of a sample
00306   inline double mean(const vector<int>& sample) {
00307     double mean = 0.0;
00308     for (size_t i = 0; i < sample.size(); ++i) {
00309       mean += sample[i];
00310     }
00311     return mean/sample.size();
00312   }
00313 
00314   // Calculate the error on the mean, assuming poissonian errors
00315   inline double mean_err(const vector<int>& sample) {
00316     double mean_e = 0.0;
00317     for (size_t i = 0; i < sample.size(); ++i) {
00318       mean_e += sqrt(sample[i]);
00319     }
00320     return mean_e/sample.size();
00321   }
00322 
00323   /// Calculate the covariance (variance) between two samples
00324   inline double covariance(const vector<int>& sample1, const vector<int>& sample2) {
00325     const double mean1 = mean(sample1);
00326     const double mean2 = mean(sample2);
00327     const size_t N = sample1.size();
00328     double cov = 0.0;
00329     for (size_t i = 0; i < N; i++) {
00330       const double cov_i = (sample1[i] - mean1)*(sample2[i] - mean2);
00331       cov += cov_i;
00332     }
00333     if (N > 1) return cov/(N-1);
00334     else return 0.0;
00335   }
00336 
00337   /// Calculate the error on the covariance (variance) of two samples, assuming poissonian errors
00338   inline double covariance_err(const vector<int>& sample1, const vector<int>& sample2) {
00339     const double mean1 = mean(sample1);
00340     const double mean2 = mean(sample2);
00341     const double mean1_e = mean_err(sample1);
00342     const double mean2_e = mean_err(sample2);
00343     const size_t N = sample1.size();
00344     double cov_e = 0.0;
00345     for (size_t i = 0; i < N; i++) {
00346       const double cov_i = (sqrt(sample1[i]) - mean1_e)*(sample2[i] - mean2) +
00347         (sample1[i] - mean1)*(sqrt(sample2[i]) - mean2_e);
00348       cov_e += cov_i;
00349     }
00350     if (N > 1) return cov_e/(N-1);
00351     else return 0.0;
00352   }
00353 
00354 
00355   /// Calculate the correlation strength between two samples
00356   inline double correlation(const vector<int>& sample1, const vector<int>& sample2) {
00357     const double cov = covariance(sample1, sample2);
00358     const double var1 = covariance(sample1, sample1);
00359     const double var2 = covariance(sample2, sample2);
00360     const double correlation = cov/sqrt(var1*var2);
00361     const double corr_strength = correlation*sqrt(var2/var1);
00362     return corr_strength;
00363   }
00364 
00365   /// Calculate the error of the correlation strength between two samples assuming Poissonian errors
00366   inline double correlation_err(const vector<int>& sample1, const vector<int>& sample2) {
00367     const double cov = covariance(sample1, sample2);
00368     const double var1 = covariance(sample1, sample1);
00369     const double var2 = covariance(sample2, sample2);
00370     const double cov_e = covariance_err(sample1, sample2);
00371     const double var1_e = covariance_err(sample1, sample1);
00372     const double var2_e = covariance_err(sample2, sample2);
00373 
00374     // Calculate the correlation
00375     const double correlation = cov/sqrt(var1*var2);
00376     // Calculate the error on the correlation
00377     const double correlation_err = cov_e/sqrt(var1*var2) -
00378       cov/(2*pow(3./2., var1*var2)) * (var1_e * var2 + var1 * var2_e);
00379 
00380 
00381     // Calculate the error on the correlation strength
00382     const double corr_strength_err = correlation_err*sqrt(var2/var1) +
00383       correlation/(2*sqrt(var2/var1)) * (var2_e/var1 - var2*var1_e/pow(2, var2));
00384 
00385     return corr_strength_err;
00386   }
00387 
00388   //@}
00389 
00390 
00391   /// @name Angle range mappings
00392   //@{
00393 
00394   /// @brief Reduce any number to the range [-2PI, 2PI]
00395   ///
00396   /// Achieved by repeated addition or subtraction of 2PI as required. Used to
00397   /// normalise angular measures.
00398   inline double _mapAngleM2PITo2Pi(double angle) {
00399     double rtn = fmod(angle, TWOPI);
00400     if (isZero(rtn)) return 0;
00401     assert(rtn >= -TWOPI && rtn <= TWOPI);
00402     return rtn;
00403   }
00404 
00405   /// Map an angle into the range (-PI, PI].
00406   inline double mapAngleMPiToPi(double angle) {
00407     double rtn = _mapAngleM2PITo2Pi(angle);
00408     if (isZero(rtn)) return 0;
00409     rtn = (rtn >   PI ? rtn-TWOPI :
00410            rtn <= -PI ? rtn+TWOPI : rtn);
00411     assert(rtn > -PI && rtn <= PI);
00412     return rtn;
00413   }
00414 
00415   /// Map an angle into the range [0, 2PI).
00416   inline double mapAngle0To2Pi(double angle) {
00417     double rtn = _mapAngleM2PITo2Pi(angle);
00418     if (isZero(rtn)) return 0;
00419     if (rtn < 0) rtn += TWOPI;
00420     if (rtn == TWOPI) rtn = 0;
00421     assert(rtn >= 0 && rtn < TWOPI);
00422     return rtn;
00423   }
00424 
00425   /// Map an angle into the range [0, PI].
00426   inline double mapAngle0ToPi(double angle) {
00427     double rtn = fabs(mapAngleMPiToPi(angle));
00428     if (isZero(rtn)) return 0;
00429     assert(rtn > 0 && rtn <= PI);
00430     return rtn;
00431   }
00432 
00433   /// Map an angle into the enum-specified range.
00434   inline double mapAngle(double angle, PhiMapping mapping) {
00435     switch (mapping) {
00436     case MINUSPI_PLUSPI:
00437       return mapAngleMPiToPi(angle);
00438     case ZERO_2PI:
00439       return mapAngle0To2Pi(angle);
00440     case ZERO_PI:
00441       return mapAngle0To2Pi(angle);
00442     default:
00443       throw Rivet::UserError("The specified phi mapping scheme is not implemented");
00444     }
00445   }
00446 
00447   //@}
00448 
00449 
00450   /// @name Phase space measure helpers
00451   //@{
00452 
00453   /// @brief Calculate the difference between two angles in radians
00454   ///
00455   /// Returns in the range [0, PI].
00456   inline double deltaPhi(double phi1, double phi2) {
00457     return mapAngle0ToPi(phi1 - phi2);
00458   }
00459 
00460   /// Calculate the abs difference between two pseudorapidities
00461   ///
00462   /// @note Just a cosmetic name for analysis code clarity.
00463   inline double deltaEta(double eta1, double eta2) {
00464     return fabs(eta1 - eta2);
00465   }
00466 
00467   /// Calculate the abs difference between two rapidities
00468   ///
00469   /// @note Just a cosmetic name for analysis code clarity.
00470   inline double deltaRap(double y1, double y2) {
00471     return fabs(y1 - y2);
00472   }
00473 
00474   /// Calculate the distance between two points in 2D rapidity-azimuthal
00475   /// ("\f$ \eta-\phi \f$") space. The phi values are given in radians.
00476   inline double deltaR(double rap1, double phi1, double rap2, double phi2) {
00477     const double dphi = deltaPhi(phi1, phi2);
00478     return sqrt( sqr(rap1-rap2) + sqr(dphi) );
00479   }
00480 
00481   /// Calculate a rapidity value from the supplied energy @a E and longitudinal momentum @a pz.
00482   inline double rapidity(double E, double pz) {
00483     if (isZero(E - pz)) {
00484       throw std::runtime_error("Divergent positive rapidity");
00485       return MAXDOUBLE;
00486     }
00487     if (isZero(E + pz)) {
00488       throw std::runtime_error("Divergent negative rapidity");
00489       return -MAXDOUBLE;
00490     }
00491     return 0.5*log((E+pz)/(E-pz));
00492   }
00493 
00494   //@}
00495 
00496 
00497 }
00498 
00499 
00500 #endif