Commit fd25140a0de69e83bfc38c411b7838f683e713ed

Authored by knodlseder
1 parent 9b984fd6

implement specific Npred computation that avoids integration convergence problems

inst/cta/include/GCTAResponse.hpp
@@ -30,6 +30,7 @@ @@ -30,6 +30,7 @@
30 /* __ Includes ___________________________________________________________ */ 30 /* __ Includes ___________________________________________________________ */
31 #include <cmath> 31 #include <cmath>
32 #include <vector> 32 #include <vector>
  33 +#include "GMatrix.hpp"
33 #include "GEvent.hpp" 34 #include "GEvent.hpp"
34 #include "GModelSky.hpp" 35 #include "GModelSky.hpp"
35 #include "GModelPointSource.hpp" 36 #include "GModelPointSource.hpp"
@@ -56,6 +57,7 @@ @@ -56,6 +57,7 @@
56 #include "GCTADir.hpp" 57 #include "GCTADir.hpp"
57 58
58 /* __ Forward declaration ________________________________________________ */ 59 /* __ Forward declaration ________________________________________________ */
  60 +class GCTAObservation;
59 61
60 62
61 /***********************************************************************//** 63 /***********************************************************************//**
@@ -108,6 +110,10 @@ public: @@ -108,6 +110,10 @@ public:
108 const GEnergy& srcEng, 110 const GEnergy& srcEng,
109 const GTime& srcTime, 111 const GTime& srcTime,
110 const GObservation& obs) const; 112 const GObservation& obs) const;
  113 + virtual double npred_extended(const GModelExtendedSource& model,
  114 + const GEnergy& srcEng,
  115 + const GTime& srcTime,
  116 + const GObservation& obs) const;
111 117
112 // Other Methods 118 // Other Methods
113 GCTAEventAtom* mc(const double& area, const GPhoton& photon, 119 GCTAEventAtom* mc(const double& area, const GPhoton& photon,
@@ -265,6 +271,77 @@ private: @@ -265,6 +271,77 @@ private:
265 double m_sin_ph; //!< Sine term for photon offset angle computation 271 double m_sin_ph; //!< Sine term for photon offset angle computation
266 }; 272 };
267 273
  274 + // Npred theta integration kernel
  275 + class npred_kern_theta : public GIntegrand {
  276 + public:
  277 + npred_kern_theta(const GCTAResponse* rsp,
  278 + const GModelRadial* radial,
  279 + const GEnergy* srcEng,
  280 + const GTime* srcTime,
  281 + const GCTAObservation* obs,
  282 + const GMatrix* rot,
  283 + double dist,
  284 + double radius,
  285 + double phi) :
  286 + m_rsp(rsp),
  287 + m_radial(radial),
  288 + m_srcEng(srcEng),
  289 + m_srcTime(srcTime),
  290 + m_obs(obs),
  291 + m_rot(rot),
  292 + m_dist(dist),
  293 + m_cos_dist(std::cos(dist)),
  294 + m_sin_dist(std::sin(dist)),
  295 + m_radius(radius),
  296 + m_cos_radius(std::cos(radius)),
  297 + m_phi(phi) { return; }
  298 + double eval(double theta);
  299 + protected:
  300 + const GCTAResponse* m_rsp; //!< Pointer to response
  301 + const GModelRadial* m_radial; //!< Pointer to radial spatial model
  302 + const GEnergy* m_srcEng; //!< Pointer to true photon energy
  303 + const GTime* m_srcTime; //!< Pointer to true photon arrival time
  304 + const GCTAObservation* m_obs; //!< Pointer to observation
  305 + const GMatrix* m_rot; //!< Rotation matrix
  306 + double m_dist; //!< Distance model-ROI centre
  307 + double m_cos_dist; //!< Cosine of distance model-ROI centre
  308 + double m_sin_dist; //!< Sine of distance model-ROI centre
  309 + double m_radius; //!< ROI+PSF radius
  310 + double m_cos_radius; //!< Cosine of ROI+PSF radius
  311 + double m_phi; //!< Position angle of ROI
  312 + };
  313 +
  314 + // Npred phi integration kernel
  315 + class npred_kern_phi : public GIntegrand {
  316 + public:
  317 + npred_kern_phi(const GCTAResponse* rsp,
  318 + const GEnergy* srcEng,
  319 + const GTime* srcTime,
  320 + const GCTAObservation* obs,
  321 + const GMatrix* rot,
  322 + double theta,
  323 + double sin_theta) :
  324 + m_rsp(rsp),
  325 + m_srcEng(srcEng),
  326 + m_srcTime(srcTime),
  327 + m_obs(obs),
  328 + m_rot(rot),
  329 + m_theta(theta),
  330 + m_cos_theta(std::cos(theta)),
  331 + m_sin_theta(sin_theta) { return; }
  332 + double eval(double phi);
  333 + protected:
  334 + const GCTAResponse* m_rsp; //!< Pointer to response
  335 + const GModelRadial* m_radial; //!< Pointer to radial spatial model
  336 + const GEnergy* m_srcEng; //!< Pointer to true photon energy
  337 + const GTime* m_srcTime; //!< Pointer to true photon arrival time
  338 + const GCTAObservation* m_obs; //!< Pointer to observation
  339 + const GMatrix* m_rot; //!< Rotation matrix
  340 + double m_theta; //!< Offset angle (radians)
  341 + double m_cos_theta; //!< cosine of offset angle
  342 + double m_sin_theta; //!< Sine of offset angle
  343 + };
  344 +
268 // Integration 345 // Integration
269 class npsf_kern_rad_azsym : public GIntegrand { 346 class npsf_kern_rad_azsym : public GIntegrand {
270 public: 347 public:
inst/cta/pyext/GCTAResponse.i
@@ -75,6 +75,10 @@ public: @@ -75,6 +75,10 @@ public:
75 const GEnergy& srcEng, 75 const GEnergy& srcEng,
76 const GTime& srcTime, 76 const GTime& srcTime,
77 const GObservation& obs) const; 77 const GObservation& obs) const;
  78 + virtual double npred_extended(const GModelExtendedSource& model,
  79 + const GEnergy& srcEng,
  80 + const GTime& srcTime,
  81 + const GObservation& obs) const;
78 82
79 // Other Methods 83 // Other Methods
80 GCTAEventAtom* mc(const double& area, const GPhoton& photon, 84 GCTAEventAtom* mc(const double& area, const GPhoton& photon,
inst/cta/src/GCTAResponse.cpp
@@ -58,6 +58,8 @@ @@ -58,6 +58,8 @@
58 " GTime&, GModelExtendedSource&, GEnergy&, GTime&, GObservation&)" 58 " GTime&, GModelExtendedSource&, GEnergy&, GTime&, GObservation&)"
59 #define G_IRF_DIFFUSE "GCTAResponse::irf_diffuse(GCTAInstDir&, GEnergy&,"\ 59 #define G_IRF_DIFFUSE "GCTAResponse::irf_diffuse(GCTAInstDir&, GEnergy&,"\
60 " GTime&, GModelDiffuseSource&, GEnergy&, GTime&, GCTAObservation&)" 60 " GTime&, GModelDiffuseSource&, GEnergy&, GTime&, GCTAObservation&)"
  61 +#define G_NPRED_EXTENDED "GCTAResponse::npred_extended(" \
  62 + "GModelExtendedSource&,GEnergy&,GTime&,GObservation&)"
61 #define G_READ "GCTAResponse::read_performance_table(std::string&)" 63 #define G_READ "GCTAResponse::read_performance_table(std::string&)"
62 64
63 /* __ Macros _____________________________________________________________ */ 65 /* __ Macros _____________________________________________________________ */
@@ -66,6 +68,7 @@ @@ -66,6 +68,7 @@
66 68
67 /* __ Debug definitions __________________________________________________ */ 69 /* __ Debug definitions __________________________________________________ */
68 //#define G_DEBUG_IRF_EXTENDED //!< Debug irf_extended method 70 //#define G_DEBUG_IRF_EXTENDED //!< Debug irf_extended method
  71 +//#define G_DEBUG_NPRED_EXTENDED //!< Debug npred_extended
69 72
70 /* __ Constants __________________________________________________________ */ 73 /* __ Constants __________________________________________________________ */
71 74
@@ -754,6 +757,115 @@ double GCTAResponse::irf_diffuse(const GInstDir&amp; obsDir, @@ -754,6 +757,115 @@ double GCTAResponse::irf_diffuse(const GInstDir&amp; obsDir,
754 } 757 }
755 758
756 759
  760 +/***********************************************************************//**
  761 + * @brief Return spatial integral of extended source model
  762 + *
  763 + * @param[in] model Extended source model.
  764 + * @param[in] srcEng True energy of photon.
  765 + * @param[in] srcTime True photon arrival time.
  766 + * @param[in] obs Observation.
  767 + ***************************************************************************/
  768 +double GCTAResponse::npred_extended(const GModelExtendedSource& model,
  769 + const GEnergy& srcEng,
  770 + const GTime& srcTime,
  771 + const GObservation& obs) const
  772 +{
  773 + // Initialise Npred value
  774 + double npred = 0.0;
  775 +
  776 + // Get pointer on CTA observation
  777 + const GCTAObservation* ctaobs = dynamic_cast<const GCTAObservation*>(&obs);
  778 + if (ctaobs == NULL)
  779 + throw GCTAException::bad_observation_type(G_NPRED_EXTENDED);
  780 +
  781 + // Get pointer on CTA events list
  782 + const GCTAEventList* events = dynamic_cast<const GCTAEventList*>(ctaobs->events());
  783 + if (events == NULL)
  784 + throw GException::no_list(G_NPRED_EXTENDED);
  785 +
  786 + // Get log10(E/TeV) of true photon energy
  787 + double srcLogEng = srcEng.log10TeV();
  788 +
  789 + // Get maximum PSF radius (radians)
  790 + double sigma = psf_dummy_sigma(srcLogEng);
  791 + double psf_max_radius = psf_dummy_max(sigma);
  792 +
  793 + // Extract ROI radius (radians)
  794 + double roi_radius = events->roi().radius() * deg2rad;
  795 +
  796 + // Compute distance between ROI and model centre (radians)
  797 + double roi_model_distance = events->roi().centre().dist(model.radial()->dir());
  798 +
  799 + // Compute the ROI radius plus maximum PSF radius (radians). Any photon
  800 + // coming from beyond this radius will not make it in the dataspace and
  801 + // thus can be neglected.
  802 + double roi_psf_radius = roi_radius + psf_max_radius;
  803 +
  804 + // Set offset angle integration range. We take here the ROI+PSF into
  805 + // account to make no integrations beyond the point where the
  806 + // contribution drops to zero.
  807 + double theta_min = (roi_model_distance > roi_psf_radius)
  808 + ? roi_model_distance - roi_psf_radius: 0.0;
  809 + double theta_max = model.radial()->theta_max();
  810 +
  811 + // Perform offset angle integration only if interval is valid
  812 + if (theta_max > theta_min) {
  813 +
  814 + // Compute rotation matrix to convert from native model coordinates,
  815 + // given by (theta,phi), into celestial coordinates.
  816 + GMatrix ry;
  817 + GMatrix rz;
  818 + GMatrix rot;
  819 + ry.eulery(model.radial()->dec() - 90.0);
  820 + rz.eulerz(-model.radial()->ra());
  821 + rot = transpose(ry * rz);
  822 +
  823 + // Compute position angle of ROI centre with respect to model
  824 + // centre (radians)
  825 + double phi = model.radial()->dir().posang(events->roi().centre().skydir());
  826 +
  827 + // Setup integration kernel
  828 + GCTAResponse::npred_kern_theta integrand(this,
  829 + model.radial(),
  830 + &srcEng,
  831 + &srcTime,
  832 + ctaobs,
  833 + &rot,
  834 + roi_model_distance,
  835 + roi_radius,
  836 + phi);
  837 +
  838 + // Integrate over theta
  839 + GIntegral integral(&integrand);
  840 + npred = integral.romb(theta_min, theta_max);
  841 +
  842 + // Compile option: Show integration results
  843 + #if defined(G_DEBUG_NPRED_EXTENDED)
  844 + std::cout << "npred_extended:";
  845 + std::cout << " theta_min=" << theta_min;
  846 + std::cout << " theta_max=" << theta_max;
  847 + std::cout << " npred=" << npred << std::endl;
  848 + #endif
  849 +
  850 + } // endif: offset angle range was valid
  851 +
  852 + // Debug: Check for NaN
  853 + #if defined(G_NAN_CHECK)
  854 + if (isnotanumber(npred) || isinfinite(npred)) {
  855 + std::cout << "*** ERROR: GCTAResponse::npred_extended:";
  856 + std::cout << " NaN/Inf encountered";
  857 + std::cout << " (npred=" << npred;
  858 + std::cout << ", theta_min=" << theta_min;
  859 + std::cout << ", theta_max=" << theta_max;
  860 + std::cout << ")" << std::endl;
  861 + }
  862 + #endif
  863 +
  864 + // Return Npred
  865 + return npred;
  866 +}
  867 +
  868 +
757 /*========================================================================== 869 /*==========================================================================
758 = = 870 = =
759 = Low-level CTA response methods = 871 = Low-level CTA response methods =
@@ -928,8 +1040,9 @@ double GCTAResponse::npsf(const GSkyDir&amp; srcDir, @@ -928,8 +1040,9 @@ double GCTAResponse::npsf(const GSkyDir&amp; srcDir,
928 1040
929 // If PSF is sufficiently enclosed by ROI, skip the numerical integration 1041 // If PSF is sufficiently enclosed by ROI, skip the numerical integration
930 // and assume that the integral is 1.0 1042 // and assume that the integral is 1.0
931 - if (psf+rmax < radroi) 1043 + if (psf+rmax < radroi) {
932 value = 1.0; 1044 value = 1.0;
  1045 + }
933 1046
934 // ... otherwise perform numerical integration 1047 // ... otherwise perform numerical integration
935 else { 1048 else {
@@ -1349,6 +1462,123 @@ double GCTAResponse::irf_kern_omega::eval(double omega) @@ -1349,6 +1462,123 @@ double GCTAResponse::irf_kern_omega::eval(double omega)
1349 1462
1350 1463
1351 /***********************************************************************//** 1464 /***********************************************************************//**
  1465 + * @brief Kernel for zenith angle Npred integration or radial model
  1466 + *
  1467 + * @param[in] theta Radial model zenith angle (radians).
  1468 + *
  1469 + * This method integrates a radial model for a given zenith angle theta over
  1470 + * all azimuth angles that fall within the ROI+PSF radius. The limitation to
  1471 + * an arc assures that the integration converges properly.
  1472 + *
  1473 + * @todo Rename method to npred_radial_kern_theta.
  1474 + ***************************************************************************/
  1475 +double GCTAResponse::npred_kern_theta::eval(double theta)
  1476 +{
  1477 + // Initialise Npred value
  1478 + double npred = 0.0;
  1479 +
  1480 + // Compute half length of arc that lies within ROI+PSF radius (radians)
  1481 + double dphi = 0.5 * cta_roi_arclength(theta,
  1482 + m_dist,
  1483 + m_cos_dist,
  1484 + m_sin_dist,
  1485 + m_radius,
  1486 + m_cos_radius);
  1487 +
  1488 +
  1489 + // Continue only if arc length is positive
  1490 + if (dphi > 0.0) {
  1491 +
  1492 + // Compute phi integration range
  1493 + double phi_min = m_phi - dphi;
  1494 + double phi_max = m_phi + dphi;
  1495 +
  1496 + // Get radial model value
  1497 + double model = m_radial->eval(theta);
  1498 +
  1499 + // Compute sine of offset angle
  1500 + double sin_theta = std::sin(theta);
  1501 +
  1502 + // Setup phi integration kernel
  1503 + GCTAResponse::npred_kern_phi integrand(m_rsp,
  1504 + m_srcEng,
  1505 + m_srcTime,
  1506 + m_obs,
  1507 + m_rot,
  1508 + theta,
  1509 + sin_theta);
  1510 +
  1511 + // Integrate over phi
  1512 + GIntegral integral(&integrand);
  1513 + npred = integral.romb(phi_min, phi_max) * sin_theta * model;
  1514 +
  1515 + // Debug: Check for NaN
  1516 + #if defined(G_NAN_CHECK)
  1517 + if (isnotanumber(npred) || isinfinite(npred)) {
  1518 + std::cout << "*** ERROR: GCTAResponse::npred_kern_theta::eval";
  1519 + std::cout << "(theta=" << theta << "):";
  1520 + std::cout << " NaN/Inf encountered";
  1521 + std::cout << " (npred=" << npred;
  1522 + std::cout << ", model=" << model;
  1523 + std::cout << ", phi=[" << phi_min << "," << phi_max << "]";
  1524 + std::cout << ", sin_theta=" << sin_theta;
  1525 + std::cout << ")" << std::endl;
  1526 + }
  1527 + #endif
  1528 +
  1529 + } // endif: arc length was positive
  1530 +
  1531 + // Return Npred
  1532 + return npred;
  1533 +}
  1534 +
  1535 +
  1536 +/***********************************************************************//**
  1537 + * @brief Kernel for azimuth angle Npred integration of radial model
  1538 + *
  1539 + * @param[in] phi Azimuth angle (radians).
  1540 + *
  1541 + * @todo Rename method to npred_radial_kern_phi.
  1542 + * @todo Re-consider formula for possible simplification (dumb matrix
  1543 + * multiplication is definitely not the fastest way to do that
  1544 + * computation).
  1545 + ***************************************************************************/
  1546 +double GCTAResponse::npred_kern_phi::eval(double phi)
  1547 +{
  1548 + // Compute sky direction vector in native coordinates
  1549 + double cos_phi = std::cos(phi);
  1550 + double sin_phi = std::sin(phi);
  1551 + GVector native(-cos_phi*m_sin_theta, sin_phi*m_sin_theta, m_cos_theta);
  1552 +
  1553 + // Rotate from native into celestial system
  1554 + GVector cel = *m_rot * native;
  1555 +
  1556 + // Set sky direction
  1557 + GSkyDir srcDir;
  1558 + srcDir.celvector(cel);
  1559 +
  1560 + // Compute Npred for this sky direction
  1561 + double npred = m_rsp->npred(srcDir, *m_srcEng, *m_srcTime, *m_obs);
  1562 +
  1563 + // Debug: Check for NaN
  1564 + #if defined(G_NAN_CHECK)
  1565 + if (isnotanumber(npred) || isinfinite(npred)) {
  1566 + std::cout << "*** ERROR: GCTAResponse::npred_kern_phi::eval";
  1567 + std::cout << "(phi=" << phi << "):";
  1568 + std::cout << " NaN/Inf encountered";
  1569 + std::cout << " (npred=" << npred;
  1570 + std::cout << ", cos_phi=" << cos_phi;
  1571 + std::cout << ", sin_phi=" << sin_phi;
  1572 + std::cout << ")" << std::endl;
  1573 + }
  1574 + #endif
  1575 +
  1576 + // Return Npred
  1577 + return npred;
  1578 +}
  1579 +
  1580 +
  1581 +/***********************************************************************//**
1352 * @brief Integration kernel for npsf() method 1582 * @brief Integration kernel for npsf() method
1353 * 1583 *
1354 * @param[in] theta Zenith angle with respect to PSF centre. 1584 * @param[in] theta Zenith angle with respect to PSF centre.