Commit f636ce90b078fc3d8f90cfb82d3f5d2bb28d6bef

Authored by Jürgen Knödlseder
2 parents 668e2cd5 0c5d33d6

Merge branch '1304-improve-ellipse-fitting' into integration

Conflicts:
	ChangeLog
	inst/cta/src/GCTAResponse_helpers.cpp
	inst/cta/src/GCTASupport.hpp
ChangeLog
1   -2014-10-26 Juergen Knoedlseder <jurgen.knodlseder@irap.omp.eu>
  1 +2014-10-30 Juergen Knoedlseder <jurgen.knodlseder@irap.omp.eu>
2 2  
3 3 * Version 0.9.0 released
4 4 ========================
... ... @@ -19,8 +19,8 @@
19 19 Read/write altitude and azimuth attributes of CTA data
20 20 Refactor CTA response class, add GCTAResponseCube class
21 21 Improve CTA response computation for extended models
22   - Implement CTA cube-style analysis
23   - Optimize CTA binned and cube-style analysis
  22 + Implement CTA stacked analysis
  23 + Optimize CTA binned and stacked analysis
24 24 Load events only when needed into GCTAObservation
25 25 Implement full automatic typecasting in Python module
26 26 Fill DETX and DETY values in GCTAResponseIrf::mc
... ...
... ... @@ -236,7 +236,7 @@ The type of the GCTAModelCubeBackground class has been set to GCTACubeBackground
236 236 the type of the GCTAModelIrfBackground class to GCTAIrfBackground. XML files need
237 237 to be adapted accordingly.
238 238  
239   -For cube-style analysis, the GCTAExposure and GCTAMeanPsf classes have been added.
  239 +For stacked analysis, the GCTAExposure and GCTAMeanPsf classes have been added.
240 240 GCTAExposure represents the summed exposure for a list of CTA observations
241 241 (computed as the sum of effective area times livetime), GCTAMeanPsf represents
242 242 the average PSF, computed by weighing the PSF of each observation by the respective
... ... @@ -257,10 +257,10 @@ GCTAResponseIrf derives. The class GCTAResponseCube, which also derives from
257 257 GCTAResponse, has been added to handle cube-style analysis. GCTAResponseCube
258 258 holds an exposure cube (GCTAExposure) and a PSF cube (GCTAMeanPsf) to describe
259 259 the response. GCTAObservation has been modified to allocate either IRF-style or
260   -cube-style response, dependent on the information found in the XML file.
  260 +stacked response, dependent on the information found in the XML file.
261 261  
262 262 The abstract GCTASourceCube class has been added to handle the pre-computation
263   -cache for cube-style analysis. A first implementation of this cache is provided
  263 +cache for stacked analysis. A first implementation of this cache is provided
264 264 by the GCTASourceCubePointSource class for point source analysis. The cache has
265 265 been added to GCTAResponseCube who takes care of initializing and using the cache.
266 266  
... ...
include/GModelSpatialElliptical.hpp
... ... @@ -95,6 +95,10 @@ public:
95 95 void dec(const double& dec);
96 96 double posangle(void) const;
97 97 void posangle(const double& posangle);
  98 + double semiminor(void) const;
  99 + double semimajor(void) const;
  100 + void semiminor(const double& semiminor);
  101 + void semimajor(const double& semimajor);
98 102 GSkyDir dir(void) const;
99 103 void dir(const GSkyDir& dir);
100 104  
... ... @@ -105,9 +109,11 @@ protected:
105 109 void free_members(void);
106 110  
107 111 // Proteced members
108   - GModelPar m_ra; //!< Right Ascension (deg)
109   - GModelPar m_dec; //!< Declination (deg)
110   - GModelPar m_posangle; //!< Position angle from North, counterclockwise (deg)
  112 + GModelPar m_ra; //!< Right Ascension (deg)
  113 + GModelPar m_dec; //!< Declination (deg)
  114 + GModelPar m_posangle; //!< Position angle from North, counterclockwise (deg)
  115 + GModelPar m_semiminor; //!< Semi-minor axis of ellipse (deg)
  116 + GModelPar m_semimajor; //!< Semi-major axis of ellipse (deg)
111 117 };
112 118  
113 119  
... ... @@ -215,6 +221,64 @@ void GModelSpatialElliptical::posangle(const double&amp; posangle)
215 221  
216 222  
217 223 /***********************************************************************//**
  224 + * @brief Return semi-minor axis of ellipse
  225 + *
  226 + * @return Semi-minor axis of ellipse (degrees).
  227 + *
  228 + * Returns the semi-minor axis of the ellipse in degrees.
  229 + ***************************************************************************/
  230 +inline
  231 +double GModelSpatialElliptical::semiminor(void) const
  232 +{
  233 + return (m_semiminor.value());
  234 +}
  235 +
  236 +
  237 +/***********************************************************************//**
  238 + * @brief Set semi-minor axis of ellipse
  239 + *
  240 + * @param[in] semiminor Semi-minor axis of ellipse (degrees)
  241 + *
  242 + * Sets the semi-minor axis of the ellipse in degrees.
  243 + ***************************************************************************/
  244 +inline
  245 +void GModelSpatialElliptical::semiminor(const double& semiminor)
  246 +{
  247 + m_semiminor.value(semiminor);
  248 + return;
  249 +}
  250 +
  251 +
  252 +/***********************************************************************//**
  253 + * @brief Return semi-major axis of ellipse
  254 + *
  255 + * @return Semi-major axis of ellipse (degrees).
  256 + *
  257 + * Returns the semi-major axis of the ellipse in degrees.
  258 + ***************************************************************************/
  259 +inline
  260 +double GModelSpatialElliptical::semimajor(void) const
  261 +{
  262 + return (m_semimajor.value());
  263 +}
  264 +
  265 +
  266 +/***********************************************************************//**
  267 + * @brief Set semi-major axis of ellipse
  268 + *
  269 + * @param[in] semimajor Semi-major axis of ellipse (degrees)
  270 + *
  271 + * Sets the semi-major axis of the ellipse in degrees.
  272 + ***************************************************************************/
  273 +inline
  274 +void GModelSpatialElliptical::semimajor(const double& semimajor)
  275 +{
  276 + m_semimajor.value(semimajor);
  277 + return;
  278 +}
  279 +
  280 +
  281 +/***********************************************************************//**
218 282 * @brief Return normalization of elliptical source
219 283 *
220 284 * @return Normalization.
... ...
include/GModelSpatialEllipticalDisk.hpp
... ... @@ -81,11 +81,6 @@ public:
81 81 virtual void write(GXmlElement& xml) const;
82 82 virtual std::string print(const GChatter& chatter = NORMAL) const;
83 83  
84   - // Other methods
85   - double semiminor(void) const;
86   - double semimajor(void) const;
87   - void semiminor(const double& semiminor);
88   - void semimajor(const double& semimajor);
89 84  
90 85 protected:
91 86 // Protected methods
... ... @@ -94,10 +89,6 @@ protected:
94 89 void free_members(void);
95 90 void update(void) const;
96 91  
97   - // Protected members
98   - GModelPar m_semiminor; //!< semi-minor axis of ellipse (degrees)
99   - GModelPar m_semimajor; //!< semi-major axis of ellipse (degrees)
100   -
101 92 // Cached members used for pre-computations
102 93 mutable double m_last_semiminor; //!< Last semi-minor axis
103 94 mutable double m_last_semimajor; //!< Last semi-major axis
... ... @@ -132,62 +123,4 @@ std::string GModelSpatialEllipticalDisk::type(void) const
132 123 return "EllipticalDisk";
133 124 }
134 125  
135   -
136   -/***********************************************************************//**
137   - * @brief Return semi-minor axis of ellipse
138   - *
139   - * @return Semi-minor axis of ellipse (degrees).
140   - *
141   - * Returns the semi-minor axis of the ellipse in degrees.
142   - ***************************************************************************/
143   -inline
144   -double GModelSpatialEllipticalDisk::semiminor(void) const
145   -{
146   - return (m_semiminor.value());
147   -}
148   -
149   -
150   -/***********************************************************************//**
151   - * @brief Set semi-minor axis of ellipse
152   - *
153   - * @param[in] semiminor Semi-minor axis of ellipse (degrees)
154   - *
155   - * Sets the semi-minor axis of the ellipse in degrees.
156   - ***************************************************************************/
157   -inline
158   -void GModelSpatialEllipticalDisk::semiminor(const double& semiminor)
159   -{
160   - m_semiminor.value(semiminor);
161   - return;
162   -}
163   -
164   -
165   -/***********************************************************************//**
166   - * @brief Return semi-major axis of ellipse
167   - *
168   - * @return Semi-major axis of ellipse (degrees).
169   - *
170   - * Returns the semi-major axis of the ellipse in degrees.
171   - ***************************************************************************/
172   -inline
173   -double GModelSpatialEllipticalDisk::semimajor(void) const
174   -{
175   - return (m_semimajor.value());
176   -}
177   -
178   -
179   -/***********************************************************************//**
180   - * @brief Set semi-major axis of ellipse
181   - *
182   - * @param[in] semimajor Semi-major axis of ellipse (degrees)
183   - *
184   - * Sets the semi-major axis of the ellipse in degrees.
185   - ***************************************************************************/
186   -inline
187   -void GModelSpatialEllipticalDisk::semimajor(const double& semimajor)
188   -{
189   - m_semimajor.value(semimajor);
190   - return;
191   -}
192   -
193 126 #endif /* GMODELSPATIALELLIPTICALDISK_HPP */
... ...
inst/cta/include/GCTAResponseCube.hpp
... ... @@ -112,13 +112,13 @@ private:
112 112 void free_members(void);
113 113 int cache_index(const std::string& name) const;
114 114 double psf_radial(const GModelSpatialRadial* model,
115   - const double& zeta,
  115 + const double& rho_obs,
116 116 const GSkyDir& obsDir,
117 117 const GEnergy& srcEng,
118 118 const GTime& srcTime) const;
119 119 double psf_elliptical(const GModelSpatialElliptical* model,
120   - const double& zeta,
121   - const double& omega,
  120 + const double& rho_obs,
  121 + const double& posangle_obs,
122 122 const GSkyDir& obsDir,
123 123 const GEnergy& srcEng,
124 124 const GTime& srcTime) const;
... ...
inst/cta/src/GCTAResponseCube.cpp
... ... @@ -37,6 +37,7 @@
37 37 #include "GCTASourceCubeDiffuse.hpp"
38 38 #include "GModelSpatialPointSource.hpp"
39 39 #include "GModelSpatialRadial.hpp"
  40 +#include "GModelSpatialRadialShell.hpp"
40 41 #include "GModelSpatialElliptical.hpp"
41 42 #include "GModelSpatialDiffuse.hpp"
42 43 #include "GPhoton.hpp"
... ... @@ -63,6 +64,7 @@
63 64 /* __ Macros _____________________________________________________________ */
64 65  
65 66 /* __ Coding definitions _________________________________________________ */
  67 +#define G_RADIAL_PSF_BASED //!< Use Psf-based integration for radial model
66 68  
67 69 /* __ Debug definitions __________________________________________________ */
68 70  
... ... @@ -236,11 +238,12 @@ GCTAResponseCube* GCTAResponseCube::clone(void) const
236 238  
237 239  
238 240 /***********************************************************************//**
239   - * @brief Return value of instrument response function
  241 + * @brief Return instrument response
240 242 *
241 243 * @param[in] event Observed event.
242 244 * @param[in] photon Incident photon.
243 245 * @param[in] obs Observation.
  246 + * @return Instrument response.
244 247 ***************************************************************************/
245 248 double GCTAResponseCube::irf(const GEvent& event,
246 249 const GPhoton& photon,
... ... @@ -307,14 +310,14 @@ double GCTAResponseCube::irf(const GEvent&amp; event,
307 310  
308 311  
309 312 /***********************************************************************//**
310   - * @brief Return value of instrument response function
  313 + * @brief Return instrument response
311 314 *
312 315 * @param[in] event Event.
313 316 * @param[in] source Source.
314 317 * @param[in] obs Observation.
  318 + * @return Instrument response.
315 319 *
316   - * Returns the instrument response function for a given event, source and
317   - * observation.
  320 + * Returns the instrument response for a given event, source and observation.
318 321 ***************************************************************************/
319 322 double GCTAResponseCube::irf(const GEvent& event,
320 323 const GSource& source,
... ... @@ -347,15 +350,14 @@ double GCTAResponseCube::irf(const GEvent&amp; event,
347 350  
348 351  
349 352 /***********************************************************************//**
350   - * @brief Return value of point source instrument response function
  353 + * @brief Return instrument response to point source
351 354 *
352 355 * @param[in] event Observed event.
353 356 * @param[in] source Source.
354 357 * @param[in] obs Observation.
355   - * @return Value of instrument response function for a point source.
  358 + * @return Instrument response to point source.
356 359 *
357   - * This method returns the value of the instrument response function for a
358   - * point source.
  360 + * Returns the instrument response to a specified point source.
359 361 ***************************************************************************/
360 362 double GCTAResponseCube::irf_ptsrc(const GEvent& event,
361 363 const GSource& source,
... ... @@ -425,15 +427,14 @@ double GCTAResponseCube::irf_ptsrc(const GEvent&amp; event,
425 427  
426 428  
427 429 /***********************************************************************//**
428   - * @brief Return value of radial source instrument response function
  430 + * @brief Return instrument response to radial source
429 431 *
430 432 * @param[in] event Observed event.
431 433 * @param[in] source Source.
432 434 * @param[in] obs Observation.
433   - * @return Value of instrument response function for a radial source.
  435 + * @return Instrument response to radial source.
434 436 *
435   - * This method returns the value of the instrument response function for a
436   - * radial source. Response values are computed on the fly.
  437 + * Returns the instrument response to a specified radial source.
437 438 ***************************************************************************/
438 439 double GCTAResponseCube::irf_radial(const GEvent& event,
439 440 const GSource& source,
... ... @@ -462,19 +463,19 @@ double GCTAResponseCube::irf_radial(const GEvent&amp; event,
462 463  
463 464 // Compute angle between model centre and measured photon direction
464 465 // (radians)
465   - double zeta = model->dir().dist(obsDir);
  466 + double rho_obs = model->dir().dist(obsDir);
466 467  
467 468 // Continue only if we're sufficiently close to the model centre to get a
468 469 // non-zero response
469   - if (zeta <= model->theta_max()+psf().delta_max()) {
  470 + if (rho_obs <= model->theta_max()+psf().delta_max()) {
470 471  
471 472 // Get exposure
472 473 irf = exposure()(obsDir, obsEng);
473 474  
474 475 // Continue only if exposure is positive
475 476 if (irf > 0.0) {
476   - irf /= obs.livetime(); //!< Revover effective area from exposure
477   - irf *= psf_radial(model, zeta, obsDir, obsEng, obsTime);
  477 + irf /= obs.livetime(); //!< Recover effective area from exposure
  478 + irf *= psf_radial(model, rho_obs, obsDir, obsEng, obsTime);
478 479 irf *= obs.deadc(obsTime);
479 480 }
480 481  
... ... @@ -496,15 +497,14 @@ double GCTAResponseCube::irf_radial(const GEvent&amp; event,
496 497  
497 498  
498 499 /***********************************************************************//**
499   - * @brief Return value of elliptical source instrument response function
  500 + * @brief Return instrument response to elliptical source
500 501 *
501 502 * @param[in] event Observed event.
502 503 * @param[in] source Source.
503 504 * @param[in] obs Observation.
504   - * @return Value of instrument response function for an elliptical source.
  505 + * @return Instrument response to elliptical source.
505 506 *
506   - * This method returns the value of the instrument response function for an
507   - * elliptical source. Response values are computed on the fly.
  507 + * Returns the instrument response to a specified elliptical source.
508 508 ***************************************************************************/
509 509 double GCTAResponseCube::irf_elliptical(const GEvent& event,
510 510 const GSource& source,
... ... @@ -533,20 +533,20 @@ double GCTAResponseCube::irf_elliptical(const GEvent&amp; event,
533 533  
534 534 // Compute angle between model centre and measured photon direction and
535 535 // position angle (radians)
536   - double zeta = model->dir().dist(obsDir);
537   - double omega = model->dir().posang(obsDir);
  536 + double rho_obs = model->dir().dist(obsDir);
  537 + double posangle_obs = model->dir().posang(obsDir);
538 538  
539 539 // Continue only if we're sufficiently close to the model centre to get a
540 540 // non-zero response
541   - if (zeta <= model->theta_max()+psf().delta_max()) {
  541 + if (rho_obs <= model->theta_max()+psf().delta_max()) {
542 542  
543 543 // Get exposure
544 544 irf = exposure()(obsDir, obsEng);
545 545  
546 546 // Continue only if exposure is positive
547 547 if (irf > 0.0) {
548   - irf /= obs.livetime(); //!< Revover effective area from exposure
549   - irf *= psf_elliptical(model, zeta, omega, obsDir, obsEng, obsTime);
  548 + irf /= obs.livetime(); //!< Recover effective area from exposure
  549 + irf *= psf_elliptical(model, rho_obs, posangle_obs, obsDir, obsEng, obsTime);
550 550 irf *= obs.deadc(obsTime);
551 551 }
552 552  
... ... @@ -568,21 +568,20 @@ double GCTAResponseCube::irf_elliptical(const GEvent&amp; event,
568 568  
569 569  
570 570 /***********************************************************************//**
571   - * @brief Return value of extended source instrument response function
  571 + * @brief Return instrument response to diffuse source
572 572 *
573 573 * @param[in] event Observed event.
574 574 * @param[in] source Source.
575 575 * @param[in] obs Observation.
576   - * @return Value of instrument response function for an extended source.
  576 + * @return Instrument response to diffuse source.
577 577 *
578   - * This method returns the value of the instrument response function for an
579   - * extended source. It uses a pre-computation cache to store the IRF for
580   - * the spatial model component.
  578 + * Returns the instrument response to a specified diffuse source.
581 579 *
582   - * The pre-computation cache is initialised if no cache has yet been
583   - * allocated, or if at the beginning of a scan over the events, the model
584   - * parameters have changed. The beginning of a scan is defined by an event
585   - * bin index of 0.
  580 + * The method uses a pre-computation cache to store the instrument response
  581 + * for the spatial model component. The pre-computation cache is initialised
  582 + * if no cache has yet been allocated, or if at the beginning of a scan over
  583 + * the events, the model parameters have changed. The beginning of a scan is
  584 + * defined by an event bin index of 0.
586 585 ***************************************************************************/
587 586 double GCTAResponseCube::irf_diffuse(const GEvent& event,
588 587 const GSource& source,
... ... @@ -651,9 +650,13 @@ double GCTAResponseCube::irf_diffuse(const GEvent&amp; event,
651 650 *
652 651 * @param[in] photon Incident photon.
653 652 * @param[in] obs Observation.
  653 + * @return Spatial integral of point spread function.
654 654 *
655 655 * @exception GException::feature_not_implemented
656 656 * Method not implemented.
  657 + *
  658 + * This method is a dummy method that is required for a class derived from
  659 + * GResponse but that needs not to be implemented for a binned response.
657 660 ***************************************************************************/
658 661 double GCTAResponseCube::npred(const GPhoton& photon,
659 662 const GObservation& obs) const
... ... @@ -668,21 +671,21 @@ double GCTAResponseCube::npred(const GPhoton&amp; photon,
668 671  
669 672  
670 673 /***********************************************************************//**
671   - * @brief Read response
  674 + * @brief Read response information from Xml element
672 675 *
673 676 * @param[in] xml XML element.
674 677 *
675 678 * @exception GException::xml_invalid_parnames
676 679 * Invalid parameter names found in XML element.
677 680 *
678   - * Reads information for a CTA observation from an XML element. The exposure
679   - * and PSF cubes are specified using
  681 + * Reads response information from an Xml element. The Exposure and Psf cubes
  682 + * are specified using
680 683 *
681   - * <observation name="..." id="..." instrument="...">
682   - * ...
683   - * <parameter name="ExposureCube" file="..."/>
684   - * <parameter name="PsfCube" file="..."/>
685   - * </observation>
  684 + * <observation name="..." id="..." instrument="...">
  685 + * ...
  686 + * <parameter name="ExposureCube" file="..."/>
  687 + * <parameter name="PsfCube" file="..."/>
  688 + * </observation>
686 689 *
687 690 ***************************************************************************/
688 691 void GCTAResponseCube::read(const GXmlElement& xml)
... ... @@ -737,18 +740,18 @@ void GCTAResponseCube::read(const GXmlElement&amp; xml)
737 740  
738 741  
739 742 /***********************************************************************//**
740   - * @brief Write response
  743 + * @brief Write response information into Xml element
741 744 *
742 745 * @param[in] xml XML element.
743 746 *
744   - * Writes information for a CTA observation into an XML element. The exposure
745   - * and PSF cubes are specified using
  747 + * Writes response information into an Xml element. The Exposure and Psf
  748 + * cubes are specified using
746 749 *
747   - * <observation name="..." id="..." instrument="...">
748   - * ...
749   - * <parameter name="ExposureCube" file="..."/>
750   - * <parameter name="PsfCube" file="..."/>
751   - * </observation>
  750 + * <observation name="..." id="..." instrument="...">
  751 + * ...
  752 + * <parameter name="ExposureCube" file="..."/>
  753 + * <parameter name="PsfCube" file="..."/>
  754 + * </observation>
752 755 *
753 756 ***************************************************************************/
754 757 void GCTAResponseCube::write(GXmlElement& xml) const
... ... @@ -773,10 +776,10 @@ void GCTAResponseCube::write(GXmlElement&amp; xml) const
773 776  
774 777  
775 778 /***********************************************************************//**
776   - * @brief Print CTA response information
  779 + * @brief Print response information
777 780 *
778 781 * @param[in] chatter Chattiness (defaults to NORMAL).
779   - * @return String containing CTA response information.
  782 + * @return String containing response information.
780 783 ***************************************************************************/
781 784 std::string GCTAResponseCube::print(const GChatter& chatter) const
782 785 {
... ... @@ -818,20 +821,6 @@ std::string GCTAResponseCube::print(const GChatter&amp; chatter) const
818 821  
819 822 /*==========================================================================
820 823 = =
821   - = Model type dependent CTA response methods =
822   - = =
823   - ==========================================================================*/
824   -
825   -
826   -/*==========================================================================
827   - = =
828   - = Low-level CTA response methods =
829   - = =
830   - ==========================================================================*/
831   -
832   -
833   -/*==========================================================================
834   - = =
835 824 = Private methods =
836 825 = =
837 826 ==========================================================================*/
... ... @@ -857,7 +846,7 @@ void GCTAResponseCube::init_members(void)
857 846 /***********************************************************************//**
858 847 * @brief Copy class members
859 848 *
860   - * @param[in] rsp Response to be copied
  849 + * @param[in] rsp Response.
861 850 ***************************************************************************/
862 851 void GCTAResponseCube::copy_members(const GCTAResponseCube& rsp)
863 852 {
... ... @@ -893,10 +882,12 @@ void GCTAResponseCube::free_members(void)
893 882  
894 883  
895 884 /***********************************************************************//**
896   - * @brief Determines the cache index for a given model name
  885 + * @brief Determine cache index for model
897 886 *
898 887 * @param[in] name Model name.
899 888 * @return Cache index (-1 if model has not been found).
  889 + *
  890 + * Determines the cache index for a given model @p name.
900 891 ***************************************************************************/
901 892 int GCTAResponseCube::cache_index(const std::string& name) const
902 893 {
... ... @@ -921,142 +912,362 @@ int GCTAResponseCube::cache_index(const std::string&amp; name) const
921 912 }
922 913  
923 914  
  915 +#if defined(G_RADIAL_PSF_BASED)
924 916 /***********************************************************************//**
925   - * @brief Integrate PSF over radial model
  917 + * @brief Integrate Psf over radial model
926 918 *
927 919 * @param[in] model Radial model.
928   - * @param[in] zeta Angle between model centre and measured photon direction
929   - * (radians).
  920 + * @param[in] delta_mod Angle between model centre and measured photon
  921 + * direction (radians).
930 922 * @param[in] obsDir Observed event direction.
931 923 * @param[in] srcEng True photon energy.
932 924 * @param[in] srcTime True photon arrival time.
933 925 *
934   - * Computes the integral
  926 + * Integrates the product of the spatial model and the point spread
  927 + * function over the true photon arrival direction using
935 928 *
936 929 * \f[
937   - * \int_0^{\delta_{\rm max}}
938   - * {\rm PSF}(\delta) \times
939   - * \int_0^{2\pi} {\rm M}(\delta, \phi) \sin \delta
940   - * {\rm d}\phi {\rm d}\delta
  930 + * \int_{\delta_{\rm min}}^{\delta_{\rm max}}
  931 + * \sin \delta \times {\rm Psf}(\delta) \times
  932 + * \int_{\phi_{\rm min}}^{\phi_{\rm max}}
  933 + * S_{\rm p}(\delta, \phi | E, t) d\phi d\delta
941 934 * \f]
942 935 *
943   - * where \f${\rm M}(\delta, \phi)\f$ is a radial model in the coordinate
944   - * system of the point spread function, defined by the angle \f$\delta\f$
945   - * between the true and the measured photon direction and the azimuth angle
946   - * \f$\phi\f$ around the measured photon direction.
947   - * \f${\rm PSF}(\delta)\f$ is the azimuthally symmetric point spread
948   - * function.
  936 + * where
  937 + * \f$S_{\rm p}(\delta, \phi | E, t)\f$ is the radial spatial model,
  938 + * \f${\rm Psf}(\delta)\f$ is the point spread function,
  939 + * \f$\delta\f$ is angular distance between the true and the measured
  940 + * photon direction, and
  941 + * \f$\phi\f$ is the position angle around the observed photon direction
  942 + * measured counterclockwise from the connecting line between the model
  943 + * centre and the observed photon arrival direction.
949 944 ***************************************************************************/
950 945 double GCTAResponseCube::psf_radial(const GModelSpatialRadial* model,
951   - const double& zeta,
  946 + const double& delta_mod,
952 947 const GSkyDir& obsDir,
953 948 const GEnergy& srcEng,
954 949 const GTime& srcTime) const
955 950 {
956   - // Set integration precision and Romberg order. These values have been
957   - // determined after careful testing, see
  951 + // Set number of iterations for Romberg integration.
  952 + // These values have been determined after careful testing, see
958 953 // https://cta-redmine.irap.omp.eu/issues/1291
959   - const double eps_delta = 1.0e-2;
960   - const int order_delta = 5;
961   - const double eps_phi = 1.0e-2;
962   - const int order_phi = 5;
  954 + static const int iter_delta = 5;
  955 + static const int iter_phi = 6;
963 956  
964 957 // Initialise value
965 958 double value = 0.0;
966 959  
967   - // Compute sin and cosine of angle between model centre and measured
968   - // photon direction
969   - double sin_zeta = std::sin(zeta);
970   - double cos_zeta = std::cos(zeta);
  960 + // Get maximum Psf radius (radians)
  961 + double psf_max = psf().delta_max();
971 962  
972   - // Get offset angle integration interval in radians
973   - double delta_min = 0.0;
974   - double delta_max = psf().delta_max();
  963 + // Get maximum model radius (radians)
  964 + double theta_max = model->theta_max();
  965 +
  966 + // Set offset angle integration range (radians)
  967 + double delta_min = (delta_mod > theta_max) ? delta_mod - theta_max : 0.0;
  968 + double delta_max = delta_mod + theta_max;
  969 + if (delta_max > psf_max) {
  970 + delta_max = psf_max;
  971 + }
975 972  
976 973 // Setup integration kernel. We take here the observed photon arrival
977 974 // direction as the true photon arrival direction because the PSF does
978 975 // not vary significantly over a small region.
979   - cta_psf_radial_kern_delta integrand(this, model, obsDir, srcEng, srcTime,
980   - sin_zeta, cos_zeta,
981   - eps_phi, order_phi);
982   -
983   - // Integrate over PSF delta angle
  976 + cta_psf_radial_kern_delta integrand(this,
  977 + model,
  978 + obsDir,
  979 + srcEng,
  980 + srcTime,
  981 + delta_mod,
  982 + theta_max,
  983 + iter_phi);
  984 +
  985 + // Integrate over model's zenith angle
984 986 GIntegral integral(&integrand);
985   - integral.eps(eps_delta);
986   - value = integral.romberg(delta_min, delta_max, order_delta);
  987 + integral.fixed_iter(iter_delta);
  988 +
  989 + // Setup integration boundaries
  990 + std::vector<double> bounds;
  991 + bounds.push_back(delta_min);
  992 + bounds.push_back(delta_max);
  993 +
  994 + // If the integration range includes a transition between full
  995 + // containment of Psf within model and partial containment, then
  996 + // add a boundary at this location
  997 + double transition_point = theta_max - delta_mod;
  998 + if (transition_point > delta_min && transition_point < delta_max) {
  999 + bounds.push_back(transition_point);
  1000 + }
987 1001  
988   - // Return PSF
  1002 + // Integrate kernel
  1003 + value = integral.romberg(bounds, iter_delta);
  1004 +
  1005 + // Compile option: Check for NaN/Inf
  1006 + #if defined(G_NAN_CHECK)
  1007 + if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
  1008 + std::cout << "*** ERROR: GCTAResponseCube::psf_radial:";
  1009 + std::cout << " NaN/Inf encountered";
  1010 + std::cout << " (value=" << value;
  1011 + std::cout << ", delta_min=" << delta_min;
  1012 + std::cout << ", delta_max=" << delta_max << ")";
  1013 + std::cout << std::endl;
  1014 + }
  1015 + #endif
  1016 +
  1017 + // Return integral
989 1018 return value;
990 1019 }
  1020 +#else
  1021 +/***********************************************************************//**
  1022 + * @brief Integrate Psf over radial model
  1023 + *
  1024 + * @param[in] model Radial model.
  1025 + * @param[in] rho_obs Angle between model centre and measured photon direction
  1026 + * (radians).
  1027 + * @param[in] obsDir Observed event direction.
  1028 + * @param[in] srcEng True photon energy.
  1029 + * @param[in] srcTime True photon arrival time.
  1030 + *
  1031 + * Integrates the product of the spatial model and the point spread
  1032 + * function over the true photon arrival direction using
  1033 + *
  1034 + * \f[
  1035 + * \int_{\rho_{\rm min}}^{\rho_{\rm max}}
  1036 + * \sin \rho \times S_{\rm p}(\rho | E, t) \times
  1037 + * \int_{\omega_{\rm min}}^{\omega_{\rm max}}
  1038 + * {\rm Psf}(\rho, \omega) d\omega d\rho
  1039 + * \f]
  1040 + *
  1041 + * where
  1042 + * \f$S_{\rm p}(\rho | E, t)\f$ is the radial spatial model,
  1043 + * \f${\rm Psf}(\rho, \omega)\f$ is the point spread function,
  1044 + * \f$\rho\f$ is the radial distance from the model centre, and
  1045 + * \f$\omega\f$ is the position angle around the model centre measured
  1046 + * counterclockwise from the connecting line between the model centre and
  1047 + * the observed photon arrival direction.
  1048 + ***************************************************************************/
  1049 +double GCTAResponseCube::psf_radial(const GModelSpatialRadial* model,
  1050 + const double& rho_obs,
  1051 + const GSkyDir& obsDir,
  1052 + const GEnergy& srcEng,
  1053 + const GTime& srcTime) const
  1054 +{
  1055 + // Set number of iterations for Romberg integration.
  1056 + // These values have been determined after careful testing, see
  1057 + // https://cta-redmine.irap.omp.eu/issues/1299
  1058 + static const int iter_rho = 5;
  1059 + static const int iter_phi = 5;
  1060 +
  1061 + // Initialise value
  1062 + double irf = 0.0;
  1063 +
  1064 + // Get maximum PSF radius (radians)
  1065 + double delta_max = psf().delta_max();
  1066 +
  1067 + // Set zenith angle integration range for radial model (radians)
  1068 + double rho_min = (rho_obs > delta_max) ? rho_obs - delta_max : 0.0;
  1069 + double rho_max = rho_obs + delta_max;
  1070 + double src_max = model->theta_max();
  1071 + if (rho_max > src_max) {
  1072 + rho_max = src_max;
  1073 + }
  1074 +
  1075 + // Perform zenith angle integration if interval is valid
  1076 + if (rho_max > rho_min) {
  1077 +
  1078 + // Setup integration kernel. We take here the observed photon arrival
  1079 + // direction as the true photon arrival direction because the PSF does
  1080 + // not vary significantly over a small region.
  1081 + cta_psf_radial_kern_rho integrand(this,
  1082 + model,
  1083 + obsDir,
  1084 + srcEng,
  1085 + srcTime,
  1086 + rho_obs,
  1087 + delta_max,
  1088 + iter_phi);
  1089 +
  1090 + // Integrate over model's zenith angle
  1091 + GIntegral integral(&integrand);
  1092 + integral.fixed_iter(iter_rho);
  1093 +
  1094 + // Setup integration boundaries
  1095 + std::vector<double> bounds;
  1096 + bounds.push_back(rho_min);
  1097 + bounds.push_back(rho_max);
  1098 +
  1099 + // If the integration range includes a transition between full
  1100 + // containment of model within Psf and partial containment, then
  1101 + // add a boundary at this location
  1102 + double transition_point = delta_max - rho_obs;
  1103 + if (transition_point > rho_min && transition_point < rho_max) {
  1104 + bounds.push_back(transition_point);
  1105 + }
  1106 +
  1107 + // If we have a shell model then add an integration boundary for the
  1108 + // shell radius as a function discontinuity will occur at this
  1109 + // location
  1110 + const GModelSpatialRadialShell* shell = dynamic_cast<const GModelSpatialRadialShell*>(model);
  1111 + if (shell != NULL) {
  1112 + double shell_radius = shell->radius() * gammalib::deg2rad;
  1113 + if (shell_radius > rho_min && shell_radius < rho_max) {
  1114 + bounds.push_back(shell_radius);
  1115 + }
  1116 + }
  1117 +
  1118 + // Integrate kernel
  1119 + irf = integral.romberg(bounds, iter_rho);
  1120 +
  1121 + // Compile option: Check for NaN/Inf
  1122 + #if defined(G_NAN_CHECK)
  1123 + if (gammalib::is_notanumber(irf) || gammalib::is_infinite(irf)) {
  1124 + std::cout << "*** ERROR: GCTAResponseCube::psf_radial:";
  1125 + std::cout << " NaN/Inf encountered";
  1126 + std::cout << " (irf=" << irf;
  1127 + std::cout << ", rho_min=" << rho_min;
  1128 + std::cout << ", rho_max=" << rho_max << ")";
  1129 + std::cout << std::endl;
  1130 + }
  1131 + #endif
  1132 +
  1133 + } // endif: integration interval is valid
  1134 +
  1135 + // Return PSF
  1136 + return irf;
  1137 +}
  1138 +#endif
991 1139  
992 1140  
993 1141 /***********************************************************************//**
994   - * @brief Integrate PSF over elliptical model
  1142 + * @brief Integrate Psf over elliptical model
995 1143 *
996 1144 * @param[in] model Elliptical model.
997   - * @param[in] zeta Angle between model centre and measured photon direction
998   - * (radians).
999   - * @param[in] omega Position angle of measured photon direction with
1000   - * respect to model centre (radians)
  1145 + * @param[in] rho_obs Angle between model centre and measured photon direction
  1146 + * (radians).
  1147 + * @param[in] posangle_obs Position angle of measured photon direction with
  1148 + * respect to model centre (radians).
1001 1149 * @param[in] obsDir Observed event direction.
1002 1150 * @param[in] srcEng True photon energy.
1003 1151 * @param[in] srcTime True photon arrival time.
1004 1152 *
1005   - * Computes the integral
  1153 + * Integrates the product of the spatial model and the point spread
  1154 + * function over the true photon arrival direction using
1006 1155 *
1007 1156 * \f[
1008   - * \int_0^{\delta_{\rm max}}
1009   - * {\rm PSF}(\delta) \times
1010   - * \int_0^{2\pi} {\rm M}(\delta, \phi) \sin \delta
1011   - * {\rm d}\phi {\rm d}\delta
  1157 + * \int_{\rho_{\rm min}}^{\rho_{\rm max}}
  1158 + * \sin \rho \times
  1159 + * \int_{\omega}
  1160 + * S_{\rm p}(\rho, \omega | E, t) \times
  1161 + * {\rm Psf}(\rho, \omega) d\omega d\rho
1012 1162 * \f]
1013 1163 *
1014   - * where \f${\rm M}(\delta, \phi)\f$ is an elliptical model in the coordinate
1015   - * system of the point spread function, defined by the angle \f$\delta\f$
1016   - * between the true and the measured photon direction and the azimuth angle
1017   - * \f$\phi\f$ around the measured photon direction.
1018   - * \f${\rm PSF}(\delta)\f$ is the azimuthally symmetric point spread
1019   - * function.
  1164 + * where
  1165 + * \f$S_{\rm p}(\rho, \omega | E, t)\f$ is the elliptical spatial model,
  1166 + * \f${\rm Psf}(\rho, \omega)\f$ is the point spread function,
  1167 + * \f$\rho\f$ is the radial distance from the model centre, and
  1168 + * \f$\omega\f$ is the position angle around the model centre measured
  1169 + * counterclockwise from the connecting line between the model centre and
  1170 + * the observed photon arrival direction.
1020 1171 ***************************************************************************/
1021 1172 double GCTAResponseCube::psf_elliptical(const GModelSpatialElliptical* model,
1022   - const double& zeta,
1023   - const double& omega,
  1173 + const double& rho_obs,
  1174 + const double& posangle_obs,
1024 1175 const GSkyDir& obsDir,
1025 1176 const GEnergy& srcEng,
1026 1177 const GTime& srcTime) const
1027 1178 {
1028   - // Set integration precision and Romberg order. These values have been
1029   - // determined after careful testing, see
1030   - // https://cta-redmine.irap.omp.eu/issues/1291
1031   - const double eps_delta = 1.0e-2;
1032   - const int order_delta = 5;
1033   - const double eps_phi = 1.0e-2;
1034   - const int order_phi = 5;
  1179 + // Set number of iterations for Romberg integration.
  1180 + // These values have been determined after careful testing, see
  1181 + // https://cta-redmine.irap.omp.eu/issues/1299
  1182 + static const int iter_rho = 5;
  1183 + static const int iter_phi = 5;
1035 1184  
1036 1185 // Initialise value
1037   - double value = 0.0;
1038   -
1039   - // Compute sin and cosine of angle between model centre and measured
1040   - // photon direction
1041   - double sin_zeta = std::sin(zeta);
1042   - double cos_zeta = std::cos(zeta);
  1186 + double irf = 0.0;
1043 1187  
1044   - // Get offset angle integration interval in radians
1045   - double delta_min = 0.0;
  1188 + // Get maximum PSF radius (radians)
1046 1189 double delta_max = psf().delta_max();
1047 1190  
1048   - // Setup integration kernel. We take here the observed photon arrival
1049   - // direction as the true photon arrival direction because the PSF does
1050   - // not vary significantly over a small region.
1051   - cta_psf_elliptical_kern_delta integrand(this, model, obsDir, srcEng, srcTime,
1052   - sin_zeta, cos_zeta, omega,
1053   - eps_phi, order_phi);
  1191 + // Get the ellipse boundary (radians). Note that these are NOT the
  1192 + // parameters of the ellipse but of a boundary ellipse that is used
  1193 + // for computing the relevant omega angle intervals for a given angle
  1194 + // rho. The boundary ellipse takes care of the possibility that the
  1195 + // semiminor axis is larger than the semimajor axis
  1196 + double semimajor; // Will be the larger axis
  1197 + double semiminor; // Will be the smaller axis
  1198 + double posangle; // Will be the corrected position angle
  1199 + double aspect_ratio; // Ratio between smaller/larger axis of model
  1200 + if (model->semimajor() >= model->semiminor()) {
  1201 + aspect_ratio = (model->semimajor() > 0.0) ?
  1202 + model->semiminor() / model->semimajor() : 0.0;
  1203 + posangle = model->posangle() * gammalib::deg2rad;
  1204 + }
  1205 + else {
  1206 + aspect_ratio = (model->semiminor() > 0.0) ?
  1207 + model->semimajor() / model->semiminor() : 0.0;
  1208 + posangle = model->posangle() * gammalib::deg2rad + gammalib::pihalf;
  1209 + }
  1210 + semimajor = model->theta_max();
  1211 + semiminor = semimajor * aspect_ratio;
  1212 +
  1213 + // Set zenith angle integration range for elliptical model
  1214 + double rho_min = (rho_obs > delta_max) ? rho_obs - delta_max : 0.0;
  1215 + double rho_max = rho_obs + delta_max;
  1216 + if (rho_max > semimajor) {
  1217 + rho_max = semimajor;
  1218 + }
1054 1219  
1055   - // Integrate over PSF delta angle
1056   - GIntegral integral(&integrand);
1057   - integral.eps(eps_delta);
1058   - value = integral.romberg(delta_min, delta_max, order_delta);
  1220 + // Perform zenith angle integration if interval is valid
  1221 + if (rho_max > rho_min) {
  1222 +
  1223 + // Setup integration kernel. We take here the observed photon arrival
  1224 + // direction as the true photon arrival direction because the PSF does
  1225 + // not vary significantly over a small region.
  1226 + cta_psf_elliptical_kern_rho integrand(this,
  1227 + model,
  1228 + semimajor,
  1229 + semiminor,
  1230 + posangle,
  1231 + obsDir,
  1232 + srcEng,
  1233 + srcTime,
  1234 + rho_obs,
  1235 + posangle_obs,
  1236 + delta_max,
  1237 + iter_phi);
  1238 +
  1239 + // Integrate over model's zenith angle
  1240 + GIntegral integral(&integrand);
  1241 + integral.fixed_iter(iter_rho);
  1242 +
  1243 + // Setup integration boundaries
  1244 + std::vector<double> bounds;
  1245 + bounds.push_back(rho_min);
  1246 + bounds.push_back(rho_max);
  1247 +
  1248 + // If the integration range includes the semiminor boundary, then
  1249 + // add an integration boundary at that location
  1250 + if (semiminor > rho_min && semiminor < rho_max) {
  1251 + bounds.push_back(semiminor);
  1252 + }
  1253 +
  1254 + // Integrate kernel
  1255 + irf = integral.romberg(bounds, iter_rho);
  1256 +
  1257 + // Compile option: Check for NaN/Inf
  1258 + #if defined(G_NAN_CHECK)
  1259 + if (gammalib::is_notanumber(irf) || gammalib::is_infinite(irf)) {
  1260 + std::cout << "*** ERROR: GCTAResponseCube::psf_elliptical:";
  1261 + std::cout << " NaN/Inf encountered";
  1262 + std::cout << " (irf=" << irf;
  1263 + std::cout << ", rho_min=" << rho_min;
  1264 + std::cout << ", rho_max=" << rho_max << ")";
  1265 + std::cout << std::endl;
  1266 + }
  1267 + #endif
  1268 +
  1269 + } // endif: integration interval is valid
1059 1270  
1060 1271 // Return PSF
1061   - return value;
  1272 + return irf;
1062 1273 }
... ...
inst/cta/src/GCTAResponseIrf.cpp
... ... @@ -1267,7 +1267,7 @@ std::string GCTAResponseIrf::print(const GChatter&amp; chatter) const
1267 1267  
1268 1268 /*==========================================================================
1269 1269 = =
1270   - = Model type dependent CTA response methods =
  1270 + = Model type dependent CTA response methods =
1271 1271 = =
1272 1272 ==========================================================================*/
1273 1273  
... ... @@ -1281,192 +1281,22 @@ std::string GCTAResponseIrf::print(const GChatter&amp; chatter) const
1281 1281 * @exception GCTAException::bad_model_type
1282 1282 * Model is not a radial model.
1283 1283 *
1284   - * Integrates the product of the model and the IRF over the true photon
1285   - * arrival direction using
1286   - *
1287   - * \f[
1288   - * \int_{\rho_{\rm min}}^{\rho_{\rm max}}
1289   - * \sin \rho \times S_{\rm p}(\rho | E, t) \times
1290   - * \int_{\omega_{\rm min}}^{\omega_{\rm max}}
1291   - * IRF(\rho, \omega) d\omega d\rho
1292   - * \f]
1293   - *
1294   - * where
1295   - * - \f$S_{\rm p}(\rho | E, t)\f$ is the radial model,
1296   - * - \f$IRF(\rho, \omega)\f$ is the IRF
1297   - * - \f$\rho\f$ is the distance from the model centre, and
1298   - * - \f$\omega\f$ is the azimuth angle is the position angle with respect to
1299   - * the connecting line between the model centre and the observed photon
1300   - * arrival direction.
1301   - *
1302   - * The integration is performed in the coordinate system of the source
1303   - * model spanned by \f$\rho\f$ and \f$\omega\f$ which allows to benefit
1304   - * from the symmetry of the source model.
1305   - *
1306   - * The source centre is located at \f$\vec{m}\f$, and a spherical system
1307   - * is defined around this location with \f$(\omega,\rho)\f$ being the
1308   - * azimuth and zenith angles, respectively. \f$\omega=0\f$ is defined
1309   - * by the direction that connects the source centre \f$\vec{m}\f$ to the
1310   - * measured photon direction \f$\vec{p'}\f$, and \f$\omega\f$ increases
1311   - * counterclockwise.
1312   - *
1313   - * Note that this method approximates the true theta angle (angle between
1314   - * incident photon and pointing direction) by the measured theta angle
1315   - * (angle between the measured photon arrival direction and the pointing
1316   - * direction). Given the slow variation of the PSF shape over the field of
1317   - * view, this approximation should be fine. It helps in fact a lot in
1318   - * speeding up the computations.
1319   - ***************************************************************************/
1320   -#if defined(G_USE_PSF_SYSTEM)
1321   -double GCTAResponseIrf::irf_radial(const GEvent& event,
1322   - const GSource& source,
1323   - const GObservation& obs) const
1324   -{
1325   - // Set integration precision and Romberg order. These values have been
1326   - // determined after careful testing, see
1327   - // https://cta-redmine.irap.omp.eu/issues/1299
1328   - static const int iter_delta = 5;
1329   - static const int iter_phi = 5;
1330   -
1331   - // Set a tiny angle (in radians) to be removed from the model boundary
1332   - // to avoid requesting values beyond the boundary due to round-off
1333   - // errors (this could produce a discontinuity that the integrator does
1334   - // not like)
1335   - static const double tiny = 1.0e-9;
1336   -
1337   - // Initialise Irf
1338   - double irf = 0.0;
1339   -
1340   - // Retrieve CTA pointing
1341   - const GCTAPointing& pnt = retrieve_pnt(G_IRF_RADIAL, obs);
1342   - const GCTAInstDir& dir = retrieve_dir(G_IRF_RADIAL, event);
1343   -
1344   - // Get pointer to radial model
1345   - const GModelSpatialRadial* model = static_cast<const GModelSpatialRadial*>(source.model());
1346   -
1347   - // Get event attributes
1348   - const GSkyDir& obsDir = dir.dir();
1349   - const GEnergy& obsEng = event.energy();
1350   -
1351   - // Get source attributes
1352   - const GEnergy& srcEng = source.energy();
1353   - const GTime& srcTime = source.time();
1354   - double srcLogEng = srcEng.log10TeV();
1355   -
1356   - // Angle between measured photon and model centre (radians)
1357   - double zeta = obsDir.dist(model->dir());
1358   -
1359   - // Angle between measured photon and pointing (radians)
1360   - double obsOffset = obsDir.dist(pnt.dir());
1361   -
1362   - // Compute position angle of pointing in Psf system (radians).
1363   - // The angle Phi=0 of the Psf system is given by the connecting line
1364   - // between the measured photon direction and the model centre
1365   - double phi0 = obsDir.posang(pnt.dir()) - obsDir.posang(model->dir());
1366   -
1367   - // Get Psf delta integration interval [delta_min, delta_max] (radians)
1368   - double theta_max = model->theta_max() - tiny;
1369   - double delta_min = (zeta > theta_max) ? zeta - theta_max : 0.0;
1370   - double delta_max = psf_delta_max(obsOffset, 0.0,
1371   - pnt.zenith(), pnt.azimuth(),
1372   - srcLogEng);
1373   - double distance = zeta + theta_max;
1374   - if (distance < delta_max) {
1375   - delta_max = distance;
1376   - }
1377   -
1378   - // Continue only if interval has positive length
1379   - if (delta_max > delta_min) {
1380   -
1381   - // Setup integration kernel.
1382   - cta_irf_radial_kern_delta integrand(this, model, pnt,
1383   - srcEng, srcLogEng, srcTime,
1384   - obsOffset, obsEng,
1385   - zeta, phi0, theta_max,
1386   - iter_phi);
1387   -
1388   - // Setup integration over Psf delta angle
1389   - GIntegral integral(&integrand);
1390   - integral.fixed_iter(iter_delta);
1391   -
1392   - // Setup integration boundaries
1393   - std::vector<double> bounds;
1394   - bounds.push_back(delta_min);
1395   - bounds.push_back(delta_max);
1396   -
1397   - // If the integration range includes a transition between full
1398   - // containment and partial containment, then add a boundary at
1399   - // this location
1400   - double transition_point = theta_max - zeta;
1401   - if (transition_point > delta_min && transition_point < delta_max) {
1402   - bounds.push_back(transition_point);
1403   - }
1404   -
1405   - // Integrate kernel
1406   - irf = integral.romberg(bounds, iter_delta);
1407   -
1408   - // Compile option: Check for NaN/Inf
1409   - #if defined(G_NAN_CHECK)
1410   - if (gammalib::is_notanumber(irf) || gammalib::is_infinite(irf)) {
1411   - std::cout << "*** ERROR: GCTAResponseIrf::irf_radial:";
1412   - std::cout << " NaN/Inf encountered";
1413   - std::cout << " (irf=" << irf;
1414   - std::cout << ", delta_min=" << delta_min;
1415   - std::cout << ", delta_max=" << delta_max << ")";
1416   - std::cout << std::endl;
1417   - }
1418   - #endif
1419   -
1420   - // Apply deadtime correction
1421   - irf *= obs.deadc(srcTime);
1422   -
1423   - } // endif: delta interval has positive length
1424   -
1425   - // Compile option: Show integration results
1426   - #if defined(G_DEBUG_IRF_RADIAL)
1427   - std::cout << "GCTAResponseIrf::irf_radial:";
1428   - std::cout << " delta=[" << delta_min*gammalib::rad2deg;
1429   - std::cout << ", " << delta_max*gammalib::rad2deg << " deg;";
1430   - std::cout << " zeta=" << zeta*gammalib::rad2deg << " deg;";
1431   - std::cout << " phi0=" << phi0*gammalib::rad2deg << " deg;";
1432   - std::cout << " theta_max=" << theta_max*gammalib::rad2deg << " deg;";
1433   - std::cout << " obsDir=" << obsDir << ";";
1434   - std::cout << " modelDir=" << model->dir() << ";";
1435   - std::cout << " irf=" << irf << std::endl;
1436   - #endif
1437   -
1438   - // Return Irf value
1439   - return irf;
1440   -}
1441   -#else
1442   -/***********************************************************************//**
1443   - * @brief Return IRF value for radial source model
1444   - *
1445   - * @param[in] event Observed event.
1446   - * @param[in] source Source.
1447   - * @param[in] obs Observation.
1448   - *
1449   - * @exception GCTAException::bad_model_type
1450   - * Model is not a radial model.
1451   - *
1452   - * Integrates the product of the model and the Irf over the true photon
1453   - * arrival direction using
  1284 + * Integrates the product of the spatial model and the instrument response
  1285 + * function over the true photon arrival direction using
1454 1286 *
1455 1287 * \f[
1456 1288 * \int_{\rho_{\rm min}}^{\rho_{\rm max}}
1457 1289 * \sin \rho \times S_{\rm p}(\rho | E, t) \times
1458 1290 * \int_{\omega_{\rm min}}^{\omega_{\rm max}}
1459   - * Irf(\rho, \omega) d\omega d\rho
  1291 + * {\rm Irf}(\rho, \omega) d\omega d\rho
1460 1292 * \f]
1461 1293 *
1462 1294 * where
1463   - *
1464   - * \f$S_{\rm p}(\rho | E, t)\f$ is the radial model,
1465   - * \f$Irf(\rho, \omega)\f$ is the IRF
1466   - * \f$\rho\f$ is the distance from the model centre, and
1467   - * \f$\omega\f$ is the azimuth angle is the position angle with respect
1468   - * to the connecting line between the model centre and the observed
1469   - * photon arrival direction.
  1295 + * \f$S_{\rm p}(\rho | E, t)\f$ is the radial spatial model,
  1296 + * \f${\rm Irf}(\rho, \omega)\f$ is the instrument response function,
  1297 + * \f$\rho\f$ is the radial distance from the model centre, and
  1298 + * \f$\omega\f$ is the position angle with respect to the connecting line
  1299 + * between the model centre and the observed photon arrival direction.
1470 1300 *
1471 1301 * The integration is performed in the coordinate system of the source
1472 1302 * model spanned by \f$\rho\f$ and \f$\omega\f$ which allows to benefit
... ... @@ -1596,8 +1426,8 @@ double GCTAResponseIrf::irf_radial(const GEvent&amp; event,
1596 1426 bounds.push_back(rho_max);
1597 1427  
1598 1428 // If the integration range includes a transition between full
1599   - // containment and partial containment, then add a boundary at
1600   - // this location
  1429 + // containment of model within Psf and partial containment, then
  1430 + // add a boundary at this location
1601 1431 double transition_point = delta_max - zeta;
1602 1432 if (transition_point > rho_min && transition_point < rho_max) {
1603 1433 bounds.push_back(transition_point);
... ... @@ -1608,7 +1438,7 @@ double GCTAResponseIrf::irf_radial(const GEvent&amp; event,
1608 1438 // location
1609 1439 const GModelSpatialRadialShell* shell = dynamic_cast<const GModelSpatialRadialShell*>(model);
1610 1440 if (shell != NULL) {
1611   - double shell_radius = shell->radius();
  1441 + double shell_radius = shell->radius() * gammalib::deg2rad;
1612 1442 if (shell_radius > rho_min && shell_radius < rho_max) {
1613 1443 bounds.push_back(shell_radius);
1614 1444 }
... ... @@ -1654,11 +1484,10 @@ double GCTAResponseIrf::irf_radial(const GEvent&amp; event,
1654 1484 // Return IRF value
1655 1485 return irf;
1656 1486 }
1657   -#endif
1658 1487  
1659 1488  
1660 1489 /***********************************************************************//**
1661   - * @brief Return IRF value for elliptical source model
  1490 + * @brief Return Irf value for elliptical source model
1662 1491 *
1663 1492 * @param[in] event Observed event.
1664 1493 * @param[in] source Source.
... ... @@ -1675,17 +1504,16 @@ double GCTAResponseIrf::irf_radial(const GEvent&amp; event,
1675 1504 * \f[
1676 1505 * \int_{\rho_{\rm min}}^{\rho_{\rm max}}
1677 1506 * \sin \rho \times
1678   - * \int_{\omega_{\rm min}}^{\omega_{\rm max}}
  1507 + * \int_{\omega}
1679 1508 * S_{\rm p}(\rho, \omega | E, t) \, IRF(\rho, \omega) d\omega d\rho
1680 1509 * \f]
1681 1510 *
1682 1511 * where
1683   - * - \f$S_{\rm p}(\rho, \omega | E, t)\f$ is the radial model,
1684   - * - \f$IRF(\rho, \omega)\f$ is the IRF
1685   - * - \f$\rho\f$ is the distance from the model centre, and
1686   - * - \f$\omega\f$ is the azimuth angle is the position angle with respect to
1687   - * the connecting line between the model centre and the observed photon
1688   - * arrival direction.
  1512 + * \f$S_{\rm p}(\rho, \omega | E, t)\f$ is the elliptical model,
  1513 + * \f$IRF(\rho, \omega)\f$ is the instrument response function,
  1514 + * \f$\rho\f$ is the distance from the model centre, and
  1515 + * \f$\omega\f$ is the position angle with respect to the connecting line
  1516 + * between the model centre and the observed photon arrival direction.
1689 1517 *
1690 1518 * The source model centre is located at \f$\vec{m}\f$, and a spherical
1691 1519 * coordinate system is defined around this location with \f$(\rho,\omega)\f$
... ... @@ -1731,26 +1559,16 @@ double GCTAResponseIrf::irf_elliptical(const GEvent&amp; event,
1731 1559 // Determine angular distance between observed photon direction and model
1732 1560 // centre and position angle of observed photon direction seen from the
1733 1561 // model centre [radians]
1734   - double zeta = centre.dist(obsDir);
1735   - double obsOmega = centre.posang(obsDir);
1736   -
1737   - // Determine angular distance between measured photon direction and
1738   - // pointing direction [radians]
1739   - double eta = pnt.dir().dist(obsDir);
  1562 + double rho_obs = centre.dist(obsDir);
  1563 + double posangle_obs = centre.posang(obsDir);
1740 1564  
1741 1565 // Determine angular distance between model centre and pointing direction
1742 1566 // [radians]
1743   - double lambda = centre.dist(pnt.dir());
  1567 + double rho_pnt = centre.dist(pnt.dir());
  1568 + double posangle_pnt = centre.posang(pnt.dir());
1744 1569  
1745 1570 // Compute azimuth angle of pointing in model coordinate system [radians]
1746   - // This azimuth angle is comprised in the interval [0,pi], and defines
1747   - // the zero point of the model coordinate system.
1748   - double omega0 = 0.0;
1749   - double denom = std::sin(lambda) * std::sin(zeta);
1750   - if (denom != 0.0) {
1751   - double arg = (std::cos(eta) - std::cos(lambda) * std::cos(zeta))/denom;
1752   - omega0 = gammalib::acos(arg);
1753   - }
  1571 + double omega_pnt = posangle_pnt - posangle_obs;
1754 1572  
1755 1573 // Get log10(E/TeV) of true photon energy
1756 1574 double srcLogEng = srcEng.log10TeV();
... ... @@ -1759,18 +1577,37 @@ double GCTAResponseIrf::irf_elliptical(const GEvent&amp; event,
1759 1577 // angle (eta) as the true theta angle between the source and the pointing
1760 1578 // directions. As we only use the angle to determine the maximum PSF size,
1761 1579 // this should be sufficient.
1762   - double theta = eta;
  1580 + double theta = pnt.dir().dist(obsDir);
1763 1581 double phi = 0.0; //TODO: Implement IRF Phi dependence
1764 1582 double delta_max = psf_delta_max(theta, phi, zenith, azimuth, srcLogEng);
1765 1583  
1766   - // Get maximum source model radius [radians]
1767   - double src_max = model->theta_max();
  1584 + // Get the ellipse boundary (radians). Note that these are NOT the
  1585 + // parameters of the ellipse but of a boundary ellipse that is used
  1586 + // for computing the relevant omega angle intervals for a given angle
  1587 + // rho. The boundary ellipse takes care of the possibility that the
  1588 + // semiminor axis is larger than the semimajor axis
  1589 + double semimajor; // Will be the larger axis
  1590 + double semiminor; // Will be the smaller axis
  1591 + double posangle; // Will be the corrected position angle
  1592 + double aspect_ratio; // Ratio between smaller/larger axis of model
  1593 + if (model->semimajor() >= model->semiminor()) {
  1594 + aspect_ratio = (model->semimajor() > 0.0) ?
  1595 + model->semiminor() / model->semimajor() : 0.0;
  1596 + posangle = model->posangle() * gammalib::deg2rad;
  1597 + }
  1598 + else {
  1599 + aspect_ratio = (model->semiminor() > 0.0) ?
  1600 + model->semimajor() / model->semiminor() : 0.0;
  1601 + posangle = model->posangle() * gammalib::deg2rad + gammalib::pihalf;
  1602 + }
  1603 + semimajor = model->theta_max();
  1604 + semiminor = semimajor * aspect_ratio;
1768 1605  
1769 1606 // Set zenith angle integration range for elliptical model
1770   - double rho_min = (zeta > delta_max) ? zeta - delta_max : 0.0;
1771   - double rho_max = zeta + delta_max;
1772   - if (rho_max > src_max) {
1773   - rho_max = src_max;
  1607 + double rho_min = (rho_obs > delta_max) ? rho_obs - delta_max : 0.0;
  1608 + double rho_max = rho_obs + delta_max;
  1609 + if (rho_max > semimajor) {
  1610 + rho_max = semimajor;
1774 1611 }
1775 1612  
1776 1613 // Initialise IRF value
... ... @@ -1782,16 +1619,19 @@ double GCTAResponseIrf::irf_elliptical(const GEvent&amp; event,
1782 1619 // Setup integration kernel
1783 1620 cta_irf_elliptical_kern_rho integrand(*this,
1784 1621 *model,
  1622 + semimajor,
  1623 + semiminor,
  1624 + posangle,
1785 1625 zenith,
1786 1626 azimuth,
1787 1627 srcEng,
1788 1628 srcTime,
1789 1629 srcLogEng,
1790 1630 obsEng,
1791   - zeta,
1792   - lambda,
1793   - obsOmega,
1794   - omega0,
  1631 + rho_obs,
  1632 + posangle_obs,
  1633 + rho_pnt,
  1634 + omega_pnt,
1795 1635 delta_max,
1796 1636 iter_phi);
1797 1637  
... ... @@ -1804,23 +1644,10 @@ double GCTAResponseIrf::irf_elliptical(const GEvent&amp; event,
1804 1644 bounds.push_back(rho_min);
1805 1645 bounds.push_back(rho_max);
1806 1646  
1807   - // If the integration range includes a transition between full
1808   - // containment and partial containment, then add a boundary at
1809   - // this location
1810   - double transition_point = delta_max - zeta;
1811   - if (transition_point > rho_min && transition_point < rho_max) {
1812   - bounds.push_back(transition_point);
1813   - }
1814   -
1815   - // If we have a shell model then add an integration boundary for the
1816   - // shell radius as a function discontinuity will occur at this
1817   - // location
1818   - const GModelSpatialRadialShell* shell = dynamic_cast<const GModelSpatialRadialShell*>(model);
1819   - if (shell != NULL) {
1820   - double shell_radius = shell->radius();
1821   - if (shell_radius > rho_min && shell_radius < rho_max) {
1822   - bounds.push_back(shell_radius);
1823   - }
  1647 + // If the integration range includes the semiminor boundary, then
  1648 + // add an integration boundary at that location
  1649 + if (semiminor > rho_min && semiminor < rho_max) {
  1650 + bounds.push_back(semiminor);
1824 1651 }
1825 1652  
1826 1653 // Integrate kernel
... ... @@ -1833,8 +1660,7 @@ double GCTAResponseIrf::irf_elliptical(const GEvent&amp; event,
1833 1660 std::cout << " NaN/Inf encountered";
1834 1661 std::cout << " (irf=" << irf;
1835 1662 std::cout << ", rho_min=" << rho_min;
1836   - std::cout << ", rho_max=" << rho_max;
1837   - std::cout << ", omega0=" << omega0 << ")";
  1663 + std::cout << ", rho_max=" << rho_max << ")";
1838 1664 std::cout << std::endl;
1839 1665 }
1840 1666 #endif
... ... @@ -2188,19 +2014,27 @@ double GCTAResponseIrf::npred_radial(const GSource&amp; source,
2188 2014 bounds.push_back(rho_max);
2189 2015  
2190 2016 // If the integration range includes a transition between full
2191   - // containment and partial containment, then add a boundary at
2192   - // this location
  2017 + // containment of model within ROI and partial containment, then
  2018 + // add a boundary at this location
2193 2019 double transition_point = roi_psf_radius - roi_model_distance;
2194 2020 if (transition_point > rho_min && transition_point < rho_max) {
2195 2021 bounds.push_back(transition_point);
2196 2022 }
2197 2023  
  2024 + // If the integration range includes a transition between full
  2025 + // containment of ROI within model and partial containment, then
  2026 + // add a boundary at this location
  2027 + transition_point = roi_psf_radius + roi_model_distance;
  2028 + if (transition_point > rho_min && transition_point < rho_max) {
  2029 + bounds.push_back(transition_point);
  2030 + }
  2031 +
2198 2032 // If we have a shell model then add an integration boundary for the
2199 2033 // shell radius as a function discontinuity will occur at this
2200 2034 // location
2201 2035 const GModelSpatialRadialShell* shell = dynamic_cast<const GModelSpatialRadialShell*>(model);
2202 2036 if (shell != NULL) {
2203   - double shell_radius = shell->radius();
  2037 + double shell_radius = shell->radius() * gammalib::deg2rad;
2204 2038 if (shell_radius > rho_min && shell_radius < rho_max) {
2205 2039 bounds.push_back(shell_radius);
2206 2040 }
... ... @@ -2327,23 +2161,44 @@ double GCTAResponseIrf::npred_elliptical(const GSource&amp; source,
2327 2161 // to make sure we encompass the entire PSF.
2328 2162 double psf_max_radius = psf_delta_max(0.0, 0.0, zenith, azimuth, srcLogEng);
2329 2163  
2330   - // Extract ROI radius (radians)
2331   - double roi_radius = roi.radius() * gammalib::deg2rad;
2332   -
2333   - // Compute distance between ROI and model centre (radians)
2334   - double roi_model_distance = roi.centre().dir().dist(centre);
2335   -
2336   - // Compute the ROI radius plus maximum PSF radius (radians). Any photon
  2164 + // Extract ROI radius plus maximum PSF radius (radians). Any photon
2337 2165 // coming from beyond this radius will not make it in the dataspace and
2338 2166 // thus can be neglected.
2339   - double roi_psf_radius = roi_radius + psf_max_radius;
  2167 + double radius_roi = roi.radius() * gammalib::deg2rad + psf_max_radius;
  2168 +
  2169 + // Compute distance between ROI and model centre (radians)
  2170 + double rho_roi = roi.centre().dir().dist(centre);
  2171 +
  2172 + // Get the ellipse boundary (radians). Note that these are NOT the
  2173 + // parameters of the ellipse but of a boundary ellipse that is used
  2174 + // for computing the relevant omega angle intervals for a given angle
  2175 + // rho. The boundary ellipse takes care of the possibility that the
  2176 + // semiminor axis is larger than the semimajor axis
  2177 + double semimajor; // Will be the larger axis
  2178 + double semiminor; // Will be the smaller axis
  2179 + double posangle; // Will be the corrected position angle
  2180 + double aspect_ratio; // Ratio between smaller/larger axis of model
  2181 + if (model->semimajor() >= model->semiminor()) {
  2182 + aspect_ratio = (model->semimajor() > 0.0) ?
  2183 + model->semiminor() / model->semimajor() : 0.0;
  2184 + posangle = model->posangle() * gammalib::deg2rad;
  2185 + }
  2186 + else {
  2187 + aspect_ratio = (model->semiminor() > 0.0) ?
  2188 + model->semimajor() / model->semiminor() : 0.0;
  2189 + posangle = model->posangle() * gammalib::deg2rad + gammalib::pihalf;
  2190 + }
  2191 + semimajor = model->theta_max();
  2192 + semiminor = semimajor * aspect_ratio;
2340 2193  
2341 2194 // Set offset angle integration range. We take here the ROI+PSF into
2342 2195 // account to make no integrations beyond the point where the
2343 2196 // contribution drops to zero.
2344   - double rho_min = (roi_model_distance > roi_psf_radius)
2345   - ? roi_model_distance - roi_psf_radius: 0.0;
2346   - double rho_max = model->theta_max();
  2197 + double rho_min = (rho_roi > radius_roi) ? rho_roi - radius_roi: 0.0;
  2198 + double rho_max = rho_roi + radius_roi;
  2199 + if (rho_max > semimajor) {
  2200 + rho_max = semimajor;
  2201 + }
2347 2202  
2348 2203 // Perform offset angle integration only if interval is valid
2349 2204 if (rho_max > rho_min) {
... ... @@ -2358,18 +2213,21 @@ double GCTAResponseIrf::npred_elliptical(const GSource&amp; source,
2358 2213  
2359 2214 // Compute position angle of ROI centre with respect to model
2360 2215 // centre (radians)
2361   - double omega0 = centre.posang(roi.centre().dir());
  2216 + double posangle_roi = centre.posang(roi.centre().dir());
2362 2217  
2363 2218 // Setup integration kernel
2364 2219 cta_npred_elliptical_kern_rho integrand(*this,
2365 2220 *model,
2366   - source.energy(),
2367   - source.time(),
  2221 + semimajor,
  2222 + semiminor,
  2223 + posangle,
  2224 + srcEng,
  2225 + srcTime,
2368 2226 cta,
2369 2227 rot,
2370   - roi_model_distance,
2371   - roi_psf_radius,
2372   - omega0,
  2228 + rho_roi,
  2229 + posangle_roi,
  2230 + radius_roi,
2373 2231 iter_phi);
2374 2232  
2375 2233 // Integrate over model's zenith angle
... ... @@ -2381,23 +2239,10 @@ double GCTAResponseIrf::npred_elliptical(const GSource&amp; source,
2381 2239 bounds.push_back(rho_min);
2382 2240 bounds.push_back(rho_max);
2383 2241  
2384   - // If the integration range includes a transition between full
2385   - // containment and partial containment, then add a boundary at
2386   - // this location
2387   - double transition_point = roi_psf_radius - roi_model_distance;
2388   - if (transition_point > rho_min && transition_point < rho_max) {
2389   - bounds.push_back(transition_point);
2390   - }
2391   -
2392   - // If we have a shell model then add an integration boundary for the
2393   - // shell radius as a function discontinuity will occur at this
2394   - // location
2395   - const GModelSpatialRadialShell* shell = dynamic_cast<const GModelSpatialRadialShell*>(model);
2396   - if (shell != NULL) {
2397   - double shell_radius = shell->radius();
2398   - if (shell_radius > rho_min && shell_radius < rho_max) {
2399   - bounds.push_back(shell_radius);
2400   - }
  2242 + // If the integration range includes the semiminor boundary, then
  2243 + // add an integration boundary at that location
  2244 + if (semiminor > rho_min && semiminor < rho_max) {
  2245 + bounds.push_back(semiminor);
2401 2246 }
2402 2247  
2403 2248 // Integrate kernel
... ...
inst/cta/src/GCTAResponse_helpers.cpp
... ... @@ -46,12 +46,111 @@
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  
52 53  
53 54 /*==========================================================================
54 55 = =
  56 + = Helper functions =
  57 + = =
  58 + ==========================================================================*/
  59 +
  60 +/***********************************************************************//**
  61 + * @brief Limit omega interval
  62 + *
  63 + * @param[in] min Interval minimum (radians).
  64 + * @param[in] max Interval maximum (radians).
  65 + * @param[in] domega Half length of interval (radians).
  66 + * @return Vector of intervals.
  67 + *
  68 + * Limits an omega interval [@p min,@p max] to the interval specified by
  69 + * [-@p domega,@p domega]. This may lead to a split of [@p min,@p max] in
  70 + * several intervals. The method thus returns a vector of intervals that
  71 + * overlap with [-@p domega,@p domega]. If there is no overlap with the
  72 + * interval, the method returns an empty vector.
  73 + *
  74 + * The method takes care of wrap arounds. It is assumed that on input
  75 + * [@p min,@p max] is contained within [-2pi,+2pi].
  76 + ***************************************************************************/
  77 +cta_omega_intervals gammalib::limit_omega(const double& min,
  78 + const double& max,
  79 + const double& domega)
  80 +{
  81 + // Allocate intervals
  82 + cta_omega_intervals intervals;
  83 +
  84 + // Continue only if domega is smaller than pi
  85 + if (domega < gammalib::pi) {
  86 +
  87 + // Set limiting intervals. To take care of a possible wrap around in
  88 + // omega we consider also intervals that are shifted by +/-2pi
  89 + double omega_min = -domega;
  90 + double omega_max = +domega;
  91 + double omega_min_plus = omega_min + gammalib::twopi;
  92 + double omega_max_plus = omega_max + gammalib::twopi;
  93 + double omega_min_minus = omega_min - gammalib::twopi;
  94 + double omega_max_minus = omega_max - gammalib::twopi;
  95 +
  96 + // If the [min,max] interval overlaps with the unshifted
  97 + // [-domega,domega] interval then constrain the interval
  98 + if (max > omega_min && min < omega_max) {
  99 + double interval_min = min;
  100 + double interval_max = max;
  101 + if (interval_min < omega_min) {
  102 + interval_min = omega_min;
  103 + }
  104 + if (interval_max > omega_max) {
  105 + interval_max = omega_max;
  106 + }
  107 + intervals.push_back(std::make_pair(interval_min,interval_max));
  108 + }
  109 +
  110 + // If the [min,max] interval overlaps with the [-domega,domega]
  111 + // interval shifted by +2pi then constrain the interval using the
  112 + // shifted interval
  113 + if (max > omega_min_plus && min < omega_max_plus) {
  114 + double interval_min = min;
  115 + double interval_max = max;
  116 + if (interval_min < omega_min_plus) {
  117 + interval_min = omega_min_plus;
  118 + }
  119 + if (interval_max > omega_max_plus) {
  120 + interval_max = omega_max_plus;
  121 + }
  122 + intervals.push_back(std::make_pair(interval_min,interval_max));
  123 + }
  124 +
  125 + // If the [min,max] interval overlaps with the [-domega,domega]
  126 + // interval shifted by -2pi then constrain the interval using the
  127 + // shifted interval
  128 + if (max > omega_min_minus && min < omega_max_minus) {
  129 + double interval_min = min;
  130 + double interval_max = max;
  131 + if (interval_min < omega_min_minus) {
  132 + interval_min = omega_min_minus;
  133 + }
  134 + if (interval_max > omega_max_minus) {
  135 + interval_max = omega_max_minus;
  136 + }
  137 + intervals.push_back(std::make_pair(interval_min,interval_max));
  138 + }
  139 +
  140 + } // endif: interval was not the full circle
  141 +
  142 + // ... otherwise append the provided interval
  143 + else {
  144 + intervals.push_back(std::make_pair(min,max));
  145 + }
  146 +
  147 + // Return intervals
  148 + return intervals;
  149 +}
  150 +
  151 +
  152 +/*==========================================================================
  153 + = =
55 154 = Helper class methods for response computation =
56 155 = =
57 156 ==========================================================================*/
... ... @@ -184,8 +283,26 @@ double cta_irf_radial_kern_rho::eval(const double&amp; rho)
184 283 double omega_min = -domega;
185 284 double omega_max = +domega;
186 285  
  286 + // Reduce rho by an infinite amount to avoid rounding errors
  287 + // at the boundary of a sharp edged model
  288 + double rho_kluge = rho - 1.0e-12;
  289 + if (rho_kluge < 0.0) {
  290 + rho_kluge = 0.0;
  291 + }
  292 +
187 293 // Evaluate sky model
188   - double model = m_model.eval(rho, m_srcEng, m_srcTime);
  294 + double model = m_model.eval(rho_kluge, m_srcEng, m_srcTime);
  295 +
  296 + // Debug: test if model is non positive
  297 + #if defined(G_DEBUG_MODEL_ZERO)
  298 + if (model <= 0.0) {
  299 + std::cout << "*** WARNING: cta_irf_radial_kern_rho::eval";
  300 + std::cout << " zero model for (rho)=(";
  301 + std::cout << rho*gammalib::rad2deg << ")";
  302 + std::cout << " rho-r_model=" << (rho-m_model.theta_max());
  303 + std::cout << " radians" << std::endl;
  304 + }
  305 + #endif
189 306  
190 307 // Continue only if model is positive
191 308 if (model > 0.0) {
... ... @@ -336,7 +453,7 @@ double cta_irf_radial_kern_omega::eval(const double&amp; omega)
336 453 * \f]
337 454 *
338 455 * The azimuth angle integration range
339   - * \f$[\omega_{\rm min}, \omega_{\rm max}\f$
  456 + * \f$[\omega_{\rm min}, \omega_{\rm max}]\f$
340 457 * is limited to an arc around the vector connecting the model centre to
341 458 * the ROI centre. This limitation assures that the integration converges
342 459 * properly.
... ... @@ -364,8 +481,26 @@ double cta_npred_radial_kern_rho::eval(const double&amp; rho)
364 481 double omega_min = m_omega0 - domega;
365 482 double omega_max = m_omega0 + domega;
366 483  
  484 + // Reduce rho by an infinite amount to avoid rounding errors
  485 + // at the boundary of a sharp edged model
  486 + double rho_kluge = rho - 1.0e-12;
  487 + if (rho_kluge < 0.0) {
  488 + rho_kluge = 0.0;
  489 + }
  490 +
367 491 // Get radial model value
368   - double model = m_model.eval(rho, m_srcEng, m_srcTime);
  492 + double model = m_model.eval(rho_kluge, m_srcEng, m_srcTime);
  493 +
  494 + // Debug: test if model is non positive
  495 + #if defined(G_DEBUG_MODEL_ZERO)
  496 + if (model <= 0.0) {
  497 + std::cout << "*** WARNING: cta_npred_radial_kern_rho::eval";
  498 + std::cout << " zero model for (rho)=(";
  499 + std::cout << rho*gammalib::rad2deg << ")";
  500 + std::cout << " rho-r_model=" << (rho-m_model.theta_max());
  501 + std::cout << " radians" << std::endl;
  502 + }
  503 + #endif
369 504  
370 505 // Continue only if we have a positive model value
371 506 if (model > 0.0) {
... ... @@ -470,7 +605,8 @@ double cta_npred_radial_kern_omega::eval(const double&amp; omega)
470 605 /***********************************************************************//**
471 606 * @brief Kernel for elliptical model integration over model's zenith angle
472 607 *
473   - * @param[in] rho Zenith angle with respect to model centre [radians].
  608 + * @param[in] rho Radial distance from model centre [radians].
  609 + * @return Radial IRF integration kernel.
474 610 *
475 611 * Computes
476 612 *
... ... @@ -480,6 +616,20 @@ double cta_npred_radial_kern_omega::eval(const double&amp; omega)
480 616 * S_{\rm p}(\rho, \omega | E, t) \, IRF(\rho, \omega)
481 617 * d\omega
482 618 * \f]
  619 + *
  620 + * where
  621 + * \f$\omega\f$ is the azimuth angle with respect to the model centre,
  622 + * counted counterclockwise from the vector connecting the model centre
  623 + * to the observed photon direction, and
  624 + * \f$\rho\f$ is the radial distance from the model centre.
  625 + * \f$S_{\rm p}(\rho, \omega | E, t)\f$ is the elliptical source model
  626 + * for a given true photon energy and photon arrival time,
  627 + * \f$IRF(\rho, \omega)\f$ is the instrument response function.
  628 + *
  629 + * The method performs the required coordinate transformations from the
  630 + * model system, spanned by \f$(\rho, \omega)\f$, to the system needed for
  631 + * IRF computations. Furthermore, the method limits the integration range
  632 + * to area where the ellipse intersects the IRF.
483 633 ***************************************************************************/
484 634 double cta_irf_elliptical_kern_rho::eval(const double& rho)
485 635 {
... ... @@ -489,29 +639,34 @@ double cta_irf_elliptical_kern_rho::eval(const double&amp; rho)
489 639 // Continue only if rho is positive
490 640 if (rho > 0.0) {
491 641  
492   - // Compute half length of arc that lies within PSF validity circle
493   - // (in radians)
  642 + // Compute half length of the arc (in radians) from a circle with
  643 + // radius rho that intersects with the point spread function, defined
  644 + // as a circle with maximum radius m_delta_max
494 645 double domega = 0.5 * gammalib::cta_roi_arclength(rho,
495   - m_zeta,
496   - m_cos_zeta,
497   - m_sin_zeta,
  646 + m_rho_obs,
  647 + m_cos_rho_obs,
  648 + m_sin_rho_obs,
498 649 m_delta_max,
499 650 m_cos_delta_max);
500 651  
501 652 // Continue only if arc length is positive
502 653 if (domega > 0.0) {
503 654  
504   - // Compute omega integration range
505   - double omega_min = -domega;
506   - double omega_max = +domega;
507   -
508 655 // Precompute cosine and sine terms for azimuthal integration
509 656 double cos_rho = std::cos(rho);
510 657 double sin_rho = std::sin(rho);
511   - double cos_psf = cos_rho*m_cos_zeta;
512   - double sin_psf = sin_rho*m_sin_zeta;
513   - double cos_ph = cos_rho*m_cos_lambda;
514   - double sin_ph = sin_rho*m_sin_lambda;
  658 + double cos_psf = cos_rho * m_cos_rho_obs;
  659 + double sin_psf = sin_rho * m_sin_rho_obs;
  660 + double cos_ph = cos_rho * m_cos_rho_pnt;
  661 + double sin_ph = sin_rho * m_sin_rho_pnt;
  662 +
  663 + // Reduce rho by an infinite amount to avoid rounding errors
  664 + // at the boundary of a sharp edged model (e.g. an elliptical
  665 + // disk model)
  666 + double rho_kluge = rho - 1.0e-12;
  667 + if (rho_kluge < 0.0) {
  668 + rho_kluge = 0.0;
  669 + }
515 670  
516 671 // Setup integration kernel
517 672 cta_irf_elliptical_kern_omega integrand(m_rsp,
... ... @@ -522,18 +677,93 @@ double cta_irf_elliptical_kern_rho::eval(const double&amp; rho)
522 677 m_srcTime,
523 678 m_srcLogEng,
524 679 m_obsEng,
525   - m_obsOmega,
526   - m_omega0,
527   - rho,
  680 + m_posangle_obs,
  681 + m_omega_pnt,
  682 + rho_kluge,
528 683 cos_psf,
529 684 sin_psf,
530 685 cos_ph,
531 686 sin_ph);
532 687  
533   - // Integrate over phi
  688 + // Setup integrator
534 689 GIntegral integral(&integrand);
535 690 integral.fixed_iter(m_iter);
536   - irf = integral.romberg(omega_min, omega_max, m_iter) * sin_rho;
  691 +
  692 + // If the radius rho is not larger than the semiminor axis
  693 + // boundary, the circle with that radius is fully contained in
  694 + // the ellipse and we can just integrate over the relevant arc
  695 + if (rho <= m_semiminor) {
  696 +
  697 + // Compute omega integration range
  698 + double omega_min = -domega;
  699 + double omega_max = +domega;
  700 +
  701 + // Integrate over omega
  702 + irf = integral.romberg(omega_min, omega_max, m_iter) *
  703 + sin_rho;
  704 +
  705 + } // endif: circle comprised in ellipse
  706 +
  707 + // ... otherwise there are arcs that intersect with the Psf circle
  708 + else {
  709 +
  710 + // Compute half the arc length (in radians) of a circle of
  711 + // radius rho, centred on the model, that intersects with
  712 + // the ellipse boundary
  713 + double arg1 = 1.0 - (m_semiminor*m_semiminor) / (rho*rho);
  714 + double arg2 = 1.0 - (m_semiminor*m_semiminor) /
  715 + (m_semimajor*m_semimajor);
  716 + double omega_width = std::acos(std::sqrt(arg1/arg2));
  717 +
  718 + // Continue only if the arclength is positive
  719 + if (omega_width > 0.0) {
  720 +
  721 + // Compute azimuth angle difference between ellipse
  722 + // position angle and position angle of observed
  723 + // photon in the model system. This angle will define
  724 + // the reference point around which the circle arc. Make
  725 + // sure that omega_0 is within [-pi,pi] thus that the omega
  726 + // intervals are between [-2pi,2pi]
  727 + double omega_0 = m_posangle - m_posangle_obs;
  728 + if (omega_0 > gammalib::pi) {
  729 + omega_0 -= gammalib::pi;
  730 + }
  731 + else if (omega_0 < -gammalib::pi) {
  732 + omega_0 += gammalib::pi;
  733 + }
  734 +
  735 + // Compute azimuth angle intervals
  736 + double omega1_min = omega_0 - omega_width;
  737 + double omega1_max = omega_0 + omega_width;
  738 + double omega2_min = omega1_min + gammalib::pi;
  739 + double omega2_max = omega1_max + gammalib::pi;
  740 +
  741 + // Limit intervals to the intersection of the ellipse with
  742 + // the Psf circle. This may lead to a split of intervals,
  743 + // and we gather all these intervals in a special interval
  744 + // pair containers
  745 + cta_omega_intervals intervals1 =
  746 + gammalib::limit_omega(omega1_min, omega1_max, domega);
  747 + cta_omega_intervals intervals2 =
  748 + gammalib::limit_omega(omega2_min, omega2_max, domega);
  749 +
  750 + // Integrate over all intervals for omega1
  751 + for (int i = 0; i < intervals1.size(); ++i) {
  752 + double min = intervals1[i].first;
  753 + double max = intervals1[i].second;
  754 + irf += integral.romberg(min, max, m_iter) * sin_rho;
  755 + }
  756 +
  757 + // Integrate over all intervals for omega2
  758 + for (int i = 0; i < intervals2.size(); ++i) {
  759 + double min = intervals2[i].first;
  760 + double max = intervals2[i].second;
  761 + irf += integral.romberg(min, max, m_iter) * sin_rho;
  762 + }
  763 +
  764 + } // endif: arc length was positive
  765 +
  766 + } // endelse: circle was not comprised in ellipse
537 767  
538 768 // Compile option: Check for NaN/Inf
539 769 #if defined(G_NAN_CHECK)
... ... @@ -565,12 +795,18 @@ double cta_irf_elliptical_kern_rho::eval(const double&amp; rho)
565 795 * Computes
566 796 *
567 797 * \f[
568   - * S_{\rm p}(\rho, \omega | E, t) \, IRF(\rho, \omega)
  798 + * S_{\rm p}(\omega | \rho, E, t) \, IRF(\omega | \rho)
569 799 * \f]
570 800 *
571   - * From the model coordinates \f$(\rho,\omega)\f$, the method computes the
572   - * angle between the true (\f$\vec{p}\f$) and observed (\f$\vec{p'}\f$)
573   - * photon arrival direction using
  801 + * where
  802 + * \f$\omega\f$ is the azimuth angle with respect to the model centre,
  803 + * counted counterclockwise from the vector connecting the model centre
  804 + * to the observed photon direction, and
  805 + * \f$\rho\f$ is the radial distance from the model centre.
  806 + *
  807 + * From the coordinates \f$(\rho,\omega)\f$ in the model system, the method
  808 + * computes the angle between the true (\f$\vec{p}\f$) and observed
  809 + * (\f$\vec{p'}\f$) photon arrival direction using
574 810 *
575 811 * \f[\delta = \arccos(\cos \rho \cos \zeta +
576 812 * \sin \rho \sin \zeta \cos \omega)\f]
... ... @@ -598,18 +834,42 @@ double cta_irf_elliptical_kern_omega::eval(const double&amp; omega)
598 834 // Initialise IRF value
599 835 double irf = 0.0;
600 836  
  837 + // Compute azimuth angle in model coordinate system (radians)
  838 + double omega_model = omega + m_posangle_obs;
  839 +
601 840 // Evaluate sky model
602   - double model = m_model.eval(m_rho, omega + m_obsOmega, m_srcEng, m_srcTime);
  841 + double model = m_model.eval(m_rho, omega_model, m_srcEng, m_srcTime);
  842 +
  843 + // Debug: test if model is non positive
  844 + #if defined(G_DEBUG_MODEL_ZERO)
  845 + if (model <= 0.0) {
  846 + double m_semiminor_rad = m_model.semiminor() * gammalib::deg2rad;
  847 + double m_semimajor_rad = m_model.semimajor() * gammalib::deg2rad;
  848 + double diff_angle = omega_model - m_model.posangle() * gammalib::deg2rad;
  849 + double cosinus = std::cos(diff_angle);
  850 + double sinus = std::sin(diff_angle);
  851 + double arg1 = m_semiminor_rad * cosinus;
  852 + double arg2 = m_semimajor_rad * sinus;
  853 + double r_ellipse = m_semiminor_rad * m_semimajor_rad /
  854 + std::sqrt(arg1*arg1 + arg2*arg2);
  855 + std::cout << "*** WARNING: cta_irf_elliptical_kern_omega::eval";
  856 + std::cout << " zero model for (rho,omega)=(";
  857 + std::cout << m_rho*gammalib::rad2deg << ",";
  858 + std::cout << omega*gammalib::rad2deg << ")";
  859 + std::cout << " rho-r_ellipse=" << (m_rho-r_ellipse) << " radians";
  860 + std::cout << std::endl;
  861 + }
  862 + #endif
603 863  
604 864 // Continue only if model is positive
605 865 if (model > 0.0) {
606 866  
607   - // Compute PSF offset angle [radians]
  867 + // Compute Psf offset angle [radians]
608 868 double delta = std::acos(m_cos_psf + m_sin_psf * std::cos(omega));
609 869  
610 870 // Compute true photon offset and azimuth angle in camera system
611 871 // [radians]
612   - double theta = std::acos(m_cos_ph + m_sin_ph * std::cos(m_omega0 - omega));
  872 + double theta = std::acos(m_cos_ph + m_sin_ph * std::cos(m_omega_pnt - omega));
613 873 double phi = 0.0; //TODO: Implement IRF Phi dependence
614 874  
615 875 // Evaluate IRF * model
... ... @@ -659,10 +919,20 @@ double cta_irf_elliptical_kern_omega::eval(const double&amp; omega)
659 919 * N_{\rm pred}(\rho,\omega) d\omega
660 920 * \f]
661 921 *
662   - * The azimuth angle integration range
663   - * \f$[\omega_{\rm min}, \omega_{\rm max}\f$
664   - * is limited to an arc around the vector connecting the model centre to
665   - * the ROI centre. This limitation assures proper converges properly.
  922 + * where
  923 + * \f$\omega\f$ is the azimuth angle with respect to the model centre,
  924 + * counted counterclockwise from the vector connecting the model centre
  925 + * to the ROI centre, and
  926 + * \f$\rho\f$ is the radial distance from the model centre.
  927 + * \f$S_{\rm p}(\rho, \omega | E, t)\f$ is the elliptical source model
  928 + * for a given true photon energy and photon arrival time,
  929 + * \f$N_{\rm pred}(\rho,\omega)\f$ is the data space integral over the
  930 + * response function.
  931 + *
  932 + * The method performs the required coordinate transformations from the
  933 + * model system, spanned by \f$(\rho, \omega)\f$, to the system needed for
  934 + * Npred computations. Furthermore, the method limits the integration range
  935 + * to area where the ellipse intersects the ROI.
666 936 ***************************************************************************/
667 937 double cta_npred_elliptical_kern_rho::eval(const double& rho)
668 938 {
... ... @@ -674,23 +944,27 @@ double cta_npred_elliptical_kern_rho::eval(const double&amp; rho)
674 944  
675 945 // Compute half length of arc that lies within ROI+PSF radius (radians)
676 946 double domega = 0.5 * gammalib::cta_roi_arclength(rho,
677   - m_dist,
678   - m_cos_dist,
679   - m_sin_dist,
680   - m_radius,
681   - m_cos_radius);
  947 + m_rho_roi,
  948 + m_cos_rho_roi,
  949 + m_sin_rho_roi,
  950 + m_radius_roi,
  951 + m_cos_radius_roi);
682 952  
683 953 // Continue only if arc length is positive
684 954 if (domega > 0.0) {
685 955  
686   - // Compute phi integration range
687   - double omega_min = m_omega0 - domega;
688   - double omega_max = m_omega0 + domega;
689   -
690   - // Compute sine and cosine of offset angle
  956 + // Compute sine and cosine terms for azimuthal integration
691 957 double sin_rho = std::sin(rho);
692 958 double cos_rho = std::cos(rho);
693 959  
  960 + // Reduce rho by an infinite amount to avoid rounding errors
  961 + // at the boundary of a sharp edged model (e.g. an elliptical
  962 + // disk model)
  963 + double rho_kluge = rho - 1.0e-12;
  964 + if (rho_kluge < 0.0) {
  965 + rho_kluge = 0.0;
  966 + }
  967 +
694 968 // Setup phi integration kernel
695 969 cta_npred_elliptical_kern_omega integrand(m_rsp,
696 970 m_model,
... ... @@ -698,13 +972,90 @@ double cta_npred_elliptical_kern_rho::eval(const double&amp; rho)
698 972 m_srcTime,
699 973 m_obs,
700 974 m_rot,
  975 + rho_kluge,
701 976 sin_rho,
702   - cos_rho);
  977 + cos_rho,
  978 + m_posangle_roi);
703 979  
704   - // Integrate over phi
  980 + // Setup integrator
705 981 GIntegral integral(&integrand);
706 982 integral.fixed_iter(m_iter);
707   - npred = integral.romberg(omega_min, omega_max, m_iter) * sin_rho;
  983 +
  984 + // If the radius rho is not larger than the semiminor axis
  985 + // boundary, the circle with that radius is fully contained in
  986 + // the ellipse and we can just integrate over the relevant arc
  987 + if (rho <= m_semiminor) {
  988 +
  989 + // Compute omega integration range
  990 + double omega_min = -domega;
  991 + double omega_max = +domega;
  992 +
  993 + // Integrate over omega
  994 + npred = integral.romberg(omega_min, omega_max, m_iter) *
  995 + sin_rho;
  996 +
  997 + } // endif: circle comprised in ellipse
  998 +
  999 + // ... otherwise there are arcs that intersect with the ROI
  1000 + // circle
  1001 + else {
  1002 +
  1003 + // Compute half the arc length (in radians) of a circle of
  1004 + // radius rho, centred on the model, that intersects with
  1005 + // the ellipse boundary
  1006 + double arg1 = 1.0 - (m_semiminor*m_semiminor) / (rho*rho);
  1007 + double arg2 = 1.0 - (m_semiminor*m_semiminor) /
  1008 + (m_semimajor*m_semimajor);
  1009 + double omega_width = std::acos(std::sqrt(arg1/arg2));
  1010 +
  1011 + // Continue only if the arclength is positive
  1012 + if (omega_width > 0.0) {
  1013 +
  1014 + // Compute azimuth angle difference between ellipse
  1015 + // position angle and position angle of ROI in the model
  1016 + // system. This angle will define the reference point for
  1017 + // the circle arcs. Make sure that omega_0 is within [-pi,pi]
  1018 + // thus that the omega intervals are between [-2pi,2pi]
  1019 + double omega_0 = m_posangle - m_posangle_roi;
  1020 + if (omega_0 > gammalib::pi) {
  1021 + omega_0 -= gammalib::pi;
  1022 + }
  1023 + else if (omega_0 < -gammalib::pi) {
  1024 + omega_0 += gammalib::pi;
  1025 + }
  1026 +
  1027 + // Compute azimuth angle intervals
  1028 + double omega1_min = omega_0 - omega_width;
  1029 + double omega1_max = omega_0 + omega_width;
  1030 + double omega2_min = omega1_min + gammalib::pi;
  1031 + double omega2_max = omega1_max + gammalib::pi;
  1032 +
  1033 + // Limit intervals to the intersection of the ellipse with
  1034 + // the Psf circle. This may lead to a split of intervals,
  1035 + // and we gather all these intervals in a special interval
  1036 + // pair containers
  1037 + cta_omega_intervals intervals1 =
  1038 + gammalib::limit_omega(omega1_min, omega1_max, domega);
  1039 + cta_omega_intervals intervals2 =
  1040 + gammalib::limit_omega(omega2_min, omega2_max, domega);
  1041 +
  1042 + // Integrate over all intervals for omega1
  1043 + for (int i = 0; i < intervals1.size(); ++i) {
  1044 + double min = intervals1[i].first;
  1045 + double max = intervals1[i].second;
  1046 + npred += integral.romberg(min, max, m_iter) * sin_rho;
  1047 + }
  1048 +
  1049 + // Integrate over all intervals for omega2
  1050 + for (int i = 0; i < intervals2.size(); ++i) {
  1051 + double min = intervals2[i].first;
  1052 + double max = intervals2[i].second;
  1053 + npred += integral.romberg(min, max, m_iter) * sin_rho;
  1054 + }
  1055 +
  1056 + } // endif: arc length was positive
  1057 +
  1058 + } // endelse: circle was not comprised in ellipse
708 1059  
709 1060 // Debug: Check for NaN
710 1061 #if defined(G_NAN_CHECK)
... ... @@ -713,7 +1064,6 @@ double cta_npred_elliptical_kern_rho::eval(const double&amp; rho)
713 1064 std::cout << "(rho=" << rho << "):";
714 1065 std::cout << " NaN/Inf encountered";
715 1066 std::cout << " (npred=" << npred;
716   - std::cout << ", omega=[" << omega_min << "," << omega_max << "]";
717 1067 std::cout << ", sin_rho=" << sin_rho;
718 1068 std::cout << ", cos_rho=" << cos_rho;
719 1069 std::cout << ")" << std::endl;
... ... @@ -737,34 +1087,68 @@ double cta_npred_elliptical_kern_rho::eval(const double&amp; rho)
737 1087 * Computes
738 1088 *
739 1089 * \f[
740   - * S_{\rm p}(\rho,\omega | E, t) \, N_{\rm pred}(\rho,\omega)
  1090 + * S_{\rm p}(\omega | \rho, E, t) \, N_{\rm pred}(\omega | \rho)
741 1091 * \f]
  1092 + *
  1093 + * where
  1094 + * \f$\omega\f$ is the azimuth angle with respect to the model centre,
  1095 + * counted counterclockwise from the vector connecting the model centre
  1096 + * to the centre of the Region of Interest (ROI), and
  1097 + * \f$\rho\f$ is the radial distance from the model centre.
  1098 + *
  1099 + * @todo Npred computation goes over sky coordinates. This can maybe be
  1100 + * optimized to reduce the number of coordinate transformations.
  1101 + * @todo Check whether the Npred omega argument is the right one.
742 1102 ***************************************************************************/
743 1103 double cta_npred_elliptical_kern_omega::eval(const double& omega)
744 1104 {
745 1105 // Initialise Npred value
746 1106 double npred = 0.0;
747 1107  
748   - // Compute sky direction vector in native coordinates
749   - double cos_omega = std::cos(omega);
750   - double sin_omega = std::sin(omega);
751   - GVector native(-cos_omega*m_sin_rho, sin_omega*m_sin_rho, m_cos_rho);
752   -
753   - // Rotate from native into celestial system
754   - GVector cel = m_rot * native;
  1108 + // Compute azimuth angle in model coordinate system (radians)
  1109 + double omega_model = omega + m_posangle_roi;
755 1110  
756   - // Set sky direction
757   - GSkyDir srcDir;
758   - srcDir.celvector(cel);
  1111 + // Evaluate sky model
  1112 + double model = m_model.eval(m_rho, omega_model, m_srcEng, m_srcTime);
  1113 +
  1114 + // Debug: test if model is non positive
  1115 + #if defined(G_DEBUG_MODEL_ZERO)
  1116 + if (model <= 0.0) {
  1117 + double m_semiminor_rad = m_model.semiminor() * gammalib::deg2rad;
  1118 + double m_semimajor_rad = m_model.semimajor() * gammalib::deg2rad;
  1119 + double diff_angle = omega_model - m_model.posangle() * gammalib::deg2rad;
  1120 + double cosinus = std::cos(diff_angle);
  1121 + double sinus = std::sin(diff_angle);
  1122 + double arg1 = m_semiminor_rad * cosinus;
  1123 + double arg2 = m_semimajor_rad * sinus;
  1124 + double r_ellipse = m_semiminor_rad * m_semimajor_rad /
  1125 + std::sqrt(arg1*arg1 + arg2*arg2);
  1126 + std::cout << "*** WARNING: cta_npred_elliptical_kern_omega::eval";
  1127 + std::cout << " zero model for (rho,omega)=(";
  1128 + std::cout << m_rho*gammalib::rad2deg << ",";
  1129 + std::cout << omega*gammalib::rad2deg << ")";
  1130 + std::cout << " rho-r_ellipse=" << (m_rho-r_ellipse) << " radians";
  1131 + std::cout << std::endl;
  1132 + }
  1133 + #endif
  1134 +
  1135 + // Continue only if model is positive
  1136 + if (model > 0.0) {
  1137 +
  1138 + // Compute sky direction vector in native coordinates
  1139 + double cos_omega = std::cos(omega_model);
  1140 + double sin_omega = std::sin(omega_model);
  1141 + GVector native(-cos_omega*m_sin_rho, sin_omega*m_sin_rho, m_cos_rho);
759 1142  
760   - // Set Photon
761   - GPhoton photon(srcDir, m_srcEng, m_srcTime);
  1143 + // Rotate from native into celestial system
  1144 + GVector cel = m_rot * native;
762 1145  
763   - // Get model value for this photon
764   - double model = m_model.eval(photon);
  1146 + // Set sky direction
  1147 + GSkyDir srcDir;
  1148 + srcDir.celvector(cel);
765 1149  
766   - // Continue only if sky intensity is positive
767   - if (model > 0.0) {
  1150 + // Set Photon
  1151 + GPhoton photon(srcDir, m_srcEng, m_srcTime);
768 1152  
769 1153 // Compute Npred for this sky direction
770 1154 npred = m_rsp.npred(photon, m_obs) * model;
... ... @@ -1130,136 +1514,203 @@ double cta_npred_diffuse_kern_phi::eval(const double&amp; phi)
1130 1514 }
1131 1515  
1132 1516  
  1517 +/*==========================================================================
  1518 + = =
  1519 + = Helper class methods for stacked analysis =
  1520 + = =
  1521 + ==========================================================================*/
  1522 +
1133 1523 /***********************************************************************//**
1134   - * @brief Kernel for PSF integration of spatial model
  1524 + * @brief Kernel for radial model integration over zenith angle
1135 1525 *
1136   - * @param[in] delta PSF offset angle (radians).
1137   - * @return Azimuthally integrated product between PSF and model.
  1526 + * @param[in] rho Radial distance from model centre (radians).
  1527 + * @return Integration kernel.
1138 1528 *
1139   - * Computes the azimuthally integrated product of point spread function and
1140   - * the spatial model intensity. As the PSF is azimuthally symmetric, it is
1141   - * not included in the azimuthally integration, but just multiplied on the
1142   - * azimuthally integrated model. The method returns thus
  1529 + * Computes the integration kernel
1143 1530 *
1144 1531 * \f[
1145   - * {\rm PSF}(\delta) \times
1146   - * \int_0^{2\pi} {\rm M}(\delta, \phi) \sin \delta {\rm d}\phi
  1532 + * K(\rho | E, t) = \sin \rho \times
  1533 + * S_{\rm p}(\rho | E, t) \times
  1534 + * \int_{\omega} PSF(\rho, \omega) d\omega
1147 1535 * \f]
1148 1536 *
1149   - * where \f${\rm M}(\delta, \phi)\f$ is the spatial model in the coordinate
1150   - * system of the point spread function, defined by the angle \f$\delta\f$
1151   - * between the true and the measured photon direction and the azimuth angle
1152   - * \f$\phi\f$ around the measured photon direction.
  1537 + * where
  1538 + * \f$\omega\f$ is the azimuth angle with respect to the model centre,
  1539 + * counted counterclockwise from the vector connecting the model centre
  1540 + * to the observed photon direction, and
  1541 + * \f$\rho\f$ is the radial distance from the model centre.
  1542 + * \f$S_{\rm p}(\rho | E, t)\f$ is the radial source model
  1543 + * for a given true photon energy and photon arrival time,
  1544 + * \f$PSF(\rho, \omega)\f$ is the point spread function.
1153 1545 ***************************************************************************/
1154   -double cta_psf_diffuse_kern_delta::eval(const double& delta)
  1546 +double cta_psf_radial_kern_rho::eval(const double& rho)
1155 1547 {
1156   - // Initialise value
1157   - double value = 0.0;
1158   -
1159   - // If we're at the PSF peak the model is zero (due to the sin(delta)
1160   - // term. We thus only integrate for positive deltas.
1161   - if (delta > 0.0) {
1162   -
1163   - // Get PSF for this delta
1164   - value = m_rsp->psf()(m_srcDir, delta, m_srcEng);
  1548 + // Initialise result
  1549 + double irf = 0.0;
1165 1550  
1166   - // Continue only if PSF is positive
1167   - if (value > 0.0) {
  1551 + // Continue only if rho is positive
  1552 + if (rho > 0.0) {
1168 1553  
1169   - // Compute sine and cosine of delta
1170   - double sin_delta = std::sin(delta);
1171   - double cos_delta = std::cos(delta);
  1554 + // Compute half length of the arc (in radians) from a circle with
  1555 + // radius rho that intersects with the point spread function, defined
  1556 + // as a circle with maximum radius m_delta_max
  1557 + double domega = 0.5 * gammalib::cta_roi_arclength(rho,
  1558 + m_rho_obs,
  1559 + m_cos_rho_obs,
  1560 + m_sin_rho_obs,
  1561 + m_delta_max,
  1562 + m_cos_delta_max);
1172 1563  
1173   - // Setup kernel for azimuthal integration of the diffuse model
1174   - cta_psf_diffuse_kern_phi integrand(m_model, m_srcEng, m_srcTime,
1175   - m_rot,
1176   - sin_delta, cos_delta);
  1564 + // Continue only if arc length is positive
  1565 + if (domega > 0.0) {
1177 1566  
1178   - // Azimuthally integrate model
1179   - GIntegral integral(&integrand);
1180   - integral.eps(m_eps);
1181   - value *= integral.romberg(0.0, gammalib::twopi, m_order) * sin_delta;
  1567 + // Reduce rho by an infinite amount to avoid rounding errors
  1568 + // at the boundary of a sharp edged model
  1569 + double rho_kluge = rho - 1.0e-12;
  1570 + if (rho_kluge < 0.0) {
  1571 + rho_kluge = 0.0;
  1572 + }
1182 1573  
1183   - } // endif: PSF value was positive
  1574 + // Evaluate sky model
  1575 + double model = m_model->eval(rho_kluge, m_srcEng, m_srcTime);
  1576 +
  1577 + // Debug: test if model is non positive
  1578 + #if defined(G_DEBUG_MODEL_ZERO)
  1579 + if (model <= 0.0) {
  1580 + std::cout << "*** WARNING: cta_psf_radial_kern_rho::eval";
  1581 + std::cout << " zero model for (rho)=(";
  1582 + std::cout << rho*gammalib::rad2deg << ")";
  1583 + std::cout << " rho-r_model=" << (rho-m_model->theta_max());
  1584 + std::cout << " radians" << std::endl;
  1585 + }
  1586 + #endif
1184 1587  
1185   - } // endif: delta was positive
  1588 + // Continue only if model is positive
  1589 + if (model > 0.0) {
1186 1590  
1187   - // Debug: Check for NaN
1188   - #if defined(G_NAN_CHECK)
1189   - if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
1190   - std::cout << "*** ERROR: cta_psf_diffuse_kern_delta::eval";
1191   - std::cout << "(delta=" << delta << "):";
1192   - std::cout << " NaN/Inf encountered";
1193   - std::cout << " (value=" << value;
1194   - std::cout << ")" << std::endl;
1195   - }
1196   - #endif
  1591 + // Compute omega integration range
  1592 + double omega_min = -domega;
  1593 + double omega_max = +domega;
1197 1594  
1198   - // Return kernel value
1199   - return value;
1200   -}
  1595 + // Precompute cosine and sine terms for azimuthal integration
  1596 + double cos_rho = std::cos(rho);
  1597 + double sin_rho = std::sin(rho);
  1598 + double cos_psf = cos_rho * m_cos_rho_obs;
  1599 + double sin_psf = sin_rho * m_sin_rho_obs;
1201 1600  
  1601 + // Setup integration kernel
  1602 + cta_psf_radial_kern_omega integrand(m_rsp,
  1603 + m_model,
  1604 + m_srcDir,
  1605 + m_srcEng,
  1606 + m_srcTime,
  1607 + cos_psf,
  1608 + sin_psf);
1202 1609  
1203   -/***********************************************************************//**
1204   - * @brief Kernel for map integration of spatial model
1205   - *
1206   - * @param[in] phi Azimuth angle (radians).
1207   - * @return Spatial model value.
1208   - *
1209   - * Computes the value of the spatial model at the position (delta,phi) given
1210   - * in point spread function coordinates. The transformation from point
1211   - * spread function coordinates into sky coordinates is done using a rotation
1212   - * matrix that is pre-computed on entry.
1213   - ***************************************************************************/
1214   -double cta_psf_diffuse_kern_phi::eval(const double& phi)
1215   -{
1216   - // Compute sky direction vector in native coordinates
1217   - double cos_phi = std::cos(phi);
1218   - double sin_phi = std::sin(phi);
1219   - GVector native(-cos_phi*m_sin_delta, sin_phi*m_sin_delta, m_cos_delta);
  1610 + // Setup integrator
  1611 + GIntegral integral(&integrand);
  1612 + integral.fixed_iter(m_iter);
1220 1613  
1221   - // Rotate from native into celestial system
1222   - GVector cel = m_rot * native;
  1614 + // Integrate over omega
  1615 + irf = integral.romberg(omega_min, omega_max, m_iter) *
  1616 + model * sin_rho;
1223 1617  
1224   - // Set sky direction
1225   - GSkyDir srcDir;
1226   - srcDir.celvector(cel);
  1618 + // Compile option: Check for NaN/Inf
  1619 + #if defined(G_NAN_CHECK)
  1620 + if (gammalib::is_notanumber(irf) || gammalib::is_infinite(irf)) {
  1621 + std::cout << "*** ERROR: cta_psf_radial_kern_rho";
  1622 + std::cout << "(rho=" << rho << "):";
  1623 + std::cout << " NaN/Inf encountered";
  1624 + std::cout << " (irf=" << irf;
  1625 + std::cout << ", domega=" << domega;
  1626 + std::cout << ", model=" << model;
  1627 + std::cout << ", sin_rho=" << sin_rho << ")";
  1628 + std::cout << std::endl;
  1629 + }
  1630 + #endif
1227 1631  
1228   - // Set photon
1229   - GPhoton photon(srcDir, m_srcEng, m_srcTime);
  1632 + } // endif: model was positive
1230 1633  
1231   - // Compute map value this sky direction
1232   - double value = m_model->eval(photon);
  1634 + } // endif: arclength was positive
1233 1635  
1234   - // Debug: Check for NaN
1235   - #if defined(G_NAN_CHECK)
1236   - if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
1237   - std::cout << "*** ERROR: cta_psf_diffuse_kern_phi::eval";
1238   - std::cout << "(phi=" << phi << "):";
1239   - std::cout << " NaN/Inf encountered";
1240   - std::cout << " (value=" << value;
1241   - std::cout << ")" << std::endl;
1242   - }
1243   - #endif
  1636 + } // endif: rho was positive
1244 1637  
1245   - // Return kernel value
1246   - return value;
  1638 + // Return result
  1639 + return irf;
1247 1640 }
1248 1641  
1249 1642  
1250 1643 /***********************************************************************//**
1251   - * @brief Kernel for PSF integration of radial model
  1644 + * @brief Kernel for radial model integration over azimuth angle
1252 1645 *
1253   - * @param[in] delta PSF offset angle (radians).
1254   - * @return Azimuthally integrated product between PSF and radial model.
  1646 + * @param[in] omega Azimuth angle (radians).
1255 1647 *
1256   - * Computes the azimuthally integrated product of point spread function and
1257   - * the radial model intensity. As the PSF is azimuthally symmetric, it is
1258   - * not included in the azimuthally integration, but just multiplied on the
1259   - * azimuthally integrated model. The method returns thus
  1648 + * Computes
1260 1649 *
1261 1650 * \f[
1262   - * {\rm PSF}(\delta) \times
  1651 + * K(\omega | \rho, E, t) = PSF(\omega | \rho)
  1652 + * \f]
  1653 + *
  1654 + * where
  1655 + * \f$\omega\f$ is the azimuth angle with respect to the model centre,
  1656 + * counted counterclockwise from the vector connecting the model centre
  1657 + * to the observed photon direction, and
  1658 + * \f$\rho\f$ is the radial distance from the model centre.
  1659 + *
  1660 + * From the coordinates \f$(\rho,\omega)\f$ in the model system, the method
  1661 + * computes the angle between the true (\f$\vec{p}\f$) and observed
  1662 + * (\f$\vec{p'}\f$) photon arrival direction using
  1663 + *
  1664 + * \f[\delta = \arccos(\cos \rho \cos \rho_{\rm obs} +
  1665 + * \sin \rho \sin \rho_{\rm obs} \cos \omega)\f]
  1666 + *
  1667 + * where
  1668 + * \f$\rho_{\rm obs}\f$ is the angular distance between the observed photon
  1669 + * arrival direction \f$\vec{p'}\f$ and the model centre \f$\vec{m}\f$.
  1670 + * \f$\delta\f$ is used to compute the value of the point spread function.
  1671 + ***************************************************************************/
  1672 +double cta_psf_radial_kern_omega::eval(const double& omega)
  1673 +{
  1674 + // Initialise Irf value
  1675 + double irf = 0.0;
  1676 +
  1677 + // Compute Psf offset angle (radians)
  1678 + double delta = std::acos(m_cos_psf + m_sin_psf * std::cos(omega));
  1679 +
  1680 + // Evaluate Psf * model for this delta
  1681 + irf = m_rsp->psf()(m_srcDir, delta, m_srcEng);
  1682 +
  1683 + // Compile option: Check for NaN/Inf
  1684 + #if defined(G_NAN_CHECK)
  1685 + if (gammalib::is_notanumber(irf) || gammalib::is_infinite(irf)) {
  1686 + std::cout << "*** ERROR: cta_psf_radial_kern_omega::eval";
  1687 + std::cout << "(omega=" << omega << "):";
  1688 + std::cout << " NaN/Inf encountered";
  1689 + std::cout << " (irf=" << irf;
  1690 + std::cout << ", delta=" << delta;
  1691 + std::cout << ", omega=" << omega << ")";
  1692 + std::cout << std::endl;
  1693 + }
  1694 + #endif
  1695 +
  1696 + // Return
  1697 + return irf;
  1698 +}
  1699 +
  1700 +
  1701 +/***********************************************************************//**
  1702 + * @brief Kernel for PSF integration of radial model
  1703 + *
  1704 + * @param[in] delta PSF offset angle (radians).
  1705 + * @return Azimuthally integrated product between PSF and radial model.
  1706 + *
  1707 + * Computes the azimuthally integrated product of point spread function and
  1708 + * the radial model intensity. As the PSF is azimuthally symmetric, it is
  1709 + * not included in the azimuthally integration, but just multiplied on the
  1710 + * azimuthally integrated model. The method returns thus
  1711 + *
  1712 + * \f[
  1713 + * {\rm PSF}(\delta) \times
1263 1714 * \int_0^{2\pi} {\rm M}(\delta, \phi) \sin \delta {\rm d}\phi
1264 1715 * \f]
1265 1716 *
... ... @@ -1273,44 +1724,70 @@ double cta_psf_radial_kern_delta::eval(const double&amp; delta)
1273 1724 // Initialise value
1274 1725 double value = 0.0;
1275 1726  
1276   - // If we're at the PSF peak the model is zero (due to the sin(delta)
  1727 + // If we're at the Psf peak the model is zero (due to the sin(delta)
1277 1728 // term. We thus only integrate for positive deltas.
1278 1729 if (delta > 0.0) {
1279 1730  
1280   - // Get PSF for this delta
1281   - value = m_rsp->psf()(m_srcDir, delta, m_srcEng);
  1731 + // Get Psf for this delta
  1732 + double psf = m_rsp->psf()(m_srcDir, delta, m_srcEng);
1282 1733  
1283   - // Continue only if PSF is positive
1284   - if (value > 0.0) {
  1734 + // Continue only if Psf is positive
  1735 + if (psf > 0.0) {
1285 1736  
1286   - // Compute sin(delta), sin(delta)*sin(zeta), cos(delta)*cos(zeta)
1287   - double sin_delta = std::sin(delta);
1288   - double sin_fact = sin_delta * m_sin_zeta;
1289   - double cos_fact = std::cos(delta)* m_cos_zeta;
  1737 + // Compute half length of the arc (in radians) from a circle with
  1738 + // radius delta that intersects with the model, defined as a circle
  1739 + // with maximum radius m_theta_max
  1740 + double dphi = 0.5 * gammalib::cta_roi_arclength(delta,
  1741 + m_delta_mod,
  1742 + m_cos_delta_mod,
  1743 + m_sin_delta_mod,
  1744 + m_theta_max,
  1745 + m_cos_theta_max);
1290 1746  
1291   - // Setup kernel for azimuthal integration of the spatial model
1292   - cta_psf_radial_kern_phi integrand(m_model, m_srcEng, m_srcTime,
1293   - sin_fact, cos_fact);
  1747 + // Continue only if arc length is positive
  1748 + if (dphi > 0.0) {
1294 1749  
1295   - // Azimuthally integrate model
1296   - GIntegral integral(&integrand);
1297   - integral.eps(m_eps);
1298   - value *= integral.romberg(0.0, gammalib::twopi, m_order) * sin_delta;
  1750 + // Compute phi integration range
  1751 + double phi_min = -dphi;
  1752 + double phi_max = +dphi;
1299 1753  
1300   - } // endif: PSF value was positive
  1754 + // Precompute cosine and sine terms for azimuthal integration
  1755 + double sin_delta = std::sin(delta);
  1756 + double cos_delta = std::cos(delta);
  1757 + double sin_fact = sin_delta * m_sin_delta_mod;
  1758 + double cos_fact = cos_delta * m_cos_delta_mod;
1301 1759  
1302   - } // endif: delta was positive
  1760 + // Setup kernel for azimuthal integration of the spatial model
  1761 + cta_psf_radial_kern_phi integrand(m_model,
  1762 + m_srcEng,
  1763 + m_srcTime,
  1764 + sin_fact,
  1765 + cos_fact);
1303 1766  
1304   - // Debug: Check for NaN
1305   - #if defined(G_NAN_CHECK)
1306   - if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
1307   - std::cout << "*** ERROR: cta_psf_radial_kern_delta::eval";
1308   - std::cout << "(delta=" << delta << "):";
1309   - std::cout << " NaN/Inf encountered";
1310   - std::cout << " (value=" << value;
1311   - std::cout << ")" << std::endl;
1312   - }
1313   - #endif
  1767 + // Setup integrator
  1768 + GIntegral integral(&integrand);
  1769 + integral.fixed_iter(m_iter);
  1770 +
  1771 + // Integrate over azimuth
  1772 + value = integral.romberg(phi_min, phi_max, m_iter) *
  1773 + psf * sin_delta;
  1774 +
  1775 + // Debug: Check for NaN
  1776 + #if defined(G_NAN_CHECK)
  1777 + if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
  1778 + std::cout << "*** ERROR: cta_psf_radial_kern_delta::eval";
  1779 + std::cout << "(delta=" << delta << "):";
  1780 + std::cout << " NaN/Inf encountered";
  1781 + std::cout << " (value=" << value;
  1782 + std::cout << ")" << std::endl;
  1783 + }
  1784 + #endif
  1785 +
  1786 + } // endif: Psf value was positive
  1787 +
  1788 + } // endif: arc length was positive
  1789 +
  1790 + } // endif: delta was positive
1314 1791  
1315 1792 // Return kernel value
1316 1793 return value;
... ... @@ -1343,8 +1820,26 @@ double cta_psf_radial_kern_phi::eval(const double&amp; phi)
1343 1820 // Compute radial model theta angle
1344 1821 double theta = std::acos(m_cos_fact + m_sin_fact * std::cos(phi));
1345 1822  
  1823 + // Reduce theta by an infinite amount to avoid rounding errors at the
  1824 + // boundary of a sharp edged model
  1825 + double theta_kluge = theta - 1.0e-12;
  1826 + if (theta_kluge < 0.0) {
  1827 + theta_kluge = 0.0;
  1828 + }
  1829 +
1346 1830 // Get radial model value
1347   - double value = m_model->eval(theta, m_srcEng, m_srcTime);
  1831 + double value = m_model->eval(theta_kluge, m_srcEng, m_srcTime);
  1832 +
  1833 + // Debug: test if model is non positive
  1834 + #if defined(G_DEBUG_MODEL_ZERO)
  1835 + if (value <= 0.0) {
  1836 + std::cout << "*** WARNING: cta_psf_radial_kern_phi::eval";
  1837 + std::cout << " zero model for (phi)=(";
  1838 + std::cout << phi*gammalib::rad2deg << ")";
  1839 + std::cout << " theta-r_model=" << (theta-m_model->theta_max());
  1840 + std::cout << " radians" << std::endl;
  1841 + }
  1842 + #endif
1348 1843  
1349 1844 // Debug: Check for NaN
1350 1845 #if defined(G_NAN_CHECK)
... ... @@ -1363,238 +1858,330 @@ double cta_psf_radial_kern_phi::eval(const double&amp; phi)
1363 1858  
1364 1859  
1365 1860 /***********************************************************************//**
1366   - * @brief Kernel for PSF integration of elliptical model
  1861 + * @brief Kernel for elliptical model integration over zenith angle
1367 1862 *
1368   - * @param[in] delta PSF offset angle (radians).
1369   - * @return Azimuthally integrated product between PSF and elliptical model.
  1863 + * @param[in] rho Radial distance from model centre (radians).
  1864 + * @return Integration kernel.
1370 1865 *
1371   - * Computes the azimuthally integrated product of point spread function and
1372   - * the elliptical model intensity. As the PSF is azimuthally symmetric, it is
1373   - * not included in the azimuthally integration, but just multiplied on the
1374   - * azimuthally integrated model. The method returns thus
  1866 + * Computes the integration kernel
1375 1867 *
1376 1868 * \f[
1377   - * {\rm PSF}(\delta) \times
1378   - * \int_0^{2\pi} {\rm M}(\delta, \phi) \sin \delta {\rm d}\phi
  1869 + * K(\rho | E, t) = \sin \rho \times
  1870 + * \int_{\omega}
  1871 + * S_{\rm p}(\rho, \omega | E, t) \, PSF(\rho, \omega)
  1872 + * d\omega
1379 1873 * \f]
1380 1874 *
1381   - * where \f${\rm M}(\delta, \phi)\f$ is the elliptical model in the coordinate
1382   - * system of the point spread function, defined by the angle \f$\delta\f$
1383   - * between the true and the measured photon direction and the azimuth angle
1384   - * \f$\phi\f$ around the measured photon direction.
  1875 + * where
  1876 + * \f$\omega\f$ is the azimuth angle with respect to the model centre,
  1877 + * counted counterclockwise from the vector connecting the model centre
  1878 + * to the observed photon direction, and
  1879 + * \f$\rho\f$ is the radial distance from the model centre.
  1880 + * \f$S_{\rm p}(\rho, \omega | E, t)\f$ is the elliptical source model
  1881 + * for a given true photon energy and photon arrival time,
  1882 + * \f$PSF(\rho, \omega)\f$ is the point spread function.
  1883 + * The integration is over the \f$\omega\f$ values that are comprised within
  1884 + * the ellipse boundaries.
1385 1885 ***************************************************************************/
1386   -double cta_psf_elliptical_kern_delta::eval(const double& delta)
  1886 +double cta_psf_elliptical_kern_rho::eval(const double& rho)
1387 1887 {
1388   - // Initialise value
1389   - double value = 0.0;
  1888 + // Initialise result
  1889 + double irf = 0.0;
1390 1890  
1391   - // If we're at the PSF peak the model is zero (due to the sin(delta)
1392   - // term. We thus only integrate for positive deltas.
1393   - if (delta > 0.0) {
  1891 + // Continue only if rho is positive
  1892 + if (rho > 0.0) {
1394 1893  
1395   - // Get PSF for this delta
1396   - value = m_rsp->psf()(m_srcDir, delta, m_srcEng);
  1894 + // Compute half length of the arc (in radians) from a circle with
  1895 + // radius rho that intersects with the point spread function, defined
  1896 + // as a circle with maximum radius m_delta_max
  1897 + double domega = 0.5 * gammalib::cta_roi_arclength(rho,
  1898 + m_rho_obs,
  1899 + m_cos_rho_obs,
  1900 + m_sin_rho_obs,
  1901 + m_delta_max,
  1902 + m_cos_delta_max);
1397 1903  
1398   - // Continue only if PSF is positive
1399   - if (value > 0.0) {
  1904 + // Continue only if arc length is positive
  1905 + if (domega > 0.0) {
1400 1906  
1401   - // Compute sin(delta), sin(delta)*sin(zeta), cos(delta)*cos(zeta)
1402   - double sin_delta = std::sin(delta);
1403   - double sin_fact = sin_delta * m_sin_zeta;
1404   - double cos_fact = std::cos(delta)* m_cos_zeta;
  1907 + // Precompute cosine and sine terms for azimuthal integration
  1908 + double cos_rho = std::cos(rho);
  1909 + double sin_rho = std::sin(rho);
  1910 + double cos_psf = cos_rho * m_cos_rho_obs;
  1911 + double sin_psf = sin_rho * m_sin_rho_obs;
  1912 +
  1913 + // Reduce rho by an infinite amount to avoid rounding errors
  1914 + // at the boundary of a sharp edged model (e.g. an elliptical
  1915 + // disk model)
  1916 + double rho_kluge = rho - 1.0e-12;
  1917 + if (rho_kluge < 0.0) {
  1918 + rho_kluge = 0.0;
  1919 + }
1405 1920  
1406   - // Setup kernel for azimuthal integration of the elliptical model
1407   - cta_psf_elliptical_kern_phi integrand(m_model, m_srcEng, m_srcTime,
1408   - m_omega,
1409   - sin_delta, sin_fact, cos_fact);
  1921 + // Setup integration kernel
  1922 + cta_psf_elliptical_kern_omega integrand(m_rsp,
  1923 + m_model,
  1924 + m_srcDir,
  1925 + m_srcEng,
  1926 + m_srcTime,
  1927 + m_posangle_obs,
  1928 + rho_kluge,
  1929 + cos_psf,
  1930 + sin_psf);
1410 1931  
1411   - // Azimuthally integrate model
  1932 + // Setup integrator
1412 1933 GIntegral integral(&integrand);
1413   - integral.eps(m_eps);
1414   - value *= integral.romberg(0.0, gammalib::twopi, m_order) * sin_delta;
  1934 + integral.fixed_iter(m_iter);
1415 1935  
1416   - } // endif: PSF value was positive
  1936 + // If the radius rho is not larger than the semiminor axis
  1937 + // boundary, the circle with that radius is fully contained in
  1938 + // the ellipse and we can just integrate over the relevant arc
  1939 + if (rho <= m_semiminor) {
1417 1940  
1418   - } // endif: delta was positive
  1941 + // Compute omega integration range
  1942 + double omega_min = -domega;
  1943 + double omega_max = +domega;
1419 1944  
1420   - // Debug: Check for NaN
1421   - #if defined(G_NAN_CHECK)
1422   - if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
1423   - std::cout << "*** ERROR: cta_psf_elliptical_kern_delta::eval";
1424   - std::cout << "(delta=" << delta << "):";
1425   - std::cout << " NaN/Inf encountered";
1426   - std::cout << " (value=" << value;
1427   - std::cout << ")" << std::endl;
1428   - }
1429   - #endif
  1945 + // Integrate over omega
  1946 + irf = integral.romberg(omega_min, omega_max, m_iter) *
  1947 + sin_rho;
  1948 +
  1949 + } // endif: circle comprised in ellipse
  1950 +
  1951 + // ... otherwise there are arcs that intersect with the Psf circle
  1952 + else {
  1953 +
  1954 + // Compute half the arc length (in radians) of a circle of
  1955 + // radius rho, centred on the model, that intersects with
  1956 + // the ellipse boundary
  1957 + double arg1 = 1.0 - (m_semiminor*m_semiminor) / (rho*rho);
  1958 + double arg2 = 1.0 - (m_semiminor*m_semiminor) /
  1959 + (m_semimajor*m_semimajor);
  1960 + double omega_width = std::acos(std::sqrt(arg1/arg2));
  1961 +
  1962 + // Continue only if the arclength is positive
  1963 + if (omega_width > 0.0) {
  1964 +
  1965 + // Compute azimuth angle difference between ellipse
  1966 + // position angle and position angle of observed
  1967 + // photon in the model system. This angle will define
  1968 + // the reference point around which the circle arc. Make
  1969 + // sure that omega_0 is within [-pi,pi] thus that the omega
  1970 + // intervals are between [-2pi,2pi]
  1971 + double omega_0 = m_posangle - m_posangle_obs;
  1972 + if (omega_0 > gammalib::pi) {
  1973 + omega_0 -= gammalib::pi;
  1974 + }
  1975 + else if (omega_0 < -gammalib::pi) {
  1976 + omega_0 += gammalib::pi;
  1977 + }
  1978 +
  1979 + // Compute azimuth angle intervals
  1980 + double omega1_min = omega_0 - omega_width;
  1981 + double omega1_max = omega_0 + omega_width;
  1982 + double omega2_min = omega1_min + gammalib::pi;
  1983 + double omega2_max = omega1_max + gammalib::pi;
  1984 +
  1985 + // Limit intervals to the intersection of the ellipse with
  1986 + // the Psf circle. This may lead to a split of intervals,
  1987 + // and we gather all these intervals in a special interval
  1988 + // pair containers
  1989 + cta_omega_intervals intervals1 =
  1990 + gammalib::limit_omega(omega1_min, omega1_max, domega);
  1991 + cta_omega_intervals intervals2 =
  1992 + gammalib::limit_omega(omega2_min, omega2_max, domega);
  1993 +
  1994 + // Integrate over all intervals for omega1
  1995 + for (int i = 0; i < intervals1.size(); ++i) {
  1996 + double min = intervals1[i].first;
  1997 + double max = intervals1[i].second;
  1998 + irf += integral.romberg(min, max, m_iter) * sin_rho;
  1999 + }
  2000 +
  2001 + // Integrate over all intervals for omega2
  2002 + for (int i = 0; i < intervals2.size(); ++i) {
  2003 + double min = intervals2[i].first;
  2004 + double max = intervals2[i].second;
  2005 + irf += integral.romberg(min, max, m_iter) * sin_rho;
  2006 + }
  2007 +
  2008 + } // endif: arc length was positive
  2009 +
  2010 + } // endelse: circle was not comprised in ellipse
1430 2011  
1431   - // Return kernel value
1432   - return value;
  2012 + // Compile option: Check for NaN/Inf
  2013 + #if defined(G_NAN_CHECK)
  2014 + if (gammalib::is_notanumber(irf) || gammalib::is_infinite(irf)) {
  2015 + std::cout << "*** ERROR: cta_psf_elliptical_kern_rho";
  2016 + std::cout << "(rho=" << rho << "):";
  2017 + std::cout << " NaN/Inf encountered";
  2018 + std::cout << " (irf=" << irf;
  2019 + std::cout << ", domega=" << domega;
  2020 + std::cout << ", sin_rho=" << sin_rho << ")";
  2021 + std::cout << std::endl;
  2022 + }
  2023 + #endif
  2024 +
  2025 + } // endif: arc length was positive
  2026 +
  2027 + } // endif: rho was positive
  2028 +
  2029 + // Return result
  2030 + return irf;
1433 2031 }
1434 2032  
1435 2033  
1436 2034 /***********************************************************************//**
1437   - * @brief Kernel for azimuthal elliptical model integration
  2035 + * @brief Kernel for elliptical model integration over azimuth angle
1438 2036 *
1439   - * @param[in] phi Azimuth angle (radians).
1440   - * @return Radial model value.
  2037 + * @param[in] omega Azimuth angle (radians).
1441 2038 *
1442   - * Computes the value of the elliptical model at the position \f$(\delta,\phi)\f$
1443   - * given in point spread function coordinates. The \f$\theta\f$ angle of the
1444   - * elliptical model is computed using
  2039 + * Computes
1445 2040 *
1446 2041 * \f[
1447   - * \theta = \arccos \left( \cos \delta \cos \zeta +
1448   - * \sin \delta \sin \zeta \cos \phi \right)
  2042 + * K(\omega | \rho, E, t) = S_{\rm p}(\omega | \rho, E, t) \,
  2043 + * PSF(\omega | \rho)
1449 2044 * \f]
1450 2045 *
1451   - * where \f$\delta\f$ is the angle between true and measured photon
1452   - * direction, \f$\zeta\f$ is the angle between model centre and measured
1453   - * photon direction, and \f$\phi\f$ is the azimuth angle with respect to the
1454   - * measured photon direction, where \f$\phi=0\f$ corresponds to the
1455   - * connecting line between model centre and measured photon direction.
1456   - *
1457   - * The position angle of the elliptical model is computed using
  2046 + * where
  2047 + * \f$\omega\f$ is the azimuth angle with respect to the model centre,
  2048 + * counted counterclockwise from the vector connecting the model centre
  2049 + * to the observed photon direction, and
  2050 + * \f$\rho\f$ is the radial distance from the model centre.
  2051 + *
  2052 + * From the coordinates \f$(\rho,\omega)\f$ in the model system, the method
  2053 + * computes the angle between the true (\f$\vec{p}\f$) and observed
  2054 + * (\f$\vec{p'}\f$) photon arrival direction using
  2055 + *
  2056 + * \f[\delta = \arccos(\cos \rho \cos \rho_{\rm obs} +
  2057 + * \sin \rho \sin \rho_{\rm obs} \cos \omega)\f]
1458 2058 *
1459   - * \f[
1460   - * {\rm posangle} = \arcsin \left( \frac{\sin \phi \sin \delta}
1461   - * {\sin \theta} \right)
1462   - * \f]
  2059 + * where
  2060 + * \f$\rho_{\rm obs}\f$ is the angular distance between the observed photon
  2061 + * arrival direction \f$\vec{p'}\f$ and the model centre \f$\vec{m}\f$.
  2062 + * \f$\delta\f$ is used to compute the value of the point spread function.
1463 2063 ***************************************************************************/
1464   -double cta_psf_elliptical_kern_phi::eval(const double& phi)
  2064 +double cta_psf_elliptical_kern_omega::eval(const double& omega)
1465 2065 {
1466   - // Compute radial model theta angle
1467   - double theta = std::acos(m_cos_fact + m_sin_fact * std::cos(phi));
1468   -
1469   - // Compute radial model position angle
1470   - double posangle = std::asin((std::sin(phi) * m_sin_delta)/std::sin(theta)) +
1471   - m_omega;
  2066 + // Initialise Irf value
  2067 + double irf = 0.0;
1472 2068  
1473   - // Get radial model value
1474   - double value = m_model->eval(theta, posangle, m_srcEng, m_srcTime);
  2069 + // Compute azimuth angle in model coordinate system (radians)
  2070 + double omega_model = omega + m_posangle_obs;
1475 2071  
1476   - // Debug: Check for NaN
1477   - #if defined(G_NAN_CHECK)
1478   - if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
1479   - std::cout << "*** ERROR: cta_psf_elliptical_kern_phi::eval";
1480   - std::cout << "(phi=" << phi << "):";
1481   - std::cout << " NaN/Inf encountered";
1482   - std::cout << " (value=" << value;
1483   - std::cout << ")" << std::endl;
  2072 + // Evaluate sky model
  2073 + double model = m_model->eval(m_rho, omega_model, m_srcEng, m_srcTime);
  2074 +
  2075 + // Debug: test if model is non positive
  2076 + #if defined(G_DEBUG_MODEL_ZERO)
  2077 + if (model <= 0.0) {
  2078 + double semiminor_rad = m_model->semiminor() * gammalib::deg2rad;
  2079 + double semimajor_rad = m_model->semimajor() * gammalib::deg2rad;
  2080 + double diff_angle = omega_model - m_model->posangle() * gammalib::deg2rad;
  2081 + double cosinus = std::cos(diff_angle);
  2082 + double sinus = std::sin(diff_angle);
  2083 + double arg1 = semiminor_rad * cosinus;
  2084 + double arg2 = semimajor_rad * sinus;
  2085 + double r_ellipse = semiminor_rad * semimajor_rad /
  2086 + std::sqrt(arg1*arg1 + arg2*arg2);
  2087 + std::cout << "*** WARNING: cta_psf_elliptical_kern_omega::eval";
  2088 + std::cout << " zero model for (rho,omega)=";
  2089 + std::cout << m_rho*gammalib::rad2deg << ",";
  2090 + std::cout << omega*gammalib::rad2deg << ")";
  2091 + std::cout << " rho-r_ellipse=" << (m_rho-r_ellipse) << " radians";
  2092 + std::cout << std::endl;
1484 2093 }
1485 2094 #endif
1486 2095  
1487   - // Return kernel value
1488   - return value;
1489   -}
  2096 + // Continue only if model is positive
  2097 + if (model > 0.0) {
1490 2098  
  2099 + // Compute Psf offset angle (radians)
  2100 + double delta = std::acos(m_cos_psf + m_sin_psf * std::cos(omega));
1491 2101  
1492   -/*==========================================================================
1493   - = =
1494   - = Helper class methods for response computation testing =
1495   - = =
1496   - = WARNING: These helper classes are for testing purposes only and not for =
1497   - = production. These classes may be removed at any moment without =
1498   - = any warning. =
1499   - ==========================================================================*/
  2102 + // Evaluate Psf * model for this delta
  2103 + irf = m_rsp->psf()(m_srcDir, delta, m_srcEng) * model;
  2104 +
  2105 + // Compile option: Check for NaN/Inf
  2106 + #if defined(G_NAN_CHECK)
  2107 + if (gammalib::is_notanumber(irf) || gammalib::is_infinite(irf)) {
  2108 + std::cout << "*** ERROR: cta_psf_elliptical_kern_omega::eval";
  2109 + std::cout << "(omega=" << omega << "):";
  2110 + std::cout << " NaN/Inf encountered";
  2111 + std::cout << " (irf=" << irf;
  2112 + std::cout << ", model=" << model;
  2113 + std::cout << ", delta=" << delta;
  2114 + std::cout << ", rho=" << m_rho;
  2115 + std::cout << ", omega=" << omega << ")";
  2116 + std::cout << std::endl;
  2117 + }
  2118 + #endif
  2119 +
  2120 + } // endif: model is positive
  2121 +
  2122 + // Return
  2123 + return irf;
  2124 +}
1500 2125  
1501 2126  
1502 2127 /***********************************************************************//**
1503   - * @brief Kernel for integration of radial model in Psf system
  2128 + * @brief Kernel for PSF integration of spatial model
1504 2129 *
1505   - * @param[in] delta Psf offset angle (radians).
1506   - * @return Azimuthally integrated product between Psf and radial model.
  2130 + * @param[in] delta PSF offset angle (radians).
  2131 + * @return Azimuthally integrated product between PSF and model.
1507 2132 *
1508 2133 * Computes the azimuthally integrated product of point spread function and
1509   - * the radial model intensity. As the Psf is azimuthally symmetric, it is
  2134 + * the spatial model intensity. As the PSF is azimuthally symmetric, it is
1510 2135 * not included in the azimuthally integration, but just multiplied on the
1511 2136 * azimuthally integrated model. The method returns thus
1512 2137 *
1513 2138 * \f[
1514   - * {\rm Psf}(\delta) \times
  2139 + * {\rm PSF}(\delta) \times
1515 2140 * \int_0^{2\pi} {\rm M}(\delta, \phi) \sin \delta {\rm d}\phi
1516 2141 * \f]
1517 2142 *
1518   - * where \f${\rm M}(\delta, \phi)\f$ is the radial model in the coordinate
  2143 + * where \f${\rm M}(\delta, \phi)\f$ is the spatial model in the coordinate
1519 2144 * system of the point spread function, defined by the angle \f$\delta\f$
1520 2145 * between the true and the measured photon direction and the azimuth angle
1521 2146 * \f$\phi\f$ around the measured photon direction.
1522 2147 ***************************************************************************/
1523   -double cta_irf_radial_kern_delta::eval(const double& delta)
  2148 +double cta_psf_diffuse_kern_delta::eval(const double& delta)
1524 2149 {
1525 2150 // Initialise value
1526 2151 double value = 0.0;
1527 2152  
1528   - // If we're at the Psf peak the model is zero (due to the sin(delta)
  2153 + // If we're at the PSF peak the model is zero (due to the sin(delta)
1529 2154 // term. We thus only integrate for positive deltas.
1530 2155 if (delta > 0.0) {
1531 2156  
1532   - // Get Psf for this delta. We use here the event offset instead of
1533   - // the true photon offset because the Psf varies little over the
1534   - // dimension of the Psf, hence it should basically be invariant
1535   - // over such small scales
1536   - value = m_rsp->psf(delta, m_obsOffset, 0.0, m_pnt.zenith(), m_pnt.azimuth(), m_srcLogEng);
1537   -
1538   - // Compile option: Get Aeff now using the event offset instead of
1539   - // computing Aeff later in the azimuthal integration kernel where
1540   - // the true photon offset is available. This is an approximation and
1541   - // assumes that Aeff varies little over the dimension of the Psf.
1542   - // This option is only for testing purposes and should not be used
1543   - // for production.
1544   - #if defined(G_USE_OBSDIR_FOR_AEFF)
1545   - value *= m_rsp->aeff(m_obsOffset, 0.0, m_pnt.zenith(), m_pnt.azimuth(), m_srcLogEng);
1546   - #endif
  2157 + // Get PSF for this delta
  2158 + value = m_rsp->psf()(m_srcDir, delta, m_srcEng);
1547 2159  
1548   - // Continue only if Psf is positive
  2160 + // Continue only if PSF is positive
1549 2161 if (value > 0.0) {
1550 2162  
1551   - // Compute half length of arc that lies within model (radians)
1552   - double dphi = 0.5 * gammalib::cta_roi_arclength(delta,
1553   - m_zeta,
1554   - m_cos_zeta,
1555   - m_sin_zeta,
1556   - m_theta_max,
1557   - m_cos_theta_max);
1558   -
1559   - // Continue only if arc length is positive
1560   - if (dphi > 0.0) {
1561   -
1562   - // Compute azimuth integration range [phi_min,phi_max].
1563   - double phi_min = -dphi;
1564   - double phi_max = +dphi;
1565   -
1566   - // Pre-compute terms for angular distance computation in
1567   - // azimuth integration kernel.
1568   - double sin_delta = std::sin(delta);
1569   - double cos_delta = std::cos(delta);
1570   - double sin_fact_model = sin_delta * m_sin_zeta;
1571   - double cos_fact_model = cos_delta * m_cos_zeta;
1572   - double sin_fact_inst = sin_delta * m_sin_obs_offset;
1573   - double cos_fact_inst = cos_delta * m_cos_obs_offset;
  2163 + // Compute sine and cosine of delta
  2164 + double sin_delta = std::sin(delta);
  2165 + double cos_delta = std::cos(delta);
1574 2166  
1575   - // Setup kernel for azimuthal integration of the spatial model
1576   - cta_irf_radial_kern_phi integrand(m_rsp, m_model, m_pnt,
1577   - m_srcEng, m_srcLogEng, m_srcTime,
1578   - m_obsEng,
1579   - m_phi0,
1580   - sin_fact_model, cos_fact_model,
1581   - sin_fact_inst, cos_fact_inst);
1582   -
1583   - // Integrate kernel
1584   - GIntegral integral(&integrand);
1585   - integral.fixed_iter(m_iter);
1586   - value *= integral.romberg(phi_min, phi_max, m_iter) * sin_delta;
  2167 + // Setup kernel for azimuthal integration of the diffuse model
  2168 + cta_psf_diffuse_kern_phi integrand(m_model, m_srcEng, m_srcTime,
  2169 + m_rot,
  2170 + sin_delta, cos_delta);
1587 2171  
1588   - } // endif: arc length was positive
  2172 + // Azimuthally integrate model
  2173 + GIntegral integral(&integrand);
  2174 + integral.eps(m_eps);
  2175 + value *= integral.romberg(0.0, gammalib::twopi, m_order) * sin_delta;
1589 2176  
1590   - } // endif: Psf value was positive
  2177 + } // endif: PSF value was positive
1591 2178  
1592 2179 } // endif: delta was positive
1593 2180  
1594 2181 // Debug: Check for NaN
1595 2182 #if defined(G_NAN_CHECK)
1596 2183 if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
1597   - std::cout << "*** ERROR: cta_irf_radial_kern_delta::eval";
  2184 + std::cout << "*** ERROR: cta_psf_diffuse_kern_delta::eval";
1598 2185 std::cout << "(delta=" << delta << "):";
1599 2186 std::cout << " NaN/Inf encountered";
1600 2187 std::cout << " (value=" << value;
... ... @@ -1608,79 +2195,40 @@ double cta_irf_radial_kern_delta::eval(const double&amp; delta)
1608 2195  
1609 2196  
1610 2197 /***********************************************************************//**
1611   - * @brief Kernel for azimuthal radial model integration
  2198 + * @brief Kernel for map integration of spatial model
1612 2199 *
1613 2200 * @param[in] phi Azimuth angle (radians).
1614   - * @return Radial model value.
1615   - *
1616   - * Computes the value of the radial model at the position \f$(\delta,\phi)\f$
1617   - * given in point spread function coordinates. The \f$\theta\f$ angle of the
1618   - * radial model is computed using
1619   - *
1620   - * \f[
1621   - * \theta = \arccos \left( \cos \delta \cos \zeta +
1622   - * \sin \delta \sin \zeta \cos \phi \right)
1623   - * \f]
  2201 + * @return Spatial model value.
1624 2202 *
1625   - * where \f$\delta\f$ is the angle between true and measured photon
1626   - * direction, \f$\zeta\f$ is the angle between model centre and measured
1627   - * photon direction, and \f$\phi\f$ is the azimuth angle with respect to the
1628   - * measured photon direction, where \f$\phi=0\f$ corresponds to the
1629   - * connecting line between model centre and measured photon direction.
  2203 + * Computes the value of the spatial model at the position (delta,phi) given
  2204 + * in point spread function coordinates. The transformation from point
  2205 + * spread function coordinates into sky coordinates is done using a rotation
  2206 + * matrix that is pre-computed on entry.
1630 2207 ***************************************************************************/
1631   -double cta_irf_radial_kern_phi::eval(const double& phi)
  2208 +double cta_psf_diffuse_kern_phi::eval(const double& phi)
1632 2209 {
1633   - // Compute radial model theta angle
1634   - double theta = gammalib::acos(m_cos_fact_model +
1635   - m_sin_fact_model * std::cos(phi));
1636   -
1637   - // Get radial model value
1638   - double value = m_model->eval(theta, m_srcEng, m_srcTime);
1639   -
1640   - // For debugging: Check boundary violation
1641   - if (value == 0.0) {
1642   - std::cout << "Model 0 at theta=" << theta*gammalib::rad2deg;
1643   - std::cout << " deg; phi=";
1644   - std::cout << phi*gammalib::rad2deg << " deg";
1645   - std::cout << std::endl;
1646   - throw;
1647   - }
1648   -
1649   - // If model is positive the multiply it with the effective area and
1650   - // optionally fold in energy dispersion
1651   - #if defined(G_USE_OBSDIR_FOR_AEFF)
1652   - if (m_rsp->use_edisp() && value > 0.0) {
1653   -
1654   - // Compute offset angle of true photon in camera system (radians)
1655   - double offset = gammalib::acos(m_cos_fact_inst +
1656   - m_sin_fact_inst * std::cos(m_phi0 - phi));
1657   -
1658   - // Take energy dispersion into account
1659   - value *= m_rsp->edisp(m_obsEng, offset, 0.0, m_pnt.zenith(), m_pnt.azimuth(), m_srcLogEng);
  2210 + // Compute sky direction vector in native coordinates
  2211 + double cos_phi = std::cos(phi);
  2212 + double sin_phi = std::sin(phi);
  2213 + GVector native(-cos_phi*m_sin_delta, sin_phi*m_sin_delta, m_cos_delta);
1660 2214  
1661   - } // endif: model value was positive
1662   - #else
1663   - if (value > 0.0) {
  2215 + // Rotate from native into celestial system
  2216 + GVector cel = m_rot * native;
1664 2217  
1665   - // Compute offset angle of true photon in camera system (radians)
1666   - double offset = gammalib::acos(m_cos_fact_inst +
1667   - m_sin_fact_inst * std::cos(m_phi0 - phi));
  2218 + // Set sky direction
  2219 + GSkyDir srcDir;
  2220 + srcDir.celvector(cel);
1668 2221  
1669   - // Multiply with effective area
1670   - value *= m_rsp->aeff(offset, 0.0, m_pnt.zenith(), m_pnt.azimuth(), m_srcLogEng);
  2222 + // Set photon
  2223 + GPhoton photon(srcDir, m_srcEng, m_srcTime);
1671 2224  
1672   - // Optionally take energy dispersion into account
1673   - if (m_rsp->use_edisp() && value > 0.0) {
1674   - value *= m_rsp->edisp(m_obsEng, offset, 0.0, m_pnt.zenith(), m_pnt.azimuth(), m_srcLogEng);
1675   - }
  2225 + // Compute map value this sky direction
  2226 + double value = m_model->eval(photon);
1676 2227  
1677   - } // endif: model value was positive
1678   - #endif
1679   -
1680 2228 // Debug: Check for NaN
1681 2229 #if defined(G_NAN_CHECK)
1682 2230 if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
1683   - std::cout << "*** ERROR: cta_irf_radial_kern_phi::eval";
  2231 + std::cout << "*** ERROR: cta_psf_diffuse_kern_phi::eval";
1684 2232 std::cout << "(phi=" << phi << "):";
1685 2233 std::cout << " NaN/Inf encountered";
1686 2234 std::cout << " (value=" << value;
... ...
inst/cta/src/GCTAResponse_helpers.hpp
... ... @@ -29,6 +29,8 @@
29 29  
30 30 /* __ Includes ___________________________________________________________ */
31 31 #include <cmath>
  32 +#include <vector>
  33 +#include <utility>
32 34 #include "GCTAResponseIrf.hpp"
33 35 #include "GCTAObservation.hpp"
34 36 #include "GMatrix.hpp"
... ... @@ -44,6 +46,16 @@
44 46  
45 47 /* __ Forward declaration ________________________________________________ */
46 48  
  49 +/* __ Typedefs ___________________________________________________________ */
  50 +typedef std::vector<std::pair<double,double> > cta_omega_intervals;
  51 +
  52 +/* __ Prototypes _________________________________________________________ */
  53 +namespace gammalib {
  54 + cta_omega_intervals limit_omega(const double& min,
  55 + const double& max,
  56 + const double& domega);
  57 +}
  58 +
47 59  
48 60 /***********************************************************************//**
49 61 * @class cta_npsf_kern_rad_azsym
... ... @@ -433,55 +445,64 @@ class cta_irf_elliptical_kern_rho : public GFunction {
433 445 public:
434 446 cta_irf_elliptical_kern_rho(const GCTAResponseIrf& rsp,
435 447 const GModelSpatialElliptical& model,
  448 + const double& semimajor,
  449 + const double& semiminor,
  450 + const double& posangle,
436 451 const double& zenith,
437 452 const double& azimuth,
438 453 const GEnergy& srcEng,
439 454 const GTime& srcTime,
440 455 const double& srcLogEng,
441 456 const GEnergy& obsEng,
442   - const double& zeta,
443   - const double& lambda,
444   - const double& obsOmega,
445   - const double& omega0,
  457 + const double& rho_obs,
  458 + const double& posangle_obs,
  459 + const double& rho_pnt,
  460 + const double& omega_pnt,
446 461 const double& delta_max,
447 462 const int& iter) :
448 463 m_rsp(rsp),
449 464 m_model(model),
  465 + m_semimajor(semimajor),
  466 + m_semiminor(semiminor),
  467 + m_posangle(posangle),
450 468 m_zenith(zenith),
451 469 m_azimuth(azimuth),
452 470 m_srcEng(srcEng),
453 471 m_srcTime(srcTime),
454 472 m_srcLogEng(srcLogEng),
455 473 m_obsEng(obsEng),
456   - m_zeta(zeta),
457   - m_cos_zeta(std::cos(zeta)),
458   - m_sin_zeta(std::sin(zeta)),
459   - m_lambda(lambda),
460   - m_cos_lambda(std::cos(lambda)),
461   - m_sin_lambda(std::sin(lambda)),
462   - m_obsOmega(obsOmega),
463   - m_omega0(omega0),
  474 + m_rho_obs(rho_obs),
  475 + m_cos_rho_obs(std::cos(rho_obs)),
  476 + m_sin_rho_obs(std::sin(rho_obs)),
  477 + m_posangle_obs(posangle_obs),
  478 + m_rho_pnt(rho_pnt),
  479 + m_cos_rho_pnt(std::cos(rho_pnt)),
  480 + m_sin_rho_pnt(std::sin(rho_pnt)),
  481 + m_omega_pnt(omega_pnt),
464 482 m_delta_max(delta_max),
465 483 m_cos_delta_max(std::cos(delta_max)),
466 484 m_iter(iter) { }
467 485 double eval(const double& rho);
468 486 public:
469 487 const GCTAResponseIrf& m_rsp; //!< CTA response
470   - const GModelSpatialElliptical& m_model; //!< Spatial model
  488 + const GModelSpatialElliptical& m_model; //!< Elliptical model
  489 + const double& m_semimajor; //!< Ellipse boundary semimajor axis
  490 + const double& m_semiminor; //!< Ellipse boundary semiminor axis
  491 + const double& m_posangle; //!< Ellipse boundary position angle
471 492 const double& m_zenith; //!< Zenith angle
472 493 const double& m_azimuth; //!< Azimuth angle
473 494 const GEnergy& m_srcEng; //!< True photon energy
474 495 const GTime& m_srcTime; //!< True photon time
475 496 const double& m_srcLogEng; //!< True photon log energy
476 497 const GEnergy& m_obsEng; //!< Measured event energy
477   - const double& m_zeta; //!< Distance model centre - measured photon
478   - double m_cos_zeta; //!< Cosine of zeta
479   - double m_sin_zeta; //!< Sine of zeta
480   - const double& m_lambda; //!< Distance model centre - pointing
481   - double m_cos_lambda; //!< Cosine of lambda
482   - double m_sin_lambda; //!< Sine of lambda
483   - const double& m_obsOmega; //!< Measured photon position angle from model centre
484   - const double& m_omega0; //!< Azimuth of pointing in model system
  498 + const double& m_rho_obs; //!< Distance of model centre from measured photon
  499 + double m_cos_rho_obs; //!< Cosine of m_rho_obs
  500 + double m_sin_rho_obs; //!< Sine of m_rho_obs
  501 + const double& m_posangle_obs; //!< Photon position angle measured from model centre
  502 + const double& m_rho_pnt; //!< Distance of model centre from pointing
  503 + double m_cos_rho_pnt; //!< Cosine of m_rho_pnt
  504 + double m_sin_rho_pnt; //!< Sine of m_rho_pnt
  505 + const double& m_omega_pnt; //!< Azimuth of pointing in model system
485 506 const double& m_delta_max; //!< Maximum PSF radius
486 507 double m_cos_delta_max; //!< Cosine of maximum PSF radius
487 508 const int& m_iter; //!< Integration iterations
... ... @@ -517,8 +538,8 @@ public:
517 538 const GTime& srcTime,
518 539 const double& srcLogEng,
519 540 const GEnergy& obsEng,
520   - const double& obsOmega,
521   - const double& omega0,
  541 + const double& posangle_obs,
  542 + const double& omega_pnt,
522 543 const double& rho,
523 544 const double& cos_psf,
524 545 const double& sin_psf,
... ... @@ -532,8 +553,8 @@ public:
532 553 m_srcTime(srcTime),
533 554 m_srcLogEng(srcLogEng),
534 555 m_obsEng(obsEng),
535   - m_obsOmega(obsOmega),
536   - m_omega0(omega0),
  556 + m_posangle_obs(posangle_obs),
  557 + m_omega_pnt(omega_pnt),
537 558 m_rho(rho),
538 559 m_cos_psf(cos_psf),
539 560 m_sin_psf(sin_psf),
... ... @@ -541,21 +562,21 @@ public:
541 562 m_sin_ph(sin_ph) { }
542 563 double eval(const double& omega);
543 564 public:
544   - const GCTAResponseIrf& m_rsp; //!< CTA response
545   - const GModelSpatialElliptical& m_model; //!< Spatial model
546   - const double& m_zenith; //!< Zenith angle
547   - const double& m_azimuth; //!< Azimuth angle
548   - const GEnergy& m_srcEng; //!< True photon energy
549   - const GTime& m_srcTime; //!< True photon time
550   - const double& m_srcLogEng; //!< True photon log energy
551   - const GEnergy& m_obsEng; //!< Measured event energy
552   - const double& m_obsOmega; //!< Measured photon position angle from model centre
553   - const double& m_omega0; //!< Azimuth of pointing in model system
554   - const double& m_rho; //!< Model zenith angle
555   - const double& m_cos_psf; //!< Cosine term for PSF offset angle computation
556   - const double& m_sin_psf; //!< Sine term for PSF offset angle computation
557   - const double& m_cos_ph; //!< Cosine term for photon offset angle computation
558   - const double& m_sin_ph; //!< Sine term for photon offset angle computation
  565 + const GCTAResponseIrf& m_rsp; //!< CTA response
  566 + const GModelSpatialElliptical& m_model; //!< Spatial model
  567 + const double& m_zenith; //!< Zenith angle
  568 + const double& m_azimuth; //!< Azimuth angle
  569 + const GEnergy& m_srcEng; //!< True photon energy
  570 + const GTime& m_srcTime; //!< True photon time
  571 + const double& m_srcLogEng; //!< True photon log energy
  572 + const GEnergy& m_obsEng; //!< Measured event energy
  573 + const double& m_posangle_obs; //!< Measured photon position angle from model centre
  574 + const double& m_omega_pnt; //!< Azimuth of pointing in model system
  575 + const double& m_rho; //!< Model zenith angle
  576 + const double& m_cos_psf; //!< Cosine term for PSF offset angle computation
  577 + const double& m_sin_psf; //!< Sine term for PSF offset angle computation
  578 + const double& m_cos_ph; //!< Cosine term for photon offset angle computation
  579 + const double& m_sin_ph; //!< Sine term for photon offset angle computation
559 580 };
560 581  
561 582  
... ... @@ -594,42 +615,51 @@ class cta_npred_elliptical_kern_rho : public GFunction {
594 615 public:
595 616 cta_npred_elliptical_kern_rho(const GCTAResponseIrf& rsp,
596 617 const GModelSpatialElliptical& model,
  618 + const double& semimajor,
  619 + const double& semiminor,
  620 + const double& posangle,
597 621 const GEnergy& srcEng,
598 622 const GTime& srcTime,
599 623 const GCTAObservation& obs,
600 624 const GMatrix& rot,
601   - const double& dist,
602   - const double& radius,
603   - const double& omega0,
  625 + const double& rho_roi,
  626 + const double& posangle_roi,
  627 + const double& radius_roi,
604 628 const int& iter) :
605 629 m_rsp(rsp),
606 630 m_model(model),
  631 + m_semimajor(semimajor),
  632 + m_semiminor(semiminor),
  633 + m_posangle(posangle),
607 634 m_srcEng(srcEng),
608 635 m_srcTime(srcTime),
609 636 m_obs(obs),
610 637 m_rot(rot),
611   - m_dist(dist),
612   - m_cos_dist(std::cos(dist)),
613   - m_sin_dist(std::sin(dist)),
614   - m_radius(radius),
615   - m_cos_radius(std::cos(radius)),
616   - m_omega0(omega0),
  638 + m_rho_roi(rho_roi),
  639 + m_cos_rho_roi(std::cos(rho_roi)),
  640 + m_sin_rho_roi(std::sin(rho_roi)),
  641 + m_posangle_roi(posangle_roi),
  642 + m_radius_roi(radius_roi),
  643 + m_cos_radius_roi(std::cos(radius_roi)),
617 644 m_iter(iter) { }
618 645 double eval(const double& rho);
619 646 protected:
620   - const GCTAResponseIrf& m_rsp; //!< CTA response
621   - const GModelSpatialElliptical& m_model; //!< Spatial model
622   - const GEnergy& m_srcEng; //!< True photon energy
623   - const GTime& m_srcTime; //!< True photon arrival time
624   - const GCTAObservation& m_obs; //!< CTA observation
625   - const GMatrix& m_rot; //!< Rotation matrix
626   - const double& m_dist; //!< Distance model-ROI centre
627   - double m_cos_dist; //!< Cosine of distance model-ROI centre
628   - double m_sin_dist; //!< Sine of distance model-ROI centre
629   - const double& m_radius; //!< ROI+PSF radius
630   - double m_cos_radius; //!< Cosine of ROI+PSF radius
631   - const double& m_omega0; //!< Position angle of ROI
632   - const int& m_iter; //!< Integration iterations
  647 + const GCTAResponseIrf& m_rsp; //!< CTA response
  648 + const GModelSpatialElliptical& m_model; //!< Elliptical model
  649 + const double& m_semimajor; //!< Ellipse boundary semimajor axis
  650 + const double& m_semiminor; //!< Ellipse boundary semiminor axis
  651 + const double& m_posangle; //!< Ellipse boundary position angle
  652 + const GEnergy& m_srcEng; //!< True photon energy
  653 + const GTime& m_srcTime; //!< True photon arrival time
  654 + const GCTAObservation& m_obs; //!< CTA observation
  655 + const GMatrix& m_rot; //!< Rotation matrix
  656 + const double& m_rho_roi; //!< Distance between model and ROI centre
  657 + double m_cos_rho_roi; //!< Cosine of m_rho_roi
  658 + double m_sin_rho_roi; //!< Sine of m_rho_roi
  659 + const double& m_posangle_roi; //!< Position angle of ROI
  660 + const double& m_radius_roi; //!< ROI+PSF radius
  661 + double m_cos_radius_roi; //!< Cosine of m_radius_roi
  662 + const int& m_iter; //!< Integration iterations
633 663 };
634 664  
635 665  
... ... @@ -663,26 +693,32 @@ public:
663 693 const GTime& srcTime,
664 694 const GCTAObservation& obs,
665 695 const GMatrix& rot,
  696 + const double& rho,
666 697 const double& sin_rho,
667   - const double& cos_rho) :
  698 + const double& cos_rho,
  699 + const double& posangle_roi) :
668 700 m_rsp(rsp),
669 701 m_model(model),
670 702 m_srcEng(srcEng),
671 703 m_srcTime(srcTime),
672 704 m_obs(obs),
673 705 m_rot(rot),
  706 + m_rho(rho),
674 707 m_sin_rho(sin_rho),
675   - m_cos_rho(cos_rho) { }
  708 + m_cos_rho(cos_rho),
  709 + m_posangle_roi(posangle_roi) { }
676 710 double eval(const double& omega);
677 711 protected:
678   - const GCTAResponseIrf& m_rsp; //!< CTA response
679   - const GModelSpatialElliptical& m_model; //!< Model
680   - const GEnergy& m_srcEng; //!< True photon energy
681   - const GTime& m_srcTime; //!< True photon arrival time
682   - const GCTAObservation& m_obs; //!< Pointer to observation
683   - const GMatrix& m_rot; //!< Rotation matrix
684   - const double& m_sin_rho; //!< Sine of offset angle
685   - const double& m_cos_rho; //!< Cosine of offset angle
  712 + const GCTAResponseIrf& m_rsp; //!< CTA response
  713 + const GModelSpatialElliptical& m_model; //!< Model
  714 + const GEnergy& m_srcEng; //!< True photon energy
  715 + const GTime& m_srcTime; //!< True photon arrival time
  716 + const GCTAObservation& m_obs; //!< Pointer to observation
  717 + const GMatrix& m_rot; //!< Rotation matrix
  718 + const double& m_rho;
  719 + const double& m_sin_rho; //!< Sine of offset angle
  720 + const double& m_cos_rho; //!< Cosine of offset angle
  721 + const double& m_posangle_roi; //!< Position angle of ROI
686 722 };
687 723  
688 724  
... ... @@ -946,70 +982,111 @@ protected:
946 982  
947 983  
948 984 /***********************************************************************//**
949   - * @class cta_psf_diffuse_kern_delta
  985 + * @class cta_irf_radial_kern_rho
950 986 *
951   - * @brief Kernel for Psf delta angle integration used for stacked analysis
  987 + * @brief Kernel for radial model zenith angle integration
  988 + *
  989 + * This class implements the integration kernel \f$K(\rho)\f$ for the
  990 + * integration
  991 + *
  992 + * \f[
  993 + * \int_{\rho_{\rm min}}^{\rho_{\rm max}} K(\rho | E, t) d\rho
  994 + * \f]
  995 + *
  996 + * of radial spatial models. The eval() method computes
  997 + *
  998 + * \f[
  999 + * K(\rho | E, t) = \sin \rho \times
  1000 + * S_{\rm p}(\rho | E, t) \times
  1001 + * \int_{\omega} PSF(\rho, \omega) d\omega
  1002 + * \f]
  1003 + *
  1004 + * where
  1005 + * \f$S_{\rm p}(\rho | E, t)\f$ is the radial model,
  1006 + * \f$PSF(\rho, \omega)\f$ is the point spread function,
  1007 + * \f$\rho\f$ is the distance from the model centre, and
  1008 + * \f$\omega\f$ is the position angle with respect to the connecting line
  1009 + * between the model centre and the observed photon arrival direction.
952 1010 ***************************************************************************/
953   -class cta_psf_diffuse_kern_delta : public GFunction {
  1011 +class cta_psf_radial_kern_rho : public GFunction {
954 1012 public:
955   - cta_psf_diffuse_kern_delta(const GCTAResponseCube* rsp,
956   - const GModelSpatial* model,
957   - const GSkyDir& srcDir,
958   - const GEnergy& srcEng,
959   - const GTime& srcTime,
960   - const GMatrix& rot,
961   - const double& eps,
962   - const int& order) :
963   - m_rsp(rsp),
964   - m_model(model),
965   - m_srcDir(srcDir),
966   - m_srcEng(srcEng),
967   - m_srcTime(srcTime),
968   - m_rot(rot),
969   - m_eps(eps),
970   - m_order (order),
971   - m_psf_max(rsp->psf()(srcDir, 0.0, srcEng)) { }
972   - double eval(const double& delta);
973   -protected:
974   - const GCTAResponseCube* m_rsp; //!< Response cube
975   - const GModelSpatial* m_model; //!< Spatial model
976   - const GSkyDir& m_srcDir; //!< True photon arrival direction
977   - const GEnergy& m_srcEng; //!< True photon energy
978   - const GTime& m_srcTime; //!< True photon arrival time
979   - const GMatrix& m_rot; //!< Rotation matrix
980   - const double& m_eps; //!< Integration precision
981   - const int& m_order; //!< Romberg order
982   - double m_psf_max; //!< Maximum PSF value
  1013 + cta_psf_radial_kern_rho(const GCTAResponseCube* rsp,
  1014 + const GModelSpatialRadial* model,
  1015 + const GSkyDir& srcDir,
  1016 + const GEnergy& srcEng,
  1017 + const GTime& srcTime,
  1018 + const double& rho_obs,
  1019 + const double& delta_max,
  1020 + const int& iter) :
  1021 + m_rsp(rsp),
  1022 + m_model(model),
  1023 + m_srcDir(srcDir),
  1024 + m_srcEng(srcEng),
  1025 + m_srcTime(srcTime),
  1026 + m_rho_obs(rho_obs),
  1027 + m_cos_rho_obs(std::cos(rho_obs)),
  1028 + m_sin_rho_obs(std::sin(rho_obs)),
  1029 + m_delta_max(delta_max),
  1030 + m_cos_delta_max(std::cos(delta_max)),
  1031 + m_iter(iter) { }
  1032 + double eval(const double& rho);
  1033 +public:
  1034 + const GCTAResponseCube* m_rsp; //!< CTA response
  1035 + const GModelSpatialRadial* m_model; //!< Radial model
  1036 + const GSkyDir& m_srcDir; //!< True photon arrival direction
  1037 + const GEnergy& m_srcEng; //!< True photon energy
  1038 + const GTime& m_srcTime; //!< True photon time
  1039 + const double& m_rho_obs; //!< Distance of model centre from measured photon
  1040 + double m_cos_rho_obs; //!< Cosine of m_rho_obs
  1041 + double m_sin_rho_obs; //!< Sine of m_rho_obs
  1042 + const double& m_delta_max; //!< Maximum PSF radius
  1043 + double m_cos_delta_max; //!< Cosine of maximum PSF radius
  1044 + const int& m_iter; //!< Integration iterations
983 1045 };
984 1046  
985 1047  
986 1048 /***********************************************************************//**
987   - * @class cta_psf_diffuse_kern_phi
  1049 + * @class cta_irf_radial_kern_omega
988 1050 *
989   - * @brief Kernel for Psf phi angle integration used for stacked analysis
  1051 + * @brief Kernel for radial model azimuth angle integration
  1052 + *
  1053 + * This class implements the computation of
  1054 + *
  1055 + * \f[
  1056 + * K(\omega | \rho, E, t) = PSF(\omega | \rho)
  1057 + * \f]
  1058 + *
  1059 + * where
  1060 + * \f$PSF(\omega | \rho)\f$ is the point spread function,
  1061 + * \f$\rho\f$ is the distance from the model centre, and
  1062 + * \f$\omega\f$ is the position angle with respect to the connecting line
  1063 + * between the model centre and the observed photon arrival direction.
990 1064 ***************************************************************************/
991   -class cta_psf_diffuse_kern_phi : public GFunction {
  1065 +class cta_psf_radial_kern_omega : public GFunction {
992 1066 public:
993   - cta_psf_diffuse_kern_phi(const GModelSpatial* model,
994   - const GEnergy& srcEng,
995   - const GTime& srcTime,
996   - const GMatrix& rot,
997   - const double& sin_delta,
998   - const double& cos_delta) :
999   - m_model(model),
1000   - m_srcEng(srcEng),
1001   - m_srcTime(srcTime),
1002   - m_rot(rot),
1003   - m_sin_delta(sin_delta),
1004   - m_cos_delta(cos_delta) { }
1005   - double eval(const double& phi);
1006   -protected:
1007   - const GModelSpatial* m_model; //!< Spatial model
1008   - const GEnergy& m_srcEng; //!< True photon energy
1009   - const GTime& m_srcTime; //!< True photon arrival time
1010   - const GMatrix& m_rot; //!< Rotation matrix
1011   - const double& m_sin_delta; //!< sin(delta)
1012   - const double& m_cos_delta; //!< cos(delta)
  1067 + cta_psf_radial_kern_omega(const GCTAResponseCube* rsp,
  1068 + const GModelSpatialRadial* model,
  1069 + const GSkyDir& srcDir,
  1070 + const GEnergy& srcEng,
  1071 + const GTime& srcTime,
  1072 + const double& cos_psf,
  1073 + const double& sin_psf) :
  1074 + m_rsp(rsp),
  1075 + m_model(model),
  1076 + m_srcDir(srcDir),
  1077 + m_srcEng(srcEng),
  1078 + m_srcTime(srcTime),
  1079 + m_cos_psf(cos_psf),
  1080 + m_sin_psf(sin_psf) { }
  1081 + double eval(const double& omega);
  1082 +public:
  1083 + const GCTAResponseCube* m_rsp; //!< CTA response
  1084 + const GModelSpatialRadial* m_model; //!< Radial model
  1085 + const GSkyDir& m_srcDir; //!< True photon sky direction
  1086 + const GEnergy& m_srcEng; //!< True photon energy
  1087 + const GTime& m_srcTime; //!< True photon time
  1088 + const double& m_cos_psf; //!< Cosine term for PSF offset angle computation
  1089 + const double& m_sin_psf; //!< Sine term for PSF offset angle computation
1013 1090 };
1014 1091  
1015 1092  
... ... @@ -1025,32 +1102,33 @@ public:
1025 1102 const GSkyDir& srcDir,
1026 1103 const GEnergy& srcEng,
1027 1104 const GTime& srcTime,
1028   - const double& sin_zeta,
1029   - const double& cos_zeta,
1030   - const double& eps,
1031   - const int& order) :
  1105 + const double& delta_mod,
  1106 + const double& theta_max,
  1107 + const int& iter) :
1032 1108 m_rsp(rsp),
1033 1109 m_model(model),
1034 1110 m_srcDir(srcDir),
1035 1111 m_srcEng(srcEng),
1036 1112 m_srcTime(srcTime),
1037   - m_sin_zeta(sin_zeta),
1038   - m_cos_zeta(cos_zeta),
1039   - m_eps(eps),
1040   - m_order (order),
1041   - m_psf_max(rsp->psf()(srcDir, 0.0, srcEng)) { }
  1113 + m_delta_mod(delta_mod),
  1114 + m_cos_delta_mod(std::cos(delta_mod)),
  1115 + m_sin_delta_mod(std::sin(delta_mod)),
  1116 + m_theta_max(theta_max),
  1117 + m_cos_theta_max(std::cos(theta_max)),
  1118 + m_iter(iter) { }
1042 1119 double eval(const double& delta);
1043 1120 protected:
1044   - const GCTAResponseCube* m_rsp; //!< Response cube
1045   - const GModelSpatialRadial* m_model; //!< Radial model
1046   - const GSkyDir& m_srcDir; //!< True photon arrival direction
1047   - const GEnergy& m_srcEng; //!< True photon energy
1048   - const GTime& m_srcTime; //!< True photon arrival time
1049   - const double& m_sin_zeta; //!< sin(zeta)
1050   - const double& m_cos_zeta; //!< cos(zeta)
1051   - const double& m_eps; //!< Integration precision
1052   - const int& m_order; //!< Romberg order
1053   - double m_psf_max; //!< Maximum PSF value
  1121 + const GCTAResponseCube* m_rsp; //!< Response cube
  1122 + const GModelSpatialRadial* m_model; //!< Radial model
  1123 + const GSkyDir& m_srcDir; //!< True photon arrival direction
  1124 + const GEnergy& m_srcEng; //!< True photon energy
  1125 + const GTime& m_srcTime; //!< True photon arrival time
  1126 + const double& m_delta_mod; //!< Distance of model from Psf
  1127 + double m_cos_delta_mod; //!< Cosine of m_delta_mod
  1128 + double m_sin_delta_mod; //!< Sine of m_delta_mod
  1129 + const double& m_theta_max; //!< Maximum model radius
  1130 + double m_cos_theta_max; //!< Cosine of m_theta_max
  1131 + const int& m_iter; //!< Integration iterations
1054 1132 };
1055 1133  
1056 1134  
... ... @@ -1076,197 +1154,205 @@ protected:
1076 1154 const GModelSpatialRadial* m_model; //!< Radial model
1077 1155 const GEnergy& m_srcEng; //!< True photon energy
1078 1156 const GTime& m_srcTime; //!< True photon arrival time
1079   - const double& m_sin_fact; //!< sin(delta)*sin(zeta)
1080   - const double& m_cos_fact; //!< cos(delta)*cos(zeta)
  1157 + const double& m_sin_fact; //!< sin(delta)*sin(delta_mod)
  1158 + const double& m_cos_fact; //!< cos(delta)*cos(delta_mod)
1081 1159 };
1082 1160  
1083 1161  
1084 1162 /***********************************************************************//**
1085   - * @class cta_psf_elliptical_kern_delta
  1163 + * @class cta_irf_elliptical_kern_rho
1086 1164 *
1087   - * @brief Kernel for Psf delta angle integration used for stacked analysis
  1165 + * @brief Kernel for elliptical model zenith angle integration
  1166 + *
  1167 + * This class implements the integration kernel \f$K(\rho)\f$ for the
  1168 + * integration
  1169 + *
  1170 + * \f[
  1171 + * \int_{\rho_{\rm min}}^{\rho_{\rm max}} K(\rho | E, t) d\rho
  1172 + * \f]
  1173 + *
  1174 + * of elliptical spatial models. The eval() method computes
  1175 + *
  1176 + * \f[
  1177 + * K(\rho | E, t) = \sin \rho \times
  1178 + * \int_{\omega}
  1179 + * S_{\rm p}(\rho, \omega | E, t) \, PSF(\rho, \omega)
  1180 + * d\omega
  1181 + * \f]
  1182 + *
  1183 + * where
  1184 + * \f$S_{\rm p}(\rho, \omega | E, t)\f$ is the elliptical model,
  1185 + * \f$PSF(\rho, \omega)\f$ is the point spread function,
  1186 + * \f$\rho\f$ is the distance from the model centre, and
  1187 + * \f$\omega\f$ is the position angle with respect to the connecting line
  1188 + * between the model centre and the observed photon arrival direction.
1088 1189 ***************************************************************************/
1089   -class cta_psf_elliptical_kern_delta : public GFunction {
  1190 +class cta_psf_elliptical_kern_rho : public GFunction {
1090 1191 public:
1091   - cta_psf_elliptical_kern_delta(const GCTAResponseCube* rsp,
  1192 + cta_psf_elliptical_kern_rho(const GCTAResponseCube* rsp,
  1193 + const GModelSpatialElliptical* model,
  1194 + const double& semimajor,
  1195 + const double& semiminor,
  1196 + const double& posangle,
  1197 + const GSkyDir& srcDir,
  1198 + const GEnergy& srcEng,
  1199 + const GTime& srcTime,
  1200 + const double& rho_obs,
  1201 + const double& posangle_obs,
  1202 + const double& delta_max,
  1203 + const int& iter) :
  1204 + m_rsp(rsp),
  1205 + m_model(model),
  1206 + m_semimajor(semimajor),
  1207 + m_semiminor(semiminor),
  1208 + m_posangle(posangle),
  1209 + m_srcDir(srcDir),
  1210 + m_srcEng(srcEng),
  1211 + m_srcTime(srcTime),
  1212 + m_rho_obs(rho_obs),
  1213 + m_cos_rho_obs(std::cos(rho_obs)),
  1214 + m_sin_rho_obs(std::sin(rho_obs)),
  1215 + m_posangle_obs(posangle_obs),
  1216 + m_delta_max(delta_max),
  1217 + m_cos_delta_max(std::cos(delta_max)),
  1218 + m_iter(iter) { }
  1219 + double eval(const double& rho);
  1220 +public:
  1221 + const GCTAResponseCube* m_rsp; //!< CTA response
  1222 + const GModelSpatialElliptical* m_model; //!< Elliptical model
  1223 + const double& m_semimajor; //!< Ellipse boundary semimajor axis
  1224 + const double& m_semiminor; //!< Ellipse boundary semiminor axis
  1225 + const double& m_posangle; //!< Ellipse boundary position angle
  1226 + const GSkyDir& m_srcDir; //!< True photon arrival direction
  1227 + const GEnergy& m_srcEng; //!< True photon energy
  1228 + const GTime& m_srcTime; //!< True photon time
  1229 + const double& m_rho_obs; //!< Distance of model centre from measured photon
  1230 + double m_cos_rho_obs; //!< Cosine of m_rho_obs
  1231 + double m_sin_rho_obs; //!< Sine of m_rho_obs
  1232 + const double& m_posangle_obs; //!< Photon position angle measured from model centre
  1233 + const double& m_delta_max; //!< Maximum PSF radius
  1234 + double m_cos_delta_max; //!< Cosine of maximum PSF radius
  1235 + const int& m_iter; //!< Integration iterations
  1236 +};
  1237 +
  1238 +
  1239 +/***********************************************************************//**
  1240 + * @class cta_irf_elliptical_kern_omega
  1241 + *
  1242 + * @brief Kernel for elliptical model azimuth angle integration
  1243 + *
  1244 + * This class implements the computation of
  1245 + *
  1246 + * \f[
  1247 + * S_{\rm p}(\omega | \rho, E, t) \, PSF(\omega | \rho)
  1248 + * \f]
  1249 + *
  1250 + * where
  1251 + * \f$S_{\rm p}(\omega | \rho, E, t)\f$ is the elliptical model,
  1252 + * \f$ PSF(\omega | \rho)\f$ is the point spread function,
  1253 + * \f$\rho\f$ is the distance from the model centre, and
  1254 + * \f$\omega\f$ is the position angle with respect to the connecting line
  1255 + * between the model centre and the observed photon arrival direction.
  1256 + ***************************************************************************/
  1257 +class cta_psf_elliptical_kern_omega : public GFunction {
  1258 +public:
  1259 + cta_psf_elliptical_kern_omega(const GCTAResponseCube* rsp,
1092 1260 const GModelSpatialElliptical* model,
1093 1261 const GSkyDir& srcDir,
1094 1262 const GEnergy& srcEng,
1095 1263 const GTime& srcTime,
1096   - const double& sin_zeta,
1097   - const double& cos_zeta,
1098   - const double& omega,
1099   - const double& eps,
1100   - const int& order) :
  1264 + const double& posangle_obs,
  1265 + const double& rho,
  1266 + const double& cos_psf,
  1267 + const double& sin_psf) :
1101 1268 m_rsp(rsp),
1102 1269 m_model(model),
1103 1270 m_srcDir(srcDir),
1104 1271 m_srcEng(srcEng),
1105 1272 m_srcTime(srcTime),
1106   - m_sin_zeta(sin_zeta),
1107   - m_cos_zeta(cos_zeta),
1108   - m_omega(omega),
1109   - m_eps(eps),
1110   - m_order (order),
1111   - m_psf_max(rsp->psf()(srcDir, 0.0, srcEng)) { }
1112   - double eval(const double& delta);
1113   -protected:
1114   - const GCTAResponseCube* m_rsp; //!< Response cube
1115   - const GModelSpatialElliptical* m_model; //!< Elliptical model
1116   - const GSkyDir& m_srcDir; //!< True photon arrival direction
1117   - const GEnergy& m_srcEng; //!< True photon energy
1118   - const GTime& m_srcTime; //!< True photon arrival time
1119   - const double& m_sin_zeta; //!< sin(zeta)
1120   - const double& m_cos_zeta; //!< cos(zeta)
1121   - const double& m_omega; //!< Position angle of photon w/r model
1122   - const double& m_eps; //!< Integration precision
1123   - const int& m_order; //!< Romberg order
1124   - double m_psf_max; //!< Maximum PSF value
1125   -};
1126   -
1127   -
1128   -/***********************************************************************//**
1129   - * @class cta_psf_elliptical_kern_delta
1130   - *
1131   - * @brief Kernel for Psf phi angle integration used for stacked analysis
1132   - ***************************************************************************/
1133   -class cta_psf_elliptical_kern_phi : public GFunction {
  1273 + m_posangle_obs(posangle_obs),
  1274 + m_rho(rho),
  1275 + m_cos_psf(cos_psf),
  1276 + m_sin_psf(sin_psf) { }
  1277 + double eval(const double& omega);
1134 1278 public:
1135   - cta_psf_elliptical_kern_phi(const GModelSpatialElliptical* model,
1136   - const GEnergy& srcEng,
1137   - const GTime& srcTime,
1138   - const double& omega,
1139   - const double& sin_delta,
1140   - const double& sin_fact,
1141   - const double& cos_fact) :
1142   - m_model(model),
1143   - m_srcEng(srcEng),
1144   - m_srcTime(srcTime),
1145   - m_omega(omega),
1146   - m_sin_delta(sin_delta),
1147   - m_sin_fact(sin_fact),
1148   - m_cos_fact(cos_fact) { }
1149   - double eval(const double& phi);
1150   -protected:
1151   - const GModelSpatialElliptical* m_model; //!< Elliptical model
1152   - const GEnergy& m_srcEng; //!< True photon energy
1153   - const GTime& m_srcTime; //!< True photon arrival time
1154   - const double& m_omega; //!< Position angle of photon w/r model
1155   - const double& m_sin_delta; //!< sin(delta)
1156   - const double& m_sin_fact; //!< sin(delta)*sin(zeta)
1157   - const double& m_cos_fact; //!< cos(delta)*cos(zeta)
  1279 + const GCTAResponseCube* m_rsp; //!< CTA response
  1280 + const GModelSpatialElliptical* m_model; //!< Spatial model
  1281 + const GSkyDir& m_srcDir; //!< True photon sky direction
  1282 + const GEnergy& m_srcEng; //!< True photon energy
  1283 + const GTime& m_srcTime; //!< True photon time
  1284 + const double& m_posangle_obs; //!< Measured photon position angle from model centre
  1285 + const double& m_rho; //!< Model zenith angle
  1286 + const double& m_cos_psf; //!< Cosine term for PSF offset angle computation
  1287 + const double& m_sin_psf; //!< Sine term for PSF offset angle computation
1158 1288 };
1159 1289  
1160 1290  
1161 1291 /***********************************************************************//**
1162   - * @class cta_irf_radial_kern_delta
1163   - *
1164   - * @brief Test kernel for Irf delta angle integration
  1292 + * @class cta_psf_diffuse_kern_delta
1165 1293 *
1166   - * This class is used for Psf system based integration. It is only here for
1167   - * testing and this class may be removed in the future.
  1294 + * @brief Kernel for Psf delta angle integration used for stacked analysis
1168 1295 ***************************************************************************/
1169   -class cta_irf_radial_kern_delta : public GFunction {
  1296 +class cta_psf_diffuse_kern_delta : public GFunction {
1170 1297 public:
1171   - cta_irf_radial_kern_delta(const GCTAResponseIrf* rsp,
1172   - const GModelSpatialRadial* model,
1173   - const GCTAPointing& pnt,
1174   - const GEnergy& srcEng,
1175   - const double& srcLogEng,
1176   - const GTime& srcTime,
1177   - const double& obsOffset,
1178   - const GEnergy& obsEng,
1179   - const double& zeta,
1180   - const double& phi0,
1181   - const double& theta_max,
1182   - const int& iter) :
1183   - m_rsp(rsp),
1184   - m_model(model),
1185   - m_pnt(pnt),
1186   - m_srcEng(srcEng),
1187   - m_srcLogEng(srcLogEng),
1188   - m_srcTime(srcTime),
1189   - m_obsOffset(obsOffset),
1190   - m_sin_obs_offset(std::sin(obsOffset)),
1191   - m_cos_obs_offset(std::cos(obsOffset)),
1192   - m_obsEng(obsEng),
1193   - m_zeta(zeta),
1194   - m_phi0(phi0),
1195   - m_sin_zeta(std::sin(zeta)),
1196   - m_cos_zeta(std::cos(zeta)),
1197   - m_theta_max(theta_max),
1198   - m_cos_theta_max(std::cos(theta_max)),
1199   - m_iter(iter) { }
  1298 + cta_psf_diffuse_kern_delta(const GCTAResponseCube* rsp,
  1299 + const GModelSpatial* model,
  1300 + const GSkyDir& srcDir,
  1301 + const GEnergy& srcEng,
  1302 + const GTime& srcTime,
  1303 + const GMatrix& rot,
  1304 + const double& eps,
  1305 + const int& order) :
  1306 + m_rsp(rsp),
  1307 + m_model(model),
  1308 + m_srcDir(srcDir),
  1309 + m_srcEng(srcEng),
  1310 + m_srcTime(srcTime),
  1311 + m_rot(rot),
  1312 + m_eps(eps),
  1313 + m_order (order),
  1314 + m_psf_max(rsp->psf()(srcDir, 0.0, srcEng)) { }
1200 1315 double eval(const double& delta);
1201 1316 protected:
1202   - const GCTAResponseIrf* m_rsp; //!< Response
1203   - const GModelSpatialRadial* m_model; //!< Radial model
1204   - const GCTAPointing& m_pnt; //!< CTA pointing
1205   - const GEnergy& m_srcEng; //!< True photon energy
1206   - const double& m_srcLogEng; //!< Log10(true photon energy)
1207   - const GTime& m_srcTime; //!< True photon arrival time
1208   - const double& m_obsOffset; //!< Observed photon offset angle
1209   - const double m_sin_obs_offset;
1210   - const double m_cos_obs_offset;
1211   - const GEnergy& m_obsEng; //!< Measured event energy
1212   - const double& m_zeta; //!< zeta
1213   - const double& m_phi0; //!< phi0
1214   - const double m_sin_zeta; //!< sin(zeta)
1215   - const double m_cos_zeta; //!< cos(zeta)
1216   - const double& m_theta_max; //!< Model radius
1217   - const double m_cos_theta_max;
1218   - const int& m_iter; //!< Integration iterations
  1317 + const GCTAResponseCube* m_rsp; //!< Response cube
  1318 + const GModelSpatial* m_model; //!< Spatial model
  1319 + const GSkyDir& m_srcDir; //!< True photon arrival direction
  1320 + const GEnergy& m_srcEng; //!< True photon energy
  1321 + const GTime& m_srcTime; //!< True photon arrival time
  1322 + const GMatrix& m_rot; //!< Rotation matrix
  1323 + const double& m_eps; //!< Integration precision
  1324 + const int& m_order; //!< Romberg order
  1325 + double m_psf_max; //!< Maximum PSF value
1219 1326 };
1220 1327  
1221 1328  
1222 1329 /***********************************************************************//**
1223   - * @class cta_irf_radial_kern_phi
1224   - *
1225   - * @brief Test kernel for Irf phi angle integration
  1330 + * @class cta_psf_diffuse_kern_phi
1226 1331 *
1227   - * This class is used for Psf system based integration. It is only here for
1228   - * testing and this class may be removed in the future.
  1332 + * @brief Kernel for Psf phi angle integration used for stacked analysis
1229 1333 ***************************************************************************/
1230   -class cta_irf_radial_kern_phi : public GFunction {
  1334 +class cta_psf_diffuse_kern_phi : public GFunction {
1231 1335 public:
1232   - cta_irf_radial_kern_phi(const GCTAResponseIrf* rsp,
1233   - const GModelSpatialRadial* model,
1234   - const GCTAPointing& pnt,
1235   - const GEnergy& srcEng,
1236   - const double& srcLogEng,
1237   - const GTime& srcTime,
1238   - const GEnergy& obsEng,
1239   - const double& phi0,
1240   - const double& sin_fact_model,
1241   - const double& cos_fact_model,
1242   - const double& sin_fact_inst,
1243   - const double& cos_fact_inst) :
1244   - m_rsp(rsp),
1245   - m_model(model),
1246   - m_pnt(pnt),
1247   - m_srcEng(srcEng),
1248   - m_srcLogEng(srcLogEng),
1249   - m_srcTime(srcTime),
1250   - m_obsEng(obsEng),
1251   - m_phi0(phi0),
1252   - m_sin_fact_model(sin_fact_model),
1253   - m_cos_fact_model(cos_fact_model),
1254   - m_sin_fact_inst(sin_fact_inst),
1255   - m_cos_fact_inst(cos_fact_inst) { }
  1336 + cta_psf_diffuse_kern_phi(const GModelSpatial* model,
  1337 + const GEnergy& srcEng,
  1338 + const GTime& srcTime,
  1339 + const GMatrix& rot,
  1340 + const double& sin_delta,
  1341 + const double& cos_delta) :
  1342 + m_model(model),
  1343 + m_srcEng(srcEng),
  1344 + m_srcTime(srcTime),
  1345 + m_rot(rot),
  1346 + m_sin_delta(sin_delta),
  1347 + m_cos_delta(cos_delta) { }
1256 1348 double eval(const double& phi);
1257 1349 protected:
1258   - const GCTAResponseIrf* m_rsp; //!< Response
1259   - const GModelSpatialRadial* m_model; //!< Radial model
1260   - const GCTAPointing& m_pnt; //!< CTA pointing
1261   - const GEnergy& m_srcEng; //!< True photon energy
1262   - const double& m_srcLogEng; //!< Log10(true photon energy)
1263   - const GTime& m_srcTime; //!< True photon arrival time
1264   - const GEnergy& m_obsEng; //!< Measured event energy
1265   - const double& m_phi0; //!< phi0
1266   - const double& m_sin_fact_model; //!< sin(delta)*sin(zeta)
1267   - const double& m_cos_fact_model; //!< cos(delta)*cos(zeta)
1268   - const double& m_sin_fact_inst; //!< sin(delta)*sin(obsOffset)
1269   - const double& m_cos_fact_inst; //!< cos(delta)*cos(obsOffset)
  1350 + const GModelSpatial* m_model; //!< Spatial model
  1351 + const GEnergy& m_srcEng; //!< True photon energy
  1352 + const GTime& m_srcTime; //!< True photon arrival time
  1353 + const GMatrix& m_rot; //!< Rotation matrix
  1354 + const double& m_sin_delta; //!< sin(delta)
  1355 + const double& m_cos_delta; //!< cos(delta)
1270 1356 };
1271 1357  
1272 1358 #endif /* GCTARESPONSE_HELPERS_HPP */
... ...
inst/cta/test/benchmark_ml_fitting.py
... ... @@ -59,7 +59,10 @@ def unbinned_analysis(model, evtfile, irf, caldb):
59 59 obs.models(model)
60 60  
61 61 # Allocate Levenberg-Marquardt optimizer
62   - opt = gammalib.GOptimizerLM()
  62 + log = gammalib.GLog()
  63 + log.cout(True)
  64 + opt = gammalib.GOptimizerLM(log)
  65 + opt.eps(5.0e-3)
63 66  
64 67 # Optimize model parameters
65 68 obs.optimize(opt)
... ... @@ -113,7 +116,10 @@ def binned_analysis(model, cntmap, irf, caldb):
113 116 obs.models(model)
114 117  
115 118 # Allocate Levenberg-Marquardt optimizer
116   - opt = gammalib.GOptimizerLM()
  119 + log = gammalib.GLog()
  120 + log.cout(True)
  121 + opt = gammalib.GOptimizerLM(log)
  122 + opt.eps(5.0e-3)
117 123  
118 124 # Optimize model parameters
119 125 obs.optimize(opt)
... ... @@ -169,7 +175,10 @@ def stacked_analysis(model, cntmap, expcube, psfcube):
169 175 obs.models(model)
170 176  
171 177 # Allocate Levenberg-Marquardt optimizer
172   - opt = gammalib.GOptimizerLM()
  178 + log = gammalib.GLog()
  179 + log.cout(True)
  180 + opt = gammalib.GOptimizerLM(log)
  181 + opt.eps(5.0e-3)
173 182  
174 183 # Optimize model parameters
175 184 obs.optimize(opt)
... ...
inst/cta/test/test_irf_elliptical.py 0 → 100755
  1 +#! /usr/bin/env python
  2 +# ==========================================================================
  3 +# This script tests the elliptical response computation. The script requires
  4 +# matplotlib to be installed.
  5 +#
  6 +# --------------------------------------------------------------------------
  7 +#
  8 +# Copyright (C) 2014 Juergen Knoedlseder
  9 +#
  10 +# This program is free software: you can redistribute it and/or modify
  11 +# it under the terms of the GNU General Public License as published by
  12 +# the Free Software Foundation, either version 3 of the License, or
  13 +# (at your option) any later version.
  14 +#
  15 +# This program is distributed in the hope that it will be useful,
  16 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
  17 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18 +# GNU General Public License for more details.
  19 +#
  20 +# You should have received a copy of the GNU General Public License
  21 +# along with this program. If not, see <http://www.gnu.org/licenses/>.
  22 +#
  23 +# ==========================================================================
  24 +import gammalib
  25 +import math
  26 +
  27 +
  28 +# ================= #
  29 +# Compute arclength #
  30 +# ================= #
  31 +def cta_roi_arclength(rad, dist, roi):
  32 + """
  33 + Returns length of circular arc within circular ROI
  34 +
  35 + Parameters:
  36 + rad - Circle radius in radians (<pi)
  37 + dist - Circle centre distance to ROI centre (<pi)
  38 + roi - Radius of ROI in radians
  39 + """
  40 + # Initialise
  41 + arclength = 0.0
  42 +
  43 + # Handle special case that circle centre matches ROI centre
  44 + if dist == 0.0:
  45 + if rad > roi:
  46 + arclength = 0.0
  47 + else:
  48 + arclength = 2.0 * math.pi
  49 +
  50 + # ... otherwise circle and ROI centres are not identical
  51 + else:
  52 +
  53 + # Handle special case that we evaluate exactly at the circle
  54 + # centre. In this case we have in fact a point, and if this point
  55 + # falls within the ROI it has a formal arclength of 2pi.
  56 + if rad == 0.0:
  57 + if dist > roi:
  58 + arclength = 0.0
  59 + else:
  60 + arclength = 2.0 * math.pi
  61 +
  62 + # ... otherwise we have to handle the general case
  63 + else:
  64 + d = roi - dist
  65 + if -rad >= d:
  66 + arclength = 0.0
  67 + elif rad <= d:
  68 + arclength = 2.0 * math.pi
  69 + else:
  70 + cosrad = math.cos(rad)
  71 + sinrad = math.sin(rad)
  72 + cosroi = math.cos(roi)
  73 + cosdist = math.cos(dist)
  74 + sindist = math.sin(dist)
  75 + cosang = (cosroi - cosdist * cosrad) / (sindist * sinrad)
  76 + arclength = 2.0 * gammalib.acos(cosang)
  77 +
  78 + # Return
  79 + return arclength
  80 +
  81 +# ============== #
  82 +# Limit interval #
  83 +# ============== #
  84 +def limit_omega(min, max, domega, log=False):
  85 + """
  86 + """
  87 + # Compute only if domega < pi
  88 + if (domega < gammalib.pi):
  89 +
  90 + # Save initial interval
  91 + min_initial = min
  92 + max_initial = max
  93 +
  94 + # Initialise intervals
  95 + intervals = []
  96 +
  97 + # Limit each of the intervals to [omega_min, omega_max]
  98 + omega_min = -domega
  99 + omega_max = +domega
  100 + omega_min_plus = omega_min + gammalib.twopi
  101 + omega_max_plus = omega_max + gammalib.twopi
  102 + omega_min_minus = omega_min - gammalib.twopi
  103 + omega_max_minus = omega_max - gammalib.twopi
  104 +
  105 + # Check for overlaps
  106 + in_unshifted = False
  107 + in_plus = False
  108 + in_minus = False
  109 + if (max_initial > omega_min and min_initial < omega_max):
  110 + min = min_initial
  111 + max = max_initial
  112 + in_unshifted = True
  113 + if (min < omega_min):
  114 + min = omega_min
  115 + if (max > omega_max):
  116 + max = omega_max
  117 + intervals.append({'min': min, 'max': max})
  118 + if (max_initial > omega_min_plus and min_initial < omega_max_plus):
  119 + min = min_initial
  120 + max = max_initial
  121 + in_plus = True
  122 + if (min < omega_min_plus):
  123 + min = omega_min_plus
  124 + if (max > omega_max_plus):
  125 + max = omega_max_plus
  126 + intervals.append({'min': min, 'max': max})
  127 + if (max_initial > omega_min_minus and min_initial < omega_max_minus):
  128 + min = min_initial
  129 + max = max_initial
  130 + in_minus = True
  131 + if (min < omega_min_minus):
  132 + min = omega_min_minus
  133 + if (max > omega_max_minus):
  134 + max = omega_max_minus
  135 + intervals.append({'min': min, 'max': max})
  136 +
  137 + # Debug
  138 + if log:
  139 + print("[%7.2f,%7.2f] => [%7.2f,%7.2f] (%i[%7.2f,%7.2f] %i[%7.2f,%7.2f] %i[%7.2f,%7.2f])" %
  140 + (min_initial*gammalib.rad2deg, max_initial*gammalib.rad2deg, \
  141 + min*gammalib.rad2deg, max*gammalib.rad2deg, \
  142 + in_unshifted, \
  143 + omega_min*gammalib.rad2deg, omega_max*gammalib.rad2deg, \
  144 + in_plus, \
  145 + omega_min_plus*gammalib.rad2deg, omega_max_plus*gammalib.rad2deg, \
  146 + in_minus, \
  147 + omega_min_minus*gammalib.rad2deg, omega_max_minus*gammalib.rad2deg))
  148 +
  149 + # ... just copy over the input interval
  150 + else:
  151 + intervals = [{'min': min, 'max': max}]
  152 +
  153 + # Return
  154 + return intervals
  155 +
  156 +
  157 +# ======================= #
  158 +# Show ellptical response #
  159 +# ======================= #
  160 +def show_response(centre, semimajor, semiminor, posang, obsDir, pntDir, delta_max, log=False):
  161 + """
  162 + Show ellptical response
  163 + """
  164 + # Create figure
  165 + plt.figure(1)
  166 + plt.title("Elliptical response")
  167 +
  168 + # Get ellipse boundary (used for intersection computation)
  169 + if (semimajor >= semiminor):
  170 + bound_major = semimajor
  171 + bound_minor = semiminor
  172 + bound_pa = posang
  173 + else:
  174 + bound_major = semiminor
  175 + bound_minor = semimajor
  176 + bound_pa = posang + 90.0
  177 +
  178 + # Compute some stuff
  179 + cos_pa = math.cos(posang * gammalib.deg2rad)
  180 + sin_pa = math.sin(posang * gammalib.deg2rad)
  181 + rho_obs = centre.dist_deg(obsDir)
  182 + posangle_obs = centre.posang(obsDir)
  183 + rho_pnt = centre.dist(pntDir)
  184 + posangle_pnt = centre.posang(pntDir)
  185 + omega_pnt = posangle_pnt - posangle_obs
  186 + src_max = semimajor
  187 +
  188 + # Rho limits
  189 + rho_min = 0.0
  190 + rho_max = rho_obs + delta_max
  191 + if (rho_obs > delta_max):
  192 + rho_min = rho_obs - delta_max
  193 + if rho_max > src_max:
  194 + rho_max = src_max
  195 +
  196 + # Convert to radians
  197 + delta_max *= gammalib.deg2rad
  198 + rho_obs *= gammalib.deg2rad
  199 + src_max *= gammalib.deg2rad
  200 +
  201 + # Setup rhos
  202 + rhos = []
  203 + for i in range(51):
  204 + rho = i*0.02
  205 + if (rho >= rho_min and rho <= rho_max):
  206 + rhos.append(rho)
  207 +
  208 + # Setup ellipse as vector
  209 + x = []
  210 + y = []
  211 + for angle in range(0, 360):
  212 + radians = angle * gammalib.deg2rad
  213 + cos_radians = math.cos(radians)
  214 + sin_radians = math.sin(radians)
  215 + x.append(semimajor * cos_radians * sin_pa - semiminor * sin_radians * cos_pa)
  216 + y.append(semimajor * cos_radians * cos_pa + semiminor * sin_radians * sin_pa)
  217 +
  218 + # Plot ellipse
  219 + plt.plot(x, y, 'r-')
  220 + plt.xlim([ 1.5*bound_major,-1.5*bound_major])
  221 + plt.ylim([-1.5*bound_major, 1.5*bound_major])
  222 + plt.plot([0.0], [0.0], 'ro')
  223 +
  224 + # Plot observed direction
  225 + x_obs = obsDir.ra_deg() - centre.ra_deg()
  226 + y_obs = obsDir.dec_deg() - centre.dec_deg()
  227 + plt.plot([x_obs], [y_obs], 'bo')
  228 +
  229 + # Draw connecting line model-photon'
  230 + n = 10
  231 + step = rho_obs/n * gammalib.rad2deg
  232 + x = []
  233 + y = []
  234 + for i in range(n+1):
  235 + x.append(i*step * math.sin(posangle_obs))
  236 + y.append(i*step * math.cos(posangle_obs))
  237 + plt.plot(x, y, 'b-')
  238 +
  239 + # Plot Psf validity circle
  240 + x = []
  241 + y = []
  242 + for angle in range(0, 360):
  243 + radians = angle * gammalib.deg2rad
  244 + cos_radians = math.cos(radians)
  245 + sin_radians = math.sin(radians)
  246 + x.append(delta_max * cos_radians * gammalib.rad2deg + x_obs)
  247 + y.append(delta_max * sin_radians * gammalib.rad2deg + y_obs)
  248 + plt.plot(x, y, 'b-')
  249 +
  250 + # Plot pointing direction
  251 + x = pntDir.ra_deg() - centre.ra_deg()
  252 + y = pntDir.dec_deg() - centre.dec_deg()
  253 + plt.plot([x], [y], 'go')
  254 +
  255 + # Print some stuff
  256 + print("Distance model-event (rho_obs) ..........: %.2f deg" % (rho_obs*gammalib.rad2deg))
  257 + print("PA of event from model (posangle_obs) ...: %.2f deg" % (posangle_obs*gammalib.rad2deg))
  258 + print("Distance model-pointing (rho_pnt) .......: %.2f deg" % (rho_pnt*gammalib.rad2deg))
  259 + print("PA of pointing from model (posangle_pnt) : %.2f deg" % (posangle_pnt*gammalib.rad2deg))
  260 + print("Azimuth of pointing (omega_pnt) .........: %.2f deg" % (omega_pnt*gammalib.rad2deg))
  261 + print("Rho interval ............................: %.2f-%.2f deg" % (rho_min*gammalib.rad2deg,rho_max*gammalib.rad2deg))
  262 +
  263 + # Plot rhos
  264 + for rho in rhos:
  265 +
  266 + # Print header
  267 + if log:
  268 + print("Rho ..................................: %.2f deg" % (rho))
  269 +
  270 + # Compute some stuff
  271 + rho_rad = rho * gammalib.deg2rad
  272 +
  273 + # Compute half length of arc that lies within Psf validity circle
  274 + domega = 0.5 * cta_roi_arclength(rho_rad, rho_obs, delta_max)
  275 +
  276 + # Print domega
  277 + if log:
  278 + print(" domega .............................: %.2f deg" % (domega*gammalib.rad2deg))
  279 +
  280 + # Continue only if arc length is positive
  281 + if (domega > 0.0):
  282 +
  283 + # Precompute cosine and sine terms for azimuthal integration
  284 + cos_rho = math.cos(rho_rad);
  285 + sin_rho = math.sin(rho_rad);
  286 + cos_psf = cos_rho * math.cos(rho_obs)
  287 + sin_psf = sin_rho * math.sin(rho_obs)
  288 + cos_ph = cos_rho * math.cos(rho_pnt)
  289 + sin_ph = sin_rho * math.sin(rho_pnt)
  290 +
  291 + # Full containment within ellipse
  292 + if (rho < bound_minor):
  293 +
  294 + # Set Omega range
  295 + omega_min = -domega
  296 + omega_max = +domega
  297 +
  298 + # Print
  299 + if log:
  300 + print(" Omega range ........................: %.2f - %.2f deg" % (omega_min*gammalib.rad2deg,omega_max*gammalib.rad2deg))
  301 +
  302 + # Plot segment
  303 + n = 100
  304 + x = []
  305 + y = []
  306 + step = (omega_max - omega_min)/n
  307 + for i in range(n+1):
  308 + radians = i*step + omega_min
  309 + cos_radians = math.sin(radians+posangle_obs)
  310 + sin_radians = math.cos(radians+posangle_obs)
  311 + x.append(rho * cos_radians)
  312 + y.append(rho * sin_radians)
  313 + plt.plot(x, y, 'k-')
  314 +
  315 + # Partial containment within ellipse
  316 + else:
  317 +
  318 + # Azimuth angle of intersection points between ellipse and
  319 + # circle with radius of rho
  320 + arg1 = 1.0 - (bound_minor*bound_minor) / (rho*rho)
  321 + arg2 = 1.0 - (bound_minor*bound_minor) / (bound_major*bound_major)
  322 + omega_width = math.acos(math.sqrt(arg1/arg2))
  323 +
  324 + # Print Omega width
  325 + if log:
  326 + print(" omega_width ........................: %.2f deg" % (omega_width*gammalib.rad2deg))
  327 +
  328 + #
  329 + if (omega_width > 0.0):
  330 +
  331 + # Compute Omega intervals. Recall that Omega is with respect
  332 + # to connecting line between model and observed photon. Make
  333 + # sure that omega_0 is within [-pi,pi] thus that the omega
  334 + # intervals are between [-2pi,2pi]
  335 + omega_0 = bound_pa * gammalib.deg2rad - posangle_obs
  336 + if (omega_0 > gammalib.pi):
  337 + omega_0 -= gammalib.pi
  338 + elif (omega_0 < -gammalib.pi):
  339 + omega_0 += gammalib.pi
  340 +
  341 + # Compute intervals
  342 + omega1_min = omega_0 - omega_width
  343 + omega1_max = omega_0 + omega_width
  344 + omega2_min = omega1_min + gammalib.pi
  345 + omega2_max = omega1_max + gammalib.pi
  346 +
  347 + # Logging
  348 + if log:
  349 + print(" omega_0 ............................: %.2f deg" % (omega_0*gammalib.rad2deg))
  350 + print(" Omega range 1 ......................: %.2f - %.2f deg" % (omega1_min*gammalib.rad2deg,omega1_max*gammalib.rad2deg))
  351 + print(" Omega range 2 ......................: %.2f - %.2f deg" % (omega2_min*gammalib.rad2deg,omega2_max*gammalib.rad2deg))
  352 +
  353 + # Limit intervals
  354 + intervals = limit_omega(omega1_min, omega1_max, domega)
  355 + intervals2 = limit_omega(omega2_min, omega2_max, domega)
  356 + intervals.extend(intervals2)
  357 +
  358 + # Print results
  359 + if log:
  360 + for interval in intervals:
  361 + print(" Omega interval .....................: %.2f - %.2f deg" % (interval['min']*gammalib.rad2deg,interval['max']*gammalib.rad2deg))
  362 +
  363 + # Draw Omega range lines and circle segments
  364 + for interval in intervals:
  365 + omega_min = interval['min']
  366 + omega_max = interval['max']
  367 + if (omega_min < omega_max):
  368 + n = 100
  369 + x = []
  370 + y = []
  371 + step = (omega_max - omega_min)/n
  372 + for i in range(n+1):
  373 + radians = i*step + omega_min
  374 + cos_radians = math.sin(radians+posangle_obs)
  375 + sin_radians = math.cos(radians+posangle_obs)
  376 + x.append(rho * cos_radians)
  377 + y.append(rho * sin_radians)
  378 + plt.plot(x, y, 'k-')
  379 +
  380 +
  381 + # Show plot
  382 + plt.show()
  383 +
  384 + # Return
  385 + return
  386 +
  387 +
  388 +# ======================== #
  389 +# Main routine entry point #
  390 +# ======================== #
  391 +if __name__ == '__main__':
  392 + """
  393 + Test elliptical response computation
  394 + """
  395 + # Dump header
  396 + print("")
  397 + print("****************************************")
  398 + print("* Test elliptical response computation *")
  399 + print("****************************************")
  400 +
  401 + # Check if matplotlib is available
  402 + try:
  403 + import matplotlib.pyplot as plt
  404 + except ImportError:
  405 + print("Matplotlib is not installed on your system. Abort.")
  406 + sys.exit()
  407 +
  408 + # Set Psf size
  409 + delta_max = 0.5
  410 +
  411 + # Set model parameters
  412 + centre = gammalib.GSkyDir()
  413 + centre.radec_deg(0.0, 0.0)
  414 + semimajor = 1.0
  415 + semiminor = 0.50
  416 + #semimajor = 0.5
  417 + #semiminor = 1.0
  418 + posang = 30.0
  419 +
  420 + # Set observed photon direction
  421 + obsDir = gammalib.GSkyDir()
  422 + obsDir.radec_deg(0.4, -0.3)
  423 + #obsDir.radec_deg(-0.4, -0.3)
  424 + #obsDir.radec_deg(0.4, 0.4)
  425 +
  426 + # Set pointing direction
  427 + pntDir = gammalib.GSkyDir()
  428 + pntDir.radec_deg(0.1, -1.0)
  429 +
  430 + # Show elliptical response geometry
  431 + show_response(centre, semimajor, semiminor, posang, obsDir, pntDir, delta_max)
  432 +
... ...
inst/cta/test/test_irf_radial.py 0 → 100755
  1 +#! /usr/bin/env python
  2 +# ==========================================================================
  3 +# This script tests the radial response computation. The script requires
  4 +# matplotlib to be installed.
  5 +#
  6 +# --------------------------------------------------------------------------
  7 +#
  8 +# Copyright (C) 2014 Juergen Knoedlseder
  9 +#
  10 +# This program is free software: you can redistribute it and/or modify
  11 +# it under the terms of the GNU General Public License as published by
  12 +# the Free Software Foundation, either version 3 of the License, or
  13 +# (at your option) any later version.
  14 +#
  15 +# This program is distributed in the hope that it will be useful,
  16 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
  17 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18 +# GNU General Public License for more details.
  19 +#
  20 +# You should have received a copy of the GNU General Public License
  21 +# along with this program. If not, see <http://www.gnu.org/licenses/>.
  22 +#
  23 +# ==========================================================================
  24 +import gammalib
  25 +import math
  26 +
  27 +
  28 +# ================= #
  29 +# Compute arclength #
  30 +# ================= #
  31 +def cta_roi_arclength(rad, dist, roi):
  32 + """
  33 + Returns length of circular arc within circular ROI
  34 +
  35 + Parameters:
  36 + rad - Circle radius in radians (<pi)
  37 + dist - Circle centre distance to ROI centre (<pi)
  38 + roi - Radius of ROI in radians
  39 + """
  40 + # Initialise
  41 + arclength = 0.0
  42 +
  43 + # Handle special case that circle centre matches ROI centre
  44 + if dist == 0.0:
  45 + if rad > roi:
  46 + arclength = 0.0
  47 + else:
  48 + arclength = 2.0 * math.pi
  49 +
  50 + # ... otherwise circle and ROI centres are not identical
  51 + else:
  52 +
  53 + # Handle special case that we evaluate exactly at the circle
  54 + # centre. In this case we have in fact a point, and if this point
  55 + # falls within the ROI it has a formal arclength of 2pi.
  56 + if rad == 0.0:
  57 + if dist > roi:
  58 + arclength = 0.0
  59 + else:
  60 + arclength = 2.0 * math.pi
  61 +
  62 + # ... otherwise we have to handle the general case
  63 + else:
  64 + d = roi - dist
  65 + if -rad >= d:
  66 + arclength = 0.0
  67 + elif rad <= d:
  68 + arclength = 2.0 * math.pi
  69 + else:
  70 + cosrad = math.cos(rad)
  71 + sinrad = math.sin(rad)
  72 + cosroi = math.cos(roi)
  73 + cosdist = math.cos(dist)
  74 + sindist = math.sin(dist)
  75 + cosang = (cosroi - cosdist * cosrad) / (sindist * sinrad)
  76 + arclength = 2.0 * gammalib.acos(cosang)
  77 +
  78 + # Return
  79 + return arclength
  80 +
  81 +
  82 +# ==================== #
  83 +# Show radial response #
  84 +# ==================== #
  85 +def show_response(centre, radius, obsDir, delta_max):
  86 + """
  87 + Show radial response
  88 + """
  89 + # Create figure
  90 + plt.figure(1)
  91 + plt.title("Radial response")
  92 +
  93 + # Compute some stuff
  94 + rho_obs = centre.dist_deg(obsDir)
  95 + posangle_obs = centre.posang(obsDir)
  96 +
  97 + # Rho limits
  98 + rho_min = 0.0
  99 + rho_max = rho_obs + delta_max
  100 + if (rho_obs > delta_max):
  101 + rho_min = rho_obs - delta_max
  102 + if rho_max > radius:
  103 + rho_max = radius
  104 +
  105 + # Convert to radians
  106 + delta_max *= gammalib.deg2rad
  107 + rho_obs *= gammalib.deg2rad
  108 +
  109 + # Setup rhos
  110 + rhos = []
  111 + for i in range(51):
  112 + rho = i*0.02
  113 + if (rho >= rho_min and rho <= rho_max):
  114 + rhos.append(rho)
  115 +
  116 + # Plot radial model
  117 + x = []
  118 + y = []
  119 + for angle in range(0, 361):
  120 + radians = angle * gammalib.deg2rad
  121 + cos_radians = math.cos(radians)
  122 + sin_radians = math.sin(radians)
  123 + x.append(radius * cos_radians)
  124 + y.append(radius * sin_radians)
  125 + plt.plot(x, y, 'r-')
  126 + plt.xlim([ 2.0*radius,-2.0*radius])
  127 + plt.ylim([-2.0*radius, 2.0*radius])
  128 + plt.plot([0.0], [0.0], 'ro')
  129 +
  130 + # Plot observed direction
  131 + x_obs = obsDir.ra_deg() - centre.ra_deg()
  132 + y_obs = obsDir.dec_deg() - centre.dec_deg()
  133 + plt.plot([x_obs], [y_obs], 'bo')
  134 +
  135 + # Draw connecting line model-photon'
  136 + n = 10
  137 + step = rho_obs/n * gammalib.rad2deg
  138 + x = []
  139 + y = []
  140 + for i in range(n+1):
  141 + x.append(i*step * math.sin(posangle_obs))
  142 + y.append(i*step * math.cos(posangle_obs))
  143 + plt.plot(x, y, 'b-')
  144 +
  145 + # Plot Psf validity circle
  146 + x = []
  147 + y = []
  148 + for angle in range(0, 361):
  149 + radians = angle * gammalib.deg2rad
  150 + cos_radians = math.cos(radians)
  151 + sin_radians = math.sin(radians)
  152 + x.append(delta_max * cos_radians * gammalib.rad2deg + x_obs)
  153 + y.append(delta_max * sin_radians * gammalib.rad2deg + y_obs)
  154 + plt.plot(x, y, 'b-')
  155 +
  156 + # Plot rhos
  157 + for rho in rhos:
  158 +
  159 + # Compute some stuff
  160 + rho_rad = rho * gammalib.deg2rad
  161 +
  162 + # Compute half length of arc that lies within Psf validity circle
  163 + domega = 0.5 * cta_roi_arclength(rho_rad, rho_obs, delta_max)
  164 +
  165 + # Continue only if arc length is positive
  166 + if (domega > 0.0):
  167 +
  168 + # Set Omega range
  169 + omega_min = -domega
  170 + omega_max = +domega
  171 +
  172 + # Plot segment
  173 + n = 100
  174 + x = []
  175 + y = []
  176 + step = (omega_max - omega_min)/n
  177 + for i in range(n+1):
  178 + radians = i*step + omega_min
  179 + cos_radians = math.sin(radians+posangle_obs)
  180 + sin_radians = math.cos(radians+posangle_obs)
  181 + x.append(rho * cos_radians)
  182 + y.append(rho * sin_radians)
  183 + plt.plot(x, y, 'k-')
  184 +
  185 + # Show plot
  186 + plt.show()
  187 +
  188 + # Return
  189 + return
  190 +
  191 +
  192 +# ======================== #
  193 +# Main routine entry point #
  194 +# ======================== #
  195 +if __name__ == '__main__':
  196 + """
  197 + Test radial response computation
  198 + """
  199 + # Dump header
  200 + print("")
  201 + print("************************************")
  202 + print("* Test radial response computation *")
  203 + print("************************************")
  204 +
  205 + # Check if matplotlib is available
  206 + try:
  207 + import matplotlib.pyplot as plt
  208 + except ImportError:
  209 + print("Matplotlib is not installed on your system. Abort.")
  210 + sys.exit()
  211 +
  212 + # Set Psf size
  213 + delta_max = 0.8
  214 +
  215 + # Set model parameters
  216 + centre = gammalib.GSkyDir()
  217 + centre.radec_deg(0.0, 0.0)
  218 + radius = 1.0
  219 +
  220 + # Set observed photon direction
  221 + obsDir = gammalib.GSkyDir()
  222 + #obsDir.radec_deg(0.4, -0.3)
  223 + #obsDir.radec_deg(-0.4, -0.3)
  224 + #obsDir.radec_deg(0.4, 0.4)
  225 + obsDir.radec_deg(0.0, 0.3)
  226 +
  227 + # Show elliptical response geometry
  228 + show_response(centre, radius, obsDir, delta_max)
  229 +
... ...
inst/cta/test/test_likelihood_profile.py
... ... @@ -82,7 +82,7 @@ def show_likelihood_profile(result):
82 82 # ========================== #
83 83 # Compute likelihood profile #
84 84 # ========================== #
85   -def compute_likelihood_profile(obs, parname="RA", scale=1.0e-4, steps=10):
  85 +def compute_likelihood_profile(obs, parname="Radius", scale=1.0e-5, steps=10):
86 86 """
87 87 Compute the log-likelihood profile for a given parameter.
88 88  
... ... @@ -93,12 +93,12 @@ def compute_likelihood_profile(obs, parname=&quot;RA&quot;, scale=1.0e-4, steps=10):
93 93 """
94 94 # Free parameter for which profile is to be determined
95 95 obs.models()[0][parname].free()
96   - obs.models()[0]["DEC"].free()
97 96  
98 97 # Allocate Levenberg-Marquardt optimizer
99 98 log = gammalib.GLog()
100 99 log.cout(True)
101 100 opt = gammalib.GOptimizerLM(log)
  101 + opt.eps(5.0e-3)
102 102  
103 103 # Optimize model parameters
104 104 obs.optimize(opt)
... ... @@ -256,8 +256,8 @@ if __name__ == &#39;__main__&#39;:
256 256  
257 257 # Set test
258 258 #test = "point"
259   - test = "gauss"
260   - #test = "disk"
  259 + #test = "gauss"
  260 + test = "disk"
261 261 #test = "ldisk"
262 262 #test = "shell"
263 263 #test = "ellipse"
... ... @@ -309,10 +309,10 @@ if __name__ == &#39;__main__&#39;:
309 309 cntref = 337.5 # +/- 1.8 (from ctobssim)
310 310  
311 311 # Perform unbinned computation
312   - original_likelihood_profile(model, events, irf, gammalib.GCaldb(caldb), cntref)
  312 + #original_likelihood_profile(model, events, irf, gammalib.GCaldb(caldb), cntref)
313 313  
314 314 # Perform binned computation
315 315 #original_likelihood_profile(model, cntmap, irf, gammalib.GCaldb(caldb), cntref)
316 316  
317 317 # Perform stacked computation
318   - #stacked_likelihood_profile(model, cntmap, expcube, psfcube, cntref)
  318 + stacked_likelihood_profile(model, cntmap, expcube, psfcube, cntref)
... ...
inst/cta/test/test_npred_elliptical.py 0 → 100755
  1 +#! /usr/bin/env python
  2 +# ==========================================================================
  3 +# This script tests the elliptical Npred computation. The script requires
  4 +# matplotlib to be installed.
  5 +#
  6 +# --------------------------------------------------------------------------
  7 +#
  8 +# Copyright (C) 2014 Juergen Knoedlseder
  9 +#
  10 +# This program is free software: you can redistribute it and/or modify
  11 +# it under the terms of the GNU General Public License as published by
  12 +# the Free Software Foundation, either version 3 of the License, or
  13 +# (at your option) any later version.
  14 +#
  15 +# This program is distributed in the hope that it will be useful,
  16 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
  17 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18 +# GNU General Public License for more details.
  19 +#
  20 +# You should have received a copy of the GNU General Public License
  21 +# along with this program. If not, see <http://www.gnu.org/licenses/>.
  22 +#
  23 +# ==========================================================================
  24 +import gammalib
  25 +import math
  26 +
  27 +
  28 +# ================= #
  29 +# Compute arclength #
  30 +# ================= #
  31 +def cta_roi_arclength(rad, dist, roi):
  32 + """
  33 + Returns length of circular arc within circular ROI
  34 +
  35 + Parameters:
  36 + rad - Circle radius in radians (<pi)
  37 + dist - Circle centre distance to ROI centre (<pi)
  38 + roi - Radius of ROI in radians
  39 + """
  40 + # Initialise
  41 + arclength = 0.0
  42 +
  43 + # Handle special case that circle centre matches ROI centre
  44 + if dist == 0.0:
  45 + if rad > roi:
  46 + arclength = 0.0
  47 + else:
  48 + arclength = 2.0 * math.pi
  49 +
  50 + # ... otherwise circle and ROI centres are not identical
  51 + else:
  52 +
  53 + # Handle special case that we evaluate exactly at the circle
  54 + # centre. In this case we have in fact a point, and if this point
  55 + # falls within the ROI it has a formal arclength of 2pi.
  56 + if rad == 0.0:
  57 + if dist > roi:
  58 + arclength = 0.0
  59 + else:
  60 + arclength = 2.0 * math.pi
  61 +
  62 + # ... otherwise we have to handle the general case
  63 + else:
  64 + d = roi - dist
  65 + if -rad >= d:
  66 + arclength = 0.0
  67 + elif rad <= d:
  68 + arclength = 2.0 * math.pi
  69 + else:
  70 + cosrad = math.cos(rad)
  71 + sinrad = math.sin(rad)
  72 + cosroi = math.cos(roi)
  73 + cosdist = math.cos(dist)
  74 + sindist = math.sin(dist)
  75 + cosang = (cosroi - cosdist * cosrad) / (sindist * sinrad)
  76 + arclength = 2.0 * gammalib.acos(cosang)
  77 +
  78 + # Return
  79 + return arclength
  80 +
  81 +
  82 +# ============== #
  83 +# Limit interval #
  84 +# ============== #
  85 +def limit_omega(min, max, domega, log=True):
  86 + """
  87 + """
  88 + # Compute only if domega < pi
  89 + if (domega < gammalib.pi):
  90 +
  91 + # Save initial interval
  92 + min_initial = min
  93 + max_initial = max
  94 +
  95 + # Initialise intervals
  96 + intervals = []
  97 +
  98 + # Limit each of the intervals to [omega_min, omega_max]
  99 + omega_min = -domega
  100 + omega_max = +domega
  101 + omega_min_plus = omega_min + gammalib.twopi
  102 + omega_max_plus = omega_max + gammalib.twopi
  103 + omega_min_minus = omega_min - gammalib.twopi
  104 + omega_max_minus = omega_max - gammalib.twopi
  105 +
  106 + # Check for overlaps
  107 + in_unshifted = False
  108 + in_plus = False
  109 + in_minus = False
  110 + if (max_initial > omega_min and min_initial < omega_max):
  111 + min = min_initial
  112 + max = max_initial
  113 + in_unshifted = True
  114 + if (min < omega_min):
  115 + min = omega_min
  116 + if (max > omega_max):
  117 + max = omega_max
  118 + intervals.append({'min': min, 'max': max})
  119 + if (max_initial > omega_min_plus and min_initial < omega_max_plus):
  120 + min = min_initial
  121 + max = max_initial
  122 + in_plus = True
  123 + if (min < omega_min_plus):
  124 + min = omega_min_plus
  125 + if (max > omega_max_plus):
  126 + max = omega_max_plus
  127 + intervals.append({'min': min, 'max': max})
  128 + if (max_initial > omega_min_minus and min_initial < omega_max_minus):
  129 + min = min_initial
  130 + max = max_initial
  131 + in_minus = True
  132 + if (min < omega_min_minus):
  133 + min = omega_min_minus
  134 + if (max > omega_max_minus):
  135 + max = omega_max_minus
  136 + intervals.append({'min': min, 'max': max})
  137 +
  138 + # Debug
  139 + if log:
  140 + print("[%7.2f,%7.2f] => [%7.2f,%7.2f] (%i[%7.2f,%7.2f] %i[%7.2f,%7.2f] %i[%7.2f,%7.2f])" %
  141 + (min_initial*gammalib.rad2deg, max_initial*gammalib.rad2deg, \
  142 + min*gammalib.rad2deg, max*gammalib.rad2deg, \
  143 + in_unshifted, \
  144 + omega_min*gammalib.rad2deg, omega_max*gammalib.rad2deg, \
  145 + in_plus, \
  146 + omega_min_plus*gammalib.rad2deg, omega_max_plus*gammalib.rad2deg, \
  147 + in_minus, \
  148 + omega_min_minus*gammalib.rad2deg, omega_max_minus*gammalib.rad2deg))
  149 +
  150 + # ... just copy over the input interval
  151 + else:
  152 + intervals = [{'min': min, 'max': max}]
  153 +
  154 + # Return
  155 + return intervals
  156 +
  157 +
  158 +# ======================= #
  159 +# Show ellptical response #
  160 +# ======================= #
  161 +def show_response(centre, semimajor, semiminor, posang, roiDir, pntDir, roi_radius, psf_max_radius, log=False):
  162 + """
  163 + Show ellptical response
  164 + """
  165 + # Create figure
  166 + plt.figure(1)
  167 + plt.title("Elliptical Npred computation")
  168 +
  169 + # Get ellipse boundary (used for intersection computation)
  170 + if (semimajor >= semiminor):
  171 + bound_major = semimajor
  172 + bound_minor = semiminor
  173 + bound_pa = posang
  174 + else:
  175 + bound_major = semiminor
  176 + bound_minor = semimajor
  177 + bound_pa = posang + 90.0
  178 +
  179 + # Compute effective ROI radius
  180 + radius_roi = roi_radius + psf_max_radius
  181 +
  182 + # Compute some stuff
  183 + cos_pa = math.cos(posang * gammalib.deg2rad)
  184 + sin_pa = math.sin(posang * gammalib.deg2rad)
  185 + rho_roi = roiDir.dist_deg(centre)
  186 + theta_max = bound_major
  187 + posangle_roi = centre.posang(roiDir) # omega0
  188 +
  189 + # Rho limits
  190 + rho_min = 0.0
  191 + rho_max = rho_roi + radius_roi
  192 + if (rho_roi > radius_roi):
  193 + rho_min = rho_roi - radius_roi
  194 + if (rho_max > theta_max):
  195 + rho_max = theta_max
  196 +
  197 + # Convert to radians
  198 + roi_radius *= gammalib.deg2rad
  199 + psf_max_radius *= gammalib.deg2rad
  200 + radius_roi *= gammalib.deg2rad
  201 + rho_roi *= gammalib.deg2rad
  202 + theta_max *= gammalib.deg2rad
  203 +
  204 + # Setup rhos
  205 + rhos = []
  206 + for i in range(51):
  207 + rho = i*0.02
  208 + if (rho >= rho_min and rho <= rho_max):
  209 + rhos.append(rho)
  210 +
  211 + # Setup ellipse as vector
  212 + x = []
  213 + y = []
  214 + for angle in range(0, 360):
  215 + radians = angle * gammalib.deg2rad
  216 + cos_radians = math.cos(radians)
  217 + sin_radians = math.sin(radians)
  218 + x.append(semimajor * cos_radians * sin_pa - semiminor * sin_radians * cos_pa)
  219 + y.append(semimajor * cos_radians * cos_pa + semiminor * sin_radians * sin_pa)
  220 +
  221 + # Plot ellipse
  222 + plt.plot(x, y, 'r-')
  223 + plt.xlim([ 2.0*bound_major,-2.0*bound_major])
  224 + plt.ylim([-2.0*bound_major, 2.0*bound_major])
  225 + plt.plot([0.0], [0.0], 'ro')
  226 +
  227 + # Plot ROI centre
  228 + x_roi = roiDir.ra_deg() - centre.ra_deg()
  229 + y_roi = roiDir.dec_deg() - centre.dec_deg()
  230 + plt.plot([x_roi], [y_roi], 'bo')
  231 +
  232 + # Plot ROI circle
  233 + x = []
  234 + y = []
  235 + for angle in range(0, 360):
  236 + radians = angle * gammalib.deg2rad
  237 + cos_radians = math.cos(radians)
  238 + sin_radians = math.sin(radians)
  239 + x.append(roi_radius * cos_radians * gammalib.rad2deg + x_roi)
  240 + y.append(roi_radius * sin_radians * gammalib.rad2deg + y_roi)
  241 + plt.plot(x, y, 'b-.')
  242 +
  243 + # Plot ROI+Psf circle
  244 + x = []
  245 + y = []
  246 + for angle in range(0, 360):
  247 + radians = angle * gammalib.deg2rad
  248 + cos_radians = math.cos(radians)
  249 + sin_radians = math.sin(radians)
  250 + x.append(radius_roi * cos_radians * gammalib.rad2deg + x_roi)
  251 + y.append(radius_roi * sin_radians * gammalib.rad2deg + y_roi)
  252 + plt.plot(x, y, 'b-')
  253 +
  254 + # Print some stuff
  255 + print("Distance model-ROI (rho_roi) ............: %.2f deg" % (rho_roi*gammalib.rad2deg))
  256 + print("Rho interval ............................: %.2f-%.2f deg" % (rho_min*gammalib.rad2deg,rho_max*gammalib.rad2deg))
  257 +
  258 + # Continue only if rho range is valid
  259 + if (rho_max > rho_min):
  260 +
  261 + # Plot rhos
  262 + for rho in rhos:
  263 +
  264 + # Print header
  265 + if log:
  266 + print("Rho ..................................: %.2f deg" % (rho))
  267 +
  268 + # Compute some stuff
  269 + rho_rad = rho * gammalib.deg2rad
  270 +
  271 + # Compute half length of arc that lies within Psf validity circle
  272 + domega = 0.5 * cta_roi_arclength(rho_rad, rho_roi, radius_roi)
  273 +
  274 + # Print domega
  275 + if log:
  276 + print(" domega .............................: %.2f deg" % (domega*gammalib.rad2deg))
  277 +
  278 + #
  279 + if (domega > 0.0):
  280 +
  281 + # Full containment within ellipse
  282 + if (rho <= bound_minor):
  283 +
  284 + # Compute phi integration range
  285 + omega_min = -domega
  286 + omega_max = +domega
  287 +
  288 + # Print
  289 + if log:
  290 + print(" Omega range ........................: %.2f - %.2f deg" % (omega_min*gammalib.rad2deg,omega_max*gammalib.rad2deg))
  291 +
  292 + # Plot segment
  293 + n = 100
  294 + x = []
  295 + y = []
  296 + step = (omega_max - omega_min)/n
  297 + for i in range(n+1):
  298 + radians = i*step + omega_min
  299 + cos_radians = math.sin(radians+posangle_roi)
  300 + sin_radians = math.cos(radians+posangle_roi)
  301 + x.append(rho * cos_radians)
  302 + y.append(rho * sin_radians)
  303 + plt.plot(x, y, 'k-')
  304 +
  305 + # ... otherwise compute the arcs
  306 + else:
  307 +
  308 + # Azimuth angle of intersection points between ellipse and
  309 + # circle with radius of rho
  310 + arg1 = 1.0 - (bound_minor*bound_minor) / (rho*rho)
  311 + arg2 = 1.0 - (bound_minor*bound_minor) / (bound_major*bound_major)
  312 + omega_width = math.acos(math.sqrt(arg1/arg2))
  313 +
  314 + # Print Omega width
  315 + if log:
  316 + print(" omega_width ........................: %.2f deg" % (omega_width*gammalib.rad2deg))
  317 +
  318 + #
  319 + if (omega_width > 0.0):
  320 +
  321 + # Compute Omega intervals. Recall that Omega is with respect
  322 + # to connecting line between model and observed photon. Make
  323 + # sure that omega_0 is within [-pi,pi] thus that the omega
  324 + # intervals are between [-2pi,2pi]
  325 + omega_0 = bound_pa * gammalib.deg2rad - posangle_roi
  326 + if (omega_0 > gammalib.pi):
  327 + omega_0 -= gammalib.pi
  328 + elif (omega_0 < -gammalib.pi):
  329 + omega_0 += gammalib.pi
  330 +
  331 + # Compute intervals
  332 + omega1_min = omega_0 - omega_width
  333 + omega1_max = omega_0 + omega_width
  334 + omega2_min = omega1_min + gammalib.pi
  335 + omega2_max = omega1_max + gammalib.pi
  336 +
  337 + # Logging
  338 + if log:
  339 + print(" Omega range 1 (initial) ............: %.2f - %.2f deg" % (omega1_min*gammalib.rad2deg,omega1_max*gammalib.rad2deg))
  340 + print(" Omega range 2 (initial) ............: %.2f - %.2f deg" % (omega2_min*gammalib.rad2deg,omega2_max*gammalib.rad2deg))
  341 +
  342 + # Limit intervals
  343 + intervals = limit_omega(omega1_min, omega1_max, domega)
  344 + intervals2 = limit_omega(omega2_min, omega2_max, domega)
  345 + intervals.extend(intervals2)
  346 +
  347 + # Print results
  348 + if log:
  349 + for interval in intervals:
  350 + print(" Omega interval .....................: %.2f - %.2f deg" % (interval['min']*gammalib.rad2deg,interval['max']*gammalib.rad2deg))
  351 +
  352 + # Draw Omega range lines and circle segments
  353 + for interval in intervals:
  354 + omega_min = interval['min']
  355 + omega_max = interval['max']
  356 + if (omega_min < omega_max):
  357 + n = 100
  358 + x = []
  359 + y = []
  360 + step = (omega_max - omega_min)/n
  361 + for i in range(n+1):
  362 + radians = i*step + omega_min
  363 + cos_radians = math.sin(radians+posangle_roi)
  364 + sin_radians = math.cos(radians+posangle_roi)
  365 + x.append(rho * cos_radians)
  366 + y.append(rho * sin_radians)
  367 + plt.plot(x, y, 'k-')
  368 +
  369 + # Show plot
  370 + plt.show()
  371 +
  372 + # Return
  373 + return
  374 +
  375 +
  376 +# ======================== #
  377 +# Main routine entry point #
  378 +# ======================== #
  379 +if __name__ == '__main__':
  380 + """
  381 + Test elliptical response computation
  382 + """
  383 + # Dump header
  384 + print("")
  385 + print("*************************************")
  386 + print("* Test elliptical Npred computation *")
  387 + print("*************************************")
  388 +
  389 + # Check if matplotlib is available
  390 + try:
  391 + import matplotlib.pyplot as plt
  392 + except ImportError:
  393 + print("Matplotlib is not installed on your system. Abort.")
  394 + sys.exit()
  395 +
  396 + # Set Parameters
  397 + roi_radius = 0.5
  398 + delta_max = 0.4
  399 +
  400 + # Set model parameters
  401 + centre = gammalib.GSkyDir()
  402 + centre.radec_deg(0.0, 0.0)
  403 + semimajor = 1.0
  404 + semiminor = 0.50
  405 + posang = 30.0
  406 +
  407 + # Set ROI direction
  408 + roiDir = gammalib.GSkyDir()
  409 + roiDir.radec_deg(-0.4, -0.3)
  410 + #roiDir.radec_deg(+0.4, -0.3)
  411 + #roiDir.radec_deg(+0.4, +0.3)
  412 + #roiDir.radec_deg(0.0, 0.1)
  413 +
  414 + # Set pointing direction
  415 + pntDir = gammalib.GSkyDir()
  416 + pntDir.radec_deg(0.1, -1.0)
  417 +
  418 + # Show elliptical response geometry
  419 + show_response(centre, semimajor, semiminor, posang, roiDir, pntDir, roi_radius, delta_max)
  420 +
... ...
inst/cta/test/test_psf_radial.py 0 → 100755
  1 +#! /usr/bin/env python
  2 +# ==========================================================================
  3 +# This script tests the radial response computation. The script requires
  4 +# matplotlib to be installed.
  5 +#
  6 +# --------------------------------------------------------------------------
  7 +#
  8 +# Copyright (C) 2014 Juergen Knoedlseder
  9 +#
  10 +# This program is free software: you can redistribute it and/or modify
  11 +# it under the terms of the GNU General Public License as published by
  12 +# the Free Software Foundation, either version 3 of the License, or
  13 +# (at your option) any later version.
  14 +#
  15 +# This program is distributed in the hope that it will be useful,
  16 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
  17 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18 +# GNU General Public License for more details.
  19 +#
  20 +# You should have received a copy of the GNU General Public License
  21 +# along with this program. If not, see <http://www.gnu.org/licenses/>.
  22 +#
  23 +# ==========================================================================
  24 +import gammalib
  25 +import math
  26 +
  27 +
  28 +# ================= #
  29 +# Compute arclength #
  30 +# ================= #
  31 +def cta_roi_arclength(rad, dist, roi):
  32 + """
  33 + Returns length of circular arc within circular ROI
  34 +
  35 + Parameters:
  36 + rad - Circle radius in radians (<pi)
  37 + dist - Circle centre distance to ROI centre (<pi)
  38 + roi - Radius of ROI in radians
  39 + """
  40 + # Initialise
  41 + arclength = 0.0
  42 +
  43 + # Handle special case that circle centre matches ROI centre
  44 + if dist == 0.0:
  45 + if rad > roi:
  46 + arclength = 0.0
  47 + else:
  48 + arclength = 2.0 * math.pi
  49 +
  50 + # ... otherwise circle and ROI centres are not identical
  51 + else:
  52 +
  53 + # Handle special case that we evaluate exactly at the circle
  54 + # centre. In this case we have in fact a point, and if this point
  55 + # falls within the ROI it has a formal arclength of 2pi.
  56 + if rad == 0.0:
  57 + if dist > roi:
  58 + arclength = 0.0
  59 + else:
  60 + arclength = 2.0 * math.pi
  61 +
  62 + # ... otherwise we have to handle the general case
  63 + else:
  64 + d = roi - dist
  65 + if -rad >= d:
  66 + arclength = 0.0
  67 + elif rad <= d:
  68 + arclength = 2.0 * math.pi
  69 + else:
  70 + cosrad = math.cos(rad)
  71 + sinrad = math.sin(rad)
  72 + cosroi = math.cos(roi)
  73 + cosdist = math.cos(dist)
  74 + sindist = math.sin(dist)
  75 + cosang = (cosroi - cosdist * cosrad) / (sindist * sinrad)
  76 + arclength = 2.0 * gammalib.acos(cosang)
  77 +
  78 + # Return
  79 + return arclength
  80 +
  81 +
  82 +# ==================== #
  83 +# Show radial response #
  84 +# ==================== #
  85 +def show_response(centre, theta_max, obsDir, psf_max):
  86 + """
  87 + Show radial response
  88 + """
  89 + # Create figure
  90 + plt.figure(1)
  91 + plt.title("Radial response")
  92 +
  93 + # Compute some stuff
  94 + delta_mod = obsDir.dist_deg(centre)
  95 + posangle_mod = obsDir.posang(centre)
  96 +
  97 + # Delta limits
  98 + delta_min = 0.0
  99 + delta_max = delta_mod + theta_max
  100 + if (delta_mod > theta_max):
  101 + delta_min = delta_mod - theta_max
  102 + if delta_max > psf_max:
  103 + delta_max = psf_max
  104 +
  105 + # Convert to radians
  106 + delta_mod *= gammalib.deg2rad
  107 + theta_max *= gammalib.deg2rad
  108 +
  109 + # Setup deltas
  110 + deltas = []
  111 + for i in range(51):
  112 + delta = i*0.02
  113 + if (delta >= delta_min and delta <= delta_max):
  114 + deltas.append(delta)
  115 +
  116 + # Plot Psf validity circle
  117 + x = []
  118 + y = []
  119 + for angle in range(0, 361):
  120 + radians = angle * gammalib.deg2rad
  121 + cos_radians = math.cos(radians)
  122 + sin_radians = math.sin(radians)
  123 + x.append(psf_max * cos_radians)
  124 + y.append(psf_max * sin_radians)
  125 + plt.plot(x, y, 'b-')
  126 + plt.xlim([ 2.0*psf_max,-2.0*psf_max])
  127 + plt.ylim([-2.0*psf_max, 2.0*psf_max])
  128 + plt.plot([0.0], [0.0], 'bo')
  129 +
  130 + # Plot model direction
  131 + x_mod = centre.ra_deg() - obsDir.ra_deg()
  132 + y_mod = centre.dec_deg() - obsDir.dec_deg()
  133 + plt.plot([x_mod], [y_mod], 'ro')
  134 +
  135 + # Plot radial model
  136 + x = []
  137 + y = []
  138 + for angle in range(0, 361):
  139 + radians = angle * gammalib.deg2rad
  140 + cos_radians = math.cos(radians)
  141 + sin_radians = math.sin(radians)
  142 + x.append(theta_max * cos_radians * gammalib.rad2deg + x_mod)
  143 + y.append(theta_max * sin_radians * gammalib.rad2deg + y_mod)
  144 + plt.plot(x, y, 'r-')
  145 +
  146 + # Plot deltas
  147 + for delta in deltas:
  148 +
  149 + # Compute some stuff
  150 + delta_rad = delta * gammalib.deg2rad
  151 +
  152 + # Compute half length of arc that lies within model
  153 + dphi = 0.5 * cta_roi_arclength(delta_rad, delta_mod, theta_max)
  154 +
  155 + # Continue only if arc length is positive
  156 + if (dphi > 0.0):
  157 +
  158 + # Set Phi range
  159 + phi_min = -dphi
  160 + phi_max = +dphi
  161 +
  162 + # Plot segment
  163 + n = 100
  164 + x = []
  165 + y = []
  166 + step = (phi_max - phi_min)/n
  167 + for i in range(n+1):
  168 + radians = i*step + phi_min
  169 + cos_radians = math.sin(radians+posangle_mod)
  170 + sin_radians = math.cos(radians+posangle_mod)
  171 + x.append(delta * cos_radians)
  172 + y.append(delta * sin_radians)
  173 + plt.plot(x, y, 'k-')
  174 +
  175 + # Show plot
  176 + plt.show()
  177 +
  178 + # Return
  179 + return
  180 +
  181 +
  182 +# ======================== #
  183 +# Main routine entry point #
  184 +# ======================== #
  185 +if __name__ == '__main__':
  186 + """
  187 + Test radial response computation
  188 + """
  189 + # Dump header
  190 + print("")
  191 + print("*******************************")
  192 + print("* Test radial Psf computation *")
  193 + print("*******************************")
  194 +
  195 + # Check if matplotlib is available
  196 + try:
  197 + import matplotlib.pyplot as plt
  198 + except ImportError:
  199 + print("Matplotlib is not installed on your system. Abort.")
  200 + sys.exit()
  201 +
  202 + # Set Psf size
  203 + psf_max = 1.0
  204 +
  205 + # Set observed photon direction
  206 + obsDir = gammalib.GSkyDir()
  207 + obsDir.radec_deg(0.0, 0.0)
  208 +
  209 + # Set model parameters
  210 + centre = gammalib.GSkyDir()
  211 + centre.radec_deg(0.0, 0.6)
  212 + theta_max = 0.5
  213 +
  214 + # Show elliptical response geometry
  215 + show_response(centre, theta_max, obsDir, psf_max)
  216 +
... ...
inst/lat/test/test_LAT.cpp
... ... @@ -617,23 +617,8 @@ void TestGLATObservation::test_one_binned_obs(const std::string&amp; datadir, const
617 617 void TestGLATOptimize::test_binned_optimizer_p6(void)
618 618 {
619 619 // Set expected fit results
620   - /*
621 620 double fit_results[] = {1, 0,
622   - 3.214411831, 0.744781,
623   - 1, 0,
624   - 1, 0,
625   - 0.8068908818, 0.08764042542,
626   - 1, 0,
627   - 83.6331, 0,
628   - 22.0145, 0,
629   - 2.103749294e-06, 1.560611406e-07,
630   - -2.126826057, 0.053618,
631   - 100, 0,
632   - 500000, 0,
633   - 1, 0};
634   - */
635   - double fit_results[] = {1, 0,
636   - 2.342467831, 0.7339917,
  621 + 2.34236, 0.7339917,
637 622 1, 0,
638 623 1, 0,
639 624 0.8167256818, 0.08569407542,
... ... @@ -663,23 +648,8 @@ void TestGLATOptimize::test_binned_optimizer_p6(void)
663 648 void TestGLATOptimize::test_binned_optimizer_p7(void)
664 649 {
665 650 // Set expected fit results
666   - /*
667   - double fit_results[] = {1, 0,
668   - 2.896021569, 0.441842,
669   - 1, 0,
670   - 1, 0,
671   - 0.8629520722, 0.06376048926,
672   - 1, 0,
673   - 83.6331, 0,
674   - 22.0145, 0,
675   - 1.922525774e-06, 1.209507237e-07,
676   - -2.038629122, 0.0509173084,
677   - 100, 0,
678   - 500000, 0,
679   - 1, 0};
680   - */
681 651 double fit_results[] = {1, 0,
682   - 2.374909569, 0.4548979,
  652 + 2.37468, 0.4548979,
683 653 1, 0,
684 654 1, 0,
685 655 0.8419824722, 0.06340531926,
... ... @@ -687,7 +657,7 @@ void TestGLATOptimize::test_binned_optimizer_p7(void)
687 657 83.6331, 0,
688 658 22.0145, 0,
689 659 1.922525774e-06, 1.209507237e-07,
690   - -2.124145722, 0.0493105484,
  660 + -2.12421, 0.0493105484,
691 661 100, 0,
692 662 500000, 0,
693 663 1, 0};
... ...
inst/mwl/test/test_MWL.cpp
1 1 /***************************************************************************
2 2 * test_MWL.cpp - Test multi-wavelength classes *
3 3 * ----------------------------------------------------------------------- *
4   - * copyright (C) 2010-2013 by Juergen Knoedlseder *
  4 + * copyright (C) 2010-2014 by Juergen Knoedlseder *
5 5 * ----------------------------------------------------------------------- *
6 6 * *
7 7 * This program is free software: you can redistribute it and/or modify *
... ... @@ -219,13 +219,13 @@ void TestGMWL::test_optimizer(void)
219 219 double fit_results[] = {83.6331, 0,
220 220 22.0145, 0,
221 221 0.00231438040883405, 0.000191759607698647,
222   - -3.1111154491587, 0.125598,
  222 + -3.12006, 0.128382,
223 223 1, 0,
224 224 1, 0,
225 225 83.6331, 0,
226 226 22.0145, 0,
227 227 4.5050091460345e-11, 1.95885803229878e-12,
228   - -2.13740092737976, 0.00578641,
  228 + -2.13765, 0.00578641,
229 229 1000, 0,
230 230 1, 0};
231 231 test_try("Perform LM optimization");
... ...
pyext/GModelSpatialElliptical.i
... ... @@ -73,6 +73,10 @@ public:
73 73 void dec(const double& dec);
74 74 double posangle(void) const;
75 75 void posangle(const double& posangle);
  76 + double semiminor(void) const;
  77 + double semimajor(void) const;
  78 + void semiminor(const double& semiminor);
  79 + void semimajor(const double& semimajor);
76 80 GSkyDir dir(void) const;
77 81 void dir(const GSkyDir& dir);
78 82 };
... ...
pyext/GModelSpatialEllipticalDisk.i
1 1 /***************************************************************************
2 2 * GModelSpatialEllipticalDisk.i - 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 *
... ... @@ -65,12 +65,6 @@ public:
65 65 virtual double theta_max(void) const;
66 66 virtual void read(const GXmlElement& xml);
67 67 virtual void write(GXmlElement& xml) const;
68   -
69   - // Other methods
70   - double semiminor(void) const;
71   - double semimajor(void) const;
72   - void semiminor(const double& semiminor);
73   - void semimajor(const double& semimajor);
74 68 };
75 69  
76 70  
... ...
src/model/GModelSpatialElliptical.cpp
1 1 /***************************************************************************
2 2 * GModelSpatialElliptical.cpp - Abstract elliptical spatial model class *
3 3 * ----------------------------------------------------------------------- *
4   - * copyright (C) 2013 by Juergen Knoedlseder *
  4 + * copyright (C) 2013-2014 by Juergen Knoedlseder *
5 5 * ----------------------------------------------------------------------- *
6 6 * *
7 7 * This program is free software: you can redistribute it and/or modify *
... ... @@ -467,11 +467,35 @@ void GModelSpatialElliptical::init_members(void)
467 467 m_posangle.gradient(0.0);
468 468 m_posangle.has_grad(false);
469 469  
  470 + // Initialise semi-major axis
  471 + m_semimajor.clear();
  472 + m_semimajor.name("MajorRadius");
  473 + m_semimajor.unit("deg");
  474 + m_semimajor.value(2.778e-4); // 1 arcsec
  475 + m_semimajor.min(2.778e-4); // 1 arcsec
  476 + m_semimajor.free();
  477 + m_semimajor.scale(1.0);
  478 + m_semimajor.gradient(0.0);
  479 + m_semimajor.has_grad(false); // Elliptical components never have gradients
  480 +
  481 + // Initialise semi-minor axis
  482 + m_semiminor.clear();
  483 + m_semiminor.name("MinorRadius");
  484 + m_semiminor.unit("deg");
  485 + m_semiminor.value(2.778e-4); // 1 arcsec
  486 + m_semiminor.min(2.778e-4); // 1 arcsec
  487 + m_semiminor.free();
  488 + m_semiminor.scale(1.0);
  489 + m_semiminor.gradient(0.0);
  490 + m_semiminor.has_grad(false); // Elliptical components never have gradients
  491 +
470 492 // Set parameter pointer(s)
471 493 m_pars.clear();
472 494 m_pars.push_back(&m_ra);
473 495 m_pars.push_back(&m_dec);
474 496 m_pars.push_back(&m_posangle);
  497 + m_pars.push_back(&m_semimajor);
  498 + m_pars.push_back(&m_semiminor);
475 499  
476 500 // Return
477 501 return;
... ... @@ -486,15 +510,19 @@ void GModelSpatialElliptical::init_members(void)
486 510 void GModelSpatialElliptical::copy_members(const GModelSpatialElliptical& model)
487 511 {
488 512 // Copy members
489   - m_ra = model.m_ra;
490   - m_dec = model.m_dec;
491   - m_posangle = model.m_posangle;
  513 + m_ra = model.m_ra;
  514 + m_dec = model.m_dec;
  515 + m_posangle = model.m_posangle;
  516 + m_semiminor = model.m_semiminor;
  517 + m_semimajor = model.m_semimajor;
492 518  
493 519 // Set parameter pointer(s)
494 520 m_pars.clear();
495 521 m_pars.push_back(&m_ra);
496 522 m_pars.push_back(&m_dec);
497 523 m_pars.push_back(&m_posangle);
  524 + m_pars.push_back(&m_semimajor);
  525 + m_pars.push_back(&m_semiminor);
498 526  
499 527 // Return
500 528 return;
... ...
src/model/GModelSpatialEllipticalDisk.cpp
1 1 /***************************************************************************
2 2 * GModelSpatialEllipticalDisk.cpp - 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 *
... ... @@ -405,7 +405,7 @@ GSkyDir GModelSpatialEllipticalDisk::mc(const GEnergy&amp; energy,
405 405 /***********************************************************************//**
406 406 * @brief Return maximum model radius (in radians)
407 407 *
408   - * @return Returns maximum model radians.
  408 + * @return Returns maximum model radius.
409 409 ***************************************************************************/
410 410 double GModelSpatialEllipticalDisk::theta_max(void) const
411 411 {
... ... @@ -634,32 +634,6 @@ std::string GModelSpatialEllipticalDisk::print(const GChatter&amp; chatter) const
634 634 ***************************************************************************/
635 635 void GModelSpatialEllipticalDisk::init_members(void)
636 636 {
637   - // Initialise semi-minor axis
638   - m_semiminor.clear();
639   - m_semiminor.name("MinorRadius");
640   - m_semiminor.unit("deg");
641   - m_semiminor.value(2.778e-4); // 1 arcsec
642   - m_semiminor.min(2.778e-4); // 1 arcsec
643   - m_semiminor.free();
644   - m_semiminor.scale(1.0);
645   - m_semiminor.gradient(0.0);
646   - m_semiminor.has_grad(false); // Elliptical components never have gradients
647   -
648   - // Initialise semi-major axis
649   - m_semimajor.clear();
650   - m_semimajor.name("MajorRadius");
651   - m_semimajor.unit("deg");
652   - m_semimajor.value(2.778e-4); // 1 arcsec
653   - m_semimajor.min(2.778e-4); // 1 arcsec
654   - m_semimajor.free();
655   - m_semimajor.scale(1.0);
656   - m_semimajor.gradient(0.0);
657   - m_semimajor.has_grad(false); // Elliptical components never have gradients
658   -
659   - // Set parameter pointer(s)
660   - m_pars.push_back(&m_semiminor);
661   - m_pars.push_back(&m_semimajor);
662   -
663 637 // Initialise precomputation cache. Note that zero values flag
664 638 // uninitialised as a zero radius is not meaningful
665 639 m_last_semiminor = 0.0;
... ... @@ -684,10 +658,6 @@ void GModelSpatialEllipticalDisk::init_members(void)
684 658 ***************************************************************************/
685 659 void GModelSpatialEllipticalDisk::copy_members(const GModelSpatialEllipticalDisk& model)
686 660 {
687   - // Copy members
688   - m_semiminor = model.m_semiminor;
689   - m_semimajor = model.m_semimajor;
690   -
691 661 // Copy precomputation cache
692 662 m_last_semiminor = model.m_last_semiminor;
693 663 m_last_semimajor = model.m_last_semimajor;
... ... @@ -722,7 +692,7 @@ void GModelSpatialEllipticalDisk::free_members(void)
722 692 ***************************************************************************/
723 693 void GModelSpatialEllipticalDisk::update() const
724 694 {
725   - // Update if one axis has changed has changed
  695 + // Update if one axis has changed
726 696 if (m_last_semiminor != semiminor() || m_last_semimajor != semimajor()) {
727 697  
728 698 // Store last values
... ...
src/opt/GOptimizerLM.cpp
... ... @@ -635,10 +635,10 @@ void GOptimizerLM::init_members(void)
635 635 m_lambda_start = 1.0e-3;
636 636 m_lambda_inc = 10.0;
637 637 m_lambda_dec = 0.1;
638   - m_eps = 1.0e-6;
639   - m_max_iter = 1000;
  638 + m_eps = 5.0e-3; //!< Changed on 30/10/2014 from 1.0e-6
  639 + m_max_iter = 100; //!< Changed on 30/10/2014 from 1000
640 640 m_max_stall = 10;
641   - m_max_hit = 3; //!< Maximum successive boundary hits before freeze
  641 + m_max_hit = 3; //!< Maximum successive boundary hits before freeze
642 642 m_step_adjust = true;
643 643  
644 644 // Initialise bookkeeping arrays
... ...