Commit f636ce90b078fc3d8f90cfb82d3f5d2bb28d6bef
Merge branch '1304-improve-ellipse-fitting' into integration
Conflicts: ChangeLog inst/cta/src/GCTAResponse_helpers.cpp inst/cta/src/GCTASupport.hpp
Showing
22 changed files
with
3259 additions
and
1300 deletions
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 |
NEWS
| @@ -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& posangle) | @@ -215,6 +221,64 @@ void GModelSpatialElliptical::posangle(const double& 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& event, | @@ -307,14 +310,14 @@ double GCTAResponseCube::irf(const GEvent& 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& event, | @@ -347,15 +350,14 @@ double GCTAResponseCube::irf(const GEvent& 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& event, | @@ -425,15 +427,14 @@ double GCTAResponseCube::irf_ptsrc(const GEvent& 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& event, | @@ -462,19 +463,19 @@ double GCTAResponseCube::irf_radial(const GEvent& 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& event, | @@ -496,15 +497,14 @@ double GCTAResponseCube::irf_radial(const GEvent& 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& event, | @@ -533,20 +533,20 @@ double GCTAResponseCube::irf_elliptical(const GEvent& 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& event, | @@ -568,21 +568,20 @@ double GCTAResponseCube::irf_elliptical(const GEvent& 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& event, | @@ -651,9 +650,13 @@ double GCTAResponseCube::irf_diffuse(const GEvent& 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& photon, | @@ -668,21 +671,21 @@ double GCTAResponseCube::npred(const GPhoton& 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& xml) | @@ -737,18 +740,18 @@ void GCTAResponseCube::read(const GXmlElement& 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& xml) const | @@ -773,10 +776,10 @@ void GCTAResponseCube::write(GXmlElement& 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& chatter) const | @@ -818,20 +821,6 @@ std::string GCTAResponseCube::print(const GChatter& 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& name) const | @@ -921,142 +912,362 @@ int GCTAResponseCube::cache_index(const std::string& 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& chatter) const | @@ -1267,7 +1267,7 @@ std::string GCTAResponseIrf::print(const GChatter& 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& chatter) const | @@ -1281,192 +1281,22 @@ std::string GCTAResponseIrf::print(const GChatter& 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& event, | @@ -1596,8 +1426,8 @@ double GCTAResponseIrf::irf_radial(const GEvent& 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& event, | @@ -1608,7 +1438,7 @@ double GCTAResponseIrf::irf_radial(const GEvent& 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& event, | @@ -1654,11 +1484,10 @@ double GCTAResponseIrf::irf_radial(const GEvent& 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& event, | @@ -1675,17 +1504,16 @@ double GCTAResponseIrf::irf_radial(const GEvent& 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& event, | @@ -1731,26 +1559,16 @@ double GCTAResponseIrf::irf_elliptical(const GEvent& 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& event, | @@ -1759,18 +1577,37 @@ double GCTAResponseIrf::irf_elliptical(const GEvent& 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& event, | @@ -1782,16 +1619,19 @@ double GCTAResponseIrf::irf_elliptical(const GEvent& 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& event, | @@ -1804,23 +1644,10 @@ double GCTAResponseIrf::irf_elliptical(const GEvent& 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& event, | @@ -1833,8 +1660,7 @@ double GCTAResponseIrf::irf_elliptical(const GEvent& 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& source, | @@ -2188,19 +2014,27 @@ double GCTAResponseIrf::npred_radial(const GSource& 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& source, | @@ -2327,23 +2161,44 @@ double GCTAResponseIrf::npred_elliptical(const GSource& 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& source, | @@ -2358,18 +2213,21 @@ double GCTAResponseIrf::npred_elliptical(const GSource& 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& source, | @@ -2381,23 +2239,10 @@ double GCTAResponseIrf::npred_elliptical(const GSource& 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& rho) | @@ -184,8 +283,26 @@ double cta_irf_radial_kern_rho::eval(const double& 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& omega) | @@ -336,7 +453,7 @@ double cta_irf_radial_kern_omega::eval(const double& 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& rho) | @@ -364,8 +481,26 @@ double cta_npred_radial_kern_rho::eval(const double& 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& omega) | @@ -470,7 +605,8 @@ double cta_npred_radial_kern_omega::eval(const double& 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& omega) | @@ -480,6 +616,20 @@ double cta_npred_radial_kern_omega::eval(const double& 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& rho) | @@ -489,29 +639,34 @@ double cta_irf_elliptical_kern_rho::eval(const double& 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& rho) | @@ -522,18 +677,93 @@ double cta_irf_elliptical_kern_rho::eval(const double& 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& rho) | @@ -565,12 +795,18 @@ double cta_irf_elliptical_kern_rho::eval(const double& 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& omega) | @@ -598,18 +834,42 @@ double cta_irf_elliptical_kern_omega::eval(const double& 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& omega) | @@ -659,10 +919,20 @@ double cta_irf_elliptical_kern_omega::eval(const double& 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& rho) | @@ -674,23 +944,27 @@ double cta_npred_elliptical_kern_rho::eval(const double& 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& rho) | @@ -698,13 +972,90 @@ double cta_npred_elliptical_kern_rho::eval(const double& 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& rho) | @@ -713,7 +1064,6 @@ double cta_npred_elliptical_kern_rho::eval(const double& 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& rho) | @@ -737,34 +1087,68 @@ double cta_npred_elliptical_kern_rho::eval(const double& 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& phi) | @@ -1130,136 +1514,203 @@ double cta_npred_diffuse_kern_phi::eval(const double& 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& delta) | @@ -1273,44 +1724,70 @@ double cta_psf_radial_kern_delta::eval(const double& 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& phi) | @@ -1343,8 +1820,26 @@ double cta_psf_radial_kern_phi::eval(const double& 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& phi) | @@ -1363,238 +1858,330 @@ double cta_psf_radial_kern_phi::eval(const double& 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& delta) | @@ -1608,79 +2195,40 @@ double cta_irf_radial_kern_delta::eval(const double& 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 | |||
| 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="RA", scale=1.0e-4, steps=10): | @@ -93,12 +93,12 @@ def compute_likelihood_profile(obs, parname="RA", 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__ == '__main__': | @@ -256,8 +256,8 @@ if __name__ == '__main__': | ||
| 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__ == '__main__': | @@ -309,10 +309,10 @@ if __name__ == '__main__': | ||
| 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 | |||
| 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& datadir, const | @@ -617,23 +617,8 @@ void TestGLATObservation::test_one_binned_obs(const std::string& 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& energy, | @@ -405,7 +405,7 @@ GSkyDir GModelSpatialEllipticalDisk::mc(const GEnergy& 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& chatter) const | @@ -634,32 +634,6 @@ std::string GModelSpatialEllipticalDisk::print(const GChatter& 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 |