Commit 05fc86b04f3eee85d1e473ae9c7e2c011ce1430b

Authored by Jürgen Knödlseder
1 parent b3e3958f

Revise GCTAEdisp2D class (#2704)

inst/cta/include/GCTAEdisp2D.hpp
@@ -54,45 +54,37 @@ namespace gammalib { @@ -54,45 +54,37 @@ namespace gammalib {
54 * 54 *
55 * This class implements the energy dispersion for the CTA 2D response. The 55 * This class implements the energy dispersion for the CTA 2D response. The
56 * CTA 2D energy dispersion is in fact a 3-dimensional function, where the 56 * CTA 2D energy dispersion is in fact a 3-dimensional function, where the
57 - * energy dispersion is given as function of true energy \f$E_{\rm true}\f$,  
58 - * migration value \f$m = E_{\rm reco}/E_{\rm true}\f$ and offset angle  
59 - * \f$\theta\f$ 57 + * energy dispersion
60 * 58 *
61 * \f[ 59 * \f[
62 - * E_{\rm disp}(E_{\rm true}, m, \theta) 60 + * E_{\rm disp}(E_{\rm reco} | E_{\rm true}, \theta)
63 * \f] 61 * \f]
64 * 62 *
65 - * with 63 + * is given as function of true energy \f$E_{\rm true}\f$, reconstructed
  64 + * energy \f$E_{\rm reco}\f$ and offset angle \f$\theta\f$. The
  65 + * GCTAEdisp2D::operator() returns the value of the function
  66 + * \f$E_{\rm disp}(E_{\rm reco} | E_{\rm true}, \theta)\f$ in units of
  67 + * MeV\f$^{-1}\f$ with
66 * 68 *
67 * \f[ 69 * \f[
68 - * \int_{m_{\rm min}}^{m_{\rm max}}  
69 - * E_{\rm disp}(E_{\rm true}, m, \theta) \, dm = 1  
70 - * \f]  
71 - *  
72 - * The energy dispersion as function of reconstructed energy  
73 - * \f$E_{\rm reco}\f$ is given by  
74 - *  
75 - * \f[  
76 - * E_{\rm disp}(E_{\rm true}, E_{\rm reco}, \theta) =  
77 - * \frac{E_{\rm disp}(E_{\rm true}, m, \theta)}{E_{\rm true}} 70 + * \int_{E_{\rm reco}^{\rm min}}^{E_{\rm reco}^{\rm max}}
  71 + * E_{\rm disp}(E_{\rm reco} | E_{\rm true}, \theta) \, dE_{\rm reco} = 1
78 * \f] 72 * \f]
79 * 73 *
80 - * the energy dispersion as function of the natural logarithm of  
81 - * reconstructed energy \f$\log E_{\rm reco}\f$ is given by 74 + * The energy dispersion is stored internally as a 3-dimensional response
  75 + * table, spanned by true energy \f$E_{\rm true}\f$, migration
  76 + * \f$m=E_{\rm reco}/E_{\rm true}\f$ and offset angle \f$\theta\f$, with
82 * 77 *
83 * \f[ 78 * \f[
84 - * E_{\rm disp}(E_{\rm true}, \log E_{\rm reco}, \theta) =  
85 - * E_{\rm disp}(E_{\rm true}, m, \theta) \times  
86 - * \frac{E_{\rm reco}}{E_{\rm true}} 79 + * E_{\rm disp}(E_{\rm reco} | E_{\rm true}, \theta) =
  80 + * \frac{E_{\rm disp}(m | E_{\rm true}, \theta)}{E_{\rm true}}
87 * \f] 81 * \f]
88 * 82 *
89 - * and the energy dispersion as function of the base 10 logarithm of  
90 - * reconstructed energy \f$\log_{10} E_{\rm reco}\f$ is given by 83 + * and
91 * 84 *
92 * \f[ 85 * \f[
93 - * E_{\rm disp}(E_{\rm true}, \log_{10} E_{\rm reco}, \theta) =  
94 - * E_{\rm disp}(E_{\rm true}, m, \theta) \times  
95 - * \frac{\log 10 \times E_{\rm reco}}{E_{\rm true}} 86 + * \int_{m_{\rm min}}^{m_{\rm max}}
  87 + * E_{\rm disp}(m | E_{\rm true}, \theta) \, dm = 1
96 * \f] 88 * \f]
97 ***************************************************************************/ 89 ***************************************************************************/
98 class GCTAEdisp2D : public GCTAEdisp { 90 class GCTAEdisp2D : public GCTAEdisp {
@@ -152,46 +144,47 @@ public: @@ -152,46 +144,47 @@ public:
152 144
153 private: 145 private:
154 // Methods 146 // Methods
155 - void init_members(void);  
156 - void copy_members(const GCTAEdisp2D& edisp);  
157 - void free_members(void);  
158 - void update(const double& logEobs,  
159 - const double& logEsrc,  
160 - const double& theta) const;  
161 - void compute_ebounds_obs(const double& theta = 0.0,  
162 - const double& phi = 0.0,  
163 - const double& zenith = 0.0,  
164 - const double& azimuth = 0.0) const;  
165 - void compute_ebounds_src(const double& theta = 0.0,  
166 - const double& phi = 0.0,  
167 - const double& zenith = 0.0,  
168 - const double& azimuth = 0.0) const;  
169 - void set_table(void);  
170 - void set_boundaries(void);  
171 - void set_max_edisp(void) const;  
172 - void normalize_table(void);  
173 - int table_index(const int& ietrue,  
174 - const int& imigra,  
175 - const int& itheta) const;  
176 - int table_stride(const int& axis) const;  
177 - double table_value(const int& base_ll,  
178 - const int& base_lr,  
179 - const int& base_rl,  
180 - const int& base_rr,  
181 - const double& wgt_el,  
182 - const double& wgt_er,  
183 - const double& wgt_tl,  
184 - const double& wgt_tr,  
185 - const int& offset) const;  
186 - double table_value(const int& base_ll,  
187 - const int& base_lr,  
188 - const int& base_rl,  
189 - const int& base_rr,  
190 - const double& wgt_el,  
191 - const double& wgt_er,  
192 - const double& wgt_tl,  
193 - const double& wgt_tr,  
194 - const double& migra) const; 147 + void init_members(void);
  148 + void copy_members(const GCTAEdisp2D& edisp);
  149 + void free_members(void);
  150 + GEnergy etrue(const int& ietrue) const;
  151 + double migra(const int& imigra) const;
  152 + double theta(const int& itheta) const;
  153 + void compute_ereco_bounds(const double& theta = 0.0,
  154 + const double& phi = 0.0,
  155 + const double& zenith = 0.0,
  156 + const double& azimuth = 0.0) const;
  157 + void compute_etrue_bounds(const double& theta = 0.0,
  158 + const double& phi = 0.0,
  159 + const double& zenith = 0.0,
  160 + const double& azimuth = 0.0) const;
  161 + void set_table(void);
  162 + void set_boundaries(void);
  163 + void set_max_edisp(void);
  164 + double get_max_edisp(const GEnergy& etrue, const double& theta) const;
  165 + void normalize_table(void);
  166 + int table_index(const int& ietrue,
  167 + const int& imigra,
  168 + const int& itheta) const;
  169 + int table_stride(const int& axis) const;
  170 + double table_value(const int& base_ll,
  171 + const int& base_lr,
  172 + const int& base_rl,
  173 + const int& base_rr,
  174 + const double& wgt_el,
  175 + const double& wgt_er,
  176 + const double& wgt_tl,
  177 + const double& wgt_tr,
  178 + const int& offset) const;
  179 + double table_value(const int& base_ll,
  180 + const int& base_lr,
  181 + const int& base_rl,
  182 + const int& base_rr,
  183 + const double& wgt_el,
  184 + const double& wgt_er,
  185 + const double& wgt_tl,
  186 + const double& wgt_tr,
  187 + const double& migra) const;
195 188
196 // Kludge 189 // Kludge
197 void smooth_table(void); 190 void smooth_table(void);
@@ -213,15 +206,15 @@ private: @@ -213,15 +206,15 @@ private:
213 const double& total) const; 206 const double& total) const;
214 207
215 // Protected classes 208 // Protected classes
216 - class edisp_kern : public GFunction { 209 + class edisp_ereco_kern : public GFunction {
217 public: 210 public:
218 - edisp_kern(const GCTAEdisp2D* parent,  
219 - const GEnergy& etrue,  
220 - const double& theta) :  
221 - m_parent(parent),  
222 - m_etrue(etrue),  
223 - m_theta(theta) { }  
224 - double eval(const double& logEobs); 211 + edisp_ereco_kern(const GCTAEdisp2D* parent,
  212 + const GEnergy& etrue,
  213 + const double& theta) :
  214 + m_parent(parent),
  215 + m_etrue(etrue),
  216 + m_theta(theta) { }
  217 + double eval(const double& log10Ereco);
225 protected: 218 protected:
226 const GCTAEdisp2D* m_parent; //!< Pointer to parent class 219 const GCTAEdisp2D* m_parent; //!< Pointer to parent class
227 GEnergy m_etrue; //!< True photon energy 220 GEnergy m_etrue; //!< True photon energy
@@ -229,32 +222,32 @@ private: @@ -229,32 +222,32 @@ private:
229 }; 222 };
230 223
231 // Members 224 // Members
232 - mutable GFilename m_filename; //!< Name of Edisp response file  
233 - GCTAResponseTable m_edisp; //!< Edisp response table  
234 - mutable bool m_fetched; //!< Signals that Edisp has been fetched  
235 - int m_inx_etrue; //!< True energy index  
236 - int m_inx_migra; //!< Migration index  
237 - int m_inx_theta; //!< Theta index  
238 - int m_inx_matrix; //!< Matrix  
239 - double m_logEsrc_min; //!< Minimum logE (log10(E/TeV))  
240 - double m_logEsrc_max; //!< Maximum logE (log10(E/TeV))  
241 - double m_migra_min; //!< Minimum migration  
242 - double m_migra_max; //!< Maximum migration  
243 - double m_theta_min; //!< Minimum theta (radians)  
244 - double m_theta_max; //!< Maximum theta (radians) 225 + mutable GFilename m_filename; //!< Name of Edisp response file
  226 + GCTAResponseTable m_edisp; //!< Edisp response table
  227 + mutable bool m_fetched; //!< Signals that Edisp has been fetched
  228 + int m_inx_etrue; //!< True energy index
  229 + int m_inx_migra; //!< Migration index
  230 + int m_inx_theta; //!< Theta index
  231 + int m_inx_matrix; //!< Matrix
  232 + double m_logEsrc_min; //!< Minimum logE (log10(E/TeV))
  233 + double m_logEsrc_max; //!< Maximum logE (log10(E/TeV))
  234 + double m_migra_min; //!< Minimum migration
  235 + double m_migra_max; //!< Maximum migration
  236 + double m_theta_min; //!< Minimum theta (radians)
  237 + double m_theta_max; //!< Maximum theta (radians)
  238 + std::vector<double> m_max_edisp; //!< Maximum Edisp
245 239
246 // Computation cache 240 // Computation cache
247 - mutable bool m_ebounds_obs_computed;  
248 - mutable bool m_ebounds_src_computed;  
249 - mutable double m_last_theta_obs;  
250 - mutable double m_last_theta_src; 241 + mutable bool m_ereco_bounds_computed;
  242 + mutable bool m_etrue_bounds_computed;
  243 + mutable double m_last_theta_ereco;
  244 + mutable double m_last_theta_etrue;
251 mutable GEnergy m_last_etrue; 245 mutable GEnergy m_last_etrue;
252 mutable GEnergy m_last_ereco; 246 mutable GEnergy m_last_ereco;
253 - mutable int m_index_obs;  
254 - mutable int m_index_src;  
255 - mutable double m_max_edisp;  
256 - mutable std::vector<GEbounds> m_ebounds_obs;  
257 - mutable std::vector<GEbounds> m_ebounds_src; 247 + mutable int m_index_ereco;
  248 + mutable int m_index_etrue;
  249 + mutable std::vector<GEbounds> m_ereco_bounds;
  250 + mutable std::vector<GEbounds> m_etrue_bounds;
258 }; 251 };
259 252
260 253
inst/cta/src/GCTAEdisp2D.cpp
@@ -191,15 +191,14 @@ GCTAEdisp2D&amp; GCTAEdisp2D::operator=(const GCTAEdisp2D&amp; edisp) @@ -191,15 +191,14 @@ GCTAEdisp2D&amp; GCTAEdisp2D::operator=(const GCTAEdisp2D&amp; edisp)
191 * Returns the energy dispersion 191 * Returns the energy dispersion
192 * 192 *
193 * \f[ 193 * \f[
194 - * E_{\rm disp}(E_{\rm true}, E_{\rm reco}, \theta) =  
195 - * \frac{E_{\rm disp}(E_{\rm true}, m, \theta)}{E_{\rm true}} 194 + * E_{\rm disp}(E_{\rm reco} | E_{\rm true}, \theta) =
  195 + * \frac{E_{\rm disp}(m | E_{\rm true}, \theta)}{E_{\rm true}}
196 * \f] 196 * \f]
197 * 197 *
198 * in units of MeV\f$^{-1}\f$ where 198 * in units of MeV\f$^{-1}\f$ where
199 - * \f$\log_{10} E_{\rm reco}\f$ is the base 10 logarithm of the reconstructed  
200 - * energy in TeV,  
201 - * \f$\log_{10} E_{\rm true}\f$ is the base 10 logarithm of the true energy  
202 - * in TeV and \f$\theta\f$ is the offset angle in radians. 199 + * \f$E_{\rm reco}\f$ is the reconstructed energy,
  200 + * \f$E_{\rm true}\f$ is the true energy, and
  201 + * \f$\theta\f$ is the offset angle.
203 ***************************************************************************/ 202 ***************************************************************************/
204 double GCTAEdisp2D::operator()(const GEnergy& ereco, 203 double GCTAEdisp2D::operator()(const GEnergy& ereco,
205 const GEnergy& etrue, 204 const GEnergy& etrue,
@@ -222,7 +221,6 @@ double GCTAEdisp2D::operator()(const GEnergy&amp; ereco, @@ -222,7 +221,6 @@ double GCTAEdisp2D::operator()(const GEnergy&amp; ereco,
222 (theta >= m_theta_min) && (theta <= m_theta_max)) { 221 (theta >= m_theta_min) && (theta <= m_theta_max)) {
223 222
224 // Compute migration (migra=Ereco/Etrue 223 // Compute migration (migra=Ereco/Etrue
225 - //double EobsOverEsrc = std::exp((logEobs-logEsrc) * gammalib::ln10);  
226 double migra = ereco.MeV() / etrue.MeV(); 224 double migra = ereco.MeV() / etrue.MeV();
227 225
228 // Continue only if migration is in validity range 226 // Continue only if migration is in validity range
@@ -478,7 +476,7 @@ void GCTAEdisp2D::save(const GFilename&amp; filename, const bool&amp; clobber) const @@ -478,7 +476,7 @@ void GCTAEdisp2D::save(const GFilename&amp; filename, const bool&amp; clobber) const
478 * No energy dispersion information found for parameters or 476 * No energy dispersion information found for parameters or
479 * energy dispersion matrix is invalid. 477 * energy dispersion matrix is invalid.
480 * 478 *
481 - * Draws observed energy value given a true energy @p logEsrc and offset 479 + * Draws observed energy value given a true energy @p etrue and offset
482 * angle @p theta. If no energy dispersion information is available the 480 * angle @p theta. If no energy dispersion information is available the
483 * method will return the true photon energy. 481 * method will return the true photon energy.
484 ***************************************************************************/ 482 ***************************************************************************/
@@ -492,20 +490,20 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran, @@ -492,20 +490,20 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran,
492 // Make sure that energy dispersion is online 490 // Make sure that energy dispersion is online
493 fetch(); 491 fetch();
494 492
495 - // Initialise reconstructed photon energy  
496 - GEnergy ereco; 493 + // Initialise reconstructed event energy with true photon energy
  494 + GEnergy ereco = etrue;
497 495
498 - // Get boundaries for observed energy 496 + // Get boundaries for reconstructed energy
499 GEbounds ebounds = ereco_bounds(etrue, theta, phi, zenith, azimuth); 497 GEbounds ebounds = ereco_bounds(etrue, theta, phi, zenith, azimuth);
500 - double emin = ebounds.emin().log10TeV();  
501 - double emax = ebounds.emax().log10TeV(); 498 + double emin = ebounds.emin().TeV();
  499 + double emax = ebounds.emax().TeV();
502 500
503 // Throw an exception if minimum energy is equal or larger than maximum 501 // Throw an exception if minimum energy is equal or larger than maximum
504 // energy (this means that no energy dispersion information is available 502 // energy (this means that no energy dispersion information is available
505 // for that energy) 503 // for that energy)
506 if (emin >= emax) { 504 if (emin >= emax) {
507 std::string msg = "No valid energy dispersion information available " 505 std::string msg = "No valid energy dispersion information available "
508 - "for true photon energy "+etrue.print()+"," 506 + "for true photon energy "+etrue.print()+", "
509 "offset angle "+ 507 "offset angle "+
510 gammalib::str(theta*gammalib::rad2deg)+" deg " 508 gammalib::str(theta*gammalib::rad2deg)+" deg "
511 "and azimuth angle "+ 509 "and azimuth angle "+
@@ -515,8 +513,11 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran, @@ -515,8 +513,11 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran,
515 throw GException::invalid_return_value(G_MC, msg); 513 throw GException::invalid_return_value(G_MC, msg);
516 } 514 }
517 515
  516 + // Get maximum energy dispersion value (including some margin)
  517 + double max_edisp = 1.5 * get_max_edisp(etrue, theta);
  518 +
518 // Throw an exception if maximum energy dispersion is zero 519 // Throw an exception if maximum energy dispersion is zero
519 - if (m_max_edisp <= 0.0) { 520 + if (max_edisp <= 0.0) {
520 std::string msg = "Energy dispersion matrix is empty. Please provide " 521 std::string msg = "Energy dispersion matrix is empty. Please provide "
521 "a valid energy dispersion matrix."; 522 "a valid energy dispersion matrix.";
522 throw GException::invalid_return_value(G_MC, msg); 523 throw GException::invalid_return_value(G_MC, msg);
@@ -524,7 +525,6 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran, @@ -524,7 +525,6 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran,
524 525
525 // Initialise rejection method 526 // Initialise rejection method
526 double ewidth = emax - emin; 527 double ewidth = emax - emin;
527 - double logEobs = etrue.log10TeV();  
528 double f = 0.0; 528 double f = 0.0;
529 double ftest = 1.0; 529 double ftest = 1.0;
530 int zeros = 0; 530 int zeros = 0;
@@ -540,14 +540,13 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran, @@ -540,14 +540,13 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran,
540 // subsequent zeros the loop is terminated with an exception 540 // subsequent zeros the loop is terminated with an exception
541 int zeros = 0; 541 int zeros = 0;
542 do { 542 do {
543 - logEobs = emin + ewidth * ran.uniform();  
544 - ereco.log10TeV(logEobs);  
545 - f = operator()(ereco, etrue, theta, phi, zenith, azimuth); 543 + ereco.TeV(emin + ewidth * ran.uniform());
  544 + f = operator()(ereco, etrue, theta, phi, zenith, azimuth);
546 if (f == 0.0) { 545 if (f == 0.0) {
547 zeros++; 546 zeros++;
548 if (zeros > max_subsequent_zeros) { 547 if (zeros > max_subsequent_zeros) {
549 std::string msg = "No valid energy dispersion information " 548 std::string msg = "No valid energy dispersion information "
550 - "available for true photon energy "+ 549 + "available for true photon energy "+
551 etrue.print()+", offset angle "+ 550 etrue.print()+", offset angle "+
552 gammalib::str(theta*gammalib::rad2deg)+ 551 gammalib::str(theta*gammalib::rad2deg)+
553 " deg and azimuth angle "+ 552 " deg and azimuth angle "+
@@ -564,7 +563,7 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran, @@ -564,7 +563,7 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran,
564 // Get uniform random value between zero and the maximum energy 563 // Get uniform random value between zero and the maximum energy
565 // dispersion value. The loop is quite if the random number is smaller 564 // dispersion value. The loop is quite if the random number is smaller
566 // than the energy dispersion value 565 // than the energy dispersion value
567 - ftest = ran.uniform() * m_max_edisp; 566 + ftest = ran.uniform() * max_edisp;
568 567
569 } // endwhile: 568 } // endwhile:
570 569
@@ -584,7 +583,7 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran, @@ -584,7 +583,7 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran,
584 * @return Observed energy boundaries. 583 * @return Observed energy boundaries.
585 * 584 *
586 * Returns the band of observed energies outside of which the energy 585 * Returns the band of observed energies outside of which the energy
587 - * dispersion becomes negligible for a given true energy @p logEsrc and 586 + * dispersion becomes negligible for a given true energy @p etrue and
588 * offset angle @p theta. 587 * offset angle @p theta.
589 ***************************************************************************/ 588 ***************************************************************************/
590 GEbounds GCTAEdisp2D::ereco_bounds(const GEnergy& etrue, 589 GEbounds GCTAEdisp2D::ereco_bounds(const GEnergy& etrue,
@@ -597,15 +596,15 @@ GEbounds GCTAEdisp2D::ereco_bounds(const GEnergy&amp; etrue, @@ -597,15 +596,15 @@ GEbounds GCTAEdisp2D::ereco_bounds(const GEnergy&amp; etrue,
597 fetch(); 596 fetch();
598 597
599 // Compute only if parameters changed 598 // Compute only if parameters changed
600 - if (!m_ebounds_obs_computed || theta != m_last_theta_obs) { 599 + if (!m_ereco_bounds_computed || theta != m_last_theta_ereco) {
601 600
602 // Set computation flag 601 // Set computation flag
603 - m_ebounds_obs_computed = true;  
604 - m_last_theta_obs = theta;  
605 - m_last_etrue.log10TeV(-30.0); // force update 602 + m_ereco_bounds_computed = true;
  603 + m_last_theta_ereco = theta;
  604 + m_last_etrue.TeV(0.0); // force update
606 605
607 - // Compute ebounds_obs  
608 - compute_ebounds_obs(theta, phi, zenith, azimuth); 606 + // Compute reconstructed energy bounds
  607 + compute_ereco_bounds(theta, phi, zenith, azimuth);
609 608
610 } 609 }
611 610
@@ -620,7 +619,7 @@ GEbounds GCTAEdisp2D::ereco_bounds(const GEnergy&amp; etrue, @@ -620,7 +619,7 @@ GEbounds GCTAEdisp2D::ereco_bounds(const GEnergy&amp; etrue,
620 619
621 // Find right index with bisection 620 // Find right index with bisection
622 int low = 0; 621 int low = 0;
623 - int high = m_ebounds_obs.size() - 1; 622 + int high = m_ereco_bounds.size() - 1;
624 while ((high-low) > 1) { 623 while ((high-low) > 1) {
625 int mid = (low+high) / 2; 624 int mid = (low+high) / 2;
626 double e = m_edisp.axis_lo(m_inx_etrue, mid); 625 double e = m_edisp.axis_lo(m_inx_etrue, mid);
@@ -633,12 +632,12 @@ GEbounds GCTAEdisp2D::ereco_bounds(const GEnergy&amp; etrue, @@ -633,12 +632,12 @@ GEbounds GCTAEdisp2D::ereco_bounds(const GEnergy&amp; etrue,
633 } 632 }
634 633
635 // Index found 634 // Index found
636 - m_index_obs = low; 635 + m_index_ereco = low;
637 636
638 } 637 }
639 638
640 // Return energy boundaries 639 // Return energy boundaries
641 - return m_ebounds_obs[m_index_obs]; 640 + return m_ereco_bounds[m_index_ereco];
642 } 641 }
643 642
644 643
@@ -653,7 +652,7 @@ GEbounds GCTAEdisp2D::ereco_bounds(const GEnergy&amp; etrue, @@ -653,7 +652,7 @@ GEbounds GCTAEdisp2D::ereco_bounds(const GEnergy&amp; etrue,
653 * @return True energy boundaries. 652 * @return True energy boundaries.
654 * 653 *
655 * Returns the band of true photon energies outside of which the energy 654 * Returns the band of true photon energies outside of which the energy
656 - * dispersion becomes negligible for a given observed energy @p logEobs and 655 + * dispersion becomes negligible for a given observed energy @p ereco and
657 * offset angle @p theta. 656 * offset angle @p theta.
658 ***************************************************************************/ 657 ***************************************************************************/
659 GEbounds GCTAEdisp2D::etrue_bounds(const GEnergy& ereco, 658 GEbounds GCTAEdisp2D::etrue_bounds(const GEnergy& ereco,
@@ -666,15 +665,15 @@ GEbounds GCTAEdisp2D::etrue_bounds(const GEnergy&amp; ereco, @@ -666,15 +665,15 @@ GEbounds GCTAEdisp2D::etrue_bounds(const GEnergy&amp; ereco,
666 fetch(); 665 fetch();
667 666
668 // Compute true energy boundaries only if parameters changed 667 // Compute true energy boundaries only if parameters changed
669 - if (!m_ebounds_src_computed || theta != m_last_theta_src) { 668 + if (!m_etrue_bounds_computed || theta != m_last_theta_etrue) {
670 669
671 // Set computation flag 670 // Set computation flag
672 - m_ebounds_src_computed = true;  
673 - m_last_theta_src = theta;  
674 - m_last_ereco.log10TeV(-30.0); // force update 671 + m_etrue_bounds_computed = true;
  672 + m_last_theta_etrue = theta;
  673 + m_last_ereco.TeV(0.0); // force update
675 674
676 - // Compute ebounds_src  
677 - compute_ebounds_src(theta, phi, zenith, azimuth); 675 + // Compute true energy boundaries
  676 + compute_etrue_bounds(theta, phi, zenith, azimuth);
678 677
679 } 678 }
680 679
@@ -689,7 +688,7 @@ GEbounds GCTAEdisp2D::etrue_bounds(const GEnergy&amp; ereco, @@ -689,7 +688,7 @@ GEbounds GCTAEdisp2D::etrue_bounds(const GEnergy&amp; ereco,
689 688
690 // Find right index with bisection 689 // Find right index with bisection
691 int low = 0; 690 int low = 0;
692 - int high = m_ebounds_src.size() - 1; 691 + int high = m_etrue_bounds.size() - 1;
693 while ((high-low) > 1) { 692 while ((high-low) > 1) {
694 int mid = (low+high) / 2; 693 int mid = (low+high) / 2;
695 double e = m_edisp.axis_lo(m_inx_etrue, mid); 694 double e = m_edisp.axis_lo(m_inx_etrue, mid);
@@ -702,12 +701,12 @@ GEbounds GCTAEdisp2D::etrue_bounds(const GEnergy&amp; ereco, @@ -702,12 +701,12 @@ GEbounds GCTAEdisp2D::etrue_bounds(const GEnergy&amp; ereco,
702 } 701 }
703 702
704 // Index found 703 // Index found
705 - m_index_src = low; 704 + m_index_etrue = low;
706 705
707 } 706 }
708 707
709 // Return energy boundaries 708 // Return energy boundaries
710 - return m_ebounds_src[m_index_src]; 709 + return m_etrue_bounds[m_index_etrue];
711 } 710 }
712 711
713 712
@@ -821,7 +820,7 @@ void GCTAEdisp2D::fetch(void) const @@ -821,7 +820,7 @@ void GCTAEdisp2D::fetch(void) const
821 * 820 *
822 * \f[ 821 * \f[
823 * \int_{E_{\rm reco}^{\rm min}}^{E_{\rm reco}^{\rm max}} 822 * \int_{E_{\rm reco}^{\rm min}}^{E_{\rm reco}^{\rm max}}
824 - * E_{\rm disp}(E_{\rm true}, E_{\rm reco}, \theta) \, dE_{\rm reco} 823 + * E_{\rm disp}(E_{\rm reco} | E_{\rm true}, \theta) \, dE_{\rm reco}
825 * \f] 824 * \f]
826 * 825 *
827 * where 826 * where
@@ -1046,19 +1045,19 @@ void GCTAEdisp2D::init_members(void) @@ -1046,19 +1045,19 @@ void GCTAEdisp2D::init_members(void)
1046 m_migra_max = 0.0; 1045 m_migra_max = 0.0;
1047 m_theta_min = 0.0; 1046 m_theta_min = 0.0;
1048 m_theta_max = 0.0; 1047 m_theta_max = 0.0;
  1048 + m_max_edisp.clear();
1049 1049
1050 // Initialise computation cache 1050 // Initialise computation cache
1051 - m_ebounds_obs_computed = false;  
1052 - m_ebounds_src_computed = false;  
1053 - m_last_theta_obs = -1.0;  
1054 - m_last_theta_src = -1.0;  
1055 - m_last_etrue.log10TeV(-30.0);  
1056 - m_last_ereco.log10TeV(-30.0);  
1057 - m_index_obs = 0;  
1058 - m_index_src = 0;  
1059 - m_max_edisp = 0.0;  
1060 - m_ebounds_obs.clear();  
1061 - m_ebounds_src.clear(); 1051 + m_ereco_bounds_computed = false;
  1052 + m_etrue_bounds_computed = false;
  1053 + m_index_ereco = 0;
  1054 + m_index_etrue = 0;
  1055 + m_last_theta_ereco = -1.0;
  1056 + m_last_theta_etrue = -1.0;
  1057 + m_last_etrue.TeV(0.0);
  1058 + m_last_ereco.TeV(0.0);
  1059 + m_ereco_bounds.clear();
  1060 + m_etrue_bounds.clear();
1062 1061
1063 // Return 1062 // Return
1064 return; 1063 return;
@@ -1086,19 +1085,19 @@ void GCTAEdisp2D::copy_members(const GCTAEdisp2D&amp; edisp) @@ -1086,19 +1085,19 @@ void GCTAEdisp2D::copy_members(const GCTAEdisp2D&amp; edisp)
1086 m_migra_max = edisp.m_migra_max; 1085 m_migra_max = edisp.m_migra_max;
1087 m_theta_min = edisp.m_theta_min; 1086 m_theta_min = edisp.m_theta_min;
1088 m_theta_max = edisp.m_theta_max; 1087 m_theta_max = edisp.m_theta_max;
  1088 + m_max_edisp = edisp.m_max_edisp;
1089 1089
1090 // Copy computation cache 1090 // Copy computation cache
1091 - m_ebounds_obs_computed = edisp.m_ebounds_obs_computed;  
1092 - m_ebounds_src_computed = edisp.m_ebounds_src_computed;  
1093 - m_last_theta_obs = edisp.m_last_theta_obs;  
1094 - m_last_theta_src = edisp.m_last_theta_src;  
1095 - m_last_etrue = edisp.m_last_etrue;  
1096 - m_last_ereco = edisp.m_last_ereco;  
1097 - m_index_obs = edisp.m_index_obs;  
1098 - m_index_src = edisp.m_index_src;  
1099 - m_max_edisp = edisp.m_max_edisp;  
1100 - m_ebounds_obs = edisp.m_ebounds_obs;  
1101 - m_ebounds_src = edisp.m_ebounds_src; 1091 + m_ereco_bounds_computed = edisp.m_ereco_bounds_computed;
  1092 + m_etrue_bounds_computed = edisp.m_etrue_bounds_computed;
  1093 + m_index_ereco = edisp.m_index_ereco;
  1094 + m_index_etrue = edisp.m_index_etrue;
  1095 + m_last_theta_ereco = edisp.m_last_theta_ereco;
  1096 + m_last_theta_etrue = edisp.m_last_theta_etrue;
  1097 + m_last_etrue = edisp.m_last_etrue;
  1098 + m_last_ereco = edisp.m_last_ereco;
  1099 + m_ereco_bounds = edisp.m_ereco_bounds;
  1100 + m_etrue_bounds = edisp.m_etrue_bounds;
1102 1101
1103 // Return 1102 // Return
1104 return; 1103 return;
@@ -1116,89 +1115,141 @@ void GCTAEdisp2D::free_members(void) @@ -1116,89 +1115,141 @@ void GCTAEdisp2D::free_members(void)
1116 1115
1117 1116
1118 /***********************************************************************//** 1117 /***********************************************************************//**
1119 - * @brief Compute ebounds_obs vector 1118 + * @brief Get true energy
  1119 + *
  1120 + * @param[in] ietrue True energy index.
  1121 + * @return True energy.
  1122 + ***************************************************************************/
  1123 +GEnergy GCTAEdisp2D::etrue(const int& ietrue) const
  1124 +{
  1125 + // Compute true energy in TeV
  1126 + double etrue_TeV = std::sqrt(m_edisp.axis_lo(m_inx_etrue, ietrue) *
  1127 + m_edisp.axis_hi(m_inx_etrue, ietrue));
  1128 +
  1129 + // Set true energy
  1130 + GEnergy etrue;
  1131 + etrue.TeV(etrue_TeV);
  1132 +
  1133 + // Return true energy
  1134 + return etrue;
  1135 +}
  1136 +
  1137 +
  1138 +/***********************************************************************//**
  1139 + * @brief Get migration
  1140 + *
  1141 + * @param[in] imigra Migration index.
  1142 + * @return Migration.
  1143 + ***************************************************************************/
  1144 +double GCTAEdisp2D::migra(const int& imigra) const
  1145 +{
  1146 + // Compute migration
  1147 + double migra = 0.5 * (m_edisp.axis_hi(m_inx_migra, imigra) +
  1148 + m_edisp.axis_lo(m_inx_migra, imigra));
  1149 +
  1150 + // Return migration
  1151 + return migra;
  1152 +}
  1153 +
  1154 +
  1155 +/***********************************************************************//**
  1156 + * @brief Get offset angle in radiaus
  1157 + *
  1158 + * @param[in] itheta Offset angle index.
  1159 + * @return Offset angle (radians).
  1160 + ***************************************************************************/
  1161 +double GCTAEdisp2D::theta(const int& itheta) const
  1162 +{
  1163 + // Compute offset angle in radians
  1164 + double theta = 0.5 * (m_edisp.axis_lo(m_inx_theta, itheta) +
  1165 + m_edisp.axis_hi(m_inx_theta, itheta)) *
  1166 + gammalib::deg2rad;
  1167 +
  1168 + // Return offset angle
  1169 + return theta;
  1170 +}
  1171 +
  1172 +
  1173 +/***********************************************************************//**
  1174 + * @brief Compute vector of reconstructed energy bounds
1120 * 1175 *
1121 * @param[in] theta Offset angle (radians). 1176 * @param[in] theta Offset angle (radians).
1122 * @param[in] phi Azimuth angle (radians). 1177 * @param[in] phi Azimuth angle (radians).
1123 * @param[in] zenith Zenith angle (radians). 1178 * @param[in] zenith Zenith angle (radians).
1124 * @param[in] azimuth Azimuth angle (radians). 1179 * @param[in] azimuth Azimuth angle (radians).
1125 * 1180 *
1126 - * Computes for all true energies the energy boundaries of the observed 1181 + * Computes for all true energies the energy boundaries of the reconstructed
1127 * energies covered by valid migration matrix elements. Only matrix elements 1182 * energies covered by valid migration matrix elements. Only matrix elements
1128 * with values >= 1.0e-12 are considered as valid elements. In case that no 1183 * with values >= 1.0e-12 are considered as valid elements. In case that no
1129 * matrix elements are found of a given true energy, the interval of observed 1184 * matrix elements are found of a given true energy, the interval of observed
1130 - * energies will be set to [1 TeV, 1 TeV] (i.e. an empty interval). 1185 + * energies will be set to [0,0] (i.e. an empty interval).
1131 ***************************************************************************/ 1186 ***************************************************************************/
1132 -void GCTAEdisp2D::compute_ebounds_obs(const double& theta,  
1133 - const double& phi,  
1134 - const double& zenith,  
1135 - const double& azimuth) const 1187 +void GCTAEdisp2D::compute_ereco_bounds(const double& theta,
  1188 + const double& phi,
  1189 + const double& zenith,
  1190 + const double& azimuth) const
1136 { 1191 {
1137 // Clear ebounds_obs vector 1192 // Clear ebounds_obs vector
1138 - m_ebounds_obs.clear(); 1193 + m_ereco_bounds.clear();
1139 1194
1140 // Set epsilon 1195 // Set epsilon
1141 const double eps = 1.0e-12; 1196 const double eps = 1.0e-12;
1142 1197
1143 // Determine number of true energy and migration bins 1198 // Determine number of true energy and migration bins
1144 - int n_bins = m_edisp.axis_bins(m_inx_etrue);  
1145 - int n_migra = m_edisp.axis_bins(m_inx_migra); 1199 + int netrue = m_edisp.axis_bins(m_inx_etrue);
  1200 + int nmigra = m_edisp.axis_bins(m_inx_migra);
1146 1201
1147 - // Loop over Esrc  
1148 - for (int isrc = 0; isrc < n_bins; ++isrc) { 1202 + // Loop over true photon energy
  1203 + for (int ietrue = 0; ietrue < netrue; ++ietrue) {
1149 1204
1150 - // Set Esrc  
1151 - double Esrc = std::sqrt(m_edisp.axis_hi(m_inx_etrue,isrc) *  
1152 - m_edisp.axis_lo(m_inx_etrue,isrc));  
1153 - double logEsrc = std::log10(Esrc); 1205 + // Compute true photon energy
  1206 + GEnergy etrue = this->etrue(ietrue);
1154 1207
1155 // Initialise results 1208 // Initialise results
1156 - double logEobsMin = 0.0;  
1157 - double logEobsMax = 0.0;  
1158 - bool minFound = false;  
1159 - bool maxFound = false; 1209 + double ereco_min = 0.0;
  1210 + double ereco_max = 0.0;
  1211 + bool found_min = false;
  1212 + bool found_max = false;
1160 1213
1161 // Find minimum boundary 1214 // Find minimum boundary
1162 - for (int i = 0; i < n_migra; ++i) { 1215 + for (int imigra = 0; imigra < nmigra; ++imigra) {
1163 1216
1164 - // Compute EobsOverEsrc value  
1165 - double EobsOverEsrc = 0.5 * (m_edisp.axis_hi(m_inx_migra,i) +  
1166 - m_edisp.axis_lo(m_inx_migra,i)); 1217 + // Compute migra value
  1218 + double migra = this->migra(imigra);
1167 1219
1168 // Get matrix term 1220 // Get matrix term
1169 double arg[3]; 1221 double arg[3];
1170 - arg[m_inx_etrue] = logEsrc;  
1171 - arg[m_inx_migra] = EobsOverEsrc; 1222 + arg[m_inx_etrue] = etrue.log10TeV();
  1223 + arg[m_inx_migra] = migra;
1172 arg[m_inx_theta] = theta; 1224 arg[m_inx_theta] = theta;
1173 double edisp = m_edisp(m_inx_matrix, arg[0], arg[1], arg[2]); 1225 double edisp = m_edisp(m_inx_matrix, arg[0], arg[1], arg[2]);
1174 1226
1175 // Find first non-negligible matrix term 1227 // Find first non-negligible matrix term
1176 if (edisp >= eps) { 1228 if (edisp >= eps) {
1177 - minFound = true;  
1178 - logEobsMin = std::log10(EobsOverEsrc * Esrc); 1229 + found_min = true;
  1230 + ereco_min = migra * etrue.TeV();
1179 break; 1231 break;
1180 } 1232 }
1181 1233
1182 } // endfor: find minimum boundary 1234 } // endfor: find minimum boundary
1183 1235
1184 // Find maximum boundary 1236 // Find maximum boundary
1185 - for (int i = n_migra-1; i >= 0; i--) { 1237 + for (int imigra = nmigra-1; imigra >= 0; imigra--) {
1186 1238
1187 - // Compute EobsOverEsrc value  
1188 - double EobsOverEsrc = 0.5 * (m_edisp.axis_hi(m_inx_migra,i) +  
1189 - m_edisp.axis_lo(m_inx_migra,i)); 1239 + // Compute migra value
  1240 + double migra = this->migra(imigra);
1190 1241
1191 // Get matrix term 1242 // Get matrix term
1192 double arg[3]; 1243 double arg[3];
1193 - arg[m_inx_etrue] = logEsrc;  
1194 - arg[m_inx_migra] = EobsOverEsrc; 1244 + arg[m_inx_etrue] = etrue.log10TeV();
  1245 + arg[m_inx_migra] = migra;
1195 arg[m_inx_theta] = theta; 1246 arg[m_inx_theta] = theta;
1196 double edisp = m_edisp(m_inx_matrix, arg[0], arg[1], arg[2]); 1247 double edisp = m_edisp(m_inx_matrix, arg[0], arg[1], arg[2]);
1197 1248
1198 // Find first non-negligible matrix term 1249 // Find first non-negligible matrix term
1199 if (edisp >= eps) { 1250 if (edisp >= eps) {
1200 - maxFound = true;  
1201 - logEobsMax = std::log10(EobsOverEsrc * Esrc); 1251 + found_max = true;
  1252 + ereco_max = migra * etrue.TeV();
1202 break; 1253 break;
1203 } 1254 }
1204 1255
@@ -1206,19 +1257,19 @@ void GCTAEdisp2D::compute_ebounds_obs(const double&amp; theta, @@ -1206,19 +1257,19 @@ void GCTAEdisp2D::compute_ebounds_obs(const double&amp; theta,
1206 1257
1207 // If we did not find boundaries then set the interval to 1258 // If we did not find boundaries then set the interval to
1208 // a zero interval for safety 1259 // a zero interval for safety
1209 - if (!minFound || !maxFound) {  
1210 - logEobsMin = 0.0;  
1211 - logEobsMax = 0.0; 1260 + if (!found_min || !found_max) {
  1261 + ereco_min = 0.0;
  1262 + ereco_max = 0.0;
1212 } 1263 }
1213 1264
1214 // Set energy boundaries 1265 // Set energy boundaries
1215 GEnergy emin; 1266 GEnergy emin;
1216 GEnergy emax; 1267 GEnergy emax;
1217 - emin.log10TeV(logEobsMin);  
1218 - emax.log10TeV(logEobsMax); 1268 + emin.TeV(ereco_min);
  1269 + emax.TeV(ereco_max);
1219 1270
1220 // Add energy boundaries to vector 1271 // Add energy boundaries to vector
1221 - m_ebounds_obs.push_back(GEbounds(emin, emax)); 1272 + m_ereco_bounds.push_back(GEbounds(emin, emax));
1222 1273
1223 } // endfor: looped over logEsrc 1274 } // endfor: looped over logEsrc
1224 1275
@@ -1228,7 +1279,7 @@ void GCTAEdisp2D::compute_ebounds_obs(const double&amp; theta, @@ -1228,7 +1279,7 @@ void GCTAEdisp2D::compute_ebounds_obs(const double&amp; theta,
1228 1279
1229 1280
1230 /***********************************************************************//** 1281 /***********************************************************************//**
1231 - * @brief Compute ebounds_src vector 1282 + * @brief Compute vector of true energy boundaries
1232 * 1283 *
1233 * @param[in] theta Offset angle (radians). 1284 * @param[in] theta Offset angle (radians).
1234 * @param[in] phi Azimuth angle (radians). 1285 * @param[in] phi Azimuth angle (radians).
@@ -1239,62 +1290,59 @@ void GCTAEdisp2D::compute_ebounds_obs(const double&amp; theta, @@ -1239,62 +1290,59 @@ void GCTAEdisp2D::compute_ebounds_obs(const double&amp; theta,
1239 * energies covered by valid migration matrix elements. Only matrix elements 1290 * energies covered by valid migration matrix elements. Only matrix elements
1240 * with values >= 1.0e-12 are considered as valid elements. In case that no 1291 * with values >= 1.0e-12 are considered as valid elements. In case that no
1241 * matrix elements are found of a given observed energy, the interval of true 1292 * matrix elements are found of a given observed energy, the interval of true
1242 - * energies will be set to [1 TeV, 1 TeV] (i.e. an empty interval). 1293 + * energies will be set to [0,0] (i.e. an empty interval).
1243 ***************************************************************************/ 1294 ***************************************************************************/
1244 -void GCTAEdisp2D::compute_ebounds_src(const double& theta,  
1245 - const double& phi,  
1246 - const double& zenith,  
1247 - const double& azimuth) const 1295 +void GCTAEdisp2D::compute_etrue_bounds(const double& theta,
  1296 + const double& phi,
  1297 + const double& zenith,
  1298 + const double& azimuth) const
1248 { 1299 {
1249 // Initialise energies 1300 // Initialise energies
1250 GEnergy etrue; 1301 GEnergy etrue;
1251 GEnergy ereco; 1302 GEnergy ereco;
1252 1303
1253 // Clear ebounds_src vector 1304 // Clear ebounds_src vector
1254 - m_ebounds_src.clear(); 1305 + m_etrue_bounds.clear();
1255 1306
1256 // Set epsilon 1307 // Set epsilon
1257 const double eps = 1.0e-12; 1308 const double eps = 1.0e-12;
1258 1309
1259 // Determine number of true energy bins 1310 // Determine number of true energy bins
1260 - int n_bins = m_edisp.axis_bins(m_inx_etrue);  
1261 - int n_migras = m_edisp.axis_bins(m_inx_migra); 1311 + int nereco = m_edisp.axis_bins(m_inx_etrue);
  1312 + int nmigras = m_edisp.axis_bins(m_inx_migra);
1262 1313
1263 - // Loop over Eobs  
1264 - for (int iobs = 0; iobs < n_bins; ++iobs) { 1314 + // Loop over reconstructed energy
  1315 + for (int iereco = 0; iereco < nereco; ++iereco) {
1265 1316
1266 - // Set Eobs  
1267 - double Eobs = std::sqrt(m_edisp.axis_hi(m_inx_etrue,iobs) *  
1268 - m_edisp.axis_lo(m_inx_etrue,iobs));  
1269 - ereco.TeV(Eobs); 1317 + // Set ereco (we use the true energy axis for the computation)
  1318 + GEnergy ereco = this->etrue(iereco);
1270 1319
1271 // Initialise results 1320 // Initialise results
1272 GEnergy etrue_min; 1321 GEnergy etrue_min;
1273 GEnergy etrue_max; 1322 GEnergy etrue_max;
1274 - bool minFound = false;  
1275 - bool maxFound = false; 1323 + bool found_min = false;
  1324 + bool found_max = false;
1276 1325
1277 // Find minimum boundary 1326 // Find minimum boundary
1278 - for (int imigra = 0; imigra < n_migras; ++imigra) { 1327 + for (int imigra = 0; imigra < nmigras; ++imigra) {
1279 1328
1280 - // Get mean migra  
1281 - double migra = 0.5 * (m_edisp.axis_lo(m_inx_migra,imigra) +  
1282 - m_edisp.axis_lo(m_inx_migra,imigra)); 1329 + // Compute migra value
  1330 + double migra = this->migra(imigra);
1283 1331
1284 // Fall through if migration is zero 1332 // Fall through if migration is zero
1285 if (migra == 0.0) { 1333 if (migra == 0.0) {
1286 continue; 1334 continue;
1287 } 1335 }
1288 1336
1289 - // Compute log10 of true energy 1337 + // Compute true energy
1290 etrue = ereco / migra; 1338 etrue = ereco / migra;
1291 1339
1292 - // Get matrix term 1340 + // Get energy dispersion
1293 double edisp = operator()(ereco, etrue, theta); 1341 double edisp = operator()(ereco, etrue, theta);
1294 1342
1295 // Find first non-negligible matrix term 1343 // Find first non-negligible matrix term
1296 if (edisp >= eps) { 1344 if (edisp >= eps) {
1297 - maxFound = true; 1345 + found_max = true;
1298 etrue_max = etrue; 1346 etrue_max = etrue;
1299 break; 1347 break;
1300 } 1348 }
@@ -1302,41 +1350,40 @@ void GCTAEdisp2D::compute_ebounds_src(const double&amp; theta, @@ -1302,41 +1350,40 @@ void GCTAEdisp2D::compute_ebounds_src(const double&amp; theta,
1302 } // endfor: find minimum boundary 1350 } // endfor: find minimum boundary
1303 1351
1304 // Find maximum boundary 1352 // Find maximum boundary
1305 - for (int imigra = n_migras-1; imigra >= 0; imigra--) { 1353 + for (int imigra = nmigras-1; imigra >= 0; imigra--) {
1306 1354
1307 - // Get mean migra  
1308 - double migra = 0.5 * (m_edisp.axis_lo(m_inx_migra,imigra) +  
1309 - m_edisp.axis_lo(m_inx_migra,imigra)); 1355 + // Compute migra value
  1356 + double migra = this->migra(imigra);
1310 1357
1311 // Fall through if migration is zero 1358 // Fall through if migration is zero
1312 if (migra == 0.0) { 1359 if (migra == 0.0) {
1313 continue; 1360 continue;
1314 } 1361 }
1315 1362
1316 - // Compute log10 of true energy 1363 + // Compute true energy
1317 etrue = ereco / migra; 1364 etrue = ereco / migra;
1318 1365
1319 - // Get matrix term 1366 + // Get energy dispersion
1320 double edisp = operator()(ereco, etrue, theta); 1367 double edisp = operator()(ereco, etrue, theta);
1321 1368
1322 // Find first non-negligible matrix term 1369 // Find first non-negligible matrix term
1323 if (edisp >= eps) { 1370 if (edisp >= eps) {
1324 - minFound = true; 1371 + found_min = true;
1325 etrue_min = etrue; 1372 etrue_min = etrue;
1326 break; 1373 break;
1327 } 1374 }
1328 1375
1329 } // endfor: find maximum boundary 1376 } // endfor: find maximum boundary
1330 1377
1331 - // If we did not find boundaries then set the interval to  
1332 - // a zero interval for safety  
1333 - if (!minFound || !maxFound) { 1378 + // If we did not find boundaries then set the interval to a zero
  1379 + // interval for safety
  1380 + if (!found_min || !found_max) {
1334 etrue_min.clear(); 1381 etrue_min.clear();
1335 etrue_max.clear(); 1382 etrue_max.clear();
1336 } 1383 }
1337 1384
1338 // Add energy boundaries to vector 1385 // Add energy boundaries to vector
1339 - m_ebounds_src.push_back(GEbounds(etrue_min, etrue_max)); 1386 + m_etrue_bounds.push_back(GEbounds(etrue_min, etrue_max));
1340 1387
1341 } // endfor : loopend over observed energy 1388 } // endfor : loopend over observed energy
1342 1389
@@ -1349,15 +1396,7 @@ void GCTAEdisp2D::compute_ebounds_src(const double&amp; theta, @@ -1349,15 +1396,7 @@ void GCTAEdisp2D::compute_ebounds_src(const double&amp; theta,
1349 * @brief Set table 1396 * @brief Set table
1350 * 1397 *
1351 * After assigning or loading a response table, performs all necessary steps 1398 * After assigning or loading a response table, performs all necessary steps
1352 - * to set the table. Specifically, the method normalizes the energy  
1353 - * dispersion for each offset angle and true energy so that  
1354 - *  
1355 - * \f[  
1356 - * \int_{m_{\rm min}}^{m_{\rm max}}  
1357 - * E_{\rm disp}(E_{\rm true}, m, \theta) \, dm = 1  
1358 - * \f]  
1359 - *  
1360 - * where \f$m=E_{\rm reco}/E_{\rm true}\f$. 1399 + * to set the table.
1361 * 1400 *
1362 * For CTA, the method applies a kludge that smoothes the energy dispersion 1401 * For CTA, the method applies a kludge that smoothes the energy dispersion
1363 * table to get rid of numerical fluctuations. 1402 * table to get rid of numerical fluctuations.
@@ -1407,7 +1446,7 @@ void GCTAEdisp2D::set_table(void) @@ -1407,7 +1446,7 @@ void GCTAEdisp2D::set_table(void)
1407 // Normalize energy dispersion table 1446 // Normalize energy dispersion table
1408 normalize_table(); 1447 normalize_table();
1409 1448
1410 - // Set maximum energy dispersion value 1449 + // Set maximum energy dispersion value array
1411 set_max_edisp(); 1450 set_max_edisp();
1412 1451
1413 // Save test 1452 // Save test
@@ -1452,20 +1491,50 @@ void GCTAEdisp2D::set_boundaries(void) @@ -1452,20 +1491,50 @@ void GCTAEdisp2D::set_boundaries(void)
1452 1491
1453 1492
1454 /***********************************************************************//** 1493 /***********************************************************************//**
1455 - * @brief Set maximum energy dispersion value 1494 + * @brief Set array of maximum energy dispersion values
  1495 + *
  1496 + * Sets an internal array of maximum energy dispersion values for each given
  1497 + * true photon energy and offset angle in the response table. This array will
  1498 + * be used for Monte Carlo simulations.
1456 ***************************************************************************/ 1499 ***************************************************************************/
1457 -void GCTAEdisp2D::set_max_edisp(void) const 1500 +void GCTAEdisp2D::set_max_edisp(void)
1458 { 1501 {
1459 - // Initialise maximum  
1460 - m_max_edisp = 0.0;  
1461 -  
1462 - // Loop over all response table elements  
1463 - for (int i = 0; i < m_edisp.elements(); ++i) {  
1464 - double value = m_edisp(m_inx_matrix,i);  
1465 - if (value > m_max_edisp) {  
1466 - m_max_edisp = value;  
1467 - }  
1468 - } 1502 + // Get number of bins
  1503 + int netrue = m_edisp.axis_bins(m_inx_etrue);
  1504 + int nmigra = m_edisp.axis_bins(m_inx_migra);
  1505 + int ntheta = m_edisp.axis_bins(m_inx_theta);
  1506 +
  1507 + // Initialise maximum energy dispersion
  1508 + m_max_edisp.assign(netrue*ntheta, 0.0);
  1509 +
  1510 + // Loop over offset angle
  1511 + for (int itheta = 0, index = 0; itheta < ntheta; ++itheta) {
  1512 +
  1513 + // Loop over true photon energy
  1514 + for (int ietrue = 0; ietrue < netrue; ++ietrue, ++index) {
  1515 +
  1516 + // Get true photon energy
  1517 + GEnergy etrue = this->etrue(ietrue);
  1518 +
  1519 + // Get offset and stride
  1520 + int offset = table_index(ietrue, 0, itheta);
  1521 + int stride = table_stride(m_inx_migra);
  1522 +
  1523 + // Find maximum
  1524 + double maximum = 0.0;
  1525 + for (int imigra = 0, i = offset; imigra < nmigra; ++imigra, i += stride) {
  1526 + double value = m_edisp(m_inx_matrix, i);
  1527 + if (value > maximum) {
  1528 + maximum = value;
  1529 + }
  1530 + }
  1531 +
  1532 + // Set maximum
  1533 + m_max_edisp[index] = maximum / etrue.MeV();
  1534 +
  1535 + } // endfor: looped over true photon energy
  1536 +
  1537 + } // endfor: looped over offset angle
1469 1538
1470 // Return 1539 // Return
1471 return; 1540 return;
@@ -1473,13 +1542,76 @@ void GCTAEdisp2D::set_max_edisp(void) const @@ -1473,13 +1542,76 @@ void GCTAEdisp2D::set_max_edisp(void) const
1473 1542
1474 1543
1475 /***********************************************************************//** 1544 /***********************************************************************//**
  1545 + * @brief Get maximum energy dispersion value
  1546 + *
  1547 + * @param[in] etrue True photon energy.
  1548 + * @param[in] theta Offset angle (radians).
  1549 + * @return Maximum energy dispersion value.
  1550 + *
  1551 + * For a given true energy @p etrue and offset angle @p theta, returns the
  1552 + * maximum energy dispersion value that may occur. This method makes use
  1553 + * of the internal array pre-computed by set_max_edisp().
  1554 + *
  1555 + * If set_max_edisp() was not called, the method returns 0.
  1556 + ***************************************************************************/
  1557 +double GCTAEdisp2D::get_max_edisp(const GEnergy& etrue,
  1558 + const double& theta) const
  1559 +{
  1560 + // Initialise maximum energy dispersion
  1561 + double max_edisp = 0.0;
  1562 +
  1563 + // Continue only if maximum energy dispersion array is set
  1564 + if (m_max_edisp.size() > 0) {
  1565 +
  1566 + // Get number of true energy bins
  1567 + int netrue = m_edisp.axis_bins(m_inx_etrue);
  1568 +
  1569 + // Get references to axis nodes
  1570 + const GNodeArray& etrue_nodes = m_edisp.axis_nodes(m_inx_etrue);
  1571 + const GNodeArray& theta_nodes = m_edisp.axis_nodes(m_inx_theta);
  1572 +
  1573 + // Set values
  1574 + etrue_nodes.set_value(etrue.log10TeV());
  1575 + theta_nodes.set_value(theta);
  1576 +
  1577 + // Get indices that bound the true energy and offset angle
  1578 + int inx_left = theta_nodes.inx_left() * netrue;
  1579 + int inx_right = theta_nodes.inx_right() * netrue;
  1580 + int index_1 = inx_left + etrue_nodes.inx_left();
  1581 + int index_2 = inx_left + etrue_nodes.inx_right();
  1582 + int index_3 = inx_right + etrue_nodes.inx_left();
  1583 + int index_4 = inx_right + etrue_nodes.inx_right();
  1584 +
  1585 + // Get maximum energy dispersion
  1586 + max_edisp = m_max_edisp[index_1];
  1587 + double max_edisp_2 = m_max_edisp[index_2];
  1588 + double max_edisp_3 = m_max_edisp[index_3];
  1589 + double max_edisp_4 = m_max_edisp[index_4];
  1590 + if (max_edisp_2 > max_edisp) {
  1591 + max_edisp = max_edisp_2;
  1592 + }
  1593 + if (max_edisp_3 > max_edisp) {
  1594 + max_edisp = max_edisp_3;
  1595 + }
  1596 + if (max_edisp_4 > max_edisp) {
  1597 + max_edisp = max_edisp_4;
  1598 + }
  1599 +
  1600 + } // endif: Maximum energy dispersion array was set
  1601 +
  1602 + // Return maximum energy dispersion
  1603 + return max_edisp;
  1604 +}
  1605 +
  1606 +
  1607 +/***********************************************************************//**
1476 * @brief Normalize energy dispersion table 1608 * @brief Normalize energy dispersion table
1477 * 1609 *
1478 * Normalize the energy dispersion table using 1610 * Normalize the energy dispersion table using
1479 * 1611 *
1480 * \f[ 1612 * \f[
1481 * \int_{E_{\rm reco}^{\rm min}}^{E_{\rm reco}^{\rm max}} 1613 * \int_{E_{\rm reco}^{\rm min}}^{E_{\rm reco}^{\rm max}}
1482 - * E_{\rm disp}(E_{\rm true}, E_{\rm reco}, \theta) \, dE_{\rm reco} = 1 1614 + * E_{\rm disp}(E_{\rm reco} | E_{\rm true}, \theta) \, dE_{\rm reco} = 1
1483 * \f] 1615 * \f]
1484 * 1616 *
1485 * where 1617 * where
@@ -1487,8 +1619,7 @@ void GCTAEdisp2D::set_max_edisp(void) const @@ -1487,8 +1619,7 @@ void GCTAEdisp2D::set_max_edisp(void) const
1487 * minimum and maximum migration value for a given true energy as returned 1619 * minimum and maximum migration value for a given true energy as returned
1488 * by the ebounds_obs() method, and 1620 * by the ebounds_obs() method, and
1489 * \f$E_{\rm disp}(E_{\rm true}, E_{\rm reco}, \theta)\f$ is the energy 1621 * \f$E_{\rm disp}(E_{\rm true}, E_{\rm reco}, \theta)\f$ is the energy
1490 - * dispersion returned by the GCTAEdisp2D::operator(), given in units of  
1491 - * \f$MeV^{-1}\f$. 1622 + * dispersion returned by the operator(), given in units of \f$MeV^{-1}\f$.
1492 * 1623 *
1493 * The normalisation is performed for each \f$E_{\rm true}\f$ and 1624 * The normalisation is performed for each \f$E_{\rm true}\f$ and
1494 * \f$\theta\f$ bin using a Romberg integration method. Since the 1625 * \f$\theta\f$ bin using a Romberg integration method. Since the
@@ -1518,21 +1649,13 @@ void GCTAEdisp2D::normalize_table(void) @@ -1518,21 +1649,13 @@ void GCTAEdisp2D::normalize_table(void)
1518 for (int i_theta = 0; i_theta < theta_size; ++i_theta) { 1649 for (int i_theta = 0; i_theta < theta_size; ++i_theta) {
1519 1650
1520 // Get offset angle (in radians) 1651 // Get offset angle (in radians)
1521 - double theta = 0.5 * (m_edisp.axis_lo(m_inx_theta,i_theta) +  
1522 - m_edisp.axis_hi(m_inx_theta,i_theta)) *  
1523 - gammalib::deg2rad; 1652 + double theta = this->theta(i_theta);
1524 1653
1525 // Loop over true photon energy 1654 // Loop over true photon energy
1526 for (int i_etrue = 0; i_etrue < etrue_size; ++i_etrue) { 1655 for (int i_etrue = 0; i_etrue < etrue_size; ++i_etrue) {
1527 1656
1528 - // Get the base 10 logarithm of the true energy  
1529 - double emin = std::log10(m_edisp.axis_lo(m_inx_etrue,i_etrue));  
1530 - double emax = std::log10(m_edisp.axis_hi(m_inx_etrue,i_etrue));  
1531 - double logEsrc = 0.5*(emin+emax);  
1532 -  
1533 - // Set true energy  
1534 - GEnergy etrue;  
1535 - etrue.log10TeV(logEsrc); 1657 + // Get true photon energy
  1658 + GEnergy etrue = this->etrue(i_etrue);
1536 1659
1537 // Initialise integration 1660 // Initialise integration
1538 double sum = 0.0; 1661 double sum = 0.0;
@@ -1544,12 +1667,12 @@ void GCTAEdisp2D::normalize_table(void) @@ -1544,12 +1667,12 @@ void GCTAEdisp2D::normalize_table(void)
1544 for (int i = 0; i < ebounds.size(); ++i) { 1667 for (int i = 0; i < ebounds.size(); ++i) {
1545 1668
1546 // Get energy boundaries in log10 of energy in MeV 1669 // Get energy boundaries in log10 of energy in MeV
1547 - emin = ebounds.emin(i).log10MeV();  
1548 - emax = ebounds.emax(i).log10MeV(); 1670 + double emin = ebounds.emin(i).log10MeV();
  1671 + double emax = ebounds.emax(i).log10MeV();
1549 1672
1550 // Setup integration function 1673 // Setup integration function
1551 - edisp_kern integrand(this, etrue, theta);  
1552 - GIntegral integral(&integrand); 1674 + edisp_ereco_kern integrand(this, etrue, theta);
  1675 + GIntegral integral(&integrand);
1553 1676
1554 // Set integration precision 1677 // Set integration precision
1555 integral.eps(1.0e-6); 1678 integral.eps(1.0e-6);
@@ -2108,37 +2231,37 @@ GNdarray GCTAEdisp2D::gaussian_array(const double&amp; mean, @@ -2108,37 +2231,37 @@ GNdarray GCTAEdisp2D::gaussian_array(const double&amp; mean,
2108 2231
2109 2232
2110 /***********************************************************************//** 2233 /***********************************************************************//**
2111 - * @brief Integration kernel for GCTAEdisp2D::edisp_kern class 2234 + * @brief Integration kernel for GCTAEdisp2D::edisp_ereco_kern class
2112 * 2235 *
2113 - * @param[in] logEobs Log10 of observation energy (\f$\log_{10}\f$ MeV). 2236 + * @param[in] log10Ereco Log10 of reconstructed energy (\f$\log_{10}\f$ MeV).
2114 * @return Energy dispersion (\f$(\log_{10}\f$ MeV\f$)^{-1}\f$). 2237 * @return Energy dispersion (\f$(\log_{10}\f$ MeV\f$)^{-1}\f$).
2115 * 2238 *
2116 * This method implements the function 2239 * This method implements the function
2117 * 2240 *
2118 * \f[ 2241 * \f[
2119 - * E_{\rm disp}(E_{\rm true}, \log_{10} E_{\rm reco}, \theta) =  
2120 - * E_{\rm disp}(E_{\rm true}, m, \theta) \times 2242 + * E_{\rm disp}(\log_{10} E_{\rm reco} | E_{\rm true}, \theta) =
  2243 + * E_{\rm disp}(m | E_{\rm true}, \theta) \times
2121 * \frac{\log 10 \times E_{\rm reco}}{E_{\rm true}} 2244 * \frac{\log 10 \times E_{\rm reco}}{E_{\rm true}}
2122 * \f] 2245 * \f]
2123 * 2246 *
2124 - * which is the integration kernel needed for the GCTAEdisp2D::edisp_kern  
2125 - * class. The class is used by GCTAEdisp2D::normalize_table to integrate the  
2126 - * energy dispersion information using 2247 + * which is the integration kernel needed for the
  2248 + * GCTAEdisp2D::edisp_ereco_kern class. The class is used by
  2249 + * GCTAEdisp2D::normalize_table() to integrate the energy dispersion
  2250 + * information using
2127 * 2251 *
2128 * \f[ 2252 * \f[
2129 * \int_{\log_{10} E_{\rm reco}^{\rm min}}^{\log_{10} E_{\rm reco}^{\rm max}} 2253 * \int_{\log_{10} E_{\rm reco}^{\rm min}}^{\log_{10} E_{\rm reco}^{\rm max}}
2130 - * E_{\rm disp}(E_{\rm true}, \log_{10} E_{\rm reco}, \theta) \, 2254 + * E_{\rm disp}(\log_{10} E_{\rm reco} | E_{\rm true}, \theta) \,
2131 * d(\log_{10} E_{\rm reco}) 2255 * d(\log_{10} E_{\rm reco})
2132 * \f] 2256 * \f]
2133 ***************************************************************************/ 2257 ***************************************************************************/
2134 -double GCTAEdisp2D::edisp_kern::eval(const double& logEobs) 2258 +double GCTAEdisp2D::edisp_ereco_kern::eval(const double& log10Ereco)
2135 { 2259 {
2136 - // Set reconstructued energy 2260 + // Set reconstructued event energy
2137 GEnergy ereco; 2261 GEnergy ereco;
2138 - ereco.log10MeV(logEobs); 2262 + ereco.log10MeV(log10Ereco);
2139 2263
2140 - // Get function value. Note that logEobs is in log10 MeV, while we need  
2141 - // the value in log10 TeV. 2264 + // Get function value
2142 double value = m_parent->operator()(ereco, m_etrue, m_theta); 2265 double value = m_parent->operator()(ereco, m_etrue, m_theta);
2143 2266
2144 // Normalize energy dispersion so that the units are per log10 MeV 2267 // Normalize energy dispersion so that the units are per log10 MeV
@@ -2147,8 +2270,8 @@ double GCTAEdisp2D::edisp_kern::eval(const double&amp; logEobs) @@ -2147,8 +2270,8 @@ double GCTAEdisp2D::edisp_kern::eval(const double&amp; logEobs)
2147 // Compile option: Check for NaN 2270 // Compile option: Check for NaN
2148 #if defined(G_NAN_CHECK) 2271 #if defined(G_NAN_CHECK)
2149 if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) { 2272 if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
2150 - std::cout << "*** ERROR: GCTAEdisp2D::edisp_kern::eval";  
2151 - std::cout << "(logEobs=" << logEobs << "): "; 2273 + std::cout << "*** ERROR: GCTAEdisp2D::edisp_ereco_kern::eval";
  2274 + std::cout << "(log10Ereco=" << log10Ereco << "): ";
2152 std::cout << " NaN/Inf encountered"; 2275 std::cout << " NaN/Inf encountered";
2153 std::cout << " (value=" << value; 2276 std::cout << " (value=" << value;
2154 std::cout << ")" << std::endl; 2277 std::cout << ")" << std::endl;
inst/cta/test/dev/test_sim_edisp.py
@@ -31,7 +31,7 @@ import time @@ -31,7 +31,7 @@ import time
31 # ============== # 31 # ============== #
32 # Simulate Edisp # 32 # Simulate Edisp #
33 # ============== # 33 # ============== #
34 -def sim_edisp(edisp, etrue, eobs_max=40.0, ebins=1000, nmc=100000): 34 +def sim_edisp(edisp, etrue, theta=0.0, ebins=1000, nmc=1000000):
35 """ 35 """
36 Simulate Edisp and show results using matplotlib (if available). 36 Simulate Edisp and show results using matplotlib (if available).
37 """ 37 """
@@ -40,20 +40,31 @@ def sim_edisp(edisp, etrue, eobs_max=40.0, ebins=1000, nmc=100000): @@ -40,20 +40,31 @@ def sim_edisp(edisp, etrue, eobs_max=40.0, ebins=1000, nmc=100000):
40 import matplotlib.pyplot as plt 40 import matplotlib.pyplot as plt
41 has_matplotlib = True 41 has_matplotlib = True
42 except ImportError: 42 except ImportError:
43 - print("Matplotlib is not (correctly) installed on your system.") 43 + print('Matplotlib is not (correctly) installed on your system.')
44 has_matplotlib = False 44 has_matplotlib = False
45 45
46 # Continue only if matplotlib is available 46 # Continue only if matplotlib is available
47 if has_matplotlib: 47 if has_matplotlib:
48 48
49 - # Set log10(etrue)  
50 - logEtrue = math.log10(etrue)  
51 -  
52 - # Create eobs axis  
53 - eobs_axis = []  
54 - dEobs = eobs_max/ebins 49 + # Create ereco axis
  50 + ereco_axis = []
  51 + ereco_bounds = edisp.ereco_bounds(etrue, theta)
  52 + ereco_min = ereco_bounds.emin().TeV()
  53 + ereco_max = ereco_bounds.emax().TeV()
  54 + dE = (ereco_max-ereco_min)/ebins
  55 + for i in range(ebins):
  56 + ereco_axis.append(ereco_min+i*dE)
  57 + print('Ereco: %.3f - %.3f TeV' % (ereco_min, ereco_max))
  58 +
  59 + # Create etrue axis
  60 + etrue_axis = []
  61 + etrue_bounds = edisp.etrue_bounds(etrue, theta)
  62 + etrue_min = etrue_bounds.emin().TeV()
  63 + etrue_max = etrue_bounds.emax().TeV()
  64 + dEtrue = (etrue_max-etrue_min)/ebins
55 for i in range(ebins): 65 for i in range(ebins):
56 - eobs_axis.append((i+0.5)*dEobs) 66 + etrue_axis.append(etrue_min+i*dEtrue)
  67 + print('Etrue: %.3f - %.3f TeV' % (etrue_min, etrue_max))
57 68
58 # Reset histogram 69 # Reset histogram
59 counts = [0.0 for i in range(ebins)] 70 counts = [0.0 for i in range(ebins)]
@@ -64,87 +75,122 @@ def sim_edisp(edisp, etrue, eobs_max=40.0, ebins=1000, nmc=100000): @@ -64,87 +75,122 @@ def sim_edisp(edisp, etrue, eobs_max=40.0, ebins=1000, nmc=100000):
64 # Simulate observed energies 75 # Simulate observed energies
65 tstart = time.clock() 76 tstart = time.clock()
66 for i in range(nmc): 77 for i in range(nmc):
67 - eobs = edisp.mc(ran, logEtrue).TeV()  
68 - #eobs = edisp.mc(ran, logEtrue, float(i)).TeV()  
69 - index = int(eobs/dEobs) 78 + eobs = edisp.mc(ran, etrue, theta).TeV()
  79 + index = int((eobs-ereco_min)/dE-0.5)
70 if (index < ebins): 80 if (index < ebins):
71 counts[index] += 1.0 81 counts[index] += 1.0
72 telapsed = (time.clock() - tstart) 82 telapsed = (time.clock() - tstart)
73 - print("Elapsed CPU time (sec) %f" % telapsed) 83 + print('Elapsed CPU time (sec) %f' % telapsed)
  84 +
  85 + # Dump probabilities
  86 + print('Probability integrated over Ereco: %f' % \
  87 + edisp.prob_erecobin(ereco_bounds.emin(), ereco_bounds.emax(), etrue, theta))
74 88
75 # Create error bars 89 # Create error bars
76 error = [math.sqrt(c) for c in counts] 90 error = [math.sqrt(c) for c in counts]
77 91
78 # Get expected energy dispersion 92 # Get expected energy dispersion
79 - sum = 0.0  
80 - exp_edisp = []  
81 - cumul = []  
82 - #fmax = 0.0 93 + edisp_ereco = []
  94 + sum_ereco = 0.0
83 for i in range(ebins): 95 for i in range(ebins):
84 - eobs = eobs_axis[i]  
85 - if eobs <= edisp.ebounds_obs(logEtrue).emax().TeV():  
86 - dLogEobs = math.log10(eobs_axis[i]) - math.log10(eobs_axis[i-1])  
87 - value = edisp(math.log10(eobs), logEtrue) * dLogEobs * nmc  
88 - #if fmax < value / dLogEobs / nmc:  
89 - # fmax = value / dLogEobs / nmc 96 + ereco = gammalib.GEnergy(ereco_axis[i], 'TeV')
  97 + if ereco.TeV() >= ereco_min and ereco.TeV() <= ereco_max:
  98 + value = edisp(ereco, etrue, theta) * dE * 1.0e6
90 else: 99 else:
91 value = 0.0 100 value = 0.0
92 - sum += value  
93 - exp_edisp.append(value)  
94 - cumul.append(sum/nmc)  
95 - print(sum/nmc)  
96 - #print(fmax)  
97 -  
98 - # Create figure  
99 - fig, ax1 = plt.subplots()  
100 -  
101 - # Plot simulated data  
102 - ax1.plot(eobs_axis, counts, 'ro')  
103 - #ax1.errorbar(eobs_axis, counts, error, ecolor='g')  
104 -  
105 - # Plot energy dispersion  
106 - ax1.plot(eobs_axis, exp_edisp, 'b-')  
107 -  
108 - # Set axes  
109 - ax1.set_xlabel("Observed energy (TeV)")  
110 - ax1.set_ylabel("Number of counts") 101 + sum_ereco += value
  102 + edisp_ereco.append(value)
  103 + print('Sum computed over Ereco: %f' % sum_ereco)
111 104
112 - #ax2 = ax1.twinx() 105 + # Get energy dispersion versus etrue
  106 + edisp_etrue = []
  107 + sum_etrue = 0.0
  108 + for i in range(ebins):
  109 + ereco = gammalib.GEnergy(etrue_axis[i], 'TeV')
  110 + if ereco.TeV() >= etrue_min and ereco.TeV() <= etrue_max:
  111 + value = edisp(etrue, ereco, theta) * dEtrue * 1.0e6
  112 + else:
  113 + value = 0.0
  114 + sum_etrue += value
  115 + edisp_etrue.append(value)
  116 + print('Sum computed over Etrue: %f' % sum_etrue)
113 117
114 - # Plot cumulative dispersion  
115 - #ax2.plot(eobs_axis, cumul, 'm') 118 + # Normalise expectation
  119 + if sum_ereco > 0.0:
  120 + norm = nmc/sum_ereco
  121 + for i in range(ebins):
  122 + edisp_ereco[i] *= norm
116 123
117 - # Set axis  
118 - #ax2.set_ylabel("Cumulative probability") 124 + # Create figure
  125 + fig = plt.figure(figsize=(14,6))
  126 +
  127 + # Add title
  128 + title = r'MC simulated energy dispersion (E = %s, theta = %.2f)' % \
  129 + (str(etrue), theta*gammalib.rad2deg)
  130 + fig.suptitle(title, fontsize=16)
  131 +
  132 + # Plot energy dispersion versus ereco
  133 + ax1 = fig.add_subplot(121)
  134 + ax1.plot(ereco_axis, edisp_ereco, '-', linewidth=2)
  135 + ax1.step(ereco_axis, counts, 'r', where='mid', linewidth=1)
  136 + ymin, ymax = ax1.get_ylim()
  137 + ax1.plot([etrue.TeV(), etrue.TeV()], [ymin,ymax], 'g-', linewidth=2)
  138 + ax1.set_xlabel('Reconstructed energy (TeV)')
  139 + ax1.set_ylabel('Number of counts')
  140 + ax1.set_title(r'Energy dispersion versus E$_{\rm reco}$')
  141 +
  142 + # Plot energy dispersion versus etrue
  143 + ax2 = fig.add_subplot(122)
  144 + ax2.plot(etrue_axis, edisp_etrue, '-', linewidth=2)
  145 + ymin, ymax = ax2.get_ylim()
  146 + ax2.plot([etrue.TeV(), etrue.TeV()], [ymin,ymax], 'g-', linewidth=2)
  147 + ax2.set_xlabel('True energy (TeV)')
  148 + ax2.set_ylabel('Arbitrary unit')
  149 + ax2.set_title(r'Energy dispersion versus E$_{\rm true}$')
119 150
120 # Notify 151 # Notify
121 - print("PLEASE CLOSE WINDOW TO CONTINUE ...") 152 + print('PLEASE CLOSE WINDOW TO CONTINUE ...')
122 153
123 # Show plot 154 # Show plot
124 - plt.title("MC simulated Energy dispersion (true photon energy "+str(etrue)+" TeV)")  
125 plt.show() 155 plt.show()
126 156
127 # Return 157 # Return
128 return 158 return
129 159
130 160
131 -#==========================# 161 +# ======================== #
132 # Main routine entry point # 162 # Main routine entry point #
133 -#==========================# 163 +# ======================== #
134 if __name__ == '__main__': 164 if __name__ == '__main__':
135 - """  
136 - Simulate Energy dispersion.  
137 - """ 165 +
138 # Dump header 166 # Dump header
139 - print("")  
140 - print("******************************")  
141 - print("* Simulate Energy dispersion *")  
142 - print("******************************") 167 + print('')
  168 + print('******************************')
  169 + print('* Simulate Energy dispersion *')
  170 + print('******************************')
143 171
144 # Load edisp 172 # Load edisp
145 - #edisp = gammalib.GCTAEdispRmf("./caldb/dc1/rmf.fits")  
146 - #edisp = gammalib.GCTAEdispPerfTable("./caldb/cta_dummy_irf.dat")  
147 - edisp = gammalib.GCTAEdisp2D("../caldb/edisp_matrix.fits") 173 + #edisp = gammalib.GCTAEdispRmf('./caldb/dc1/rmf.fits')
  174 + #edisp = gammalib.GCTAEdispPerfTable('./caldb/cta_dummy_irf.dat')
  175 +
  176 + # Simulate 2D energy dispersion matrix
  177 + #edisp = gammalib.GCTAEdisp2D('../caldb/edisp_matrix.fits')
  178 + #sim_edisp(edisp, gammalib.GEnergy(0.1, 'TeV'))
  179 + #sim_edisp(edisp, gammalib.GEnergy(1.0, 'TeV'))
  180 + #sim_edisp(edisp, gammalib.GEnergy(10.0, 'TeV'))
  181 + #sim_edisp(edisp, gammalib.GEnergy(1.0, 'TeV'), theta=5.0*gammalib.deg2rad)
  182 +
  183 + # Test H.E.S.S. energy dispersion
  184 + edisp = gammalib.GCTAEdisp2D('/project-data/hess/hess_dl3_dr1/data/hess_dl3_dr1_obs_id_023523.fits')
  185 + #sim_edisp(edisp, gammalib.GEnergy(0.2, 'TeV'))
  186 + #sim_edisp(edisp, gammalib.GEnergy(0.3, 'TeV'))
  187 + #sim_edisp(edisp, gammalib.GEnergy(0.4, 'TeV'), theta=2.0*gammalib.deg2rad)
  188 + #sim_edisp(edisp, gammalib.GEnergy(0.5, 'TeV'), theta=2.5*gammalib.deg2rad)
  189 + #sim_edisp(edisp, gammalib.GEnergy(0.7, 'TeV'))
  190 + #sim_edisp(edisp, gammalib.GEnergy(1.0, 'TeV'))
  191 + #sim_edisp(edisp, gammalib.GEnergy(0.7, 'TeV'))
  192 + sim_edisp(edisp, gammalib.GEnergy(5.0, 'TeV'))
  193 + #sim_edisp(edisp, gammalib.GEnergy(50.0, 'TeV'))
  194 + #sim_edisp(edisp, gammalib.GEnergy(1.0, 'TeV'), theta=2.5*gammalib.deg2rad)
  195 +
148 196
149 - # Simulate Edisp  
150 - sim_edisp(edisp, 20.0) # Energy in TeV