File indexing completed on 2025-04-19 09:13:36
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-8) {
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 Num add_quad(Num a, Num b) {
0223 return sqrt(a*a + b*b);
0224 }
0225
0226
0227 template <typename Num>
0228 inline Num add_quad(Num a, Num b, Num c) {
0229 return sqrt(a*a + b*b + c*c);
0230 }
0231
0232
0233 inline int sign(double val) {
0234 if (isZero(val)) return ZERO;
0235 const int valsign = (val > 0) ? PLUS : MINUS;
0236 return valsign;
0237 }
0238
0239
0240 inline int sign(int val) {
0241 if (val == 0) return ZERO;
0242 return (val > 0) ? PLUS : MINUS;
0243 }
0244
0245
0246 inline int sign(long val) {
0247 if (val == 0) return ZERO;
0248 return (val > 0) ? PLUS : MINUS;
0249 }
0250
0251
0252
0253
0254
0255
0256
0257
0258
0259
0260
0261 inline std::vector<double> linspace(size_t nbins, double xmin, double xmax, bool include_end=true) {
0262 if (xmax < xmin) throw RangeError("xmax should not be smaller than xmin!");
0263 if (nbins == 0) throw RangeError("Requested number of bins is 0!");
0264 std::vector<double> rtn;
0265 const double interval = (xmax-xmin)/static_cast<double>(nbins);
0266 for (size_t i = 0; i < nbins; ++i) {
0267 rtn.push_back(xmin + i*interval);
0268 }
0269 assert(rtn.size() == nbins);
0270 if (include_end) rtn.push_back(xmax);
0271 return rtn;
0272 }
0273
0274
0275
0276
0277
0278
0279
0280 inline std::vector<double> logspace(size_t nbins, double xmin, double xmax, bool include_end=true) {
0281 if (xmax < xmin) throw RangeError("xmax should not be smaller than xmin!");
0282 if (xmin < 0) throw RangeError("xmin should not be negative!");
0283 if (nbins == 0) throw RangeError("Requested number of bins is 0!");
0284 const double logxmin = std::log(xmin);
0285 const double logxmax = std::log(xmax);
0286 const std::vector<double> logvals = linspace(nbins, logxmin, logxmax);
0287 assert(logvals.size() == nbins+1);
0288 std::vector<double> rtn; rtn.reserve(logvals.size());
0289 rtn.push_back(xmin);
0290 for (size_t i = 1; i < logvals.size()-1; ++i) {
0291 rtn.push_back(std::exp(logvals[i]));
0292 }
0293 assert(rtn.size() == nbins);
0294 if (include_end) rtn.push_back(xmax);
0295 return rtn;
0296 }
0297
0298
0299
0300
0301
0302
0303
0304
0305
0306
0307
0308
0309
0310
0311
0312
0313
0314
0315
0316
0317
0318
0319
0320
0321
0322 inline std::vector<double> pdfspace(size_t nbins, double xmin, double xmax, std::function<double(double)>& fn, size_t nsample=10000) {
0323 const double dx = (xmax-xmin)/(double)nsample;
0324 const std::vector<double> xs = linspace(nsample, xmin, xmax);
0325 std::vector<double> ys(0, nsample);
0326 auto posfn = [&](double x){return std::max(fn(x), 0.0);};
0327 std::transform(xs.begin(), xs.end(), ys.begin(), posfn);
0328 std::vector<double> areas; areas.reserve(nsample);
0329 double areasum = 0;
0330 for (size_t i = 0; i < ys.size()-1; ++i) {
0331 const double area = (ys[i] + ys[i+1])*dx/2.0;
0332 areas[i] = area;
0333 areasum += area;
0334 }
0335 const double df = areasum/(double)nbins;
0336 std::vector<double> xedges{xmin}; xedges.reserve(nbins+1);
0337 double fsum = 0;
0338 for (size_t i = 0; i < nsample-1; ++i) {
0339 fsum += areas[i];
0340 if (fsum > df) {
0341 fsum = 0;
0342 xedges.push_back(xs[i+1]);
0343 }
0344 }
0345 xedges.push_back(xmax);
0346 assert(xedges.size() == nbins+1);
0347 return xedges;
0348 }
0349
0350
0351
0352
0353
0354 template <typename NUM>
0355 inline int index_between(const NUM& val, const std::vector<NUM>& binedges) {
0356 if (!inRange(val, binedges.front(), binedges.back())) return -1;
0357 int index = -1;
0358 for (size_t i = 1; i < binedges.size(); ++i) {
0359 if (val < binedges[i]) {
0360 index = i-1;
0361 break;
0362 }
0363 }
0364 assert(inRange(index, -1, binedges.size()-1));
0365 return index;
0366 }
0367
0368
0369
0370
0371
0372
0373
0374
0375 inline double effNumEntries(const double sumW, const double sumW2) {
0376 if (isZero(sumW2)) return 0;
0377 return sqr(sumW) / sumW2;
0378 }
0379
0380
0381 inline double effNumEntries(const std::vector<double>& weights) {
0382 double sumW = 0.0, sumW2 = 0.0;
0383 for (size_t i = 0; i < weights.size(); ++i) {
0384 sumW += weights[i];
0385 sumW2 += sqr(weights[i]);
0386 }
0387 return effNumEntries(sumW, sumW2);
0388 }
0389
0390
0391 inline double mean(const std::vector<int>& sample) {
0392 double mean = 0.0;
0393 for (size_t i=0; i<sample.size(); ++i) {
0394 mean += sample[i];
0395 }
0396 return mean/sample.size();
0397 }
0398
0399
0400 inline double mean(const double sumWX, const double sumW) {
0401 return sumW? sumWX / sumW : std::numeric_limits<double>::quiet_NaN();
0402 }
0403
0404
0405 inline double mean(const std::vector<double>& sample,
0406 const std::vector<double>& weights) {
0407 if (sample.size() != weights.size()) throw RangeError("Inputs should have equal length!");
0408 double sumWX = 0., sumW = 0.;
0409 for (size_t i = 0; i < sample.size(); ++i) {
0410 sumW += weights[i];
0411 sumWX += weights[i]*sample[i];
0412 }
0413 return mean(sumWX, sumW);
0414 }
0415
0416
0417
0418
0419
0420
0421 inline double variance(const double sumWX, const double sumW,
0422 const double sumWX2, const double sumW2) {
0423 const double num = sumWX2*sumW - sqr(sumWX);
0424 const double den = sqr(sumW) - sumW2;
0425
0426
0427
0428
0429
0430
0431
0432
0433
0434
0435 return den? fabs(num/den): std::numeric_limits<double>::quiet_NaN();
0436 }
0437
0438
0439 inline double variance(const std::vector<double>& sample,
0440 const std::vector<double>& weights) {
0441 if (sample.size() != weights.size()) throw RangeError("Inputs should have equal length!");
0442 if (fuzzyLessEquals(effNumEntries(weights), 1.0)) {
0443
0444 return std::numeric_limits<double>::quiet_NaN();
0445 }
0446 double sumWX = 0., sumW = 0.;
0447 double sumWX2 = 0., sumW2 = 0.;
0448 for (size_t i = 0; i < sample.size(); ++i) {
0449 sumW += weights[i];
0450 sumWX += weights[i]*sample[i];
0451 sumW2 += sqr(weights[i]);
0452 sumWX2 += weights[i]*sqr(sample[i]);
0453 }
0454 return variance(sumWX, sumW, sumWX2, sumW2);
0455 }
0456
0457
0458 inline double stdDev(const double sumWX, const double sumW,
0459 const double sumWX2, const double sumW2) {
0460 return std::sqrt(variance(sumWX, sumW, sumWX2, sumW2));
0461 }
0462
0463
0464 inline double stdDev(const std::vector<double>& sample,
0465 const std::vector<double>& weights) {
0466 return std::sqrt(variance(sample, weights));
0467 }
0468
0469
0470 inline double stdErr(const double sumWX, const double sumW,
0471 const double sumWX2, const double sumW2) {
0472 const double effN = effNumEntries(sumW, sumW2);
0473 if (effN == 0) return std::numeric_limits<double>::quiet_NaN();
0474 const double var = variance(sumWX, sumW, sumWX2, sumW2);
0475 return std::sqrt(var / effN);
0476 }
0477
0478
0479 inline double stdErr(const std::vector<double>& sample,
0480 const std::vector<double>& weights) {
0481 if (sample.size() != weights.size()) throw RangeError("Inputs should have equal length!");
0482 const double effN = effNumEntries(weights);
0483 if (effN == 0) return std::numeric_limits<double>::quiet_NaN();
0484 const double var = variance(sample, weights);
0485 return std::sqrt(var / effN);
0486 }
0487
0488
0489 inline double RMS(const double sumWX2, const double sumW, const double sumW2) {
0490
0491
0492 const double effN = effNumEntries(sumW, sumW2);
0493 if (effN == 0) return std::numeric_limits<double>::quiet_NaN();
0494 const double meanSq = sumWX2 / sumW;
0495 return std::sqrt(meanSq);
0496 }
0497
0498
0499 inline double RMS(const std::vector<double>& sample,
0500 const std::vector<double>& weights) {
0501 if (sample.size() != weights.size()) throw RangeError("Inputs should have equal length!");
0502 double sumWX2 = 0., sumW = 0., sumW2 = 0.;
0503 for (size_t i = 0; i < sample.size(); ++i) {
0504 sumW += weights[i];
0505 sumW2 += sqr(weights[i]);
0506 sumWX2 += weights[i]*sqr(sample[i]);
0507 }
0508 return RMS(sumWX2, sumW, sumW2);
0509 }
0510
0511
0512 inline double covariance(const std::vector<int>& sample1, const std::vector<int>& sample2) {
0513 const double mean1 = mean(sample1);
0514 const double mean2 = mean(sample2);
0515 const size_t N = sample1.size();
0516 double cov = 0.0;
0517 for (size_t i = 0; i < N; i++) {
0518 const double cov_i = (sample1[i] - mean1)*(sample2[i] - mean2);
0519 cov += cov_i;
0520 }
0521 if (N > 1) return cov/(N-1);
0522 else return 0.0;
0523 }
0524
0525
0526
0527 inline double correlation(const std::vector<int>& sample1, const std::vector<int>& sample2) {
0528 const double cov = covariance(sample1, sample2);
0529 const double var1 = covariance(sample1, sample1);
0530 const double var2 = covariance(sample2, sample2);
0531 const double correlation = cov/sqrt(var1*var2);
0532 const double corr_strength = correlation*sqrt(var2/var1);
0533 return corr_strength;
0534 }
0535
0536
0537
0538
0539
0540
0541
0542
0543 inline double naiveChi2(const std::vector<double>& sample1, const std::vector<double>& sample2,
0544 const std::vector<double>& s1errors = std::vector<double>{},
0545 const std::vector<double>& s2errors = std::vector<double>{}) {
0546 if (sample1.size() != sample2.size()) {
0547 throw RangeError("Inputs should have equal length!");
0548 }
0549 if (s1errors.size() && sample1.size() != s1errors.size()) {
0550 throw RangeError("Inputs should have equal length!");
0551 }
0552 if (s2errors.size() && sample2.size() != s2errors.size()) {
0553 throw RangeError("Inputs should have equal length!");
0554 }
0555 const size_t N = sample1.size();
0556 double chi2 = 0.0;
0557 for (size_t i = 0; i < N; ++i) {
0558 double temp = sqr(sample1[i] - sample2[i]);
0559 if (s1errors.size()) {
0560 temp /= sqr(s1errors[i]) + sqr(s2errors[i]);
0561 }
0562 chi2 += temp;
0563 }
0564 return chi2;
0565 }
0566
0567
0568
0569
0570
0571
0572
0573 inline double naiveChi2reduced(const std::vector<double>& sample1, const std::vector<double>& sample2,
0574 const std::vector<double>& s1errors = std::vector<double>{},
0575 const std::vector<double>& s2errors = std::vector<double>{}) {
0576 if (sample1.empty()) throw RangeError("Inputs should not have 0 length!");
0577 return naiveChi2(sample1, sample2, s1errors, s2errors)/sample1.size();
0578 }
0579
0580
0581
0582
0583 }
0584
0585 #endif