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,20 +284,27 @@ double GCTAResponse::irf(const GInstDir& obsDir, | ||
| 284 | // Compute only if we're sufficiently close to PSF | 284 | // Compute only if we're sufficiently close to PSF |
| 285 | if (delta <= delta_max) { | 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 | } // endif: we were sufficiently close to PSF | 309 | } // endif: we were sufficiently close to PSF |
| 303 | 310 | ||
| @@ -369,17 +376,24 @@ double GCTAResponse::npred(const GSkyDir& srcDir, | @@ -369,17 +376,24 @@ double GCTAResponse::npred(const GSkyDir& srcDir, | ||
| 369 | // Get log10(E/TeV) of true photon energy. | 376 | // Get log10(E/TeV) of true photon energy. |
| 370 | double srcLogEng = srcEng.log10TeV(); | 377 | double srcLogEng = srcEng.log10TeV(); |
| 371 | 378 | ||
| 372 | - // Get IRF components | 379 | + // Get effectve area components |
| 373 | double npred = aeff(theta, phi, zenith, azimuth, srcLogEng); | 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 | // Multiply-in energy dispersion | 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 | // Compile option: Check for NaN/Inf | 398 | // Compile option: Check for NaN/Inf |
| 385 | #if defined(G_NAN_CHECK) | 399 | #if defined(G_NAN_CHECK) |
| @@ -496,8 +510,9 @@ void GCTAResponse::caldb(const std::string& caldb) | @@ -496,8 +510,9 @@ void GCTAResponse::caldb(const std::string& caldb) | ||
| 496 | std::string caldb_expanded = expand_env(caldb); | 510 | std::string caldb_expanded = expand_env(caldb); |
| 497 | 511 | ||
| 498 | // Check if calibration database directory is accessible | 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 | throw GException::caldb_not_found(G_CALDB, caldb_expanded); | 514 | throw GException::caldb_not_found(G_CALDB, caldb_expanded); |
| 515 | + } | ||
| 501 | 516 | ||
| 502 | // Store the path to the calibration database | 517 | // Store the path to the calibration database |
| 503 | m_caldb = caldb_expanded; | 518 | m_caldb = caldb_expanded; |
| @@ -552,8 +567,9 @@ std::string GCTAResponse::print(void) const | @@ -552,8 +567,9 @@ std::string GCTAResponse::print(void) const | ||
| 552 | result.append("=== GCTAResponse ==="); | 567 | result.append("=== GCTAResponse ==="); |
| 553 | result.append("\n"+parformat("Calibration database")+m_caldb); | 568 | result.append("\n"+parformat("Calibration database")+m_caldb); |
| 554 | result.append("\n"+parformat("Response name")+m_rspname); | 569 | result.append("\n"+parformat("Response name")+m_rspname); |
| 555 | - if (m_offset_sigma == 0) | 570 | + if (m_offset_sigma == 0) { |
| 556 | result.append("\n"+parformat("Offset angle dependence")+"none"); | 571 | result.append("\n"+parformat("Offset angle dependence")+"none"); |
| 572 | + } | ||
| 557 | else { | 573 | else { |
| 558 | std::string txt = "Fixed sigma="+str(m_offset_sigma); | 574 | std::string txt = "Fixed sigma="+str(m_offset_sigma); |
| 559 | result.append("\n"+parformat("Offset angle dependence")+txt); | 575 | result.append("\n"+parformat("Offset angle dependence")+txt); |
| @@ -832,7 +848,7 @@ double GCTAResponse::npred_extended(const GModelExtendedSource& model, | @@ -832,7 +848,7 @@ double GCTAResponse::npred_extended(const GModelExtendedSource& model, | ||
| 832 | ctaobs, | 848 | ctaobs, |
| 833 | &rot, | 849 | &rot, |
| 834 | roi_model_distance, | 850 | roi_model_distance, |
| 835 | - roi_radius, | 851 | + roi_psf_radius, |
| 836 | phi); | 852 | phi); |
| 837 | 853 | ||
| 838 | // Integrate over theta | 854 | // Integrate over theta |
| @@ -1013,15 +1029,20 @@ double GCTAResponse::edisp(const double& obsLogEng, | @@ -1013,15 +1029,20 @@ double GCTAResponse::edisp(const double& obsLogEng, | ||
| 1013 | * @param[in] pnt CTA pointing. | 1029 | * @param[in] pnt CTA pointing. |
| 1014 | * @param[in] roi CTA region of interest. | 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 | * Integration is done in a polar coordinate system centred on the PSF since | 1033 | * Integration is done in a polar coordinate system centred on the PSF since |
| 1018 | * the PSF is assumed to be azimuthally symmetric. The polar integration is | 1034 | * the PSF is assumed to be azimuthally symmetric. The polar integration is |
| 1019 | * done using the method npsf_kern_rad_azsym() that computes analytically | 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 | double GCTAResponse::npsf(const GSkyDir& srcDir, | 1047 | double GCTAResponse::npsf(const GSkyDir& srcDir, |
| 1027 | const double& srcLogEng, | 1048 | const double& srcLogEng, |
| @@ -1033,44 +1054,68 @@ double GCTAResponse::npsf(const GSkyDir& srcDir, | @@ -1033,44 +1054,68 @@ double GCTAResponse::npsf(const GSkyDir& srcDir, | ||
| 1033 | double value = 0.0; | 1054 | double value = 0.0; |
| 1034 | 1055 | ||
| 1035 | // Extract relevant parameters from arguments | 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 | value = 1.0; | 1065 | value = 1.0; |
| 1045 | } | 1066 | } |
| 1046 | 1067 | ||
| 1047 | // ... otherwise perform numerical integration | 1068 | // ... otherwise perform numerical integration |
| 1048 | else { | 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 | } // endelse: numerical integration required | 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 | // Return integrated PSF | 1119 | // Return integrated PSF |
| 1075 | return value; | 1120 | return value; |
| 1076 | } | 1121 | } |
| @@ -1202,8 +1247,8 @@ void GCTAResponse::init_members(void) | @@ -1202,8 +1247,8 @@ void GCTAResponse::init_members(void) | ||
| 1202 | m_aeff.clear(); | 1247 | m_aeff.clear(); |
| 1203 | m_r68.clear(); | 1248 | m_r68.clear(); |
| 1204 | m_r80.clear(); | 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 | // Return | 1253 | // Return |
| 1209 | return; | 1254 | return; |
| @@ -1439,9 +1484,10 @@ double GCTAResponse::irf_kern_omega::eval(double omega) | @@ -1439,9 +1484,10 @@ double GCTAResponse::irf_kern_omega::eval(double omega) | ||
| 1439 | m_rsp->psf_dummy(delta, m_sigma); | 1484 | m_rsp->psf_dummy(delta, m_sigma); |
| 1440 | 1485 | ||
| 1441 | // Optionally take energy dispersion into account | 1486 | // Optionally take energy dispersion into account |
| 1442 | - if (m_rsp->hasedisp()) | 1487 | + if (m_rsp->hasedisp() && irf > 0.0) { |
| 1443 | irf *= m_rsp->edisp(m_obsLogEng, theta, phi, m_zenith, m_azimuth, m_srcLogEng); | 1488 | irf *= m_rsp->edisp(m_obsLogEng, theta, phi, m_zenith, m_azimuth, m_srcLogEng); |
| 1444 | - | 1489 | + } |
| 1490 | + | ||
| 1445 | // Compile option: Check for NaN/Inf | 1491 | // Compile option: Check for NaN/Inf |
| 1446 | #if defined(G_NAN_CHECK) | 1492 | #if defined(G_NAN_CHECK) |
| 1447 | if (isnotanumber(irf) || isinfinite(irf)) { | 1493 | if (isnotanumber(irf) || isinfinite(irf)) { |
| @@ -1588,24 +1634,37 @@ double GCTAResponse::npred_kern_phi::eval(double phi) | @@ -1588,24 +1634,37 @@ double GCTAResponse::npred_kern_phi::eval(double phi) | ||
| 1588 | ***************************************************************************/ | 1634 | ***************************************************************************/ |
| 1589 | double GCTAResponse::npsf_kern_rad_azsym::eval(double theta) | 1635 | double GCTAResponse::npsf_kern_rad_azsym::eval(double theta) |
| 1590 | { | 1636 | { |
| 1637 | + // Initialise PSF value | ||
| 1638 | + double value = 0.0; | ||
| 1639 | + | ||
| 1591 | // Get arclength for given radius in radians | 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 | // Return | 1669 | // Return |
| 1611 | return value; | 1670 | return value; |