File indexing completed on 2026-09-22 09:05:48
0001
0002 #ifndef RIVET_MathUtils_HH
0003 #define RIVET_MathUtils_HH
0004
0005 #include "Rivet/Math/MathConstants.hh"
0006 #include <type_traits>
0007 #include <cassert>
0008
0009 namespace Rivet {
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022 template <typename NUM>
0023 inline typename std::enable_if_t<std::is_floating_point_v<NUM>, bool>
0024 isZero(NUM val, double tolerance=1e-8) {
0025 return fabs(val) < tolerance;
0026 }
0027
0028
0029
0030
0031
0032 template <typename NUM>
0033 inline typename std::enable_if_t<std::is_integral_v<NUM>, bool>
0034 isZero(NUM val, double=1e-5) {
0035 return val == 0;
0036 }
0037
0038
0039 template <typename NUM>
0040 inline typename std::enable_if_t<std::is_floating_point_v<NUM>, bool>
0041 isNaN(NUM val) { return std::isnan(val); }
0042
0043
0044 template <typename NUM>
0045 inline typename std::enable_if_t<std::is_floating_point_v<NUM>, bool>
0046 notNaN(NUM val) { return !std::isnan(val); }
0047
0048
0049 template <typename NUM>
0050 inline typename std::enable_if<std::is_floating_point<NUM>::value, NUM>::type
0051 sqrt_signed(NUM val) { return std::copysign(sqrt(std::abs(val)), val); }
0052
0053
0054
0055
0056
0057
0058 template <typename N1, typename N2>
0059 inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2> &&
0060 (std::is_floating_point_v<N1> || std::is_floating_point_v<N2>), bool>
0061 fuzzyEquals(N1 a, N2 b, double tolerance=1e-5) {
0062 const double absavg = (std::abs(a) + std::abs(b))/2.0;
0063 const double absdiff = std::abs(a - b);
0064 const bool rtn = (isZero(a) && isZero(b)) || absdiff < tolerance*absavg;
0065 return rtn;
0066 }
0067
0068
0069
0070
0071
0072 template <typename N1, typename N2>
0073 inline typename std::enable_if_t<std::is_integral_v<N1> && std::is_integral_v<N2>, bool>
0074 fuzzyEquals(N1 a, N2 b, double) {
0075 return a == b;
0076 }
0077
0078
0079
0080
0081
0082 template <typename N1, typename N2>
0083 inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2>, bool>
0084 fuzzyGtrEquals(N1 a, N2 b, double tolerance=1e-5) {
0085 return a > b || fuzzyEquals(a, b, tolerance);
0086 }
0087
0088
0089
0090
0091
0092 template <typename N1, typename N2>
0093 inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2>, bool>
0094 fuzzyLessEquals(N1 a, N2 b, double tolerance=1e-5) {
0095 return a < b || fuzzyEquals(a, b, tolerance);
0096 }
0097
0098
0099
0100
0101 template <typename N1, typename N2>
0102 inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2>,
0103 signed_if_mixed_t<N1,N2> >
0104 min(N1 a, N2 b) {
0105 using rtnT = signed_if_mixed_t<N1,N2>;
0106 return ((rtnT)a > (rtnT)b)? b : a;
0107 }
0108
0109
0110
0111
0112 template <typename N1, typename N2>
0113 inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2>,
0114 signed_if_mixed_t<N1,N2> >
0115 max(N1 a, N2 b) {
0116 using rtnT = signed_if_mixed_t<N1,N2>;
0117 return ((rtnT)a > (rtnT)b)? a : b;
0118 }
0119
0120
0121
0122
0123
0124
0125
0126
0127
0128
0129
0130 enum RangeBoundary { OPEN=0, SOFT=0, CLOSED=1, HARD=1 };
0131
0132
0133
0134
0135 template <typename N1, typename N2, typename N3>
0136 inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2> && std::is_arithmetic_v<N3>, bool>
0137 inRange(N1 value, N2 low, N3 high,
0138 RangeBoundary lowbound=CLOSED, RangeBoundary highbound=OPEN) {
0139 if (lowbound == OPEN && highbound == OPEN) {
0140 return (value > low && value < high);
0141 } else if (lowbound == OPEN && highbound == CLOSED) {
0142 return (value > low && value <= high);
0143 } else if (lowbound == CLOSED && highbound == OPEN) {
0144 return (value >= low && value < high);
0145 } else {
0146 return (value >= low && value <= high);
0147 }
0148 }
0149
0150
0151
0152
0153
0154 template <typename N1, typename N2, typename N3>
0155 inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2> && std::is_arithmetic_v<N3>, bool>
0156 fuzzyInRange(N1 value, N2 low, N3 high,
0157 RangeBoundary lowbound=CLOSED, RangeBoundary highbound=OPEN) {
0158 if (lowbound == OPEN && highbound == OPEN) {
0159 return (value > low && value < high);
0160 } else if (lowbound == OPEN && highbound == CLOSED) {
0161 return (value > low && fuzzyLessEquals(value, high));
0162 } else if (lowbound == CLOSED && highbound == OPEN) {
0163 return (fuzzyGtrEquals(value, low) && value < high);
0164 } else {
0165 return (fuzzyGtrEquals(value, low) && fuzzyLessEquals(value, high));
0166 }
0167 }
0168
0169
0170 template <typename N1, typename N2, typename N3>
0171 inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2> && std::is_arithmetic_v<N3>, bool>
0172 inRange(N1 value, pair<N2, N3> lowhigh,
0173 RangeBoundary lowbound=CLOSED, RangeBoundary highbound=OPEN) {
0174 return inRange(value, lowhigh.first, lowhigh.second, lowbound, highbound);
0175 }
0176
0177
0178
0179
0180
0181
0182
0183 template <typename N1, typename N2, typename N3>
0184 inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2> && std::is_arithmetic_v<N3>, bool>
0185 in_range(N1 val, N2 low, N3 high) {
0186 return inRange(val, low, high, CLOSED, OPEN);
0187 }
0188
0189
0190
0191
0192 template <typename N1, typename N2, typename N3>
0193 inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2> && std::is_arithmetic_v<N3>, bool>
0194 in_closed_range(N1 val, N2 low, N3 high) {
0195 return inRange(val, low, high, CLOSED, CLOSED);
0196 }
0197
0198
0199
0200
0201 template <typename N1, typename N2, typename N3>
0202 inline typename std::enable_if_t<std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2> && std::is_arithmetic_v<N3>, bool>
0203 in_open_range(N1 val, N2 low, N3 high) {
0204 return inRange(val, low, high, OPEN, OPEN);
0205 }
0206
0207
0208
0209
0210
0211
0212
0213
0214
0215
0216 template <typename NUM>
0217 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0218 sqr(NUM a) {
0219 return a*a;
0220 }
0221
0222
0223 inline double subtract(double a, double b, double tolerance = 1e-5) {
0224 if (fuzzyEquals(a,b,tolerance)) return 0.;
0225 return a - b;
0226 }
0227
0228
0229 inline double add(double a, double b, double tolerance = 1e-5) {
0230 return subtract(a,-b,tolerance);
0231 }
0232
0233
0234
0235
0236
0237
0238 template <typename NUM>
0239 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0240
0241 add_quad(NUM a, NUM b) {
0242 return sqrt(a*a + b*b);
0243 }
0244
0245
0246
0247
0248
0249
0250 template <typename NUM>
0251 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0252
0253 add_quad(NUM a, NUM b, NUM c) {
0254 return sqrt(a*a + b*b + c*c);
0255 }
0256
0257
0258
0259 inline double safediv(double num, double den, double fail=0.0) {
0260 return (!isZero(den)) ? num/den : fail;
0261 }
0262
0263
0264 template <typename NUM>
0265 constexpr inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0266 intpow(NUM val, unsigned int exp) {
0267 if (exp == 0) return (NUM) 1;
0268 else if (exp == 1) return val;
0269 return val * intpow(val, exp-1);
0270 }
0271
0272
0273 template <typename NUM>
0274 constexpr inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, int>
0275 sign(NUM val) {
0276 if (isZero(val)) return ZERO;
0277 const int valsign = (val > 0) ? PLUS : MINUS;
0278 return valsign;
0279 }
0280
0281
0282
0283
0284
0285
0286
0287
0288 inline double cdfBW(double x, double mu, double gamma) {
0289
0290 const double xn = (x - mu)/gamma;
0291 return std::atan(xn)/M_PI + 0.5;
0292 }
0293
0294
0295 inline double invcdfBW(double p, double mu, double gamma) {
0296 const double xn = std::tan(M_PI*(p-0.5));
0297 return gamma*xn + mu;
0298 }
0299
0300
0301
0302
0303
0304
0305
0306
0307
0308
0309
0310
0311
0312 inline vector<double> linspace(size_t nbins, double start, double end, bool include_end=true) {
0313 assert(nbins > 0);
0314 vector<double> rtn;
0315 const double interval = (end-start)/static_cast<double>(nbins);
0316 for (size_t i = 0; i < nbins; ++i) {
0317 rtn.push_back(start + i*interval);
0318 }
0319 assert(rtn.size() == nbins);
0320 if (include_end) rtn.push_back(end);
0321 return rtn;
0322 }
0323
0324
0325
0326
0327
0328
0329
0330
0331
0332
0333
0334
0335
0336 inline vector<double> aspace(double step, double start, double end, bool include_end=true, double tol=1e-2) {
0337 assert( (end-start)*step > 0);
0338 vector<double> rtn;
0339 double next = start;
0340 while (true) {
0341 if (next > end) break;
0342 rtn.push_back(next);
0343 next += step;
0344 }
0345 if (include_end) {
0346 if (end - rtn[rtn.size()-1] > tol*step) rtn.push_back(end);
0347 }
0348 return rtn;
0349 }
0350
0351
0352
0353
0354
0355 inline vector<double> fnspace(size_t nbins, double start, double end,
0356 const std::function<double(double)>& fn, const std::function<double(double)>& invfn,
0357 bool include_end=true) {
0358
0359 assert(nbins > 0);
0360 const double pmin = fn(start);
0361 const double pmax = fn(end);
0362 const vector<double> edges = linspace(nbins, pmin, pmax, false);
0363 assert(edges.size() == nbins);
0364 vector<double> rtn; rtn.reserve(nbins+1);
0365 rtn.push_back(start);
0366 for (size_t i = 1; i < edges.size(); ++i) {
0367 rtn.push_back(invfn(edges[i]));
0368 }
0369 assert(rtn.size() == nbins);
0370 if (include_end) rtn.push_back(end);
0371 return rtn;
0372 }
0373
0374
0375
0376
0377
0378
0379
0380
0381
0382
0383
0384 inline vector<double> logspace(size_t nbins, double start, double end, bool include_end=true) {
0385 return fnspace(nbins, start, end,
0386 [](double x){ return std::log(x); },
0387 [](double x){ return std::exp(x); },
0388 include_end);
0389 }
0390
0391
0392
0393
0394
0395
0396
0397
0398
0399
0400
0401 inline vector<double> powspace(size_t nbins, double start, double end, double npow, bool include_end=true) {
0402 assert(start >= 0);
0403 return fnspace(nbins, start, end,
0404 [&](double x){ return std::pow(x, npow); },
0405 [&](double x){ return std::pow(x, 1/npow); },
0406 include_end);
0407 }
0408
0409
0410
0411
0412
0413
0414
0415
0416
0417
0418
0419
0420 inline vector<double> powdbnspace(size_t nbins, double start, double end, double npow, bool include_end=true) {
0421 assert(start >= 0);
0422 return fnspace(nbins, start, end,
0423 [&](double x){ return std::pow(x, npow+1) / (npow+1); },
0424 [&](double x){ return std::pow((npow+1) * x, 1/(npow+1)); },
0425 include_end);
0426 }
0427
0428
0429
0430
0431
0432
0433
0434
0435
0436 inline vector<double> bwdbnspace(size_t nbins, double start, double end, double mu, double gamma, bool include_end=true) {
0437 return fnspace(nbins, start, end,
0438 [&](double x){ return cdfBW(x, mu, gamma); },
0439 [&](double x){ return invcdfBW(x, mu, gamma); },
0440 include_end);
0441 }
0442
0443
0444
0445 template <typename NUM, typename CONTAINER>
0446 inline typename std::enable_if_t<std::is_arithmetic_v<NUM> && std::is_arithmetic_v<typename CONTAINER::value_type>, int>
0447 _binIndex(NUM val, const CONTAINER& binedges, bool allow_overflow=false) {
0448 if (val < *begin(binedges)) return -1;
0449
0450 if (val >= *(end(binedges)-1)) return allow_overflow ? int(binedges.size())-1 : -1;
0451 auto it = std::upper_bound(begin(binedges), end(binedges), val);
0452 return std::distance(begin(binedges), --it);
0453 }
0454
0455
0456
0457
0458
0459
0460
0461
0462
0463 template <typename NUM1, typename NUM2>
0464 inline typename std::enable_if_t<std::is_arithmetic_v<NUM1> && std::is_arithmetic_v<NUM2>, int>
0465 binIndex(NUM1 val, std::initializer_list<NUM2> binedges, bool allow_overflow=false) {
0466 return _binIndex(val, binedges, allow_overflow);
0467 }
0468
0469
0470
0471
0472
0473
0474
0475
0476
0477 template <typename NUM, typename CONTAINER>
0478 inline typename std::enable_if_t<std::is_arithmetic_v<NUM> && std::is_arithmetic_v<typename CONTAINER::value_type>, int>
0479 binIndex(NUM val, const CONTAINER& binedges, bool allow_overflow=false) {
0480 return _binIndex(val, binedges, allow_overflow);
0481 }
0482
0483
0484
0485
0486
0487
0488
0489
0490
0491 template <typename NUM>
0492 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0493 median(const vector<NUM>& sample) {
0494 if (sample.empty()) throw RangeError("Can't compute median of an empty set");
0495 vector<NUM> tmp = sample;
0496 std::sort(tmp.begin(), tmp.end());
0497 const size_t imid = tmp.size()/2;
0498 if (sample.size() % 2 == 0) return (tmp.at(imid-1) + tmp.at(imid)) / 2.0;
0499 else return tmp.at(imid);
0500 }
0501
0502
0503
0504
0505 template <typename NUM>
0506 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0507 mean(const vector<NUM>& sample) {
0508 if (sample.empty()) throw RangeError("Can't compute mean of an empty set");
0509 double mean = 0.0;
0510 for (size_t i = 0; i < sample.size(); ++i) {
0511 mean += sample[i];
0512 }
0513 return mean/sample.size();
0514 }
0515
0516
0517
0518 template <typename NUM>
0519 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0520 mean_err(const vector<NUM>& sample) {
0521 if (sample.empty()) throw RangeError("Can't compute mean_err of an empty set");
0522 double mean_e = 0.0;
0523 for (size_t i = 0; i < sample.size(); ++i) {
0524 mean_e += sqrt(sample[i]);
0525 }
0526 return mean_e/sample.size();
0527 }
0528
0529
0530
0531
0532 template <typename NUM>
0533 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0534 covariance(const vector<NUM>& sample1, const vector<NUM>& sample2) {
0535 if (sample1.empty() || sample2.empty()) throw RangeError("Can't compute covariance of an empty set");
0536 if (sample1.size() != sample2.size()) throw RangeError("Sizes of samples must be equal for covariance calculation");
0537 const double mean1 = mean(sample1);
0538 const double mean2 = mean(sample2);
0539 const size_t N = sample1.size();
0540 double cov = 0.0;
0541 for (size_t i = 0; i < N; i++) {
0542 const double cov_i = (sample1[i] - mean1)*(sample2[i] - mean2);
0543 cov += cov_i;
0544 }
0545 if (N > 1) return cov/(N-1);
0546 else return 0.0;
0547 }
0548
0549
0550
0551 template <typename NUM>
0552 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0553 covariance_err(const vector<NUM>& sample1, const vector<NUM>& sample2) {
0554 if (sample1.empty() || sample2.empty()) throw RangeError("Can't compute covariance_err of an empty set");
0555 if (sample1.size() != sample2.size()) throw RangeError("Sizes of samples must be equal for covariance_err calculation");
0556 const double mean1 = mean(sample1);
0557 const double mean2 = mean(sample2);
0558 const double mean1_e = mean_err(sample1);
0559 const double mean2_e = mean_err(sample2);
0560 const size_t N = sample1.size();
0561 double cov_e = 0.0;
0562 for (size_t i = 0; i < N; i++) {
0563 const double cov_i = (sqrt(sample1[i]) - mean1_e)*(sample2[i] - mean2) +
0564 (sample1[i] - mean1)*(sqrt(sample2[i]) - mean2_e);
0565 cov_e += cov_i;
0566 }
0567 if (N > 1) return cov_e/(N-1);
0568 else return 0.0;
0569 }
0570
0571
0572
0573
0574 template <typename NUM>
0575 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0576 correlation(const vector<NUM>& sample1, const vector<NUM>& sample2) {
0577 const double cov = covariance(sample1, sample2);
0578 const double var1 = covariance(sample1, sample1);
0579 const double var2 = covariance(sample2, sample2);
0580 const double correlation = cov/sqrt(var1*var2);
0581 const double corr_strength = correlation*sqrt(var2/var1);
0582 return corr_strength;
0583 }
0584
0585
0586
0587 template <typename NUM>
0588 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0589 correlation_err(const vector<NUM>& sample1, const vector<NUM>& sample2) {
0590 const double cov = covariance(sample1, sample2);
0591 const double var1 = covariance(sample1, sample1);
0592 const double var2 = covariance(sample2, sample2);
0593 const double cov_e = covariance_err(sample1, sample2);
0594 const double var1_e = covariance_err(sample1, sample1);
0595 const double var2_e = covariance_err(sample2, sample2);
0596
0597
0598 const double correlation = cov/sqrt(var1*var2);
0599
0600 const double correlation_err = cov_e/sqrt(var1*var2) -
0601 cov/(2*pow(3./2., var1*var2)) * (var1_e * var2 + var1 * var2_e);
0602
0603
0604 const double corr_strength_err = correlation_err*sqrt(var2/var1) +
0605 correlation/(2*sqrt(var2/var1)) * (var2_e/var1 - var2*var1_e/pow(2, var2));
0606
0607 return corr_strength_err;
0608 }
0609
0610
0611
0612
0613
0614
0615
0616
0617
0618
0619
0620 inline double _mapAngleM2PITo2Pi(double angle) {
0621 double rtn = fmod(angle, TWOPI);
0622 if (isZero(rtn)) return 0;
0623 assert(rtn >= -TWOPI && rtn <= TWOPI);
0624 return rtn;
0625 }
0626
0627
0628 inline double mapAngleMPiToPi(double angle) {
0629 double rtn = _mapAngleM2PITo2Pi(angle);
0630 if (isZero(rtn)) return 0;
0631 if (rtn > PI) rtn -= TWOPI;
0632 if (rtn <= -PI) rtn += TWOPI;
0633 assert(rtn > -PI && rtn <= PI);
0634 return rtn;
0635 }
0636
0637
0638 inline double mapAngle0To2Pi(double angle) {
0639 double rtn = _mapAngleM2PITo2Pi(angle);
0640 if (isZero(rtn)) return 0;
0641 if (rtn < 0) rtn += TWOPI;
0642 if (rtn == TWOPI) rtn = 0;
0643 assert(rtn >= 0 && rtn < TWOPI);
0644 return rtn;
0645 }
0646
0647
0648 inline double mapAngle0ToPi(double angle) {
0649 double rtn = fabs(mapAngleMPiToPi(angle));
0650 if (isZero(rtn)) return 0;
0651 assert(rtn > 0 && rtn <= PI);
0652 return rtn;
0653 }
0654
0655
0656 inline double mapAngle(double angle, PhiMapping mapping) {
0657 switch (mapping) {
0658 case MINUSPI_PLUSPI:
0659 return mapAngleMPiToPi(angle);
0660 case ZERO_2PI:
0661 return mapAngle0To2Pi(angle);
0662 case ZERO_PI:
0663 return mapAngle0ToPi(angle);
0664 default:
0665 throw Rivet::UserError("The specified phi mapping scheme is not implemented");
0666 }
0667 }
0668
0669
0670
0671
0672
0673
0674
0675
0676
0677
0678 inline double deltaPhi(double phi1, double phi2, bool sign=false) {
0679 const double x = mapAngleMPiToPi(phi1 - phi2);
0680 return sign ? x : fabs(x);
0681 }
0682
0683
0684
0685
0686 inline double deltaEta(double eta1, double eta2, bool sign=false) {
0687 const double x = eta1 - eta2;
0688 return sign ? x : fabs(x);
0689 }
0690
0691
0692
0693
0694 inline double deltaRap(double y1, double y2, bool sign=false) {
0695 const double x = y1 - y2;
0696 return sign? x : fabs(x);
0697 }
0698
0699
0700
0701 inline double deltaR2(double rap1, double phi1, double rap2, double phi2) {
0702 const double dphi = deltaPhi(phi1, phi2);
0703 return sqr(rap1-rap2) + sqr(dphi);
0704 }
0705
0706
0707
0708 inline double deltaR(double rap1, double phi1, double rap2, double phi2) {
0709 return sqrt(deltaR2(rap1, phi1, rap2, phi2));
0710 }
0711
0712
0713 inline double rapidity(double E, double pz) {
0714 if (isZero(E - pz)) {
0715 throw std::runtime_error("Divergent positive rapidity");
0716 return DBL_MAX;
0717 }
0718 if (isZero(E + pz)) {
0719 throw std::runtime_error("Divergent negative rapidity");
0720 return -DBL_MAX;
0721 }
0722 return 0.5*log((E+pz)/(E-pz));
0723 }
0724
0725
0726
0727
0728
0729
0730
0731 inline double mT(double pT1, double pT2, double dphi) {
0732 return sqrt(2*pT1*pT2 * (1 - cos(dphi)) );
0733 }
0734
0735
0736 }
0737
0738
0739 #endif