Commit fd25140a0de69e83bfc38c411b7838f683e713ed
1 parent
9b984fd6
implement specific Npred computation that avoids integration convergence problems
Showing
3 changed files
with
312 additions
and
1 deletions
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& obsDir, | @@ -754,6 +757,115 @@ double GCTAResponse::irf_diffuse(const GInstDir& 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& srcDir, | @@ -928,8 +1040,9 @@ double GCTAResponse::npsf(const GSkyDir& 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. |