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 Binning helper functions 00192 //@{ 00193 00194 /// @brief Make a list of @a nbins + 1 values equally spaced between @a start and @a end inclusive. 00195 /// 00196 /// NB. The arg ordering and the meaning of the nbins variable is "histogram-like", 00197 /// as opposed to the Numpy/Matlab version. 00198 inline vector<double> linspace(size_t nbins, double start, double end, bool include_end=true) { 00199 assert(end >= start); 00200 assert(nbins > 0); 00201 vector<double> rtn; 00202 const double interval = (end-start)/static_cast<double>(nbins); 00203 double edge = start; 00204 while (inRange(edge, start, end, CLOSED, CLOSED)) { 00205 rtn.push_back(edge); 00206 edge += interval; 00207 } 00208 assert(rtn.size() == nbins+1); 00209 if (!include_end) rtn.pop_back(); 00210 return rtn; 00211 } 00212 00213 00214 /// @brief Make a list of @a nbins + 1 values exponentially spaced between @a start and @a end inclusive. 00215 /// 00216 /// NB. The arg ordering and the meaning of the nbins variable is "histogram-like", 00217 /// as opposed to the Numpy/Matlab version, and the start and end arguments are expressed 00218 /// in "normal" space, rather than as the logarithms of the start/end values as in Numpy/Matlab. 00219 inline vector<double> logspace(size_t nbins, double start, double end, bool include_end=true) { 00220 assert(end >= start); 00221 assert(start > 0); 00222 assert(nbins > 0); 00223 const double logstart = std::log(start); 00224 const double logend = std::log(end); 00225 const vector<double> logvals = linspace(nbins, logstart, logend); 00226 assert(logvals.size() == nbins+1); 00227 vector<double> rtn; 00228 foreach (double logval, logvals) { 00229 rtn.push_back(std::exp(logval)); 00230 } 00231 assert(rtn.size() == nbins+1); 00232 if (!include_end) rtn.pop_back(); 00233 return rtn; 00234 } 00235 00236 00237 namespace BWHelpers { 00238 00239 /// @brief CDF for the Breit-Wigner distribution 00240 inline double CDF(double x, double mu, double gamma) { 00241 // normalize to (0;1) distribution 00242 const double xn = (x - mu)/gamma; 00243 return std::atan(xn)/M_PI + 0.5; 00244 } 00245 00246 /// @brief inverse CDF for the Breit-Wigner distribution 00247 inline double antiCDF(double p, double mu, double gamma) { 00248 const double xn = std::tan(M_PI*(p-0.5)); 00249 return gamma*xn + mu; 00250 } 00251 00252 } 00253 00254 /// @brief Make a list of @a nbins + 1 values spaced for equal area 00255 /// Breit-Wigner binning between @a start and @a end inclusive. @a 00256 /// mu and @a gamma are the Breit-Wigner parameters. 00257 /// 00258 /// NB. The arg ordering and the meaning of the nbins variable is "histogram-like", 00259 /// as opposed to the Numpy/Matlab version, and the start and end arguments are expressed 00260 /// in "normal" space. 00261 inline vector<double> BWspace(size_t nbins, double start, double end, double mu, double gamma) { 00262 assert(end >= start); 00263 assert(nbins > 0); 00264 const double pmin = BWHelpers::CDF(start,mu,gamma); 00265 const double pmax = BWHelpers::CDF(end, mu,gamma); 00266 const vector<double> edges = linspace(nbins, pmin, pmax); 00267 assert(edges.size() == nbins+1); 00268 vector<double> rtn; 00269 foreach (double edge, edges) { 00270 rtn.push_back(BWHelpers::antiCDF(edge,mu,gamma)); 00271 } 00272 assert(rtn.size() == nbins+1); 00273 return rtn; 00274 } 00275 00276 00277 /// @brief Return the bin index of the given value, @a val, given a vector of bin edges 00278 /// 00279 /// NB. The @a binedges vector must be sorted 00280 template <typename NUM> 00281 inline int index_between(const NUM& val, const vector<NUM>& binedges) { 00282 if (!inRange(val, binedges.front(), binedges.back())) return -1; //< Out of histo range 00283 int index = -1; 00284 for (size_t i = 1; i < binedges.size(); ++i) { 00285 if (val < binedges[i]) { 00286 index = i-1; 00287 break; 00288 } 00289 } 00290 assert(inRange(index, -1, binedges.size()-1)); 00291 return index; 00292 } 00293 00294 //@} 00295 00296 00297 /// @name Statistics functions 00298 //@{ 00299 00300 /// Calculate the mean of a sample 00301 inline double mean(const vector<int>& sample) { 00302 double mean = 0.0; 00303 for (size_t i=0; i<sample.size(); ++i) { 00304 mean += sample[i]; 00305 } 00306 return mean/sample.size(); 00307 } 00308 00309 // Calculate the error on the mean, assuming poissonian errors 00310 inline double mean_err(const vector<int>& sample) { 00311 double mean_e = 0.0; 00312 for (size_t i=0; i<sample.size(); ++i) { 00313 mean_e += sqrt(sample[i]); 00314 } 00315 return mean_e/sample.size(); 00316 } 00317 00318 /// Calculate the covariance (variance) between two samples 00319 inline double covariance(const vector<int>& sample1, const vector<int>& sample2) { 00320 const double mean1 = mean(sample1); 00321 const double mean2 = mean(sample2); 00322 const size_t N = sample1.size(); 00323 double cov = 0.0; 00324 for (size_t i = 0; i < N; i++) { 00325 const double cov_i = (sample1[i] - mean1)*(sample2[i] - mean2); 00326 cov += cov_i; 00327 } 00328 if (N > 1) return cov/(N-1); 00329 else return 0.0; 00330 } 00331 00332 /// Calculate the error on the covariance (variance) of two samples, assuming poissonian errors 00333 inline double covariance_err(const vector<int>& sample1, const vector<int>& sample2) { 00334 const double mean1 = mean(sample1); 00335 const double mean2 = mean(sample2); 00336 const double mean1_e = mean_err(sample1); 00337 const double mean2_e = mean_err(sample2); 00338 const size_t N = sample1.size(); 00339 double cov_e = 0.0; 00340 for (size_t i = 0; i < N; i++) { 00341 const double cov_i = (sqrt(sample1[i]) - mean1_e)*(sample2[i] - mean2) + 00342 (sample1[i] - mean1)*(sqrt(sample2[i]) - mean2_e); 00343 cov_e += cov_i; 00344 } 00345 if (N > 1) return cov_e/(N-1); 00346 else return 0.0; 00347 } 00348 00349 00350 /// Calculate the correlation strength between two samples 00351 inline double correlation(const vector<int>& sample1, const vector<int>& sample2) { 00352 const double cov = covariance(sample1, sample2); 00353 const double var1 = covariance(sample1, sample1); 00354 const double var2 = covariance(sample2, sample2); 00355 const double correlation = cov/sqrt(var1*var2); 00356 const double corr_strength = correlation*sqrt(var2/var1); 00357 return corr_strength; 00358 } 00359 00360 /// Calculate the error of the correlation strength between two samples assuming Poissonian errors 00361 inline double correlation_err(const vector<int>& sample1, const vector<int>& sample2) { 00362 const double cov = covariance(sample1, sample2); 00363 const double var1 = covariance(sample1, sample1); 00364 const double var2 = covariance(sample2, sample2); 00365 const double cov_e = covariance_err(sample1, sample2); 00366 const double var1_e = covariance_err(sample1, sample1); 00367 const double var2_e = covariance_err(sample2, sample2); 00368 00369 // Calculate the correlation 00370 const double correlation = cov/sqrt(var1*var2); 00371 // Calculate the error on the correlation 00372 const double correlation_err = cov_e/sqrt(var1*var2) - 00373 cov/(2*pow(3./2., var1*var2)) * (var1_e * var2 + var1 * var2_e); 00374 00375 00376 // Calculate the error on the correlation strength 00377 const double corr_strength_err = correlation_err*sqrt(var2/var1) + 00378 correlation/(2*sqrt(var2/var1)) * (var2_e/var1 - var2*var1_e/pow(2, var2)); 00379 00380 return corr_strength_err; 00381 } 00382 //@} 00383 00384 00385 /// @name Angle range mappings 00386 //@{ 00387 00388 /// @brief Reduce any number to the range [-2PI, 2PI] 00389 /// 00390 /// Achieved by repeated addition or subtraction of 2PI as required. Used to 00391 /// normalise angular measures. 00392 inline double _mapAngleM2PITo2Pi(double angle) { 00393 double rtn = fmod(angle, TWOPI); 00394 if (isZero(rtn)) return 0; 00395 assert(rtn >= -TWOPI && rtn <= TWOPI); 00396 return rtn; 00397 } 00398 00399 /// Map an angle into the range (-PI, PI]. 00400 inline double mapAngleMPiToPi(double angle) { 00401 double rtn = _mapAngleM2PITo2Pi(angle); 00402 if (isZero(rtn)) return 0; 00403 rtn = (rtn > PI ? rtn-TWOPI : 00404 rtn <= -PI ? rtn+TWOPI : rtn); 00405 assert(rtn > -PI && rtn <= PI); 00406 return rtn; 00407 } 00408 00409 /// Map an angle into the range [0, 2PI). 00410 inline double mapAngle0To2Pi(double angle) { 00411 double rtn = _mapAngleM2PITo2Pi(angle); 00412 if (isZero(rtn)) return 0; 00413 if (rtn < 0) rtn += TWOPI; 00414 if (rtn == TWOPI) rtn = 0; 00415 assert(rtn >= 0 && rtn < TWOPI); 00416 return rtn; 00417 } 00418 00419 /// Map an angle into the range [0, PI]. 00420 inline double mapAngle0ToPi(double angle) { 00421 double rtn = fabs(mapAngleMPiToPi(angle)); 00422 if (isZero(rtn)) return 0; 00423 assert(rtn > 0 && rtn <= PI); 00424 return rtn; 00425 } 00426 00427 /// Map an angle into the enum-specified range. 00428 inline double mapAngle(double angle, PhiMapping mapping) { 00429 switch (mapping) { 00430 case MINUSPI_PLUSPI: 00431 return mapAngleMPiToPi(angle); 00432 case ZERO_2PI: 00433 return mapAngle0To2Pi(angle); 00434 case ZERO_PI: 00435 return mapAngle0To2Pi(angle); 00436 default: 00437 throw Rivet::UserError("The specified phi mapping scheme is not implemented"); 00438 } 00439 } 00440 00441 //@} 00442 00443 00444 /// @name Phase space measure helpers 00445 //@{ 00446 00447 /// @brief Calculate the difference between two angles in radians 00448 /// 00449 /// Returns in the range [0, PI]. 00450 inline double deltaPhi(double phi1, double phi2) { 00451 return mapAngle0ToPi(phi1 - phi2); 00452 } 00453 00454 /// Calculate the abs difference between two pseudorapidities 00455 /// 00456 /// @note Just a cosmetic name for analysis code clarity. 00457 inline double deltaEta(double eta1, double eta2) { 00458 return fabs(eta1 - eta2); 00459 } 00460 00461 /// Calculate the abs difference between two rapidities 00462 /// 00463 /// @note Just a cosmetic name for analysis code clarity. 00464 inline double deltaRap(double y1, double y2) { 00465 return fabs(y1 - y2); 00466 } 00467 00468 /// Calculate the distance between two points in 2D rapidity-azimuthal 00469 /// ("\f$ \eta-\phi \f$") space. The phi values are given in radians. 00470 inline double deltaR(double rap1, double phi1, double rap2, double phi2) { 00471 const double dphi = deltaPhi(phi1, phi2); 00472 return sqrt( sqr(rap1-rap2) + sqr(dphi) ); 00473 } 00474 00475 /// Calculate a rapidity value from the supplied energy @a E and longitudinal momentum @a pz. 00476 inline double rapidity(double E, double pz) { 00477 if (isZero(E - pz)) { 00478 throw std::runtime_error("Divergent positive rapidity"); 00479 return MAXDOUBLE; 00480 } 00481 if (isZero(E + pz)) { 00482 throw std::runtime_error("Divergent negative rapidity"); 00483 return -MAXDOUBLE; 00484 } 00485 return 0.5*log((E+pz)/(E-pz)); 00486 } 00487 00488 //@} 00489 00490 00491 } 00492 00493 00494 #endif Generated on Thu Feb 6 2014 17:38:45 for The Rivet MC analysis system by ![]() |