Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-16 09:21:00

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