Commit 6c5c214b77f4702458008d2d3a4590d728d9269e
1 parent
20f41c7b
improve numerical Npred intergations to avoid Romberg integration convergence problems
Showing
1 changed file
with
135 additions
and
76 deletions
inst/cta/src/GCTAResponse.cpp
| ... | ... | @@ -284,20 +284,27 @@ double GCTAResponse::irf(const GInstDir& obsDir, |
| 284 | 284 | // Compute only if we're sufficiently close to PSF |
| 285 | 285 | if (delta <= delta_max) { |
| 286 | 286 | |
| 287 | - // Get point source IRF | |
| 288 | - irf = aeff(theta, phi, zenith, azimuth, srcLogEng); | |
| 289 | - irf *= psf(delta, theta, phi, zenith, azimuth, srcLogEng); | |
| 290 | - | |
| 291 | - // Multiply-in energy dispersion | |
| 292 | - if (hasedisp()) { | |
| 287 | + // Get effective area component | |
| 288 | + irf = aeff(theta, phi, zenith, azimuth, srcLogEng); | |
| 289 | + | |
| 290 | + // Multiply-in PSF | |
| 291 | + if (irf > 0) { | |
| 292 | + | |
| 293 | + // Get PSF component | |
| 294 | + irf *= psf(delta, theta, phi, zenith, azimuth, srcLogEng); | |
| 295 | + | |
| 296 | + // Multiply-in energy dispersion | |
| 297 | + if (hasedisp() && irf > 0) { | |
| 293 | 298 | |
| 294 | - // Get log10(E/TeV) of measured photon energy. | |
| 295 | - double obsLogEng = obsEng.log10TeV(); | |
| 299 | + // Get log10(E/TeV) of measured photon energy. | |
| 300 | + double obsLogEng = obsEng.log10TeV(); | |
| 296 | 301 | |
| 297 | - // Multiply-in energy dispersion | |
| 298 | - irf *= edisp(obsLogEng, theta, phi, zenith, azimuth, srcLogEng); | |
| 302 | + // Multiply-in energy dispersion | |
| 303 | + irf *= edisp(obsLogEng, theta, phi, zenith, azimuth, srcLogEng); | |
| 299 | 304 | |
| 300 | - } // endif: energy dispersion was available | |
| 305 | + } // endif: energy dispersion was available and PSF was non-zero | |
| 306 | + | |
| 307 | + } // endif: Aeff was non-zero | |
| 301 | 308 | |
| 302 | 309 | } // endif: we were sufficiently close to PSF |
| 303 | 310 | |
| ... | ... | @@ -369,17 +376,24 @@ double GCTAResponse::npred(const GSkyDir& srcDir, |
| 369 | 376 | // Get log10(E/TeV) of true photon energy. |
| 370 | 377 | double srcLogEng = srcEng.log10TeV(); |
| 371 | 378 | |
| 372 | - // Get IRF components | |
| 379 | + // Get effectve area components | |
| 373 | 380 | double npred = aeff(theta, phi, zenith, azimuth, srcLogEng); |
| 374 | - npred *= npsf(srcDir, srcLogEng, srcTime, *pnt, events->roi()); | |
| 375 | - | |
| 376 | - // Multiply-in energy dispersion | |
| 377 | - if (hasedisp()) { | |
| 381 | + | |
| 382 | + // Multiply-in PSF | |
| 383 | + if (npred > 0.0) { | |
| 384 | + | |
| 385 | + // Get PSF | |
| 386 | + npred *= npsf(srcDir, srcLogEng, srcTime, *pnt, events->roi()); | |
| 378 | 387 | |
| 379 | 388 | // Multiply-in energy dispersion |
| 380 | - npred *= nedisp(srcDir, srcEng, srcTime, *pnt, events->ebounds()); | |
| 389 | + if (hasedisp() && npred > 0.0) { | |
| 381 | 390 | |
| 382 | - } // endif: had energy dispersion | |
| 391 | + // Get energy dispersion | |
| 392 | + npred *= nedisp(srcDir, srcEng, srcTime, *pnt, events->ebounds()); | |
| 393 | + | |
| 394 | + } // endif: had energy dispersion | |
| 395 | + | |
| 396 | + } // endif: had non-zero effective area | |
| 383 | 397 | |
| 384 | 398 | // Compile option: Check for NaN/Inf |
| 385 | 399 | #if defined(G_NAN_CHECK) |
| ... | ... | @@ -496,8 +510,9 @@ void GCTAResponse::caldb(const std::string& caldb) |
| 496 | 510 | std::string caldb_expanded = expand_env(caldb); |
| 497 | 511 | |
| 498 | 512 | // Check if calibration database directory is accessible |
| 499 | - if (access(caldb_expanded.c_str(), R_OK) != 0) | |
| 513 | + if (access(caldb_expanded.c_str(), R_OK) != 0) { | |
| 500 | 514 | throw GException::caldb_not_found(G_CALDB, caldb_expanded); |
| 515 | + } | |
| 501 | 516 | |
| 502 | 517 | // Store the path to the calibration database |
| 503 | 518 | m_caldb = caldb_expanded; |
| ... | ... | @@ -552,8 +567,9 @@ std::string GCTAResponse::print(void) const |
| 552 | 567 | result.append("=== GCTAResponse ==="); |
| 553 | 568 | result.append("\n"+parformat("Calibration database")+m_caldb); |
| 554 | 569 | result.append("\n"+parformat("Response name")+m_rspname); |
| 555 | - if (m_offset_sigma == 0) | |
| 570 | + if (m_offset_sigma == 0) { | |
| 556 | 571 | result.append("\n"+parformat("Offset angle dependence")+"none"); |
| 572 | + } | |
| 557 | 573 | else { |
| 558 | 574 | std::string txt = "Fixed sigma="+str(m_offset_sigma); |
| 559 | 575 | result.append("\n"+parformat("Offset angle dependence")+txt); |
| ... | ... | @@ -832,7 +848,7 @@ double GCTAResponse::npred_extended(const GModelExtendedSource& model, |
| 832 | 848 | ctaobs, |
| 833 | 849 | &rot, |
| 834 | 850 | roi_model_distance, |
| 835 | - roi_radius, | |
| 851 | + roi_psf_radius, | |
| 836 | 852 | phi); |
| 837 | 853 | |
| 838 | 854 | // Integrate over theta |
| ... | ... | @@ -1013,15 +1029,20 @@ double GCTAResponse::edisp(const double& obsLogEng, |
| 1013 | 1029 | * @param[in] pnt CTA pointing. |
| 1014 | 1030 | * @param[in] roi CTA region of interest. |
| 1015 | 1031 | * |
| 1016 | - * This method integrates the PSF over the circular region of interest. | |
| 1032 | + * This method integrates the PSF over the circular region of interest (ROI). | |
| 1017 | 1033 | * Integration is done in a polar coordinate system centred on the PSF since |
| 1018 | 1034 | * the PSF is assumed to be azimuthally symmetric. The polar integration is |
| 1019 | 1035 | * done using the method npsf_kern_rad_azsym() that computes analytically |
| 1020 | - * the arclength that is comprised within the ROI. The radial integration | |
| 1021 | - * is done using a standard Romberg integration. Note that the integration | |
| 1022 | - * is only performed when the PSF is sufficiently close to the ROI border so | |
| 1023 | - * that part of the PSF may be outside the ROI. For all other cases, the | |
| 1024 | - * integral is simply 1. | |
| 1036 | + * the arclength that is comprised within the ROI. | |
| 1037 | + * | |
| 1038 | + * Note that the integration is only performed when the PSF is spilling out | |
| 1039 | + * of the ROI border, otherwise the integral is simply 1. Numerical | |
| 1040 | + * integration is done using the standard Romberg method. The integration | |
| 1041 | + * boundaries are computed so that only the PSF section that falls in the ROI | |
| 1042 | + * is considered. | |
| 1043 | + * | |
| 1044 | + * @todo Enhance romb() integration method for small integration regions | |
| 1045 | + * (see comment abobr kluge below) | |
| 1025 | 1046 | ***************************************************************************/ |
| 1026 | 1047 | double GCTAResponse::npsf(const GSkyDir& srcDir, |
| 1027 | 1048 | const double& srcLogEng, |
| ... | ... | @@ -1033,44 +1054,68 @@ double GCTAResponse::npsf(const GSkyDir& srcDir, |
| 1033 | 1054 | double value = 0.0; |
| 1034 | 1055 | |
| 1035 | 1056 | // Extract relevant parameters from arguments |
| 1036 | - double radroi = roi.radius() * deg2rad; | |
| 1037 | - double psf = roi.centre().dist(srcDir); | |
| 1038 | - double sigma = psf_dummy_sigma(srcLogEng); | |
| 1039 | - double rmax = psf_dummy_max(sigma); | |
| 1040 | - | |
| 1041 | - // If PSF is sufficiently enclosed by ROI, skip the numerical integration | |
| 1042 | - // and assume that the integral is 1.0 | |
| 1043 | - if (psf+rmax < radroi) { | |
| 1057 | + double roi_radius = roi.radius() * deg2rad; | |
| 1058 | + double roi_psf_distance = roi.centre().dist(srcDir); | |
| 1059 | + double sigma = psf_dummy_sigma(srcLogEng); | |
| 1060 | + double rmax = psf_dummy_max(sigma); | |
| 1061 | + | |
| 1062 | + // If PSF is fully enclosed by the ROI then skip the numerical | |
| 1063 | + // integration and assume that the integral is 1.0 | |
| 1064 | + if (roi_psf_distance + rmax <= roi_radius) { | |
| 1044 | 1065 | value = 1.0; |
| 1045 | 1066 | } |
| 1046 | 1067 | |
| 1047 | 1068 | // ... otherwise perform numerical integration |
| 1048 | 1069 | else { |
| 1049 | 1070 | |
| 1050 | - // Setup integration kernel | |
| 1051 | - GCTAResponse::npsf_kern_rad_azsym integrand(this, radroi, psf, sigma); | |
| 1052 | - | |
| 1053 | - // Integrate PSF | |
| 1054 | - GIntegral integral(&integrand); | |
| 1055 | - integral.eps(m_eps); | |
| 1056 | - value = integral.romb(0.0, rmax); | |
| 1071 | + // Compute minimum PSF integration radius | |
| 1072 | + double rmin = (roi_psf_distance > roi_radius) | |
| 1073 | + ? roi_psf_distance - roi_radius : 0.0; | |
| 1074 | + | |
| 1075 | + // Continue only if integration range is valid | |
| 1076 | + if (rmax > rmin) { | |
| 1077 | + | |
| 1078 | + // Setup integration kernel | |
| 1079 | + GCTAResponse::npsf_kern_rad_azsym integrand(this, | |
| 1080 | + roi_radius, | |
| 1081 | + roi_psf_distance, | |
| 1082 | + sigma); | |
| 1083 | + | |
| 1084 | + // Setup integration | |
| 1085 | + GIntegral integral(&integrand); | |
| 1086 | + integral.eps(m_eps); | |
| 1087 | + | |
| 1088 | + // Radially integrate PSF. In case that the radial integration | |
| 1089 | + // region is small, we do the integration using a simple | |
| 1090 | + // trapezoidal rule. This is a kluge to prevent convergence | |
| 1091 | + // problems in the romb() method for small integration intervals. | |
| 1092 | + // Ideally, the romb() method should be enhanced to handle this | |
| 1093 | + // case automatically. The kluge threshold was fixed manually! | |
| 1094 | + if (rmax-rmin < 1.0e-12) { | |
| 1095 | + value = integral.trapzd(rmin, rmax); | |
| 1096 | + } | |
| 1097 | + else { | |
| 1098 | + value = integral.romb(rmin, rmax); | |
| 1099 | + } | |
| 1100 | + | |
| 1101 | + // Compile option: Check for NaN/Inf | |
| 1102 | + #if defined(G_NAN_CHECK) | |
| 1103 | + if (isnotanumber(value) || isinfinite(value)) { | |
| 1104 | + std::cout << "*** ERROR: GCTAResponse::npsf:"; | |
| 1105 | + std::cout << " NaN/Inf encountered"; | |
| 1106 | + std::cout << " (value=" << value; | |
| 1107 | + std::cout << ", roi_radius=" << roi_radius; | |
| 1108 | + std::cout << ", roi_psf_distance=" << roi_psf_distance; | |
| 1109 | + std::cout << ", sigma=" << sigma; | |
| 1110 | + std::cout << ", r=[" << rmin << "," << rmax << "])"; | |
| 1111 | + std::cout << std::endl; | |
| 1112 | + } | |
| 1113 | + #endif | |
| 1114 | + | |
| 1115 | + } // endif: integration range was valid | |
| 1057 | 1116 | |
| 1058 | 1117 | } // endelse: numerical integration required |
| 1059 | 1118 | |
| 1060 | - // Compile option: Check for NaN/Inf | |
| 1061 | - #if defined(G_NAN_CHECK) | |
| 1062 | - if (isnotanumber(value) || isinfinite(value)) { | |
| 1063 | - std::cout << "*** ERROR: GCTAResponse::npsf:"; | |
| 1064 | - std::cout << " NaN/Inf encountered"; | |
| 1065 | - std::cout << " (value=" << value; | |
| 1066 | - std::cout << ", radroi=" << radroi; | |
| 1067 | - std::cout << ", psf=" << psf; | |
| 1068 | - std::cout << ", sigma=" << sigma; | |
| 1069 | - std::cout << ", rmax=" << rmax << ")"; | |
| 1070 | - std::cout << std::endl; | |
| 1071 | - } | |
| 1072 | - #endif | |
| 1073 | - | |
| 1074 | 1119 | // Return integrated PSF |
| 1075 | 1120 | return value; |
| 1076 | 1121 | } |
| ... | ... | @@ -1202,8 +1247,8 @@ void GCTAResponse::init_members(void) |
| 1202 | 1247 | m_aeff.clear(); |
| 1203 | 1248 | m_r68.clear(); |
| 1204 | 1249 | m_r80.clear(); |
| 1205 | - m_eps = 1.0e-5; | |
| 1206 | - m_offset_sigma = 3.0; // default value for now ... | |
| 1250 | + m_eps = 1.0e-5; // Precision for Romberg integration | |
| 1251 | + m_offset_sigma = 3.0; // Default value for now ... | |
| 1207 | 1252 | |
| 1208 | 1253 | // Return |
| 1209 | 1254 | return; |
| ... | ... | @@ -1439,9 +1484,10 @@ double GCTAResponse::irf_kern_omega::eval(double omega) |
| 1439 | 1484 | m_rsp->psf_dummy(delta, m_sigma); |
| 1440 | 1485 | |
| 1441 | 1486 | // Optionally take energy dispersion into account |
| 1442 | - if (m_rsp->hasedisp()) | |
| 1487 | + if (m_rsp->hasedisp() && irf > 0.0) { | |
| 1443 | 1488 | irf *= m_rsp->edisp(m_obsLogEng, theta, phi, m_zenith, m_azimuth, m_srcLogEng); |
| 1444 | - | |
| 1489 | + } | |
| 1490 | + | |
| 1445 | 1491 | // Compile option: Check for NaN/Inf |
| 1446 | 1492 | #if defined(G_NAN_CHECK) |
| 1447 | 1493 | if (isnotanumber(irf) || isinfinite(irf)) { |
| ... | ... | @@ -1588,24 +1634,37 @@ double GCTAResponse::npred_kern_phi::eval(double phi) |
| 1588 | 1634 | ***************************************************************************/ |
| 1589 | 1635 | double GCTAResponse::npsf_kern_rad_azsym::eval(double theta) |
| 1590 | 1636 | { |
| 1637 | + // Initialise PSF value | |
| 1638 | + double value = 0.0; | |
| 1639 | + | |
| 1591 | 1640 | // Get arclength for given radius in radians |
| 1592 | - double phi = cta_roi_arclength(theta, m_psf, m_cospsf, m_sinpsf, m_roi, m_cosroi); | |
| 1593 | - | |
| 1594 | - // Get PSF value | |
| 1595 | - double value = m_parent->psf_dummy(theta, m_sigma) * phi * std::sin(theta); | |
| 1641 | + double phi = cta_roi_arclength(theta, | |
| 1642 | + m_psf, | |
| 1643 | + m_cospsf, | |
| 1644 | + m_sinpsf, | |
| 1645 | + m_roi, | |
| 1646 | + m_cosroi); | |
| 1647 | + | |
| 1648 | + // If arclength is positive then compute the PSF value | |
| 1649 | + if (phi > 0) { | |
| 1650 | + | |
| 1651 | + // Compute PSF value | |
| 1652 | + value = m_parent->psf_dummy(theta, m_sigma) * phi * std::sin(theta); | |
| 1596 | 1653 | |
| 1597 | - // Compile option: Check for NaN/Inf | |
| 1598 | - #if defined(G_NAN_CHECK) | |
| 1599 | - if (isnotanumber(value) || isinfinite(value)) { | |
| 1600 | - std::cout << "*** ERROR: GCTAResponse::npsf_kern_rad_azsym::eval"; | |
| 1601 | - std::cout << "(theta=" << theta << ")."; | |
| 1602 | - std::cout << " NaN/Inf encountered"; | |
| 1603 | - std::cout << " (value=" << value; | |
| 1604 | - std::cout << ", theta=" << theta; | |
| 1605 | - std::cout << ", phi=" << phi << ")"; | |
| 1606 | - std::cout << std::endl; | |
| 1607 | - } | |
| 1608 | - #endif | |
| 1654 | + // Compile option: Check for NaN/Inf | |
| 1655 | + #if defined(G_NAN_CHECK) | |
| 1656 | + if (isnotanumber(value) || isinfinite(value)) { | |
| 1657 | + std::cout << "*** ERROR: GCTAResponse::npsf_kern_rad_azsym::eval"; | |
| 1658 | + std::cout << "(theta=" << theta << ")."; | |
| 1659 | + std::cout << " NaN/Inf encountered"; | |
| 1660 | + std::cout << " (value=" << value; | |
| 1661 | + std::cout << ", theta=" << theta; | |
| 1662 | + std::cout << ", phi=" << phi << ")"; | |
| 1663 | + std::cout << std::endl; | |
| 1664 | + } | |
| 1665 | + #endif | |
| 1666 | + | |
| 1667 | + } // endif: arclength was positive | |
| 1609 | 1668 | |
| 1610 | 1669 | // Return |
| 1611 | 1670 | return value; | ... | ... |