GammaLib  2.1.0.dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
GCTAEdisp2D.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * GCTAEdisp2D.cpp - CTA 2D energy dispersion class *
3  * ----------------------------------------------------------------------- *
4  * copyright (C) 2015-2021 by Florent Forest *
5  * ----------------------------------------------------------------------- *
6  * *
7  * This program is free software: you can redistribute it and/or modify *
8  * it under the terms of the GNU General Public License as published by *
9  * the Free Software Foundation, either version 3 of the License, or *
10  * (at your option) any later version. *
11  * *
12  * This program is distributed in the hope that it will be useful, *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15  * GNU General Public License for more details. *
16  * *
17  * You should have received a copy of the GNU General Public License *
18  * along with this program. If not, see <http://www.gnu.org/licenses/>. *
19  * *
20  ***************************************************************************/
21 /**
22  * @file GCTAEdisp2D.cpp
23  * @brief CTA 2D energy dispersion class implementation
24  * @author Florent Forest
25  */
26 
27 /* __ Includes ___________________________________________________________ */
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31 #include <cmath>
32 #include <vector>
33 #include "GTools.hpp"
34 #include "GMath.hpp"
35 #include "GNdarray.hpp"
36 #include "GFft.hpp"
37 #include "GException.hpp"
38 #include "GIntegral.hpp"
39 #include "GFilename.hpp"
40 #include "GRan.hpp"
41 #include "GFits.hpp"
42 #include "GFitsTable.hpp"
43 #include "GFitsBinTable.hpp"
44 #include "GCTAEdisp2D.hpp"
45 #include "GCTASupport.hpp"
46 
47 /* __ Method name definitions ____________________________________________ */
48 #define G_READ "GCTAEdisp2D::read(GFitsTable&)"
49 #define G_MC "GCTAEdisp2D::mc(GRan&, GEnergy&, double&, double&, double&, "\
50  "double&)"
51 #define G_FETCH "GCTAEdisp2D::fetch()"
52 
53 /* __ Macros _____________________________________________________________ */
54 
55 /* __ Coding definitions _________________________________________________ */
56 #define G_SMOOTH_EDISP_KLUDGE //!< Get rid of noise in energy dispersion
57 //#define G_SMOOTH_EDISP_SAVE_TEST //!< Save test matrix
58 
59 /* __ Debug definitions __________________________________________________ */
60 //#define G_DEBUG_HESS_RENORMALIZATION
61 
62 /* __ Constants __________________________________________________________ */
63 
64 
65 /*==========================================================================
66  = =
67  = Constructors/destructors =
68  = =
69  ==========================================================================*/
70 
71 /***********************************************************************//**
72  * @brief Void constructor
73  *
74  * Constructs empty energy dispersion.
75  ***************************************************************************/
77 {
78  // Initialise class members
79  init_members();
80 
81  // Return
82  return;
83 }
84 
85 
86 /***********************************************************************//**
87  * @brief File constructor
88  *
89  * @param[in] filename FITS file name.
90  *
91  * Constructs energy dispersion from a FITS file.
92  ***************************************************************************/
94 {
95 
96  // Initialise class members
97  init_members();
98 
99  // Load energy dispersion from file
100  load(filename);
101 
102  // Return
103  return;
104 }
105 
106 
107 /***********************************************************************//**
108  * @brief Copy constructor
109  *
110  * @param[in] edisp Energy dispersion.
111  *
112  * Constructs energy dispersion by copying from another energy dispersion.
113  ***************************************************************************/
115 {
116  // Initialise class members
117  init_members();
118 
119  // Copy members
120  copy_members(edisp);
121 
122  // Return
123  return;
124 }
125 
126 
127 /***********************************************************************//**
128  * @brief Destructor
129  *
130  * Destructs energy dispersion.
131  ***************************************************************************/
133 {
134  // Free members
135  free_members();
136 
137  // Return
138  return;
139 }
140 
141 
142 /*==========================================================================
143  = =
144  = Operators =
145  = =
146  ==========================================================================*/
147 
148 /***********************************************************************//**
149  * @brief Assignment operator
150  *
151  * @param[in] edisp Energy dispersion.
152  * @return Energy dispersion.
153  *
154  * Assigns energy dispersion.
155  ***************************************************************************/
157 {
158  // Execute only if object is not identical
159  if (this != &edisp) {
160 
161  // Copy base class members
162  this->GCTAEdisp::operator=(edisp);
163 
164  // Free members
165  free_members();
166 
167  // Initialise private members
168  init_members();
169 
170  // Copy members
171  copy_members(edisp);
172 
173  } // endif: object was not identical
174 
175  // Return this object
176  return *this;
177 }
178 
179 
180 /***********************************************************************//**
181  * @brief Return energy dispersion in units of MeV\f$^{-1}\f$
182  *
183  * @param[in] ereco Reconstructed photon energy.
184  * @param[in] etrue True photon energy.
185  * @param[in] theta Offset angle in camera system (radians).
186  * @param[in] phi Azimuth angle in camera system (radians). Not used.
187  * @param[in] zenith Zenith angle in Earth system (radians). Not used.
188  * @param[in] azimuth Azimuth angle in Earth system (radians). Not used.
189  * @return Energy dispersion (MeV\f$^{-1}\f$)
190  *
191  * Returns the energy dispersion
192  *
193  * \f[
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]
197  *
198  * in units of MeV\f$^{-1}\f$ where
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.
202  ***************************************************************************/
203 double GCTAEdisp2D::operator()(const GEnergy& ereco,
204  const GEnergy& etrue,
205  const double& theta,
206  const double& phi,
207  const double& zenith,
208  const double& azimuth) const
209 {
210  // Make sure that energy dispersion is online
211  fetch();
212 
213  // Initalize edisp
214  double edisp = 0.0;
215 
216  // Get log10 of true photon energy
217  double logEsrc = etrue.log10TeV();
218 
219  // Continue only if logEsrc and theta are in validity range
220  if ((logEsrc >= m_logEsrc_min) && (logEsrc <= m_logEsrc_max) &&
221  (theta >= m_theta_min) && (theta <= m_theta_max)) {
222 
223  // Compute migration (migra=Ereco/Etrue)
224  double migra = ereco.MeV() / etrue.MeV();
225 
226  // Continue only if migration is in validity range
227  if ((migra >= m_migra_min) && (migra <= m_migra_max)) {
228 
229  // Setup argument vector
230  double arg[3];
231  arg[m_inx_etrue] = logEsrc;
232  arg[m_inx_migra] = migra;
233  arg[m_inx_theta] = theta;
234 
235  // Compute edisp
236  edisp = m_edisp(m_inx_matrix, arg[0], arg[1], arg[2]);
237 
238  // Make sure that energy dispersion is non-negative
239  if (edisp < 0.0) {
240  edisp = 0.0;
241  }
242 
243  // If energy dispersion is positive then divide the energy
244  // dispersion value by true photon energy to get the energy
245  // dispersion per MeV
246  else {
247  edisp /= etrue.MeV();
248  }
249 
250  } // endif: migration was in valid range
251 
252  } // endif: true energy and offset angle was in valid range
253 
254  // Return
255  return edisp;
256 }
257 
258 
259 /*==========================================================================
260  = =
261  = Public methods =
262  = =
263  ==========================================================================*/
264 
265 /***********************************************************************//**
266  * @brief Clear energy dispersion
267  *
268  * Clears energy dispersion.
269  ***************************************************************************/
271 {
272  // Free class members
273  free_members();
274  this->GCTAEdisp::free_members();
275 
276  // Initialise members
277  this->GCTAEdisp::init_members();
278  init_members();
279 
280  // Return
281  return;
282 }
283 
284 
285 /***********************************************************************//**
286  * @brief Clone energy dispersion
287  *
288  * @return Deep copy of energy dispersion.
289  *
290  * Returns a pointer to a deep copy of the point spread function.
291  ***************************************************************************/
293 {
294  return new GCTAEdisp2D(*this);
295 }
296 
297 
298 /***********************************************************************//**
299  * @brief Assign response table
300  *
301  * @param[in] table Response table.
302  *
303  * Assigns the response table for an energy dispersion.
304  ***************************************************************************/
306 {
307  // Assign response table
308  m_edisp = table;
309 
310  // Set table
311  set_table();
312 
313  // Return
314  return;
315 }
316 
317 
318 /***********************************************************************//**
319  * @brief Read energy dispersion from FITS table
320  *
321  * @param[in] table FITS table.
322  *
323  * @exception GException::invalid_value
324  * Response table is not three-dimensional.
325  *
326  * Reads the energy dispersion form the FITS @p table. The following column
327  * names are mandatory:
328  *
329  * ENERG_LO - True energy lower bin boundaries (alternative name: ETRUE_LO)
330  * ENERG_HI - True energy upper bin boundaries (alternative name: ETRUE_HI)
331  * MIGRA_LO - Migration lower bin boundaries
332  * MIGRA_HI - Migration upper bin boundaries
333  * THETA_LO - Offset angle lower bin boundaries
334  * THETA_HI - Offset angle upper bin boundaries
335  * MATRIX - Migration matrix
336  *
337  * The data are stored in the m_edisp member. The energy axis will be set to
338  * \f$log_{10} E_{\rm true}\f$, the offset angle axis to radians.
339  *
340  * The method assures that the energy dispersion is properly normalised.
341  ***************************************************************************/
342 void GCTAEdisp2D::read(const GFitsTable& table)
343 {
344  // Clear response table
345  m_edisp.clear();
346 
347  // Read energy dispersion table
348  m_edisp.read(table);
349 
350  // Throw an exception if the table is not two-dimensional
351  if (m_edisp.axes() != 3) {
352  std::string msg = "Expected three-dimensional energy dispersion "
353  "response table but found "+
355  " dimensions. Please specify a three-dimensional "
356  "energy dispersion.";
357  throw GException::invalid_value(G_READ, msg);
358  }
359 
360  // Set table
361  set_table();
362 
363  // Return
364  return;
365 }
366 
367 
368 /***********************************************************************//**
369  * @brief Write energy dispersion into FITS binary table
370  *
371  * @param[in] table FITS binary table.
372  *
373  * Writes the energy dispersion into the FITS binary @p table.
374  *
375  * @todo Add keywords.
376  ***************************************************************************/
378 {
379  // Make sure that energy dispersion is online
380  fetch();
381 
382  // Write background table
383  m_edisp.write(table);
384 
385  // Return
386  return;
387 }
388 
389 
390 /***********************************************************************//**
391  * @brief Load energy dispersion from FITS file
392  *
393  * @param[in] filename FITS file name.
394  *
395  * Loads the energy dispersion from a FITS file.
396  *
397  * The method does not actually load the FITS file, which will only be loaded
398  * on request. Only the filename is stored. See the fetch() method for the
399  * actually loading of the energy dispersion.
400  ***************************************************************************/
401 void GCTAEdisp2D::load(const GFilename& filename)
402 {
403  // Clear object
404  clear();
405 
406  // Store filename
408 
409  // Return
410  return;
411 }
412 
413 
414 /***********************************************************************//**
415  * @brief Save energy dispersion table into FITS file
416  *
417  * @param[in] filename FITS file name.
418  * @param[in] clobber Overwrite existing file?
419  *
420  * Saves energy dispersion into a FITS file. If a file with the given
421  * @p filename does not yet exist it will be created, otherwise the method
422  * opens the existing file. The method will create a (or replace an existing)
423  * energy dispersion extension. The extension name can be specified as part
424  * of the @p filename, or if no extension name is given, is assumed to be
425  * `ENERGY DISPERSION`.
426  *
427  * An existing file will only be modified if the @p clobber flag is set to
428  * true.
429  ***************************************************************************/
430 void GCTAEdisp2D::save(const GFilename& filename, const bool& clobber) const
431 {
432  // Get extension name
433  std::string extname = filename.extname(gammalib::extname_cta_edisp2d);
434 
435  // Open or create FITS file (without extension name since the requested
436  // extension may not yet exist in the file)
437  GFits fits(filename.url(), true);
438 
439  // Remove extension if it exists already
440  if (fits.contains(extname)) {
441  fits.remove(extname);
442  }
443 
444  // Create binary table
446 
447  // Write the background table
448  write(table);
449 
450  // Set binary table extension name
451  table.extname(extname);
452 
453  // Append table to FITS file
454  fits.append(table);
455 
456  // Save to file
457  fits.save(clobber);
458 
459  // Return
460  return;
461 }
462 
463 
464 /***********************************************************************//**
465  * @brief Simulate energy dispersion
466  *
467  * @param[in] ran Random number generator.
468  * @param[in] etrue True photon energy.
469  * @param[in] theta Offset angle in camera system (radians).
470  * @param[in] phi Azimuth angle in camera system (radians). Not used.
471  * @param[in] zenith Zenith angle in Earth system (radians). Not used.
472  * @param[in] azimuth Azimuth angle in Earth system (radians). Not used.
473  * @return Reconstructed energy.
474  *
475  * @exception GException::invalid_return_value
476  * No energy dispersion information found for parameters or
477  * energy dispersion matrix is empty.
478  *
479  * Draws reconstructed energy value given a true energy @p etrue and offset
480  * angle @p theta. If no energy dispersion information is available the
481  * method will return the true photon energy.
482  ***************************************************************************/
484  const GEnergy& etrue,
485  const double& theta,
486  const double& phi,
487  const double& zenith,
488  const double& azimuth) const
489 {
490  // Make sure that energy dispersion is online
491  fetch();
492 
493  // Initialise reconstructed event energy with true photon energy
494  GEnergy ereco = etrue;
495 
496  // Get boundaries for reconstructed energy
497  GEbounds ebounds = ereco_bounds(etrue, theta, phi, zenith, azimuth);
498  double emin = ebounds.emin().TeV();
499  double emax = ebounds.emax().TeV();
500 
501  // Throw an exception if minimum energy is equal or larger than maximum
502  // energy (this means that no energy dispersion information is available
503  // for that energy)
504  if (emin >= emax) {
505  std::string msg = "No valid energy dispersion information available "
506  "for true photon energy "+etrue.print()+", "
507  "offset angle "+
508  gammalib::str(theta*gammalib::rad2deg)+" deg "
509  "and azimuth angle "+
510  gammalib::str(phi*gammalib::rad2deg)+" deg. Either "
511  "provide an energy dispersion matrix covering these "
512  "parameters or restrict the parameter space.";
514  }
515 
516  // Get maximum energy dispersion value (including some margin)
517  double max_edisp = 1.5 * get_max_edisp(etrue, theta);
518 
519  // Throw an exception if maximum energy dispersion is zero
520  if (max_edisp <= 0.0) {
521  std::string msg = "Energy dispersion matrix is empty. Please provide "
522  "a valid energy dispersion matrix.";
524  }
525 
526  // Initialise rejection method
527  double ewidth = emax - emin;
528  double f = 0.0;
529  double ftest = 1.0;
530  const int max_subsequent_zeros = 10;
531 
532  // Find energy by rejection method
533  while (ftest > f) {
534 
535  // Draw random observed energy and evaluate energy dispersion matrix
536  // until a non-zero energy dispersion value is found. Subsequent zero
537  // energy dispersion values may indicate that there is no valid energy
538  // dispersion information available. After a limited number of
539  // subsequent zeros the loop is terminated with an exception
540  int zeros = 0;
541  do {
542  ereco.TeV(emin + ewidth * ran.uniform());
543  f = operator()(ereco, etrue, theta, phi, zenith, azimuth);
544  if (f == 0.0) {
545  zeros++;
546  if (zeros > max_subsequent_zeros) {
547  std::string msg = "No valid energy dispersion information "
548  "available for true photon energy "+
549  etrue.print()+", offset angle "+
551  " deg and azimuth angle "+
552  gammalib::str(phi*gammalib::rad2deg)+
553  " deg. Either provide an energy "
554  "dispersion matrix covering these "
555  "parameters or restrict the parameter "
556  "space.";
558  }
559  }
560  } while (f == 0);
561 
562  // Get uniform random value between zero and the maximum energy
563  // dispersion value. The loop is quit if the random number is smaller
564  // than the energy dispersion value
565  ftest = ran.uniform() * max_edisp;
566 
567  } // endwhile:
568 
569  // Return energy
570  return ereco;
571 }
572 
573 
574 /***********************************************************************//**
575  * @brief Return observed energy interval that contains the energy dispersion.
576  *
577  * @param[in] etrue True photon energy.
578  * @param[in] theta Offset angle in camera system (radians).
579  * @param[in] phi Azimuth angle in camera system (radians). Not used.
580  * @param[in] zenith Zenith angle in Earth system (radians). Not used.
581  * @param[in] azimuth Azimuth angle in Earth system (radians). Not used.
582  * @return Observed energy boundaries.
583  *
584  * Returns the band of observed energies outside of which the energy
585  * dispersion becomes negligible for a given true energy @p etrue and
586  * offset angle @p theta.
587  ***************************************************************************/
589  const double& theta,
590  const double& phi,
591  const double& zenith,
592  const double& azimuth) const
593 {
594  // Make sure that energy dispersion is online
595  fetch();
596 
597  // Compute only if parameters changed
598  if (!m_ereco_bounds_computed || theta != m_last_theta_ereco) {
599 
600  // Set computation flag
603  m_last_etrue.TeV(0.0); // force update
604 
605  // Compute reconstructed energy bounds
606  compute_ereco_bounds(theta, phi, zenith, azimuth);
607 
608  }
609 
610  // Search index only if true photon energy has changed
611  if (etrue != m_last_etrue) {
612 
613  // Store last true photon energy
615 
616  // Get true energy in TeV
617  double etrue_TeV = etrue.TeV();
618 
619  // Find right index with bisection
620  int low = 0;
621  int high = m_ereco_bounds.size() - 1;
622  while ((high-low) > 1) {
623  int mid = (low+high) / 2;
624  double e = m_edisp.axis_lo(m_inx_etrue, mid);
625  if (etrue_TeV < e) {
626  high = mid;
627  }
628  else {
629  low = mid;
630  }
631  }
632 
633  // Index found
634  m_index_ereco = low;
635 
636  }
637 
638  // Return energy boundaries
640 }
641 
642 
643 /***********************************************************************//**
644  * @brief Return true energy interval that contains the energy dispersion.
645  *
646  * @param[in] ereco Reconstructed event energy.
647  * @param[in] theta Offset angle in camera system (radians).
648  * @param[in] phi Azimuth angle in camera system (radians). Not used.
649  * @param[in] zenith Zenith angle in Earth system (radians). Not used.
650  * @param[in] azimuth Azimuth angle in Earth system (radians). Not used.
651  * @return True energy boundaries.
652  *
653  * Returns the band of true photon energies outside of which the energy
654  * dispersion becomes negligible for a given observed energy @p ereco and
655  * offset angle @p theta.
656  ***************************************************************************/
658  const double& theta,
659  const double& phi,
660  const double& zenith,
661  const double& azimuth) const
662 {
663  // Make sure that energy dispersion is online
664  fetch();
665 
666  // Compute true energy boundaries only if parameters changed
667  if (!m_etrue_bounds_computed || theta != m_last_theta_etrue) {
668 
669  // Set computation flag
672  m_last_ereco.TeV(0.0); // force update
673 
674  // Compute true energy boundaries
675  compute_etrue_bounds(theta, phi, zenith, azimuth);
676 
677  }
678 
679  // Search index only if reconstructed event energy has changed
680  if (ereco != m_last_ereco) {
681 
682  // Store reconstructed event energy
683  m_last_ereco = ereco;
684 
685  // Get reconstructed event energy in TeV
686  double ereco_TeV = ereco.TeV();
687 
688  // Find right index with bisection
689  int low = 0;
690  int high = m_etrue_bounds.size() - 1;
691  while ((high-low) > 1) {
692  int mid = (low+high) / 2;
693  double e = m_edisp.axis_lo(m_inx_etrue, mid);
694  if (ereco_TeV < e) {
695  high = mid;
696  }
697  else {
698  low = mid;
699  }
700  }
701 
702  // Index found
703  m_index_etrue = low;
704 
705  }
706 
707  // Return energy boundaries
709 }
710 
711 
712 /***********************************************************************//**
713  * @brief Fetch energy dispersion
714  *
715  * @exception GException::file_error
716  * File not found.
717  * Unable to load energy dispersion.
718  *
719  * Fetches the energy dispersion by reading it from a FITS file.
720  *
721  * If the filename contains no extension name the method scans the `HDUCLASS`
722  * keywords of all extensions and loads the energy dispersion from the first
723  * extension for which `HDUCLAS4=EDISP_2D`.
724  *
725  * Otherwise, the background will be loaded from the `ENERGY DISPERSION`
726  * extension.
727  *
728  * This method does nothing if the energy dispersion is already loaded, if
729  * there is nothing to fetch, or if the m_filename member is empty.
730  *
731  * The method is thread save. The method checks whether the file from which
732  * the energy dispersion should be loaded actually exists.
733  ***************************************************************************/
734 void GCTAEdisp2D::fetch(void) const
735 {
736  // Continue only if energy dispersion has not yet been fetched
737  if (!m_fetched) {
738 
739  // Continue only if the file name is not empty
740  if (!m_filename.is_empty()) {
741 
742  // Throw an exception if the file does not exist
743  if (!m_filename.exists()) {
744  std::string msg = "File \""+m_filename+"\" not found. Cannot "
745  "fetch energy dispersion. Maybe the file has "
746  "been deleted in the meantime.";
748  }
749 
750  // Signal that energy dispersion will be fetched (has to come
751  // before reading since the ebounds_obs() and ebounds_src() methods
752  // will otherwise call the fetch() method recursively.
753  m_fetched = true;
754 
755  // Initialise exception flag
756  bool has_exception = false;
757 
758  // Load energy dispersion. Catch any exception. Put the code into
759  // a critical zone as it might be called from within a parallelized
760  // thread.
761  #pragma omp critical(GCTAEdisp2D_fetch)
762  {
763  try {
764 
765 
766  // Open FITS file
767  GFits fits(m_filename);
768 
769  // Get the default extension name. If no GADF compliant name
770  // was found then set the default extension name to
771  // "ENERGY DISPERSION".
772  std::string extname = gammalib::gadf_hduclas4(fits, "EDISP_2D");
773  if (extname.empty()) {
775  }
776 
777  // Get energy dispersion table
778  const GFitsTable& table = *fits.table(m_filename.extname(extname));
779 
780  // Read energy dispersion from table
781  const_cast<GCTAEdisp2D*>(this)->read(table);
782 
783  // Close FITS file
784  fits.close();
785 
786  }
787  catch (...) {
788  has_exception = true;
789  }
790  }
791 
792  // Throw an exception if an exception has occured
793  if (has_exception) {
794  std::string msg = "Unable to load energy dispersion from "
795  "file \""+m_filename+"\".";
796  throw GException::file_error(G_FETCH, msg);
797  }
798 
799  } // endif: filename was not empty
800 
801  } // endif: energy dispersion had not yet been fetched
802 
803  // Return
804  return;
805 }
806 
807 
808 /***********************************************************************//**
809  * @brief Return energy dispersion probability for reconstructed energy
810  * interval
811  *
812  * @param[in] ereco_min Minimum of reconstructed energy interval.
813  * @param[in] ereco_max Maximum of reconstructed energy interval.
814  * @param[in] etrue True energy.
815  * @param[in] theta Offset angle (radians).
816  * @return Integrated energy dispersion probability.
817  *
818  * Computes
819  *
820  * \f[
821  * \int_{E_{\rm reco}^{\rm min}}^{E_{\rm reco}^{\rm max}}
822  * E_{\rm disp}(E_{\rm reco} | E_{\rm true}, \theta) \, dE_{\rm reco}
823  * \f]
824  *
825  * where
826  * \f$E_{\rm reco}\f$ is the reconstructed energy,
827  * \f$E_{\rm true}\f$ is the true energy and
828  * \f$\theta\f$ is the offset angle.
829  *
830  * The method takes into account that the energy dispersion data are stored
831  * in a matrix and uses the trapezoidal rule for integration of the tabulated
832  * data. This assures the fastest possible integration.
833  ***************************************************************************/
834 double GCTAEdisp2D::prob_erecobin(const GEnergy& ereco_min,
835  const GEnergy& ereco_max,
836  const GEnergy& etrue,
837  const double& theta) const
838 {
839  // Make sure that energy dispersion is online
840  fetch();
841 
842  // Initalize probability
843  double prob = 0.0;
844 
845  // Get log10 of true energy
846  double logEsrc = etrue.log10TeV();
847 
848  // Get migration limits
849  double migra_min = ereco_min / etrue;
850  double migra_max = ereco_max / etrue;
851 
852  // Continue only if logEsrc and theta are in validity range and migration
853  // interval overlaps with response table
854  if ((logEsrc >= m_logEsrc_min) && (logEsrc <= m_logEsrc_max) &&
855  (theta >= m_theta_min) && (theta <= m_theta_max) &&
856  (migra_max > m_migra_min) && (migra_min < m_migra_max)) {
857 
858  // Constrain migration limits to response table
859  if (migra_min < m_migra_min) {
860  migra_min = m_migra_min;
861  }
862  if (migra_max > m_migra_max) {
863  migra_max = m_migra_max;
864  }
865 
866  // Retrieve references to node arrays
867  const GNodeArray& etrue_nodes = m_edisp.axis_nodes(m_inx_etrue);
868  const GNodeArray& theta_nodes = m_edisp.axis_nodes(m_inx_theta);
869  const GNodeArray& migra_nodes = m_edisp.axis_nodes(m_inx_migra);
870 
871  // Set logEsrc and theta for node array interpolation
872  etrue_nodes.set_value(logEsrc);
873  theta_nodes.set_value(theta);
874 
875  // Compute base indices
876  int base_ll = table_index(etrue_nodes.inx_left(), 0, theta_nodes.inx_left());
877  int base_lr = table_index(etrue_nodes.inx_left(), 0, theta_nodes.inx_right());
878  int base_rl = table_index(etrue_nodes.inx_right(), 0, theta_nodes.inx_left());
879  int base_rr = table_index(etrue_nodes.inx_right(), 0, theta_nodes.inx_right());
880 
881  // Get migration stride
882  int stride = table_stride(m_inx_migra);
883 
884  // Initialise first and second node
885  double x1 = migra_min;
886  double f1 = table_value(base_ll, base_lr, base_rl, base_rr,
887  etrue_nodes.wgt_left(),
888  etrue_nodes.wgt_right(),
889  theta_nodes.wgt_left(),
890  theta_nodes.wgt_right(), x1);
891  double x2 = 0.0;
892  double f2 = 0.0;
893 
894  // Loop over all migration nodes
895  for (int i = 0, offset = 0; i < migra_nodes.size(); ++i, offset += stride) {
896 
897  // If migration value is below migra_min then skip node
898  if (migra_nodes[i] <= migra_min) {
899  continue;
900  }
901 
902  // If migration value is above maximum migration value then use
903  // migra_max as migration value
904  if (migra_nodes[i] > migra_max) {
905  x2 = migra_max;
906  f2 = table_value(base_ll, base_lr, base_rl, base_rr,
907  etrue_nodes.wgt_left(),
908  etrue_nodes.wgt_right(),
909  theta_nodes.wgt_left(),
910  theta_nodes.wgt_right(), x2);
911  }
912 
913  // ... otherwise use migration value
914  else {
915  x2 = migra_nodes[i];
916  f2 = table_value(base_ll, base_lr, base_rl, base_rr,
917  etrue_nodes.wgt_left(),
918  etrue_nodes.wgt_right(),
919  theta_nodes.wgt_left(),
920  theta_nodes.wgt_right(), offset);
921  }
922 
923  // Compute integral
924  prob += 0.5 * (f1 + f2) * (x2 - x1);
925 
926  // Set second node as first node
927  x1 = x2;
928  f1 = f2;
929 
930  // If node energy is above migra_max then break now
931  if (migra_nodes[i] > migra_max) {
932  break;
933  }
934 
935  } // endfor: looped over all nodes
936 
937  // If last node energy is below migra_max then compute last part of
938  // integral up to emax
939  if (x1 < migra_max) {
940  x2 = migra_max;
941  f2 = table_value(base_ll, base_lr, base_rl, base_rr,
942  etrue_nodes.wgt_left(),
943  etrue_nodes.wgt_right(),
944  theta_nodes.wgt_left(),
945  theta_nodes.wgt_right(), x2);
946  prob += 0.5 * (f1 + f2) * (x2 - x1);
947  }
948 
949  } // endif: logEsrc, theta and migration range were valid
950 
951  // Return probability
952  return prob;
953 }
954 
955 
956 /***********************************************************************//**
957  * @brief Print energy dispersion information
958  *
959  * @param[in] chatter Chattiness.
960  * @return String containing energy dispersion information.
961  ***************************************************************************/
962 std::string GCTAEdisp2D::print(const GChatter& chatter) const
963 {
964  // Initialise result string
965  std::string result;
966 
967  // Continue only if chatter is not silent
968  if (chatter != SILENT) {
969 
970  // Append header
971  result.append("=== GCTAEdisp2D ===");
972  result.append("\n"+gammalib::parformat("Filename")+m_filename);
973 
974  // Make sure that energy dispersion is online
975  fetch();
976 
977  // Initialise information
978  int nebins = 0;
979  int nmigrabins = 0;
980  int nthetabins = 0;
981  double emin = 0.0;
982  double emax = 0.0;
983  double mmin = 0.0;
984  double mmax = 0.0;
985  double omin = 0.0;
986  double omax = 0.0;
987 
988  // Extract information if there are axes in the response table
989  if (m_edisp.axes() > 0) {
990  nebins = m_edisp.axis_bins(m_inx_etrue);
991  nmigrabins = m_edisp.axis_bins(m_inx_migra);
992  nthetabins = m_edisp.axis_bins(m_inx_theta);
993  emin = m_edisp.axis_lo(m_inx_etrue,0);
994  emax = m_edisp.axis_hi(m_inx_etrue,nebins-1);
995  mmin = m_edisp.axis_lo(m_inx_migra,0);
996  mmax = m_edisp.axis_hi(m_inx_migra,nmigrabins-1);
997  omin = m_edisp.axis_lo(m_inx_theta,0);
998  omax = m_edisp.axis_hi(m_inx_theta,nthetabins-1);
999  }
1000 
1001  // Append information
1002  result.append("\n"+gammalib::parformat("Number of energy bins") +
1003  gammalib::str(nebins));
1004  result.append("\n"+gammalib::parformat("Number of migration bins") +
1005  gammalib::str(nmigrabins));
1006  result.append("\n"+gammalib::parformat("Number of offset bins") +
1007  gammalib::str(nthetabins));
1008  result.append("\n"+gammalib::parformat("Energy range"));
1009  result.append(gammalib::str(emin)+" - "+gammalib::str(emax)+" TeV");
1010  result.append("\n"+gammalib::parformat("Migration range"));
1011  result.append(gammalib::str(mmin)+" - "+gammalib::str(mmax));
1012  result.append("\n"+gammalib::parformat("Offset angle range"));
1013  result.append(gammalib::str(omin)+" - "+gammalib::str(omax)+" deg");
1014 
1015  } // endif: chatter was not silent
1016 
1017  // Return result
1018  return result;
1019 }
1020 
1021 
1022 /*==========================================================================
1023  = =
1024  = Private methods =
1025  = =
1026  ==========================================================================*/
1027 
1028 /***********************************************************************//**
1029  * @brief Initialise class members
1030  ***************************************************************************/
1032 {
1033  // Initialise members
1034  m_filename.clear();
1035  m_edisp.clear();
1036  m_fetched = false;
1037  m_inx_etrue = 0;
1038  m_inx_migra = 1;
1039  m_inx_theta = 2;
1040  m_inx_matrix = 0;
1041  m_logEsrc_min = 0.0;
1042  m_logEsrc_max = 0.0;
1043  m_migra_min = 0.0;
1044  m_migra_max = 0.0;
1045  m_theta_min = 0.0;
1046  m_theta_max = 0.0;
1047  m_max_edisp.clear();
1048 
1049  // Initialise computation cache
1050  m_ereco_bounds_computed = false;
1051  m_etrue_bounds_computed = false;
1052  m_index_ereco = 0;
1053  m_index_etrue = 0;
1054  m_last_theta_ereco = -1.0;
1055  m_last_theta_etrue = -1.0;
1056  m_last_etrue.TeV(0.0);
1057  m_last_ereco.TeV(0.0);
1058  m_ereco_bounds.clear();
1059  m_etrue_bounds.clear();
1060 
1061  // Return
1062  return;
1063 }
1064 
1065 
1066 /***********************************************************************//**
1067  * @brief Copy class members
1068  *
1069  * @param[in] edisp Energy dispersion.
1070  ***************************************************************************/
1072 {
1073  // Copy members
1074  m_filename = edisp.m_filename;
1075  m_edisp = edisp.m_edisp;
1076  m_fetched = edisp.m_fetched;
1077  m_inx_etrue = edisp.m_inx_etrue;
1078  m_inx_migra = edisp.m_inx_migra;
1079  m_inx_theta = edisp.m_inx_theta;
1080  m_inx_matrix = edisp.m_inx_matrix;
1081  m_logEsrc_min = edisp.m_logEsrc_min;
1082  m_logEsrc_max = edisp.m_logEsrc_max;
1083  m_migra_min = edisp.m_migra_min;
1084  m_migra_max = edisp.m_migra_max;
1085  m_theta_min = edisp.m_theta_min;
1086  m_theta_max = edisp.m_theta_max;
1087  m_max_edisp = edisp.m_max_edisp;
1088 
1089  // Copy computation cache
1092  m_index_ereco = edisp.m_index_ereco;
1093  m_index_etrue = edisp.m_index_etrue;
1096  m_last_etrue = edisp.m_last_etrue;
1097  m_last_ereco = edisp.m_last_ereco;
1100 
1101  // Return
1102  return;
1103 }
1104 
1105 
1106 /***********************************************************************//**
1107  * @brief Delete class members
1108  ***************************************************************************/
1110 {
1111  // Return
1112  return;
1113 }
1114 
1115 
1116 /***********************************************************************//**
1117  * @brief Get true energy
1118  *
1119  * @param[in] ietrue True energy index.
1120  * @return True energy.
1121  ***************************************************************************/
1122 GEnergy GCTAEdisp2D::etrue(const int& ietrue) const
1123 {
1124  // Compute true energy in TeV
1125  double etrue_TeV = std::sqrt(m_edisp.axis_lo(m_inx_etrue, ietrue) *
1126  m_edisp.axis_hi(m_inx_etrue, ietrue));
1127 
1128  // Set true energy
1129  GEnergy etrue;
1130  etrue.TeV(etrue_TeV);
1131 
1132  // Return true energy
1133  return etrue;
1134 }
1135 
1136 
1137 /***********************************************************************//**
1138  * @brief Get migration
1139  *
1140  * @param[in] imigra Migration index.
1141  * @return Migration.
1142  ***************************************************************************/
1143 double GCTAEdisp2D::migra(const int& imigra) const
1144 {
1145  // Compute migration
1146  double migra = 0.5 * (m_edisp.axis_hi(m_inx_migra, imigra) +
1147  m_edisp.axis_lo(m_inx_migra, imigra));
1148 
1149  // Return migration
1150  return migra;
1151 }
1152 
1153 
1154 /***********************************************************************//**
1155  * @brief Get offset angle in radiaus
1156  *
1157  * @param[in] itheta Offset angle index.
1158  * @return Offset angle (radians).
1159  ***************************************************************************/
1160 double GCTAEdisp2D::theta(const int& itheta) const
1161 {
1162  // Compute offset angle in radians
1163  double theta = 0.5 * (m_edisp.axis_lo(m_inx_theta, itheta) +
1164  m_edisp.axis_hi(m_inx_theta, itheta)) *
1166 
1167  // Return offset angle
1168  return theta;
1169 }
1170 
1171 
1172 /***********************************************************************//**
1173  * @brief Compute vector of reconstructed energy bounds
1174  *
1175  * @param[in] theta Offset angle (radians).
1176  * @param[in] phi Azimuth angle (radians).
1177  * @param[in] zenith Zenith angle (radians).
1178  * @param[in] azimuth Azimuth angle (radians).
1179  *
1180  * Computes for all true energies the energy boundaries of the reconstructed
1181  * energies covered by valid migration matrix elements. Only matrix elements
1182  * with values >= 1.0e-12 are considered as valid elements. In case that no
1183  * matrix elements are found of a given true energy, the interval of observed
1184  * energies will be set to [0,0] (i.e. an empty interval).
1185  ***************************************************************************/
1186 void GCTAEdisp2D::compute_ereco_bounds(const double& theta,
1187  const double& phi,
1188  const double& zenith,
1189  const double& azimuth) const
1190 {
1191  // Clear ebounds_obs vector
1192  m_ereco_bounds.clear();
1193 
1194  // Set epsilon
1195  const double eps = 1.0e-12;
1196 
1197  // Determine number of true energy and migration bins
1198  int netrue = m_edisp.axis_bins(m_inx_etrue);
1199  int nmigra = m_edisp.axis_bins(m_inx_migra);
1200 
1201  // Loop over true photon energy
1202  for (int ietrue = 0; ietrue < netrue; ++ietrue) {
1203 
1204  // Compute true photon energy
1205  GEnergy etrue = this->etrue(ietrue);
1206 
1207  // Initialise results
1208  double ereco_min = 0.0;
1209  double ereco_max = 0.0;
1210  bool found_min = false;
1211  bool found_max = false;
1212 
1213  // Find minimum boundary
1214  for (int imigra = 0; imigra < nmigra; ++imigra) {
1215 
1216  // Compute migra value
1217  double migra = this->migra(imigra);
1218 
1219  // Get matrix term
1220  double arg[3];
1221  arg[m_inx_etrue] = etrue.log10TeV();
1222  arg[m_inx_migra] = migra;
1223  arg[m_inx_theta] = theta;
1224  double edisp = m_edisp(m_inx_matrix, arg[0], arg[1], arg[2]);
1225 
1226  // Find first non-negligible matrix term
1227  if (edisp >= eps) {
1228  found_min = true;
1229  ereco_min = migra * etrue.TeV();
1230  break;
1231  }
1232 
1233  } // endfor: find minimum boundary
1234 
1235  // Find maximum boundary
1236  for (int imigra = nmigra-1; imigra >= 0; imigra--) {
1237 
1238  // Compute migra value
1239  double migra = this->migra(imigra);
1240 
1241  // Get matrix term
1242  double arg[3];
1243  arg[m_inx_etrue] = etrue.log10TeV();
1244  arg[m_inx_migra] = migra;
1245  arg[m_inx_theta] = theta;
1246  double edisp = m_edisp(m_inx_matrix, arg[0], arg[1], arg[2]);
1247 
1248  // Find first non-negligible matrix term
1249  if (edisp >= eps) {
1250  found_max = true;
1251  ereco_max = migra * etrue.TeV();
1252  break;
1253  }
1254 
1255  } // endfor: find maximum boundary
1256 
1257  // If we did not find boundaries then set the interval to
1258  // a zero interval for safety
1259  if (!found_min || !found_max) {
1260  ereco_min = 0.0;
1261  ereco_max = 0.0;
1262  }
1263 
1264  // Set energy boundaries
1265  GEnergy emin;
1266  GEnergy emax;
1267  emin.TeV(ereco_min);
1268  emax.TeV(ereco_max);
1269 
1270  // Add energy boundaries to vector
1271  m_ereco_bounds.push_back(GEbounds(emin, emax));
1272 
1273  } // endfor: looped over logEsrc
1274 
1275  // Return
1276  return;
1277 }
1278 
1279 
1280 /***********************************************************************//**
1281  * @brief Compute vector of true energy boundaries
1282  *
1283  * @param[in] theta Offset angle (radians).
1284  * @param[in] phi Azimuth angle (radians).
1285  * @param[in] zenith Zenith angle (radians).
1286  * @param[in] azimuth Azimuth angle (radians).
1287  *
1288  * Computes for all observed energies the energy boundaries of the true
1289  * energies covered by valid migration matrix elements. Only matrix elements
1290  * with values >= 1.0e-12 are considered as valid elements. In case that no
1291  * matrix elements are found of a given observed energy, the interval of true
1292  * energies will be set to [0,0] (i.e. an empty interval).
1293  ***************************************************************************/
1294 void GCTAEdisp2D::compute_etrue_bounds(const double& theta,
1295  const double& phi,
1296  const double& zenith,
1297  const double& azimuth) const
1298 {
1299  // Initialise energies
1300  GEnergy etrue;
1301  GEnergy ereco;
1302 
1303  // Clear ebounds_src vector
1304  m_etrue_bounds.clear();
1305 
1306  // Set epsilon
1307  const double eps = 1.0e-12;
1308 
1309  // Determine number of true energy bins
1310  int nereco = m_edisp.axis_bins(m_inx_etrue);
1311  int nmigras = m_edisp.axis_bins(m_inx_migra);
1312 
1313  // Loop over reconstructed energy
1314  for (int iereco = 0; iereco < nereco; ++iereco) {
1315 
1316  // Set ereco (we use the true energy axis for the computation)
1317  GEnergy ereco = this->etrue(iereco);
1318 
1319  // Initialise results
1320  GEnergy etrue_min;
1321  GEnergy etrue_max;
1322  bool found_min = false;
1323  bool found_max = false;
1324 
1325  // Find minimum boundary
1326  for (int imigra = 0; imigra < nmigras; ++imigra) {
1327 
1328  // Compute migra value
1329  double migra = this->migra(imigra);
1330 
1331  // Fall through if migration is zero
1332  if (migra == 0.0) {
1333  continue;
1334  }
1335 
1336  // Compute true energy
1337  etrue = ereco / migra;
1338 
1339  // Get energy dispersion
1340  double edisp = operator()(ereco, etrue, theta);
1341 
1342  // Find first non-negligible matrix term
1343  if (edisp >= eps) {
1344  found_max = true;
1345  etrue_max = etrue;
1346  break;
1347  }
1348 
1349  } // endfor: find minimum boundary
1350 
1351  // Find maximum boundary
1352  for (int imigra = nmigras-1; imigra >= 0; imigra--) {
1353 
1354  // Compute migra value
1355  double migra = this->migra(imigra);
1356 
1357  // Fall through if migration is zero
1358  if (migra == 0.0) {
1359  continue;
1360  }
1361 
1362  // Compute true energy
1363  etrue = ereco / migra;
1364 
1365  // Get energy dispersion
1366  double edisp = operator()(ereco, etrue, theta);
1367 
1368  // Find first non-negligible matrix term
1369  if (edisp >= eps) {
1370  found_min = true;
1371  etrue_min = etrue;
1372  break;
1373  }
1374 
1375  } // endfor: find maximum boundary
1376 
1377  // If we did not find boundaries then set the interval to a zero
1378  // interval for safety
1379  if (!found_min || !found_max) {
1380  etrue_min.clear();
1381  etrue_max.clear();
1382  }
1383 
1384  // Add energy boundaries to vector
1385  m_etrue_bounds.push_back(GEbounds(etrue_min, etrue_max));
1386 
1387  } // endfor : loopend over observed energy
1388 
1389  // Return
1390  return;
1391 }
1392 
1393 
1394 /***********************************************************************//**
1395  * @brief Set table
1396  *
1397  * After assigning or loading a response table, performs all necessary steps
1398  * to set the table.
1399  *
1400  * For CTA, the method applies a kludge that smoothes the energy dispersion
1401  * table to get rid of numerical fluctuations.
1402  *
1403  * Sets the data members
1404  * m_inx_etrue
1405  * m_inx_migra
1406  * m_inx_theta
1407  * m_inx_matrix
1408  * m_logEsrc_min
1409  * m_logEsrc_max
1410  * m_migra_min
1411  * m_migra_max
1412  * m_theta_min
1413  * m_theta_max
1414  * m_max_edisp
1415  ***************************************************************************/
1417 {
1418  // Set table indices
1419  if (m_edisp.has_axis("ENERG")) {
1420  m_inx_etrue = m_edisp.axis("ENERG");
1421  }
1422  else {
1423  m_inx_etrue = m_edisp.axis("ETRUE"); // Old name, should not be used
1424  }
1425  m_inx_migra = m_edisp.axis("MIGRA");
1426  m_inx_theta = m_edisp.axis("THETA");
1427  m_inx_matrix = m_edisp.table("MATRIX");
1428 
1429  // Set true energy axis to logarithmic scale
1431 
1432  // Set offset angle axis to radians
1434 
1435  // Set table boundaries
1436  set_boundaries();
1437 
1438  // Smooth energy dispersion table (kludge only for CTA)
1439  #if defined(G_SMOOTH_EDISP_KLUDGE)
1440  if (gammalib::strip_whitespace(m_edisp.telescope()) == "CTA") {
1441  smooth_table();
1442  }
1443  #endif
1444 
1445  // Normalize energy dispersion table
1446  normalize_table();
1447 
1448  // Set maximum energy dispersion value array
1449  set_max_edisp();
1450 
1451  // Save test
1452  #if defined(G_SMOOTH_EDISP_SAVE_TEST)
1453  this->save("test_edisp.fits", true);
1454  #endif
1455 
1456  // Return
1457  return;
1458 }
1459 
1460 
1461 /***********************************************************************//**
1462  * @brief Set energy dispersion boundaries
1463  *
1464  * Sets the data members m_logEsrc_min, m_logEsrc_max, m_migra_min,
1465  * m_migra_max, m_theta_min and m_theta_max that define the validity range
1466  * of the energy dispersion.
1467  ***************************************************************************/
1469 {
1470  // Get number of bins
1471  int netrue = m_edisp.axis_bins(m_inx_etrue);
1472  int nmigra = m_edisp.axis_bins(m_inx_migra);
1473  int ntheta = m_edisp.axis_bins(m_inx_theta);
1474 
1475  // Compute minimum and maximum logEsrc boundaries
1478 
1479  // Compute minimum and maximum migration boundaries
1481  m_migra_max = m_edisp.axis_hi(m_inx_migra, nmigra-1);
1482 
1483  // Compute minimum and maximum theta boundaries
1486 
1487  // Return
1488  return;
1489 }
1490 
1491 
1492 /***********************************************************************//**
1493  * @brief Set array of maximum energy dispersion values
1494  *
1495  * Sets an internal array of maximum energy dispersion values for each given
1496  * true photon energy and offset angle in the response table. This array will
1497  * be used for Monte Carlo simulations.
1498  ***************************************************************************/
1500 {
1501  // Get number of bins
1502  int netrue = m_edisp.axis_bins(m_inx_etrue);
1503  int nmigra = m_edisp.axis_bins(m_inx_migra);
1504  int ntheta = m_edisp.axis_bins(m_inx_theta);
1505 
1506  // Initialise maximum energy dispersion
1507  m_max_edisp.assign(netrue*ntheta, 0.0);
1508 
1509  // Loop over offset angle
1510  for (int itheta = 0, index = 0; itheta < ntheta; ++itheta) {
1511 
1512  // Loop over true photon energy
1513  for (int ietrue = 0; ietrue < netrue; ++ietrue, ++index) {
1514 
1515  // Get true photon energy
1516  GEnergy etrue = this->etrue(ietrue);
1517 
1518  // Get offset and stride
1519  int offset = table_index(ietrue, 0, itheta);
1520  int stride = table_stride(m_inx_migra);
1521 
1522  // Find maximum
1523  double maximum = 0.0;
1524  for (int imigra = 0, i = offset; imigra < nmigra; ++imigra, i += stride) {
1525  double value = m_edisp(m_inx_matrix, i);
1526  if (value > maximum) {
1527  maximum = value;
1528  }
1529  }
1530 
1531  // Set maximum
1532  m_max_edisp[index] = maximum / etrue.MeV();
1533 
1534  } // endfor: looped over true photon energy
1535 
1536  } // endfor: looped over offset angle
1537 
1538  // Return
1539  return;
1540 }
1541 
1542 
1543 /***********************************************************************//**
1544  * @brief Get maximum energy dispersion value
1545  *
1546  * @param[in] etrue True photon energy.
1547  * @param[in] theta Offset angle (radians).
1548  * @return Maximum energy dispersion value.
1549  *
1550  * For a given true energy @p etrue and offset angle @p theta, returns the
1551  * maximum energy dispersion value that may occur. This method makes use
1552  * of the internal array pre-computed by set_max_edisp().
1553  *
1554  * If set_max_edisp() was not called, the method returns 0.
1555  ***************************************************************************/
1557  const double& theta) const
1558 {
1559  // Initialise maximum energy dispersion
1560  double max_edisp = 0.0;
1561 
1562  // Continue only if maximum energy dispersion array is set
1563  if (m_max_edisp.size() > 0) {
1564 
1565  // Get number of true energy bins
1566  int netrue = m_edisp.axis_bins(m_inx_etrue);
1567 
1568  // Get references to axis nodes
1569  const GNodeArray& etrue_nodes = m_edisp.axis_nodes(m_inx_etrue);
1570  const GNodeArray& theta_nodes = m_edisp.axis_nodes(m_inx_theta);
1571 
1572  // Set values
1573  etrue_nodes.set_value(etrue.log10TeV());
1574  theta_nodes.set_value(theta);
1575 
1576  // Get indices that bound the true energy and offset angle
1577  int inx_left = theta_nodes.inx_left() * netrue;
1578  int inx_right = theta_nodes.inx_right() * netrue;
1579  int index_1 = inx_left + etrue_nodes.inx_left();
1580  int index_2 = inx_left + etrue_nodes.inx_right();
1581  int index_3 = inx_right + etrue_nodes.inx_left();
1582  int index_4 = inx_right + etrue_nodes.inx_right();
1583 
1584  // Get maximum energy dispersion
1585  max_edisp = m_max_edisp[index_1];
1586  double max_edisp_2 = m_max_edisp[index_2];
1587  double max_edisp_3 = m_max_edisp[index_3];
1588  double max_edisp_4 = m_max_edisp[index_4];
1589  if (max_edisp_2 > max_edisp) {
1590  max_edisp = max_edisp_2;
1591  }
1592  if (max_edisp_3 > max_edisp) {
1593  max_edisp = max_edisp_3;
1594  }
1595  if (max_edisp_4 > max_edisp) {
1596  max_edisp = max_edisp_4;
1597  }
1598 
1599  } // endif: Maximum energy dispersion array was set
1600 
1601  // Return maximum energy dispersion
1602  return max_edisp;
1603 }
1604 
1605 
1606 /***********************************************************************//**
1607  * @brief Normalize energy dispersion table
1608  *
1609  * Normalize the energy dispersion table using
1610  *
1611  * \f[
1612  * \int_{E_{\rm reco}^{\rm min}}^{E_{\rm reco}^{\rm max}}
1613  * E_{\rm disp}(E_{\rm reco} | E_{\rm true}, \theta) \, dE_{\rm reco} = 1
1614  * \f]
1615  *
1616  * where
1617  * \f$E_{\rm reco}_{\rm min}\f$ and \f$E_{\rm reco}_{\rm max}\f$ is the
1618  * minimum and maximum migration value for a given true energy as returned
1619  * by the ebounds_obs() method, and
1620  * \f$E_{\rm disp}(E_{\rm true}, E_{\rm reco}, \theta)\f$ is the energy
1621  * dispersion returned by the operator(), given in units of \f$MeV^{-1}\f$.
1622  *
1623  * The normalisation is performed for each \f$E_{\rm true}\f$ and
1624  * \f$\theta\f$ bin using a Romberg integration method. Since the
1625  * normalisation may affect the energy boundaries
1626  * \f$E_{\rm reco}^{\rm min}\f$ and \f$E_{\rm reco}^{\rm max}\f$, two
1627  * normalisation passes are performed to stabilize the result.
1628  ***************************************************************************/
1630 {
1631  // Get axes dimensions
1632  int etrue_size = m_edisp.axis_bins(m_inx_etrue);
1633  int migra_size = m_edisp.axis_bins(m_inx_migra);
1634  int theta_size = m_edisp.axis_bins(m_inx_theta);
1635 
1636  // Now normalize the migration matrix vectors. We do this by integrating
1637  // for each true energy and offset angle the measured energy. We perform
1638  // here two passes as the ebounds_obs() method depends on the content of
1639  // the matrix, and by having two passes we are sure that we get a
1640  // consistent energy interval.
1641  for (int pass = 0; pass < 2; ++pass) {
1642 
1643  // Initialise sums
1644  std::vector<double> sums;
1645  sums.reserve(theta_size*etrue_size);
1646 
1647  // Loop over offset angle.
1648  for (int i_theta = 0; i_theta < theta_size; ++i_theta) {
1649 
1650  // Get offset angle (in radians)
1651  double theta = this->theta(i_theta);
1652 
1653  // Loop over true photon energy
1654  for (int i_etrue = 0; i_etrue < etrue_size; ++i_etrue) {
1655 
1656  // Get true photon energy
1657  GEnergy etrue = this->etrue(i_etrue);
1658 
1659  // Initialise integration
1660  double sum = 0.0;
1661 
1662  // Get integration boundaries
1663  GEbounds ebounds = ereco_bounds(etrue, theta);
1664 
1665  // Loop over all energy intervals
1666  for (int i = 0; i < ebounds.size(); ++i) {
1667 
1668  // Get energy boundaries in log10 of energy in MeV
1669  double emin = ebounds.emin(i).log10MeV();
1670  double emax = ebounds.emax(i).log10MeV();
1671 
1672  // Setup integration function
1673  edisp_ereco_kern integrand(this, etrue, theta);
1674  GIntegral integral(&integrand);
1675 
1676  // Set integration precision
1677  integral.eps(1.0e-6);
1678 
1679  // Do Romberg integration
1680  sum += integral.romberg(emin, emax);
1681 
1682  } // endfor: looped over energy intervals
1683 
1684  // Store sum
1685  sums.push_back(sum);
1686 
1687  } // endfor: looped over Etrue
1688 
1689  } // endfor: looped over theta
1690 
1691  // Normalize vectors of migration matrix for all etrue and theta.
1692  for (int i_theta = 0, inx = 0; i_theta < theta_size; ++i_theta) {
1693  for (int i_etrue = 0; i_etrue < etrue_size; ++i_etrue, ++inx) {
1694  double sum = sums[inx];
1695  if (sum > 0.0) {
1696  int offset = table_index(i_etrue, 0, i_theta);
1697  int stride = table_stride(m_inx_migra);
1698  for (int k = 0, i = offset; k < migra_size; ++k, i += stride) {
1699  m_edisp(m_inx_matrix,i) /= sum;
1700  }
1701  }
1702  }
1703  }
1704 
1705  } // endfor: made 2 passes
1706 
1707  // Return
1708  return;
1709 }
1710 
1711 
1712 /***********************************************************************//**
1713  * @brief Return index of response table element
1714  *
1715  * @param[in] ietrue True energy index.
1716  * @param[in] imigra Migration index.
1717  * @param[in] itheta Offset index.
1718  * @return Index of response table element.
1719  ***************************************************************************/
1720 int GCTAEdisp2D::table_index(const int& ietrue,
1721  const int& imigra,
1722  const int& itheta) const
1723 {
1724  // Set index vector
1725  int inx[3];
1726  inx[m_inx_etrue] = ietrue;
1727  inx[m_inx_migra] = imigra;
1728  inx[m_inx_theta] = itheta;
1729 
1730  // Compute index
1731  int index = inx[0] + (inx[1] + inx[2] * m_edisp.axis_bins(1)) *
1732  m_edisp.axis_bins(0);
1733 
1734  // Return index
1735  return index;
1736 }
1737 
1738 
1739 /***********************************************************************//**
1740  * @brief Return stride of response table axis
1741  *
1742  * @param[in] axis Response table axis.
1743  * @return Stride of response table axis.
1744  ***************************************************************************/
1745 int GCTAEdisp2D::table_stride(const int& axis) const
1746 {
1747  // Initialise stride
1748  int stride = 1;
1749 
1750  // Multiply in higher
1751  if (axis > 0) {
1752  stride *= m_edisp.axis_bins(0);
1753  }
1754  if (axis > 1) {
1755  stride *= m_edisp.axis_bins(1);
1756  }
1757 
1758  // Return stride
1759  return stride;
1760 }
1761 
1762 
1763 /***********************************************************************//**
1764  * @brief Return bi-linearly interpolate table value for given migration bin
1765  *
1766  * @param[in] base_ll Base index for left true energy and left offset angle.
1767  * @param[in] base_lr Base index for left true energy and right offset angle.
1768  * @param[in] base_rl Base index for right true energy and left offset angle.
1769  * @param[in] base_rr Base index for right true energy and right offset angle.
1770  * @param[in] wgt_el Weighting for left true energy.
1771  * @param[in] wgt_er Weighting for right true energy.
1772  * @param[in] wgt_tl Weighting for left offset angle.
1773  * @param[in] wgt_tr Weighting for right offset angle.
1774  * @param[in] offset Offset of migration bin with respect to base indices.
1775  * @return Bi-linearly interpolate table value.
1776  ***************************************************************************/
1777 double GCTAEdisp2D::table_value(const int& base_ll,
1778  const int& base_lr,
1779  const int& base_rl,
1780  const int& base_rr,
1781  const double& wgt_el,
1782  const double& wgt_er,
1783  const double& wgt_tl,
1784  const double& wgt_tr,
1785  const int& offset) const
1786 {
1787  // Compute table element indices
1788  int inx_ll = base_ll + offset;
1789  int inx_lr = base_lr + offset;
1790  int inx_rl = base_rl + offset;
1791  int inx_rr = base_rr + offset;
1792 
1793  // Get
1794  double value = wgt_el * (m_edisp(m_inx_matrix, inx_ll) * wgt_tl +
1795  m_edisp(m_inx_matrix, inx_lr) * wgt_tr) +
1796  wgt_er * (m_edisp(m_inx_matrix, inx_rl) * wgt_tl +
1797  m_edisp(m_inx_matrix, inx_rr) * wgt_tr);
1798 
1799  // Return value
1800  return value;
1801 }
1802 
1803 
1804 /***********************************************************************//**
1805  * @brief Return bi-linearly interpolate table value for given migration value
1806  *
1807  * @param[in] base_ll Base index for left true energy and left offset angle.
1808  * @param[in] base_lr Base index for left true energy and right offset angle.
1809  * @param[in] base_rl Base index for right true energy and left offset angle.
1810  * @param[in] base_rr Base index for right true energy and right offset angle.
1811  * @param[in] wgt_el Weighting for left true energy.
1812  * @param[in] wgt_er Weighting for right true energy.
1813  * @param[in] wgt_tl Weighting for left offset angle.
1814  * @param[in] wgt_tr Weighting for right offset angle.
1815  * @param[in] migra Migration value.
1816  * @return Bi-linearly interpolate table value.
1817  ***************************************************************************/
1818 double GCTAEdisp2D::table_value(const int& base_ll,
1819  const int& base_lr,
1820  const int& base_rl,
1821  const int& base_rr,
1822  const double& wgt_el,
1823  const double& wgt_er,
1824  const double& wgt_tl,
1825  const double& wgt_tr,
1826  const double& migra) const
1827 {
1828  // Retrieve references to migration node array
1829  const GNodeArray& migra_nodes = m_edisp.axis_nodes(m_inx_migra);
1830 
1831  // Set migration value for node array interpolation
1832  migra_nodes.set_value(migra);
1833 
1834  // Get migration stride
1835  int stride = table_stride(m_inx_migra);
1836 
1837  // Get left value
1838  double value_left = table_value(base_ll, base_lr, base_rl, base_rr,
1839  wgt_el, wgt_er, wgt_tl, wgt_tr,
1840  migra_nodes.inx_left() * stride);
1841 
1842  // Get right value
1843  double value_right = table_value(base_ll, base_lr, base_rl, base_rr,
1844  wgt_el, wgt_er, wgt_tl, wgt_tr,
1845  migra_nodes.inx_right() * stride);
1846 
1847  // Interpolate result
1848  double value = migra_nodes.wgt_left() * value_left +
1849  migra_nodes.wgt_right() * value_right;
1850 
1851  // Make sure that interpolation result is not negative
1852  if (value < 0.0) {
1853  value = 0.0;
1854  }
1855 
1856  // Return
1857  return value;
1858 }
1859 
1860 
1861 /***********************************************************************//**
1862  * @brief Smoothed energy dispersion table
1863  *
1864  * This method implements the kludge for CTA that reduces the statistical
1865  * noise in the energy dispersion matrix.
1866  ***************************************************************************/
1868 {
1869  // Get axes dimensions
1870  int netrue = m_edisp.axis_bins(m_inx_etrue);
1871  int nmigra = m_edisp.axis_bins(m_inx_migra);
1872  int ntheta = m_edisp.axis_bins(m_inx_theta);
1873  int npix = netrue * nmigra;
1874 
1875  // Loop over all offset angles
1876  for (int itheta = 0; itheta < ntheta; ++itheta) {
1877 
1878  // Compute means, rms and total as function of etrue
1879  GNdarray mean(netrue);
1880  GNdarray rms(netrue);
1881  GNdarray total(netrue);
1882  get_moments(itheta, &mean, &rms, &total);
1883 
1884  // Smooth all three
1885  mean = smooth_array(mean, 30, 30, 0.5);
1886  rms = smooth_array(rms, 30, 30, 0.03);
1887  total = smooth_array(total, 30, 30, 0.0);
1888 
1889  // Make sure that total is not negative
1890  for (int ietrue = 0; ietrue < netrue; ++ietrue) {
1891  if (total(ietrue) < 0.0) {
1892  total(ietrue) = 0.0;
1893  }
1894  }
1895 
1896  // Replace matrix by Gaussians
1897  for (int ietrue = 0; ietrue < netrue; ++ietrue) {
1898 
1899  // Continue only if there is information
1900  if (total(ietrue) > 0.0) {
1901 
1902  // Get Gaussian
1903  GNdarray gauss = gaussian_array(mean(ietrue), rms(ietrue), total(ietrue));
1904 
1905  // Compute base index
1906  int ibase = itheta * npix + ietrue;
1907  if ((mean(ietrue) > 0.0) && (rms(ietrue))) {
1908  for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {
1909  m_edisp(m_inx_matrix, i) = gauss(imigra);
1910  }
1911  }
1912  else {
1913  for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {
1914  m_edisp(m_inx_matrix, i) = 0.0;
1915  }
1916  }
1917 
1918  } // endif: there was information
1919 
1920  // ... otherwise reset the matrix to zero
1921  else {
1922  int ibase = itheta * npix + ietrue;
1923  for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {
1924  m_edisp(m_inx_matrix, i) = 0.0;
1925  }
1926  }
1927 
1928  } // endfor: looped over true energies
1929 
1930  } // endfor: looped over offset angles
1931 
1932  // Return
1933  return;
1934 }
1935 
1936 
1937 /***********************************************************************//**
1938  * @brief Smoothed array
1939  *
1940  * @param[in] array Array.
1941  * @param[in] nstart Number of nodes used for regression at first array value.
1942  * @param[in] nstop Number of nodes used for regression at last array value.
1943  * @param[in] limit Limit for array element exclusion.
1944  * @return Smoothed array.
1945  *
1946  * Returns a smoothed array that is computed by locally adjusting a linear
1947  * regression law to the array elements.
1948  ***************************************************************************/
1950  const int& nstart,
1951  const int& nstop,
1952  const double& limit) const
1953 {
1954  // Initialise smoothed array
1955  GNdarray smoothed_array(array.size());
1956 
1957  // Compute node step size
1958  double nstep = double(nstop - nstart)/double(array.size());
1959 
1960  // Loop over all array elements and computed the smoothed value
1961  for (int i = 0; i < array.size(); ++i) {
1962  int nodes = nstart + int(i*nstep);
1963  smoothed_array(i) = smoothed_array_value(i, array, nodes, limit);
1964  }
1965 
1966  // Return smoothed array
1967  return smoothed_array;
1968 }
1969 
1970 
1971 /***********************************************************************//**
1972  * @brief Get smoothed array value
1973  *
1974  * @param[in] inx Index of array element.
1975  * @param[in] array Array.
1976  * @param[in] nodes Number of nodes used for regression.
1977  * @param[in] limit Limit for array element exclusion.
1978  * @return Smoothed array value.
1979  *
1980  * Returns a smoothed array value that is derived from adjusting a linear
1981  * law using regression to @p nodes adjacent array elements. Array elements
1982  * with values below @p limit are excluded from the regression.
1983  ***************************************************************************/
1985  const GNdarray& array,
1986  const int& nodes,
1987  const double& limit) const
1988 {
1989  // Initialise variables
1990  double mean_x = 0.0;
1991  double mean_y = 0.0;
1992  int ileft = inx - 1;
1993  int iright = inx + 1;
1994  int nleft = 0;
1995  int nright = 0;
1996 
1997  // Allocate vector of elements used for regression
1998  std::vector<double> x_vals;
1999  std::vector<double> y_vals;
2000 
2001  // Add nodes on the left of the element of interest
2002  while (nleft < nodes) {
2003  if (ileft < 0) {
2004  break;
2005  }
2006  if (array(ileft) > limit) {
2007  double x = double(ileft);
2008  double y = array(ileft);
2009  x_vals.push_back(x);
2010  y_vals.push_back(y);
2011  mean_x += x;
2012  mean_y += y;
2013  nleft++;
2014  }
2015  ileft--;
2016  }
2017 
2018  // Add remaining nodes on the right of the element of interest
2019  while (nright < 2*nodes-nleft) {
2020  if (iright >= array.size()) {
2021  break;
2022  }
2023  if (array(iright) > limit) {
2024  double x = double(iright);
2025  double y = array(iright);
2026  x_vals.push_back(x);
2027  y_vals.push_back(y);
2028  mean_x += x;
2029  mean_y += y;
2030  nright++;
2031  }
2032  iright++;
2033  }
2034 
2035  // Add remaining nodes on the left if all right nodes were not exhausted
2036  while (nleft < 2*nodes-nright) {
2037  if (ileft < 0) {
2038  break;
2039  }
2040  if (array(ileft) > limit) {
2041  double x = double(ileft);
2042  double y = array(ileft);
2043  x_vals.push_back(x);
2044  y_vals.push_back(y);
2045  mean_x += x;
2046  mean_y += y;
2047  nleft++;
2048  }
2049  ileft--;
2050  }
2051 
2052  // Compute mean x and y values
2053  double total = double(nleft+nright);
2054  if (total > 0.0) {
2055  mean_x /= total;
2056  mean_y /= total;
2057  }
2058 
2059  // Compute regression line slope
2060  double beta_nom = 0.0;
2061  double beta_denom = 0.0;
2062  for (int i = 0; i < x_vals.size(); ++i) {
2063  double x = x_vals[i];
2064  double y = y_vals[i];
2065  beta_nom += (x - mean_x) * (y - mean_y);
2066  beta_denom += (x - mean_x) * (x - mean_x);
2067  }
2068  double beta = beta_nom / beta_denom;
2069 
2070  // Compute regression line offset
2071  double alpha = mean_y - beta * mean_x;
2072 
2073  // Compute value
2074  double value = alpha + beta * double(inx);
2075 
2076  // Return value
2077  return value;
2078 }
2079 
2080 
2081 /***********************************************************************//**
2082  * @brief Compute moments
2083  *
2084  * @param[in] itheta Offset angle index.
2085  * @param[out] mean Pointer to mean array.
2086  * @param[out] rms Pointer to rms array.
2087  * @param[out] total Pointer to total array.
2088  *
2089  * Computes the mean and root mean square values as function of true energy
2090  * for a given offset angle.
2091  ***************************************************************************/
2092 void GCTAEdisp2D::get_moments(const int& itheta,
2093  GNdarray* mean,
2094  GNdarray* rms,
2095  GNdarray* total) const
2096 {
2097  // Get axes dimensions
2098  int netrue = m_edisp.axis_bins(m_inx_etrue);
2099  int nmigra = m_edisp.axis_bins(m_inx_migra);
2100  int npix = netrue * nmigra;
2101 
2102  // Loop over all true energies
2103  for (int ietrue = 0; ietrue < netrue; ++ietrue) {
2104 
2105  // Compute base index
2106  int ibase = itheta * npix + ietrue;
2107 
2108  // Extract array
2109  GNdarray array(nmigra);
2110  for (int imigra = 0, i = ibase; imigra < nmigra; ++imigra, i += netrue) {
2111  array(imigra) = m_edisp(m_inx_matrix, i);
2112  }
2113 
2114  // Store sum
2115  (*total)(ietrue) = sum(array);
2116 
2117  // Get Gaussian mean and rms
2118  double mean_value = 0.0;
2119  double rms_value = 0.0;
2120  get_mean_rms(array, &mean_value, &rms_value);
2121 
2122  // Store mean and rms
2123  (*mean)(ietrue) = mean_value;
2124  (*rms)(ietrue) = rms_value;
2125 
2126  } // endfor: looped over all true energies
2127 
2128  // Return
2129  return;
2130 }
2131 
2132 
2133 /***********************************************************************//**
2134  * @brief Compute mean and root mean square of migration array
2135  *
2136  * @param[in] array Energy dispersion array.
2137  * @param[out] mean Pointer to mean migration value.
2138  * @param[out] rms Pointer to root mean square value.
2139  *
2140  * Computes the mean and the root mean square of the migration array. If the
2141  * migration array is empty the mean and root mean square values are set to
2142  * zero.
2143  *
2144  * The method does not check whether the @p mean and @p rms pointers are
2145  * valid.
2146  ***************************************************************************/
2148  double* mean,
2149  double* rms) const
2150 {
2151  // Initialise mean and rms
2152  *mean = 0.0;
2153  *rms = 0.0;
2154 
2155  // Get reference to migration values
2156  const GNodeArray& migras = m_edisp.axis_nodes(m_inx_migra);
2157  int nmigra = migras.size();
2158 
2159  // Pre-compute values for mean and rms computation
2160  double sum = 0.0;
2161  for (int imigra = 0; imigra < nmigra; ++imigra) {
2162  double migra = migras[imigra];
2163  double weight = migra * array(imigra);
2164  *mean += migra * array(imigra);
2165  *rms += weight * migra;
2166  sum += array(imigra);
2167  }
2168 
2169  // If the array is not empty then compute now mean and standard deviation
2170  if (sum > 0.0) {
2171  *mean /= sum;
2172  *rms /= sum;
2173  *rms -= (*mean) * (*mean);
2174  *rms = std::sqrt(*rms);
2175  }
2176 
2177  // Return
2178  return;
2179 }
2180 
2181 
2182 /***********************************************************************//**
2183  * @brief Return Gaussian approximation of energy dispersion array
2184  *
2185  * @param[in] mean Gaussian mean.
2186  * @param[in] rms Gaussian rms.
2187  * @param[in] total Gaussian total.
2188  * @return Gaussian approximation of energy dispersion array.
2189  *
2190  * Returns a Gaussian approximation of the energy dispersion array by
2191  * computing the mean migration value and its root mean square and by using
2192  * these values as the centre and the width of a Gaussian function. The
2193  * Gaussian function is normalized so that the sum of the output array is
2194  * unity.
2195  ***************************************************************************/
2197  const double& rms,
2198  const double& total) const
2199 {
2200  // Initialise empty Gaussian array
2201  int nmigra = m_edisp.axis_bins(m_inx_migra);
2202  GNdarray gaussian(nmigra);
2203 
2204  // If the rms is valid then compute Gaussian
2205  if (rms > 0.0) {
2206 
2207  // Get reference to migration values
2208  const GNodeArray& migras = m_edisp.axis_nodes(m_inx_migra);
2209  int nmigra = migras.size();
2210 
2211  // Compute Gaussian
2212  double total_gauss = 0.0;
2213  for (int imigra = 0; imigra < nmigra; ++imigra) {
2214  double arg = (migras[imigra] - mean) / rms;
2215  double value = std::exp(-0.5*arg*arg);
2216  gaussian(imigra) = value;
2217  total_gauss += value;
2218  }
2219 
2220  // Normalise Gaussian
2221  if (total_gauss > 0.0) {
2222  gaussian *= total / total_gauss;
2223  }
2224 
2225  } // endif: computed Gaussian
2226 
2227  // Return Gaussian array
2228  return gaussian;
2229 }
2230 
2231 
2232 /***********************************************************************//**
2233  * @brief Integration kernel for GCTAEdisp2D::edisp_ereco_kern class
2234  *
2235  * @param[in] log10Ereco Log10 of reconstructed energy (\f$\log_{10}\f$ MeV).
2236  * @return Energy dispersion (\f$(\log_{10}\f$ MeV\f$)^{-1}\f$).
2237  *
2238  * This method implements the function
2239  *
2240  * \f[
2241  * E_{\rm disp}(\log_{10} E_{\rm reco} | E_{\rm true}, \theta) =
2242  * E_{\rm disp}(m | E_{\rm true}, \theta) \times
2243  * \frac{\log 10 \times E_{\rm reco}}{E_{\rm true}}
2244  * \f]
2245  *
2246  * which is the integration kernel needed for the
2247  * GCTAEdisp2D::edisp_ereco_kern class. The class is used by
2248  * GCTAEdisp2D::normalize_table() to integrate the energy dispersion
2249  * information using
2250  *
2251  * \f[
2252  * \int_{\log_{10} E_{\rm reco}^{\rm min}}^{\log_{10} E_{\rm reco}^{\rm max}}
2253  * E_{\rm disp}(\log_{10} E_{\rm reco} | E_{\rm true}, \theta) \,
2254  * d(\log_{10} E_{\rm reco})
2255  * \f]
2256  ***************************************************************************/
2257 double GCTAEdisp2D::edisp_ereco_kern::eval(const double& log10Ereco)
2258 {
2259  // Set reconstructued event energy
2260  GEnergy ereco;
2261  ereco.log10MeV(log10Ereco);
2262 
2263  // Get function value
2264  double value = m_parent->operator()(ereco, m_etrue, m_theta);
2265 
2266  // Normalize energy dispersion so that the units are per log10 MeV
2267  value *= gammalib::ln10 * ereco.MeV();
2268 
2269  // Compile option: Check for NaN
2270  #if defined(G_NAN_CHECK)
2271  if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
2272  std::cout << "*** ERROR: GCTAEdisp2D::edisp_ereco_kern::eval";
2273  std::cout << "(log10Ereco=" << log10Ereco << "): ";
2274  std::cout << " NaN/Inf encountered";
2275  std::cout << " (value=" << value;
2276  std::cout << ")" << std::endl;
2277  }
2278  #endif
2279 
2280  // Return value
2281  return value;
2282 }
int m_inx_etrue
True energy index.
const GNodeArray & axis_nodes(const int &axis) const
Return axis nodes.
void normalize_table(void)
Normalize energy dispersion table.
int size(void) const
Return number of nodes in node array.
Definition: GNodeArray.hpp:192
CTA 2D energy dispersion class.
Definition: GCTAEdisp2D.hpp:90
std::string print(const GChatter &chatter=NORMAL) const
Print energy dispersion information.
GEnergy m_etrue
True photon energy.
GFilename filename(void) const
Return filename.
GFitsTable * table(const int &extno)
Get pointer to table HDU.
Definition: GFits.cpp:482
Node array class.
Definition: GNodeArray.hpp:60
double romberg(std::vector< double > bounds, const int &order=5)
Perform Romberg integration.
Definition: GIntegral.cpp:382
GEnergy etrue(const int &ietrue) const
Get true energy.
Random number generator class definition.
const GCTAEdisp2D * m_parent
Pointer to parent class.
double smoothed_array_value(const int &inx, const GNdarray &array, const int &nodes, const double &limit) const
Get smoothed array value.
double theta(const int &itheta) const
Get offset angle in radiaus.
int size(void) const
Return number of energy boundaries.
Definition: GEbounds.hpp:163
GCTAEdisp2D(void)
Void constructor.
Definition: GCTAEdisp2D.cpp:76
void set_max_edisp(void)
Set array of maximum energy dispersion values.
bool is_empty(void) const
Signal if filename is empty.
Definition: GFilename.hpp:160
void free_members(void)
Delete class members.
Definition: GCTAEdisp.cpp:164
const double & wgt_left(void) const
Returns left node weight.
Definition: GNodeArray.hpp:264
Abstract base class for the CTA energy dispersion.
Definition: GCTAEdisp.hpp:49
double m_last_theta_etrue
bool m_ereco_bounds_computed
double TeV(void) const
Return energy in TeV.
Definition: GEnergy.cpp:348
double sum(const GVector &vector)
Computes vector sum.
Definition: GVector.cpp:944
std::string extname(const std::string &defaultname="") const
Return extension name.
Definition: GFilename.cpp:385
double log10TeV(void) const
Return log10 of energy in TeV.
Definition: GEnergy.cpp:457
double m_last_theta_ereco
Random number generator class.
Definition: GRan.hpp:44
double migra(const int &imigra) const
Get migration.
void copy_members(const GCTAEdisp2D &edisp)
Copy class members.
double MeV(void) const
Return energy in MeV.
Definition: GEnergy.cpp:321
virtual ~GCTAEdisp2D(void)
Destructor.
GCTAResponseTable m_edisp
Edisp response table.
Gammalib tools definition.
void fetch(void) const
Fetch energy dispersion.
GIntegral class interface definition.
Definition: GIntegral.hpp:46
FITS file class.
Definition: GFits.hpp:63
void axis_radians(const int &axis)
Set nodes for a radians axis.
FITS file class interface definition.
GEnergy mc(GRan &ran, const GEnergy &etrue, const double &theta=0.0, const double &phi=0.0, const double &zenith=0.0, const double &azimuth=0.0) const
Simulate energy dispersion.
void set_value(const double &value) const
Set indices and weighting factors for interpolation.
Definition: GNodeArray.cpp:587
GEbounds etrue_bounds(const GEnergy &ereco, const double &theta=0.0, const double &phi=0.0, const double &zenith=0.0, const double &azimuth=0.0) const
Return true energy interval that contains the energy dispersion.
std::string strip_whitespace(const std::string &arg)
Strip leading and trailing whitespace from string.
Definition: GTools.cpp:80
bool m_etrue_bounds_computed
void read(const GFitsTable &table)
Read response table from FITS table HDU.
const double ln10
Definition: GMath.hpp:46
double m_theta_max
Maximum theta (radians)
double log10MeV(void) const
Return log10 of energy in MeV.
Definition: GEnergy.cpp:423
bool m_fetched
Signals that Edisp has been fetched.
bool is_notanumber(const double &x)
Signal if argument is not a number.
Definition: GTools.hpp:201
void write(GFitsBinTable &table) const
Write energy dispersion into FITS binary table.
const double & axis_lo(const int &axis, const int &bin) const
Return lower bin boundary for bin in axis.
bool is_infinite(const double &x)
Signal if argument is infinite.
Definition: GTools.hpp:184
Definition of support function used by CTA classes.
void compute_ereco_bounds(const double &theta=0.0, const double &phi=0.0, const double &zenith=0.0, const double &azimuth=0.0) const
Compute vector of reconstructed energy bounds.
void write(GFitsTable &table) const
Write response table into FITS table HDU.
int axis(const std::string &name) const
Determine index of an axis.
const double deg2rad
Definition: GMath.hpp:43
GVector sqrt(const GVector &vector)
Computes square root of vector elements.
Definition: GVector.cpp:1358
#define G_FETCH
Definition: GCTAEdisp2D.cpp:51
const double & wgt_right(void) const
Returns right node weight.
Definition: GNodeArray.hpp:279
void load(const GFilename &filename)
Load energy dispersion from FITS file.
Energy boundaries container class.
Definition: GEbounds.hpp:60
void remove(const int &extno)
Remove HDU from FITS file.
Definition: GFits.cpp:862
void set_table(void)
Set table.
std::string print(const GChatter &chatter=NORMAL) const
Print energy.
Definition: GEnergy.cpp:748
double eval(const double &log10Ereco)
Integration kernel for GCTAEdisp2D::edisp_ereco_kern class.
int table_stride(const int &axis) const
Return stride of response table axis.
void clear(void)
Clear response table.
Filename class.
Definition: GFilename.hpp:62
void smooth_table(void)
Smoothed energy dispersion table.
N-dimensional array class interface definition.
const GEnergy & emin(void) const
Return minimum energy of all intervals.
Definition: GEbounds.hpp:187
double m_logEsrc_min
Minimum logE (log10(E/TeV))
void get_mean_rms(const GNdarray &array, double *mean, double *rms) const
Compute mean and root mean square of migration array.
bool exists(void) const
Checks whether file exists.
Definition: GFilename.cpp:223
double uniform(void)
Returns random double precision floating value in range 0 to 1.
Definition: GRan.cpp:242
int m_inx_matrix
Matrix.
Abstract interface for FITS table.
Definition: GFitsTable.hpp:44
GEbounds ereco_bounds(const GEnergy &etrue, const double &theta=0.0, const double &phi=0.0, const double &zenith=0.0, const double &azimuth=0.0) const
Return observed energy interval that contains the energy dispersion.
int size(void) const
Return number of elements in array.
Definition: GNdarray.hpp:294
GChatter
Definition: GTypemaps.hpp:33
#define G_READ
Definition: GCTAEdisp2D.cpp:48
const int & inx_left(void) const
Returns left node index.
Definition: GNodeArray.hpp:235
CTA 2D energy dispersion class definition.
void save(const GFilename &filename, const bool &clobber=false) const
Save energy dispersion table into FITS file.
N-dimensional array class.
Definition: GNdarray.hpp:44
#define G_MC
Definition: GCTAEdisp2D.cpp:49
const std::string & extname(void) const
Return extension name.
Definition: GFitsHDU.hpp:162
void axis_log10(const int &axis)
Set nodes for a logarithmic (base 10) axis.
double get_max_edisp(const GEnergy &etrue, const double &theta) const
Get maximum energy dispersion value.
GNdarray smooth_array(const GNdarray &array, const int &nstart, const int &nstop, const double &limit) const
Smoothed array.
int table(const std::string &name) const
Determine index of table.
void compute_etrue_bounds(const double &theta=0.0, const double &phi=0.0, const double &zenith=0.0, const double &azimuth=0.0) const
Compute vector of true energy boundaries.
const GCTAResponseTable & table(void) const
Return response table.
std::string gadf_hduclas4(const GFits &fits, const std::string &hduclas4)
Return extension name for GADF response table of given HDU class 4.
double m_logEsrc_max
Maximum logE (log10(E/TeV))
const int & inx_right(void) const
Returns right node index.
Definition: GNodeArray.hpp:249
void free_members(void)
Delete class members.
Fast Fourier transformation class interface definition.
std::vector< GEbounds > m_ereco_bounds
GCTAEdisp & operator=(const GCTAEdisp &edisp)
Assignment operator.
Definition: GCTAEdisp.cpp:106
void clear(void)
Clear energy dispersion.
double m_theta_min
Minimum theta (radians)
std::vector< double > m_max_edisp
Maximum Edisp.
const std::string & telescope(void) const
Return telescope string.
GCTAEdisp2D * clone(void) const
Clone energy dispersion.
const std::string extname_cta_edisp2d
Definition: GCTAEdisp2D.hpp:46
void init_members(void)
Initialise class members.
void eps(const double &eps)
Set relative precision.
Definition: GIntegral.hpp:214
int m_inx_theta
Theta index.
GFilename m_filename
Name of Edisp response file.
std::string url(void) const
Return Uniform Resource Locator (URL)
Definition: GFilename.hpp:189
int axis_bins(const int &axis) const
Return number bins in an axis.
void clear(void)
Clear file name.
Definition: GFilename.cpp:188
double table_value(const int &base_ll, const int &base_lr, const int &base_rl, const int &base_rr, const double &wgt_el, const double &wgt_er, const double &wgt_tl, const double &wgt_tr, const int &offset) const
Return bi-linearly interpolate table value for given migration bin.
int table_index(const int &ietrue, const int &imigra, const int &itheta) const
Return index of response table element.
int m_inx_migra
Migration index.
GNdarray gaussian_array(const double &mean, const double &rms, const double &total) const
Return Gaussian approximation of energy dispersion array.
FITS binary table class.
bool has_axis(const std::string &name) const
Check whether an axis exists.
const double & axis_hi(const int &axis, const int &bin) const
Return upper bin boundary for bin in axis.
Exception handler interface definition.
FITS binary table class definition.
GVector exp(const GVector &vector)
Computes exponential of vector elements.
Definition: GVector.cpp:1232
CTA response table class.
double operator()(const GEnergy &ereco, const GEnergy &etrue, const double &theta=0.0, const double &phi=0.0, const double &zenith=0.0, const double &azimuth=0.0) const
Return energy dispersion in units of MeV .
void init_members(void)
Initialise class members.
Definition: GCTAEdisp.cpp:142
double m_migra_min
Minimum migration.
const GEnergy & emax(void) const
Return maximum energy of all intervals.
Definition: GEbounds.hpp:199
void get_moments(const int &itheta, GNdarray *mean, GNdarray *rms, GNdarray *total) const
Compute moments.
const double rad2deg
Definition: GMath.hpp:44
GEnergy m_last_etrue
std::string parformat(const std::string &s, const int &indent=0)
Convert string in parameter format.
Definition: GTools.cpp:1143
Integration class interface definition.
GEnergy m_last_ereco
void close(void)
Close FITS file.
Definition: GFits.cpp:1342
double prob_erecobin(const GEnergy &ereco_min, const GEnergy &ereco_max, const GEnergy &etrue, const double &theta) const
Return energy dispersion probability for reconstructed energy interval.
const int & axes(void) const
Return number of axes of the tables.
GCTAEdisp2D & operator=(const GCTAEdisp2D &edisp)
Assignment operator.
double m_theta
Offset angle.
void clear(void)
Clear instance.
Definition: GEnergy.cpp:261
void read(const GFitsTable &table)
Read energy dispersion from FITS table.
double m_migra_max
Maximum migration.
Filename class interface definition.
Mathematical function definitions.
GVector log10(const GVector &vector)
Computes base10 logarithm of vector elements.
Definition: GVector.cpp:1295
Class that handles energies in a unit independent way.
Definition: GEnergy.hpp:48
std::vector< GEbounds > m_etrue_bounds
void set_boundaries(void)
Set energy dispersion boundaries.
std::string str(const unsigned short int &value)
Convert unsigned short integer value into string.
Definition: GTools.cpp:489
FITS table abstract base class interface definition.