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 Generated on Tue May 13 2014 11:38:28 for The Rivet MC analysis system by ![]() |