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 /// @brief Determine if @a value is in the range @a low to @a high, for floating point numbers 00098 /// 00099 /// Interval boundary types are defined by @a lowbound and @a highbound. 00100 template <typename N1, typename N2, typename N3> 00101 inline typename boost::enable_if_c< 00102 boost::is_arithmetic<N1>::value && boost::is_arithmetic<N2>::value && boost::is_arithmetic<N3>::value, bool>::type 00103 inRange(N1 value, N2 low, N3 high, 00104 RangeBoundary lowbound=CLOSED, RangeBoundary highbound=OPEN) { 00105 if (lowbound == OPEN && highbound == OPEN) { 00106 return (value > low && value < high); 00107 } else if (lowbound == OPEN && highbound == CLOSED) { 00108 return (value > low && fuzzyLessEquals(value, high)); 00109 } else if (lowbound == CLOSED && highbound == OPEN) { 00110 return (fuzzyGtrEquals(value, low) && value < high); 00111 } else { // if (lowbound == CLOSED && highbound == CLOSED) { 00112 return (fuzzyGtrEquals(value, low) && fuzzyLessEquals(value, high)); 00113 } 00114 } 00115 00116 /// Alternative version of inRange which accepts a pair for the range arguments. 00117 template <typename N1, typename N2, typename N3> 00118 inline typename boost::enable_if_c< 00119 boost::is_arithmetic<N1>::value && boost::is_arithmetic<N2>::value && boost::is_arithmetic<N3>::value, bool>::type 00120 inRange(N1 value, pair<N2, N3> lowhigh, 00121 RangeBoundary lowbound=CLOSED, RangeBoundary highbound=OPEN) { 00122 return inRange(value, lowhigh.first, lowhigh.second, lowbound, highbound); 00123 } 00124 00125 00126 // Alternative forms, with snake_case names and boundary types in names rather than as args -- from MCUtils 00127 00128 /// @brief Boolean function to determine if @a value is within the given range 00129 /// 00130 /// @note The interval is closed (inclusive) at the low end, and open (exclusive) at the high end. 00131 template <typename N1, typename N2, typename N3> 00132 inline typename boost::enable_if_c< 00133 boost::is_arithmetic<N1>::value && boost::is_arithmetic<N2>::value && boost::is_arithmetic<N3>::value, bool>::type 00134 in_range(N1 val, N2 low, N3 high) { 00135 return inRange(val, low, high, CLOSED, OPEN); 00136 } 00137 00138 /// @brief Boolean function to determine if @a value is within the given range 00139 /// 00140 /// @note The interval is closed at both ends. 00141 template <typename N1, typename N2, typename N3> 00142 inline typename boost::enable_if_c< 00143 boost::is_arithmetic<N1>::value && boost::is_arithmetic<N2>::value && boost::is_arithmetic<N3>::value, bool>::type 00144 in_closed_range(N1 val, N2 low, N3 high) { 00145 return inRange(val, low, high, CLOSED, CLOSED); 00146 } 00147 00148 /// @brief Boolean function to determine if @a value is within the given range 00149 /// 00150 /// @note The interval is open at both ends. 00151 template <typename N1, typename N2, typename N3> 00152 inline typename boost::enable_if_c< 00153 boost::is_arithmetic<N1>::value && boost::is_arithmetic<N2>::value && boost::is_arithmetic<N3>::value, bool>::type 00154 in_open_range(N1 val, N2 low, N3 high) { 00155 return inRange(val, low, high, OPEN, OPEN); 00156 } 00157 00158 /// @todo Add pair-based versions of the named range-boundary functions 00159 00160 //@} 00161 00162 00163 /// @name Miscellaneous numerical helpers 00164 //@{ 00165 00166 /// Named number-type squaring operation. 00167 template <typename NUM> 00168 inline typename boost::enable_if_c<boost::is_arithmetic<NUM>::value, NUM>::type 00169 sqr(NUM a) { 00170 return a*a; 00171 } 00172 00173 /// @brief Named number-type addition in quadrature operation. 00174 /// 00175 /// @note Result has the sqrt operation applied. 00176 /// @todo When std::common_type can be used, generalise to multiple numeric types with appropriate return type. 00177 // template <typename N1, typename N2> 00178 template <typename NUM> 00179 inline typename boost::enable_if_c<boost::is_arithmetic<NUM>::value, NUM>::type 00180 //std::common_type<N1, N2>::type 00181 add_quad(NUM a, NUM b) { 00182 return sqrt(a*a + b*b); 00183 } 00184 00185 /// Named number-type addition in quadrature operation. 00186 /// 00187 /// @note Result has the sqrt operation applied. 00188 /// @todo When std::common_type can be used, generalise to multiple numeric types with appropriate return type. 00189 // template <typename N1, typename N2> 00190 template <typename NUM> 00191 inline typename boost::enable_if_c<boost::is_arithmetic<NUM>::value, NUM>::type 00192 //std::common_type<N1, N2, N3>::type 00193 add_quad(NUM a, NUM b, NUM c) { 00194 return sqrt(a*a + b*b + c*c); 00195 } 00196 00197 /// Return a/b, or @a fail if b = 0 00198 /// @todo When std::common_type can be used, generalise to multiple numeric types with appropriate return type. 00199 inline double safediv(double num, double den, double fail=0.0) { 00200 return (!isZero(den)) ? num/den : fail; 00201 } 00202 00203 /// A more efficient version of pow for raising numbers to integer powers. 00204 template <typename NUM> 00205 inline typename boost::enable_if_c<boost::is_arithmetic<NUM>::value, NUM>::type 00206 intpow(NUM val, unsigned int exp) { 00207 assert(exp >= 0); 00208 if (exp == 0) return (NUM) 1; 00209 else if (exp == 1) return val; 00210 return val * intpow(val, exp-1); 00211 } 00212 00213 /// Find the sign of a number 00214 template <typename NUM> 00215 inline typename boost::enable_if_c<boost::is_arithmetic<NUM>::value, int>::type 00216 sign(NUM val) { 00217 if (isZero(val)) return ZERO; 00218 const int valsign = (val > 0) ? PLUS : MINUS; 00219 return valsign; 00220 } 00221 00222 //@} 00223 00224 00225 /// @name Physics statistical distributions 00226 //@{ 00227 00228 /// @brief CDF for the Breit-Wigner distribution 00229 inline double cdfBW(double x, double mu, double gamma) { 00230 // normalize to (0;1) distribution 00231 const double xn = (x - mu)/gamma; 00232 return std::atan(xn)/M_PI + 0.5; 00233 } 00234 00235 /// @brief Inverse CDF for the Breit-Wigner distribution 00236 inline double invcdfBW(double p, double mu, double gamma) { 00237 const double xn = std::tan(M_PI*(p-0.5)); 00238 return gamma*xn + mu; 00239 } 00240 00241 //@} 00242 00243 00244 /// @name Binning helper functions 00245 //@{ 00246 00247 /// @brief Make a list of @a nbins + 1 values equally spaced between @a start and @a end inclusive. 00248 /// 00249 /// NB. The arg ordering and the meaning of the nbins variable is "histogram-like", 00250 /// as opposed to the Numpy/Matlab version. 00251 inline vector<double> linspace(size_t nbins, double start, double end, bool include_end=true) { 00252 assert(end >= start); 00253 assert(nbins > 0); 00254 vector<double> rtn; 00255 const double interval = (end-start)/static_cast<double>(nbins); 00256 double edge = start; 00257 while (inRange(edge, start, end, CLOSED, CLOSED)) { 00258 rtn.push_back(edge); 00259 edge += interval; 00260 } 00261 assert(rtn.size() == nbins+1); 00262 if (!include_end) rtn.pop_back(); 00263 return rtn; 00264 } 00265 00266 00267 /// @brief Make a list of @a nbins + 1 values exponentially spaced between @a start and @a end inclusive. 00268 /// 00269 /// NB. The arg ordering and the meaning of the nbins variable is "histogram-like", 00270 /// as opposed to the Numpy/Matlab version, and the start and end arguments are expressed 00271 /// in "normal" space, rather than as the logarithms of the start/end values as in Numpy/Matlab. 00272 inline vector<double> logspace(size_t nbins, double start, double end, bool include_end=true) { 00273 assert(end >= start); 00274 assert(start > 0); 00275 assert(nbins > 0); 00276 const double logstart = std::log(start); 00277 const double logend = std::log(end); 00278 const vector<double> logvals = linspace(nbins, logstart, logend); 00279 assert(logvals.size() == nbins+1); 00280 vector<double> rtn; 00281 foreach (double logval, logvals) { 00282 rtn.push_back(std::exp(logval)); 00283 } 00284 assert(rtn.size() == nbins+1); 00285 if (!include_end) rtn.pop_back(); 00286 return rtn; 00287 } 00288 00289 00290 /// @brief Make a list of @a nbins + 1 values spaced for equal area 00291 /// Breit-Wigner binning between @a start and @a end inclusive. @a 00292 /// mu and @a gamma are the Breit-Wigner parameters. 00293 /// 00294 /// @note The arg ordering and the meaning of the nbins variable is "histogram-like", 00295 /// as opposed to the Numpy/Matlab version, and the start and end arguments are expressed 00296 /// in "normal" space. 00297 inline vector<double> bwspace(size_t nbins, double start, double end, double mu, double gamma) { 00298 assert(end >= start); 00299 assert(nbins > 0); 00300 const double pmin = cdfBW(start, mu, gamma); 00301 const double pmax = cdfBW(end, mu, gamma); 00302 const vector<double> edges = linspace(nbins, pmin, pmax); 00303 assert(edges.size() == nbins+1); 00304 vector<double> rtn; 00305 foreach (double edge, edges) { 00306 rtn.push_back(invcdfBW(edge, mu, gamma)); 00307 } 00308 assert(rtn.size() == nbins+1); 00309 return rtn; 00310 } 00311 00312 00313 /// @brief Return the bin index of the given value, @a val, given a vector of bin edges 00314 /// 00315 /// NB. The @a binedges vector must be sorted 00316 template <typename NUM1, typename NUM2> 00317 inline typename boost::enable_if_c<boost::is_arithmetic<NUM1>::value && boost::is_floating_point<NUM2>::value, int>::type 00318 binIndex(NUM1 val, const vector<NUM2>& binedges) { 00319 /// @todo Use std::common_type<NUM1, NUM2>::type x = val; ? 00320 /// @todo Add linear & log guesses, and binary split via upper_bound for large binnings 00321 if (!inRange(val, binedges.front(), binedges.back())) return -1; //< Out of histo range 00322 int index = -1; 00323 for (size_t i = 1; i < binedges.size(); ++i) { 00324 if (val < binedges[i]) { 00325 index = i-1; 00326 break; 00327 } 00328 } 00329 assert(inRange(index, -1, binedges.size()-1)); 00330 return index; 00331 } 00332 00333 //@} 00334 00335 00336 /// @name Discrete statistics functions 00337 //@{ 00338 00339 /// Calculate the mean of a sample 00340 inline double mean(const vector<int>& sample) { 00341 double mean = 0.0; 00342 for (size_t i = 0; i < sample.size(); ++i) { 00343 mean += sample[i]; 00344 } 00345 return mean/sample.size(); 00346 } 00347 00348 // Calculate the error on the mean, assuming poissonian errors 00349 inline double mean_err(const vector<int>& sample) { 00350 double mean_e = 0.0; 00351 for (size_t i = 0; i < sample.size(); ++i) { 00352 mean_e += sqrt(sample[i]); 00353 } 00354 return mean_e/sample.size(); 00355 } 00356 00357 /// Calculate the covariance (variance) between two samples 00358 inline double covariance(const vector<int>& sample1, const vector<int>& sample2) { 00359 const double mean1 = mean(sample1); 00360 const double mean2 = mean(sample2); 00361 const size_t N = sample1.size(); 00362 double cov = 0.0; 00363 for (size_t i = 0; i < N; i++) { 00364 const double cov_i = (sample1[i] - mean1)*(sample2[i] - mean2); 00365 cov += cov_i; 00366 } 00367 if (N > 1) return cov/(N-1); 00368 else return 0.0; 00369 } 00370 00371 /// Calculate the error on the covariance (variance) of two samples, assuming poissonian errors 00372 inline double covariance_err(const vector<int>& sample1, const vector<int>& sample2) { 00373 const double mean1 = mean(sample1); 00374 const double mean2 = mean(sample2); 00375 const double mean1_e = mean_err(sample1); 00376 const double mean2_e = mean_err(sample2); 00377 const size_t N = sample1.size(); 00378 double cov_e = 0.0; 00379 for (size_t i = 0; i < N; i++) { 00380 const double cov_i = (sqrt(sample1[i]) - mean1_e)*(sample2[i] - mean2) + 00381 (sample1[i] - mean1)*(sqrt(sample2[i]) - mean2_e); 00382 cov_e += cov_i; 00383 } 00384 if (N > 1) return cov_e/(N-1); 00385 else return 0.0; 00386 } 00387 00388 00389 /// Calculate the correlation strength between two samples 00390 inline double correlation(const vector<int>& sample1, const vector<int>& sample2) { 00391 const double cov = covariance(sample1, sample2); 00392 const double var1 = covariance(sample1, sample1); 00393 const double var2 = covariance(sample2, sample2); 00394 const double correlation = cov/sqrt(var1*var2); 00395 const double corr_strength = correlation*sqrt(var2/var1); 00396 return corr_strength; 00397 } 00398 00399 /// Calculate the error of the correlation strength between two samples assuming Poissonian errors 00400 inline double correlation_err(const vector<int>& sample1, const vector<int>& sample2) { 00401 const double cov = covariance(sample1, sample2); 00402 const double var1 = covariance(sample1, sample1); 00403 const double var2 = covariance(sample2, sample2); 00404 const double cov_e = covariance_err(sample1, sample2); 00405 const double var1_e = covariance_err(sample1, sample1); 00406 const double var2_e = covariance_err(sample2, sample2); 00407 00408 // Calculate the correlation 00409 const double correlation = cov/sqrt(var1*var2); 00410 // Calculate the error on the correlation 00411 const double correlation_err = cov_e/sqrt(var1*var2) - 00412 cov/(2*pow(3./2., var1*var2)) * (var1_e * var2 + var1 * var2_e); 00413 00414 00415 // Calculate the error on the correlation strength 00416 const double corr_strength_err = correlation_err*sqrt(var2/var1) + 00417 correlation/(2*sqrt(var2/var1)) * (var2_e/var1 - var2*var1_e/pow(2, var2)); 00418 00419 return corr_strength_err; 00420 } 00421 00422 //@} 00423 00424 00425 /// @name Angle range mappings 00426 //@{ 00427 00428 /// @brief Reduce any number to the range [-2PI, 2PI] 00429 /// 00430 /// Achieved by repeated addition or subtraction of 2PI as required. Used to 00431 /// normalise angular measures. 00432 inline double _mapAngleM2PITo2Pi(double angle) { 00433 double rtn = fmod(angle, TWOPI); 00434 if (isZero(rtn)) return 0; 00435 assert(rtn >= -TWOPI && rtn <= TWOPI); 00436 return rtn; 00437 } 00438 00439 /// Map an angle into the range (-PI, PI]. 00440 inline double mapAngleMPiToPi(double angle) { 00441 double rtn = _mapAngleM2PITo2Pi(angle); 00442 if (isZero(rtn)) return 0; 00443 rtn = (rtn > PI ? rtn-TWOPI : 00444 rtn <= -PI ? rtn+TWOPI : rtn); 00445 assert(rtn > -PI && rtn <= PI); 00446 return rtn; 00447 } 00448 00449 /// Map an angle into the range [0, 2PI). 00450 inline double mapAngle0To2Pi(double angle) { 00451 double rtn = _mapAngleM2PITo2Pi(angle); 00452 if (isZero(rtn)) return 0; 00453 if (rtn < 0) rtn += TWOPI; 00454 if (rtn == TWOPI) rtn = 0; 00455 assert(rtn >= 0 && rtn < TWOPI); 00456 return rtn; 00457 } 00458 00459 /// Map an angle into the range [0, PI]. 00460 inline double mapAngle0ToPi(double angle) { 00461 double rtn = fabs(mapAngleMPiToPi(angle)); 00462 if (isZero(rtn)) return 0; 00463 assert(rtn > 0 && rtn <= PI); 00464 return rtn; 00465 } 00466 00467 /// Map an angle into the enum-specified range. 00468 inline double mapAngle(double angle, PhiMapping mapping) { 00469 switch (mapping) { 00470 case MINUSPI_PLUSPI: 00471 return mapAngleMPiToPi(angle); 00472 case ZERO_2PI: 00473 return mapAngle0To2Pi(angle); 00474 case ZERO_PI: 00475 return mapAngle0To2Pi(angle); 00476 default: 00477 throw Rivet::UserError("The specified phi mapping scheme is not implemented"); 00478 } 00479 } 00480 00481 //@} 00482 00483 00484 /// @name Phase space measure helpers 00485 //@{ 00486 00487 /// @brief Calculate the difference between two angles in radians 00488 /// 00489 /// Returns in the range [0, PI]. 00490 inline double deltaPhi(double phi1, double phi2) { 00491 return mapAngle0ToPi(phi1 - phi2); 00492 } 00493 00494 /// Calculate the abs difference between two pseudorapidities 00495 /// 00496 /// @note Just a cosmetic name for analysis code clarity. 00497 inline double deltaEta(double eta1, double eta2) { 00498 return fabs(eta1 - eta2); 00499 } 00500 00501 /// Calculate the abs difference between two rapidities 00502 /// 00503 /// @note Just a cosmetic name for analysis code clarity. 00504 inline double deltaRap(double y1, double y2) { 00505 return fabs(y1 - y2); 00506 } 00507 00508 /// Calculate the distance between two points in 2D rapidity-azimuthal 00509 /// ("\f$ \eta-\phi \f$") space. The phi values are given in radians. 00510 inline double deltaR(double rap1, double phi1, double rap2, double phi2) { 00511 const double dphi = deltaPhi(phi1, phi2); 00512 return sqrt( sqr(rap1-rap2) + sqr(dphi) ); 00513 } 00514 00515 /// Calculate a rapidity value from the supplied energy @a E and longitudinal momentum @a pz. 00516 inline double rapidity(double E, double pz) { 00517 if (isZero(E - pz)) { 00518 throw std::runtime_error("Divergent positive rapidity"); 00519 return MAXDOUBLE; 00520 } 00521 if (isZero(E + pz)) { 00522 throw std::runtime_error("Divergent negative rapidity"); 00523 return -MAXDOUBLE; 00524 } 00525 return 0.5*log((E+pz)/(E-pz)); 00526 } 00527 00528 //@} 00529 00530 00531 } 00532 00533 00534 #endif Generated on Mon Jun 2 2014 15:23:01 for The Rivet MC analysis system by ![]() |