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 Generated on Fri Oct 25 2013 12:41:46 for The Rivet MC analysis system by ![]() |