Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-18 09:31:47

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