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