Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-16 10:29:41

0001 /*
0002  * Project: RooFit
0003  * Authors:
0004  *   Jonas Rembser, CERN 2024
0005  *   Garima Singh, CERN 2023
0006  *
0007  * Copyright (c) 2024, CERN
0008  *
0009  * Redistribution and use in source and binary forms,
0010  * with or without modification, are permitted according to the terms
0011  * listed in LICENSE (http://roofit.sourceforge.net/license.txt)
0012  */
0013 
0014 #ifndef RooFit_Detail_MathFuncs_h
0015 #define RooFit_Detail_MathFuncs_h
0016 
0017 #include <TMath.h>
0018 #include <Math/PdfFuncMathCore.h>
0019 #include <Math/ProbFuncMathCore.h>
0020 
0021 #include <algorithm>
0022 #include <cmath>
0023 #include <stdexcept>
0024 
0025 namespace RooFit::Detail::MathFuncs {
0026 
0027 /// Calculates the binomial coefficient n over k.
0028 /// Equivalent to TMath::Binomial, but inlined.
0029 inline double binomial(int n, int k)
0030 {
0031    if (n < 0 || k < 0 || n < k)
0032       return TMath::SignalingNaN();
0033    if (k == 0 || n == k)
0034       return 1;
0035 
0036    int k1 = std::min(k, n - k);
0037    int k2 = n - k1;
0038    double fact = k2 + 1;
0039    for (double i = k1; i > 1.; --i) {
0040       fact *= (k2 + i) / i;
0041    }
0042    return fact;
0043 }
0044 
0045 /// The caller needs to make sure that there is at least one coefficient.
0046 template <typename DoubleArray>
0047 double bernstein(double x, double xmin, double xmax, DoubleArray coefs, int nCoefs)
0048 {
0049    double xScaled = (x - xmin) / (xmax - xmin); // rescale to [0,1]
0050    int degree = nCoefs - 1;                     // n+1 polys of degree n
0051 
0052    // in case list of arguments passed is empty
0053    if (degree < 0) {
0054       return TMath::SignalingNaN();
0055    } else if (degree == 0) {
0056       return coefs[0];
0057    } else if (degree == 1) {
0058 
0059       double a0 = coefs[0];      // c0
0060       double a1 = coefs[1] - a0; // c1 - c0
0061       return a1 * xScaled + a0;
0062 
0063    } else if (degree == 2) {
0064 
0065       double a0 = coefs[0];            // c0
0066       double a1 = 2 * (coefs[1] - a0); // 2 * (c1 - c0)
0067       double a2 = coefs[2] - a1 - a0;  // c0 - 2 * c1 + c2
0068       return (a2 * xScaled + a1) * xScaled + a0;
0069    }
0070 
0071    double t = xScaled;
0072    double s = 1. - xScaled;
0073 
0074    double result = coefs[0] * s;
0075    for (int i = 1; i < degree; i++) {
0076       result = (result + t * binomial(degree, i) * coefs[i]) * s;
0077       t *= xScaled;
0078    }
0079    result += t * coefs[degree];
0080 
0081    return result;
0082 }
0083 
0084 /// @brief Function to evaluate an un-normalized RooGaussian.
0085 inline double gaussian(double x, double mean, double sigma)
0086 {
0087    const double arg = x - mean;
0088    const double sig = sigma;
0089    return std::exp(-0.5 * arg * arg / (sig * sig));
0090 }
0091 
0092 template <typename DoubleArray>
0093 double product(DoubleArray factors, std::size_t nFactors)
0094 {
0095    double out = 1.0;
0096    for (std::size_t i = 0; i < nFactors; ++i) {
0097       out *= factors[i];
0098    }
0099    return out;
0100 }
0101 
0102 // RooRatio evaluate function.
0103 inline double ratio(double numerator, double denominator)
0104 {
0105    return numerator / denominator;
0106 }
0107 
0108 inline double bifurGauss(double x, double mean, double sigmaL, double sigmaR)
0109 {
0110    // Note: this simplification does not work with Clad as of v1.1!
0111    // return gaussian(x, mean, x < mean ? sigmaL : sigmaR);
0112    if (x < mean)
0113       return gaussian(x, mean, sigmaL);
0114    return gaussian(x, mean, sigmaR);
0115 }
0116 
0117 inline double efficiency(double effFuncVal, int catIndex, int sigCatIndex)
0118 {
0119    // Truncate efficiency function in range 0.0-1.0
0120    effFuncVal = std::clamp(effFuncVal, 0.0, 1.0);
0121 
0122    if (catIndex == sigCatIndex)
0123       return effFuncVal; // Accept case
0124    else
0125       return 1 - effFuncVal; // Reject case
0126 }
0127 
0128 /// In pdfMode, a coefficient for the constant term of 1.0 is implied if lowestOrder > 0.
0129 template <bool pdfMode = false, typename DoubleArray>
0130 double polynomial(DoubleArray coeffs, int nCoeffs, int lowestOrder, double x)
0131 {
0132    double retVal = coeffs[nCoeffs - 1];
0133    for (int i = nCoeffs - 2; i >= 0; i--) {
0134       retVal = coeffs[i] + x * retVal;
0135    }
0136    retVal = retVal * std::pow(x, lowestOrder);
0137    return retVal + (pdfMode && lowestOrder > 0 ? 1.0 : 0.0);
0138 }
0139 
0140 template <typename DoubleArray>
0141 double chebychev(DoubleArray coeffs, unsigned int nCoeffs, double x_in, double xMin, double xMax)
0142 {
0143    // transform to range [-1, +1]
0144    const double xPrime = (x_in - 0.5 * (xMax + xMin)) / (0.5 * (xMax - xMin));
0145 
0146    // extract current values of coefficients
0147    double sum = 1.;
0148    if (nCoeffs > 0) {
0149       double curr = xPrime;
0150       double twox = 2 * xPrime;
0151       double last = 1;
0152       double newval = twox * curr - last;
0153       last = curr;
0154       curr = newval;
0155       for (unsigned int i = 0; nCoeffs != i; ++i) {
0156          sum += last * coeffs[i];
0157          newval = twox * curr - last;
0158          last = curr;
0159          curr = newval;
0160       }
0161    }
0162    return sum;
0163 }
0164 
0165 template <typename DoubleArray>
0166 double multipdf(int idx, DoubleArray pdfs)
0167 {
0168    /* if (idx < 0 || idx >= static_cast<int>(pdfs.size())){
0169         throw std::out_of_range("Invalid PDF index");
0170 
0171    }
0172    */
0173    return pdfs[idx];
0174 }
0175 
0176 template <typename DoubleArray>
0177 double constraintSum(DoubleArray comp, unsigned int compSize)
0178 {
0179    double sum = 0;
0180    #ifdef __CLING__
0181    #pragma clad checkpoint loop
0182    #endif
0183    for (unsigned int i = 0; i < compSize; i++) {
0184       sum -= std::log(comp[i]);
0185    }
0186    return sum;
0187 }
0188 
0189 inline unsigned int uniformBinNumber(double low, double high, double val, unsigned int numBins, double coef)
0190 {
0191    double binWidth = (high - low) / numBins;
0192    return coef * (val >= high ? numBins - 1 : std::abs((val - low) / binWidth));
0193 }
0194 
0195 template <typename DoubleArray>
0196 unsigned int rawBinNumber(double x, DoubleArray boundaries, std::size_t nBoundaries)
0197 {
0198    DoubleArray end = boundaries + nBoundaries;
0199    DoubleArray it = std::lower_bound(boundaries, end, x);
0200    // always return valid bin number
0201    while (boundaries != it && (end == it || end == it + 1 || x < *it)) {
0202       --it;
0203    }
0204    return it - boundaries;
0205 }
0206 
0207 template <typename DoubleArray>
0208 unsigned int binNumber(double x, double coef, DoubleArray boundaries, unsigned int nBoundaries, int nbins, int blo)
0209 {
0210    const int rawBin = rawBinNumber(x, boundaries, nBoundaries);
0211    int tmp = std::min(nbins, rawBin - blo);
0212    return coef * std::max(0, tmp);
0213 }
0214 
0215 template <typename DoubleArray>
0216 double interpolate1d(double low, double high, double val, unsigned int numBins, DoubleArray vals)
0217 {
0218    double binWidth = (high - low) / numBins;
0219    int idx = val >= high ? numBins - 1 : std::abs((val - low) / binWidth);
0220 
0221    // interpolation
0222    double central = low + (idx + 0.5) * binWidth;
0223    if (val > low + 0.5 * binWidth && val < high - 0.5 * binWidth) {
0224       double slope;
0225       if (val < central) {
0226          slope = vals[idx] - vals[idx - 1];
0227       } else {
0228          slope = vals[idx + 1] - vals[idx];
0229       }
0230       return vals[idx] + slope * (val - central) / binWidth;
0231    }
0232 
0233    return vals[idx];
0234 }
0235 
0236 inline double poisson(double x, double par)
0237 {
0238    if (par < 0)
0239       return TMath::QuietNaN();
0240 
0241    if (x < 0) {
0242       return 0;
0243    } else if (x == 0.0) {
0244       return std::exp(-par);
0245    } else {
0246       double out = x * std::log(par) - TMath::LnGamma(x + 1.) - par;
0247       return std::exp(out);
0248    }
0249 }
0250 
0251 inline double flexibleInterpSingle(unsigned int code, double low, double high, double boundary, double nominal,
0252                                    double paramVal, double res)
0253 {
0254    if (code == 0) {
0255       // piece-wise linear
0256       if (paramVal > 0) {
0257          return paramVal * (high - nominal);
0258       } else {
0259          return paramVal * (nominal - low);
0260       }
0261    } else if (code == 1) {
0262       // piece-wise log
0263       if (paramVal >= 0) {
0264          return res * (std::pow(high / nominal, +paramVal) - 1);
0265       } else {
0266          return res * (std::pow(low / nominal, -paramVal) - 1);
0267       }
0268    } else if (code == 2) {
0269       // parabolic with linear
0270       double a = 0.5 * (high + low) - nominal;
0271       double b = 0.5 * (high - low);
0272       double c = 0;
0273       if (paramVal > 1) {
0274          return (2 * a + b) * (paramVal - 1) + high - nominal;
0275       } else if (paramVal < -1) {
0276          return -1 * (2 * a - b) * (paramVal + 1) + low - nominal;
0277       } else {
0278          return a * paramVal * paramVal + b * paramVal + c;
0279       }
0280       // According to an old comment in the source code, code 3 was apparently
0281       // meant to be a "parabolic version of log-normal", but it never got
0282       // implemented. If someone would need it, it could be implemented as doing
0283       // code 2 in log space.
0284    } else if (code == 4 || code == 6) {
0285       double x = paramVal;
0286       double mod = 1.0;
0287       if (code == 6) {
0288          high /= nominal;
0289          low /= nominal;
0290          nominal = 1;
0291       }
0292       if (x >= boundary) {
0293          mod = x * (high - nominal);
0294       } else if (x <= -boundary) {
0295          mod = x * (nominal - low);
0296       } else {
0297          // interpolate 6th degree
0298          double t = x / boundary;
0299          double eps_plus = high - nominal;
0300          double eps_minus = nominal - low;
0301          double S = 0.5 * (eps_plus + eps_minus);
0302          double A = 0.0625 * (eps_plus - eps_minus);
0303 
0304          mod = x * (S + t * A * (15 + t * t * (-10 + t * t * 3)));
0305       }
0306 
0307       // code 6 is multiplicative version of code 4
0308       if (code == 6) {
0309          mod *= res;
0310       }
0311       return mod;
0312 
0313    } else if (code == 5) {
0314       double x = paramVal;
0315       double mod = 1.0;
0316       if (x >= boundary) {
0317          mod = std::pow(high / nominal, +paramVal);
0318       } else if (x <= -boundary) {
0319          mod = std::pow(low / nominal, -paramVal);
0320       } else {
0321          // interpolate 6th degree exp
0322          double x0 = boundary;
0323 
0324          high /= nominal;
0325          low /= nominal;
0326 
0327          // GHL: Swagato's suggestions
0328          double logHi = std::log(high);
0329          double logLo = std::log(low);
0330          double powUp = std::exp(x0 * logHi);
0331          double powDown = std::exp(x0 * logLo);
0332          double powUpLog = high <= 0.0 ? 0.0 : powUp * logHi;
0333          double powDownLog = low <= 0.0 ? 0.0 : -powDown * logLo;
0334          double powUpLog2 = high <= 0.0 ? 0.0 : powUpLog * logHi;
0335          double powDownLog2 = low <= 0.0 ? 0.0 : -powDownLog * logLo;
0336 
0337          double S0 = 0.5 * (powUp + powDown);
0338          double A0 = 0.5 * (powUp - powDown);
0339          double S1 = 0.5 * (powUpLog + powDownLog);
0340          double A1 = 0.5 * (powUpLog - powDownLog);
0341          double S2 = 0.5 * (powUpLog2 + powDownLog2);
0342          double A2 = 0.5 * (powUpLog2 - powDownLog2);
0343 
0344          // fcns+der+2nd_der are eq at bd
0345 
0346          double x0Sq = x0 * x0;
0347 
0348          double a = 1. / (8 * x0) * (15 * A0 - 7 * x0 * S1 + x0 * x0 * A2);
0349          double b = 1. / (8 * x0Sq) * (-24 + 24 * S0 - 9 * x0 * A1 + x0 * x0 * S2);
0350          double c = 1. / (4 * x0Sq * x0) * (-5 * A0 + 5 * x0 * S1 - x0 * x0 * A2);
0351          double d = 1. / (4 * x0Sq * x0Sq) * (12 - 12 * S0 + 7 * x0 * A1 - x0 * x0 * S2);
0352          double e = 1. / (8 * x0Sq * x0Sq * x0) * (+3 * A0 - 3 * x0 * S1 + x0 * x0 * A2);
0353          double f = 1. / (8 * x0Sq * x0Sq * x0Sq) * (-8 + 8 * S0 - 5 * x0 * A1 + x0 * x0 * S2);
0354 
0355          // evaluate the 6-th degree polynomial using Horner's method
0356          double value = 1. + x * (a + x * (b + x * (c + x * (d + x * (e + x * f)))));
0357          mod = value;
0358       }
0359       return res * (mod - 1.0);
0360    }
0361 
0362    return 0.0;
0363 }
0364 
0365 template <typename ParamsArray, typename DoubleArray>
0366 double flexibleInterp(unsigned int code, ParamsArray params, unsigned int n, DoubleArray low, DoubleArray high,
0367                       double boundary, double nominal, int doCutoff)
0368 {
0369    double total = nominal;
0370    #ifdef __CLING__
0371    #pragma clad checkpoint loop
0372    #endif
0373    for (std::size_t i = 0; i < n; ++i) {
0374       total += flexibleInterpSingle(code, low[i], high[i], boundary, nominal, params[i], total);
0375    }
0376 
0377    return doCutoff && total <= 0 ? TMath::Limits<double>::Min() : total;
0378 }
0379 
0380 inline double landau(double x, double mu, double sigma)
0381 {
0382    if (sigma <= 0.)
0383       return 0.;
0384    return ROOT::Math::landau_pdf((x - mu) / sigma);
0385 }
0386 
0387 inline double logNormal(double x, double k, double m0)
0388 {
0389    return ROOT::Math::lognormal_pdf(x, std::log(m0), std::abs(std::log(k)));
0390 }
0391 
0392 inline double logNormalStandard(double x, double sigma, double mu)
0393 {
0394    return ROOT::Math::lognormal_pdf(x, mu, std::abs(sigma));
0395 }
0396 
0397 inline double effProd(double eff, double pdf)
0398 {
0399    return eff * pdf;
0400 }
0401 
0402 inline double nll(double pdf, double weight, int binnedL, int doBinOffset)
0403 {
0404    if (binnedL) {
0405       // Special handling of this case since std::log(Poisson(0,0)=0 but can't be
0406       // calculated with usual log-formula since std::log(mu)=0. No update of result
0407       // is required since term=0.
0408       if (std::abs(pdf) < 1e-10 && std::abs(weight) < 1e-10) {
0409          return 0.0;
0410       }
0411       if (doBinOffset) {
0412          return pdf - weight - weight * (std::log(pdf) - std::log(weight));
0413       }
0414       return pdf - weight * std::log(pdf) + TMath::LnGamma(weight + 1);
0415    } else {
0416       return -weight * std::log(pdf);
0417    }
0418 }
0419 
0420 template <typename DoubleArray>
0421 double recursiveFraction(DoubleArray a, unsigned int n)
0422 {
0423    double prod = a[0];
0424 
0425    for (unsigned int i = 1; i < n; ++i) {
0426       prod *= 1.0 - a[i];
0427    }
0428 
0429    return prod;
0430 }
0431 
0432 inline double cbShape(double m, double m0, double sigma, double alpha, double n)
0433 {
0434    double t = (m - m0) / sigma;
0435    if (alpha < 0)
0436       t = -t;
0437 
0438    double absAlpha = std::abs(alpha);
0439 
0440    if (t >= -absAlpha) {
0441       return std::exp(-0.5 * t * t);
0442    } else {
0443       double r = n / absAlpha;
0444       double a = std::exp(-0.5 * absAlpha * absAlpha);
0445       double b = r - absAlpha;
0446 
0447       return a * std::pow(r / (b - t), n);
0448    }
0449 }
0450 
0451 // For RooCBShape
0452 inline double approxErf(double arg)
0453 {
0454    if (arg > 5.0)
0455       return 1.0;
0456    if (arg < -5.0)
0457       return -1.0;
0458 
0459    return std::erf(arg);
0460 }
0461 
0462 /// @brief Function to calculate the integral of an un-normalized RooGaussian over x. To calculate the integral over
0463 /// mean, just interchange the respective values of x and mean.
0464 /// @param xMin Minimum value of variable to integrate wrt.
0465 /// @param xMax Maximum value of of variable to integrate wrt.
0466 /// @param mean Mean.
0467 /// @param sigma Sigma.
0468 /// @return The integral of an un-normalized RooGaussian over the value in x.
0469 inline double gaussianIntegral(double xMin, double xMax, double mean, double sigma)
0470 {
0471    // The normalisation constant 1./sqrt(2*pi*sigma^2) is left out in evaluate().
0472    // Therefore, the integral is scaled up by that amount to make RooFit normalise
0473    // correctly.
0474    double resultScale = 0.5 * std::sqrt(TMath::TwoPi()) * sigma;
0475 
0476    // Here everything is scaled and shifted into a standard normal distribution:
0477    double xscale = TMath::Sqrt2() * sigma;
0478    double scaledMin = 0.;
0479    double scaledMax = 0.;
0480    scaledMin = (xMin - mean) / xscale;
0481    scaledMax = (xMax - mean) / xscale;
0482 
0483    // Here we go for maximum precision: We compute all integrals in the UPPER
0484    // tail of the Gaussian, because erfc has the highest precision there.
0485    // Therefore, the different cases for range limits in the negative hemisphere are mapped onto
0486    // the equivalent points in the upper hemisphere using erfc(-x) = 2. - erfc(x)
0487    double ecmin = std::erfc(std::abs(scaledMin));
0488    double ecmax = std::erfc(std::abs(scaledMax));
0489 
0490    double cond = 0.0;
0491    // Don't put this "prd" inside the "if" because clad will not be able to differentiate the code correctly (as of
0492    // v1.1)!
0493    double prd = scaledMin * scaledMax;
0494    if (prd < 0.0) {
0495       cond = 2.0 - (ecmin + ecmax);
0496    } else if (scaledMax <= 0.0) {
0497       cond = ecmax - ecmin;
0498    } else {
0499       cond = ecmin - ecmax;
0500    }
0501    return resultScale * cond;
0502 }
0503 
0504 inline double bifurGaussIntegral(double xMin, double xMax, double mean, double sigmaL, double sigmaR)
0505 {
0506    const double xscaleL = TMath::Sqrt2() * sigmaL;
0507    const double xscaleR = TMath::Sqrt2() * sigmaR;
0508 
0509    const double resultScale = 0.5 * std::sqrt(TMath::TwoPi());
0510 
0511    if (xMax < mean) {
0512       return resultScale * (sigmaL * (std::erf((xMax - mean) / xscaleL) - std::erf((xMin - mean) / xscaleL)));
0513    } else if (xMin > mean) {
0514       return resultScale * (sigmaR * (std::erf((xMax - mean) / xscaleR) - std::erf((xMin - mean) / xscaleR)));
0515    } else {
0516       return resultScale * (sigmaR * std::erf((xMax - mean) / xscaleR) - sigmaL * std::erf((xMin - mean) / xscaleL));
0517    }
0518 }
0519 
0520 inline double exponentialIntegral(double xMin, double xMax, double constant)
0521 {
0522    if (constant == 0.0) {
0523       return xMax - xMin;
0524    }
0525 
0526    return (std::exp(constant * xMax) - std::exp(constant * xMin)) / constant;
0527 }
0528 
0529 /// In pdfMode, a coefficient for the constant term of 1.0 is implied if lowestOrder > 0.
0530 template <bool pdfMode = false, typename DoubleArray>
0531 double polynomialIntegral(DoubleArray coeffs, int nCoeffs, int lowestOrder, double xMin, double xMax)
0532 {
0533    int denom = lowestOrder + nCoeffs;
0534    double min = coeffs[nCoeffs - 1] / double(denom);
0535    double max = coeffs[nCoeffs - 1] / double(denom);
0536 
0537    for (int i = nCoeffs - 2; i >= 0; i--) {
0538       denom--;
0539       min = (coeffs[i] / double(denom)) + xMin * min;
0540       max = (coeffs[i] / double(denom)) + xMax * max;
0541    }
0542 
0543    max = max * std::pow(xMax, 1 + lowestOrder);
0544    min = min * std::pow(xMin, 1 + lowestOrder);
0545 
0546    return max - min + (pdfMode && lowestOrder > 0.0 ? xMax - xMin : 0.0);
0547 }
0548 
0549 /// use fast FMA if available, fall back to normal arithmetic if not
0550 inline double fast_fma(double x, double y, double z) noexcept
0551 {
0552 #if defined(FP_FAST_FMA) // check if std::fma has fast hardware implementation
0553    return std::fma(x, y, z);
0554 #else // defined(FP_FAST_FMA)
0555    // std::fma might be slow, so use a more pedestrian implementation
0556 #if defined(__clang__)
0557 #pragma STDC FP_CONTRACT ON // hint clang that using an FMA is okay here
0558 #endif                      // defined(__clang__)
0559    return (x * y) + z;
0560 #endif                      // defined(FP_FAST_FMA)
0561 }
0562 
0563 template <typename DoubleArray>
0564 double
0565 chebychevIntegral(DoubleArray coeffs, unsigned int nCoeffs, double xMin, double xMax, double xMinFull, double xMaxFull)
0566 {
0567    const double halfrange = .5 * (xMax - xMin);
0568    const double mid = .5 * (xMax + xMin);
0569 
0570    // the full range of the function is mapped to the normalised [-1, 1] range
0571    const double b = (xMaxFull - mid) / halfrange;
0572    const double a = (xMinFull - mid) / halfrange;
0573 
0574    // coefficient for integral(T_0(x)) is 1 (implicit), integrate by hand
0575    // T_0(x) and T_1(x), and use for n > 1: integral(T_n(x) dx) =
0576    // (T_n+1(x) / (n + 1) - T_n-1(x) / (n - 1)) / 2
0577    double sum = b - a; // integrate T_0(x) by hand
0578 
0579    const unsigned int iend = nCoeffs;
0580    if (iend > 0) {
0581       {
0582          // integrate T_1(x) by hand...
0583          const double c = coeffs[0];
0584          sum = fast_fma(0.5 * (b + a) * (b - a), c, sum);
0585       }
0586       if (1 < iend) {
0587          double bcurr = b;
0588          double btwox = 2 * b;
0589          double blast = 1;
0590 
0591          double acurr = a;
0592          double atwox = 2 * a;
0593          double alast = 1;
0594 
0595          double newval = atwox * acurr - alast;
0596          alast = acurr;
0597          acurr = newval;
0598 
0599          newval = btwox * bcurr - blast;
0600          blast = bcurr;
0601          bcurr = newval;
0602          double nminus1 = 1.;
0603          for (unsigned int i = 1; iend != i; ++i) {
0604             // integrate using recursion relation
0605             const double c = coeffs[i];
0606             const double term2 = (blast - alast) / nminus1;
0607 
0608             newval = atwox * acurr - alast;
0609             alast = acurr;
0610             acurr = newval;
0611 
0612             newval = btwox * bcurr - blast;
0613             blast = bcurr;
0614             bcurr = newval;
0615 
0616             ++nminus1;
0617             const double term1 = (bcurr - acurr) / (nminus1 + 1.);
0618             const double intTn = 0.5 * (term1 - term2);
0619             sum = fast_fma(intTn, c, sum);
0620          }
0621       }
0622    }
0623 
0624    // take care to multiply with the right factor to account for the mapping to
0625    // normalised range [-1, 1]
0626    return halfrange * sum;
0627 }
0628 
0629 // The last param should be of type bool but it is not as that causes some issues with Cling for some reason...
0630 inline double
0631 poissonIntegral(int code, double mu, double x, double integrandMin, double integrandMax, unsigned int protectNegative)
0632 {
0633    if (protectNegative && mu < 0.0) {
0634       return std::exp(-2.0 * mu); // make it fall quickly
0635    }
0636 
0637    if (code == 1) {
0638       // Implement integral over x as summation. Add special handling in case
0639       // range boundaries are not on integer values of x
0640       integrandMin = std::max(0., integrandMin);
0641 
0642       if (integrandMax < 0. || integrandMax < integrandMin) {
0643          return 0;
0644       }
0645       const double delta = 100.0 * std::sqrt(mu);
0646       // If the limits are more than many standard deviations away from the mean,
0647       // we might as well return the integral of the full Poisson distribution to
0648       // save computing time.
0649       if (integrandMin < std::max(mu - delta, 0.0) && integrandMax > mu + delta) {
0650          return 1.;
0651       }
0652 
0653       // The range as integers. ixMin is included, ixMax outside.
0654       const unsigned int ixMin = integrandMin;
0655       const unsigned int ixMax = std::min(integrandMax + 1, (double)std::numeric_limits<unsigned int>::max());
0656 
0657       // Sum from 0 to just before the bin outside of the range.
0658       if (ixMin == 0) {
0659          return ROOT::Math::inc_gamma_c(ixMax, mu);
0660       } else {
0661          // If necessary, subtract from 0 to the beginning of the range
0662          if (ixMin <= mu) {
0663             return ROOT::Math::inc_gamma_c(ixMax, mu) - ROOT::Math::inc_gamma_c(ixMin, mu);
0664          } else {
0665             // Avoid catastrophic cancellation in the high tails:
0666             return ROOT::Math::inc_gamma(ixMin, mu) - ROOT::Math::inc_gamma(ixMax, mu);
0667          }
0668       }
0669    }
0670 
0671    // the integral with respect to the mean is the integral of a gamma distribution
0672    // negative ix does not need protection (gamma returns 0.0)
0673    const double ix = 1 + x;
0674 
0675    return ROOT::Math::inc_gamma(ix, integrandMax) - ROOT::Math::inc_gamma(ix, integrandMin);
0676 }
0677 
0678 inline double logNormalIntegral(double xMin, double xMax, double m0, double k)
0679 {
0680    const double root2 = std::sqrt(2.);
0681 
0682    double ln_k = std::abs(std::log(k));
0683    double ret = 0.5 * (std::erf(std::log(xMax / m0) / (root2 * ln_k)) - std::erf(std::log(xMin / m0) / (root2 * ln_k)));
0684 
0685    return ret;
0686 }
0687 
0688 inline double logNormalIntegralStandard(double xMin, double xMax, double mu, double sigma)
0689 {
0690    const double root2 = std::sqrt(2.);
0691 
0692    double ln_k = std::abs(sigma);
0693    double ret =
0694       0.5 * (std::erf((std::log(xMax) - mu) / (root2 * ln_k)) - std::erf((std::log(xMin) - mu) / (root2 * ln_k)));
0695 
0696    return ret;
0697 }
0698 
0699 inline double cbShapeIntegral(double mMin, double mMax, double m0, double sigma, double alpha, double n)
0700 {
0701    const double sqrtPiOver2 = 1.2533141373;
0702    const double sqrt2 = 1.4142135624;
0703 
0704    double result = 0.0;
0705    bool useLog = false;
0706 
0707    if (std::abs(n - 1.0) < 1.0e-05)
0708       useLog = true;
0709 
0710    double sig = std::abs(sigma);
0711 
0712    double tmin = (mMin - m0) / sig;
0713    double tmax = (mMax - m0) / sig;
0714 
0715    if (alpha < 0) {
0716       double tmp = tmin;
0717       tmin = -tmax;
0718       tmax = -tmp;
0719    }
0720 
0721    double absAlpha = std::abs(alpha);
0722 
0723    if (tmin >= -absAlpha) {
0724       result += sig * sqrtPiOver2 * (approxErf(tmax / sqrt2) - approxErf(tmin / sqrt2));
0725    } else if (tmax <= -absAlpha) {
0726       double r = n / absAlpha;
0727       double a = r * std::exp(-0.5 * absAlpha * absAlpha);
0728       double b = r - absAlpha;
0729 
0730       if (useLog) {
0731          double log_b_tmin = std::log(b - tmin);
0732          double log_b_tmax = std::log(b - tmax);
0733          result += a * std::pow(r, n - 1) * sig *
0734                    (log_b_tmin - log_b_tmax + 0.5 * (1.0 - n) * (log_b_tmin * log_b_tmin - log_b_tmax * log_b_tmax));
0735       } else {
0736          result += a * sig / (1.0 - n) * (std::pow(r / (b - tmin), n - 1.0) - std::pow(r / (b - tmax), n - 1.0));
0737       }
0738    } else {
0739       double r = n / absAlpha;
0740       double a = r * std::exp(-0.5 * absAlpha * absAlpha);
0741       double b = r - absAlpha;
0742 
0743       double term1 = 0.0;
0744       if (useLog) {
0745          double log_b_tmin = std::log(b - tmin);
0746          double log_r = std::log(r);
0747          term1 = a * std::pow(r, n - 1) * sig *
0748                  (log_b_tmin - log_r + 0.5 * (1.0 - n) * (log_b_tmin * log_b_tmin - log_r * log_r));
0749       } else {
0750          term1 = a * sig / (1.0 - n) * (std::pow(r / (b - tmin), n - 1.0) - 1.0);
0751       }
0752 
0753       double term2 = sig * sqrtPiOver2 * (approxErf(tmax / sqrt2) - approxErf(-absAlpha / sqrt2));
0754 
0755       result += term1 + term2;
0756    }
0757 
0758    if (result == 0)
0759       return 1.E-300;
0760    return result;
0761 }
0762 
0763 template <typename DoubleArray>
0764 double bernsteinIntegral(double xlo, double xhi, double xmin, double xmax, DoubleArray coefs, int nCoefs)
0765 {
0766    double xloScaled = (xlo - xmin) / (xmax - xmin);
0767    double xhiScaled = (xhi - xmin) / (xmax - xmin);
0768 
0769    int degree = nCoefs - 1; // n+1 polys of degree n
0770    double norm = 0.;
0771 
0772    for (int i = 0; i <= degree; ++i) {
0773       // for each of the i Bernstein basis polynomials
0774       // represent it in the 'power basis' (the naive polynomial basis)
0775       // where the integral is straight forward.
0776       double temp = 0.;
0777       for (int j = i; j <= degree; ++j) { // power basisŧ
0778          double binCoefs = binomial(degree, j) * binomial(j, i);
0779          double oneOverJPlusOne = 1. / (j + 1.);
0780          double powDiff = std::pow(xhiScaled, j + 1.) - std::pow(xloScaled, j + 1.);
0781          temp += std::pow(-1., j - i) * binCoefs * powDiff * oneOverJPlusOne;
0782       }
0783       temp *= coefs[i]; // include coeff
0784       norm += temp;     // add this basis's contribution to total
0785    }
0786 
0787    return norm * (xmax - xmin);
0788 }
0789 
0790 template <typename XArray, typename MuArray, typename CovArray>
0791 double multiVarGaussian(int n, XArray x, MuArray mu, CovArray covI)
0792 {
0793    double result = 0.0;
0794 
0795    // Compute the bilinear form (x-mu)^T * covI * (x-mu)
0796    for (int i = 0; i < n; ++i) {
0797       for (int j = 0; j < n; ++j) {
0798          result += (x[i] - mu[i]) * covI[i * n + j] * (x[j] - mu[j]);
0799       }
0800    }
0801    return std::exp(-0.5 * result);
0802 }
0803 
0804 // Integral of a step function defined by `nBins` intervals, where the
0805 // intervals have values `coefs` and the boundary on the interval `iBin` is
0806 // given by `[boundaries[i], boundaries[i+1])`.
0807 template <typename DoubleArray>
0808 double stepFunctionIntegral(double xmin, double xmax, std::size_t nBins, DoubleArray boundaries, DoubleArray coefs)
0809 {
0810    double out = 0.0;
0811    for (std::size_t i = 0; i < nBins; ++i) {
0812       double a = boundaries[i];
0813       double b = boundaries[i + 1];
0814       out += coefs[i] * std::max(0.0, std::min(b, xmax) - std::max(a, xmin));
0815    }
0816    return out;
0817 }
0818 
0819 } // namespace RooFit::Detail::MathFuncs
0820 
0821 namespace clad::custom_derivatives {
0822 namespace RooFit::Detail::MathFuncs {
0823 
0824 // Clad can't generate the pullback for binNumber because of the
0825 // std::lower_bound usage. But since binNumber returns an integer, and such
0826 // functions have mathematically no derivatives anyway, we just declare a
0827 // custom dummy pullback that does nothing.
0828 
0829 template <class... Types>
0830 void binNumber_pullback(Types...)
0831 {
0832 }
0833 
0834 } // namespace RooFit::Detail::MathFuncs
0835 } // namespace clad::custom_derivatives
0836 
0837 #endif