Analysis-Utilities 26.9.9
C++/ROOT utilities for nuclear measurement data analysis
Loading...
Searching...
No Matches
RooFitPhotopeakPdfs.cpp
Go to the documentation of this file.
2
3#include <RooArgList.h>
4#include <RooArgSet.h>
5#include <RooRealVar.h>
6#include <cmath>
7
8#ifdef AU_ROOFIT_BACKEND_CUDA
10#include <cuda_runtime.h>
11
12namespace {
13bool IsDevicePointer(const void *ptr) {
14 cudaPointerAttributes attrs;
15 cudaError_t err = cudaPointerGetAttributes(&attrs, ptr);
16 if (err != cudaSuccess) {
17 // Clear the error state — querying a host pointer is not a hard failure.
18 cudaGetLastError();
19 return false;
20 }
21 return attrs.type == cudaMemoryTypeDevice ||
22 attrs.type == cudaMemoryTypeManaged;
23}
24} // namespace
25#endif
26
27ClassImp(RooStepShelf);
28ClassImp(RooLowExpTail);
29ClassImp(RooLowLinTail);
30ClassImp(RooHighExpTail);
31
32namespace {
33
34Double_t SoftPlusNeg(Double_t z) {
35 if (z > 0)
36 return std::log1p(std::exp(-z));
37 return -z + std::log1p(std::exp(z));
38}
39
40Double_t SigmoidNeg(Double_t z) {
41 if (z < 0)
42 return 1.0 / (1.0 + std::exp(z));
43 return std::exp(-z) / (1.0 + std::exp(-z));
44}
45
46Double_t StepAntideriv(Double_t z) { return -SoftPlusNeg(z) + SigmoidNeg(z); }
47
48// exp(a) * erfc(b) computed without intermediate overflow. In the exponential
49// tails a = y/tau and b = y/(sqrt(2)*sigma) always share the sign of y, and
50// b = a * tau/(sqrt(2)*sigma) = a * ratio/sqrt(2) with ratio >= 1, so whenever
51// exp(a) would overflow (a large positive) b is also large positive and
52// erfc(b) underflows -- the mathematical product tends to 0. Fold the two
53// exponentials together and use the large-argument expansion of the scaled
54// complementary error function erfcx(b) = exp(b*b)*erfc(b) ~
55// 1/(b*sqrt(pi)) * (1 - 1/(2 b^2) + 3/(4 b^4) - 15/(8 b^6)).
56// The direct branch (a <= 700) keeps exp(a) finite (exp(700) ~ 1e304).
57Double_t ExpErfc(Double_t a, Double_t b) {
58 if (a <= 700.0)
59 return std::exp(a) * std::erfc(b);
60 Double_t t = 1.0 / (b * b);
61 Double_t erfcx = (1.0 / (b * std::sqrt(M_PI))) *
62 (1.0 - 0.5 * t + 0.75 * t * t - 1.875 * t * t * t);
63 return std::exp(a - b * b) * erfcx;
64}
65
66// Smallest density any component pdf is allowed to return. A pdf that evaluates
67// to exactly 0 across the fit range integrates to 0, and RooFit's extended
68// Range() projection then forms a 0/0 ratio (RooRatio::rangeProj) -> NaN. A
69// tiny positive floor keeps every component's normalized integral well-defined
70// while being utterly negligible wherever the real density is non-zero.
71const Double_t kDensityFloor = 1e-300;
72
73Double_t ExpTailDensity(Double_t y, Double_t sigma, Double_t tau) {
74 Double_t sqrt2_sigma = std::sqrt(2.0) * sigma;
75 Double_t v = ExpErfc(y / tau, y / sqrt2_sigma);
76 return v > kDensityFloor ? v : kDensityFloor;
77}
78
79Double_t ExpTailAntideriv(Double_t y, Double_t sigma, Double_t tau) {
80 Double_t sqrt2_sigma = std::sqrt(2.0) * sigma;
81 Double_t offset = sigma * sigma / tau;
82 Double_t gauss_corr = std::exp(sigma * sigma / (2.0 * tau * tau));
83 return tau * (ExpErfc(y / tau, y / sqrt2_sigma) +
84 gauss_corr * std::erf((y - offset) / sqrt2_sigma));
85}
86
87Double_t LowLinDensity(Double_t y, Double_t sigma, Double_t slope) {
88 Double_t lin = 1.0 + slope * y;
89 if (lin < 0.0)
90 lin = 0.0;
91 Double_t sqrt2_sigma = std::sqrt(2.0) * sigma;
92 Double_t v = lin * std::erfc(y / sqrt2_sigma);
93 return v > kDensityFloor ? v : kDensityFloor;
94}
95
96Double_t LowLinAntideriv(Double_t y, Double_t sigma, Double_t slope) {
97 Double_t sqrt2_sigma = std::sqrt(2.0) * sigma;
98 Double_t beta = 1.0 / sqrt2_sigma;
99 Double_t by = beta * y;
100 Double_t erfc_by = std::erfc(by);
101 Double_t erf_by = std::erf(by);
102 Double_t gauss = std::exp(-(by * by));
103 Double_t inv_beta_sqrt_pi = 1.0 / (beta * std::sqrt(M_PI));
104 Double_t flat = y * erfc_by - gauss * inv_beta_sqrt_pi;
105 Double_t lin = 0.5 * y * y * erfc_by - y * gauss * inv_beta_sqrt_pi / 2.0 +
106 erf_by / (4.0 * beta * beta);
107 return flat + slope * lin;
108}
109
110Double_t LowLinIntegral(Double_t y_lo, Double_t y_hi, Double_t sigma,
111 Double_t slope) {
112 Double_t eff_lo = y_lo;
113 Double_t eff_hi = y_hi;
114 if (slope > 1e-10) {
115 Double_t threshold = -1.0 / slope;
116 if (threshold > eff_lo)
117 eff_lo = threshold;
118 if (eff_lo >= eff_hi)
119 return 1e-300;
120 } else if (slope < -1e-10) {
121 Double_t threshold = -1.0 / slope;
122 if (threshold < eff_hi)
123 eff_hi = threshold;
124 if (eff_lo >= eff_hi)
125 return 1e-300;
126 }
127 Double_t val = LowLinAntideriv(eff_hi, sigma, slope) -
128 LowLinAntideriv(eff_lo, sigma, slope);
129 if (!std::isfinite(val) || val < 1e-300)
130 return 1e-300;
131 return val;
132}
133
134} // namespace
135
136RooStepShelf::RooStepShelf(const char *name, const char *title, RooAbsReal &x,
137 RooAbsReal &mu, RooAbsReal &sigma)
138 : RooAbsPdf(name, title), x_("x", "x", this, x), mu_("mu", "mu", this, mu),
139 sigma_("sigma", "sigma", this, sigma) {}
140
141RooStepShelf::RooStepShelf(const RooStepShelf &other, const char *name)
142 : RooAbsPdf(other, name), x_("x", this, other.x_),
143 mu_("mu", this, other.mu_), sigma_("sigma", this, other.sigma_) {}
144
145Double_t RooStepShelf::evaluate() const {
146 Double_t sigma = (Double_t)sigma_;
147 if (sigma <= 0)
148 return kDensityFloor;
149 Double_t z = ((Double_t)x_ - (Double_t)mu_) / sigma;
150 Double_t s = SigmoidNeg(z);
151 Double_t v = s * s;
152 return v > kDensityFloor ? v : kDensityFloor;
153}
154
155void RooStepShelf::doEval(RooFit::EvalContext &ctx) const {
156 std::span<const double> x_vals = ctx.at(x_);
157 std::span<double> output = ctx.output();
158 size_t n = output.size();
159 Double_t sigma = ctx.at(sigma_)[0];
160 Double_t mu = ctx.at(mu_)[0];
161 if (sigma <= 0) {
162 for (size_t i = 0; i < n; ++i)
163 output[i] = kDensityFloor;
164 return;
165 }
166 Double_t inv_sigma = 1.0 / sigma;
167#ifdef AU_ROOFIT_BACKEND_CUDA
168 if (IsDevicePointer(output.data())) {
169 RooStepShelf_launchKernel(output.data(), x_vals.data(), mu, inv_sigma, n);
170 return;
171 }
172#endif
173#pragma omp parallel for simd
174 for (size_t i = 0; i < n; ++i) {
175 Double_t z = (x_vals[i] - mu) * inv_sigma;
176 Double_t s = SigmoidNeg(z);
177 Double_t v = s * s;
178 output[i] = v > kDensityFloor ? v : kDensityFloor;
179 }
180}
181
182Int_t RooStepShelf::getAnalyticalIntegral(RooArgSet &allVars,
183 RooArgSet &analVars,
184 const char * /*rangeName*/) const {
185 if (matchArgs(allVars, analVars, x_))
186 return 1;
187 return 0;
188}
189
191 const char *rangeName) const {
192 if (code != 1)
193 return 1e-300;
194 Double_t sigma = (Double_t)sigma_;
195 if (sigma <= 0)
196 return 1e-300;
197 Double_t mu = (Double_t)mu_;
198 Double_t x_lo = x_.min(rangeName);
199 Double_t x_hi = x_.max(rangeName);
200 Double_t z_lo = (x_lo - mu) / sigma;
201 Double_t z_hi = (x_hi - mu) / sigma;
202 Double_t val = sigma * (StepAntideriv(z_hi) - StepAntideriv(z_lo));
203 if (!std::isfinite(val) || val < 1e-300)
204 return 1e-300;
205 return val;
206}
207
208RooLowExpTail::RooLowExpTail(const char *name, const char *title, RooAbsReal &x,
209 RooAbsReal &mu, RooAbsReal &sigma,
210 RooAbsReal &tau_ratio)
211 : RooAbsPdf(name, title), x_("x", "x", this, x), mu_("mu", "mu", this, mu),
212 sigma_("sigma", "sigma", this, sigma),
213 tau_ratio_("tau_ratio", "tau_ratio", this, tau_ratio) {}
214
215RooLowExpTail::RooLowExpTail(const RooLowExpTail &other, const char *name)
216 : RooAbsPdf(other, name), x_("x", this, other.x_),
217 mu_("mu", this, other.mu_), sigma_("sigma", this, other.sigma_),
218 tau_ratio_("tau_ratio", this, other.tau_ratio_) {}
219
220Double_t RooLowExpTail::evaluate() const {
221 Double_t sigma = (Double_t)sigma_;
222 Double_t tau_ratio = (Double_t)tau_ratio_;
223 if (sigma <= 0 || tau_ratio <= 0)
224 return 0.0;
225 Double_t tau = tau_ratio * sigma;
226 Double_t y = (Double_t)x_ - (Double_t)mu_;
227 return ExpTailDensity(y, sigma, tau);
228}
229
230void RooLowExpTail::doEval(RooFit::EvalContext &ctx) const {
231 std::span<const double> x_vals = ctx.at(x_);
232 std::span<double> output = ctx.output();
233 size_t n = output.size();
234 Double_t sigma = ctx.at(sigma_)[0];
235 Double_t mu = ctx.at(mu_)[0];
236 Double_t tau_ratio = ctx.at(tau_ratio_)[0];
237 if (sigma <= 0 || tau_ratio <= 0) {
238 for (size_t i = 0; i < n; ++i)
239 output[i] = kDensityFloor;
240 return;
241 }
242 Double_t tau = tau_ratio * sigma;
243 Double_t inv_tau = 1.0 / tau;
244 Double_t inv_sqrt2_sigma = 1.0 / (std::sqrt(2.0) * sigma);
245#ifdef AU_ROOFIT_BACKEND_CUDA
246 if (IsDevicePointer(output.data())) {
247 RooLowExpTail_launchKernel(output.data(), x_vals.data(), mu, inv_tau,
248 inv_sqrt2_sigma, n);
249 return;
250 }
251#endif
252#pragma omp parallel for simd
253 for (size_t i = 0; i < n; ++i) {
254 Double_t y = x_vals[i] - mu;
255 Double_t v = ExpErfc(y * inv_tau, y * inv_sqrt2_sigma);
256 output[i] = v > kDensityFloor ? v : kDensityFloor;
257 }
258}
259
261 RooArgSet &analVars,
262 const char * /*rangeName*/) const {
263 if (matchArgs(allVars, analVars, x_))
264 return 1;
265 return 0;
266}
267
269 const char *rangeName) const {
270 if (code != 1)
271 return 1e-300;
272 Double_t sigma = (Double_t)sigma_;
273 Double_t tau_ratio = (Double_t)tau_ratio_;
274 if (sigma <= 0 || tau_ratio <= 0)
275 return 1e-300;
276 Double_t tau = tau_ratio * sigma;
277 Double_t mu = (Double_t)mu_;
278 Double_t x_lo = x_.min(rangeName);
279 Double_t x_hi = x_.max(rangeName);
280 Double_t y_lo = x_lo - mu;
281 Double_t y_hi = x_hi - mu;
282 Double_t val =
283 ExpTailAntideriv(y_hi, sigma, tau) - ExpTailAntideriv(y_lo, sigma, tau);
284 if (!std::isfinite(val) || val < 1e-300)
285 return 1e-300;
286 return val;
287}
288
289RooLowLinTail::RooLowLinTail(const char *name, const char *title, RooAbsReal &x,
290 RooAbsReal &mu, RooAbsReal &sigma,
291 RooAbsReal &slope)
292 : RooAbsPdf(name, title), x_("x", "x", this, x), mu_("mu", "mu", this, mu),
293 sigma_("sigma", "sigma", this, sigma),
294 slope_("slope", "slope", this, slope) {}
295
296RooLowLinTail::RooLowLinTail(const RooLowLinTail &other, const char *name)
297 : RooAbsPdf(other, name), x_("x", this, other.x_),
298 mu_("mu", this, other.mu_), sigma_("sigma", this, other.sigma_),
299 slope_("slope", this, other.slope_) {}
300
301Double_t RooLowLinTail::evaluate() const {
302 Double_t sigma = (Double_t)sigma_;
303 if (sigma <= 0)
304 return 0.0;
305 Double_t y = (Double_t)x_ - (Double_t)mu_;
306 Double_t slope = (Double_t)slope_;
307 return LowLinDensity(y, sigma, slope);
308}
309
310void RooLowLinTail::doEval(RooFit::EvalContext &ctx) const {
311 std::span<const double> x_vals = ctx.at(x_);
312 std::span<double> output = ctx.output();
313 size_t n = output.size();
314 Double_t sigma = ctx.at(sigma_)[0];
315 Double_t mu = ctx.at(mu_)[0];
316 Double_t slope = ctx.at(slope_)[0];
317 if (sigma <= 0) {
318 for (size_t i = 0; i < n; ++i)
319 output[i] = kDensityFloor;
320 return;
321 }
322 Double_t inv_sqrt2_sigma = 1.0 / (std::sqrt(2.0) * sigma);
323#ifdef AU_ROOFIT_BACKEND_CUDA
324 if (IsDevicePointer(output.data())) {
325 RooLowLinTail_launchKernel(output.data(), x_vals.data(), mu, slope,
326 inv_sqrt2_sigma, n);
327 return;
328 }
329#endif
330#pragma omp parallel for simd
331 for (size_t i = 0; i < n; ++i) {
332 Double_t y = x_vals[i] - mu;
333 Double_t lin = 1.0 + slope * y;
334 if (lin < 0.0)
335 lin = 0.0;
336 Double_t v = lin * std::erfc(y * inv_sqrt2_sigma);
337 output[i] = v > kDensityFloor ? v : kDensityFloor;
338 }
339}
340
342 RooArgSet &analVars,
343 const char * /*rangeName*/) const {
344 if (matchArgs(allVars, analVars, x_))
345 return 1;
346 return 0;
347}
348
350 const char *rangeName) const {
351 if (code != 1)
352 return 0.0;
353 Double_t sigma = (Double_t)sigma_;
354 if (sigma <= 0)
355 return 0.0;
356 Double_t mu = (Double_t)mu_;
357 Double_t slope = (Double_t)slope_;
358 Double_t x_lo = x_.min(rangeName);
359 Double_t x_hi = x_.max(rangeName);
360 Double_t y_lo = x_lo - mu;
361 Double_t y_hi = x_hi - mu;
362 return LowLinIntegral(y_lo, y_hi, sigma, slope);
363}
364
365RooHighExpTail::RooHighExpTail(const char *name, const char *title,
366 RooAbsReal &x, RooAbsReal &mu, RooAbsReal &sigma,
367 RooAbsReal &tau_ratio)
368 : RooAbsPdf(name, title), x_("x", "x", this, x), mu_("mu", "mu", this, mu),
369 sigma_("sigma", "sigma", this, sigma),
370 tau_ratio_("tau_ratio", "tau_ratio", this, tau_ratio) {}
371
372RooHighExpTail::RooHighExpTail(const RooHighExpTail &other, const char *name)
373 : RooAbsPdf(other, name), x_("x", this, other.x_),
374 mu_("mu", this, other.mu_), sigma_("sigma", this, other.sigma_),
375 tau_ratio_("tau_ratio", this, other.tau_ratio_) {}
376
377Double_t RooHighExpTail::evaluate() const {
378 Double_t sigma = (Double_t)sigma_;
379 Double_t tau_ratio = (Double_t)tau_ratio_;
380 if (sigma <= 0 || tau_ratio <= 0)
381 return 0.0;
382 Double_t tau = tau_ratio * sigma;
383 Double_t z = (Double_t)mu_ - (Double_t)x_;
384 return ExpTailDensity(z, sigma, tau);
385}
386
387void RooHighExpTail::doEval(RooFit::EvalContext &ctx) const {
388 std::span<const double> x_vals = ctx.at(x_);
389 std::span<double> output = ctx.output();
390 size_t n = output.size();
391 Double_t sigma = ctx.at(sigma_)[0];
392 Double_t mu = ctx.at(mu_)[0];
393 Double_t tau_ratio = ctx.at(tau_ratio_)[0];
394 if (sigma <= 0 || tau_ratio <= 0) {
395 for (size_t i = 0; i < n; ++i)
396 output[i] = kDensityFloor;
397 return;
398 }
399 Double_t tau = tau_ratio * sigma;
400 Double_t inv_tau = 1.0 / tau;
401 Double_t inv_sqrt2_sigma = 1.0 / (std::sqrt(2.0) * sigma);
402#ifdef AU_ROOFIT_BACKEND_CUDA
403 if (IsDevicePointer(output.data())) {
404 RooHighExpTail_launchKernel(output.data(), x_vals.data(), mu, inv_tau,
405 inv_sqrt2_sigma, n);
406 return;
407 }
408#endif
409#pragma omp parallel for simd
410 for (size_t i = 0; i < n; ++i) {
411 Double_t z = mu - x_vals[i];
412 Double_t v = ExpErfc(z * inv_tau, z * inv_sqrt2_sigma);
413 output[i] = v > kDensityFloor ? v : kDensityFloor;
414 }
415}
416
418 RooArgSet &analVars,
419 const char * /*rangeName*/) const {
420 if (matchArgs(allVars, analVars, x_))
421 return 1;
422 return 0;
423}
424
426 const char *rangeName) const {
427 if (code != 1)
428 return 1e-300;
429 Double_t sigma = (Double_t)sigma_;
430 Double_t tau_ratio = (Double_t)tau_ratio_;
431 if (sigma <= 0 || tau_ratio <= 0)
432 return 1e-300;
433 Double_t tau = tau_ratio * sigma;
434 Double_t mu = (Double_t)mu_;
435 Double_t x_lo = x_.min(rangeName);
436 Double_t x_hi = x_.max(rangeName);
437 Double_t z_lo = mu - x_lo;
438 Double_t z_hi = mu - x_hi;
439 Double_t val =
440 ExpTailAntideriv(z_lo, sigma, tau) - ExpTailAntideriv(z_hi, sigma, tau);
441 if (!std::isfinite(val) || val < 1e-300)
442 return 1e-300;
443 return val;
444}
Host-callable launch wrappers for the photopeak PDF CUDA kernels.
void RooLowLinTail_launchKernel(double *output, const double *x_vals, double mu, double slope, double inv_sqrt2_sigma, size_t n)
Launch the low-energy linear tail kernel.
void RooHighExpTail_launchKernel(double *output, const double *x_vals, double mu, double inv_tau, double inv_sqrt2_sigma, size_t n)
Launch the high-energy exponential tail kernel.
void RooLowExpTail_launchKernel(double *output, const double *x_vals, double mu, double inv_tau, double inv_sqrt2_sigma, size_t n)
Launch the low-energy exponential tail kernel.
void RooStepShelf_launchKernel(double *output, const double *x_vals, double mu, double inv_sigma, size_t n)
Launch the resolution-smeared step-shelf kernel.
Custom RooFit PDF components for gamma-ray photopeak fitting.
#define M_PI
Exponential tail above the photopeak, convolved with the resolution.
Int_t getAnalyticalIntegral(RooArgSet &allVars, RooArgSet &analVars, const char *rangeName=nullptr) const override
Advertise the analytic integral over the observable.
RooHighExpTail()
Default constructor for ROOT I/O; leaves proxies unbound.
Double_t evaluate() const override
Scalar evaluation for a single observable value.
Double_t analyticalIntegral(Int_t code, const char *rangeName=nullptr) const override
Evaluate the analytic integral advertised above.
void doEval(RooFit::EvalContext &ctx) const override
Batched evaluation over every event in one pass.
Exponential tail below the photopeak, convolved with the resolution.
Double_t evaluate() const override
Scalar evaluation for a single observable value.
RooLowExpTail()
Default constructor for ROOT I/O; leaves proxies unbound.
Int_t getAnalyticalIntegral(RooArgSet &allVars, RooArgSet &analVars, const char *rangeName=nullptr) const override
Advertise the analytic integral over the observable.
Double_t analyticalIntegral(Int_t code, const char *rangeName=nullptr) const override
Evaluate the analytic integral advertised above.
void doEval(RooFit::EvalContext &ctx) const override
Batched evaluation over every event in one pass.
Linear tail below the photopeak, convolved with the resolution.
Double_t analyticalIntegral(Int_t code, const char *rangeName=nullptr) const override
Evaluate the analytic integral advertised above.
RooLowLinTail()
Default constructor for ROOT I/O; leaves proxies unbound.
void doEval(RooFit::EvalContext &ctx) const override
Batched evaluation over every event in one pass.
Int_t getAnalyticalIntegral(RooArgSet &allVars, RooArgSet &analVars, const char *rangeName=nullptr) const override
Advertise the analytic integral over the observable.
Double_t evaluate() const override
Scalar evaluation for a single observable value.
Resolution-smeared step on the low side of the photopeak.
Double_t analyticalIntegral(Int_t code, const char *rangeName=nullptr) const override
Evaluate the analytic integral advertised above.
void doEval(RooFit::EvalContext &ctx) const override
Batched evaluation over every event in one pass.
Int_t getAnalyticalIntegral(RooArgSet &allVars, RooArgSet &analVars, const char *rangeName=nullptr) const override
Advertise the analytic integral over the observable.
RooStepShelf()
Default constructor for ROOT I/O; leaves proxies unbound.
Double_t evaluate() const override
Scalar evaluation for a single observable value.