GammaLib 2.0.0
Loading...
Searching...
No Matches
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
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
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 ***************************************************************************/
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();
275
276 // Initialise 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 ***************************************************************************/
343{
344 // Clear response table
345 m_edisp.clear();
346
347 // Read energy dispersion 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.";
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
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 ***************************************************************************/
401void 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 ***************************************************************************/
430void 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 "+
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 "+
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
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
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 ***************************************************************************/
734void 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+"\".";
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 ***************************************************************************/
834double 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 ***************************************************************************/
962std::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
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;
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
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 ***************************************************************************/
1122GEnergy 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 ***************************************************************************/
1143double 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 ***************************************************************************/
1160double 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 ***************************************************************************/
1186void 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 ***************************************************************************/
1294void 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
1437
1438 // Smooth energy dispersion table (kludge only for CTA)
1439 #if defined(G_SMOOTH_EDISP_KLUDGE)
1441 smooth_table();
1442 }
1443 #endif
1444
1445 // Normalize energy dispersion 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
1476 m_logEsrc_min = std::log10(m_edisp.axis_lo(m_inx_etrue, 0));
1477 m_logEsrc_max = std::log10(m_edisp.axis_hi(m_inx_etrue, netrue-1));
1478
1479 // Compute minimum and maximum migration boundaries
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 ***************************************************************************/
1720int 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 ***************************************************************************/
1745int 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 ***************************************************************************/
1777double 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 ***************************************************************************/
1818double 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 ***************************************************************************/
2092void 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 ***************************************************************************/
2257double 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}
#define G_READ
#define G_FETCH
CTA 2D energy dispersion class definition.
Definition of support function used by CTA classes.
Exception handler interface definition.
Fast Fourier transformation class interface definition.
Filename class interface definition.
FITS binary table class definition.
FITS table abstract base class interface definition.
FITS file class interface definition.
Integration class interface definition.
Mathematical function definitions.
N-dimensional array class interface definition.
Random number generator class definition.
Gammalib tools definition.
GChatter
Definition GTypemaps.hpp:33
@ SILENT
Definition GTypemaps.hpp:34
double sum(const GVector &vector)
Computes vector sum.
Definition GVector.cpp:944
double m_theta
Offset angle.
GEnergy m_etrue
True photon energy.
double eval(const double &log10Ereco)
Integration kernel for GCTAEdisp2D::edisp_ereco_kern class.
const GCTAEdisp2D * m_parent
Pointer to parent class.
CTA 2D energy dispersion class.
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.
double m_last_theta_etrue
void copy_members(const GCTAEdisp2D &edisp)
Copy class members.
void fetch(void) const
Fetch energy dispersion.
std::vector< double > m_max_edisp
Maximum Edisp.
int m_inx_matrix
Matrix.
GEnergy etrue(const int &ietrue) const
Get true energy.
void set_table(void)
Set table.
void init_members(void)
Initialise class members.
double get_max_edisp(const GEnergy &etrue, const double &theta) const
Get maximum energy dispersion value.
void save(const GFilename &filename, const bool &clobber=false) const
Save energy dispersion table into FITS file.
GNdarray gaussian_array(const double &mean, const double &rms, const double &total) const
Return Gaussian approximation of energy dispersion array.
GEnergy m_last_ereco
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.
void get_moments(const int &itheta, GNdarray *mean, GNdarray *rms, GNdarray *total) const
Compute moments.
double smoothed_array_value(const int &inx, const GNdarray &array, const int &nodes, const double &limit) const
Get smoothed array value.
double m_migra_min
Minimum migration.
double m_theta_min
Minimum theta (radians)
const GCTAResponseTable & table(void) const
Return response table.
void set_boundaries(void)
Set energy dispersion boundaries.
GFilename filename(void) const
Return filename.
void clear(void)
Clear energy dispersion.
std::vector< GEbounds > m_ereco_bounds
void get_mean_rms(const GNdarray &array, double *mean, double *rms) const
Compute mean and root mean square of migration array.
int m_inx_etrue
True energy index.
double m_theta_max
Maximum theta (radians)
int table_stride(const int &axis) const
Return stride of response table axis.
void load(const GFilename &filename)
Load energy dispersion from FITS file.
double m_logEsrc_max
Maximum logE (log10(E/TeV))
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.
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 .
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.
std::vector< GEbounds > m_etrue_bounds
double m_last_theta_ereco
void set_max_edisp(void)
Set array of maximum energy dispersion values.
void write(GFitsBinTable &table) const
Write energy dispersion into FITS binary table.
void normalize_table(void)
Normalize energy dispersion table.
int m_inx_theta
Theta index.
GCTAEdisp2D & operator=(const GCTAEdisp2D &edisp)
Assignment operator.
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.
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.
bool m_fetched
Signals that Edisp has been fetched.
GCTAResponseTable m_edisp
Edisp response table.
std::string print(const GChatter &chatter=NORMAL) const
Print energy dispersion information.
double m_migra_max
Maximum migration.
virtual ~GCTAEdisp2D(void)
Destructor.
double m_logEsrc_min
Minimum logE (log10(E/TeV))
GFilename m_filename
Name of Edisp response file.
void free_members(void)
Delete class members.
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.
GNdarray smooth_array(const GNdarray &array, const int &nstart, const int &nstop, const double &limit) const
Smoothed array.
bool m_ereco_bounds_computed
int m_inx_migra
Migration index.
bool m_etrue_bounds_computed
GCTAEdisp2D * clone(void) const
Clone energy dispersion.
double theta(const int &itheta) const
Get offset angle in radiaus.
int table_index(const int &ietrue, const int &imigra, const int &itheta) const
Return index of response table element.
void read(const GFitsTable &table)
Read energy dispersion from FITS table.
void smooth_table(void)
Smoothed energy dispersion table.
double migra(const int &imigra) const
Get migration.
GEnergy m_last_etrue
GCTAEdisp2D(void)
Void constructor.
Abstract base class for the CTA energy dispersion.
Definition GCTAEdisp.hpp:49
GCTAEdisp & operator=(const GCTAEdisp &edisp)
Assignment operator.
void free_members(void)
Delete class members.
void init_members(void)
Initialise class members.
CTA response table class.
void read(const GFitsTable &table)
Read response table from FITS table HDU.
int table(const std::string &name) const
Determine index of table.
const std::string & telescope(void) const
Return telescope string.
void axis_log10(const int &axis)
Set nodes for a logarithmic (base 10) axis.
void axis_radians(const int &axis)
Set nodes for a radians axis.
int axis(const std::string &name) const
Determine index of an axis.
const int & axes(void) const
Return number of axes of the tables.
void write(GFitsTable &table) const
Write response table into FITS table HDU.
int axis_bins(const int &axis) const
Return number bins in an axis.
const double & axis_hi(const int &axis, const int &bin) const
Return upper bin boundary for bin in axis.
bool has_axis(const std::string &name) const
Check whether an axis exists.
const GNodeArray & axis_nodes(const int &axis) const
Return axis nodes.
void clear(void)
Clear response table.
const double & axis_lo(const int &axis, const int &bin) const
Return lower bin boundary for bin in axis.
Energy boundaries container class.
Definition GEbounds.hpp:60
const GEnergy & emax(void) const
Return maximum energy of all intervals.
Definition GEbounds.hpp:199
int size(void) const
Return number of energy boundaries.
Definition GEbounds.hpp:163
const GEnergy & emin(void) const
Return minimum energy of all intervals.
Definition GEbounds.hpp:187
Class that handles energies in a unit independent way.
Definition GEnergy.hpp:48
double log10TeV(void) const
Return log10 of energy in TeV.
Definition GEnergy.cpp:457
double MeV(void) const
Return energy in MeV.
Definition GEnergy.cpp:321
double log10MeV(void) const
Return log10 of energy in MeV.
Definition GEnergy.cpp:423
std::string print(const GChatter &chatter=NORMAL) const
Print energy.
Definition GEnergy.cpp:748
void clear(void)
Clear instance.
Definition GEnergy.cpp:261
double TeV(void) const
Return energy in TeV.
Definition GEnergy.cpp:348
Filename class.
Definition GFilename.hpp:62
std::string url(void) const
Return Uniform Resource Locator (URL)
std::string extname(const std::string &defaultname="") const
Return extension name.
bool exists(void) const
Checks whether file exists.
bool is_empty(void) const
Signal if filename is empty.
void clear(void)
Clear file name.
FITS binary table class.
Abstract interface for FITS table.
FITS file class.
Definition GFits.hpp:63
bool contains(const int &extno) const
Check if HDU exists in FITS file.
Definition GFits.hpp:282
GFitsHDU * append(const GFitsHDU &hdu)
Append HDU to FITS file.
Definition GFits.cpp:678
void close(void)
Close FITS file.
Definition GFits.cpp:1342
void remove(const int &extno)
Remove HDU from FITS file.
Definition GFits.cpp:862
void save(const bool &clobber=false)
Saves FITS file.
Definition GFits.cpp:1178
GFitsTable * table(const int &extno)
Get pointer to table HDU.
Definition GFits.cpp:482
GIntegral class interface definition.
Definition GIntegral.hpp:46
void eps(const double &eps)
Set relative precision.
double romberg(std::vector< double > bounds, const int &order=5)
Perform Romberg integration.
N-dimensional array class.
Definition GNdarray.hpp:44
int size(void) const
Return number of elements in array.
Definition GNdarray.hpp:293
Node array class.
void set_value(const double &value) const
Set indices and weighting factors for interpolation.
const int & inx_right(void) const
Returns right node index.
const int & inx_left(void) const
Returns left node index.
const double & wgt_right(void) const
Returns right node weight.
const double & wgt_left(void) const
Returns left node weight.
int size(void) const
Return number of nodes in node array.
Random number generator class.
Definition GRan.hpp:44
double uniform(void)
Returns random double precision floating value in range 0 to 1.
Definition GRan.cpp:242
std::string parformat(const std::string &s, const int &indent=0)
Convert string in parameter format.
Definition GTools.cpp:1143
bool is_infinite(const double &x)
Signal if argument is infinite.
Definition GTools.hpp:184
bool is_notanumber(const double &x)
Signal if argument is not a number.
Definition GTools.hpp:201
const double ln10
Definition GMath.hpp:46
std::string str(const unsigned short int &value)
Convert unsigned short integer value into string.
Definition GTools.cpp:489
std::string strip_whitespace(const std::string &arg)
Strip leading and trailing whitespace from string.
Definition GTools.cpp:80
const double deg2rad
Definition GMath.hpp:43
const std::string extname_cta_edisp2d
std::string gadf_hduclas4(const GFits &fits, const std::string &hduclas4)
Return extension name for GADF response table of given HDU class 4.
const double rad2deg
Definition GMath.hpp:44