5 #include <atm/Arbeletche2021CherenkovAngularModel.h>
7 #include <utl/AugerException.h>
8 #include <utl/MathConstants.h>
9 #include <utl/Integrator.h>
17 const double showerEnergyTeV,
30 const double showerAge,
31 const double refractiveIndex
37 const double nMinusOne =
std::max(refractiveIndex, 1.0 + 3e-5) - 1.0;
38 const double truncatedAge =
std::max(0.5, std::min(showerAge, 1.5));
39 const double logShowerAge = std::log(truncatedAge);
42 fNu = 0.21155 *
std::pow(nMinusOne, -0.16639) + 1.21803 * logShowerAge;
43 fOmega1 = 1.0 / (fOmegaEnergyDep *
std::pow(nMinusOne, 0.45092) - 0.058687 * logShowerAge);
44 fOmega2 = fOmega1 * (1.0 - 1.0 / (0.90725 + 0.41722 * truncatedAge));
47 const double v = 1. - 1./refractiveIndex;
48 fThetaEm = v < 0.001 ?
std::sqrt(2.*v)*(1.+(v/12.)*(1.+9.*v/40.)) : std::acos(1./refractiveIndex);
49 fCosThetaEm = 1./refractiveIndex;
50 fSinThetaEm =
std::sqrt((1.-fCosThetaEm)*(1.+fCosThetaEm));
53 fNormalization = UnnormalizedIntegral(0.0, fMaxTheta);
64 (std::exp(-theta*fOmega1) + fEpsilon*std::exp(-theta*fOmega2));
71 const double lowAngle,
72 const double highAngle
78 if (lowAngle < fThetaEm) {
79 integral += UnnormalizedIntegralLeft(lowAngle, std::min(highAngle, fThetaEm));
83 if (highAngle > fThetaEm) {
84 integral += UnnormalizedIntegralRight(
std::max(lowAngle, fThetaEm), highAngle);
94 const double lowAngle,
95 const double highAngle
103 const double deltaLow = fThetaEm-lowAngle;
104 const double deltaHigh = fThetaEm-highAngle;
105 const double deltaCosLow = std::cos(lowAngle) - fCosThetaEm;
106 const double deltaCosHigh = std::cos(highAngle) - fCosThetaEm;
107 const double coeffs[4] = {fCosThetaEm, fSinThetaEm, -fCosThetaEm, -fSinThetaEm};
114 bool hasConverged =
false;
115 for (
int k = 1; k < 1000; ++k) {
116 powLow *= deltaLow / k;
117 powHigh *= deltaHigh / k;
118 term = (powLow - powHigh) / k;
119 integral += coeffs[k%4] * term;
120 if (std::fabs(term) <= 1e-10*std::fabs(integral)) {
128 const std::string message =
"Failed to integrate the angular distribution of Cherenkov photons";
134 integral +=
kPi * (deltaCosLow - deltaCosHigh);
135 integral += deltaHigh < 1e-10 ?
136 std::log(
std::pow(deltaHigh/fThetaEm+1e-200,deltaCosHigh)) :
137 deltaCosHigh * std::log(deltaHigh/fThetaEm);
138 integral -= deltaLow < 1e-10 ?
139 std::log(
std::pow(deltaLow/fThetaEm+1e-200,deltaCosLow)) :
140 deltaCosLow * std::log(deltaLow/fThetaEm);
143 integral *= FunctionK(fThetaEm) / fSinThetaEm;
152 const double lowAngle,
153 const double highAngle
161 auto integrand = [=](
double t) {
162 const double theta = fThetaEm / ((1.0-t)*(1.0+t));
165 (std::exp(-fOmega1*theta) + fEpsilon*std::exp(-fOmega2*theta));
169 auto uSeries = [=](
const double theta,
const double omega)->
double{
170 const double u = 1.0 - fThetaEm/theta;
175 const double pimlog = u > 1e-5 ?
176 u*(
kPi - std::log(u)) :
180 double bm1 = std::exp(-omega*fThetaEm);
181 double sum = (pimlog+u)*bm1;
182 double a1 = fNu-1-omega*fThetaEm;
186 for (
int n = 2; n < 1000; ++n) {
189 b = a1*bm1 - a2*(fNu+a2)*bm2*upn;
192 term = b * (pimlog + upn);
194 if (std::fabs(term) <= 1e-10*std::fabs(sum)) {
195 return sum *
std::pow(fThetaEm, fNu);
206 auto rSeries = [=](
const double theta,
const double omega)->
double{
207 const double wt = omega*theta;
208 const double r = fThetaEm / theta;
209 const double wtem = fThetaEm * omega;
217 bool hasConverged =
false;
220 const double fpMin = 1e-30;
221 double b = wt + 1 - fNu;
222 double c = 1.0 / fpMin;
225 for (
int n = 1; n < 1000; ++n) {
226 double an = -n * (n - fNu);
229 if (std::fabs(d) < fpMin) {
233 if (std::fabs(c) < fpMin) {
239 if (std::fabs(del - 1) < 1e-10) {
246 double term = 1.0/fNu;
248 for (
int n = 1; n < 1000; ++n) {
249 term *= wt / (n + fNu);
251 if (std::fabs(term) < 1e-10*std::fabs(h)) {
256 h -= std::exp(wt - fNu*std::log(wt) + std::lgamma(fNu));
262 double sum =
kPi * h;
263 for (
int k = 1; k < 1000; ++k) {
265 h = (rPower + wtem*h) / (fNu - k);
266 sum += h / double(k);
267 if (std::fabs(h /
double(k)) <= 1e-10*std::fabs(sum)) {
268 return sum * std::exp(-wt + fNu * std::log(theta));
283 const double thresholdAngle = 2*fThetaEm;
290 if (lowAngle < thresholdAngle) {
291 const double actualHighAngle = std::min(highAngle, thresholdAngle);
293 integral += uSeries(actualHighAngle, fOmega1) - uSeries(lowAngle, fOmega1) +
294 fEpsilon * (uSeries(actualHighAngle, fOmega2) - uSeries(lowAngle, fOmega2));
296 const std::string message =
"Failed to integrate the angular distribution of Cherenkov photons";
306 if (highAngle > thresholdAngle) {
307 const double actualLowAngle =
std::max(lowAngle, thresholdAngle);
308 if (std::fabs(fNu - std::round(fNu)) < 1e-4) {
310 const double tMin =
std::sqrt(1.0 - fThetaEm/actualLowAngle);
311 const double tMax =
std::sqrt(1.0 - fThetaEm/highAngle);
317 integral += rSeries(highAngle, fOmega1) - rSeries(actualLowAngle, fOmega1) +
318 fEpsilon * (rSeries(highAngle, fOmega2) - rSeries(actualLowAngle, fOmega2));
320 const std::string message =
"Failed to integrate the angular distribution of Cherenkov photons";
334 const double lowAngle,
335 const double highAngle
343 const double deltaLow = 2.0*(fThetaEm - lowAngle);
344 const double deltaHigh = 2.0*(fThetaEm - highAngle);
345 const double twoThetaEm = 2.*fThetaEm;
346 const double cosTwoThetaEm = 2.0*fCosThetaEm*fCosThetaEm - 1.0;
347 const double sinTwoThetaEm = 2.0*fSinThetaEm*fCosThetaEm;
348 const double factorLow = sinTwoThetaEm - std::sin(2.0*lowAngle) - deltaLow;
349 const double factorHigh = sinTwoThetaEm - std::sin(2.0*highAngle) - deltaHigh;
350 const double coeffs[4] = {sinTwoThetaEm, -cosTwoThetaEm, -sinTwoThetaEm, cosTwoThetaEm};
357 double powHigh = 1.0;
359 bool hasConverged =
false;
360 for (
int k = 1; k < 1000; ++k) {
361 powLow *= deltaLow / k;
362 powHigh *= deltaHigh / k;
363 term = (powLow - powHigh) / k;
364 integral += coeffs[k%4] * term;
365 if (std::fabs(term) <= 1e-10*std::fabs(integral)) {
373 const std::string message =
"Failed to integrate the angular distribution of Cherenkov photons";
379 integral += deltaLow - deltaHigh;
380 integral +=
kPi * (factorHigh-factorLow);
381 integral -= deltaHigh < 1e-10 ?
382 std::log(
std::pow(deltaHigh/twoThetaEm+1e-200,factorHigh)) :
383 factorHigh * std::log(deltaHigh/twoThetaEm);
384 integral += deltaLow < 1e-10 ?
385 std::log(
std::pow(deltaLow/twoThetaEm+1e-200,factorLow)) :
386 factorLow * std::log(deltaLow/twoThetaEm);
389 integral *= 0.25 * FunctionK(fThetaEm) / fSinThetaEm;
398 const double lowAngle,
399 const double highAngle
407 auto integrand = [=](
double t) {
408 const double theta = fThetaEm / ((1.0-t)*(1.0+t));
409 return std::sin(theta) *
std::pow(theta, fNu + 1) *
411 (std::exp(-fOmega1*theta) + fEpsilon*std::exp(-fOmega2*theta));
415 auto uSeries = [=](
const double theta,
const double omega)->
double{
416 const double u = 1.0 - fThetaEm/theta;
421 const double pimlog = u > 1e-5 ?
422 u*(
kPi - std::log(u)) :
427 double bm1 = std::exp(-omega*fThetaEm);
428 double am1 = bm1 * fCosThetaEm;
430 double sum = (pimlog + u) * bm1;
431 double c1 = fNu-1-omega*fThetaEm;
435 for (
int n = 2; n < 1000; ++n) {
438 a = c1*am1 - c2*(fNu+c2)*am2*upn - fThetaEm*bm1;
439 b = c1*bm1 - c2*(fNu+c2)*bm2*upn + fThetaEm*am1;
443 term = (pimlog + upn) * b;
445 if (std::fabs(term) <= 1e-10*std::fabs(sum)) {
446 return sum *
std::pow(fThetaEm, fNu);
459 auto rSeries = [=](
const double theta,
const double omega) {
460 const double r = fThetaEm/theta;
461 const double sint = std::sin(theta);
462 const double cost = std::cos(theta);
463 const double invt = 1.0/theta;
468 double kap = 1.0/fNu;
473 double den = fNu * invt;
474 bool hasConverged =
false;
475 for (
int n = 1; n < 1000; ++n) {
478 kap = (omega*kap - sig) / den;
479 sig = (omega*sig + old) / den;
482 if (std::fabs(kap) <= 1e-10*std::fabs(c) && std::fabs(sig) <= 1e-10*std::fabs(s)) {
490 double b = sint*c - cost*
s;
492 double series =
kPi *
b;
494 for (
int k = 1; k < 1000; ++k) {
498 s = (omega*s +
c) * fThetaEm / den;
499 c = (rp + fThetaEm*(omega*c - old)) / den;
500 b = (sint*c - cost*
s) / k;
502 if (std::fabs(b) < 1e-10*std::fabs(series)) {
503 return series * std::exp(-omega*theta + fNu*std::log(theta));
518 const double thresholdAngle = 2*fThetaEm;
525 if (lowAngle < thresholdAngle) {
526 const double actualHighAngle = std::min(highAngle, thresholdAngle);
528 integral += uSeries(actualHighAngle, fOmega1) - uSeries(lowAngle, fOmega1) +
529 fEpsilon * (uSeries(actualHighAngle, fOmega2) - uSeries(lowAngle, fOmega2));
531 const std::string message =
"Failed to integrate the angular distribution of Cherenkov photons";
541 if (highAngle > thresholdAngle) {
542 const double actualLowAngle =
std::max(lowAngle, thresholdAngle);
543 if (std::fabs(fNu - std::round(fNu)) < 1e-4) {
545 const double tMin =
std::sqrt(1.0 - fThetaEm/actualLowAngle);
546 const double tMax =
std::sqrt(1.0 - fThetaEm/highAngle);
552 integral += rSeries(highAngle, fOmega1) - rSeries(actualLowAngle, fOmega1) +
553 fEpsilon * (rSeries(highAngle, fOmega2) - rSeries(actualLowAngle, fOmega2));
555 const std::string message =
"Failed to integrate the angular distribution of Cherenkov photons";
570 const double showerAge,
571 const double refractiveIndex
574 static const double peakWidth = 1e-5;
579 }
else if (theta > fMaxTheta) {
584 UpdateParameters(showerAge, refractiveIndex);
586 if (theta <= fThetaEm - peakWidth) {
588 return FunctionK(fThetaEm) * (
kPi - std::log(1.0-theta/fThetaEm)) *
589 (std::sin(theta)/fSinThetaEm) / fNormalization;
591 else if (theta < fThetaEm + peakWidth) {
593 return UnnormalizedIntegral(fThetaEm - peakWidth, fThetaEm + peakWidth) /
594 (2.0*peakWidth*fNormalization);
598 return FunctionK(theta) * (
kPi - std::log(1.0 - fThetaEm/theta)) / fNormalization;
607 const double showerAge,
608 const double refractiveIndex
614 }
else if (theta >= fMaxTheta) {
619 UpdateParameters(showerAge, refractiveIndex);
622 return UnnormalizedIntegral(0, theta) / fNormalization;
629 const double lowAngle,
630 const double highAngle,
631 const double showerAge,
632 const double refractiveIndex
636 if (lowAngle > highAngle) {
637 return -Integral(highAngle, lowAngle, showerAge, refractiveIndex);
641 UpdateParameters(showerAge, refractiveIndex);
645 if (lowAngle < fThetaEm) {
646 integral += UnnormalizedIntegralSineLeft(
std::max(lowAngle,0.), std::min(highAngle,fThetaEm));
649 if (highAngle > fThetaEm) {
650 integral += UnnormalizedIntegralSineRight(
std::max(lowAngle,fThetaEm), std::min(highAngle,fMaxTheta));
654 return integral / fNormalization;
Class for integration of functions with one independent parameter.
double UnnormalizedIntegral(const double lowAngle, const double highAngle) const
double UnnormalizedIntegralLeft(const double lowAngle, const double highAngle) const
double CDF(const double theta, const double showerAge, const double refractiveIndex) const
Computes the CDF of the angular distribution of Cherenkov photons.
double UnnormalizedIntegralSineRight(const double lowAngle, const double highAngle) const
double pow(const double x, const unsigned int i)
double PDF(const double theta, const double showerAge, const double refractiveIndex) const
Computes the PDF of the angular distribution of Cherenkov photons.
Arbeletche2021CherenkovAngularModel(const double showerEnergyTeV, const double maxTheta)
double Integral(const double lowAngle, const double highAngle, const double showerAge, const double refractiveIndex) const
Compute the integral of PDF(theta)*sin(theta) between specified angles.
constexpr double kPiOnTwo
double UnnormalizedIntegralRight(const double lowAngle, const double highAngle) const
Base class for inconsistency/illogicality exceptions.
double FunctionK(const double theta) const
void UpdateParameters(const double showerAge, const double refractiveIndex) const
#define ERROR(message)
Macro for logging error messages.
double UnnormalizedIntegralSineLeft(const double lowAngle, const double highAngle) const
double GetRombergIntegral(const double a, const double b, const int order=5, const int maxIterations=20) const
Romberg integration Setting order to 2 is equivalent to the Simpson's method.