File indexing completed on 2025-04-19 09:06:46
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
0224
0225
0226
0227 template <typename NUM>
0228 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0229
0230 add_quad(NUM a, NUM b) {
0231 return sqrt(a*a + b*b);
0232 }
0233
0234
0235
0236
0237
0238
0239 template <typename NUM>
0240 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0241
0242 add_quad(NUM a, NUM b, NUM c) {
0243 return sqrt(a*a + b*b + c*c);
0244 }
0245
0246
0247
0248 inline double safediv(double num, double den, double fail=0.0) {
0249 return (!isZero(den)) ? num/den : fail;
0250 }
0251
0252
0253 template <typename NUM>
0254 constexpr inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0255 intpow(NUM val, unsigned int exp) {
0256 if (exp == 0) return (NUM) 1;
0257 else if (exp == 1) return val;
0258 return val * intpow(val, exp-1);
0259 }
0260
0261
0262 template <typename NUM>
0263 constexpr inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, int>
0264 sign(NUM val) {
0265 if (isZero(val)) return ZERO;
0266 const int valsign = (val > 0) ? PLUS : MINUS;
0267 return valsign;
0268 }
0269
0270
0271
0272
0273
0274
0275
0276
0277 inline double cdfBW(double x, double mu, double gamma) {
0278
0279 const double xn = (x - mu)/gamma;
0280 return std::atan(xn)/M_PI + 0.5;
0281 }
0282
0283
0284 inline double invcdfBW(double p, double mu, double gamma) {
0285 const double xn = std::tan(M_PI*(p-0.5));
0286 return gamma*xn + mu;
0287 }
0288
0289
0290
0291
0292
0293
0294
0295
0296
0297
0298
0299
0300
0301 inline vector<double> linspace(size_t nbins, double start, double end, bool include_end=true) {
0302 assert(nbins > 0);
0303 vector<double> rtn;
0304 const double interval = (end-start)/static_cast<double>(nbins);
0305 for (size_t i = 0; i < nbins; ++i) {
0306 rtn.push_back(start + i*interval);
0307 }
0308 assert(rtn.size() == nbins);
0309 if (include_end) rtn.push_back(end);
0310 return rtn;
0311 }
0312
0313
0314
0315
0316
0317
0318
0319
0320
0321
0322
0323
0324
0325 inline vector<double> aspace(double step, double start, double end, bool include_end=true, double tol=1e-2) {
0326 assert( (end-start)*step > 0);
0327 vector<double> rtn;
0328 double next = start;
0329 while (true) {
0330 if (next > end) break;
0331 rtn.push_back(next);
0332 next += step;
0333 }
0334 if (include_end) {
0335 if (end - rtn[rtn.size()-1] > tol*step) rtn.push_back(end);
0336 }
0337 return rtn;
0338 }
0339
0340
0341
0342
0343
0344 inline vector<double> fnspace(size_t nbins, double start, double end,
0345 const std::function<double(double)>& fn, const std::function<double(double)>& invfn,
0346 bool include_end=true) {
0347
0348 assert(nbins > 0);
0349 const double pmin = fn(start);
0350 const double pmax = fn(end);
0351 const vector<double> edges = linspace(nbins, pmin, pmax, false);
0352 assert(edges.size() == nbins);
0353 vector<double> rtn; rtn.reserve(nbins+1);
0354 rtn.push_back(start);
0355 for (size_t i = 1; i < edges.size(); ++i) {
0356 rtn.push_back(invfn(edges[i]));
0357 }
0358 assert(rtn.size() == nbins);
0359 if (include_end) rtn.push_back(end);
0360 return rtn;
0361 }
0362
0363
0364
0365
0366
0367
0368
0369
0370
0371
0372
0373 inline vector<double> logspace(size_t nbins, double start, double end, bool include_end=true) {
0374 return fnspace(nbins, start, end,
0375 [](double x){ return std::log(x); },
0376 [](double x){ return std::exp(x); },
0377 include_end);
0378 }
0379
0380
0381
0382
0383
0384
0385
0386
0387
0388
0389
0390 inline vector<double> powspace(size_t nbins, double start, double end, double npow, bool include_end=true) {
0391 assert(start >= 0);
0392 return fnspace(nbins, start, end,
0393 [&](double x){ return std::pow(x, npow); },
0394 [&](double x){ return std::pow(x, 1/npow); },
0395 include_end);
0396 }
0397
0398
0399
0400
0401
0402
0403
0404
0405
0406
0407
0408
0409 inline vector<double> powdbnspace(size_t nbins, double start, double end, double npow, bool include_end=true) {
0410 assert(start >= 0);
0411 return fnspace(nbins, start, end,
0412 [&](double x){ return std::pow(x, npow+1) / (npow+1); },
0413 [&](double x){ return std::pow((npow+1) * x, 1/(npow+1)); },
0414 include_end);
0415 }
0416
0417
0418
0419
0420
0421
0422
0423
0424
0425 inline vector<double> bwdbnspace(size_t nbins, double start, double end, double mu, double gamma, bool include_end=true) {
0426 return fnspace(nbins, start, end,
0427 [&](double x){ return cdfBW(x, mu, gamma); },
0428 [&](double x){ return invcdfBW(x, mu, gamma); },
0429 include_end);
0430 }
0431
0432
0433
0434 template <typename NUM, typename CONTAINER>
0435 inline typename std::enable_if_t<std::is_arithmetic_v<NUM> && std::is_arithmetic_v<typename CONTAINER::value_type>, int>
0436 _binIndex(NUM val, const CONTAINER& binedges, bool allow_overflow=false) {
0437 if (val < *begin(binedges)) return -1;
0438
0439 if (val >= *(end(binedges)-1)) return allow_overflow ? int(binedges.size())-1 : -1;
0440 auto it = std::upper_bound(begin(binedges), end(binedges), val);
0441 return std::distance(begin(binedges), --it);
0442 }
0443
0444
0445
0446
0447
0448
0449
0450
0451
0452 template <typename NUM1, typename NUM2>
0453 inline typename std::enable_if_t<std::is_arithmetic_v<NUM1> && std::is_arithmetic_v<NUM2>, int>
0454 binIndex(NUM1 val, std::initializer_list<NUM2> binedges, bool allow_overflow=false) {
0455 return _binIndex(val, binedges, allow_overflow);
0456 }
0457
0458
0459
0460
0461
0462
0463
0464
0465
0466 template <typename NUM, typename CONTAINER>
0467 inline typename std::enable_if_t<std::is_arithmetic_v<NUM> && std::is_arithmetic_v<typename CONTAINER::value_type>, int>
0468 binIndex(NUM val, const CONTAINER& binedges, bool allow_overflow=false) {
0469 return _binIndex(val, binedges, allow_overflow);
0470 }
0471
0472
0473
0474
0475
0476
0477
0478
0479
0480 template <typename NUM>
0481 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, NUM>
0482 median(const vector<NUM>& sample) {
0483 if (sample.empty()) throw RangeError("Can't compute median of an empty set");
0484 vector<NUM> tmp = sample;
0485 std::sort(tmp.begin(), tmp.end());
0486 const size_t imid = tmp.size()/2;
0487 if (sample.size() % 2 == 0) return (tmp.at(imid-1) + tmp.at(imid)) / 2.0;
0488 else return tmp.at(imid);
0489 }
0490
0491
0492
0493
0494 template <typename NUM>
0495 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0496 mean(const vector<NUM>& sample) {
0497 if (sample.empty()) throw RangeError("Can't compute mean of an empty set");
0498 double mean = 0.0;
0499 for (size_t i = 0; i < sample.size(); ++i) {
0500 mean += sample[i];
0501 }
0502 return mean/sample.size();
0503 }
0504
0505
0506
0507 template <typename NUM>
0508 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0509 mean_err(const vector<NUM>& sample) {
0510 if (sample.empty()) throw RangeError("Can't compute mean_err of an empty set");
0511 double mean_e = 0.0;
0512 for (size_t i = 0; i < sample.size(); ++i) {
0513 mean_e += sqrt(sample[i]);
0514 }
0515 return mean_e/sample.size();
0516 }
0517
0518
0519
0520
0521 template <typename NUM>
0522 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0523 covariance(const vector<NUM>& sample1, const vector<NUM>& sample2) {
0524 if (sample1.empty() || sample2.empty()) throw RangeError("Can't compute covariance of an empty set");
0525 if (sample1.size() != sample2.size()) throw RangeError("Sizes of samples must be equal for covariance calculation");
0526 const double mean1 = mean(sample1);
0527 const double mean2 = mean(sample2);
0528 const size_t N = sample1.size();
0529 double cov = 0.0;
0530 for (size_t i = 0; i < N; i++) {
0531 const double cov_i = (sample1[i] - mean1)*(sample2[i] - mean2);
0532 cov += cov_i;
0533 }
0534 if (N > 1) return cov/(N-1);
0535 else return 0.0;
0536 }
0537
0538
0539
0540 template <typename NUM>
0541 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0542 covariance_err(const vector<NUM>& sample1, const vector<NUM>& sample2) {
0543 if (sample1.empty() || sample2.empty()) throw RangeError("Can't compute covariance_err of an empty set");
0544 if (sample1.size() != sample2.size()) throw RangeError("Sizes of samples must be equal for covariance_err calculation");
0545 const double mean1 = mean(sample1);
0546 const double mean2 = mean(sample2);
0547 const double mean1_e = mean_err(sample1);
0548 const double mean2_e = mean_err(sample2);
0549 const size_t N = sample1.size();
0550 double cov_e = 0.0;
0551 for (size_t i = 0; i < N; i++) {
0552 const double cov_i = (sqrt(sample1[i]) - mean1_e)*(sample2[i] - mean2) +
0553 (sample1[i] - mean1)*(sqrt(sample2[i]) - mean2_e);
0554 cov_e += cov_i;
0555 }
0556 if (N > 1) return cov_e/(N-1);
0557 else return 0.0;
0558 }
0559
0560
0561
0562
0563 template <typename NUM>
0564 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0565 correlation(const vector<NUM>& sample1, const vector<NUM>& sample2) {
0566 const double cov = covariance(sample1, sample2);
0567 const double var1 = covariance(sample1, sample1);
0568 const double var2 = covariance(sample2, sample2);
0569 const double correlation = cov/sqrt(var1*var2);
0570 const double corr_strength = correlation*sqrt(var2/var1);
0571 return corr_strength;
0572 }
0573
0574
0575
0576 template <typename NUM>
0577 inline typename std::enable_if_t<std::is_arithmetic_v<NUM>, double>
0578 correlation_err(const vector<NUM>& sample1, const vector<NUM>& sample2) {
0579 const double cov = covariance(sample1, sample2);
0580 const double var1 = covariance(sample1, sample1);
0581 const double var2 = covariance(sample2, sample2);
0582 const double cov_e = covariance_err(sample1, sample2);
0583 const double var1_e = covariance_err(sample1, sample1);
0584 const double var2_e = covariance_err(sample2, sample2);
0585
0586
0587 const double correlation = cov/sqrt(var1*var2);
0588
0589 const double correlation_err = cov_e/sqrt(var1*var2) -
0590 cov/(2*pow(3./2., var1*var2)) * (var1_e * var2 + var1 * var2_e);
0591
0592
0593 const double corr_strength_err = correlation_err*sqrt(var2/var1) +
0594 correlation/(2*sqrt(var2/var1)) * (var2_e/var1 - var2*var1_e/pow(2, var2));
0595
0596 return corr_strength_err;
0597 }
0598
0599
0600
0601
0602
0603
0604
0605
0606
0607
0608
0609 inline double _mapAngleM2PITo2Pi(double angle) {
0610 double rtn = fmod(angle, TWOPI);
0611 if (isZero(rtn)) return 0;
0612 assert(rtn >= -TWOPI && rtn <= TWOPI);
0613 return rtn;
0614 }
0615
0616
0617 inline double mapAngleMPiToPi(double angle) {
0618 double rtn = _mapAngleM2PITo2Pi(angle);
0619 if (isZero(rtn)) return 0;
0620 if (rtn > PI) rtn -= TWOPI;
0621 if (rtn <= -PI) rtn += TWOPI;
0622 assert(rtn > -PI && rtn <= PI);
0623 return rtn;
0624 }
0625
0626
0627 inline double mapAngle0To2Pi(double angle) {
0628 double rtn = _mapAngleM2PITo2Pi(angle);
0629 if (isZero(rtn)) return 0;
0630 if (rtn < 0) rtn += TWOPI;
0631 if (rtn == TWOPI) rtn = 0;
0632 assert(rtn >= 0 && rtn < TWOPI);
0633 return rtn;
0634 }
0635
0636
0637 inline double mapAngle0ToPi(double angle) {
0638 double rtn = fabs(mapAngleMPiToPi(angle));
0639 if (isZero(rtn)) return 0;
0640 assert(rtn > 0 && rtn <= PI);
0641 return rtn;
0642 }
0643
0644
0645 inline double mapAngle(double angle, PhiMapping mapping) {
0646 switch (mapping) {
0647 case MINUSPI_PLUSPI:
0648 return mapAngleMPiToPi(angle);
0649 case ZERO_2PI:
0650 return mapAngle0To2Pi(angle);
0651 case ZERO_PI:
0652 return mapAngle0To2Pi(angle);
0653 default:
0654 throw Rivet::UserError("The specified phi mapping scheme is not implemented");
0655 }
0656 }
0657
0658
0659
0660
0661
0662
0663
0664
0665
0666
0667 inline double deltaPhi(double phi1, double phi2, bool sign=false) {
0668 const double x = mapAngleMPiToPi(phi1 - phi2);
0669 return sign ? x : fabs(x);
0670 }
0671
0672
0673
0674
0675 inline double deltaEta(double eta1, double eta2, bool sign=false) {
0676 const double x = eta1 - eta2;
0677 return sign ? x : fabs(x);
0678 }
0679
0680
0681
0682
0683 inline double deltaRap(double y1, double y2, bool sign=false) {
0684 const double x = y1 - y2;
0685 return sign? x : fabs(x);
0686 }
0687
0688
0689
0690 inline double deltaR2(double rap1, double phi1, double rap2, double phi2) {
0691 const double dphi = deltaPhi(phi1, phi2);
0692 return sqr(rap1-rap2) + sqr(dphi);
0693 }
0694
0695
0696
0697 inline double deltaR(double rap1, double phi1, double rap2, double phi2) {
0698 return sqrt(deltaR2(rap1, phi1, rap2, phi2));
0699 }
0700
0701
0702 inline double rapidity(double E, double pz) {
0703 if (isZero(E - pz)) {
0704 throw std::runtime_error("Divergent positive rapidity");
0705 return DBL_MAX;
0706 }
0707 if (isZero(E + pz)) {
0708 throw std::runtime_error("Divergent negative rapidity");
0709 return -DBL_MAX;
0710 }
0711 return 0.5*log((E+pz)/(E-pz));
0712 }
0713
0714
0715
0716
0717
0718
0719 inline double mT(double pT1, double pT2, double dphi) {
0720 return sqrt(2*pT1*pT2 * (1 - cos(dphi)) );
0721 }
0722
0723
0724 }
0725
0726
0727 #endif