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 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&amp; 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&amp; 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&amp; 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&amp; 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&amp; 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;
... ...