Logo ROOT   6.30.04
Reference Guide
 All Namespaces Files Pages
RooHypatia2.cxx
Go to the documentation of this file.
1 // Author: Stephan Hageboeck, CERN, Oct 2019
2 // Based on RooIpatia2 by Diego Martinez Santos, Nikhef, Diego.Martinez.Santos@cern.ch
3 /*****************************************************************************
4  * Project: RooFit *
5  * Authors: *
6  * WV, Wouter Verkerke, UC Santa Barbara, verkerke@slac.stanford.edu *
7  * DK, David Kirkby, UC Irvine, dkirkby@uci.edu *
8  * *
9  * Copyright (c) 2000-2019, Regents of the University of California *
10  * and Stanford University. All rights reserved. *
11  * *
12  * Redistribution and use in source and binary forms, *
13  * with or without modification, are permitted according to the terms *
14  * listed in LICENSE (http://roofit.sourceforge.net/license.txt) *
15  *****************************************************************************/
16 
17 /**
18  * \class RooHypatia2
19  *
20  * RooHypatia2 is the two-sided version of the Hypatia distribution described in https://arxiv.org/abs/1312.5000.
21  * \image html RooHypatia2.png
22  *
23  * It has a hyperbolic core of a crystal-ball-like function \f$ G \f$ and two tails:
24  * \f[
25  * \mathrm{Hypatia2}(x, \mu, \sigma, \lambda, \zeta, \beta, a_l, n_l, a_r, n_r) =
26  * \begin{cases}
27  * \frac{ G(\mu - a_l \sigma, \mu, \sigma, \lambda, \zeta, \beta) }
28  * { \left( 1 - \frac{x}{n_l G(\ldots)/G'(\ldots) - a_l\sigma } \right)^{n_l} }
29  * & \text{if } \frac{x-\mu}{\sigma} < -a_l \\
30  * \left( (x-\mu)^2 + A^2_\lambda(\zeta)\sigma^2 \right)^{\frac{1}{2}\lambda-\frac{1}{4}} e^{\beta(x-\mu)} K_{\lambda-\frac{1}{2}}
31  * \left( \zeta \sqrt{1+\left( \frac{x-\mu}{A_\lambda(\zeta)\sigma} \right)^2 } \right) \equiv G(x, \mu, \ldots)
32  * & \text{otherwise} \\
33  * \frac{ G(\mu + a_r \sigma, \mu, \sigma, \lambda, \zeta, \beta) }
34  * { \left( 1 - \frac{x}{-n_r G(\ldots)/G'(\ldots) - a_r\sigma } \right)^{n_r} }
35  * & \text{if } \frac{x-\mu}{\sigma} > a_r \\
36  * \end{cases}
37  * \f]
38  * Here, \f$ K_\lambda \f$ are the modified Bessel functions of the second kind
39  * ("irregular modified cylindrical Bessel functions" from the gsl,
40  * "special Bessel functions of the third kind"),
41  * and \f$ A^2_\lambda(\zeta) \f$ is a ratio of these:
42  * \f[
43  * A_\lambda^{2}(\zeta) = \frac{\zeta K_\lambda(\zeta)}{K_{\lambda+1}(\zeta)}
44  * \f]
45  *
46  * \if false
47  * TODO Enable once analytic integrals work.
48  * ### Analytical Integration
49  * The Hypatia distribution can be integrated analytically if \f$ \beta = \zeta = 0 \f$ and
50  * \f$ \lambda < 0 \f$. An analytic integral will only be used, though, if the parameters are **constant**
51  * at zero, and if \f$ \lambda < 0 \f$. This can be ensured as follows:
52  * ```
53  * RooRealVar beta("beta", "beta", 0.); // NOT beta("beta", "beta", 0., -1., 1.) This would allow it to float.
54  * RooRealVar zeta("zeta", "zeta", 0.);
55  * RooRealVar lambda("lambda", "lambda", -1., -10., -0.00001);
56  * ```
57  * In all other cases, slower / less accurate numeric integration will be used.
58  * Note that including `0.` in the value range of lambda excludes using analytic integrals.
59  * \endif
60  *
61  * ### Concavity
62  * Note that unless the parameters \f$ a_l,\ a_r \f$ are very large, the function has non-hyperbolic tails. This requires
63  * \f$ G \f$ to be strictly concave, *i.e.*, peaked, as otherwise the tails would yield imaginary numbers. Choosing \f$ \lambda,
64  * \beta, \zeta \f$ inappropriately will therefore lead to evaluation errors.
65  *
66  * Further, the original paper establishes that to keep the tails from rising,
67  * \f[
68  * \begin{split}
69  * \beta^2 &< \alpha^2 \\
70  * \Leftrightarrow \beta^2 &< \frac{\zeta^2}{\delta^2} = \frac{\zeta^2}{\sigma^2 A_{\lambda}^2(\zeta)}
71  * \end{split}
72  * \f]
73  * needs to be satisfied, unless the fit range is very restricted, because otherwise, the function rises in the tails.
74  *
75  *
76  * In case of evaluation errors, it is advisable to choose very large values for \f$ a_l,\ a_r \f$, tweak the parameters of the core region to
77  * make it concave, and re-enable the tails. Especially \f$ \beta \f$ needs to be close to zero.
78  *
79  * ## Relation to RooIpatia2
80  * This implementation is largely based on RooIpatia2, https://gitlab.cern.ch/lhcb/Urania/blob/master/PhysFit/P2VV/src/RooIpatia2.cxx,
81  * but there are differences:
82  * - At construction time, the Hypatia implementation checks if the range of parameters extends into regions where
83  * the function might be undefined.
84  * - Hypatia supports I/O to ROOT files.
85  * - Hypatia will support faster batched function evaluations.
86  * - Hypatia might support analytical integration for the case \f$ \zeta = \beta = 0, \lambda < 1 \f$.
87  *
88  * Because of these differences, and to avoid name clashes for setups where RooFit is used in an environment that also has
89  * RooIpatia2, class names need to be different.
90  */
91 
92 #include "RooHypatia2.h"
93 
94 #include "RooAbsReal.h"
95 #include "RooHelpers.h"
96 #include "BatchHelpers.h"
97 
98 #include "TMath.h"
99 #include "Math/SpecFunc.h"
100 #include "ROOT/RConfig.hxx"
101 
102 #include <cmath>
103 #include <algorithm>
104 
105 ///////////////////////////////////////////////////////////////////////////////////////////
106 /// Construct a new Hypatia2 PDF.
107 /// \param[in] name Name of this instance.
108 /// \param[in] title Title (for plotting)
109 /// \param[in] x The variable of this distribution
110 /// \param[in] lambda Shape parameter. Note that \f$ \lambda < 0 \f$ is required if \f$ \zeta = 0 \f$.
111 /// \param[in] zeta Shape parameter (\f$ \zeta >= 0 \f$).
112 /// \param[in] beta Asymmetry parameter \f$ \beta \f$. Symmetric case is \f$ \beta = 0 \f$,
113 /// choose values close to zero.
114 /// \param[in] sigma Width parameter. If \f$ \beta = 0, \ \sigma \f$ is the RMS width.
115 /// \param[in] mu Location parameter. Shifts the distribution left/right.
116 /// \param[in] a Start of the left tail (\f$ a \geq 0 \f$, to the left of the peak). Note that when setting \f$ a = \sigma = 1 \f$,
117 /// the tail region is to the left of \f$ x = \mu - 1 \f$, so a should be positive.
118 /// \param[in] n Shape parameter of left tail (\f$ n \ge 0 \f$). With \f$ n = 0 \f$, the function is constant.
119 /// \param[in] a2 Start of right tail.
120 /// \param[in] n2 Shape parameter of right tail (\f$ n2 \ge 0 \f$). With \f$ n2 = 0 \f$, the function is constant.
121 RooHypatia2::RooHypatia2(const char *name, const char *title, RooAbsReal& x, RooAbsReal& lambda,
122  RooAbsReal& zeta, RooAbsReal& beta, RooAbsReal& sigm, RooAbsReal& mu, RooAbsReal& a,
123  RooAbsReal& n, RooAbsReal& a2, RooAbsReal& n2) :
124  RooAbsPdf(name, title),
125  _x("x", "x", this, x),
126  _lambda("lambda", "Lambda", this, lambda),
127  _zeta("zeta", "zeta", this, zeta),
128  _beta("beta", "Asymmetry parameter beta", this, beta),
129  _sigma("sigma", "Width parameter sigma", this, sigm),
130  _mu("mu", "Location parameter mu", this, mu),
131  _a("a", "Left tail location a", this, a),
132  _n("n", "Left tail parameter n", this, n),
133  _a2("a2", "Right tail location a2", this, a2),
134  _n2("n2", "Right tail parameter n2", this, n2)
135 {
136  RooHelpers::checkRangeOfParameters(this, {&sigm}, 0.);
137  RooHelpers::checkRangeOfParameters(this, {&zeta, &n, &n2, &a, &a2}, 0., std::numeric_limits<double>::max(), true);
138  if (zeta.getVal() == 0. && zeta.isConstant()) {
139  RooHelpers::checkRangeOfParameters(this, {&lambda}, -std::numeric_limits<double>::max(), 0., false,
140  std::string("Lambda needs to be negative when ") + _zeta.GetName() + " is zero.");
141  }
142 
143 #ifndef R__HAS_MATHMORE
144  throw std::logic_error("RooHypatia2 needs ROOT with mathmore enabled to access gsl functions.");
145 #endif
146 }
147 
148 
149 ///////////////////////////////////////////////////////////////////////////////////////////
150 /// Copy a new Hypatia2 PDF.
151 /// \param[in] other Original to copy from.
152 /// \param[in] name Optional new name.
153 RooHypatia2::RooHypatia2(const RooHypatia2& other, const char* name) :
154  RooAbsPdf(other, name),
155  _x("x", this, other._x),
156  _lambda("lambda", this, other._lambda),
157  _zeta("zeta", this, other._zeta),
158  _beta("beta", this, other._beta),
159  _sigma("sigma", this, other._sigma),
160  _mu("mu", this, other._mu),
161  _a("a", this, other._a),
162  _n("n", this, other._n),
163  _a2("a2", this, other._a2),
164  _n2("n2", this, other._n2)
165 {
166 #ifndef R__HAS_MATHMORE
167  throw std::logic_error("RooHypatia2 needs ROOT with mathmore enabled to access gsl functions.");
168 #endif
169 }
170 
171 namespace {
172 const double sq2pi_inv = 1./std::sqrt(TMath::TwoPi());
173 const double logsq2pi = std::log(std::sqrt(TMath::TwoPi()));
174 const double ln2 = std::log(2.);
175 
176 double low_x_BK(double nu, double x){
177  return TMath::Gamma(nu)*std::pow(2., nu-1.)*std::pow(x, -nu);
178 }
179 
180 double low_x_LnBK(double nu, double x){
181  return std::log(TMath::Gamma(nu)) + ln2*(nu-1.) - std::log(x) * nu;
182 }
183 
184 double besselK(double ni, double x) {
185  const double nu = std::fabs(ni);
186  if ((x < 1.e-06 && nu > 0.) ||
187  (x < 1.e-04 && nu > 0. && nu < 55.) ||
188  (x < 0.1 && nu >= 55.) )
189  return low_x_BK(nu, x);
190 
191 #ifdef R__HAS_MATHMORE
192  return ROOT::Math::cyl_bessel_k(nu, x);
193 #else
194  return std::numeric_limits<double>::signaling_NaN();
195 #endif
196 
197 }
198 
199 double LnBesselK(double ni, double x) {
200  const double nu = std::fabs(ni);
201  if ((x < 1.e-06 && nu > 0.) ||
202  (x < 1.e-04 && nu > 0. && nu < 55.) ||
203  (x < 0.1 && nu >= 55.) )
204  return low_x_LnBK(nu, x);
205 
206 #ifdef R__HAS_MATHMORE
207  return std::log(ROOT::Math::cyl_bessel_k(nu, x));
208 #else
209  return std::numeric_limits<double>::signaling_NaN();
210 #endif
211 }
212 
213 
214 double LogEval(double d, double l, double alpha, double beta, double delta) {
215  const double gamma = alpha;//std::sqrt(alpha*alpha-beta*beta);
216  const double dg = delta*gamma;
217  const double thing = delta*delta + d*d;
218  const double logno = l*std::log(gamma/delta) - logsq2pi - LnBesselK(l, dg);
219 
220  return std::exp(logno + beta*d
221  + (0.5-l)*(std::log(alpha)-0.5*std::log(thing))
222  + LnBesselK(l-0.5, alpha*std::sqrt(thing)) );// + std::log(std::fabs(beta)+0.0001) );
223 
224 }
225 
226 
227 double diff_eval(double d, double l, double alpha, double beta, double delta){
228  const double gamma = alpha;
229  const double dg = delta*gamma;
230 
231  const double thing = delta*delta + d*d;
232  const double sqrthing = std::sqrt(thing);
233  const double alphasq = alpha*sqrthing;
234  const double no = std::pow(gamma/delta, l)/besselK(l, dg)*sq2pi_inv;
235  const double ns1 = 0.5-l;
236 
237  return no * std::pow(alpha, ns1) * std::pow(thing, l/2. - 1.25)
238  * ( -d * alphasq * (besselK(l - 1.5, alphasq)
239  + besselK(l + 0.5, alphasq))
240  + (2.*(beta*thing + d*l) - d) * besselK(ns1, alphasq) )
241  * std::exp(beta*d) * 0.5;
242 }
243 
244 /*
245 double Gauss2F1(double a, double b, double c, double x){
246  if (fabs(x) <= 1.) {
247  return ROOT::Math::hyperg(a, b, c, x);
248  } else {
249  return ROOT::Math::hyperg(c-a, b, c, 1-1/(1-x))/std::pow(1-x, b);
250  }
251 }
252 
253 double stIntegral(double d1, double delta, double l){
254  return d1 * Gauss2F1(0.5, 0.5-l, 3./2, -d1*d1/(delta*delta));
255 }
256 */
257 }
258 
259 double RooHypatia2::evaluate() const
260 {
261  const double d = _x-_mu;
262  const double cons0 = std::sqrt(_zeta);
263  const double asigma = _a*_sigma;
264  const double a2sigma = _a2*_sigma;
265  const double beta = _beta;
266  double out = 0.;
267 
268  if (_zeta > 0.) {
269  // careful if zeta -> 0. You can implement a function for the ratio,
270  // but careful again that |nu + 1 | != |nu| + 1 so you have to deal with the signs
271  const double phi = besselK(_lambda+1., _zeta) / besselK(_lambda, _zeta);
272  const double cons1 = _sigma/std::sqrt(phi);
273  const double alpha = cons0/cons1;
274  const double delta = cons0*cons1;
275 
276  if (d < -asigma){
277  const double k1 = LogEval(-asigma, _lambda, alpha, beta, delta);
278  const double k2 = diff_eval(-asigma, _lambda, alpha, beta, delta);
279  const double B = -asigma + _n*k1/k2;
280  const double A = k1 * std::pow(B+asigma, _n);
281 
282  out = A * std::pow(B-d, -_n);
283  }
284  else if (d>a2sigma) {
285  const double k1 = LogEval(a2sigma, _lambda, alpha, beta, delta);
286  const double k2 = diff_eval(a2sigma, _lambda, alpha, beta, delta);
287  const double B = -a2sigma - _n2*k1/k2;
288  const double A = k1 * std::pow(B+a2sigma, _n2);
289 
290  out = A * std::pow(B+d, -_n2);
291  }
292  else {
293  out = LogEval(d, _lambda, alpha, beta, delta);
294  }
295  }
296  else if (_zeta < 0.) {
297  coutE(Eval) << "The parameter " << _zeta.GetName() << " of the RooHypatia2 " << GetName() << " cannot be < 0." << std::endl;
298  }
299  else if (_lambda < 0.) {
300  const double delta = _sigma;
301 
302  if (d < -asigma ) {
303  const double cons1 = std::exp(-beta*asigma);
304  const double phi = 1. + _a * _a;
305  const double k1 = cons1 * std::pow(phi, _lambda-0.5);
306  const double k2 = beta*k1 - cons1*(_lambda-0.5) * std::pow(phi, _lambda-1.5) * 2.*_a/delta;
307  const double B = -asigma + _n*k1/k2;
308  const double A = k1*std::pow(B+asigma, _n);
309 
310  out = A*std::pow(B-d, -_n);
311  }
312  else if (d > a2sigma) {
313  const double cons1 = std::exp(beta*a2sigma);
314  const double phi = 1. + _a2*_a2;
315  const double k1 = cons1 * std::pow(phi, _lambda-0.5);
316  const double k2 = beta*k1 + cons1*(_lambda-0.5) * std::pow(phi, _lambda-1.5) * 2.*_a2/delta;
317  const double B = -a2sigma - _n2*k1/k2;
318  const double A = k1*std::pow(B+a2sigma, _n2);
319 
320  out = A*std::pow(B+d, -_n2);
321  }
322  else {
323  out = std::exp(beta*d) * std::pow(1. + d*d/(delta*delta), _lambda - 0.5);
324  }
325  }
326  else {
327  coutE(Eval) << "zeta = 0 only supported for lambda < 0. lambda = " << double(_lambda) << std::endl;
328  }
329 
330  return out;
331 }
332 
333 namespace {
334 //////////////////////////////////////////////////////////////////////////////////////////
335 /// The generic compute function that recalculates everything for every loop iteration.
336 /// This is only needed in the rare case where a parameter is used as an observable.
337 template<typename Tx, typename Tl, typename Tz, typename Tb, typename Ts, typename Tm, typename Ta, typename Tn,
338 typename Ta2, typename Tn2>
339 void compute(RooSpan<double> output, Tx x, Tl lambda, Tz zeta, Tb beta, Ts sigma, Tm mu, Ta a, Tn n, Ta2 a2, Tn2 n2) {
340  const auto N = output.size();
341  const bool zetaIsAlwaysLargerZero = !zeta.isBatch() && zeta[0] > 0.;
342  const bool zetaIsAlwaysZero = !zeta.isBatch() && zeta[0] == 0.;
343 
344  for (std::size_t i = 0; i < N; ++i) {
345 
346  const double d = x[i] - mu[i];
347  const double cons0 = std::sqrt(zeta[i]);
348  const double asigma = a[i]*sigma[i];
349  const double a2sigma = a2[i]*sigma[i];
350 // const double beta = beta[i];
351 
352  if (zetaIsAlwaysLargerZero || zeta[i] > 0.) {
353  const double phi = besselK(lambda[i]+1., zeta[i]) / besselK(lambda[i], zeta[i]);
354  const double cons1 = sigma[i]/std::sqrt(phi);
355  const double alpha = cons0/cons1;
356  const double delta = cons0*cons1;
357 
358  if (d < -asigma){
359  const double k1 = LogEval(-asigma, lambda[i], alpha, beta[i], delta);
360  const double k2 = diff_eval(-asigma, lambda[i], alpha, beta[i], delta);
361  const double B = -asigma + n[i]*k1/k2;
362  const double A = k1 * std::pow(B+asigma, n[i]);
363 
364  output[i] = A * std::pow(B-d, -n[i]);
365  }
366  else if (d>a2sigma) {
367  const double k1 = LogEval(a2sigma, lambda[i], alpha, beta[i], delta);
368  const double k2 = diff_eval(a2sigma, lambda[i], alpha, beta[i], delta);
369  const double B = -a2sigma - n2[i]*k1/k2;
370  const double A = k1 * std::pow(B+a2sigma, n2[i]);
371 
372  output[i] = A * std::pow(B+d, -n2[i]);
373  }
374  else {
375  output[i] = LogEval(d, lambda[i], alpha, beta[i], delta);
376  }
377  }
378 
379  if ((!zetaIsAlwaysLargerZero && zetaIsAlwaysZero) || (zeta[i] == 0. && lambda[i] < 0.)) {
380  const double delta = sigma[i];
381 
382  if (d < -asigma ) {
383  const double cons1 = std::exp(-beta[i]*asigma);
384  const double phi = 1. + a[i] * a[i];
385  const double k1 = cons1 * std::pow(phi, lambda[i]-0.5);
386  const double k2 = beta[i]*k1 - cons1*(lambda[i]-0.5) * std::pow(phi, lambda[i]-1.5) * 2.*a[i]/delta;
387  const double B = -asigma + n[i]*k1/k2;
388  const double A = k1*std::pow(B+asigma, n[i]);
389 
390  output[i] = A*std::pow(B-d, -n[i]);
391  }
392  else if (d > a2sigma) {
393  const double cons1 = std::exp(beta[i]*a2sigma);
394  const double phi = 1. + a2[i]*a2[i];
395  const double k1 = cons1 * std::pow(phi, lambda[i]-0.5);
396  const double k2 = beta[i]*k1 + cons1*(lambda[i]-0.5) * std::pow(phi, lambda[i]-1.5) * 2.*a2[i]/delta;
397  const double B = -a2sigma - n2[i]*k1/k2;
398  const double A = k1*std::pow(B+a2sigma, n2[i]);
399 
400  output[i] = A*std::pow(B+d, -n2[i]);
401  }
402  else {
403  output[i] = std::exp(beta[i]*d) * std::pow(1. + d*d/(delta*delta), lambda[i] - 0.5);
404  }
405  }
406  }
407 }
408 
409 template<bool right>
410 std::pair<double, double> computeAB_zetaNZero(double asigma, double lambda, double alpha, double beta, double delta, double n) {
411  const double k1 = LogEval(right ? asigma : -asigma, lambda, alpha, beta, delta);
412  const double k2 = diff_eval(right ? asigma : -asigma, lambda, alpha, beta, delta);
413  const double B = -asigma + (right ? -1 : 1.) * n*k1/k2;
414  const double A = k1 * std::pow(B+asigma, n);
415 
416  return {A, B};
417 }
418 
419 template<bool right>
420 std::pair<double, double> computeAB_zetaZero(double beta, double asigma, double a, double lambda, double delta, double n) {
421  const double cons1 = std::exp((right ? 1. : -1.) * beta*asigma);
422  const double phi = 1. + a * a;
423  const double k1 = cons1 * std::pow(phi, lambda-0.5);
424  const double k2 = beta*k1 + (right ? 1. : -1) * cons1*(lambda-0.5) * std::pow(phi, lambda-1.5) * 2.*a/delta;
425  const double B = -asigma + (right ? -1. : 1.) * n*k1/k2;
426  const double A = k1*std::pow(B+asigma, n);
427 
428  return {A, B};
429 }
430 
431 using BatchHelpers::BracketAdapter;
432 //////////////////////////////////////////////////////////////////////////////////////////
433 /// A specialised compute function where x is an observable, and all parameters are used as
434 /// parameters. Since many things can be calculated outside of the loop, it is faster.
435 void compute(RooSpan<double> output, RooSpan<const double> x,
436  BracketAdapter<double> lambda, BracketAdapter<double> zeta, BracketAdapter<double> beta,
437  BracketAdapter<double> sigma, BracketAdapter<double> mu,
438  BracketAdapter<double> a, BracketAdapter<double> n, BracketAdapter<double> a2, BracketAdapter<double> n2) {
439  const auto N = output.size();
440 
441  const double cons0 = std::sqrt(zeta);
442  const double asigma = a*sigma;
443  const double a2sigma = a2*sigma;
444 
445  if (zeta > 0.) {
446  const double phi = besselK(lambda+1., zeta) / besselK(lambda, zeta);
447  const double cons1 = sigma/std::sqrt(phi);
448  const double alpha = cons0/cons1;
449  const double delta = cons0*cons1;
450 
451  const auto AB_l = computeAB_zetaNZero<false>(asigma, lambda, alpha, beta, delta, n);
452  const auto AB_r = computeAB_zetaNZero<true>(a2sigma, lambda, alpha, beta, delta, n2);
453 
454  for (std::size_t i = 0; i < N; ++i) {
455  const double d = x[i] - mu[i];
456 
457  if (d < -asigma){
458  output[i] = AB_l.first * std::pow(AB_l.second - d, -n);
459  }
460  else if (d>a2sigma) {
461  output[i] = AB_r.first * std::pow(AB_r.second + d, -n2);
462  }
463  else {
464  output[i] = LogEval(d, lambda, alpha, beta, delta);
465  }
466  }
467  } else if (zeta == 0. && lambda < 0.) {
468  const double delta = sigma;
469 
470  const auto AB_l = computeAB_zetaZero<false>(beta, asigma, a, lambda, delta, n);
471  const auto AB_r = computeAB_zetaZero<true>(beta, a2sigma, a2, lambda, delta, n2);
472 
473  for (std::size_t i = 0; i < N; ++i) {
474  const double d = x[i] - mu[i];
475 
476  if (d < -asigma ) {
477  output[i] = AB_l.first*std::pow(AB_l.second - d, -n);
478  }
479  else if (d > a2sigma) {
480  output[i] = AB_r.first * std::pow(AB_r.second + d, -n2);
481  }
482  else {
483  output[i] = std::exp(beta*d) * std::pow(1. + d*d/(delta*delta), lambda - 0.5);
484  }
485  }
486  }
487 }
488 
489 }
490 
491 RooSpan<double> RooHypatia2::evaluateBatch(std::size_t begin, std::size_t batchSize) const {
492  using namespace BatchHelpers;
493 
494  auto x = _x.getValBatch(begin, batchSize);
495  auto lambda = _lambda.getValBatch(begin, batchSize);
496  auto zeta = _zeta.getValBatch(begin, batchSize);
497  auto beta = _beta.getValBatch(begin, batchSize);
498  auto sig = _sigma.getValBatch(begin, batchSize);
499  auto mu = _mu.getValBatch(begin, batchSize);
500  auto a = _a.getValBatch(begin, batchSize);
501  auto n = _n.getValBatch(begin, batchSize);
502  auto a2 = _a2.getValBatch(begin, batchSize);
503  auto n2 = _n2.getValBatch(begin, batchSize);
504 
505  batchSize = BatchHelpers::findSize({x, lambda, zeta, beta, sig, mu, a, n, a2, n2});
506 
507  auto output = _batchData.makeWritableBatchInit(begin, batchSize, 0.);
508 
509  const std::vector<RooSpan<const double>> params = {lambda, zeta, beta, sig, mu, a, n, a2, n2};
510  auto emptySpan = [](const RooSpan<const double>& span) { return span.empty(); };
511  if (!x.empty() && std::all_of(params.begin(), params.end(), emptySpan)) {
512  compute(output, x,
513  BracketAdapter<double>(_lambda), BracketAdapter<double>(_zeta),
514  BracketAdapter<double>(_beta), BracketAdapter<double>(_sigma), BracketAdapter<double>(_mu),
515  BracketAdapter<double>(_a), BracketAdapter<double>(_n),
516  BracketAdapter<double>(_a2), BracketAdapter<double>(_n2));
517  } else {
518  compute(output, BracketAdapterWithMask(_x, x),
519  BracketAdapterWithMask(_lambda, lambda), BracketAdapterWithMask(_zeta, zeta),
520  BracketAdapterWithMask(_beta, beta), BracketAdapterWithMask(_sigma, sig),
521  BracketAdapterWithMask(_mu, mu),
522  BracketAdapterWithMask(_a, a), BracketAdapterWithMask(_n, n),
523  BracketAdapterWithMask(_a2, a2), BracketAdapterWithMask(_n2, n2));
524  }
525 
526  return output;
527 }
528 
529 
530 /* Analytical integrals need testing.
531 
532 Int_t RooHypatia2::getAnalyticalIntegral(RooArgSet& allVars, RooArgSet& analVars, const char*) const
533 {
534  if (matchArgs(allVars, analVars, _x)
535  && _beta == 0. && _beta.arg().isConstant()
536  && _zeta == 0. && _zeta.arg().isConstant()
537  && _lambda.max() < 0.) return 1;
538  return 0 ;
539 }
540 
541 
542 
543 double RooHypatia2::analyticalIntegral(Int_t code, const char* rangeName) const
544 {
545  if (_beta != 0. || _zeta != 0. || _lambda >= 0) {
546  auto& logstream = coutF(Integration) << "When the PDF " << GetName()
547  << " was constructed, beta,zeta were 0, lambda<0 and all three were constant.\n"
548  << "This allowed for analytic integration, but now, numeric integration would be required.\n"
549  << "These parameters must either be kept constant, or be floating from the beginning. "
550  << " Terminating fit ..."
551  << std::endl;
552  RooArgSet vars;
553  vars.add(_beta.arg());
554  vars.add(_zeta.arg());
555  vars.add(_lambda.arg());
556  vars.printStream(logstream, vars.defaultPrintContents(nullptr), RooPrintable::kVerbose);
557  throw std::runtime_error("RooHypatia2 cannot be integrated analytically since parameters changed.");
558  }
559 
560  // The formulae to follow still use beta and zeta to facilitate comparisons with the
561  // evaluate function. Since beta and zeta are zero, all relevant terms will be disabled
562  // by defining these two constexpr:
563  constexpr double beta = 0.;
564  constexpr double cons1 = 1.;
565 
566  if (code != 1) {
567  throw std::logic_error("Trying to compute analytic integral with unknown configuration.");
568  }
569 
570  const double asigma = _a * _sigma;
571  const double a2sigma = _a2 * _sigma;
572  const double d0 = _x.min(rangeName) - _mu;
573  const double d1 = _x.max(rangeName) - _mu;
574 
575 
576  double delta;
577  if (_lambda <= -1.0) {
578  delta = _sigma * sqrt(-2. + -2.*_lambda);
579  }
580  else {
581  delta = _sigma;
582  }
583  const double deltaSq = delta*delta;
584 
585  if ((d0 > -asigma) && (d1 < a2sigma)) {
586  return stIntegral(d1, delta, _lambda) - stIntegral(d0, delta, _lambda);
587  }
588 
589  if (d0 > a2sigma) {
590  const double phi = 1. + a2sigma*a2sigma/deltaSq;
591  const double k1 = cons1*std::pow(phi,_lambda-0.5);
592  const double k2 = beta*k1+ cons1*(_lambda-0.5)*std::pow(phi,_lambda-1.5)*2.*a2sigma/deltaSq;
593  const double B = -a2sigma - _n2*k1/k2;
594  const double A = k1*std::pow(B+a2sigma,_n2);
595  return A*(std::pow(B+d1,1-_n2)/(1-_n2) -std::pow(B+d0,1-_n2)/(1-_n2) ) ;
596 
597  }
598 
599  if (d1 < -asigma) {
600  const double phi = 1. + asigma*asigma/deltaSq;
601  const double k1 = cons1*std::pow(phi,_lambda-0.5);
602  const double k2 = beta*k1- cons1*(_lambda-0.5)*std::pow(phi,_lambda-1.5)*2*asigma/deltaSq;
603  const double B = -asigma + _n*k1/k2;
604  const double A = k1*std::pow(B+asigma,_n);
605  const double I0 = A*std::pow(B-d0,1-_n)/(_n-1);
606  const double I1 = A*std::pow(B-d1,1-_n)/(_n-1);
607  return I1 - I0;
608  }
609 
610 
611  double I0;
612  double I1a = 0;
613  double I1b = 0;
614  if (d0 <-asigma) {
615  const double phi = 1. + asigma*asigma/deltaSq;
616  const double k1 = cons1*std::pow(phi,_lambda-0.5);
617  const double k2 = beta*k1- cons1*(_lambda-0.5)*std::pow(phi,_lambda-1.5)*2*asigma/deltaSq;
618  const double B = -asigma + _n*k1/k2;
619  const double A = k1*std::pow(B+asigma,_n);
620  I0 = A*std::pow(B-d0,1-_n)/(_n-1);
621  I1a = A*std::pow(B+asigma,1-_n)/(_n-1) - stIntegral(-asigma, delta, _lambda);
622  }
623  else {
624  I0 = stIntegral(d0, delta, _lambda);
625  }
626 
627  if (d1 > a2sigma) {
628  const double phi = 1. + a2sigma*a2sigma/deltaSq;
629  const double k1 = cons1*std::pow(phi,_lambda-0.5);
630  const double k2 = beta*k1+ cons1*(_lambda-0.5)*std::pow(phi,_lambda-1.5)*2.*a2sigma/deltaSq;
631  const double B = -a2sigma - _n2*k1/k2;
632  const double A = k1*std::pow(B+a2sigma,_n2);
633  I1b = A*(std::pow(B+d1,1-_n2)/(1-_n2) -std::pow(B+a2sigma,1-_n2)/(1-_n2) ) - stIntegral(d1, delta, _lambda) + stIntegral(a2sigma,delta, _lambda) ;
634  }
635 
636  const double I1 = stIntegral(d1, delta, _lambda) + I1a + I1b;
637  return I1 - I0;
638 }
639 
640 */