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 = 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 return resultScale * 0.5 * cond;
67}
68
69inline double exponentialIntegral(double xMin, double xMax, double constant)
70{
71 if (constant == 0.0) {
72 return xMax - xMin;
73 }
74
75 return (std::exp(constant * xMax) - std::exp(constant * xMin)) / constant;
76}
77
78/// In pdfMode, a coefficient for the constant term of 1.0 is implied if lowestOrder > 0.
79template <bool pdfMode = false>
80inline double polynomialIntegral(double const *coeffs, int nCoeffs, int lowestOrder, double xMin, double xMax)
81{
82 int denom = lowestOrder + nCoeffs;
83 double min = coeffs[nCoeffs - 1] / double(denom);
84 double max = coeffs[nCoeffs - 1] / double(denom);
85
86 for (int i = nCoeffs - 2; i >= 0; i--) {
87 denom--;
88 min = (coeffs[i] / double(denom)) + xMin * min;
89 max = (coeffs[i] / double(denom)) + xMax * max;
90 }
91
92 max = max * std::pow(xMax, 1 + lowestOrder);
93 min = min * std::pow(xMin, 1 + lowestOrder);
94
95 return max - min + (pdfMode && lowestOrder > 0.0 ? xMax - xMin : 0.0);
96}
97
98/// use fast FMA if available, fall back to normal arithmetic if not
99inline double fast_fma(double x, double y, double z) noexcept
100{
101#if defined(FP_FAST_FMA) // check if std::fma has fast hardware implementation
102 return std::fma(x, y, z);
103#else // defined(FP_FAST_FMA)
104 // std::fma might be slow, so use a more pedestrian implementation
105#if defined(__clang__)
106#pragma STDC FP_CONTRACT ON // hint clang that using an FMA is okay here
107#endif // defined(__clang__)
108 return (x * y) + z;
109#endif // defined(FP_FAST_FMA)
110}
111
112inline double chebychevIntegral(double const *coeffs, unsigned int nCoeffs, double xMin, double xMax, double xMinFull,
113 double xMaxFull)
114{
115 const double halfrange = .5 * (xMax - xMin);
116 const double mid = .5 * (xMax + xMin);
117
118 // the full range of the function is mapped to the normalised [-1, 1] range
119 const double b = (xMaxFull - mid) / halfrange;
120 const double a = (xMinFull - mid) / halfrange;
121
122 // coefficient for integral(T_0(x)) is 1 (implicit), integrate by hand
123 // T_0(x) and T_1(x), and use for n > 1: integral(T_n(x) dx) =
124 // (T_n+1(x) / (n + 1) - T_n-1(x) / (n - 1)) / 2
125 double sum = b - a; // integrate T_0(x) by hand
126
127 const unsigned int iend = nCoeffs;
128 if (iend > 0) {
129 {
130 // integrate T_1(x) by hand...
131 const double c = coeffs[0];
132 sum = fast_fma(0.5 * (b + a) * (b - a), c, sum);
133 }
134 if (1 < iend) {
135 double bcurr = b;
136 double btwox = 2 * b;
137 double blast = 1;
138
139 double acurr = a;
140 double atwox = 2 * a;
141 double alast = 1;
142
143 double newval = atwox * acurr - alast;
144 alast = acurr;
145 acurr = newval;
146
147 newval = btwox * bcurr - blast;
148 blast = bcurr;
149 bcurr = newval;
150 double nminus1 = 1.;
151 for (unsigned int i = 1; iend != i; ++i) {
152 // integrate using recursion relation
153 const double c = coeffs[i];
154 const double term2 = (blast - alast) / nminus1;
155
156 newval = atwox * acurr - alast;
157 alast = acurr;
158 acurr = newval;
159
160 newval = btwox * bcurr - blast;
161 blast = bcurr;
162 bcurr = newval;
163
164 ++nminus1;
165 const double term1 = (bcurr - acurr) / (nminus1 + 1.);
166 const double intTn = 0.5 * (term1 - term2);
167 sum = fast_fma(intTn, c, sum);
168 }
169 }
170 }
171
172 // take care to multiply with the right factor to account for the mapping to
173 // normalised range [-1, 1]
174 return halfrange * sum;
175}
176
177// Clad does not like std::max and std::min so redefined here for simplicity.
178inline double max(double x, double y)
179{
180 return x >= y ? x : y;
181}
182
183inline double min(double x, double y)
184{
185 return x <= y ? x : y;
186}
187
188// The last param should be of type bool but it is not as that causes some issues with Cling for some reason...
189inline double
190poissonIntegral(int code, double mu, double x, double integrandMin, double integrandMax, unsigned int protectNegative)
191{
192 if (protectNegative && mu < 0.0) {
193 return std::exp(-2.0 * mu); // make it fall quickly
194 }
195
196 if (code == 1) {
197 // Implement integral over x as summation. Add special handling in case
198 // range boundaries are not on integer values of x
199 integrandMin = max(0, integrandMin);
200
201 if (integrandMax < 0. || integrandMax < integrandMin) {
202 return 0;
203 }
204 const double delta = 100.0 * std::sqrt(mu);
205 // If the limits are more than many standard deviations away from the mean,
206 // we might as well return the integral of the full Poisson distribution to
207 // save computing time.
208 if (integrandMin < max(mu - delta, 0.0) && integrandMax > mu + delta) {
209 return 1.;
210 }
211
212 // The range as integers. ixMin is included, ixMax outside.
213 const unsigned int ixMin = integrandMin;
214 const unsigned int ixMax = min(integrandMax + 1, (double)std::numeric_limits<unsigned int>::max());
215
216 // Sum from 0 to just before the bin outside of the range.
217 if (ixMin == 0) {
218 return ROOT::Math::gamma_cdf_c(mu, ixMax, 1);
219 } else {
220 // If necessary, subtract from 0 to the beginning of the range
221 if (ixMin <= mu) {
222 return ROOT::Math::gamma_cdf_c(mu, ixMax, 1) - ROOT::Math::gamma_cdf_c(mu, ixMin, 1);
223 } else {
224 // Avoid catastrophic cancellation in the high tails:
225 return ROOT::Math::gamma_cdf(mu, ixMin, 1) - ROOT::Math::gamma_cdf(mu, ixMax, 1);
226 }
227 }
228 }
229
230 // the integral with respect to the mean is the integral of a gamma distribution
231 // negative ix does not need protection (gamma returns 0.0)
232 const double ix = 1 + x;
233
234 return ROOT::Math::gamma_cdf(integrandMax, ix, 1.0) - ROOT::Math::gamma_cdf(integrandMin, ix, 1.0);
235}
236
237inline double logNormalIntegral(double xMin, double xMax, double m0, double k)
238{
239 const double root2 = std::sqrt(2.);
240
241 double ln_k = TMath::Abs(std::log(k));
242 double ret =
243 0.5 * (TMath::Erf(std::log(xMax / m0) / (root2 * ln_k)) - TMath::Erf(std::log(xMin / m0) / (root2 * ln_k)));
244
245 return ret;
246}
247
248inline double logNormalIntegralStandard(double xMin, double xMax, double mu, double sigma)
249{
250 const double root2 = std::sqrt(2.);
251
252 double ln_k = std::abs(sigma);
253 double ret =
254 0.5 * (TMath::Erf((std::log(xMax) - mu) / (root2 * ln_k)) - TMath::Erf((std::log(xMin) - mu) / (root2 * ln_k)));
255
256 return ret;
257}
258
259} // namespace AnalyticalIntegrals
260
261} // namespace Detail
262
263} // namespace RooFit
264
265#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 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