Commit 89b294ab64795e6f8bef3a3ef480109b940183c9

Authored by Jürgen Knödlseder
1 parent 1ee6318e

Improve elliptical Irf and Npred computation.

The binned analysis looks okay, but unbinned shows a strange behaviour for the semiminor axis parameter which results in a strong dependency of Npred on the semiminor axis value. This seems to prevent a nice convergence in unbinned mode.

There is also an open issue to be solved related to the possibility that the minor axis is larger than the major axis. The actual code assumes that minor axis < major axis. If this is not fulfilled, the omega arc computations will be wrong.
include/GModelSpatialEllipticalDisk.hpp
1 1 /***************************************************************************
2 2 * GModelSpatialEllipticalDisk.hpp - Elliptical disk source model class *
3 3 * ----------------------------------------------------------------------- *
4   - * copyright (C) 2013 by Michael Mayer *
  4 + * copyright (C) 2013-2014 by Michael Mayer *
5 5 * ----------------------------------------------------------------------- *
6 6 * *
7 7 * This program is free software: you can redistribute it and/or modify *
... ...
inst/cta/src/GCTAResponseIrf.cpp
... ... @@ -1755,10 +1755,21 @@ double GCTAResponseIrf::irf_elliptical(const GEvent&amp; event,
1755 1755 double delta_max = psf_delta_max(theta, phi, zenith, azimuth, srcLogEng);
1756 1756  
1757 1757 // Get semimajor and semiminor ellipse boundaries [radians]
1758   - double aspect_ratio = (model->semiminor() > 0) ?
1759   - model->semimajor() / model->semiminor() : 0.0;
  1758 + /*
  1759 + double aspect_ratio = (model->semimajor() > 0) ?
  1760 + model->semiminor() / model->semimajor() : 0.0;
1760 1761 double semimajor = model->theta_max();
1761 1762 double semiminor = semimajor * aspect_ratio;
  1763 + */
  1764 + double semimajor = model->semimajor() * gammalib::deg2rad;
  1765 + double semiminor = model->semiminor() * gammalib::deg2rad;
  1766 +
  1767 + //TEST TEST TEST
  1768 + if (semimajor < semiminor) {
  1769 + std::cout << "Warning:";
  1770 + std::cout << " semiminor=" << semiminor * gammalib::rad2deg;
  1771 + std::cout << " semimajor=" << semimajor * gammalib::rad2deg;
  1772 + }
1762 1773  
1763 1774 // Set zenith angle integration range for elliptical model
1764 1775 double rho_min = (rho_obs > delta_max) ? rho_obs - delta_max : 0.0;
... ... @@ -2326,10 +2337,21 @@ double GCTAResponseIrf::npred_elliptical(const GSource&amp; source,
2326 2337 double rho_roi = roi.centre().dir().dist(centre);
2327 2338  
2328 2339 // Get semimajor and semiminor ellipse boundaries [radians]
2329   - double aspect_ratio = (model->semiminor() > 0) ?
2330   - model->semimajor() / model->semiminor() : 0.0;
  2340 + /*
  2341 + double aspect_ratio = (model->semimajor() > 0) ?
  2342 + model->semiminor() / model->semimajor() : 0.0;
2331 2343 double semimajor = model->theta_max();
2332 2344 double semiminor = semimajor * aspect_ratio;
  2345 + */
  2346 + double semimajor = model->semimajor() * gammalib::deg2rad;
  2347 + double semiminor = model->semiminor() * gammalib::deg2rad;
  2348 +
  2349 + //TEST TEST TEST
  2350 + if (semimajor < semiminor) {
  2351 + std::cout << "Warning:";
  2352 + std::cout << " semiminor=" << semiminor * gammalib::rad2deg;
  2353 + std::cout << " semimajor=" << semimajor * gammalib::rad2deg;
  2354 + }
2333 2355  
2334 2356 // Set offset angle integration range. We take here the ROI+PSF into
2335 2357 // account to make no integrations beyond the point where the
... ... @@ -2361,8 +2383,8 @@ double GCTAResponseIrf::npred_elliptical(const GSource&amp; source,
2361 2383 *model,
2362 2384 semimajor,
2363 2385 semiminor,
2364   - source.energy(),
2365   - source.time(),
  2386 + srcEng,
  2387 + srcTime,
2366 2388 cta,
2367 2389 rot,
2368 2390 rho_roi,
... ...
inst/cta/src/GCTAResponse_helpers.cpp
... ... @@ -46,6 +46,7 @@
46 46  
47 47 /* __ Debug definitions __________________________________________________ */
48 48 //#define G_DEBUG_INTEGRAL //!< Debug integration
  49 +#define G_DEBUG_MODEL_ZERO //!< Debug check for zero model values
49 50  
50 51 /* __ Constants __________________________________________________________ */
51 52  
... ... @@ -623,6 +624,14 @@ double cta_irf_elliptical_kern_rho::eval(const double&amp; rho)
623 624 double cos_ph = cos_rho * m_cos_rho_pnt;
624 625 double sin_ph = sin_rho * m_sin_rho_pnt;
625 626  
  627 + // Reduce rho by an infinite amount to avoid rounding errors
  628 + // at the boundary of a sharp edged model (e.g. an elliptical
  629 + // disk model)
  630 + double rho_kluge = rho - 1.0e-12;
  631 + if (rho_kluge < 0.0) {
  632 + rho_kluge = 0.0;
  633 + }
  634 +
626 635 // Setup integration kernel
627 636 cta_irf_elliptical_kern_omega integrand(m_rsp,
628 637 m_model,
... ... @@ -634,7 +643,7 @@ double cta_irf_elliptical_kern_rho::eval(const double&amp; rho)
634 643 m_obsEng,
635 644 m_posangle_obs,
636 645 m_omega_pnt,
637   - rho,
  646 + rho_kluge,
638 647 cos_psf,
639 648 sin_psf,
640 649 cos_ph,
... ... @@ -790,8 +799,32 @@ double cta_irf_elliptical_kern_omega::eval(const double&amp; omega)
790 799 // Initialise IRF value
791 800 double irf = 0.0;
792 801  
  802 + // Compute azimuth angle in model coordinate system (radians)
  803 + double omega_model = omega + m_posangle_obs;
  804 +
793 805 // Evaluate sky model
794   - double model = m_model.eval(m_rho, omega + m_posangle_obs, m_srcEng, m_srcTime);
  806 + double model = m_model.eval(m_rho, omega_model, m_srcEng, m_srcTime);
  807 +
  808 + // Debug: test if model is non positive
  809 + #if defined(G_DEBUG_MODEL_ZERO)
  810 + if (model <= 0.0) {
  811 + double m_semiminor_rad = m_model.semiminor() * gammalib::deg2rad;
  812 + double m_semimajor_rad = m_model.semimajor() * gammalib::deg2rad;
  813 + double diff_angle = omega_model - m_model.posangle() * gammalib::deg2rad;
  814 + double cosinus = std::cos(diff_angle);
  815 + double sinus = std::sin(diff_angle);
  816 + double arg1 = m_semiminor_rad * cosinus;
  817 + double arg2 = m_semimajor_rad * sinus;
  818 + double r_ellipse = m_semiminor_rad * m_semimajor_rad /
  819 + std::sqrt(arg1*arg1 + arg2*arg2);
  820 + std::cout << "*** WARNING: cta_irf_elliptical_kern_omega::eval";
  821 + std::cout << " zero model for (rho,omega)=";
  822 + std::cout << m_rho*gammalib::rad2deg << ",";
  823 + std::cout << omega*gammalib::rad2deg << ")";
  824 + std::cout << " rho-r_ellipse=" << (m_rho-r_ellipse) << " radians";
  825 + std::cout << std::endl;
  826 + }
  827 + #endif
795 828  
796 829 // Continue only if model is positive
797 830 if (model > 0.0) {
... ... @@ -851,10 +884,20 @@ double cta_irf_elliptical_kern_omega::eval(const double&amp; omega)
851 884 * N_{\rm pred}(\rho,\omega) d\omega
852 885 * \f]
853 886 *
854   - * The azimuth angle integration range
855   - * \f$[\omega_{\rm min}, \omega_{\rm max}\f$
856   - * is limited to an arc around the vector connecting the model centre to
857   - * the ROI centre. This limitation assures proper converges properly.
  887 + * where
  888 + * \f$\omega\f$ is the azimuth angle with respect to the model centre,
  889 + * counted counterclockwise from the vector connecting the model centre
  890 + * to the ROI centre, and
  891 + * \f$\rho\f$ is the radial distance from the model centre.
  892 + * \f$S_{\rm p}(\rho, \omega | E, t)\f$ is the elliptical source model
  893 + * for a given true photon energy and photon arrival time,
  894 + * \f$N_{\rm pred}(\rho,\omega)\f$ is the data space integral over the
  895 + * response function.
  896 + *
  897 + * The method performs the required coordinate transformations from the
  898 + * model system, spanned by \f$(\rho, \omega)\f$, to the system needed for
  899 + * Npred computations. Furthermore, the method limits the integration range
  900 + * to area where the ellipse intersects the ROI.
858 901 ***************************************************************************/
859 902 double cta_npred_elliptical_kern_rho::eval(const double& rho)
860 903 {
... ... @@ -875,14 +918,18 @@ double cta_npred_elliptical_kern_rho::eval(const double&amp; rho)
875 918 // Continue only if arc length is positive
876 919 if (domega > 0.0) {
877 920  
878   - // Compute phi integration range
879   - double omega_min = m_posangle_roi - domega;
880   - double omega_max = m_posangle_roi + domega;
881   -
882   - // Compute sine and cosine of offset angle
  921 + // Compute sine and cosine terms for azimuthal integration
883 922 double sin_rho = std::sin(rho);
884 923 double cos_rho = std::cos(rho);
885 924  
  925 + // Reduce rho by an infinite amount to avoid rounding errors
  926 + // at the boundary of a sharp edged model (e.g. an elliptical
  927 + // disk model)
  928 + double rho_kluge = rho - 1.0e-12;
  929 + if (rho_kluge < 0.0) {
  930 + rho_kluge = 0.0;
  931 + }
  932 +
886 933 // Setup phi integration kernel
887 934 cta_npred_elliptical_kern_omega integrand(m_rsp,
888 935 m_model,
... ... @@ -890,8 +937,10 @@ double cta_npred_elliptical_kern_rho::eval(const double&amp; rho)
890 937 m_srcTime,
891 938 m_obs,
892 939 m_rot,
  940 + rho_kluge,
893 941 sin_rho,
894   - cos_rho);
  942 + cos_rho,
  943 + m_posangle_roi);
895 944  
896 945 // Setup integrator
897 946 GIntegral integral(&integrand);
... ... @@ -928,11 +977,10 @@ double cta_npred_elliptical_kern_rho::eval(const double&amp; rho)
928 977 if (omega_width > 0.0) {
929 978  
930 979 // Compute azimuth angle difference between ellipse
931   - // position angle and position angle of observed
932   - // photon in the model system. This angle will define
933   - // the reference point around which the circle arc. Make
934   - // sure that omega_0 is within [-pi,pi] thus that the omega
935   - // intervals are between [-2pi,2pi]
  980 + // position angle and position angle of ROI in the model
  981 + // system. This angle will define the reference point for
  982 + // the circle arcs. Make sure that omega_0 is within [-pi,pi]
  983 + // thus that the omega intervals are between [-2pi,2pi]
936 984 double omega_0 = m_model.posangle() * gammalib::deg2rad -
937 985 m_posangle_roi;
938 986 if (omega_0 > gammalib::pi) {
... ... @@ -1005,34 +1053,68 @@ double cta_npred_elliptical_kern_rho::eval(const double&amp; rho)
1005 1053 * Computes
1006 1054 *
1007 1055 * \f[
1008   - * S_{\rm p}(\rho,\omega | E, t) \, N_{\rm pred}(\rho,\omega)
  1056 + * S_{\rm p}(\omega | \rho, E, t) \, N_{\rm pred}(\omega | \rho)
1009 1057 * \f]
  1058 + *
  1059 + * where
  1060 + * \f$\omega\f$ is the azimuth angle with respect to the model centre,
  1061 + * counted counterclockwise from the vector connecting the model centre
  1062 + * to the centre of the Region of Interest (ROI), and
  1063 + * \f$\rho\f$ is the radial distance from the model centre.
  1064 + *
  1065 + * @todo Npred computation goes over sky coordinates. This can maybe be
  1066 + * optimized to reduce the number of coordinate transformations.
  1067 + * @todo Check whether the Npred omega argument is the right one.
1010 1068 ***************************************************************************/
1011 1069 double cta_npred_elliptical_kern_omega::eval(const double& omega)
1012 1070 {
1013 1071 // Initialise Npred value
1014 1072 double npred = 0.0;
1015 1073  
1016   - // Compute sky direction vector in native coordinates
1017   - double cos_omega = std::cos(omega);
1018   - double sin_omega = std::sin(omega);
1019   - GVector native(-cos_omega*m_sin_rho, sin_omega*m_sin_rho, m_cos_rho);
1020   -
1021   - // Rotate from native into celestial system
1022   - GVector cel = m_rot * native;
  1074 + // Compute azimuth angle in model coordinate system (radians)
  1075 + double omega_model = omega + m_posangle_roi;
1023 1076  
1024   - // Set sky direction
1025   - GSkyDir srcDir;
1026   - srcDir.celvector(cel);
  1077 + // Evaluate sky model
  1078 + double model = m_model.eval(m_rho, omega_model, m_srcEng, m_srcTime);
  1079 +
  1080 + // Debug: test if model is non positive
  1081 + #if defined(G_DEBUG_MODEL_ZERO)
  1082 + if (model <= 0.0) {
  1083 + double m_semiminor_rad = m_model.semiminor() * gammalib::deg2rad;
  1084 + double m_semimajor_rad = m_model.semimajor() * gammalib::deg2rad;
  1085 + double diff_angle = omega_model - m_model.posangle() * gammalib::deg2rad;
  1086 + double cosinus = std::cos(diff_angle);
  1087 + double sinus = std::sin(diff_angle);
  1088 + double arg1 = m_semiminor_rad * cosinus;
  1089 + double arg2 = m_semimajor_rad * sinus;
  1090 + double r_ellipse = m_semiminor_rad * m_semimajor_rad /
  1091 + std::sqrt(arg1*arg1 + arg2*arg2);
  1092 + std::cout << "*** WARNING: cta_npred_elliptical_kern_omega::eval";
  1093 + std::cout << " zero model for (rho,omega)=";
  1094 + std::cout << m_rho*gammalib::rad2deg << ",";
  1095 + std::cout << omega*gammalib::rad2deg << ")";
  1096 + std::cout << " rho-r_ellipse=" << (m_rho-r_ellipse) << " radians";
  1097 + std::cout << std::endl;
  1098 + }
  1099 + #endif
  1100 +
  1101 + // Continue only if model is positive
  1102 + if (model > 0.0) {
  1103 +
  1104 + // Compute sky direction vector in native coordinates
  1105 + double cos_omega = std::cos(omega_model);
  1106 + double sin_omega = std::sin(omega_model);
  1107 + GVector native(-cos_omega*m_sin_rho, sin_omega*m_sin_rho, m_cos_rho);
1027 1108  
1028   - // Set Photon
1029   - GPhoton photon(srcDir, m_srcEng, m_srcTime);
  1109 + // Rotate from native into celestial system
  1110 + GVector cel = m_rot * native;
1030 1111  
1031   - // Get model value for this photon
1032   - double model = m_model.eval(photon);
  1112 + // Set sky direction
  1113 + GSkyDir srcDir;
  1114 + srcDir.celvector(cel);
1033 1115  
1034   - // Continue only if sky intensity is positive
1035   - if (model > 0.0) {
  1116 + // Set Photon
  1117 + GPhoton photon(srcDir, m_srcEng, m_srcTime);
1036 1118  
1037 1119 // Compute Npred for this sky direction
1038 1120 npred = m_rsp.npred(photon, m_obs) * model;
... ...
inst/cta/src/GCTAResponse_helpers.hpp
... ... @@ -687,26 +687,32 @@ public:
687 687 const GTime& srcTime,
688 688 const GCTAObservation& obs,
689 689 const GMatrix& rot,
  690 + const double& rho,
690 691 const double& sin_rho,
691   - const double& cos_rho) :
  692 + const double& cos_rho,
  693 + const double& posangle_roi) :
692 694 m_rsp(rsp),
693 695 m_model(model),
694 696 m_srcEng(srcEng),
695 697 m_srcTime(srcTime),
696 698 m_obs(obs),
697 699 m_rot(rot),
  700 + m_rho(rho),
698 701 m_sin_rho(sin_rho),
699   - m_cos_rho(cos_rho) { }
  702 + m_cos_rho(cos_rho),
  703 + m_posangle_roi(posangle_roi) { }
700 704 double eval(const double& omega);
701 705 protected:
702   - const GCTAResponseIrf& m_rsp; //!< CTA response
703   - const GModelSpatialElliptical& m_model; //!< Model
704   - const GEnergy& m_srcEng; //!< True photon energy
705   - const GTime& m_srcTime; //!< True photon arrival time
706   - const GCTAObservation& m_obs; //!< Pointer to observation
707   - const GMatrix& m_rot; //!< Rotation matrix
708   - const double& m_sin_rho; //!< Sine of offset angle
709   - const double& m_cos_rho; //!< Cosine of offset angle
  706 + const GCTAResponseIrf& m_rsp; //!< CTA response
  707 + const GModelSpatialElliptical& m_model; //!< Model
  708 + const GEnergy& m_srcEng; //!< True photon energy
  709 + const GTime& m_srcTime; //!< True photon arrival time
  710 + const GCTAObservation& m_obs; //!< Pointer to observation
  711 + const GMatrix& m_rot; //!< Rotation matrix
  712 + const double& m_rho;
  713 + const double& m_sin_rho; //!< Sine of offset angle
  714 + const double& m_cos_rho; //!< Cosine of offset angle
  715 + const double& m_posangle_roi; //!< Position angle of ROI
710 716 };
711 717  
712 718  
... ...
inst/cta/test/test_irf_elliptical.py
... ... @@ -413,8 +413,10 @@ if __name__ == &#39;__main__&#39;:
413 413 # Set model parameters
414 414 centre = gammalib.GSkyDir()
415 415 centre.radec_deg(0.0, 0.0)
416   - semimajor = 1.0
417   - semiminor = 0.50
  416 + #semimajor = 1.0
  417 + #semiminor = 0.50
  418 + semimajor = 0.5
  419 + semiminor = 1.0
418 420 posang = 120.0
419 421  
420 422 # Set observed photon direction
... ...
inst/cta/test/test_npred_elliptical.py
... ... @@ -180,9 +180,12 @@ def show_response(centre, semimajor, semiminor, posang, roiDir, pntDir, roi_radi
180 180  
181 181 # Rho limits
182 182 rho_min = 0.0
183   - rho_max = theta_max
  183 + #rho_max = theta_max
  184 + rho_max = rho_roi + radius_roi
184 185 if (rho_roi > radius_roi):
185 186 rho_min = rho_roi - radius_roi
  187 + if (rho_max > theta_max):
  188 + rho_max = theta_max
186 189  
187 190 # Setup ellipse as vector
188 191 x = []
... ... @@ -370,8 +373,8 @@ if __name__ == &#39;__main__&#39;:
370 373 sys.exit()
371 374  
372 375 # Set Parameters
373   - roi_radius = 0.5
374   - delta_max = 0.2
  376 + roi_radius = 1.0
  377 + delta_max = 0.4
375 378  
376 379 # Set model parameters
377 380 centre = gammalib.GSkyDir()
... ...
src/model/GModelSpatialEllipticalDisk.cpp
... ... @@ -692,7 +692,7 @@ void GModelSpatialEllipticalDisk::free_members(void)
692 692 ***************************************************************************/
693 693 void GModelSpatialEllipticalDisk::update() const
694 694 {
695   - // Update if one axis has changed has changed
  695 + // Update if one axis has changed
696 696 if (m_last_semiminor != semiminor() || m_last_semimajor != semimajor()) {
697 697  
698 698 // Store last values
... ...