Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
AnalyticalIntegrals.h
Go to the documentation of this file.
1/*
2 * Project: RooFit
3 * Authors:
4 * Jonas Rembser, CERN 2023
5 * Garima Singh, CERN 2023
6 *
7 * Copyright (c) 2023, CERN
8 *
9 * Redistribution and use in source and binary forms,
10 * with or without modification, are permitted according to the terms
11 * listed in LICENSE (http://roofit.sourceforge.net/license.txt)
12 */
13
14#ifndef RooFit_Detail_AnalyticalIntegrals_h
15#define RooFit_Detail_AnalyticalIntegrals_h
16
17#include <TMath.h>
19
20#include <cmath>
21
22namespace RooFit {
23
24namespace Detail {
25
26namespace AnalyticalIntegrals {
27
28/// @brief Function to calculate the integral of an un-normalized RooGaussian over x. To calculate the integral over
29/// mean, just interchange the respective values of x and mean.
30/// @param xMin Minimum value of variable to integrate wrt.
31/// @param xMax Maximum value of of variable to integrate wrt.
32/// @param mean Mean.
33/// @param sigma Sigma.
34/// @return The integral of an un-normalized RooGaussian over the value in x.
35inline double gaussianIntegral(double xMin, double xMax, double mean, double sigma)
36{
37 // The normalisation constant 1./sqrt(2*pi*sigma^2) is left out in evaluate().
38 // Therefore, the integral is scaled up by that amount to make RooFit normalise
39 // correctly.
40 double resultScale = 0.5 * std::sqrt(TMath::TwoPi()) * sigma;
41
42 // Here everything is scaled and shifted into a standard normal distribution:
43 double xscale = TMath::Sqrt2() * sigma;
44 double scaledMin = 0.;
45 double scaledMax = 0.;
46 scaledMin = (xMin - mean) / xscale;
47 scaledMax = (xMax - mean) / xscale;
48
49 // Here we go for maximum precision: We compute all integrals in the UPPER
50 // tail of the Gaussian, because erfc has the highest precision there.
51 // Therefore, the different cases for range limits in the negative hemisphere are mapped onto
52 // the equivalent points in the upper hemisphere using erfc(-x) = 2. - erfc(x)
53 double ecmin = TMath::Erfc(std::abs(scaledMin));
54 double ecmax = TMath::Erfc(std::abs(scaledMax));
55
56 double cond = 0.0;
57 // Don't put this "prd" inside the "if" because clad will not be able to differentiate the code correctly (as of
58 // v1.1)!
59 double prd = scaledMin * scaledMax;
60 if (prd < 0.0) {
61 cond = 2.0 - (ecmin + ecmax);
62 } else if (scaledMax <= 0.0) {
63 cond = ecmax - ecmin;
64 } else {
65 cond = ecmin - ecmax;
66 }
67 return resultScale * cond;
68}
69
70inline double bifurGaussIntegral(double xMin, double xMax, double mean, double sigmaL, double sigmaR)
71{
72 const double xscaleL = TMath::Sqrt2() * sigmaL;
73 const double xscaleR = TMath::Sqrt2() * sigmaR;
74
75 const double resultScale = 0.5 * std::sqrt(TMath::TwoPi());
76
77 if (xMax < mean) {
78 return resultScale * (sigmaL * (TMath::Erf((xMax - mean) / xscaleL) - TMath::Erf((xMin - mean) / xscaleL)));
79 } else if (xMin > mean) {
80 return resultScale * (sigmaR * (TMath::Erf((xMax - mean) / xscaleR) - TMath::Erf((xMin - mean) / xscaleR)));
81 } else {
82 return resultScale * (sigmaR * TMath::Erf((xMax - mean) / xscaleR) - sigmaL * TMath::Erf((xMin - mean) / xscaleL));
83 }
84}
85
86inline double exponentialIntegral(double xMin, double xMax, double constant)
87{
88 if (constant == 0.0) {
89 return xMax - xMin;
90 }
91
92 return (std::exp(constant * xMax) - std::exp(constant * xMin)) / constant;
93}
94
95/// In pdfMode, a coefficient for the constant term of 1.0 is implied if lowestOrder > 0.
96template <bool pdfMode = false>
97inline double polynomialIntegral(double const *coeffs, int nCoeffs, int lowestOrder, double xMin, double xMax)
98{
99 int denom = lowestOrder + nCoeffs;
100 double min = coeffs[nCoeffs - 1] / double(denom);
101 double max = coeffs[nCoeffs - 1] / double(denom);
102
103 for (int i = nCoeffs - 2; i >= 0; i--) {
104 denom--;
105 min = (coeffs[i] / double(denom)) + xMin * min;
106 max = (coeffs[i] / double(denom)) + xMax * max;
107 }
108
109 max = max * std::pow(xMax, 1 + lowestOrder);
110 min = min * std::pow(xMin, 1 + lowestOrder);
111
112 return max - min + (pdfMode && lowestOrder > 0.0 ? xMax - xMin : 0.0);
113}
114
115/// use fast FMA if available, fall back to normal arithmetic if not
116inline double fast_fma(double x, double y, double z) noexcept
117{
118#if defined(FP_FAST_FMA) // check if std::fma has fast hardware implementation
119 return std::fma(x, y, z);
120#else // defined(FP_FAST_FMA)
121 // std::fma might be slow, so use a more pedestrian implementation
122#if defined(__clang__)
123#pragma STDC FP_CONTRACT ON // hint clang that using an FMA is okay here
124#endif // defined(__clang__)
125 return (x * y) + z;
126#endif // defined(FP_FAST_FMA)
127}
128
129inline double chebychevIntegral(double const *coeffs, unsigned int nCoeffs, double xMin, double xMax, double xMinFull,
130 double xMaxFull)
131{
132 const double halfrange = .5 * (xMax - xMin);
133 const double mid = .5 * (xMax + xMin);
134
135 // the full range of the function is mapped to the normalised [-1, 1] range
136 const double b = (xMaxFull - mid) / halfrange;
137 const double a = (xMinFull - mid) / halfrange;
138
139 // coefficient for integral(T_0(x)) is 1 (implicit), integrate by hand
140 // T_0(x) and T_1(x), and use for n > 1: integral(T_n(x) dx) =
141 // (T_n+1(x) / (n + 1) - T_n-1(x) / (n - 1)) / 2
142 double sum = b - a; // integrate T_0(x) by hand
143
144 const unsigned int iend = nCoeffs;
145 if (iend > 0) {
146 {
147 // integrate T_1(x) by hand...
148 const double c = coeffs[0];
149 sum = fast_fma(0.5 * (b + a) * (b - a), c, sum);
150 }
151 if (1 < iend) {
152 double bcurr = b;
153 double btwox = 2 * b;
154 double blast = 1;
155
156 double acurr = a;
157 double atwox = 2 * a;
158 double alast = 1;
159
160 double newval = atwox * acurr - alast;
161 alast = acurr;
162 acurr = newval;
163
164 newval = btwox * bcurr - blast;
165 blast = bcurr;
166 bcurr = newval;
167 double nminus1 = 1.;
168 for (unsigned int i = 1; iend != i; ++i) {
169 // integrate using recursion relation
170 const double c = coeffs[i];
171 const double term2 = (blast - alast) / nminus1;
172
173 newval = atwox * acurr - alast;
174 alast = acurr;
175 acurr = newval;
176
177 newval = btwox * bcurr - blast;
178 blast = bcurr;
179 bcurr = newval;
180
181 ++nminus1;
182 const double term1 = (bcurr - acurr) / (nminus1 + 1.);
183 const double intTn = 0.5 * (term1 - term2);
184 sum = fast_fma(intTn, c, sum);
185 }
186 }
187 }
188
189 // take care to multiply with the right factor to account for the mapping to
190 // normalised range [-1, 1]
191 return halfrange * sum;
192}
193
194// Clad does not like std::max and std::min so redefined here for simplicity.
195inline double max(double x, double y)
196{
197 return x >= y ? x : y;
198}
199
200inline double min(double x, double y)
201{
202 return x <= y ? x : y;
203}
204
205// The last param should be of type bool but it is not as that causes some issues with Cling for some reason...
206inline double
207poissonIntegral(int code, double mu, double x, double integrandMin, double integrandMax, unsigned int protectNegative)
208{
209 if (protectNegative && mu < 0.0) {
210 return std::exp(-2.0 * mu); // make it fall quickly
211 }
212
213 if (code == 1) {
214 // Implement integral over x as summation. Add special handling in case
215 // range boundaries are not on integer values of x
216 integrandMin = max(0, integrandMin);
217
218 if (integrandMax < 0. || integrandMax < integrandMin) {
219 return 0;
220 }
221 const double delta = 100.0 * std::sqrt(mu);
222 // If the limits are more than many standard deviations away from the mean,
223 // we might as well return the integral of the full Poisson distribution to
224 // save computing time.
225 if (integrandMin < max(mu - delta, 0.0) && integrandMax > mu + delta) {
226 return 1.;
227 }
228
229 // The range as integers. ixMin is included, ixMax outside.
230 const unsigned int ixMin = integrandMin;
231 const unsigned int ixMax = min(integrandMax + 1, (double)std::numeric_limits<unsigned int>::max());
232
233 // Sum from 0 to just before the bin outside of the range.
234 if (ixMin == 0) {
235 return ROOT::Math::gamma_cdf_c(mu, ixMax, 1);
236 } else {
237 // If necessary, subtract from 0 to the beginning of the range
238 if (ixMin <= mu) {
239 return ROOT::Math::gamma_cdf_c(mu, ixMax, 1) - ROOT::Math::gamma_cdf_c(mu, ixMin, 1);
240 } else {
241 // Avoid catastrophic cancellation in the high tails:
242 return ROOT::Math::gamma_cdf(mu, ixMin, 1) - ROOT::Math::gamma_cdf(mu, ixMax, 1);
243 }
244 }
245 }
246
247 // the integral with respect to the mean is the integral of a gamma distribution
248 // negative ix does not need protection (gamma returns 0.0)
249 const double ix = 1 + x;
250
251 return ROOT::Math::gamma_cdf(integrandMax, ix, 1.0) - ROOT::Math::gamma_cdf(integrandMin, ix, 1.0);
252}
253
254inline double logNormalIntegral(double xMin, double xMax, double m0, double k)
255{
256 const double root2 = std::sqrt(2.);
257
258 double ln_k = TMath::Abs(std::log(k));
259 double ret =
260 0.5 * (TMath::Erf(std::log(xMax / m0) / (root2 * ln_k)) - TMath::Erf(std::log(xMin / m0) / (root2 * ln_k)));
261
262 return ret;
263}
264
265inline double logNormalIntegralStandard(double xMin, double xMax, double mu, double sigma)
266{
267 const double root2 = std::sqrt(2.);
268
269 double ln_k = std::abs(sigma);
270 double ret =
271 0.5 * (TMath::Erf((std::log(xMax) - mu) / (root2 * ln_k)) - TMath::Erf((std::log(xMin) - mu) / (root2 * ln_k)));
272
273 return ret;
274}
275
276} // namespace AnalyticalIntegrals
277
278} // namespace Detail
279
280} // namespace RooFit
281
282#endif
#define b(i)
Definition RSha256.hxx:100
#define c(i)
Definition RSha256.hxx:101
#define a(i)
Definition RSha256.hxx:99
double gamma_cdf_c(double x, double alpha, double theta, double x0=0)
Complement of the cumulative distribution function of the gamma distribution (upper tail).
double gamma_cdf(double x, double alpha, double theta, double x0=0)
Cumulative distribution function of the gamma distribution (lower tail).
const Double_t sigma
Double_t y[n]
Definition legend1.C:17
Double_t x[n]
Definition legend1.C:17
double logNormalIntegral(double xMin, double xMax, double m0, double k)
double chebychevIntegral(double const *coeffs, unsigned int nCoeffs, double xMin, double xMax, double xMinFull, double xMaxFull)
double poissonIntegral(int code, double mu, double x, double integrandMin, double integrandMax, unsigned int protectNegative)
double fast_fma(double x, double y, double z) noexcept
use fast FMA if available, fall back to normal arithmetic if not
double gaussianIntegral(double xMin, double xMax, double mean, double sigma)
Function to calculate the integral of an un-normalized RooGaussian over x.
double bifurGaussIntegral(double xMin, double xMax, double mean, double sigmaL, double sigmaR)
double exponentialIntegral(double xMin, double xMax, double constant)
double logNormalIntegralStandard(double xMin, double xMax, double mu, double sigma)
double polynomialIntegral(double const *coeffs, int nCoeffs, int lowestOrder, double xMin, double xMax)
In pdfMode, a coefficient for the constant term of 1.0 is implied if lowestOrder > 0.
The namespace RooFit contains mostly switches that change the behaviour of functions of PDFs (or othe...
Definition JSONIO.h:26
Double_t Erf(Double_t x)
Computation of the error function erf(x).
Definition TMath.cxx:190
constexpr Double_t Sqrt2()
Definition TMath.h:86
Double_t Erfc(Double_t x)
Computes the complementary error function erfc(x).
Definition TMath.cxx:199
Short_t Abs(Short_t d)
Returns the absolute value of parameter Short_t d.
Definition TMathBase.h:123
constexpr Double_t TwoPi()
Definition TMath.h:44
static uint64_t sum(uint64_t i)
Definition Factory.cxx:2345