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 54 *
55 55 * This class implements the energy dispersion for the CTA 2D response. The
56 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 59 * \f[
62   - * E_{\rm disp}(E_{\rm true}, m, \theta)
  60 + * E_{\rm disp}(E_{\rm reco} | E_{\rm true}, \theta)
63 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 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 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 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 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 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 88 * \f]
97 89 ***************************************************************************/
98 90 class GCTAEdisp2D : public GCTAEdisp {
... ... @@ -152,46 +144,47 @@ public:
152 144  
153 145 private:
154 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 189 // Kludge
197 190 void smooth_table(void);
... ... @@ -213,15 +206,15 @@ private:
213 206 const double& total) const;
214 207  
215 208 // Protected classes
216   - class edisp_kern : public GFunction {
  209 + class edisp_ereco_kern : public GFunction {
217 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 218 protected:
226 219 const GCTAEdisp2D* m_parent; //!< Pointer to parent class
227 220 GEnergy m_etrue; //!< True photon energy
... ... @@ -229,32 +222,32 @@ private:
229 222 };
230 223  
231 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 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 245 mutable GEnergy m_last_etrue;
252 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 191 * Returns the energy dispersion
192 192 *
193 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 196 * \f]
197 197 *
198 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 203 double GCTAEdisp2D::operator()(const GEnergy& ereco,
205 204 const GEnergy& etrue,
... ... @@ -222,7 +221,6 @@ double GCTAEdisp2D::operator()(const GEnergy&amp; ereco,
222 221 (theta >= m_theta_min) && (theta <= m_theta_max)) {
223 222  
224 223 // Compute migration (migra=Ereco/Etrue
225   - //double EobsOverEsrc = std::exp((logEobs-logEsrc) * gammalib::ln10);
226 224 double migra = ereco.MeV() / etrue.MeV();
227 225  
228 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 476 * No energy dispersion information found for parameters or
479 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 480 * angle @p theta. If no energy dispersion information is available the
483 481 * method will return the true photon energy.
484 482 ***************************************************************************/
... ... @@ -492,20 +490,20 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran,
492 490 // Make sure that energy dispersion is online
493 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 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 501 // Throw an exception if minimum energy is equal or larger than maximum
504 502 // energy (this means that no energy dispersion information is available
505 503 // for that energy)
506 504 if (emin >= emax) {
507 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 507 "offset angle "+
510 508 gammalib::str(theta*gammalib::rad2deg)+" deg "
511 509 "and azimuth angle "+
... ... @@ -515,8 +513,11 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran,
515 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 519 // Throw an exception if maximum energy dispersion is zero
519   - if (m_max_edisp <= 0.0) {
  520 + if (max_edisp <= 0.0) {
520 521 std::string msg = "Energy dispersion matrix is empty. Please provide "
521 522 "a valid energy dispersion matrix.";
522 523 throw GException::invalid_return_value(G_MC, msg);
... ... @@ -524,7 +525,6 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran,
524 525  
525 526 // Initialise rejection method
526 527 double ewidth = emax - emin;
527   - double logEobs = etrue.log10TeV();
528 528 double f = 0.0;
529 529 double ftest = 1.0;
530 530 int zeros = 0;
... ... @@ -540,14 +540,13 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran,
540 540 // subsequent zeros the loop is terminated with an exception
541 541 int zeros = 0;
542 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 545 if (f == 0.0) {
547 546 zeros++;
548 547 if (zeros > max_subsequent_zeros) {
549 548 std::string msg = "No valid energy dispersion information "
550   - "available for true photon energy "+
  549 + "available for true photon energy "+
551 550 etrue.print()+", offset angle "+
552 551 gammalib::str(theta*gammalib::rad2deg)+
553 552 " deg and azimuth angle "+
... ... @@ -564,7 +563,7 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran,
564 563 // Get uniform random value between zero and the maximum energy
565 564 // dispersion value. The loop is quite if the random number is smaller
566 565 // than the energy dispersion value
567   - ftest = ran.uniform() * m_max_edisp;
  566 + ftest = ran.uniform() * max_edisp;
568 567  
569 568 } // endwhile:
570 569  
... ... @@ -584,7 +583,7 @@ GEnergy GCTAEdisp2D::mc(GRan&amp; ran,
584 583 * @return Observed energy boundaries.
585 584 *
586 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 587 * offset angle @p theta.
589 588 ***************************************************************************/
590 589 GEbounds GCTAEdisp2D::ereco_bounds(const GEnergy& etrue,
... ... @@ -597,15 +596,15 @@ GEbounds GCTAEdisp2D::ereco_bounds(const GEnergy&amp; etrue,
597 596 fetch();
598 597  
599 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 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 619  
621 620 // Find right index with bisection
622 621 int low = 0;
623   - int high = m_ebounds_obs.size() - 1;
  622 + int high = m_ereco_bounds.size() - 1;
624 623 while ((high-low) > 1) {
625 624 int mid = (low+high) / 2;
626 625 double e = m_edisp.axis_lo(m_inx_etrue, mid);
... ... @@ -633,12 +632,12 @@ GEbounds GCTAEdisp2D::ereco_bounds(const GEnergy&amp; etrue,
633 632 }
634 633  
635 634 // Index found
636   - m_index_obs = low;
  635 + m_index_ereco = low;
637 636  
638 637 }
639 638  
640 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 652 * @return True energy boundaries.
654 653 *
655 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 656 * offset angle @p theta.
658 657 ***************************************************************************/
659 658 GEbounds GCTAEdisp2D::etrue_bounds(const GEnergy& ereco,
... ... @@ -666,15 +665,15 @@ GEbounds GCTAEdisp2D::etrue_bounds(const GEnergy&amp; ereco,
666 665 fetch();
667 666  
668 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 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 688  
690 689 // Find right index with bisection
691 690 int low = 0;
692   - int high = m_ebounds_src.size() - 1;
  691 + int high = m_etrue_bounds.size() - 1;
693 692 while ((high-low) > 1) {
694 693 int mid = (low+high) / 2;
695 694 double e = m_edisp.axis_lo(m_inx_etrue, mid);
... ... @@ -702,12 +701,12 @@ GEbounds GCTAEdisp2D::etrue_bounds(const GEnergy&amp; ereco,
702 701 }
703 702  
704 703 // Index found
705   - m_index_src = low;
  704 + m_index_etrue = low;
706 705  
707 706 }
708 707  
709 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 820 *
822 821 * \f[
823 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 824 * \f]
826 825 *
827 826 * where
... ... @@ -1046,19 +1045,19 @@ void GCTAEdisp2D::init_members(void)
1046 1045 m_migra_max = 0.0;
1047 1046 m_theta_min = 0.0;
1048 1047 m_theta_max = 0.0;
  1048 + m_max_edisp.clear();
1049 1049  
1050 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 1062 // Return
1064 1063 return;
... ... @@ -1086,19 +1085,19 @@ void GCTAEdisp2D::copy_members(const GCTAEdisp2D&amp; edisp)
1086 1085 m_migra_max = edisp.m_migra_max;
1087 1086 m_theta_min = edisp.m_theta_min;
1088 1087 m_theta_max = edisp.m_theta_max;
  1088 + m_max_edisp = edisp.m_max_edisp;
1089 1089  
1090 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 1102 // Return
1104 1103 return;
... ... @@ -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 1176 * @param[in] theta Offset angle (radians).
1122 1177 * @param[in] phi Azimuth angle (radians).
1123 1178 * @param[in] zenith Zenith angle (radians).
1124 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 1182 * energies covered by valid migration matrix elements. Only matrix elements
1128 1183 * with values >= 1.0e-12 are considered as valid elements. In case that no
1129 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 1192 // Clear ebounds_obs vector
1138   - m_ebounds_obs.clear();
  1193 + m_ereco_bounds.clear();
1139 1194  
1140 1195 // Set epsilon
1141 1196 const double eps = 1.0e-12;
1142 1197  
1143 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 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 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 1220 // Get matrix term
1169 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 1224 arg[m_inx_theta] = theta;
1173 1225 double edisp = m_edisp(m_inx_matrix, arg[0], arg[1], arg[2]);
1174 1226  
1175 1227 // Find first non-negligible matrix term
1176 1228 if (edisp >= eps) {
1177   - minFound = true;
1178   - logEobsMin = std::log10(EobsOverEsrc * Esrc);
  1229 + found_min = true;
  1230 + ereco_min = migra * etrue.TeV();
1179 1231 break;
1180 1232 }
1181 1233  
1182 1234 } // endfor: find minimum boundary
1183 1235  
1184 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 1242 // Get matrix term
1192 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 1246 arg[m_inx_theta] = theta;
1196 1247 double edisp = m_edisp(m_inx_matrix, arg[0], arg[1], arg[2]);
1197 1248  
1198 1249 // Find first non-negligible matrix term
1199 1250 if (edisp >= eps) {
1200   - maxFound = true;
1201   - logEobsMax = std::log10(EobsOverEsrc * Esrc);
  1251 + found_max = true;
  1252 + ereco_max = migra * etrue.TeV();
1202 1253 break;
1203 1254 }
1204 1255  
... ... @@ -1206,19 +1257,19 @@ void GCTAEdisp2D::compute_ebounds_obs(const double&amp; theta,
1206 1257  
1207 1258 // If we did not find boundaries then set the interval to
1208 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 1265 // Set energy boundaries
1215 1266 GEnergy emin;
1216 1267 GEnergy emax;
1217   - emin.log10TeV(logEobsMin);
1218   - emax.log10TeV(logEobsMax);
  1268 + emin.TeV(ereco_min);
  1269 + emax.TeV(ereco_max);
1219 1270  
1220 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 1274 } // endfor: looped over logEsrc
1224 1275  
... ... @@ -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 1284 * @param[in] theta Offset angle (radians).
1234 1285 * @param[in] phi Azimuth angle (radians).
... ... @@ -1239,62 +1290,59 @@ void GCTAEdisp2D::compute_ebounds_obs(const double&amp; theta,
1239 1290 * energies covered by valid migration matrix elements. Only matrix elements
1240 1291 * with values >= 1.0e-12 are considered as valid elements. In case that no
1241 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 1300 // Initialise energies
1250 1301 GEnergy etrue;
1251 1302 GEnergy ereco;
1252 1303  
1253 1304 // Clear ebounds_src vector
1254   - m_ebounds_src.clear();
  1305 + m_etrue_bounds.clear();
1255 1306  
1256 1307 // Set epsilon
1257 1308 const double eps = 1.0e-12;
1258 1309  
1259 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 1320 // Initialise results
1272 1321 GEnergy etrue_min;
1273 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 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 1332 // Fall through if migration is zero
1285 1333 if (migra == 0.0) {
1286 1334 continue;
1287 1335 }
1288 1336  
1289   - // Compute log10 of true energy
  1337 + // Compute true energy
1290 1338 etrue = ereco / migra;
1291 1339  
1292   - // Get matrix term
  1340 + // Get energy dispersion
1293 1341 double edisp = operator()(ereco, etrue, theta);
1294 1342  
1295 1343 // Find first non-negligible matrix term
1296 1344 if (edisp >= eps) {
1297   - maxFound = true;
  1345 + found_max = true;
1298 1346 etrue_max = etrue;
1299 1347 break;
1300 1348 }
... ... @@ -1302,41 +1350,40 @@ void GCTAEdisp2D::compute_ebounds_src(const double&amp; theta,
1302 1350 } // endfor: find minimum boundary
1303 1351  
1304 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 1358 // Fall through if migration is zero
1312 1359 if (migra == 0.0) {
1313 1360 continue;
1314 1361 }
1315 1362  
1316   - // Compute log10 of true energy
  1363 + // Compute true energy
1317 1364 etrue = ereco / migra;
1318 1365  
1319   - // Get matrix term
  1366 + // Get energy dispersion
1320 1367 double edisp = operator()(ereco, etrue, theta);
1321 1368  
1322 1369 // Find first non-negligible matrix term
1323 1370 if (edisp >= eps) {
1324   - minFound = true;
  1371 + found_min = true;
1325 1372 etrue_min = etrue;
1326 1373 break;
1327 1374 }
1328 1375  
1329 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 1381 etrue_min.clear();
1335 1382 etrue_max.clear();
1336 1383 }
1337 1384  
1338 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 1388 } // endfor : loopend over observed energy
1342 1389  
... ... @@ -1349,15 +1396,7 @@ void GCTAEdisp2D::compute_ebounds_src(const double&amp; theta,
1349 1396 * @brief Set table
1350 1397 *
1351 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 1401 * For CTA, the method applies a kludge that smoothes the energy dispersion
1363 1402 * table to get rid of numerical fluctuations.
... ... @@ -1407,7 +1446,7 @@ void GCTAEdisp2D::set_table(void)
1407 1446 // Normalize energy dispersion table
1408 1447 normalize_table();
1409 1448  
1410   - // Set maximum energy dispersion value
  1449 + // Set maximum energy dispersion value array
1411 1450 set_max_edisp();
1412 1451  
1413 1452 // Save test
... ... @@ -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 1539 // Return
1471 1540 return;
... ... @@ -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 1608 * @brief Normalize energy dispersion table
1477 1609 *
1478 1610 * Normalize the energy dispersion table using
1479 1611 *
1480 1612 * \f[
1481 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 1615 * \f]
1484 1616 *
1485 1617 * where
... ... @@ -1487,8 +1619,7 @@ void GCTAEdisp2D::set_max_edisp(void) const
1487 1619 * minimum and maximum migration value for a given true energy as returned
1488 1620 * by the ebounds_obs() method, and
1489 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 1624 * The normalisation is performed for each \f$E_{\rm true}\f$ and
1494 1625 * \f$\theta\f$ bin using a Romberg integration method. Since the
... ... @@ -1518,21 +1649,13 @@ void GCTAEdisp2D::normalize_table(void)
1518 1649 for (int i_theta = 0; i_theta < theta_size; ++i_theta) {
1519 1650  
1520 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 1654 // Loop over true photon energy
1526 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 1660 // Initialise integration
1538 1661 double sum = 0.0;
... ... @@ -1544,12 +1667,12 @@ void GCTAEdisp2D::normalize_table(void)
1544 1667 for (int i = 0; i < ebounds.size(); ++i) {
1545 1668  
1546 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 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 1677 // Set integration precision
1555 1678 integral.eps(1.0e-6);
... ... @@ -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 2237 * @return Energy dispersion (\f$(\log_{10}\f$ MeV\f$)^{-1}\f$).
2115 2238 *
2116 2239 * This method implements the function
2117 2240 *
2118 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 2244 * \frac{\log 10 \times E_{\rm reco}}{E_{\rm true}}
2122 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 2252 * \f[
2129 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 2255 * d(\log_{10} E_{\rm reco})
2132 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 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 2265 double value = m_parent->operator()(ereco, m_etrue, m_theta);
2143 2266  
2144 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 2270 // Compile option: Check for NaN
2148 2271 #if defined(G_NAN_CHECK)
2149 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 2275 std::cout << " NaN/Inf encountered";
2153 2276 std::cout << " (value=" << value;
2154 2277 std::cout << ")" << std::endl;
... ...
inst/cta/test/dev/test_sim_edisp.py
... ... @@ -31,7 +31,7 @@ import time
31 31 # ============== #
32 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 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 40 import matplotlib.pyplot as plt
41 41 has_matplotlib = True
42 42 except ImportError:
43   - print("Matplotlib is not (correctly) installed on your system.")
  43 + print('Matplotlib is not (correctly) installed on your system.')
44 44 has_matplotlib = False
45 45  
46 46 # Continue only if matplotlib is available
47 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 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 69 # Reset histogram
59 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 75 # Simulate observed energies
65 76 tstart = time.clock()
66 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 80 if (index < ebins):
71 81 counts[index] += 1.0
72 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 89 # Create error bars
76 90 error = [math.sqrt(c) for c in counts]
77 91  
78 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 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 99 else:
91 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 151 # Notify
121   - print("PLEASE CLOSE WINDOW TO CONTINUE ...")
  152 + print('PLEASE CLOSE WINDOW TO CONTINUE ...')
122 153  
123 154 # Show plot
124   - plt.title("MC simulated Energy dispersion (true photon energy "+str(etrue)+" TeV)")
125 155 plt.show()
126 156  
127 157 # Return
128 158 return
129 159  
130 160  
131   -#==========================#
  161 +# ======================== #
132 162 # Main routine entry point #
133   -#==========================#
  163 +# ======================== #
134 164 if __name__ == '__main__':
135   - """
136   - Simulate Energy dispersion.
137   - """
  165 +
138 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 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
... ...