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 | 30 | /* __ Includes ___________________________________________________________ */ |
| 31 | 31 | #include <cmath> |
| 32 | 32 | #include <vector> |
| 33 | +#include "GMatrix.hpp" | |
| 33 | 34 | #include "GEvent.hpp" |
| 34 | 35 | #include "GModelSky.hpp" |
| 35 | 36 | #include "GModelPointSource.hpp" |
| ... | ... | @@ -56,6 +57,7 @@ |
| 56 | 57 | #include "GCTADir.hpp" |
| 57 | 58 | |
| 58 | 59 | /* __ Forward declaration ________________________________________________ */ |
| 60 | +class GCTAObservation; | |
| 59 | 61 | |
| 60 | 62 | |
| 61 | 63 | /***********************************************************************//** |
| ... | ... | @@ -108,6 +110,10 @@ public: |
| 108 | 110 | const GEnergy& srcEng, |
| 109 | 111 | const GTime& srcTime, |
| 110 | 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 | 118 | // Other Methods |
| 113 | 119 | GCTAEventAtom* mc(const double& area, const GPhoton& photon, |
| ... | ... | @@ -265,6 +271,77 @@ private: |
| 265 | 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 | 345 | // Integration |
| 269 | 346 | class npsf_kern_rad_azsym : public GIntegrand { |
| 270 | 347 | public: | ... | ... |
inst/cta/pyext/GCTAResponse.i
| ... | ... | @@ -75,6 +75,10 @@ public: |
| 75 | 75 | const GEnergy& srcEng, |
| 76 | 76 | const GTime& srcTime, |
| 77 | 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 | 83 | // Other Methods |
| 80 | 84 | GCTAEventAtom* mc(const double& area, const GPhoton& photon, | ... | ... |
inst/cta/src/GCTAResponse.cpp
| ... | ... | @@ -58,6 +58,8 @@ |
| 58 | 58 | " GTime&, GModelExtendedSource&, GEnergy&, GTime&, GObservation&)" |
| 59 | 59 | #define G_IRF_DIFFUSE "GCTAResponse::irf_diffuse(GCTAInstDir&, GEnergy&,"\ |
| 60 | 60 | " GTime&, GModelDiffuseSource&, GEnergy&, GTime&, GCTAObservation&)" |
| 61 | +#define G_NPRED_EXTENDED "GCTAResponse::npred_extended(" \ | |
| 62 | + "GModelExtendedSource&,GEnergy&,GTime&,GObservation&)" | |
| 61 | 63 | #define G_READ "GCTAResponse::read_performance_table(std::string&)" |
| 62 | 64 | |
| 63 | 65 | /* __ Macros _____________________________________________________________ */ |
| ... | ... | @@ -66,6 +68,7 @@ |
| 66 | 68 | |
| 67 | 69 | /* __ Debug definitions __________________________________________________ */ |
| 68 | 70 | //#define G_DEBUG_IRF_EXTENDED //!< Debug irf_extended method |
| 71 | +//#define G_DEBUG_NPRED_EXTENDED //!< Debug npred_extended | |
| 69 | 72 | |
| 70 | 73 | /* __ Constants __________________________________________________________ */ |
| 71 | 74 | |
| ... | ... | @@ -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 | 871 | = Low-level CTA response methods = |
| ... | ... | @@ -928,8 +1040,9 @@ double GCTAResponse::npsf(const GSkyDir& srcDir, |
| 928 | 1040 | |
| 929 | 1041 | // If PSF is sufficiently enclosed by ROI, skip the numerical integration |
| 930 | 1042 | // and assume that the integral is 1.0 |
| 931 | - if (psf+rmax < radroi) | |
| 1043 | + if (psf+rmax < radroi) { | |
| 932 | 1044 | value = 1.0; |
| 1045 | + } | |
| 933 | 1046 | |
| 934 | 1047 | // ... otherwise perform numerical integration |
| 935 | 1048 | else { |
| ... | ... | @@ -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 | 1582 | * @brief Integration kernel for npsf() method |
| 1353 | 1583 | * |
| 1354 | 1584 | * @param[in] theta Zenith angle with respect to PSF centre. | ... | ... |