Commit 6c5c214b77f4702458008d2d3a4590d728d9269e

Authored by knodlseder
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&amp; srcDir, @@ -369,17 +376,24 @@ double GCTAResponse::npred(const GSkyDir&amp; 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&amp; caldb) @@ -496,8 +510,9 @@ void GCTAResponse::caldb(const std::string&amp; 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&amp; model, @@ -832,7 +848,7 @@ double GCTAResponse::npred_extended(const GModelExtendedSource&amp; 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&amp; obsLogEng, @@ -1013,15 +1029,20 @@ double GCTAResponse::edisp(const double&amp; 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&amp; srcDir, @@ -1033,44 +1054,68 @@ double GCTAResponse::npsf(const GSkyDir&amp; 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;