File indexing completed on 2025-10-31 09:20:42
0001
0002
0003
0004
0005
0006 #ifndef YODA_MathUtils_H
0007 #define YODA_MathUtils_H
0008
0009
0010
0011 #include "YODA/Exceptions.h"
0012 #include "YODA/Config/BuildConfig.h"
0013
0014 #include <algorithm>
0015 #include <functional>
0016 #include <numeric>
0017 #include <cassert>
0018 #include <cfloat>
0019 #include <climits>
0020 #include <cmath>
0021 #include <functional>
0022 #include <iostream>
0023 #include <limits>
0024 #include <map>
0025 #include <numeric>
0026 #include <ostream>
0027 #include <sstream>
0028 #include <stdexcept>
0029 #include <string>
0030 #include <utility>
0031 #include <vector>
0032
0033 namespace YODA {
0034
0035
0036
0037
0038 const static double MAXDOUBLE = DBL_MAX;
0039 const static double MAXINT = INT_MAX;
0040
0041
0042 static const double PI = M_PI;
0043
0044
0045 static const double TWOPI = 2*M_PI;
0046
0047
0048 static const double HALFPI = M_PI_2;
0049
0050
0051 enum Sign { MINUS = -1, ZERO = 0, PLUS = 1 };
0052
0053
0054
0055
0056
0057
0058
0059
0060
0061 template <typename NUM>
0062 inline typename std::enable_if_t<std::is_floating_point_v<NUM>, bool>
0063 isZero(NUM val, double tolerance=1e-5) {
0064 return fabs(val) < tolerance;
0065 }
0066
0067
0068
0069
0070
0071 template <typename NUM>
0072 inline typename std::enable_if_t<std::is_integral_v<NUM>, bool>
0073 isZero(NUM val, double = 1e-5) {
0074 return val==0;
0075 }
0076
0077
0078 template <typename NUM>
0079 inline typename std::enable_if_t<std::is_floating_point_v<NUM>, bool>
0080 isNaN(NUM val) { return std::isnan(val); }
0081
0082
0083 template <typename NUM>
0084 inline typename std::enable_if_t<std::is_floating_point_v<NUM>, bool>
0085 notNaN(NUM val) { return !std::isnan(val); }
0086
0087
0088
0089
0090
0091
0092 template <typename N1, typename N2>
0093 inline typename std::enable_if_t<
0094 std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2> &&
0095 (std::is_floating_point_v<N1> || std::is_floating_point_v<N2>), bool>
0096 fuzzyEquals(N1 a, N2 b, double tolerance=1e-5) {
0097 const double absavg = (std::abs(a) + std::abs(b))/2.0;
0098 const double absdiff = std::abs(a - b);
0099 const bool rtn = (isZero(a) && isZero(b)) || absdiff < tolerance*absavg;
0100 return rtn;
0101 }
0102
0103
0104
0105
0106
0107 template <typename N1, typename N2>
0108 inline typename std::enable_if_t<
0109 std::is_integral_v<N1> && std::is_integral_v<N2>, bool>
0110 fuzzyEquals(N1 a, N2 b, double) {
0111 return a == b;
0112 }
0113
0114
0115 static std::function<bool(const double, const double)> fuzzyEqComp =
0116 [](const double& lhs, const double& rhs) { return fuzzyEquals(lhs, rhs); };
0117
0118
0119
0120
0121
0122 template <typename N1, typename N2>
0123 inline typename std::enable_if_t<
0124 std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2>, bool>
0125 fuzzyGtrEquals(N1 a, N2 b, double tolerance=1e-5) {
0126 return a > b || fuzzyEquals(a, b, tolerance);
0127 }
0128
0129
0130
0131
0132 template <typename N1, typename N2>
0133 inline typename std::enable_if_t<
0134 std::is_arithmetic_v<N1> && std::is_arithmetic_v<N2>, bool>
0135 fuzzyLessEquals(N1 a, N2 b, double tolerance=1e-5) {
0136 return a < b || fuzzyEquals(a, b, tolerance);
0137 }
0138
0139
0140 inline double approx(double a, int n = 5) {
0141 double roundTo = pow(10.0,n);
0142 a *= roundTo;
0143 a = floor(a);
0144 return a/roundTo;
0145 }
0146
0147
0148
0149
0150
0151
0152
0153
0154
0155
0156 enum RangeBoundary { OPEN=0, SOFT=0, CLOSED=1, HARD=1 };
0157
0158
0159
0160
0161
0162
0163 template<typename NUM>
0164 inline bool inRange(NUM value, NUM low, NUM high,
0165 RangeBoundary lowbound=CLOSED, RangeBoundary highbound=OPEN) {
0166 if (lowbound == OPEN && highbound == OPEN) {
0167 return (value > low && value < high);
0168 } else if (lowbound == OPEN && highbound == CLOSED) {
0169 return (value > low && value <= high);
0170 } else if (lowbound == CLOSED && highbound == OPEN) {
0171 return (value >= low && value < high);
0172 } else {
0173 return (value >= low && value <= high);
0174 }
0175 }
0176
0177
0178 template<typename NUM>
0179 inline bool inRange(NUM value, std::pair<NUM, NUM> lowhigh,
0180 RangeBoundary lowbound=CLOSED, RangeBoundary highbound=OPEN) {
0181 return inRange(value, lowhigh.first, lowhigh.second, lowbound, highbound);
0182 }
0183
0184
0185
0186
0187
0188
0189 inline bool inRange(int value, int low, int high,
0190 RangeBoundary lowbound=CLOSED, RangeBoundary highbound=CLOSED) {
0191 if (lowbound == OPEN && highbound == OPEN) {
0192 return (value > low && value < high);
0193 } else if (lowbound == OPEN && highbound == CLOSED) {
0194 return (value > low && value <= high);
0195 } else if (lowbound == CLOSED && highbound == OPEN) {
0196 return (value >= low && value < high);
0197 } else {
0198 return (value >= low && value <= high);
0199 }
0200 }
0201
0202
0203 inline bool inRange(int value, std::pair<int, int> lowhigh,
0204 RangeBoundary lowbound=CLOSED, RangeBoundary highbound=OPEN) {
0205 return inRange(value, lowhigh.first, lowhigh.second, lowbound, highbound);
0206 }
0207
0208
0209
0210
0211
0212
0213
0214
0215 template <typename NUM>
0216 inline NUM sqr(NUM a) {
0217 return a*a;
0218 }
0219
0220
0221 template <typename... Num>
0222 inline std::enable_if_t<std::conjunction_v<std::is_arithmetic<Num>...>, double>
0223 add_quad(Num ... vals) {
0224 return sqrt((0.0 + ... + sqr(vals)));
0225 }
0226
0227
0228 inline int sign(double val) {
0229 if (isZero(val)) return ZERO;
0230 const int valsign = (val > 0) ? PLUS : MINUS;
0231 return valsign;
0232 }
0233
0234
0235 inline int sign(int val) {
0236 if (val == 0) return ZERO;
0237 return (val > 0) ? PLUS : MINUS;
0238 }
0239
0240
0241 inline int sign(long val) {
0242 if (val == 0) return ZERO;
0243 return (val > 0) ? PLUS : MINUS;
0244 }
0245
0246
0247 inline double subtract(double a, double b, double tolerance = 1e-5) {
0248 if (fuzzyEquals(a,b,tolerance)) return 0.;
0249 return a - b;
0250 }
0251
0252
0253 inline double add(double a, double b, double tolerance = 1e-5) {
0254 return subtract(a,-b,tolerance);
0255 }
0256
0257
0258
0259
0260
0261
0262
0263
0264
0265
0266
0267 inline std::vector<double> linspace(size_t nbins, double xmin, double xmax, bool include_end=true) {
0268 if (xmax < xmin) throw RangeError("xmax should not be smaller than xmin!");
0269 if (nbins == 0) throw RangeError("Requested number of bins is 0!");
0270 std::vector<double> rtn;
0271 const double interval = (xmax-xmin)/static_cast<double>(nbins);
0272 for (size_t i = 0; i < nbins; ++i) {
0273 rtn.push_back(xmin + i*interval);
0274 }
0275 assert(rtn.size() == nbins);
0276 if (include_end) rtn.push_back(xmax);
0277 return rtn;
0278 }
0279
0280
0281
0282
0283
0284
0285
0286 inline std::vector<double> logspace(size_t nbins, double xmin, double xmax, bool include_end=true) {
0287 if (xmax < xmin) throw RangeError("xmax should not be smaller than xmin!");
0288 if (xmin < 0) throw RangeError("xmin should not be negative!");
0289 if (nbins == 0) throw RangeError("Requested number of bins is 0!");
0290 const double logxmin = std::log(xmin);
0291 const double logxmax = std::log(xmax);
0292 const std::vector<double> logvals = linspace(nbins, logxmin, logxmax);
0293 assert(logvals.size() == nbins+1);
0294 std::vector<double> rtn; rtn.reserve(logvals.size());
0295 rtn.push_back(xmin);
0296 for (size_t i = 1; i < logvals.size()-1; ++i) {
0297 rtn.push_back(std::exp(logvals[i]));
0298 }
0299 assert(rtn.size() == nbins);
0300 if (include_end) rtn.push_back(xmax);
0301 return rtn;
0302 }
0303
0304
0305
0306
0307
0308
0309
0310
0311
0312
0313
0314
0315
0316
0317
0318
0319
0320
0321
0322
0323
0324
0325
0326
0327
0328 inline std::vector<double> pdfspace(size_t nbins, double xmin, double xmax, std::function<double(double)>& fn, size_t nsample=10000) {
0329 const double dx = (xmax-xmin)/(double)nsample;
0330 const std::vector<double> xs = linspace(nsample, xmin, xmax);
0331 std::vector<double> ys(0, nsample);
0332 auto posfn = [&](double x){return std::max(fn(x), 0.0);};
0333 std::transform(xs.begin(), xs.end(), ys.begin(), posfn);
0334 std::vector<double> areas; areas.reserve(nsample);
0335 double areasum = 0;
0336 for (size_t i = 0; i < ys.size()-1; ++i) {
0337 const double area = (ys[i] + ys[i+1])*dx/2.0;
0338 areas[i] = area;
0339 areasum += area;
0340 }
0341 const double df = areasum/(double)nbins;
0342 std::vector<double> xedges{xmin}; xedges.reserve(nbins+1);
0343 double fsum = 0;
0344 for (size_t i = 0; i < nsample-1; ++i) {
0345 fsum += areas[i];
0346 if (fsum > df) {
0347 fsum = 0;
0348 xedges.push_back(xs[i+1]);
0349 }
0350 }
0351 xedges.push_back(xmax);
0352 assert(xedges.size() == nbins+1);
0353 return xedges;
0354 }
0355
0356
0357
0358
0359
0360 template <typename NUM>
0361 inline int index_between(const NUM& val, const std::vector<NUM>& binedges) {
0362 if (!inRange(val, binedges.front(), binedges.back())) return -1;
0363 int index = -1;
0364 for (size_t i = 1; i < binedges.size(); ++i) {
0365 if (val < binedges[i]) {
0366 index = i-1;
0367 break;
0368 }
0369 }
0370 assert(inRange(index, -1, binedges.size()-1));
0371 return index;
0372 }
0373
0374
0375
0376
0377
0378
0379
0380
0381 inline double effNumEntries(const double sumW, const double sumW2) {
0382 if (isZero(sumW2)) return 0;
0383 return sqr(sumW) / sumW2;
0384 }
0385
0386
0387 inline double effNumEntries(const std::vector<double>& weights) {
0388 double sumW = 0.0, sumW2 = 0.0;
0389 for (size_t i = 0; i < weights.size(); ++i) {
0390 sumW += weights[i];
0391 sumW2 += sqr(weights[i]);
0392 }
0393 return effNumEntries(sumW, sumW2);
0394 }
0395
0396
0397 inline double mean(const std::vector<int>& sample) {
0398 double mean = 0.0;
0399 for (size_t i=0; i<sample.size(); ++i) {
0400 mean += sample[i];
0401 }
0402 return mean/sample.size();
0403 }
0404
0405
0406 inline double mean(const double sumWX, const double sumW) {
0407 return sumW? sumWX / sumW : std::numeric_limits<double>::quiet_NaN();
0408 }
0409
0410
0411 inline double mean(const std::vector<double>& sample,
0412 const std::vector<double>& weights) {
0413 if (sample.size() != weights.size()) throw RangeError("Inputs should have equal length!");
0414 double sumWX = 0., sumW = 0.;
0415 for (size_t i = 0; i < sample.size(); ++i) {
0416 sumW += weights[i];
0417 sumWX += weights[i]*sample[i];
0418 }
0419 return mean(sumWX, sumW);
0420 }
0421
0422
0423
0424
0425
0426
0427 inline double variance(const double sumWX, const double sumW,
0428 const double sumWX2, const double sumW2) {
0429 const double num = subtract(sumWX2*sumW, sqr(sumWX));
0430 const double den = subtract(sqr(sumW), sumW2);
0431
0432
0433
0434
0435
0436
0437
0438
0439
0440
0441 return den? fabs(num/den): std::numeric_limits<double>::quiet_NaN();
0442 }
0443
0444
0445 inline double variance(const std::vector<double>& sample,
0446 const std::vector<double>& weights) {
0447 if (sample.size() != weights.size()) throw RangeError("Inputs should have equal length!");
0448 if (fuzzyLessEquals(effNumEntries(weights), 1.0)) {
0449
0450 return std::numeric_limits<double>::quiet_NaN();
0451 }
0452 double sumWX = 0., sumW = 0.;
0453 double sumWX2 = 0., sumW2 = 0.;
0454 for (size_t i = 0; i < sample.size(); ++i) {
0455 sumW += weights[i];
0456 sumWX += weights[i]*sample[i];
0457 sumW2 += sqr(weights[i]);
0458 sumWX2 += weights[i]*sqr(sample[i]);
0459 }
0460 return variance(sumWX, sumW, sumWX2, sumW2);
0461 }
0462
0463
0464 inline double stdDev(const double sumWX, const double sumW,
0465 const double sumWX2, const double sumW2) {
0466 return std::sqrt(variance(sumWX, sumW, sumWX2, sumW2));
0467 }
0468
0469
0470 inline double stdDev(const std::vector<double>& sample,
0471 const std::vector<double>& weights) {
0472 return std::sqrt(variance(sample, weights));
0473 }
0474
0475
0476 inline double stdErr(const double sumWX, const double sumW,
0477 const double sumWX2, const double sumW2) {
0478 const double effN = effNumEntries(sumW, sumW2);
0479 if (effN == 0) return std::numeric_limits<double>::quiet_NaN();
0480 const double var = variance(sumWX, sumW, sumWX2, sumW2);
0481 return std::sqrt(var / effN);
0482 }
0483
0484
0485 inline double stdErr(const std::vector<double>& sample,
0486 const std::vector<double>& weights) {
0487 if (sample.size() != weights.size()) throw RangeError("Inputs should have equal length!");
0488 const double effN = effNumEntries(weights);
0489 if (effN == 0) return std::numeric_limits<double>::quiet_NaN();
0490 const double var = variance(sample, weights);
0491 return std::sqrt(var / effN);
0492 }
0493
0494
0495 inline double RMS(const double sumWX2, const double sumW, const double sumW2) {
0496
0497
0498 const double effN = effNumEntries(sumW, sumW2);
0499 if (effN == 0) return std::numeric_limits<double>::quiet_NaN();
0500 const double meanSq = sumWX2 / sumW;
0501 return std::sqrt(meanSq);
0502 }
0503
0504
0505 inline double RMS(const std::vector<double>& sample,
0506 const std::vector<double>& weights) {
0507 if (sample.size() != weights.size()) throw RangeError("Inputs should have equal length!");
0508 double sumWX2 = 0., sumW = 0., sumW2 = 0.;
0509 for (size_t i = 0; i < sample.size(); ++i) {
0510 sumW += weights[i];
0511 sumW2 += sqr(weights[i]);
0512 sumWX2 += weights[i]*sqr(sample[i]);
0513 }
0514 return RMS(sumWX2, sumW, sumW2);
0515 }
0516
0517
0518 inline double covariance(const std::vector<int>& sample1, const std::vector<int>& sample2) {
0519 const double mean1 = mean(sample1);
0520 const double mean2 = mean(sample2);
0521 const size_t N = sample1.size();
0522 double cov = 0.0;
0523 for (size_t i = 0; i < N; i++) {
0524 const double cov_i = (sample1[i] - mean1)*(sample2[i] - mean2);
0525 cov += cov_i;
0526 }
0527 if (N > 1) return cov/(N-1);
0528 else return 0.0;
0529 }
0530
0531
0532
0533 inline double correlation(const std::vector<int>& sample1, const std::vector<int>& sample2) {
0534 const double cov = covariance(sample1, sample2);
0535 const double var1 = covariance(sample1, sample1);
0536 const double var2 = covariance(sample2, sample2);
0537 const double correlation = cov/sqrt(var1*var2);
0538 const double corr_strength = correlation*sqrt(var2/var1);
0539 return corr_strength;
0540 }
0541
0542
0543
0544
0545
0546
0547
0548
0549 inline double naiveChi2(const std::vector<double>& sample1, const std::vector<double>& sample2,
0550 const std::vector<double>& s1errors = std::vector<double>{},
0551 const std::vector<double>& s2errors = std::vector<double>{}) {
0552 if (sample1.size() != sample2.size()) {
0553 throw RangeError("Inputs should have equal length!");
0554 }
0555 if (s1errors.size() && sample1.size() != s1errors.size()) {
0556 throw RangeError("Inputs should have equal length!");
0557 }
0558 if (s2errors.size() && sample2.size() != s2errors.size()) {
0559 throw RangeError("Inputs should have equal length!");
0560 }
0561 const size_t N = sample1.size();
0562 double chi2 = 0.0;
0563 for (size_t i = 0; i < N; ++i) {
0564 double temp = sqr(sample1[i] - sample2[i]);
0565 if (s1errors.size()) {
0566 temp /= sqr(s1errors[i]) + sqr(s2errors[i]);
0567 }
0568 chi2 += temp;
0569 }
0570 return chi2;
0571 }
0572
0573
0574
0575
0576
0577
0578
0579 inline double naiveChi2reduced(const std::vector<double>& sample1, const std::vector<double>& sample2,
0580 const std::vector<double>& s1errors = std::vector<double>{},
0581 const std::vector<double>& s2errors = std::vector<double>{}) {
0582 if (sample1.empty()) throw RangeError("Inputs should not have 0 length!");
0583 return naiveChi2(sample1, sample2, s1errors, s2errors)/sample1.size();
0584 }
0585
0586
0587
0588
0589 }
0590
0591 #endif