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 * Version 0.9.0 released 3 * Version 0.9.0 released
4 ======================== 4 ========================
@@ -19,8 +19,8 @@ @@ -19,8 +19,8 @@
19 Read/write altitude and azimuth attributes of CTA data 19 Read/write altitude and azimuth attributes of CTA data
20 Refactor CTA response class, add GCTAResponseCube class 20 Refactor CTA response class, add GCTAResponseCube class
21 Improve CTA response computation for extended models 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 Load events only when needed into GCTAObservation 24 Load events only when needed into GCTAObservation
25 Implement full automatic typecasting in Python module 25 Implement full automatic typecasting in Python module
26 Fill DETX and DETY values in GCTAResponseIrf::mc 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,7 +236,7 @@ The type of the GCTAModelCubeBackground class has been set to GCTACubeBackground
236 the type of the GCTAModelIrfBackground class to GCTAIrfBackground. XML files need 236 the type of the GCTAModelIrfBackground class to GCTAIrfBackground. XML files need
237 to be adapted accordingly. 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 GCTAExposure represents the summed exposure for a list of CTA observations 240 GCTAExposure represents the summed exposure for a list of CTA observations
241 (computed as the sum of effective area times livetime), GCTAMeanPsf represents 241 (computed as the sum of effective area times livetime), GCTAMeanPsf represents
242 the average PSF, computed by weighing the PSF of each observation by the respective 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,10 +257,10 @@ GCTAResponseIrf derives. The class GCTAResponseCube, which also derives from
257 GCTAResponse, has been added to handle cube-style analysis. GCTAResponseCube 257 GCTAResponse, has been added to handle cube-style analysis. GCTAResponseCube
258 holds an exposure cube (GCTAExposure) and a PSF cube (GCTAMeanPsf) to describe 258 holds an exposure cube (GCTAExposure) and a PSF cube (GCTAMeanPsf) to describe
259 the response. GCTAObservation has been modified to allocate either IRF-style or 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 The abstract GCTASourceCube class has been added to handle the pre-computation 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 by the GCTASourceCubePointSource class for point source analysis. The cache has 264 by the GCTASourceCubePointSource class for point source analysis. The cache has
265 been added to GCTAResponseCube who takes care of initializing and using the cache. 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,6 +95,10 @@ public:
95 void dec(const double& dec); 95 void dec(const double& dec);
96 double posangle(void) const; 96 double posangle(void) const;
97 void posangle(const double& posangle); 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 GSkyDir dir(void) const; 102 GSkyDir dir(void) const;
99 void dir(const GSkyDir& dir); 103 void dir(const GSkyDir& dir);
100 104
@@ -105,9 +109,11 @@ protected: @@ -105,9 +109,11 @@ protected:
105 void free_members(void); 109 void free_members(void);
106 110
107 // Proteced members 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,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 * @brief Return normalization of elliptical source 282 * @brief Return normalization of elliptical source
219 * 283 *
220 * @return Normalization. 284 * @return Normalization.
include/GModelSpatialEllipticalDisk.hpp
@@ -81,11 +81,6 @@ public: @@ -81,11 +81,6 @@ public:
81 virtual void write(GXmlElement& xml) const; 81 virtual void write(GXmlElement& xml) const;
82 virtual std::string print(const GChatter& chatter = NORMAL) const; 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 protected: 85 protected:
91 // Protected methods 86 // Protected methods
@@ -94,10 +89,6 @@ protected: @@ -94,10 +89,6 @@ protected:
94 void free_members(void); 89 void free_members(void);
95 void update(void) const; 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 // Cached members used for pre-computations 92 // Cached members used for pre-computations
102 mutable double m_last_semiminor; //!< Last semi-minor axis 93 mutable double m_last_semiminor; //!< Last semi-minor axis
103 mutable double m_last_semimajor; //!< Last semi-major axis 94 mutable double m_last_semimajor; //!< Last semi-major axis
@@ -132,62 +123,4 @@ std::string GModelSpatialEllipticalDisk::type(void) const @@ -132,62 +123,4 @@ std::string GModelSpatialEllipticalDisk::type(void) const
132 return "EllipticalDisk"; 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 #endif /* GMODELSPATIALELLIPTICALDISK_HPP */ 126 #endif /* GMODELSPATIALELLIPTICALDISK_HPP */
inst/cta/include/GCTAResponseCube.hpp
@@ -112,13 +112,13 @@ private: @@ -112,13 +112,13 @@ private:
112 void free_members(void); 112 void free_members(void);
113 int cache_index(const std::string& name) const; 113 int cache_index(const std::string& name) const;
114 double psf_radial(const GModelSpatialRadial* model, 114 double psf_radial(const GModelSpatialRadial* model,
115 - const double& zeta, 115 + const double& rho_obs,
116 const GSkyDir& obsDir, 116 const GSkyDir& obsDir,
117 const GEnergy& srcEng, 117 const GEnergy& srcEng,
118 const GTime& srcTime) const; 118 const GTime& srcTime) const;
119 double psf_elliptical(const GModelSpatialElliptical* model, 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 const GSkyDir& obsDir, 122 const GSkyDir& obsDir,
123 const GEnergy& srcEng, 123 const GEnergy& srcEng,
124 const GTime& srcTime) const; 124 const GTime& srcTime) const;
inst/cta/src/GCTAResponseCube.cpp
@@ -37,6 +37,7 @@ @@ -37,6 +37,7 @@
37 #include "GCTASourceCubeDiffuse.hpp" 37 #include "GCTASourceCubeDiffuse.hpp"
38 #include "GModelSpatialPointSource.hpp" 38 #include "GModelSpatialPointSource.hpp"
39 #include "GModelSpatialRadial.hpp" 39 #include "GModelSpatialRadial.hpp"
  40 +#include "GModelSpatialRadialShell.hpp"
40 #include "GModelSpatialElliptical.hpp" 41 #include "GModelSpatialElliptical.hpp"
41 #include "GModelSpatialDiffuse.hpp" 42 #include "GModelSpatialDiffuse.hpp"
42 #include "GPhoton.hpp" 43 #include "GPhoton.hpp"
@@ -63,6 +64,7 @@ @@ -63,6 +64,7 @@
63 /* __ Macros _____________________________________________________________ */ 64 /* __ Macros _____________________________________________________________ */
64 65
65 /* __ Coding definitions _________________________________________________ */ 66 /* __ Coding definitions _________________________________________________ */
  67 +#define G_RADIAL_PSF_BASED //!< Use Psf-based integration for radial model
66 68
67 /* __ Debug definitions __________________________________________________ */ 69 /* __ Debug definitions __________________________________________________ */
68 70
@@ -236,11 +238,12 @@ GCTAResponseCube* GCTAResponseCube::clone(void) const @@ -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 * @param[in] event Observed event. 243 * @param[in] event Observed event.
242 * @param[in] photon Incident photon. 244 * @param[in] photon Incident photon.
243 * @param[in] obs Observation. 245 * @param[in] obs Observation.
  246 + * @return Instrument response.
244 ***************************************************************************/ 247 ***************************************************************************/
245 double GCTAResponseCube::irf(const GEvent& event, 248 double GCTAResponseCube::irf(const GEvent& event,
246 const GPhoton& photon, 249 const GPhoton& photon,
@@ -307,14 +310,14 @@ double GCTAResponseCube::irf(const GEvent&amp; event, @@ -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 * @param[in] event Event. 315 * @param[in] event Event.
313 * @param[in] source Source. 316 * @param[in] source Source.
314 * @param[in] obs Observation. 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 double GCTAResponseCube::irf(const GEvent& event, 322 double GCTAResponseCube::irf(const GEvent& event,
320 const GSource& source, 323 const GSource& source,
@@ -347,15 +350,14 @@ double GCTAResponseCube::irf(const GEvent&amp; event, @@ -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 * @param[in] event Observed event. 355 * @param[in] event Observed event.
353 * @param[in] source Source. 356 * @param[in] source Source.
354 * @param[in] obs Observation. 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 double GCTAResponseCube::irf_ptsrc(const GEvent& event, 362 double GCTAResponseCube::irf_ptsrc(const GEvent& event,
361 const GSource& source, 363 const GSource& source,
@@ -425,15 +427,14 @@ double GCTAResponseCube::irf_ptsrc(const GEvent&amp; event, @@ -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 * @param[in] event Observed event. 432 * @param[in] event Observed event.
431 * @param[in] source Source. 433 * @param[in] source Source.
432 * @param[in] obs Observation. 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 double GCTAResponseCube::irf_radial(const GEvent& event, 439 double GCTAResponseCube::irf_radial(const GEvent& event,
439 const GSource& source, 440 const GSource& source,
@@ -462,19 +463,19 @@ double GCTAResponseCube::irf_radial(const GEvent&amp; event, @@ -462,19 +463,19 @@ double GCTAResponseCube::irf_radial(const GEvent&amp; event,
462 463
463 // Compute angle between model centre and measured photon direction 464 // Compute angle between model centre and measured photon direction
464 // (radians) 465 // (radians)
465 - double zeta = model->dir().dist(obsDir); 466 + double rho_obs = model->dir().dist(obsDir);
466 467
467 // Continue only if we're sufficiently close to the model centre to get a 468 // Continue only if we're sufficiently close to the model centre to get a
468 // non-zero response 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 // Get exposure 472 // Get exposure
472 irf = exposure()(obsDir, obsEng); 473 irf = exposure()(obsDir, obsEng);
473 474
474 // Continue only if exposure is positive 475 // Continue only if exposure is positive
475 if (irf > 0.0) { 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 irf *= obs.deadc(obsTime); 479 irf *= obs.deadc(obsTime);
479 } 480 }
480 481
@@ -496,15 +497,14 @@ double GCTAResponseCube::irf_radial(const GEvent&amp; event, @@ -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 * @param[in] event Observed event. 502 * @param[in] event Observed event.
502 * @param[in] source Source. 503 * @param[in] source Source.
503 * @param[in] obs Observation. 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 double GCTAResponseCube::irf_elliptical(const GEvent& event, 509 double GCTAResponseCube::irf_elliptical(const GEvent& event,
510 const GSource& source, 510 const GSource& source,
@@ -533,20 +533,20 @@ double GCTAResponseCube::irf_elliptical(const GEvent&amp; event, @@ -533,20 +533,20 @@ double GCTAResponseCube::irf_elliptical(const GEvent&amp; event,
533 533
534 // Compute angle between model centre and measured photon direction and 534 // Compute angle between model centre and measured photon direction and
535 // position angle (radians) 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 // Continue only if we're sufficiently close to the model centre to get a 539 // Continue only if we're sufficiently close to the model centre to get a
540 // non-zero response 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 // Get exposure 543 // Get exposure
544 irf = exposure()(obsDir, obsEng); 544 irf = exposure()(obsDir, obsEng);
545 545
546 // Continue only if exposure is positive 546 // Continue only if exposure is positive
547 if (irf > 0.0) { 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 irf *= obs.deadc(obsTime); 550 irf *= obs.deadc(obsTime);
551 } 551 }
552 552
@@ -568,21 +568,20 @@ double GCTAResponseCube::irf_elliptical(const GEvent&amp; event, @@ -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 * @param[in] event Observed event. 573 * @param[in] event Observed event.
574 * @param[in] source Source. 574 * @param[in] source Source.
575 * @param[in] obs Observation. 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 double GCTAResponseCube::irf_diffuse(const GEvent& event, 586 double GCTAResponseCube::irf_diffuse(const GEvent& event,
588 const GSource& source, 587 const GSource& source,
@@ -651,9 +650,13 @@ double GCTAResponseCube::irf_diffuse(const GEvent&amp; event, @@ -651,9 +650,13 @@ double GCTAResponseCube::irf_diffuse(const GEvent&amp; event,
651 * 650 *
652 * @param[in] photon Incident photon. 651 * @param[in] photon Incident photon.
653 * @param[in] obs Observation. 652 * @param[in] obs Observation.
  653 + * @return Spatial integral of point spread function.
654 * 654 *
655 * @exception GException::feature_not_implemented 655 * @exception GException::feature_not_implemented
656 * Method not implemented. 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 double GCTAResponseCube::npred(const GPhoton& photon, 661 double GCTAResponseCube::npred(const GPhoton& photon,
659 const GObservation& obs) const 662 const GObservation& obs) const
@@ -668,21 +671,21 @@ double GCTAResponseCube::npred(const GPhoton&amp; photon, @@ -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 * @param[in] xml XML element. 676 * @param[in] xml XML element.
674 * 677 *
675 * @exception GException::xml_invalid_parnames 678 * @exception GException::xml_invalid_parnames
676 * Invalid parameter names found in XML element. 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 void GCTAResponseCube::read(const GXmlElement& xml) 691 void GCTAResponseCube::read(const GXmlElement& xml)
@@ -737,18 +740,18 @@ void GCTAResponseCube::read(const GXmlElement&amp; 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 * @param[in] xml XML element. 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 void GCTAResponseCube::write(GXmlElement& xml) const 757 void GCTAResponseCube::write(GXmlElement& xml) const
@@ -773,10 +776,10 @@ void GCTAResponseCube::write(GXmlElement&amp; 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 * @param[in] chatter Chattiness (defaults to NORMAL). 781 * @param[in] chatter Chattiness (defaults to NORMAL).
779 - * @return String containing CTA response information. 782 + * @return String containing response information.
780 ***************************************************************************/ 783 ***************************************************************************/
781 std::string GCTAResponseCube::print(const GChatter& chatter) const 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,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 = Private methods = 824 = Private methods =
836 = = 825 = =
837 ==========================================================================*/ 826 ==========================================================================*/
@@ -857,7 +846,7 @@ void GCTAResponseCube::init_members(void) @@ -857,7 +846,7 @@ void GCTAResponseCube::init_members(void)
857 /***********************************************************************//** 846 /***********************************************************************//**
858 * @brief Copy class members 847 * @brief Copy class members
859 * 848 *
860 - * @param[in] rsp Response to be copied 849 + * @param[in] rsp Response.
861 ***************************************************************************/ 850 ***************************************************************************/
862 void GCTAResponseCube::copy_members(const GCTAResponseCube& rsp) 851 void GCTAResponseCube::copy_members(const GCTAResponseCube& rsp)
863 { 852 {
@@ -893,10 +882,12 @@ void GCTAResponseCube::free_members(void) @@ -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 * @param[in] name Model name. 887 * @param[in] name Model name.
899 * @return Cache index (-1 if model has not been found). 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 int GCTAResponseCube::cache_index(const std::string& name) const 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,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 * @param[in] model Radial model. 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 * @param[in] obsDir Observed event direction. 922 * @param[in] obsDir Observed event direction.
931 * @param[in] srcEng True photon energy. 923 * @param[in] srcEng True photon energy.
932 * @param[in] srcTime True photon arrival time. 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 * \f[ 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 * \f] 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 double GCTAResponseCube::psf_radial(const GModelSpatialRadial* model, 945 double GCTAResponseCube::psf_radial(const GModelSpatialRadial* model,
951 - const double& zeta, 946 + const double& delta_mod,
952 const GSkyDir& obsDir, 947 const GSkyDir& obsDir,
953 const GEnergy& srcEng, 948 const GEnergy& srcEng,
954 const GTime& srcTime) const 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 // https://cta-redmine.irap.omp.eu/issues/1291 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 // Initialise value 957 // Initialise value
965 double value = 0.0; 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 // Setup integration kernel. We take here the observed photon arrival 973 // Setup integration kernel. We take here the observed photon arrival
977 // direction as the true photon arrival direction because the PSF does 974 // direction as the true photon arrival direction because the PSF does
978 // not vary significantly over a small region. 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 GIntegral integral(&integrand); 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 return value; 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 * @param[in] model Elliptical model. 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 * @param[in] obsDir Observed event direction. 1149 * @param[in] obsDir Observed event direction.
1002 * @param[in] srcEng True photon energy. 1150 * @param[in] srcEng True photon energy.
1003 * @param[in] srcTime True photon arrival time. 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 * \f[ 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 * \f] 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 double GCTAResponseCube::psf_elliptical(const GModelSpatialElliptical* model, 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 const GSkyDir& obsDir, 1175 const GSkyDir& obsDir,
1025 const GEnergy& srcEng, 1176 const GEnergy& srcEng,
1026 const GTime& srcTime) const 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 // Initialise value 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 double delta_max = psf().delta_max(); 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 // Return PSF 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,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,192 +1281,22 @@ std::string GCTAResponseIrf::print(const GChatter&amp; chatter) const
1281 * @exception GCTAException::bad_model_type 1281 * @exception GCTAException::bad_model_type
1282 * Model is not a radial model. 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 * \f[ 1287 * \f[
1456 * \int_{\rho_{\rm min}}^{\rho_{\rm max}} 1288 * \int_{\rho_{\rm min}}^{\rho_{\rm max}}
1457 * \sin \rho \times S_{\rm p}(\rho | E, t) \times 1289 * \sin \rho \times S_{\rm p}(\rho | E, t) \times
1458 * \int_{\omega_{\rm min}}^{\omega_{\rm max}} 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 * \f] 1292 * \f]
1461 * 1293 *
1462 * where 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 * The integration is performed in the coordinate system of the source 1301 * The integration is performed in the coordinate system of the source
1472 * model spanned by \f$\rho\f$ and \f$\omega\f$ which allows to benefit 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,8 +1426,8 @@ double GCTAResponseIrf::irf_radial(const GEvent&amp; event,
1596 bounds.push_back(rho_max); 1426 bounds.push_back(rho_max);
1597 1427
1598 // If the integration range includes a transition between full 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 double transition_point = delta_max - zeta; 1431 double transition_point = delta_max - zeta;
1602 if (transition_point > rho_min && transition_point < rho_max) { 1432 if (transition_point > rho_min && transition_point < rho_max) {
1603 bounds.push_back(transition_point); 1433 bounds.push_back(transition_point);
@@ -1608,7 +1438,7 @@ double GCTAResponseIrf::irf_radial(const GEvent&amp; event, @@ -1608,7 +1438,7 @@ double GCTAResponseIrf::irf_radial(const GEvent&amp; event,
1608 // location 1438 // location
1609 const GModelSpatialRadialShell* shell = dynamic_cast<const GModelSpatialRadialShell*>(model); 1439 const GModelSpatialRadialShell* shell = dynamic_cast<const GModelSpatialRadialShell*>(model);
1610 if (shell != NULL) { 1440 if (shell != NULL) {
1611 - double shell_radius = shell->radius(); 1441 + double shell_radius = shell->radius() * gammalib::deg2rad;
1612 if (shell_radius > rho_min && shell_radius < rho_max) { 1442 if (shell_radius > rho_min && shell_radius < rho_max) {
1613 bounds.push_back(shell_radius); 1443 bounds.push_back(shell_radius);
1614 } 1444 }
@@ -1654,11 +1484,10 @@ double GCTAResponseIrf::irf_radial(const GEvent&amp; event, @@ -1654,11 +1484,10 @@ double GCTAResponseIrf::irf_radial(const GEvent&amp; event,
1654 // Return IRF value 1484 // Return IRF value
1655 return irf; 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 * @param[in] event Observed event. 1492 * @param[in] event Observed event.
1664 * @param[in] source Source. 1493 * @param[in] source Source.
@@ -1675,17 +1504,16 @@ double GCTAResponseIrf::irf_radial(const GEvent&amp; event, @@ -1675,17 +1504,16 @@ double GCTAResponseIrf::irf_radial(const GEvent&amp; event,
1675 * \f[ 1504 * \f[
1676 * \int_{\rho_{\rm min}}^{\rho_{\rm max}} 1505 * \int_{\rho_{\rm min}}^{\rho_{\rm max}}
1677 * \sin \rho \times 1506 * \sin \rho \times
1678 - * \int_{\omega_{\rm min}}^{\omega_{\rm max}} 1507 + * \int_{\omega}
1679 * S_{\rm p}(\rho, \omega | E, t) \, IRF(\rho, \omega) d\omega d\rho 1508 * S_{\rm p}(\rho, \omega | E, t) \, IRF(\rho, \omega) d\omega d\rho
1680 * \f] 1509 * \f]
1681 * 1510 *
1682 * where 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 * The source model centre is located at \f$\vec{m}\f$, and a spherical 1518 * The source model centre is located at \f$\vec{m}\f$, and a spherical
1691 * coordinate system is defined around this location with \f$(\rho,\omega)\f$ 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,26 +1559,16 @@ double GCTAResponseIrf::irf_elliptical(const GEvent&amp; event,
1731 // Determine angular distance between observed photon direction and model 1559 // Determine angular distance between observed photon direction and model
1732 // centre and position angle of observed photon direction seen from the 1560 // centre and position angle of observed photon direction seen from the
1733 // model centre [radians] 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 // Determine angular distance between model centre and pointing direction 1565 // Determine angular distance between model centre and pointing direction
1742 // [radians] 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 // Compute azimuth angle of pointing in model coordinate system [radians] 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 // Get log10(E/TeV) of true photon energy 1573 // Get log10(E/TeV) of true photon energy
1756 double srcLogEng = srcEng.log10TeV(); 1574 double srcLogEng = srcEng.log10TeV();
@@ -1759,18 +1577,37 @@ double GCTAResponseIrf::irf_elliptical(const GEvent&amp; event, @@ -1759,18 +1577,37 @@ double GCTAResponseIrf::irf_elliptical(const GEvent&amp; event,
1759 // angle (eta) as the true theta angle between the source and the pointing 1577 // angle (eta) as the true theta angle between the source and the pointing
1760 // directions. As we only use the angle to determine the maximum PSF size, 1578 // directions. As we only use the angle to determine the maximum PSF size,
1761 // this should be sufficient. 1579 // this should be sufficient.
1762 - double theta = eta; 1580 + double theta = pnt.dir().dist(obsDir);
1763 double phi = 0.0; //TODO: Implement IRF Phi dependence 1581 double phi = 0.0; //TODO: Implement IRF Phi dependence
1764 double delta_max = psf_delta_max(theta, phi, zenith, azimuth, srcLogEng); 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 // Set zenith angle integration range for elliptical model 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 // Initialise IRF value 1613 // Initialise IRF value
@@ -1782,16 +1619,19 @@ double GCTAResponseIrf::irf_elliptical(const GEvent&amp; event, @@ -1782,16 +1619,19 @@ double GCTAResponseIrf::irf_elliptical(const GEvent&amp; event,
1782 // Setup integration kernel 1619 // Setup integration kernel
1783 cta_irf_elliptical_kern_rho integrand(*this, 1620 cta_irf_elliptical_kern_rho integrand(*this,
1784 *model, 1621 *model,
  1622 + semimajor,
  1623 + semiminor,
  1624 + posangle,
1785 zenith, 1625 zenith,
1786 azimuth, 1626 azimuth,
1787 srcEng, 1627 srcEng,
1788 srcTime, 1628 srcTime,
1789 srcLogEng, 1629 srcLogEng,
1790 obsEng, 1630 obsEng,
1791 - zeta,  
1792 - lambda,  
1793 - obsOmega,  
1794 - omega0, 1631 + rho_obs,
  1632 + posangle_obs,
  1633 + rho_pnt,
  1634 + omega_pnt,
1795 delta_max, 1635 delta_max,
1796 iter_phi); 1636 iter_phi);
1797 1637
@@ -1804,23 +1644,10 @@ double GCTAResponseIrf::irf_elliptical(const GEvent&amp; event, @@ -1804,23 +1644,10 @@ double GCTAResponseIrf::irf_elliptical(const GEvent&amp; event,
1804 bounds.push_back(rho_min); 1644 bounds.push_back(rho_min);
1805 bounds.push_back(rho_max); 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 // Integrate kernel 1653 // Integrate kernel
@@ -1833,8 +1660,7 @@ double GCTAResponseIrf::irf_elliptical(const GEvent&amp; event, @@ -1833,8 +1660,7 @@ double GCTAResponseIrf::irf_elliptical(const GEvent&amp; event,
1833 std::cout << " NaN/Inf encountered"; 1660 std::cout << " NaN/Inf encountered";
1834 std::cout << " (irf=" << irf; 1661 std::cout << " (irf=" << irf;
1835 std::cout << ", rho_min=" << rho_min; 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 std::cout << std::endl; 1664 std::cout << std::endl;
1839 } 1665 }
1840 #endif 1666 #endif
@@ -2188,19 +2014,27 @@ double GCTAResponseIrf::npred_radial(const GSource&amp; source, @@ -2188,19 +2014,27 @@ double GCTAResponseIrf::npred_radial(const GSource&amp; source,
2188 bounds.push_back(rho_max); 2014 bounds.push_back(rho_max);
2189 2015
2190 // If the integration range includes a transition between full 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 double transition_point = roi_psf_radius - roi_model_distance; 2019 double transition_point = roi_psf_radius - roi_model_distance;
2194 if (transition_point > rho_min && transition_point < rho_max) { 2020 if (transition_point > rho_min && transition_point < rho_max) {
2195 bounds.push_back(transition_point); 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 // If we have a shell model then add an integration boundary for the 2032 // If we have a shell model then add an integration boundary for the
2199 // shell radius as a function discontinuity will occur at this 2033 // shell radius as a function discontinuity will occur at this
2200 // location 2034 // location
2201 const GModelSpatialRadialShell* shell = dynamic_cast<const GModelSpatialRadialShell*>(model); 2035 const GModelSpatialRadialShell* shell = dynamic_cast<const GModelSpatialRadialShell*>(model);
2202 if (shell != NULL) { 2036 if (shell != NULL) {
2203 - double shell_radius = shell->radius(); 2037 + double shell_radius = shell->radius() * gammalib::deg2rad;
2204 if (shell_radius > rho_min && shell_radius < rho_max) { 2038 if (shell_radius > rho_min && shell_radius < rho_max) {
2205 bounds.push_back(shell_radius); 2039 bounds.push_back(shell_radius);
2206 } 2040 }
@@ -2327,23 +2161,44 @@ double GCTAResponseIrf::npred_elliptical(const GSource&amp; source, @@ -2327,23 +2161,44 @@ double GCTAResponseIrf::npred_elliptical(const GSource&amp; source,
2327 // to make sure we encompass the entire PSF. 2161 // to make sure we encompass the entire PSF.
2328 double psf_max_radius = psf_delta_max(0.0, 0.0, zenith, azimuth, srcLogEng); 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 // coming from beyond this radius will not make it in the dataspace and 2165 // coming from beyond this radius will not make it in the dataspace and
2338 // thus can be neglected. 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 // Set offset angle integration range. We take here the ROI+PSF into 2194 // Set offset angle integration range. We take here the ROI+PSF into
2342 // account to make no integrations beyond the point where the 2195 // account to make no integrations beyond the point where the
2343 // contribution drops to zero. 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 // Perform offset angle integration only if interval is valid 2203 // Perform offset angle integration only if interval is valid
2349 if (rho_max > rho_min) { 2204 if (rho_max > rho_min) {
@@ -2358,18 +2213,21 @@ double GCTAResponseIrf::npred_elliptical(const GSource&amp; source, @@ -2358,18 +2213,21 @@ double GCTAResponseIrf::npred_elliptical(const GSource&amp; source,
2358 2213
2359 // Compute position angle of ROI centre with respect to model 2214 // Compute position angle of ROI centre with respect to model
2360 // centre (radians) 2215 // centre (radians)
2361 - double omega0 = centre.posang(roi.centre().dir()); 2216 + double posangle_roi = centre.posang(roi.centre().dir());
2362 2217
2363 // Setup integration kernel 2218 // Setup integration kernel
2364 cta_npred_elliptical_kern_rho integrand(*this, 2219 cta_npred_elliptical_kern_rho integrand(*this,
2365 *model, 2220 *model,
2366 - source.energy(),  
2367 - source.time(), 2221 + semimajor,
  2222 + semiminor,
  2223 + posangle,
  2224 + srcEng,
  2225 + srcTime,
2368 cta, 2226 cta,
2369 rot, 2227 rot,
2370 - roi_model_distance,  
2371 - roi_psf_radius,  
2372 - omega0, 2228 + rho_roi,
  2229 + posangle_roi,
  2230 + radius_roi,
2373 iter_phi); 2231 iter_phi);
2374 2232
2375 // Integrate over model's zenith angle 2233 // Integrate over model's zenith angle
@@ -2381,23 +2239,10 @@ double GCTAResponseIrf::npred_elliptical(const GSource&amp; source, @@ -2381,23 +2239,10 @@ double GCTAResponseIrf::npred_elliptical(const GSource&amp; source,
2381 bounds.push_back(rho_min); 2239 bounds.push_back(rho_min);
2382 bounds.push_back(rho_max); 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 // Integrate kernel 2248 // Integrate kernel
inst/cta/src/GCTAResponse_helpers.cpp
@@ -46,12 +46,111 @@ @@ -46,12 +46,111 @@
46 46
47 /* __ Debug definitions __________________________________________________ */ 47 /* __ Debug definitions __________________________________________________ */
48 //#define G_DEBUG_INTEGRAL //!< Debug integration 48 //#define G_DEBUG_INTEGRAL //!< Debug integration
  49 +#define G_DEBUG_MODEL_ZERO //!< Debug check for zero model values
49 50
50 /* __ Constants __________________________________________________________ */ 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 = Helper class methods for response computation = 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,8 +283,26 @@ double cta_irf_radial_kern_rho::eval(const double&amp; rho)
184 double omega_min = -domega; 283 double omega_min = -domega;
185 double omega_max = +domega; 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 // Evaluate sky model 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 // Continue only if model is positive 307 // Continue only if model is positive
191 if (model > 0.0) { 308 if (model > 0.0) {
@@ -336,7 +453,7 @@ double cta_irf_radial_kern_omega::eval(const double&amp; omega) @@ -336,7 +453,7 @@ double cta_irf_radial_kern_omega::eval(const double&amp; omega)
336 * \f] 453 * \f]
337 * 454 *
338 * The azimuth angle integration range 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 * is limited to an arc around the vector connecting the model centre to 457 * is limited to an arc around the vector connecting the model centre to
341 * the ROI centre. This limitation assures that the integration converges 458 * the ROI centre. This limitation assures that the integration converges
342 * properly. 459 * properly.
@@ -364,8 +481,26 @@ double cta_npred_radial_kern_rho::eval(const double&amp; rho) @@ -364,8 +481,26 @@ double cta_npred_radial_kern_rho::eval(const double&amp; rho)
364 double omega_min = m_omega0 - domega; 481 double omega_min = m_omega0 - domega;
365 double omega_max = m_omega0 + domega; 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 // Get radial model value 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 // Continue only if we have a positive model value 505 // Continue only if we have a positive model value
371 if (model > 0.0) { 506 if (model > 0.0) {
@@ -470,7 +605,8 @@ double cta_npred_radial_kern_omega::eval(const double&amp; omega) @@ -470,7 +605,8 @@ double cta_npred_radial_kern_omega::eval(const double&amp; omega)
470 /***********************************************************************//** 605 /***********************************************************************//**
471 * @brief Kernel for elliptical model integration over model's zenith angle 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 * Computes 611 * Computes
476 * 612 *
@@ -480,6 +616,20 @@ double cta_npred_radial_kern_omega::eval(const double&amp; omega) @@ -480,6 +616,20 @@ double cta_npred_radial_kern_omega::eval(const double&amp; omega)
480 * S_{\rm p}(\rho, \omega | E, t) \, IRF(\rho, \omega) 616 * S_{\rm p}(\rho, \omega | E, t) \, IRF(\rho, \omega)
481 * d\omega 617 * d\omega
482 * \f] 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 double cta_irf_elliptical_kern_rho::eval(const double& rho) 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,29 +639,34 @@ double cta_irf_elliptical_kern_rho::eval(const double&amp; rho)
489 // Continue only if rho is positive 639 // Continue only if rho is positive
490 if (rho > 0.0) { 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 double domega = 0.5 * gammalib::cta_roi_arclength(rho, 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 m_delta_max, 649 m_delta_max,
499 m_cos_delta_max); 650 m_cos_delta_max);
500 651
501 // Continue only if arc length is positive 652 // Continue only if arc length is positive
502 if (domega > 0.0) { 653 if (domega > 0.0) {
503 654
504 - // Compute omega integration range  
505 - double omega_min = -domega;  
506 - double omega_max = +domega;  
507 -  
508 // Precompute cosine and sine terms for azimuthal integration 655 // Precompute cosine and sine terms for azimuthal integration
509 double cos_rho = std::cos(rho); 656 double cos_rho = std::cos(rho);
510 double sin_rho = std::sin(rho); 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 // Setup integration kernel 671 // Setup integration kernel
517 cta_irf_elliptical_kern_omega integrand(m_rsp, 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,18 +677,93 @@ double cta_irf_elliptical_kern_rho::eval(const double&amp; rho)
522 m_srcTime, 677 m_srcTime,
523 m_srcLogEng, 678 m_srcLogEng,
524 m_obsEng, 679 m_obsEng,
525 - m_obsOmega,  
526 - m_omega0,  
527 - rho, 680 + m_posangle_obs,
  681 + m_omega_pnt,
  682 + rho_kluge,
528 cos_psf, 683 cos_psf,
529 sin_psf, 684 sin_psf,
530 cos_ph, 685 cos_ph,
531 sin_ph); 686 sin_ph);
532 687
533 - // Integrate over phi 688 + // Setup integrator
534 GIntegral integral(&integrand); 689 GIntegral integral(&integrand);
535 integral.fixed_iter(m_iter); 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 // Compile option: Check for NaN/Inf 768 // Compile option: Check for NaN/Inf
539 #if defined(G_NAN_CHECK) 769 #if defined(G_NAN_CHECK)
@@ -565,12 +795,18 @@ double cta_irf_elliptical_kern_rho::eval(const double&amp; rho) @@ -565,12 +795,18 @@ double cta_irf_elliptical_kern_rho::eval(const double&amp; rho)
565 * Computes 795 * Computes
566 * 796 *
567 * \f[ 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 * \f] 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 * \f[\delta = \arccos(\cos \rho \cos \zeta + 811 * \f[\delta = \arccos(\cos \rho \cos \zeta +
576 * \sin \rho \sin \zeta \cos \omega)\f] 812 * \sin \rho \sin \zeta \cos \omega)\f]
@@ -598,18 +834,42 @@ double cta_irf_elliptical_kern_omega::eval(const double&amp; omega) @@ -598,18 +834,42 @@ double cta_irf_elliptical_kern_omega::eval(const double&amp; omega)
598 // Initialise IRF value 834 // Initialise IRF value
599 double irf = 0.0; 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 // Evaluate sky model 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 // Continue only if model is positive 864 // Continue only if model is positive
605 if (model > 0.0) { 865 if (model > 0.0) {
606 866
607 - // Compute PSF offset angle [radians] 867 + // Compute Psf offset angle [radians]
608 double delta = std::acos(m_cos_psf + m_sin_psf * std::cos(omega)); 868 double delta = std::acos(m_cos_psf + m_sin_psf * std::cos(omega));
609 869
610 // Compute true photon offset and azimuth angle in camera system 870 // Compute true photon offset and azimuth angle in camera system
611 // [radians] 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 double phi = 0.0; //TODO: Implement IRF Phi dependence 873 double phi = 0.0; //TODO: Implement IRF Phi dependence
614 874
615 // Evaluate IRF * model 875 // Evaluate IRF * model
@@ -659,10 +919,20 @@ double cta_irf_elliptical_kern_omega::eval(const double&amp; omega) @@ -659,10 +919,20 @@ double cta_irf_elliptical_kern_omega::eval(const double&amp; omega)
659 * N_{\rm pred}(\rho,\omega) d\omega 919 * N_{\rm pred}(\rho,\omega) d\omega
660 * \f] 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 double cta_npred_elliptical_kern_rho::eval(const double& rho) 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,23 +944,27 @@ double cta_npred_elliptical_kern_rho::eval(const double&amp; rho)
674 944
675 // Compute half length of arc that lies within ROI+PSF radius (radians) 945 // Compute half length of arc that lies within ROI+PSF radius (radians)
676 double domega = 0.5 * gammalib::cta_roi_arclength(rho, 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 // Continue only if arc length is positive 953 // Continue only if arc length is positive
684 if (domega > 0.0) { 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 double sin_rho = std::sin(rho); 957 double sin_rho = std::sin(rho);
692 double cos_rho = std::cos(rho); 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 // Setup phi integration kernel 968 // Setup phi integration kernel
695 cta_npred_elliptical_kern_omega integrand(m_rsp, 969 cta_npred_elliptical_kern_omega integrand(m_rsp,
696 m_model, 970 m_model,
@@ -698,13 +972,90 @@ double cta_npred_elliptical_kern_rho::eval(const double&amp; rho) @@ -698,13 +972,90 @@ double cta_npred_elliptical_kern_rho::eval(const double&amp; rho)
698 m_srcTime, 972 m_srcTime,
699 m_obs, 973 m_obs,
700 m_rot, 974 m_rot,
  975 + rho_kluge,
701 sin_rho, 976 sin_rho,
702 - cos_rho); 977 + cos_rho,
  978 + m_posangle_roi);
703 979
704 - // Integrate over phi 980 + // Setup integrator
705 GIntegral integral(&integrand); 981 GIntegral integral(&integrand);
706 integral.fixed_iter(m_iter); 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 // Debug: Check for NaN 1060 // Debug: Check for NaN
710 #if defined(G_NAN_CHECK) 1061 #if defined(G_NAN_CHECK)
@@ -713,7 +1064,6 @@ double cta_npred_elliptical_kern_rho::eval(const double&amp; rho) @@ -713,7 +1064,6 @@ double cta_npred_elliptical_kern_rho::eval(const double&amp; rho)
713 std::cout << "(rho=" << rho << "):"; 1064 std::cout << "(rho=" << rho << "):";
714 std::cout << " NaN/Inf encountered"; 1065 std::cout << " NaN/Inf encountered";
715 std::cout << " (npred=" << npred; 1066 std::cout << " (npred=" << npred;
716 - std::cout << ", omega=[" << omega_min << "," << omega_max << "]";  
717 std::cout << ", sin_rho=" << sin_rho; 1067 std::cout << ", sin_rho=" << sin_rho;
718 std::cout << ", cos_rho=" << cos_rho; 1068 std::cout << ", cos_rho=" << cos_rho;
719 std::cout << ")" << std::endl; 1069 std::cout << ")" << std::endl;
@@ -737,34 +1087,68 @@ double cta_npred_elliptical_kern_rho::eval(const double&amp; rho) @@ -737,34 +1087,68 @@ double cta_npred_elliptical_kern_rho::eval(const double&amp; rho)
737 * Computes 1087 * Computes
738 * 1088 *
739 * \f[ 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 * \f] 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 double cta_npred_elliptical_kern_omega::eval(const double& omega) 1103 double cta_npred_elliptical_kern_omega::eval(const double& omega)
744 { 1104 {
745 // Initialise Npred value 1105 // Initialise Npred value
746 double npred = 0.0; 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 // Compute Npred for this sky direction 1153 // Compute Npred for this sky direction
770 npred = m_rsp.npred(photon, m_obs) * model; 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,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 * \f[ 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 * \f] 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 * \f[ 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 * \int_0^{2\pi} {\rm M}(\delta, \phi) \sin \delta {\rm d}\phi 1714 * \int_0^{2\pi} {\rm M}(\delta, \phi) \sin \delta {\rm d}\phi
1264 * \f] 1715 * \f]
1265 * 1716 *
@@ -1273,44 +1724,70 @@ double cta_psf_radial_kern_delta::eval(const double&amp; delta) @@ -1273,44 +1724,70 @@ double cta_psf_radial_kern_delta::eval(const double&amp; delta)
1273 // Initialise value 1724 // Initialise value
1274 double value = 0.0; 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 // term. We thus only integrate for positive deltas. 1728 // term. We thus only integrate for positive deltas.
1278 if (delta > 0.0) { 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 // Return kernel value 1792 // Return kernel value
1316 return value; 1793 return value;
@@ -1343,8 +1820,26 @@ double cta_psf_radial_kern_phi::eval(const double&amp; phi) @@ -1343,8 +1820,26 @@ double cta_psf_radial_kern_phi::eval(const double&amp; phi)
1343 // Compute radial model theta angle 1820 // Compute radial model theta angle
1344 double theta = std::acos(m_cos_fact + m_sin_fact * std::cos(phi)); 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 // Get radial model value 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 // Debug: Check for NaN 1844 // Debug: Check for NaN
1350 #if defined(G_NAN_CHECK) 1845 #if defined(G_NAN_CHECK)
@@ -1363,238 +1858,330 @@ double cta_psf_radial_kern_phi::eval(const double&amp; phi) @@ -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 * \f[ 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 * \f] 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 GIntegral integral(&integrand); 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 * \f[ 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 * \f] 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 #endif 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 * Computes the azimuthally integrated product of point spread function and 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 * not included in the azimuthally integration, but just multiplied on the 2135 * not included in the azimuthally integration, but just multiplied on the
1511 * azimuthally integrated model. The method returns thus 2136 * azimuthally integrated model. The method returns thus
1512 * 2137 *
1513 * \f[ 2138 * \f[
1514 - * {\rm Psf}(\delta) \times 2139 + * {\rm PSF}(\delta) \times
1515 * \int_0^{2\pi} {\rm M}(\delta, \phi) \sin \delta {\rm d}\phi 2140 * \int_0^{2\pi} {\rm M}(\delta, \phi) \sin \delta {\rm d}\phi
1516 * \f] 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 * system of the point spread function, defined by the angle \f$\delta\f$ 2144 * system of the point spread function, defined by the angle \f$\delta\f$
1520 * between the true and the measured photon direction and the azimuth angle 2145 * between the true and the measured photon direction and the azimuth angle
1521 * \f$\phi\f$ around the measured photon direction. 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 // Initialise value 2150 // Initialise value
1526 double value = 0.0; 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 // term. We thus only integrate for positive deltas. 2154 // term. We thus only integrate for positive deltas.
1530 if (delta > 0.0) { 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 if (value > 0.0) { 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 } // endif: delta was positive 2179 } // endif: delta was positive
1593 2180
1594 // Debug: Check for NaN 2181 // Debug: Check for NaN
1595 #if defined(G_NAN_CHECK) 2182 #if defined(G_NAN_CHECK)
1596 if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) { 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 std::cout << "(delta=" << delta << "):"; 2185 std::cout << "(delta=" << delta << "):";
1599 std::cout << " NaN/Inf encountered"; 2186 std::cout << " NaN/Inf encountered";
1600 std::cout << " (value=" << value; 2187 std::cout << " (value=" << value;
@@ -1608,79 +2195,40 @@ double cta_irf_radial_kern_delta::eval(const double&amp; delta) @@ -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 * @param[in] phi Azimuth angle (radians). 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 // Debug: Check for NaN 2228 // Debug: Check for NaN
1681 #if defined(G_NAN_CHECK) 2229 #if defined(G_NAN_CHECK)
1682 if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) { 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 std::cout << "(phi=" << phi << "):"; 2232 std::cout << "(phi=" << phi << "):";
1685 std::cout << " NaN/Inf encountered"; 2233 std::cout << " NaN/Inf encountered";
1686 std::cout << " (value=" << value; 2234 std::cout << " (value=" << value;
inst/cta/src/GCTAResponse_helpers.hpp
@@ -29,6 +29,8 @@ @@ -29,6 +29,8 @@
29 29
30 /* __ Includes ___________________________________________________________ */ 30 /* __ Includes ___________________________________________________________ */
31 #include <cmath> 31 #include <cmath>
  32 +#include <vector>
  33 +#include <utility>
32 #include "GCTAResponseIrf.hpp" 34 #include "GCTAResponseIrf.hpp"
33 #include "GCTAObservation.hpp" 35 #include "GCTAObservation.hpp"
34 #include "GMatrix.hpp" 36 #include "GMatrix.hpp"
@@ -44,6 +46,16 @@ @@ -44,6 +46,16 @@
44 46
45 /* __ Forward declaration ________________________________________________ */ 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 * @class cta_npsf_kern_rad_azsym 61 * @class cta_npsf_kern_rad_azsym
@@ -433,55 +445,64 @@ class cta_irf_elliptical_kern_rho : public GFunction { @@ -433,55 +445,64 @@ class cta_irf_elliptical_kern_rho : public GFunction {
433 public: 445 public:
434 cta_irf_elliptical_kern_rho(const GCTAResponseIrf& rsp, 446 cta_irf_elliptical_kern_rho(const GCTAResponseIrf& rsp,
435 const GModelSpatialElliptical& model, 447 const GModelSpatialElliptical& model,
  448 + const double& semimajor,
  449 + const double& semiminor,
  450 + const double& posangle,
436 const double& zenith, 451 const double& zenith,
437 const double& azimuth, 452 const double& azimuth,
438 const GEnergy& srcEng, 453 const GEnergy& srcEng,
439 const GTime& srcTime, 454 const GTime& srcTime,
440 const double& srcLogEng, 455 const double& srcLogEng,
441 const GEnergy& obsEng, 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 const double& delta_max, 461 const double& delta_max,
447 const int& iter) : 462 const int& iter) :
448 m_rsp(rsp), 463 m_rsp(rsp),
449 m_model(model), 464 m_model(model),
  465 + m_semimajor(semimajor),
  466 + m_semiminor(semiminor),
  467 + m_posangle(posangle),
450 m_zenith(zenith), 468 m_zenith(zenith),
451 m_azimuth(azimuth), 469 m_azimuth(azimuth),
452 m_srcEng(srcEng), 470 m_srcEng(srcEng),
453 m_srcTime(srcTime), 471 m_srcTime(srcTime),
454 m_srcLogEng(srcLogEng), 472 m_srcLogEng(srcLogEng),
455 m_obsEng(obsEng), 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 m_delta_max(delta_max), 482 m_delta_max(delta_max),
465 m_cos_delta_max(std::cos(delta_max)), 483 m_cos_delta_max(std::cos(delta_max)),
466 m_iter(iter) { } 484 m_iter(iter) { }
467 double eval(const double& rho); 485 double eval(const double& rho);
468 public: 486 public:
469 const GCTAResponseIrf& m_rsp; //!< CTA response 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 const double& m_zenith; //!< Zenith angle 492 const double& m_zenith; //!< Zenith angle
472 const double& m_azimuth; //!< Azimuth angle 493 const double& m_azimuth; //!< Azimuth angle
473 const GEnergy& m_srcEng; //!< True photon energy 494 const GEnergy& m_srcEng; //!< True photon energy
474 const GTime& m_srcTime; //!< True photon time 495 const GTime& m_srcTime; //!< True photon time
475 const double& m_srcLogEng; //!< True photon log energy 496 const double& m_srcLogEng; //!< True photon log energy
476 const GEnergy& m_obsEng; //!< Measured event energy 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 const double& m_delta_max; //!< Maximum PSF radius 506 const double& m_delta_max; //!< Maximum PSF radius
486 double m_cos_delta_max; //!< Cosine of maximum PSF radius 507 double m_cos_delta_max; //!< Cosine of maximum PSF radius
487 const int& m_iter; //!< Integration iterations 508 const int& m_iter; //!< Integration iterations
@@ -517,8 +538,8 @@ public: @@ -517,8 +538,8 @@ public:
517 const GTime& srcTime, 538 const GTime& srcTime,
518 const double& srcLogEng, 539 const double& srcLogEng,
519 const GEnergy& obsEng, 540 const GEnergy& obsEng,
520 - const double& obsOmega,  
521 - const double& omega0, 541 + const double& posangle_obs,
  542 + const double& omega_pnt,
522 const double& rho, 543 const double& rho,
523 const double& cos_psf, 544 const double& cos_psf,
524 const double& sin_psf, 545 const double& sin_psf,
@@ -532,8 +553,8 @@ public: @@ -532,8 +553,8 @@ public:
532 m_srcTime(srcTime), 553 m_srcTime(srcTime),
533 m_srcLogEng(srcLogEng), 554 m_srcLogEng(srcLogEng),
534 m_obsEng(obsEng), 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 m_rho(rho), 558 m_rho(rho),
538 m_cos_psf(cos_psf), 559 m_cos_psf(cos_psf),
539 m_sin_psf(sin_psf), 560 m_sin_psf(sin_psf),
@@ -541,21 +562,21 @@ public: @@ -541,21 +562,21 @@ public:
541 m_sin_ph(sin_ph) { } 562 m_sin_ph(sin_ph) { }
542 double eval(const double& omega); 563 double eval(const double& omega);
543 public: 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,42 +615,51 @@ class cta_npred_elliptical_kern_rho : public GFunction {
594 public: 615 public:
595 cta_npred_elliptical_kern_rho(const GCTAResponseIrf& rsp, 616 cta_npred_elliptical_kern_rho(const GCTAResponseIrf& rsp,
596 const GModelSpatialElliptical& model, 617 const GModelSpatialElliptical& model,
  618 + const double& semimajor,
  619 + const double& semiminor,
  620 + const double& posangle,
597 const GEnergy& srcEng, 621 const GEnergy& srcEng,
598 const GTime& srcTime, 622 const GTime& srcTime,
599 const GCTAObservation& obs, 623 const GCTAObservation& obs,
600 const GMatrix& rot, 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 const int& iter) : 628 const int& iter) :
605 m_rsp(rsp), 629 m_rsp(rsp),
606 m_model(model), 630 m_model(model),
  631 + m_semimajor(semimajor),
  632 + m_semiminor(semiminor),
  633 + m_posangle(posangle),
607 m_srcEng(srcEng), 634 m_srcEng(srcEng),
608 m_srcTime(srcTime), 635 m_srcTime(srcTime),
609 m_obs(obs), 636 m_obs(obs),
610 m_rot(rot), 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 m_iter(iter) { } 644 m_iter(iter) { }
618 double eval(const double& rho); 645 double eval(const double& rho);
619 protected: 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,26 +693,32 @@ public:
663 const GTime& srcTime, 693 const GTime& srcTime,
664 const GCTAObservation& obs, 694 const GCTAObservation& obs,
665 const GMatrix& rot, 695 const GMatrix& rot,
  696 + const double& rho,
666 const double& sin_rho, 697 const double& sin_rho,
667 - const double& cos_rho) : 698 + const double& cos_rho,
  699 + const double& posangle_roi) :
668 m_rsp(rsp), 700 m_rsp(rsp),
669 m_model(model), 701 m_model(model),
670 m_srcEng(srcEng), 702 m_srcEng(srcEng),
671 m_srcTime(srcTime), 703 m_srcTime(srcTime),
672 m_obs(obs), 704 m_obs(obs),
673 m_rot(rot), 705 m_rot(rot),
  706 + m_rho(rho),
674 m_sin_rho(sin_rho), 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 double eval(const double& omega); 710 double eval(const double& omega);
677 protected: 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,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 public: 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 public: 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,32 +1102,33 @@ public:
1025 const GSkyDir& srcDir, 1102 const GSkyDir& srcDir,
1026 const GEnergy& srcEng, 1103 const GEnergy& srcEng,
1027 const GTime& srcTime, 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 m_rsp(rsp), 1108 m_rsp(rsp),
1033 m_model(model), 1109 m_model(model),
1034 m_srcDir(srcDir), 1110 m_srcDir(srcDir),
1035 m_srcEng(srcEng), 1111 m_srcEng(srcEng),
1036 m_srcTime(srcTime), 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 double eval(const double& delta); 1119 double eval(const double& delta);
1043 protected: 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,197 +1154,205 @@ protected:
1076 const GModelSpatialRadial* m_model; //!< Radial model 1154 const GModelSpatialRadial* m_model; //!< Radial model
1077 const GEnergy& m_srcEng; //!< True photon energy 1155 const GEnergy& m_srcEng; //!< True photon energy
1078 const GTime& m_srcTime; //!< True photon arrival time 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 public: 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 const GModelSpatialElliptical* model, 1260 const GModelSpatialElliptical* model,
1093 const GSkyDir& srcDir, 1261 const GSkyDir& srcDir,
1094 const GEnergy& srcEng, 1262 const GEnergy& srcEng,
1095 const GTime& srcTime, 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 m_rsp(rsp), 1268 m_rsp(rsp),
1102 m_model(model), 1269 m_model(model),
1103 m_srcDir(srcDir), 1270 m_srcDir(srcDir),
1104 m_srcEng(srcEng), 1271 m_srcEng(srcEng),
1105 m_srcTime(srcTime), 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 public: 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 public: 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 double eval(const double& delta); 1315 double eval(const double& delta);
1201 protected: 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 public: 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 double eval(const double& phi); 1348 double eval(const double& phi);
1257 protected: 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 #endif /* GCTARESPONSE_HELPERS_HPP */ 1358 #endif /* GCTARESPONSE_HELPERS_HPP */
inst/cta/test/benchmark_ml_fitting.py
@@ -59,7 +59,10 @@ def unbinned_analysis(model, evtfile, irf, caldb): @@ -59,7 +59,10 @@ def unbinned_analysis(model, evtfile, irf, caldb):
59 obs.models(model) 59 obs.models(model)
60 60
61 # Allocate Levenberg-Marquardt optimizer 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 # Optimize model parameters 67 # Optimize model parameters
65 obs.optimize(opt) 68 obs.optimize(opt)
@@ -113,7 +116,10 @@ def binned_analysis(model, cntmap, irf, caldb): @@ -113,7 +116,10 @@ def binned_analysis(model, cntmap, irf, caldb):
113 obs.models(model) 116 obs.models(model)
114 117
115 # Allocate Levenberg-Marquardt optimizer 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 # Optimize model parameters 124 # Optimize model parameters
119 obs.optimize(opt) 125 obs.optimize(opt)
@@ -169,7 +175,10 @@ def stacked_analysis(model, cntmap, expcube, psfcube): @@ -169,7 +175,10 @@ def stacked_analysis(model, cntmap, expcube, psfcube):
169 obs.models(model) 175 obs.models(model)
170 176
171 # Allocate Levenberg-Marquardt optimizer 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 # Optimize model parameters 183 # Optimize model parameters
175 obs.optimize(opt) 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,7 +82,7 @@ def show_likelihood_profile(result):
82 # ========================== # 82 # ========================== #
83 # Compute likelihood profile # 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 Compute the log-likelihood profile for a given parameter. 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,12 +93,12 @@ def compute_likelihood_profile(obs, parname=&quot;RA&quot;, scale=1.0e-4, steps=10):
93 """ 93 """
94 # Free parameter for which profile is to be determined 94 # Free parameter for which profile is to be determined
95 obs.models()[0][parname].free() 95 obs.models()[0][parname].free()
96 - obs.models()[0]["DEC"].free()  
97 96
98 # Allocate Levenberg-Marquardt optimizer 97 # Allocate Levenberg-Marquardt optimizer
99 log = gammalib.GLog() 98 log = gammalib.GLog()
100 log.cout(True) 99 log.cout(True)
101 opt = gammalib.GOptimizerLM(log) 100 opt = gammalib.GOptimizerLM(log)
  101 + opt.eps(5.0e-3)
102 102
103 # Optimize model parameters 103 # Optimize model parameters
104 obs.optimize(opt) 104 obs.optimize(opt)
@@ -256,8 +256,8 @@ if __name__ == &#39;__main__&#39;: @@ -256,8 +256,8 @@ if __name__ == &#39;__main__&#39;:
256 256
257 # Set test 257 # Set test
258 #test = "point" 258 #test = "point"
259 - test = "gauss"  
260 - #test = "disk" 259 + #test = "gauss"
  260 + test = "disk"
261 #test = "ldisk" 261 #test = "ldisk"
262 #test = "shell" 262 #test = "shell"
263 #test = "ellipse" 263 #test = "ellipse"
@@ -309,10 +309,10 @@ if __name__ == &#39;__main__&#39;: @@ -309,10 +309,10 @@ if __name__ == &#39;__main__&#39;:
309 cntref = 337.5 # +/- 1.8 (from ctobssim) 309 cntref = 337.5 # +/- 1.8 (from ctobssim)
310 310
311 # Perform unbinned computation 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 # Perform binned computation 314 # Perform binned computation
315 #original_likelihood_profile(model, cntmap, irf, gammalib.GCaldb(caldb), cntref) 315 #original_likelihood_profile(model, cntmap, irf, gammalib.GCaldb(caldb), cntref)
316 316
317 # Perform stacked computation 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,23 +617,8 @@ void TestGLATObservation::test_one_binned_obs(const std::string&amp; datadir, const
617 void TestGLATOptimize::test_binned_optimizer_p6(void) 617 void TestGLATOptimize::test_binned_optimizer_p6(void)
618 { 618 {
619 // Set expected fit results 619 // Set expected fit results
620 - /*  
621 double fit_results[] = {1, 0, 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 1, 0, 622 1, 0,
638 1, 0, 623 1, 0,
639 0.8167256818, 0.08569407542, 624 0.8167256818, 0.08569407542,
@@ -663,23 +648,8 @@ void TestGLATOptimize::test_binned_optimizer_p6(void) @@ -663,23 +648,8 @@ void TestGLATOptimize::test_binned_optimizer_p6(void)
663 void TestGLATOptimize::test_binned_optimizer_p7(void) 648 void TestGLATOptimize::test_binned_optimizer_p7(void)
664 { 649 {
665 // Set expected fit results 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 double fit_results[] = {1, 0, 651 double fit_results[] = {1, 0,
682 - 2.374909569, 0.4548979, 652 + 2.37468, 0.4548979,
683 1, 0, 653 1, 0,
684 1, 0, 654 1, 0,
685 0.8419824722, 0.06340531926, 655 0.8419824722, 0.06340531926,
@@ -687,7 +657,7 @@ void TestGLATOptimize::test_binned_optimizer_p7(void) @@ -687,7 +657,7 @@ void TestGLATOptimize::test_binned_optimizer_p7(void)
687 83.6331, 0, 657 83.6331, 0,
688 22.0145, 0, 658 22.0145, 0,
689 1.922525774e-06, 1.209507237e-07, 659 1.922525774e-06, 1.209507237e-07,
690 - -2.124145722, 0.0493105484, 660 + -2.12421, 0.0493105484,
691 100, 0, 661 100, 0,
692 500000, 0, 662 500000, 0,
693 1, 0}; 663 1, 0};
inst/mwl/test/test_MWL.cpp
1 /*************************************************************************** 1 /***************************************************************************
2 * test_MWL.cpp - Test multi-wavelength classes * 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 * This program is free software: you can redistribute it and/or modify * 7 * This program is free software: you can redistribute it and/or modify *
@@ -219,13 +219,13 @@ void TestGMWL::test_optimizer(void) @@ -219,13 +219,13 @@ void TestGMWL::test_optimizer(void)
219 double fit_results[] = {83.6331, 0, 219 double fit_results[] = {83.6331, 0,
220 22.0145, 0, 220 22.0145, 0,
221 0.00231438040883405, 0.000191759607698647, 221 0.00231438040883405, 0.000191759607698647,
222 - -3.1111154491587, 0.125598, 222 + -3.12006, 0.128382,
223 1, 0, 223 1, 0,
224 1, 0, 224 1, 0,
225 83.6331, 0, 225 83.6331, 0,
226 22.0145, 0, 226 22.0145, 0,
227 4.5050091460345e-11, 1.95885803229878e-12, 227 4.5050091460345e-11, 1.95885803229878e-12,
228 - -2.13740092737976, 0.00578641, 228 + -2.13765, 0.00578641,
229 1000, 0, 229 1000, 0,
230 1, 0}; 230 1, 0};
231 test_try("Perform LM optimization"); 231 test_try("Perform LM optimization");
pyext/GModelSpatialElliptical.i
@@ -73,6 +73,10 @@ public: @@ -73,6 +73,10 @@ public:
73 void dec(const double& dec); 73 void dec(const double& dec);
74 double posangle(void) const; 74 double posangle(void) const;
75 void posangle(const double& posangle); 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 GSkyDir dir(void) const; 80 GSkyDir dir(void) const;
77 void dir(const GSkyDir& dir); 81 void dir(const GSkyDir& dir);
78 }; 82 };
pyext/GModelSpatialEllipticalDisk.i
1 /*************************************************************************** 1 /***************************************************************************
2 * GModelSpatialEllipticalDisk.i - Elliptical disk source model class * 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 * This program is free software: you can redistribute it and/or modify * 7 * This program is free software: you can redistribute it and/or modify *
@@ -65,12 +65,6 @@ public: @@ -65,12 +65,6 @@ public:
65 virtual double theta_max(void) const; 65 virtual double theta_max(void) const;
66 virtual void read(const GXmlElement& xml); 66 virtual void read(const GXmlElement& xml);
67 virtual void write(GXmlElement& xml) const; 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 * GModelSpatialElliptical.cpp - Abstract elliptical spatial model class * 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 * This program is free software: you can redistribute it and/or modify * 7 * This program is free software: you can redistribute it and/or modify *
@@ -467,11 +467,35 @@ void GModelSpatialElliptical::init_members(void) @@ -467,11 +467,35 @@ void GModelSpatialElliptical::init_members(void)
467 m_posangle.gradient(0.0); 467 m_posangle.gradient(0.0);
468 m_posangle.has_grad(false); 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 // Set parameter pointer(s) 492 // Set parameter pointer(s)
471 m_pars.clear(); 493 m_pars.clear();
472 m_pars.push_back(&m_ra); 494 m_pars.push_back(&m_ra);
473 m_pars.push_back(&m_dec); 495 m_pars.push_back(&m_dec);
474 m_pars.push_back(&m_posangle); 496 m_pars.push_back(&m_posangle);
  497 + m_pars.push_back(&m_semimajor);
  498 + m_pars.push_back(&m_semiminor);
475 499
476 // Return 500 // Return
477 return; 501 return;
@@ -486,15 +510,19 @@ void GModelSpatialElliptical::init_members(void) @@ -486,15 +510,19 @@ void GModelSpatialElliptical::init_members(void)
486 void GModelSpatialElliptical::copy_members(const GModelSpatialElliptical& model) 510 void GModelSpatialElliptical::copy_members(const GModelSpatialElliptical& model)
487 { 511 {
488 // Copy members 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 // Set parameter pointer(s) 519 // Set parameter pointer(s)
494 m_pars.clear(); 520 m_pars.clear();
495 m_pars.push_back(&m_ra); 521 m_pars.push_back(&m_ra);
496 m_pars.push_back(&m_dec); 522 m_pars.push_back(&m_dec);
497 m_pars.push_back(&m_posangle); 523 m_pars.push_back(&m_posangle);
  524 + m_pars.push_back(&m_semimajor);
  525 + m_pars.push_back(&m_semiminor);
498 526
499 // Return 527 // Return
500 return; 528 return;
src/model/GModelSpatialEllipticalDisk.cpp
1 /*************************************************************************** 1 /***************************************************************************
2 * GModelSpatialEllipticalDisk.cpp - Elliptical disk source model class * 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 * This program is free software: you can redistribute it and/or modify * 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,7 +405,7 @@ GSkyDir GModelSpatialEllipticalDisk::mc(const GEnergy&amp; energy,
405 /***********************************************************************//** 405 /***********************************************************************//**
406 * @brief Return maximum model radius (in radians) 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 double GModelSpatialEllipticalDisk::theta_max(void) const 410 double GModelSpatialEllipticalDisk::theta_max(void) const
411 { 411 {
@@ -634,32 +634,6 @@ std::string GModelSpatialEllipticalDisk::print(const GChatter&amp; chatter) const @@ -634,32 +634,6 @@ std::string GModelSpatialEllipticalDisk::print(const GChatter&amp; chatter) const
634 ***************************************************************************/ 634 ***************************************************************************/
635 void GModelSpatialEllipticalDisk::init_members(void) 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 // Initialise precomputation cache. Note that zero values flag 637 // Initialise precomputation cache. Note that zero values flag
664 // uninitialised as a zero radius is not meaningful 638 // uninitialised as a zero radius is not meaningful
665 m_last_semiminor = 0.0; 639 m_last_semiminor = 0.0;
@@ -684,10 +658,6 @@ void GModelSpatialEllipticalDisk::init_members(void) @@ -684,10 +658,6 @@ void GModelSpatialEllipticalDisk::init_members(void)
684 ***************************************************************************/ 658 ***************************************************************************/
685 void GModelSpatialEllipticalDisk::copy_members(const GModelSpatialEllipticalDisk& model) 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 // Copy precomputation cache 661 // Copy precomputation cache
692 m_last_semiminor = model.m_last_semiminor; 662 m_last_semiminor = model.m_last_semiminor;
693 m_last_semimajor = model.m_last_semimajor; 663 m_last_semimajor = model.m_last_semimajor;
@@ -722,7 +692,7 @@ void GModelSpatialEllipticalDisk::free_members(void) @@ -722,7 +692,7 @@ void GModelSpatialEllipticalDisk::free_members(void)
722 ***************************************************************************/ 692 ***************************************************************************/
723 void GModelSpatialEllipticalDisk::update() const 693 void GModelSpatialEllipticalDisk::update() const
724 { 694 {
725 - // Update if one axis has changed has changed 695 + // Update if one axis has changed
726 if (m_last_semiminor != semiminor() || m_last_semimajor != semimajor()) { 696 if (m_last_semiminor != semiminor() || m_last_semimajor != semimajor()) {
727 697
728 // Store last values 698 // Store last values
src/opt/GOptimizerLM.cpp
@@ -635,10 +635,10 @@ void GOptimizerLM::init_members(void) @@ -635,10 +635,10 @@ void GOptimizerLM::init_members(void)
635 m_lambda_start = 1.0e-3; 635 m_lambda_start = 1.0e-3;
636 m_lambda_inc = 10.0; 636 m_lambda_inc = 10.0;
637 m_lambda_dec = 0.1; 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 m_max_stall = 10; 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 m_step_adjust = true; 642 m_step_adjust = true;
643 643
644 // Initialise bookkeeping arrays 644 // Initialise bookkeeping arrays