GammaLib 2.0.0
Loading...
Searching...
No Matches
GCTAResponseIrf.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * GCTAResponseIrf.cpp - CTA instrument response function class *
3 * ----------------------------------------------------------------------- *
4 * copyright (C) 2010-2022 by Juergen Knoedlseder *
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 GCTAResponseIrf.cpp
23 * @brief CTA response class implementation
24 * @author Juergen Knoedlseder
25 */
26
27/* __ Includes ___________________________________________________________ */
28#ifdef HAVE_CONFIG_H
29#include <config.h>
30#endif
31#include <typeinfo>
32#include <cmath>
33#include <vector>
34#include <string>
35#include "GException.hpp"
36#include "GTools.hpp"
37#include "GMath.hpp"
38#include "GFits.hpp"
39#include "GFilename.hpp"
40#include "GIntegral.hpp"
41#include "GCaldb.hpp"
42#include "GSource.hpp"
43#include "GRan.hpp"
44#include "GModelSky.hpp"
51#include "GArf.hpp"
52#include "GCTAObservation.hpp"
53#include "GCTAResponseIrf.hpp"
55#include "GCTAPointing.hpp"
56#include "GCTAEventAtom.hpp"
57#include "GCTAEventList.hpp"
58#include "GCTARoi.hpp"
59#include "GCTASupport.hpp"
60#include "GCTAAeff.hpp"
61#include "GCTAAeff2D.hpp"
62#include "GCTAAeffArf.hpp"
63#include "GCTAAeffPerfTable.hpp"
64#include "GCTAPsf.hpp"
65#include "GCTAPsf2D.hpp"
66#include "GCTAPsfVector.hpp"
67#include "GCTAPsfPerfTable.hpp"
68#include "GCTAPsfKing.hpp"
69#include "GCTAPsfTable.hpp"
70#include "GCTAEdisp.hpp"
71#include "GCTAEdisp2D.hpp"
72#include "GCTAEdispRmf.hpp"
74#include "GCTABackground.hpp"
76#include "GCTABackground2D.hpp"
77#include "GCTABackground3D.hpp"
78
79/* __ Method name definitions ____________________________________________ */
80#define G_IRF "GCTAResponseIrf::irf(GInstDir&, GEnergy&, GTime&, GSkyDir&,"\
81 " GEnergy&, GTime&, GObservation&)"
82#define G_MC "GCTAResponseIrf::mc(double&, GPhoton&, GObservation&, GRan&)"
83#define G_READ "GCTAResponseIrf::read(GXmlElement&)"
84#define G_WRITE "GCTAResponseIrf::write(GXmlElement&)"
85#define G_LOAD_AEFF "GCTAResponseIrf::load_aeff(GFilename&)"
86#define G_LOAD_PSF "GCTAResponseIrf::load_psf(GFilename&)"
87#define G_LOAD_EDISP "GCTAResponseIrf::load_edisp(GFilename&)"
88#define G_LOAD_BACKGROUND "GCTAResponseIrf::load_background(GFilename&)"
89#define G_NIRF "GCTAResponseIrf::nirf(GPhoton&, GEnergy&, GTime&,"\
90 " GObservation&)"
91#define G_IRF_RADIAL "GCTAResponseIrf::irf_radial(GEvent&, GSource&,"\
92 " GObservation&)"
93#define G_IRF_ELLIPTICAL "GCTAResponseIrf::irf_elliptical(GEvent&, GSource&,"\
94 " GObservation&)"
95#define G_IRF_DIFFUSE "GCTAResponseIrf::irf_diffuse(GEvent&, GSource&,"\
96 " GObservation&)"
97#define G_NROI_RADIAL "GCTAResponseIrf::nroi_radial(GModelSky&, GEnergy&,"\
98 " GTime&, GEnergy&, GTime&, GObservation&)"
99#define G_NROI_ELLIPTICAL "GCTAResponseIrf::nroi_elliptical(GModelSky&,"\
100 " GEnergy&, GTime&, GEnergy&, GTime&, GObservation&)"
101#define G_NROI_DIFFUSE "GCTAResponseIrf::nroi_diffuse(GModelSky&, GEnergy&,"\
102 " GTime&, GEnergy&, GTime&, GObservation&)"
103#define G_AEFF "GCTAResponseIrf::aeff(double&, double&, double&, double&,"\
104 " double&)"
105#define G_PSF "GCTAResponseIrf::psf(double&, double&, double&, double&,"\
106 " double&)"
107#define G_PSF_DELTA_MAX "GCTAResponseIrf::psf_delta_max(double&, double&,"\
108 " double&, double&, double&)"
109
110/* __ Macros _____________________________________________________________ */
111
112/* __ Coding definitions _________________________________________________ */
113//#define G_USE_PSF_SYSTEM //!< Do radial Irf integrations in Psf system
114
115/* __ Debug definitions __________________________________________________ */
116//#define G_DEBUG_IRF_RADIAL //!< Debug irf_radial method
117//#define G_DEBUG_IRF_DIFFUSE //!< Debug irf_diffuse method
118//#define G_DEBUG_IRF_ELLIPTICAL //!< Debug irf_elliptical method
119//#define G_DEBUG_NROI_RADIAL //!< Debug npred_radial method
120//#define G_DEBUG_NROI_ELLIPTICAL //!< Debug npred_elliptical method
121//#define G_DEBUG_NROI_DIFFUSE //!< Debug npred_diffuse method
122//#define G_DEBUG_PRINT_AEFF //!< Debug print() Aeff method
123//#define G_DEBUG_PRINT_PSF //!< Debug print() Psf method
124//#define G_DEBUG_PSF_DUMMY_SIGMA //!< Debug psf_dummy_sigma method
125
126/* __ Constants __________________________________________________________ */
127
128
129/*==========================================================================
130 = =
131 = Constructors/destructors =
132 = =
133 ==========================================================================*/
134
135/***********************************************************************//**
136 * @brief Void constructor
137 *
138 * Constructs void CTA response.
139 ***************************************************************************/
141{
142 // Initialise members
143 init_members();
144
145 // Return
146 return;
147}
148
149
150/***********************************************************************//**
151 * @brief Copy constructor
152 *
153 * @param[in] rsp CTA response.
154 *
155 * Constructs CTA response by making a deep copy of an existing object.
156 **************************************************************************/
158{
159 // Initialise members
160 init_members();
161
162 // Copy members
163 copy_members(rsp);
164
165 // Return
166 return;
167}
168
169
170/***********************************************************************//**
171 * @brief XML constructor
172 *
173 * @param[in] xml XML element.
174 *
175 * Construct CTA response from XML element.
176 ***************************************************************************/
178{
179 // Initialise members
180 init_members();
181
182 // Read information from XML element
183 read(xml);
184
185 // Return
186 return;
187}
188
189
190/***********************************************************************//**
191 * @brief Response constructor
192 *
193 * @param[in] rspname Response file name.
194 * @param[in] caldb Calibration database.
195 *
196 * Create instance of CTA response by specifying the response name and the
197 * calibration database. The response name can be either a response identifier
198 * or a filename (see GCTAResponseIrf::load for more information).
199 ***************************************************************************/
200GCTAResponseIrf::GCTAResponseIrf(const std::string& rspname,
201 const GCaldb& caldb) : GCTAResponse()
202{
203 // Initialise members
204 init_members();
205
206 // Set calibration database
207 m_caldb = caldb;
208
209 // Load IRF
210 load(rspname);
211
212 // Return
213 return;
214}
215
216
217/***********************************************************************//**
218 * @brief Destructor
219 *
220 * Destroys instance of CTA response object.
221 ***************************************************************************/
223{
224 // Free members
225 free_members();
226
227 // Return
228 return;
229}
230
231
232/*==========================================================================
233 = =
234 = Operators =
235 = =
236 ==========================================================================*/
237
238/***********************************************************************//**
239 * @brief Assignment operator
240 *
241 * @param[in] rsp CTA response.
242 * @return CTA response.
243 *
244 * Assigns CTA response object to another CTA response object. The assignment
245 * performs a deep copy of all information, hence the original object from
246 * which the assignment has been performed can be destroyed after this
247 * operation without any loss of information.
248 ***************************************************************************/
250{
251 // Execute only if object is not identical
252 if (this != &rsp) {
253
254 // Copy base class members
255 this->GCTAResponse::operator=(rsp);
256
257 // Free members
258 free_members();
259
260 // Initialise members
261 init_members();
262
263 // Copy members
264 copy_members(rsp);
265
266 } // endif: object was not identical
267
268 // Return this object
269 return *this;
270}
271
272
273/*==========================================================================
274 = =
275 = Public methods =
276 = =
277 ==========================================================================*/
278
279/***********************************************************************//**
280 * @brief Clear instance
281 *
282 * Clears CTA response object by resetting all members to an initial state.
283 * Any information that was present in the object before will be lost.
284 ***************************************************************************/
286{
287 // Free class members (base and derived classes, derived class first)
288 free_members();
291
292 // Initialise members
295 init_members();
296
297 // Return
298 return;
299}
300
301
302/***********************************************************************//**
303 * @brief Clone instance
304 *
305 * @return Pointer to deep copy of CTA response.
306 *
307 * Creates a clone (deep copy) of a CTA response object.
308 ***************************************************************************/
310{
311 return new GCTAResponseIrf(*this);
312}
313
314
315/***********************************************************************//**
316 * @brief Return value of instrument response function
317 *
318 * @param[in] event Observed event.
319 * @param[in] photon Incident photon.
320 * @param[in] obs Observation.
321 *
322 * @todo Set polar angle phi of photon in camera system
323 ***************************************************************************/
324double GCTAResponseIrf::irf(const GEvent& event,
325 const GPhoton& photon,
326 const GObservation& obs) const
327{
328 // Retrieve CTA pointing and instrument direction
329 const GCTAPointing& pnt = gammalib::cta_pnt(G_IRF, obs);
330 const GCTAInstDir& dir = gammalib::cta_dir(G_IRF, event);
331
332 // Get event attributes
333 const GSkyDir& obsDir = dir.dir();
334 const GEnergy& obsEng = event.energy();
335
336 // Get photon attributes
337 const GSkyDir& srcDir = photon.dir();
338 const GEnergy& srcEng = photon.energy();
339 const GTime& srcTime = photon.time();
340
341 // Get pointing direction zenith angle and azimuth [radians]
342 double zenith = pnt.zenith();
343 double azimuth = pnt.azimuth();
344
345 // Get radial offset and polar angles of true photon in camera [radians]
346 double theta = pnt.dir().dist(srcDir);
347 double phi = 0.0; //TODO: Implement Phi dependence
348
349 // Get log10(E/TeV) of true photon energy.
350 double srcLogEng = srcEng.log10TeV();
351
352 // Determine angular separation between true and measured photon
353 // direction in radians
354 double delta = obsDir.dist(srcDir);
355
356 // Get maximum angular separation for which PSF is significant
357 double delta_max = psf_delta_max(theta, phi, zenith, azimuth, srcLogEng);
358
359 // Initialise IRF value
360 double irf = 0.0;
361
362 // Compute only if we're sufficiently close to PSF
363 if (delta <= delta_max) {
364
365 // Get effective area component
366 irf = aeff(theta, phi, zenith, azimuth, srcLogEng);
367
368 // Multiply-in PSF
369 if (irf > 0.0) {
370
371 // Get PSF component
372 irf *= psf(delta, theta, phi, zenith, azimuth, srcLogEng);
373
374 // Multiply-in energy dispersion
375 if (use_edisp() && irf > 0.0) {
376
377 // Multiply-in energy dispersion
378 irf *= edisp(obsEng, srcEng, theta, phi, zenith, azimuth);
379
380 } // endif: energy dispersion was available and PSF was non-zero
381
382 // Apply deadtime correction
383 irf *= obs.deadc(srcTime);
384
385 } // endif: Aeff was non-zero
386
387 } // endif: we were sufficiently close to PSF
388
389 // Compile option: Check for NaN/Inf
390 #if defined(G_NAN_CHECK)
392 std::cout << "*** ERROR: GCTAResponseIrf::irf:";
393 std::cout << " NaN/Inf encountered";
394 std::cout << " (irf=" << irf;
395 std::cout << ", theta=" << theta;
396 std::cout << ", phi=" << phi << ")";
397 std::cout << std::endl;
398 }
399 #endif
400
401 // Return IRF value
402 return irf;
403}
404
405
406/***********************************************************************//**
407 * @brief Return integral of event probability for a given sky model over ROI
408 *
409 * @param[in] model Sky model.
410 * @param[in] obsEng Observed photon energy.
411 * @param[in] obsTime Observed photon arrival time.
412 * @param[in] obs Observation.
413 * @return Event probability.
414 *
415 * @exception GException::feature_not_implemented
416 * Method is not implemented.
417 *
418 * Computes the integral
419 *
420 * \f[
421 * N_{\rm ROI}(E',t') = \int_{\rm ROI} P(p',E',t') dp'
422 * \f]
423 *
424 * of the event probability
425 *
426 * \f[
427 * P(p',E',t') = \int \int \int
428 * S(p,E,t) \times R(p',E',t'|p,E,t) \, dp \, dE \, dt
429 * \f]
430 *
431 * for a given sky model \f$S(p,E,t)\f$ and response function
432 * \f$R(p',E',t'|p,E,t)\f$ over the Region of Interest (ROI).
433 ***************************************************************************/
435 const GEnergy& obsEng,
436 const GTime& obsTime,
437 const GObservation& obs) const
438{
439 // Set number of iterations for Romberg integration.
440 static const int iter = 6;
441
442 // Initialise Nroi value
443 double nroi = 0.0;
444
445 // No time dispersion supported
446 const GTime& srcTime = obsTime;
447
448 // If energy dispersion is requested then integrate over the relevant
449 // true photon energies ...
450 if (use_edisp()) {
451
452 // Retrieve true energy boundaries
453 GEbounds ebounds = edisp()->etrue_bounds(obsEng);
454
455 // Loop over all boundaries
456 for (int i = 0; i < ebounds.size(); ++i) {
457
458 // Get true energy boundaries in MeV
459 double etrue_min = ebounds.emin(i).MeV();
460 double etrue_max = ebounds.emax(i).MeV();
461
462 // Continue only if valid
463 if (etrue_max > etrue_min) {
464
465 // Setup integration function
466 cta_nroi_kern integrand(this, &obs, &model, srcTime, obsEng, obsTime);
467 GIntegral integral(&integrand);
468
469 // Set fixed number of iterations
470 integral.fixed_iter(iter);
471
472 // Do Romberg integration
473 nroi += integral.romberg(etrue_min, etrue_max);
474
475 } // endif: interval was valid
476
477 } // endfor: looped over energy intervals
478
479 } // endif: energy dispersion requested
480
481 // ... otherwise evaluate
482 else {
483
484 // No energy dispersion
485 const GEnergy& srcEng = obsEng;
486
487 // Compute response components
488 double nroi_spatial = this->nroi(model, srcEng, srcTime, obsEng, obsTime, obs);
489 double nroi_spectral = model.spectral()->eval(srcEng, srcTime);
490 double nroi_temporal = model.temporal()->eval(srcTime);
491
492 // Compute response
493 nroi = nroi_spatial * nroi_spectral * nroi_temporal;
494
495 } // endelse: no energy dispersion requested
496
497 // If required, apply instrument specific model scaling
498 if (model.has_scales()) {
499 nroi *= model.scale(obs.instrument()).value();
500 }
501
502 // Compile option: Check for NaN/Inf
503 #if defined(G_NAN_CHECK)
505 std::cout << "*** ERROR: GCTAResponseIrf::nroi:";
506 std::cout << " NaN/Inf encountered";
507 std::cout << " (nroi=" << nroi;
508 std::cout << ", obsEng=" << obsEng;
509 std::cout << ", obsTime=" << obsTime;
510 std::cout << ")" << std::endl;
511 }
512 #endif
513
514 // Return response value
515 return nroi;
516}
517
518
519/***********************************************************************//**
520 * @brief Return true energy boundaries for a specific observed energy
521 *
522 * @param[in] obsEnergy Observed Energy.
523 * @return True energy boundaries for given observed energy.
524 ***************************************************************************/
526{
527 // Initialise an empty boundary object
529
530 // If energy dispersion is available then set the energy boundaries
531 if (edisp() != NULL) {
532 ebounds = edisp()->etrue_bounds(obsEnergy);
533 }
534
535 // Return energy boundaries
536 return ebounds;
537}
538
539
540/***********************************************************************//**
541 * @brief Simulate event from photon
542 *
543 * @param[in] area Simulation surface area.
544 * @param[in] photon Photon.
545 * @param[in] obs Observation.
546 * @param[in] ran Random number generator.
547 * @return Simulated event.
548 *
549 * Simulates a CTA event using the response function from an incident photon.
550 * If the event is not detected a NULL pointer is returned.
551 *
552 * The method also applies a deadtime correction using a Monte Carlo process,
553 * taking into account temporal deadtime variations. For this purpose, the
554 * method makes use of the time dependent GObservation::deadc method.
555 *
556 * @todo Set polar angle phi of photon in camera system
557 * @todo Implement energy dispersion
558 ***************************************************************************/
560 const GPhoton& photon,
561 const GObservation& obs,
562 GRan& ran) const
563{
564 // Initialise event
565 GCTAEventAtom* event = NULL;
566
567 // Retrieve CTA pointing
568 const GCTAPointing& pnt = gammalib::cta_pnt(G_MC, obs);
569
570 // Get pointing direction zenith angle and azimuth [radians]
571 double zenith = pnt.zenith();
572 double azimuth = pnt.azimuth();
573
574 // Get radial offset and polar angles of true photon in camera [radians]
575 double theta = pnt.dir().dist(photon.dir());
576 double phi = 0.0; //TODO Implement Phi dependence
577
578 // Compute effective area for photon
579 double srcLogEng = photon.energy().log10TeV();
580 double effective_area = aeff(theta, phi, zenith, azimuth, srcLogEng);
581
582 // Compute limiting value
583 double ulimite = effective_area / area;
584
585 // Warning if ulimite is larger than one
586 if (ulimite > 1.0) {
587 std::string msg = "Effective area "+
588 gammalib::str(effective_area)+
589 " cm2 is larger than simulation surface area "+
590 gammalib::str(area)+
591 " cm2 for photon energy "+
592 gammalib::str(photon.energy().TeV())+
593 " TeV. Simulations are inaccurate.";
595 }
596
597 // Continue only if event is detected
598 if ((ulimite > 0.0) && (ran.uniform() <= ulimite)) {
599
600 // Apply deadtime correction
601 double deadc = obs.deadc(photon.time());
602 if (deadc >= 1.0 || ran.uniform() <= deadc) {
603
604 // Simulate PSF and energy dispersion. Skip event if exception
605 // occurs
606 try {
607 // Simulate offset from photon arrival direction.
608 double delta = psf()->mc(ran, srcLogEng, theta, phi, zenith, azimuth) *
610 double alpha = 360.0 * ran.uniform();
611
612 // Rotate sky direction by offset
613 GSkyDir sky_dir = photon.dir();
614 sky_dir.rotate_deg(alpha, delta);
615
616 // Set measured photon arrival direction in instrument direction
617 GCTAInstDir inst_dir = pnt.instdir(sky_dir);
618
619 // Set measured photon energy
620 GEnergy energy = photon.energy();
621 if (use_edisp()) {
622 energy = edisp()->mc(ran, photon.energy(), theta, phi,
623 zenith, azimuth);
624 }
625
626 // Allocate event
627 event = new GCTAEventAtom;
628
629 // Set event attributes
630 event->dir(inst_dir);
631 event->energy(energy);
632 event->time(photon.time());
633
634 } // endtry: PSF and energy dispersion simulation successful
635
636 // ... otherwise catch exception
638 ;
639 }
640
641 } // endif: detector was alive
642
643 } // endif: event was detected
644
645 // Return event
646 return event;
647}
648
649
650/***********************************************************************//**
651 * @brief Read response from XML element
652 *
653 * @param[in] xml XML element.
654 *
655 * Reads information for a CTA observation from an XML element. The
656 * calibration database and response name can be specified using
657 *
658 * <observation name="..." id="..." instrument="...">
659 * ...
660 * <parameter name="Calibration" database="..." response="..."/>
661 * </observation>
662 *
663 * If even more control is required over the response, individual file names
664 * can be specified using
665 *
666 * <observation name="..." id="..." instrument="...">
667 * ...
668 * <parameter name="EffectiveArea" file="..."/>
669 * <parameter name="PointSpreadFunction" file="..."/>
670 * <parameter name="EnergyDispersion" file="..."/>
671 * <parameter name="Background" file="..."/>
672 * </observation>
673 *
674 ***************************************************************************/
676{
677 // First check for "Calibration" parameter
678 if (gammalib::xml_has_par(xml, "Calibration")) {
679
680 // Get parameter
681 const GXmlElement* par = gammalib::xml_get_par(G_READ, xml, "Calibration");
682
683 // Get instrument name in lower case from XML file. If no instrument
684 // was specified then assume CTA
685 std::string instrument = gammalib::tolower(xml.attribute("instrument"));
686 if (instrument.empty()) {
687 instrument = "cta";
688 }
689
690 // Read database and response
691 std::string xml_caldb = gammalib::strip_whitespace(par->attribute("database"));
692 std::string xml_rspname = gammalib::strip_whitespace(par->attribute("response"));
693
694 // Set calibration database
695 GCaldb caldb(instrument, xml_caldb);
696 this->caldb(caldb);
697
698 // Load response
699 this->load(xml_rspname);
700
701 // If there is a sigma attribute then set sigma values of Aeff and
702 // Background
703 if (par->has_attribute("sigma")) {
704
705 // Get sigma value
706 double sigma = gammalib::todouble(par->attribute("sigma"));
707
708 // If we have an effective area performance table then set sigma
709 // value
710 GCTAAeffPerfTable* perf = const_cast<GCTAAeffPerfTable*>(dynamic_cast<const GCTAAeffPerfTable*>(aeff()));
711 if (perf != NULL) {
712 perf->sigma(sigma);
713 }
714
715 // If we have a background performance table then set sigma value
716 GCTABackgroundPerfTable* bgm = const_cast<GCTABackgroundPerfTable*>(dynamic_cast<const GCTABackgroundPerfTable*>(background()));
717 if (bgm != NULL) {
718 bgm->sigma(sigma);
719 }
720
721 } // endif: sigma attribute specified
722
723 // Store database and response names for later writing into the XML
724 // file (we do this now since the load() method clears all
725 // GCTAResponseIrf data members)
726 //m_xml_caldb = xml_caldb;
727 //m_xml_rspname = xml_rspname;
728
729 } // endif: "Calibration" parameter found
730
731 // ... otherwise check for components
732 else {
733
734 // Handle effective area
735 if (gammalib::xml_has_par(xml, "EffectiveArea")) {
736
737 // Get parameter
738 const GXmlElement* par = gammalib::xml_get_par(G_READ, xml, "EffectiveArea");
739
740 // Get filename
741 std::string filename = gammalib::strip_whitespace(par->attribute("file"));
742
743 // If filename is not empty then load effective area
744 if (!filename.empty()) {
745
746 // Load effective area
747 load_aeff(filename);
748
749 // Optional attributes
750 double thetacut = 0.0;
751 double scale = 1.0;
752 double sigma = 3.0;
753
754 // Optionally extract thetacut (0.0 if no thetacut)
755 if (par->has_attribute("thetacut")) {
756 thetacut = gammalib::todouble(par->attribute("thetacut"));
757 }
758
759 // Optionally extract scale factor (1.0 if no scale)
760 if (par->has_attribute("scale")) {
761 scale = gammalib::todouble(par->attribute("scale"));
762 }
763
764 // Optionally extract sigma (3.0 if no sigma)
765 if (par->has_attribute("sigma")) {
766 sigma = gammalib::todouble(par->attribute("sigma"));
767 }
768
769 // If we have an ARF then set attributes
770 GCTAAeffArf* arf = const_cast<GCTAAeffArf*>(dynamic_cast<const GCTAAeffArf*>(aeff()));
771 if (arf != NULL) {
772 arf->thetacut(thetacut);
773 arf->scale(scale);
774 arf->sigma(sigma);
775 }
776
777 // If we have a performance table then set attributes
778 GCTAAeffPerfTable* perf = const_cast<GCTAAeffPerfTable*>(dynamic_cast<const GCTAAeffPerfTable*>(aeff()));
779 if (perf != NULL) {
780 perf->sigma(sigma);
781 }
782
783 } // endif: effective area filename was valid
784
785 } // endif: handled effective area
786
787 // Handle PSF
788 if (gammalib::xml_has_par(xml, "PointSpreadFunction")) {
789
790 // Get parameter
791 const GXmlElement* par = gammalib::xml_get_par(G_READ, xml, "PointSpreadFunction");
792
793 // Get filename
794 std::string filename = gammalib::strip_whitespace(par->attribute("file"));
795
796 // If filename is not empty then load point spread function
797 if (!filename.empty()) {
798 load_psf(filename);
799 }
800
801 } // endif: handled PSF
802
803 // Handle energy dispersion
804 if (gammalib::xml_has_par(xml, "EnergyDispersion")) {
805
806 // Get parameter
807 const GXmlElement* par = gammalib::xml_get_par(G_READ, xml, "EnergyDispersion");
808
809 // Get filename
810 std::string filename = gammalib::strip_whitespace(par->attribute("file"));
811
812 // If filename is not empty then load energy dispersion
813 if (!filename.empty()) {
814 load_edisp(filename);
815 }
816
817 } // endif: handled energy dispersion
818
819 // Handle Background
820 if (gammalib::xml_has_par(xml, "Background")) {
821
822 // Get parameter
823 const GXmlElement* par = gammalib::xml_get_par(G_READ, xml, "Background");
824
825 // Get filename
826 std::string filename = gammalib::strip_whitespace(par->attribute("file"));
827
828 // If filename is not empty then load background model
829 if (!filename.empty()) {
830 load_background(filename);
831 }
832
833 // Optional attributes
834 double sigma = 3.0;
835
836 // Optionally extract sigma (3.0 if no sigma)
837 if (par->has_attribute("sigma")) {
838 sigma = gammalib::todouble(par->attribute("sigma"));
839 }
840
841 // If we have a performance table then set attributes
842 GCTABackgroundPerfTable* bgm = const_cast<GCTABackgroundPerfTable*>(dynamic_cast<const GCTABackgroundPerfTable*>(background()));
843 if (bgm != NULL) {
844 bgm->sigma(sigma);
845 }
846
847 }
848
849 } // endelse: handled components
850
851 // If we have an ARF then remove thetacut if necessary
852 GCTAAeffArf* arf = const_cast<GCTAAeffArf*>(dynamic_cast<const GCTAAeffArf*>(aeff()));
853 if (arf != NULL) {
854 if (arf->thetacut() > 0.0) {
855 arf->remove_thetacut(*this);
856 }
857 }
858
859 // Return
860 return;
861}
862
863
864/***********************************************************************//**
865 * @brief Write response information into XML element
866 *
867 * @param[in] xml XML element.
868 *
869 * Writes information for a CTA response into an XML element. If the
870 * calibration database and response name had been specified, the following
871 * output is written
872 *
873 * <observation name="..." id="..." instrument="...">
874 * ...
875 * <parameter name="Calibration" database="..." response="..."/>
876 * </observation>
877 *
878 * If even more control was required over the response and individual file
879 * names were specified, the following output is written
880 *
881 * <observation name="..." id="..." instrument="...">
882 * ...
883 * <parameter name="EffectiveArea" file="..."/>
884 * <parameter name="PointSpreadFunction" file="..."/>
885 * <parameter name="EnergyDispersion" file="..."/>
886 * <parameter name="Background" file="..."/>
887 * </observation>
888 ***************************************************************************/
890{
891 // Determine number of existing parameter nodes in XML element
892 //int npars = xml.elements("parameter");
893
894 // If we have a calibration database and response name, then set
895 // the information ...
896 if (!m_xml_caldb.empty() || !m_xml_rspname.empty()) {
897 GXmlElement* par = gammalib::xml_need_par(G_WRITE, xml, "Calibration");
898 par->attribute("database", m_xml_caldb);
899 par->attribute("response", m_xml_rspname);
900 }
901
902 // ... otherwise add response components if they exist
903 else {
904
905 // Add effective area if it exists
906 if (aeff() != NULL) {
907
908 // Get effective area filename
909 GFilename filename = aeff()->filename();
910
911 // Continue only if filename is not empty
912 if (!(filename.is_empty())) {
913
914 // Get pointer to effective area
915 GXmlElement* par =
916 gammalib::xml_need_par(G_WRITE, xml, "EffectiveArea");
917
918 // Initialise attributes
919 double thetacut = 0.0;
920 double scale = 1.0;
921 double sigma = 0.0;
922
923 // Get optional ARF attributes
924 const GCTAAeffArf* arf =
925 dynamic_cast<const GCTAAeffArf*>(aeff());
926 if (arf != NULL) {
927 thetacut = arf->thetacut();
928 scale = arf->scale();
929 sigma = arf->sigma();
930 }
931
932 // Get optional performance table attributes
933 const GCTAAeffPerfTable* perf =
934 dynamic_cast<const GCTAAeffPerfTable*>(aeff());
935 if (perf != NULL) {
936 sigma = perf->sigma();
937 }
938
939 // Set attributes
940 par->attribute("file", filename);
941 if (thetacut > 0.0) {
942 par->attribute("thetacut", gammalib::str(thetacut));
943 }
944 if (scale != 1.0) {
945 par->attribute("scale", gammalib::str(scale));
946 }
947 if (sigma > 0.0) {
948 par->attribute("sigma", gammalib::str(sigma));
949 }
950
951 }
952 }
953
954 // Add PSF if it exists
955 if (psf() != NULL) {
956 if (!(psf()->filename().is_empty())) {
957 GXmlElement* par =
958 gammalib::xml_need_par(G_WRITE, xml, "PointSpreadFunction");
959 par->attribute("file", psf()->filename());
960 }
961 }
962
963 // Add Edisp if it exists
964 if (edisp() != NULL) {
965 if (!(edisp()->filename().is_empty())) {
966 GXmlElement* par =
967 gammalib::xml_need_par(G_WRITE, xml, "EnergyDispersion");
968 par->attribute("file", edisp()->filename());
969 }
970 }
971
972 // Add background if it exists
973 if (background() != NULL) {
974 if (!(background()->filename().is_empty())) {
975 GXmlElement* par =
976 gammalib::xml_need_par(G_WRITE, xml, "Background");
977 par->attribute("file", background()->filename());
978 }
979 }
980
981 } // endelse: response components added
982
983 // Return
984 return;
985}
986
987
988/***********************************************************************//**
989 * @brief Load CTA response
990 *
991 * @param[in] rspname CTA response name.
992 *
993 * Loads the CTA response with specified name @p rspname. The method first
994 * searchs for an appropriate response in the calibration database. If no
995 * appropriate response is found, the method takes the database root path
996 * and response name to build the full path to the response file, and tries
997 * to load the response from these paths.
998 *
999 * The method sets the calibration database and response names for writing
1000 * of the response information into the XML file.
1001 ***************************************************************************/
1002void GCTAResponseIrf::load(const std::string& rspname)
1003{
1004 // Clear instance but conserve calibration database
1006 clear();
1007 m_caldb = caldb;
1008
1009 // First attempt reading the response using the GCaldb interface
1010 std::string expr = "NAME("+rspname+")";
1011 GFilename aeffname = m_caldb.filename("","","EFF_AREA","","",expr);
1012 GFilename psfname = m_caldb.filename("","","RPSF","","",expr);
1013 GFilename edispname = m_caldb.filename("","","EDISP","","",expr);
1014 GFilename bgdname = m_caldb.filename("","","BKG","","",expr);
1015
1016 // Kluge: if the background filename is empty it may be because we have
1017 // and old response file that used "BGD" as the name of the background
1018 // extension. So we try to get here the old name
1019 if (bgdname.is_empty()) {
1020 bgdname = m_caldb.filename("","","BGD","","",expr);
1021 }
1022
1023 // Signal usage of calibration database
1024 bool use_caldb = true;
1025
1026 // If filenames are empty then build filenames from CALDB root path and
1027 // response name
1028 if (aeffname.is_empty()) {
1030 use_caldb = false;
1031 }
1032 if (psfname.is_empty()) {
1034 use_caldb = false;
1035 }
1036 if (edispname.is_empty()) {
1038 use_caldb = false;
1039 }
1040 if (bgdname.is_empty()) {
1042 use_caldb = false;
1043 }
1044
1045 // Load effective area
1046 load_aeff(aeffname);
1047
1048 // Load point spread function
1049 load_psf(psfname);
1050
1051 // Load energy dispersion
1052 load_edisp(edispname);
1053
1054 // Load background
1055 load_background(bgdname);
1056
1057 // Remove theta cut
1058 GCTAAeffArf* arf = const_cast<GCTAAeffArf*>(dynamic_cast<const GCTAAeffArf*>(m_aeff));
1059 if (arf != NULL) {
1060 arf->remove_thetacut(*this);
1061 }
1062
1063 // Store response name
1065
1066 // If the calibration database has been used then store database and
1067 // response names for later writing into the XML file
1068 if (use_caldb) {
1071 }
1072
1073 // Return
1074 return;
1075}
1076
1077
1078/***********************************************************************//**
1079 * @brief Load effective area
1080 *
1081 * @param[in] filename Effective area filename.
1082 *
1083 * @exception GException::file_error
1084 * File or extension not found.
1085 *
1086 * Loads the effective area from a response file.
1087 *
1088 * If the file is a FITS file, the method will either use the extension name
1089 * specified with the filename, or if no extension name is given, search for
1090 * an `EFFECTIVE AREA` or `SPECRESP` extension in the file and open the
1091 * corresponding FITS table. If a column named `SPECRESP` is found in the
1092 * table, a GCTAAeffArf object will be allocated. Otherwise, a GCTAAeff2D
1093 * object will be allocated. In both cases, the method will extract the
1094 * optional `LO_THRES` and `HI_THRES` safe energy thresholds from the FITS
1095 * file header.
1096 *
1097 * If the file is not a FITS file, it will be interpreted as a
1098 * GCTAAeffPerfTable performance table.
1099 ***************************************************************************/
1101{
1102 // Free any existing effective area instance
1103 if (m_aeff != NULL) delete m_aeff;
1104 m_aeff = NULL;
1105
1106 // Check for existence of file
1107 if (!filename.exists()) {
1108 std::string msg = "File \""+filename+"\" not found. Please specify "
1109 "a valid effective area response file.";
1111 }
1112
1113 // If file is a FITS file ...
1114 if (filename.is_fits()) {
1115
1116 // Open FITS file
1117 GFits fits(filename);
1118
1119 // Get the extension name. If an extension name has been specified
1120 // then use this name, otherwise preset the extension name according
1121 // to the GADF specifications. If no GADF compliant name was found,
1122 // search either for "EFFECTIVE AREA" or the "SPECRESP" extension.
1123 std::string extname = gammalib::gadf_hduclas4(fits, "AEFF_2D");
1124 if (filename.has_extname()) {
1125 extname = filename.extname();
1126 }
1127 if (extname.empty()) {
1130 }
1131 else if (fits.contains(gammalib::extname_arf)) {
1132 extname = gammalib::extname_arf;
1133 }
1134 }
1135
1136 // Continue only if extension name is not empty
1137 if (!extname.empty()) {
1138
1139 // Get FITS table
1140 const GFitsTable& table = *fits.table(extname);
1141
1142 // Read safe energy thresholds if available
1143 if (table.has_card("LO_THRES")) {
1144 m_lo_safe_thres = table.real("LO_THRES");
1145 }
1146 if (table.has_card("HI_THRES")) {
1147 m_hi_safe_thres = table.real("HI_THRES");
1148 }
1149
1150 // Check for specific table column
1151 if (table.contains(gammalib::extname_arf)) {
1152
1153 // Close file
1154 fits.close();
1155
1156 // Allocate Aeff from file
1157 m_aeff = new GCTAAeffArf(filename);
1158
1159 } // endif: load as GCTAAeffArf
1160
1161 else {
1162
1163 // Close file
1164 fits.close();
1165
1166 // Allocate Aeff from file
1167 m_aeff = new GCTAAeff2D(filename);
1168
1169 } // endelse: load as GCTAAeff2D
1170
1171 } // endif: extension name is not empty
1172
1173 // Signal that no extension was found
1174 else {
1175 std::string msg = "FITS file \""+filename+"\" does not "
1176 "contain a valid effective area table. "
1177 "Please specify a valid effective area "
1178 "response file.";
1180 }
1181
1182 } // endif: file was FITS file
1183
1184 // ... else try handling an ASCII performance table
1185 else {
1186
1187 // Allocate a performance table
1188 m_aeff = new GCTAAeffPerfTable(filename);
1189
1190 } // endif: load as GCTAAeffPerfTable
1191
1192 // Return
1193 return;
1194}
1195
1196
1197/***********************************************************************//**
1198 * @brief Load CTA PSF vector
1199 *
1200 * @param[in] filename FITS file name.
1201 *
1202 * @exception GException::file_error
1203 * File or extension not found.
1204 *
1205 * Loads the point spead function from a response file.
1206 *
1207 * If the file is a FITS file, the method will either use the extension name
1208 * specified with the filename, or if no extension name is given, search for
1209 * one of the following extension names
1210 *
1211 * POINT SPREAD FUNCTION
1212 * PSF
1213 * PSF_2D_TABLE
1214 *
1215 * in the file and open the corresponding FITS table. If columns named `GAMMA`
1216 * and `SIGMA` are found in the table, a GCTAPsfKing object will be allocated.
1217 *
1218 * If columns named `SCALE`, `SIGMA_1`, `AMPL_2`, `SIGMA_2`, `AMPL_3` and
1219 * `SIGMA_3` are found in the table, a GCTAPsf2D object will be allocated.
1220 *
1221 * If columns named `RAD_LO`, `RAD_HI`, and `RPSF` are found in the table, a
1222 * GCTAPsfTable object will be allocated.
1223 *
1224 * Otherwise, a CTAPsfVector object will be allocated.
1225 *
1226 * If the file is not a FITS file, it will be interpreted as a
1227 * GCTAPsfPerfTable performance table.
1228 ***************************************************************************/
1230{
1231 // Free any existing point spread function instance
1232 if (m_psf != NULL) delete m_psf;
1233 m_psf = NULL;
1234
1235 // Check for existence of file
1236 if (!filename.exists()) {
1237 std::string msg = "File \""+filename+"\" not found. Please specify "
1238 "a valid point spread function response file.";
1240 }
1241
1242 // If file is a FITS file ...
1243 if (filename.is_fits()) {
1244
1245 // Open FITS file
1246 GFits fits(filename);
1247
1248 // Get the extension name. If an extension name has been specified
1249 // then use this name, otherwise preset the extension name according
1250 // to the GADF specifications. If no GADF compliant name was found,
1251 // search either for "POINT SPREAD FUNCTION" or the "PSF" extension.
1252 std::string extname = gammalib::gadf_hduclas4(fits, "PSF_TABLE");
1253 if (filename.has_extname()) {
1254 extname = filename.extname();
1255 }
1256 if (extname.empty()) {
1257 if (fits.contains("POINT SPREAD FUNCTION")) {
1258 extname = "POINT SPREAD FUNCTION";
1259 }
1260 else if (fits.contains("PSF")) {
1261 extname = "PSF";
1262 }
1263 }
1264
1265 // Continue only if extension name is not empty
1266 if (!extname.empty()) {
1267
1268 // Get FITS table
1269 const GFitsTable& table = *fits.table(extname);
1270
1271 // Check for King profile specific table columns
1272 if (table.contains("GAMMA") && table.contains("SIGMA")) {
1273
1274 // Close FITS file
1275 fits.close();
1276
1277 // Allocate King profile PSF
1278 m_psf = new GCTAPsfKing(filename);
1279
1280 }
1281
1282 // ... otherwise check for Gaussian profile specific table
1283 // columns
1284 else if (table.contains("SCALE") && table.contains("SIGMA_1") &&
1285 table.contains("AMPL_2") && table.contains("SIGMA_2") &&
1286 table.contains("AMPL_3") && table.contains("SIGMA_3")) {
1287
1288 // Close FITS file
1289 fits.close();
1290
1291 // Allocate Gaussian profile PSF
1292 m_psf = new GCTAPsf2D(filename);
1293
1294 }
1295
1296 // ... otherwise check for PSF table specific table columns
1297 else if (table.contains("RAD_LO") && table.contains("RAD_HI") &&
1298 table.contains("RPSF")) {
1299
1300 // Close FITS file
1301 fits.close();
1302
1303 // Allocate PSF table
1304 m_psf = new GCTAPsfTable(filename);
1305
1306 }
1307
1308 // ... otherwise try opening as vector PSF
1309 else {
1310
1311 // Close FITS file
1312 fits.close();
1313
1314 // Allocate vector PSF
1315 m_psf = new GCTAPsfVector(filename);
1316
1317 }
1318
1319 } // endif: extension name is not empty
1320
1321 // Signal that no extension was found
1322 else {
1323 std::string msg = "FITS file \""+filename+"\" does not "
1324 "contain a valid point spread function table. "
1325 "Please specify a valid point spread function "
1326 "response file.";
1328 }
1329
1330 } // endif: file was FITS file
1331
1332 // ... otherwise load file as a performance table
1333 else {
1334
1335 // Allocate a performance table
1336 m_psf = new GCTAPsfPerfTable(filename);
1337
1338 }
1339
1340 // Return
1341 return;
1342}
1343
1344
1345/***********************************************************************//**
1346 * @brief Load energy dispersion information
1347 *
1348 * @param[in] filename Energy dispersion file name.
1349 *
1350 * @exception GException::file_error
1351 * File or extension not found.
1352 *
1353 * Loads the energy dispersion from a response file.
1354 *
1355 * If the file is a FITS file, the method will either use the extension name
1356 * specified with the filename, or if no extension name is given, search for
1357 * an `ENERGY DISPERSION` or `MATRIX` extension in the file and open the
1358 * corresponding FITS table. If columns named "MIGRA_LO" and "MIGRA_HI" are
1359 * found in the table, a GCTAEdisp2D object will be allocated. Otherwise, a
1360 * GCTAEdispRmf object will be allocated.
1361 *
1362 * If the file is not a FITS file, it will be interpreted as a
1363 * GCTAEdispPerfTable performance table.
1364 ***************************************************************************/
1366{
1367 // Free any existing energy dispersion instance
1368 if (m_edisp != NULL) delete m_edisp;
1369 m_edisp = NULL;
1370
1371 // Check for existence of file
1372 if (!filename.exists()) {
1373 std::string msg = "File \""+filename+"\" not found. Please specify "
1374 "a valid energy dispersion response file.";
1376 }
1377
1378 // If file is a FITS file ...
1379 if (filename.is_fits()) {
1380
1381 // Open FITS file
1382 GFits fits(filename);
1383
1384 // Get the extension name. If an extension name has been specified
1385 // then use this name, otherwise preset the extension name according
1386 // to the GADF specifications. If no GADF compliant name was found,
1387 // search either for "ENERGY DISPERSION" or the "MATRIX" extension.
1388 std::string extname = gammalib::gadf_hduclas4(fits, "EDISP_2D");
1389 if (filename.has_extname()) {
1390 extname = filename.extname();
1391 }
1392 if (extname.empty()) {
1395 }
1396 else if (fits.contains(gammalib::extname_rmf)) {
1397 extname = gammalib::extname_rmf;
1398 }
1399 }
1400
1401 // Continue only if extension name is not empty
1402 if (!extname.empty()) {
1403
1404 // Get FITS table
1405 const GFitsTable& table = *fits.table(extname);
1406
1407 // Check for 2D migration matrix
1408 if (table.contains("MIGRA_LO") && table.contains("MIGRA_HI")) {
1409
1410 // Close FITS file
1411 fits.close();
1412
1413 // Allocate 2D migration matrix
1414 m_edisp = new GCTAEdisp2D(filename);
1415
1416 }
1417
1418 // ... otherwise allocate RMF
1419 else {
1420
1421 // Close FITS file
1422 fits.close();
1423
1424 // Allocate Gaussian profile PSF
1425 m_edisp = new GCTAEdispRmf(filename);
1426
1427 }
1428
1429 } // endif: extension name is not empty
1430
1431 // Signal that no extension was found
1432 else {
1433 std::string msg = "FITS file \""+filename+"\" does not "
1434 "contain a valid energy dispersion table. "
1435 "Please specify a valid energy dispersion "
1436 "response file.";
1438 }
1439
1440 } // endif: file was FITS file
1441
1442 // ... otherwise load file as a performance table
1443 else {
1444
1445 // Allocate a performance table
1446 m_edisp = new GCTAEdispPerfTable(filename);
1447
1448 }
1449
1450 // Return
1451 return;
1452}
1453
1454
1455/***********************************************************************//**
1456 * @brief Load background model
1457 *
1458 * @param[in] filename Background model file name.
1459 *
1460 * @exception GException::file_error
1461 * File not found.
1462 *
1463 * Loads the background model from a response file.
1464 *
1465 * If the file is a FITS file the method will check whether the file contains
1466 * a BKG_2D or BKG_3D table, and load correspondingly a GCTABackground2D
1467 * or GCTABackground3D response.
1468 *
1469 * If the file is not a FITS file it will be interpreted as a
1470 * GCTABackgroundPerfTable performance table.
1471 ***************************************************************************/
1473{
1474 // Free any existing background model instance
1475 if (m_background != NULL) delete m_background;
1476 m_background = NULL;
1477
1478 // Check for existence of file
1479 if (!filename.exists()) {
1480 std::string msg = "File \""+filename+"\" not found. Please specify "
1481 "a valid background response file.";
1483 }
1484
1485 // If file is a FITS file than load background as 3D background
1486 if (filename.is_fits()) {
1487
1488 // Open FITS file
1489 GFits fits(filename);
1490
1491 // Get the extension name. If an extension name has been specified
1492 // then use this name, otherwise preset the extension name according
1493 // to the GADF specifications. If no GADF compliant name was found,
1494 // search either for "BACKGROUND" or the "BKG" extension.
1495 std::string extname;
1496 if (filename.has_extname()) {
1497 extname = filename.extname();
1498 }
1499 if (extname.empty()) {
1502 }
1505 }
1506 }
1507
1508 // Initialise pointer to FITS table
1509 const GFitsTable* table = NULL;
1510
1511 // If an extension name is given then get the respective FITS table,
1512 // otherwise get the first FITS table
1513 if (!extname.empty()) {
1514 table = fits.table(extname);
1515 }
1516 else {
1517 table = fits.table(1);
1518 }
1519
1520 // If THETA_LO and THETA_HI columns are present then allocate a
1521 // 2D background model
1522 if (table->contains("THETA_LO") && table->contains("THETA_HI")) {
1523
1524 // Close FITS file
1525 fits.close();
1526
1527 // Allocate 2D background model
1528 m_background = new GCTABackground2D(filename);
1529
1530 }
1531
1532 // ... otherwise allocate 3D background model
1533 else {
1534
1535 // Close FITS file
1536 fits.close();
1537
1538 // Allocate Gaussian profile PSF
1539 m_background = new GCTABackground3D(filename);
1540
1541 }
1542
1543 } // endif: file was a FITS file
1544
1545 // ... otherwise load background as performance table
1546 else {
1547 m_background = new GCTABackgroundPerfTable(filename);
1548 }
1549
1550 // Return
1551 return;
1552}
1553
1554
1555/***********************************************************************//**
1556 * @brief Set offset angle dependence (degrees)
1557 *
1558 * @param[in] sigma Offset angle dependence value (degrees).
1559 *
1560 * Set the offset angle dependence for 1D effective area functions. The
1561 * method set the sigma value in case that the effective area function
1562 * is of type GCTAAeffArf or GCTAAeffPerfTable. Otherwise, nothing will
1563 * be done.
1564 ***************************************************************************/
1565void GCTAResponseIrf::offset_sigma(const double& sigma)
1566{
1567 // If effective area is an ARF then set offset angle
1568 GCTAAeffArf* arf = dynamic_cast<GCTAAeffArf*>(m_aeff);
1569 if (arf != NULL) {
1570 arf->sigma(sigma);
1571 }
1572
1573 // If effective area is a performance table then set offset angle
1574 GCTAAeffPerfTable* prf = dynamic_cast<GCTAAeffPerfTable*>(m_aeff);
1575 if (prf != NULL) {
1576 prf->sigma(sigma);
1577 }
1578
1579 // Return
1580 return;
1581}
1582
1583
1584/***********************************************************************//**
1585 * @brief Return offset angle dependence (degrees)
1586 *
1587 * @return Offset angle dependence value (degrees).
1588 *
1589 * Return the offset angle dependence for 1D effective area functions. The
1590 * method returns the sigma value in case that the effective area function
1591 * is of type GCTAAeffArf or GCTAAeffPerfTable. Otherwise, 0.0 will be
1592 * returned.
1593 ***************************************************************************/
1595{
1596 // Initialise value
1597 double sigma = 0.0;
1598
1599 // If effective area is an ARF then get offset angle
1600 GCTAAeffArf* arf = dynamic_cast<GCTAAeffArf*>(m_aeff);
1601 if (arf != NULL) {
1602 sigma = arf->sigma();
1603 }
1604
1605 // If effective area is a performance table then get offset angle
1606 GCTAAeffPerfTable* prf = dynamic_cast<GCTAAeffPerfTable*>(m_aeff);
1607 if (prf != NULL) {
1608 sigma = prf->sigma();
1609 }
1610
1611 // Return sigma
1612 return sigma;
1613}
1614
1615
1616/***********************************************************************//**
1617 * @brief Print CTA response information
1618 *
1619 * @param[in] chatter Chattiness.
1620 * @return String containing CTA response information.
1621 ***************************************************************************/
1622std::string GCTAResponseIrf::print(const GChatter& chatter) const
1623{
1624 // Initialise result string
1625 std::string result;
1626
1627 // Continue only if chatter is not silent
1628 if (chatter != SILENT) {
1629
1630 // Append header
1631 result.append("=== GCTAResponseIrf ===");
1632
1633 // Append response information
1634 result.append("\n"+gammalib::parformat("Caldb mission")+m_caldb.mission());
1635 result.append("\n"+gammalib::parformat("Caldb instrument")+m_caldb.instrument());
1636 result.append("\n"+gammalib::parformat("Response name")+m_rspname);
1637 result.append("\n"+gammalib::parformat("Energy dispersion"));
1638 if (use_edisp()) {
1639 result.append("Used");
1640 }
1641 else {
1642 if (apply_edisp()) {
1643 result.append("Not available");
1644 }
1645 else {
1646 result.append("Not used");
1647 }
1648 }
1649
1650 // Append safe energy threshold information
1651 result.append("\n"+gammalib::parformat("Safe energy range"));
1652 if (m_lo_safe_thres > 0.0 && m_hi_safe_thres) {
1653 result.append(gammalib::str(m_lo_safe_thres));
1654 result.append(" - ");
1655 result.append(gammalib::str(m_hi_safe_thres));
1656 result.append(" TeV");
1657 }
1658 else if (m_lo_safe_thres > 0.0) {
1659 result.append("> ");
1660 result.append(gammalib::str(m_lo_safe_thres));
1661 result.append(" TeV");
1662 }
1663 else if (m_hi_safe_thres > 0.0) {
1664 result.append("< ");
1665 result.append(gammalib::str(m_hi_safe_thres));
1666 result.append(" TeV");
1667 }
1668 else {
1669 result.append("undefined");
1670 }
1671
1672 // Get reduced chatter level
1673 GChatter reduced_chatter = gammalib::reduce(chatter);
1674
1675 // Append detailed information
1676 if (chatter >= NORMAL) {
1677
1678 // Append calibration database
1679 result.append("\n"+m_caldb.print(reduced_chatter));
1680
1681 // Append effective area information
1682 if (m_aeff != NULL) {
1683 result.append("\n"+m_aeff->print(reduced_chatter));
1684 }
1685
1686 // Append point spread function information
1687 if (m_psf != NULL) {
1688 result.append("\n"+m_psf->print(reduced_chatter));
1689 }
1690
1691 // Append energy dispersion information
1692 if (m_edisp != NULL) {
1693 result.append("\n"+m_edisp->print(reduced_chatter));
1694 }
1695
1696 // Append background information
1697 if (m_background != NULL) {
1698 result.append("\n"+m_background->print(reduced_chatter));
1699 }
1700
1701 // Append cache information
1702 result.append("\n"+m_irf_cache.print(reduced_chatter));
1703 result.append("\n"+m_nroi_cache.print(reduced_chatter));
1704
1705 } // endif: appended detailed information
1706
1707 } // endif: chatter was not silent
1708
1709 // Return result
1710 return result;
1711}
1712
1713
1714/*==========================================================================
1715 = =
1716 = Low-level CTA response methods =
1717 = =
1718 ==========================================================================*/
1719
1720/***********************************************************************//**
1721 * @brief Return effective area (in units of cm2)
1722 *
1723 * @param[in] theta Radial offset angle of photon in camera (radians).
1724 * @param[in] phi Polar angle of photon in camera (radians).
1725 * @param[in] zenith Zenith angle of telescope pointing (radians).
1726 * @param[in] azimuth Azimuth angle of telescope pointing (radians).
1727 * @param[in] srcLogEng Log10 of true photon energy (E/TeV).
1728 * @return Effective area in units fo cm2.
1729 *
1730 * @exception GException::invalid_value
1731 * No effective area information found.
1732 *
1733 * Returns the effective area as function of the true photon position in the
1734 * camera system and the telescope pointing direction in the Earth system.
1735 ***************************************************************************/
1736double GCTAResponseIrf::aeff(const double& theta,
1737 const double& phi,
1738 const double& zenith,
1739 const double& azimuth,
1740 const double& srcLogEng) const
1741{
1742 // Throw an exception if instrument response is not defined
1743 if (m_aeff == NULL) {
1744 std::string msg = "No effective area information found in response.\n"
1745 "Please make sure that the instrument response is"
1746 " properly defined.";
1748 }
1749
1750 // Get effective area
1751 double aeff = (*m_aeff)(srcLogEng, theta, phi, zenith, azimuth);
1752
1753 // Return effective area
1754 return aeff;
1755}
1756
1757
1758/***********************************************************************//**
1759 * @brief Return point spread function (in units of sr^-1)
1760 *
1761 * @param[in] delta Angular separation between true and measured photon
1762 * directions (radians).
1763 * @param[in] theta Radial offset angle of photon in camera (radians).
1764 * @param[in] phi Polar angle of photon in camera (radians).
1765 * @param[in] zenith Zenith angle of telescope pointing (radians).
1766 * @param[in] azimuth Azimuth angle of telescope pointing (radians).
1767 * @param[in] srcLogEng Log10 of true photon energy (E/TeV).
1768 *
1769 * @exception GException::invalid_value
1770 * No point spread function information found.
1771 *
1772 * Returns the point spread function for a given offset angle as function
1773 * of the true photon position in the camera system and the telescope
1774 * pointing direction in the Earth system.
1775 ***************************************************************************/
1776double GCTAResponseIrf::psf(const double& delta,
1777 const double& theta,
1778 const double& phi,
1779 const double& zenith,
1780 const double& azimuth,
1781 const double& srcLogEng) const
1782{
1783 // Throw an exception if instrument response is not defined
1784 if (m_psf == NULL) {
1785 std::string msg = "No point spread function information found in"
1786 " response.\n"
1787 "Please make sure that the instrument response is"
1788 " properly defined.";
1789 throw GException::invalid_value(G_PSF, msg);
1790 }
1791
1792 // Compute PSF
1793 double psf = (*m_psf)(delta, srcLogEng, theta, phi, zenith, azimuth);
1794
1795 // Return PSF
1796 return psf;
1797}
1798
1799
1800/***********************************************************************//**
1801 * @brief Return maximum angular separation (in radians)
1802 *
1803 * @param[in] theta Radial offset angle in camera (radians).
1804 * @param[in] phi Polar angle in camera (radians).
1805 * @param[in] zenith Zenith angle of telescope pointing (radians).
1806 * @param[in] azimuth Azimuth angle of telescope pointing (radians).
1807 * @param[in] srcLogEng Log10 of true photon energy (E/TeV).
1808 *
1809 * @exception GException::invalid_value
1810 * No point spread function information found.
1811 *
1812 * This method returns the maximum angular separation between true and
1813 * measured photon directions for which the PSF is non zero as function
1814 * of the true photon position in the camera system and the telescope
1815 * pointing direction in the Earth system.
1816 ***************************************************************************/
1817double GCTAResponseIrf::psf_delta_max(const double& theta,
1818 const double& phi,
1819 const double& zenith,
1820 const double& azimuth,
1821 const double& srcLogEng) const
1822{
1823 // Throw an exception if instrument response is not defined
1824 if (m_psf == NULL) {
1825 std::string msg = "No point spread function information found in"
1826 " response.\n"
1827 "Please make sure that the instrument response is"
1828 " properly defined.";
1830 }
1831
1832 // Compute PSF
1833 double delta_max = m_psf->delta_max(srcLogEng, theta, phi, zenith, azimuth);
1834
1835 // Return PSF
1836 return delta_max;
1837}
1838
1839
1840/***********************************************************************//**
1841 * @brief Return energy dispersion (in units of MeV\f$^{-1}\f$)
1842 *
1843 * @param[in] ereco Reconstructed event energy.
1844 * @param[in] etrue True photon energy.
1845 * @param[in] theta Radial offset angle in camera (radians).
1846 * @param[in] phi Polar angle in camera (radians).
1847 * @param[in] zenith Zenith angle of telescope pointing (radians).
1848 * @param[in] azimuth Azimuth angle of telescope pointing (radians).
1849 * @return Energy dispersion (MeV\f$^{-1}\f$).
1850 ***************************************************************************/
1852 const GEnergy& etrue,
1853 const double& theta,
1854 const double& phi,
1855 const double& zenith,
1856 const double& azimuth) const
1857{
1858 // Compute energy dispersion
1859 double edisp = (*m_edisp)(ereco, etrue, theta, phi, zenith, azimuth);
1860
1861 // Return energy dispersion
1862 return edisp;
1863}
1864
1865
1866/***********************************************************************//**
1867 * @brief Return spatial integral of sky model
1868 *
1869 * @param[in] model Sky Model.
1870 * @param[in] srcEng True photon energy.
1871 * @param[in] srcTime True photon arrival time.
1872 * @param[in] obsEng Observed event energy.
1873 * @param[in] obsTime Observed event arrival time.
1874 * @param[in] obs Observation.
1875 *
1876 * Computes the integral
1877 *
1878 * \f[
1879 * N_{\rm ROI}(E',t'|E,t) = \int_{\rm ROI} P(p',E',t'|E,t) dp'
1880 * \f]
1881 *
1882 * of
1883 *
1884 * \f[
1885 * P(p',E',t'|E,t) = \int
1886 * S(p,E,t) \times R(p',E',t'|p,E,t) \, dp
1887 * \f]
1888 *
1889 * over the Region of Interest (ROI) for a sky model \f$S(p,E,t)\f$ and the
1890 * response function \f$R(p',E',t'|p,E,t)\f$.
1891 ***************************************************************************/
1893 const GEnergy& srcEng,
1894 const GTime& srcTime,
1895 const GEnergy& obsEng,
1896 const GTime& obsTime,
1897 const GObservation& obs) const
1898{
1899 // Initialise response value
1900 double nroi = 0.0;
1901
1902 // Set response value attributes
1903 std::string name = obs.id() + "::" + model.name();
1904
1905 // Signal if spatial model has free parameters
1906 bool has_free_pars = model.spatial()->has_free_pars();
1907
1908 // If the spatial model component has free parameters, or the response
1909 // cache should not be used, or the cache does not contain the requested
1910 // IRF value then compute the IRF value for the spatial model.
1911 if (has_free_pars ||
1913 !m_nroi_cache.contains(name, obsEng, srcEng, &nroi)) {
1914
1915 // Select method depending on the spatial model type
1916 switch (model.spatial()->code()) {
1918 nroi = nroi_ptsrc(model, srcEng, srcTime, obsEng, obsTime, obs);
1919 break;
1921 nroi = nroi_radial(model, srcEng, srcTime, obsEng, obsTime, obs);
1922 break;
1924 nroi = nroi_elliptical(model, srcEng, srcTime, obsEng, obsTime, obs);
1925 break;
1927 nroi = nroi_diffuse(model, srcEng, srcTime, obsEng, obsTime, obs);
1928 break;
1930 nroi = nroi_composite(model, srcEng, srcTime, obsEng, obsTime, obs);
1931 break;
1932 default:
1933 break;
1934 }
1935
1936 } // endif: computed spatial model
1937
1938 // If the spatial model has no free parameters and the response cache
1939 // should be used then put the IRF value in the response cache.
1940 if (!has_free_pars && m_use_nroi_cache) {
1941 m_nroi_cache.set(name, obsEng, srcEng, nroi);
1942 }
1943
1944 // Return response value
1945 return nroi;
1946}
1947
1948
1949/***********************************************************************//**
1950 * @brief Return spatial integral of Instrument Response Function
1951 *
1952 * @param[in] photon Photon.
1953 * @param[in] obsEng Observed event energy.
1954 * @param[in] obsTime Observed event time.
1955 * @param[in] obs Observation.
1956 *
1957 * Computes the integral of the instrument response function over the Region
1958 * of Interest
1959 *
1960 * \f[
1961 * R(E',t'|p,E,t) = \int_{\rm ROI} R(p',E',t'|p,E,t) dp'
1962 * \f]
1963 ***************************************************************************/
1964double GCTAResponseIrf::nirf(const GPhoton& photon,
1965 const GEnergy& obsEng,
1966 const GTime& obsTime,
1967 const GObservation& obs) const
1968{
1969 // Retrieve CTA observation, ROI and pointing
1970 const GCTAObservation& cta = gammalib::cta_obs(G_NIRF, obs);
1971 const GCTARoi& roi = gammalib::cta_event_list(G_NIRF, obs).roi();
1972 const GCTAPointing& pnt = cta.pointing();
1973
1974 // Get photon attributes
1975 const GSkyDir& srcDir = photon.dir();
1976 const GEnergy& srcEng = photon.energy();
1977 const GTime& srcTime = photon.time();
1978
1979 // Get pointing direction zenith angle and azimuth [radians]
1980 double zenith = pnt.zenith();
1981 double azimuth = pnt.azimuth();
1982
1983 // Get radial offset and polar angles of true photon in camera [radians]
1984 double theta = pnt.dir().dist(srcDir);
1985 double phi = 0.0; //TODO: Implement Phi dependence
1986
1987 // Get log10(E/TeV) of true photon energy.
1988 double srcLogEng = srcEng.log10TeV();
1989
1990 // Get effectve area components
1991 double nroi = aeff(theta, phi, zenith, azimuth, srcLogEng);
1992
1993 // Multiply-in PSF
1994 if (nroi > 0.0) {
1995
1996 // Get PSF
1997 nroi *= npsf(srcDir, srcLogEng, srcTime, pnt, roi);
1998
1999 // Multiply-in energy dispersion
2000 if (use_edisp() && nroi > 0.0) {
2001
2002 // Multiply-in energy dispersion
2003 nroi *= edisp(obsEng, srcEng, theta, phi, zenith, azimuth);
2004
2005 } // endif: had energy dispersion
2006
2007 // Apply deadtime correction
2008 nroi *= obs.deadc(srcTime);
2009
2010 } // endif: had non-zero effective area
2011
2012 // Compile option: Check for NaN/Inf
2013 #if defined(G_NAN_CHECK)
2015 std::cout << "*** ERROR: GCTAResponseIrf::nroi:";
2016 std::cout << " NaN/Inf encountered";
2017 std::cout << " (nroi=" << nroi;
2018 std::cout << ", theta=" << theta;
2019 std::cout << ", phi=" << phi << ")";
2020 std::cout << std::endl;
2021 }
2022 #endif
2023
2024 // Return Nroi
2025 return nroi;
2026}
2027
2028
2029/***********************************************************************//**
2030 * @brief Return result of PSF integration over ROI.
2031 *
2032 * @param[in] srcDir True photon direction.
2033 * @param[in] srcLogEng Log10 of true photon energy (E/TeV).
2034 * @param[in] srcTime True photon arrival time (not used).
2035 * @param[in] pnt CTA pointing.
2036 * @param[in] roi CTA region of interest.
2037 *
2038 * This method integrates the PSF over the circular region of interest (ROI).
2039 * Integration is done in a polar coordinate system centred on the PSF since
2040 * the PSF is assumed to be azimuthally symmetric. The polar integration is
2041 * done using the method npsf_kern_rad_azsym() that computes analytically
2042 * the arclength that is comprised within the ROI.
2043 *
2044 * Note that the integration is only performed when the PSF is spilling out
2045 * of the ROI border, otherwise the integral is simply 1. Numerical
2046 * integration is done using the standard Romberg method. The integration
2047 * boundaries are computed so that only the PSF section that falls in the ROI
2048 * is considered.
2049 *
2050 * @todo Enhance romberg() integration method for small integration regions
2051 * (see comment about kluge below)
2052 * @todo Implement phi dependence in camera system
2053 ***************************************************************************/
2054double GCTAResponseIrf::npsf(const GSkyDir& srcDir,
2055 const double& srcLogEng,
2056 const GTime& srcTime,
2057 const GCTAPointing& pnt,
2058 const GCTARoi& roi) const
2059{
2060 // Set number of iterations for Romberg integration.
2061 static const int iter = 6;
2062
2063 // Declare result
2064 double value = 0.0;
2065
2066 // Get pointing direction zenith angle and azimuth [radians]
2067 double zenith = pnt.zenith();
2068 double azimuth = pnt.azimuth();
2069
2070 // Compute offset angle of source direction in camera system
2071 double theta = pnt.dir().dist(srcDir);
2072
2073 // Compute azimuth angle of source direction in camera system
2074 double phi = 0.0; //TODO: Implement phi dependence
2075
2076 // Extract relevant parameters from arguments
2077 double roi_radius = roi.radius() * gammalib::deg2rad;
2078 double roi_psf_distance = roi.centre().dir().dist(srcDir);
2079 double rmax = psf_delta_max(theta, phi, zenith, azimuth, srcLogEng);
2080
2081 // If PSF is fully enclosed by the ROI then skip the numerical
2082 // integration and assume that the integral is 1.0
2083 if (roi_psf_distance + rmax <= roi_radius) {
2084 value = 1.0;
2085 }
2086
2087 // ... otherwise perform numerical integration
2088 else {
2089
2090 // Compute minimum PSF integration radius
2091 double rmin = (roi_psf_distance > roi_radius)
2092 ? roi_psf_distance - roi_radius : 0.0;
2093
2094 // Continue only if integration range is valid
2095 if (rmax > rmin) {
2096
2097 // Setup integration kernel
2098 cta_npsf_kern_rad_azsym integrand(this,
2099 roi_radius,
2100 roi_psf_distance,
2101 srcLogEng,
2102 theta,
2103 phi,
2104 zenith,
2105 azimuth);
2106
2107 // Setup integration
2108 GIntegral integral(&integrand);
2109
2110 // Set fixed number of iterations
2111 integral.fixed_iter(iter);
2112
2113 // Radially integrate PSF. In case that the radial integration
2114 // region is small, we do the integration using a simple
2115 // trapezoidal rule. This is a kluge to prevent convergence
2116 // problems in the romberg() method for small integration intervals.
2117 // Ideally, the romberg() method should be enhanced to handle this
2118 // case automatically. The kluge threshold was fixed manually!
2119 if (rmax-rmin < 1.0e-12) {
2120 value = integral.trapzd(rmin, rmax);
2121 }
2122 else {
2123 value = integral.romberg(rmin, rmax);
2124 }
2125
2126 // Compile option: Check for NaN/Inf
2127 #if defined(G_NAN_CHECK)
2128 if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
2129 std::cout << "*** ERROR: GCTAResponseIrf::npsf:";
2130 std::cout << " NaN/Inf encountered";
2131 std::cout << " (value=" << value;
2132 std::cout << ", roi_radius=" << roi_radius * gammalib::rad2deg;
2133 std::cout << ", roi_psf_distance=";
2134 std::cout << roi_psf_distance * gammalib::rad2deg;
2135 std::cout << ", r=[" << rmin * gammalib::rad2deg;
2136 std::cout << "," << rmax * gammalib::rad2deg << "])";
2137 std::cout << std::endl;
2138 }
2139 #endif
2140
2141 } // endif: integration range was valid
2142
2143 } // endelse: numerical integration required
2144
2145 // Return integrated PSF
2146 return value;
2147}
2148
2149
2150/*==========================================================================
2151 = =
2152 = Private methods =
2153 = =
2154 ==========================================================================*/
2155
2156/***********************************************************************//**
2157 * @brief Initialise class members
2158 ***************************************************************************/
2160{
2161 // Initialise members
2162 m_caldb.clear();
2163 m_rspname.clear();
2164 m_aeff = NULL;
2165 m_psf = NULL;
2166 m_edisp = NULL;
2167 m_background = NULL;
2168 m_apply_edisp = false; //!< Switched off by default
2169 m_lo_safe_thres = 0.0;
2170 m_hi_safe_thres = 0.0;
2171
2172 // XML response filenames
2173 m_xml_caldb.clear();
2174 m_xml_rspname.clear();
2175
2176 // Return
2177 return;
2178}
2179
2180
2181/***********************************************************************//**
2182 * @brief Copy class members
2183 *
2184 * @param[in] rsp Response to be copied
2185 ***************************************************************************/
2187{
2188 // Copy members
2189 m_caldb = rsp.m_caldb;
2190 m_rspname = rsp.m_rspname;
2194
2195 // Copy response filenames
2198
2199 // Copy nroi cache
2200
2201 // Clone members
2202 m_aeff = (rsp.m_aeff != NULL) ? rsp.m_aeff->clone() : NULL;
2203 m_psf = (rsp.m_psf != NULL) ? rsp.m_psf->clone() : NULL;
2204 m_edisp = (rsp.m_edisp != NULL) ? rsp.m_edisp->clone() : NULL;
2205 m_background = (rsp.m_background != NULL) ? rsp.m_background->clone() : NULL;
2206
2207 // Return
2208 return;
2209}
2210
2211
2212/***********************************************************************//**
2213 * @brief Delete class members
2214 ***************************************************************************/
2216{
2217 // Free memory
2218 if (m_aeff != NULL) delete m_aeff;
2219 if (m_psf != NULL) delete m_psf;
2220 if (m_edisp != NULL) delete m_edisp;
2221 if (m_background != NULL) delete m_background;
2222
2223 // Initialise pointers
2224 m_aeff = NULL;
2225 m_psf = NULL;
2226 m_edisp = NULL;
2227 m_background = NULL;
2228
2229 // Return
2230 return;
2231}
2232
2233
2234/***********************************************************************//**
2235 * @brief Return filename with appropriate extension
2236 *
2237 * @param[in] filename File name.
2238 * @return File name.
2239 *
2240 * Checks if the specified @p filename exists, and if not, checks whether a
2241 * file with the added suffix .dat exists. Returns the file name with the
2242 * appropriate extension.
2243 ***************************************************************************/
2244GFilename GCTAResponseIrf::irf_filename(const std::string& filename) const
2245{
2246 // Set input filename as result filename
2247 GFilename result = filename;
2248
2249 // If file does not exist then try a variant with extension .dat
2250 if (!result.exists()) {
2251 GFilename testname = filename + ".dat";
2252 if (testname.exists()) {
2253 result = testname;
2254 }
2255 }
2256
2257 // Return result
2258 return result;
2259}
2260
2261
2262/***********************************************************************//**
2263 * @brief Return value of point source instrument response function
2264 *
2265 * @param[in] event Observed event.
2266 * @param[in] source Source.
2267 * @param[in] obs Observation.
2268 * @return Value of instrument response function for a point source.
2269 *
2270 * This method returns the value of the instrument response function for a
2271 * point source. The method assumes that source.model() is of type
2272 * GModelSpatialPointSource.
2273 ***************************************************************************/
2275 const GSource& source,
2276 const GObservation& obs) const
2277{
2278 // Get point source spatial model
2279 const GModelSpatialPointSource* src =
2280 static_cast<const GModelSpatialPointSource*>(source.model());
2281
2282 // Set Photon
2283 GPhoton photon(src->dir(), source.energy(), source.time());
2284
2285 // Compute IRF
2286 double irf = this->irf(event, photon, obs);
2287
2288 // Return IRF
2289 return irf;
2290}
2291
2292
2293/***********************************************************************//**
2294 * @brief Return IRF value for radial source model
2295 *
2296 * @param[in] event Observed event.
2297 * @param[in] source Source.
2298 * @param[in] obs Observation.
2299 *
2300 * @exception GException::invalid_argument
2301 * Model is not a radial model.
2302 *
2303 * Integrates the product of the spatial model and the instrument response
2304 * function over the true photon arrival direction using
2305 *
2306 * \f[
2307 * \int_{\rho_{\rm min}}^{\rho_{\rm max}}
2308 * \sin \rho \times S_{\rm p}(\rho | E, t) \times
2309 * \int_{\omega_{\rm min}}^{\omega_{\rm max}}
2310 * {\rm Irf}(\rho, \omega) d\omega d\rho
2311 * \f]
2312 *
2313 * where
2314 * \f$S_{\rm p}(\rho | E, t)\f$ is the radial spatial model,
2315 * \f${\rm Irf}(\rho, \omega)\f$ is the instrument response function,
2316 * \f$\rho\f$ is the radial distance from the model centre, and
2317 * \f$\omega\f$ is the position angle with respect to the connecting line
2318 * between the model centre and the observed photon arrival direction.
2319 *
2320 * The integration is performed in the coordinate system of the source
2321 * model spanned by \f$\rho\f$ and \f$\omega\f$ which allows to benefit
2322 * from the symmetry of the source model.
2323 *
2324 * The source centre is located at \f$\vec{m}\f$, and a spherical system
2325 * is defined around this location with \f$(\omega,\rho)\f$ being the
2326 * azimuth and zenith angles, respectively. \f$\omega=0\f$ is defined
2327 * by the direction that connects the source centre \f$\vec{m}\f$ to the
2328 * measured photon direction \f$\vec{p'}\f$, and \f$\omega\f$ increases
2329 * counterclockwise.
2330 *
2331 * Note that this method approximates the true theta angle (angle between
2332 * incident photon and pointing direction) by the measured theta angle
2333 * (angle between the measured photon arrival direction and the pointing
2334 * direction). Given the slow variation of the Psf shape over the field of
2335 * view, this approximation should be fine. It helps in fact a lot in
2336 * speeding up the computations.
2337 ***************************************************************************/
2339 const GSource& source,
2340 const GObservation& obs) const
2341{
2342 // Retrieve CTA pointing
2343 const GCTAPointing& pnt = gammalib::cta_pnt(G_IRF_RADIAL, obs);
2344 const GCTAInstDir& dir = gammalib::cta_dir(G_IRF_RADIAL, event);
2345
2346 // Get pointer on radial model
2347 const GModelSpatialRadial* model =
2348 dynamic_cast<const GModelSpatialRadial*>(source.model());
2349 if (model == NULL) {
2350 std::string cls = std::string(typeid(source.model()).name());
2351 std::string msg = "Invalid spatial model type \""+cls+"\" specified. "
2352 "Please specify a \"GModelSpatialRadial\" source "
2353 "model as argument.";
2355 }
2356
2357 // Get pointer on shell model (will be NULL for other models)
2358 const GModelSpatialRadialShell* shell =
2359 dynamic_cast<const GModelSpatialRadialShell*>(model);
2360
2361 // Get pointer on ring model (will be NULL for other models)
2362 const GModelSpatialRadialRing* ring =
2363 dynamic_cast<const GModelSpatialRadialRing*>(model);
2364
2365 // Set number of iterations for Romberg integration.
2366 // These values have been determined after careful testing, see
2367 // https://cta-redmine.irap.omp.eu/issues/1299
2368 // https://cta-redmine.irap.omp.eu/issues/1521
2369 // https://cta-redmine.irap.omp.eu/issues/2860
2370 static const int iter_rho = 6;
2371 static const int iter_phi = 6;
2372
2373 // Get event attributes
2374 const GSkyDir& obsDir = dir.dir();
2375 const GEnergy& obsEng = event.energy();
2376
2377 // Get source attributes
2378 const GSkyDir& centre = model->dir();
2379 const GEnergy& srcEng = source.energy();
2380 const GTime& srcTime = source.time();
2381
2382 // Get pointing direction zenith angle and azimuth [radians]
2383 double zenith = pnt.zenith();
2384 double azimuth = pnt.azimuth();
2385
2386 // Determine angular distance between measured photon direction and model
2387 // centre [radians]
2388 double zeta = centre.dist(obsDir);
2389
2390 // Determine angular distance between measured photon direction and
2391 // pointing direction [radians]
2392 double eta = pnt.dir().dist(obsDir);
2393
2394 // Determine angular distance between model centre and pointing direction
2395 // [radians]
2396 double lambda = centre.dist(pnt.dir());
2397
2398 // Compute azimuth angle of pointing in model system [radians]
2399 // Will be comprised in interval [0,pi]
2400 double omega0 = 0.0;
2401 double denom = std::sin(lambda) * std::sin(zeta);
2402 if (denom != 0.0) {
2403 double arg = (std::cos(eta) - std::cos(lambda) * std::cos(zeta))/denom;
2404 omega0 = gammalib::acos(arg);
2405 }
2406
2407 // Get log10(E/TeV) of true photon energy
2408 double srcLogEng = srcEng.log10TeV();
2409
2410 // Assign the observed theta angle (eta) as the true theta angle
2411 // between the source and the pointing directions. This is a (not
2412 // too bad) approximation which helps to speed up computations.
2413 // If we want to do this correctly, however, we would need to move
2414 // the psf_dummy_sigma down to the integration kernel, and we would
2415 // need to make sure that psf_delta_max really gives the absolute
2416 // maximum (this is certainly less critical)
2417 double theta = eta;
2418 double phi = 0.0; //TODO: Implement IRF Phi dependence
2419
2420 // Get maximum PSF and source radius in radians.
2421 double delta_max = psf_delta_max(theta, phi, zenith, azimuth, srcLogEng);
2422 double src_max = model->theta_max();
2423
2424 // Set radial model zenith angle range
2425 double rho_min = (zeta > delta_max) ? zeta - delta_max : 0.0;
2426 double rho_max = zeta + delta_max;
2427 if (rho_max > src_max) {
2428 rho_max = src_max;
2429 }
2430
2431 // Initialise IRF value
2432 double irf = 0.0;
2433
2434 // Perform zenith angle integration if interval is valid
2435 if (rho_max > rho_min) {
2436
2437 // Setup integration kernel
2438 cta_irf_radial_kern_rho integrand(this,
2439 model,
2440 zenith,
2441 azimuth,
2442 srcEng,
2443 srcTime,
2444 obsEng,
2445 zeta,
2446 lambda,
2447 omega0,
2448 delta_max,
2449 iter_phi);
2450
2451 // Integrate over model's zenith angle
2452 GIntegral integral(&integrand);
2453 integral.fixed_iter(iter_rho);
2454
2455 // Setup integration boundaries
2456 std::vector<double> bounds;
2457 bounds.push_back(rho_min);
2458 bounds.push_back(rho_max);
2459
2460 // If the integration range includes a transition between full
2461 // containment of model within Psf and partial containment, then
2462 // add a boundary at this location
2463 double transition_point = delta_max - zeta;
2464 if (transition_point > rho_min && transition_point < rho_max) {
2465 bounds.push_back(transition_point);
2466 }
2467
2468 // If we have a shell model then add an integration boundary for the
2469 // shell radius as a function discontinuity will occur at this
2470 // location
2471 if (shell != NULL) {
2472 double shell_radius = shell->radius() * gammalib::deg2rad;
2473 if (shell_radius > rho_min && shell_radius < rho_max) {
2474 bounds.push_back(shell_radius);
2475 }
2476 }
2477
2478 // If we have a ring model then add an integration boundary for the
2479 // ring radius as a function discontinuity will occur at this
2480 // location
2481 if (ring != NULL) {
2482 double ring_radius = ring->radius() * gammalib::deg2rad;
2483 if (ring_radius > rho_min && ring_radius < rho_max) {
2484 bounds.push_back(ring_radius);
2485 }
2486 }
2487
2488 // Integrate kernel
2489 irf = integral.romberg(bounds, iter_rho);
2490
2491 // Compile option: Check for NaN/Inf
2492 #if defined(G_NAN_CHECK)
2494 std::cout << "*** ERROR: GCTAResponseIrf::irf_radial:";
2495 std::cout << " NaN/Inf encountered";
2496 std::cout << " (irf=" << irf;
2497 std::cout << ", rho_min=" << rho_min;
2498 std::cout << ", rho_max=" << rho_max;
2499 std::cout << ", omega0=" << omega0 << ")";
2500 std::cout << std::endl;
2501 }
2502 #endif
2503
2504 // Apply deadtime correction
2505 irf *= obs.deadc(srcTime);
2506
2507 } // endif: integration interval is valid
2508
2509 // Compile option: Show integration results
2510 #if defined(G_DEBUG_IRF_RADIAL)
2511 std::cout << "GCTAResponseIrf::irf_radial:";
2512 std::cout << " rho=[" << rho_min*gammalib::rad2deg;
2513 std::cout << ", " << rho_max*gammalib::rad2deg << " deg;";
2514 std::cout << " zeta=" << zeta*gammalib::rad2deg << " deg;";
2515 std::cout << " eta=" << eta*gammalib::rad2deg << " deg;";
2516 std::cout << " lambda=" << lambda*gammalib::rad2deg << " deg;";
2517 std::cout << " omega0=" << omega0*gammalib::rad2deg << " deg;";
2518 std::cout << " theta_max=" << src_max*gammalib::rad2deg << " deg;";
2519 std::cout << " delta_max=" << delta_max*gammalib::rad2deg << " deg;";
2520 std::cout << " obsDir=" << obsDir << ";";
2521 std::cout << " modelDir=" << model->dir() << ";";
2522 std::cout << " irf=" << irf << std::endl;
2523 #endif
2524
2525 // Return IRF value
2526 return irf;
2527}
2528
2529
2530/***********************************************************************//**
2531 * @brief Return Irf value for elliptical source model
2532 *
2533 * @param[in] event Observed event.
2534 * @param[in] source Source.
2535 * @param[in] obs Observation.
2536 *
2537 * @exception GException::invalid_argument
2538 * Model is not an elliptical model.
2539 *
2540 * Integrates the product of the model and the IRF over the true photon
2541 * arrival direction using
2542 *
2543 * \f[
2544 * \int_{\rho_{\rm min}}^{\rho_{\rm max}}
2545 * \sin \rho \times
2546 * \int_{\omega}
2547 * S_{\rm p}(\rho, \omega | E, t) \, IRF(\rho, \omega) d\omega d\rho
2548 * \f]
2549 *
2550 * where
2551 * \f$S_{\rm p}(\rho, \omega | E, t)\f$ is the elliptical model,
2552 * \f$IRF(\rho, \omega)\f$ is the instrument response function,
2553 * \f$\rho\f$ is the distance from the model centre, and
2554 * \f$\omega\f$ is the position angle with respect to the connecting line
2555 * between the model centre and the observed photon arrival direction.
2556 *
2557 * The source model centre is located at \f$\vec{m}\f$, and a spherical
2558 * coordinate system is defined around this location with \f$(\rho,\omega)\f$
2559 * being the zenith and azimuth angles, respectively. The azimuth angle
2560 * \f$(\omega)\f$ is counted counterclockwise from the vector that runs from
2561 * the model centre \f$\vec{m}\f$ to the measured photon direction
2562 * \f$\vec{p'}\f$.
2563 ***************************************************************************/
2565 const GSource& source,
2566 const GObservation& obs) const
2567{
2568 // Set number of iterations for Romberg integration.
2569 // These values have been determined after careful testing, see
2570 // https://cta-redmine.irap.omp.eu/issues/1299
2571 // https://cta-redmine.irap.omp.eu/issues/1521
2572 // https://cta-redmine.irap.omp.eu/issues/2860
2573 static const int iter_rho = 6;
2574 static const int iter_phi = 6;
2575
2576 // Retrieve CTA pointing
2578 const GCTAInstDir& dir = gammalib::cta_dir(G_IRF_ELLIPTICAL, event);
2579
2580 // Get pointer on elliptical model
2581 const GModelSpatialElliptical* model =
2582 dynamic_cast<const GModelSpatialElliptical*>(source.model());
2583 if (model == NULL) {
2584 std::string cls = std::string(typeid(source.model()).name());
2585 std::string msg = "Invalid spatial model type \""+cls+"\" specified. "
2586 "Please specify a \"GModelSpatialElliptical\" source "
2587 "model as argument.";
2589 }
2590
2591 // Get event attributes (measured photon)
2592 const GSkyDir& obsDir = dir.dir();
2593 const GEnergy& obsEng = event.energy();
2594
2595 // Get source attributes
2596 const GSkyDir& centre = model->dir();
2597 const GEnergy& srcEng = source.energy();
2598 const GTime& srcTime = source.time();
2599
2600 // Get pointing direction zenith angle and azimuth [radians]
2601 double zenith = pnt.zenith();
2602 double azimuth = pnt.azimuth();
2603
2604 // Determine angular distance between observed photon direction and model
2605 // centre and position angle of observed photon direction seen from the
2606 // model centre [radians]
2607 double rho_obs = centre.dist(obsDir);
2608 double posangle_obs = centre.posang(obsDir); // Celestial system
2609
2610 // Determine angular distance between model centre and pointing direction
2611 // [radians]
2612 double rho_pnt = centre.dist(pnt.dir());
2613 double posangle_pnt = centre.posang(pnt.dir()); // Celestial system
2614
2615 // Compute azimuth angle of pointing in model coordinate system [radians]
2616 double omega_pnt = posangle_pnt - posangle_obs;
2617
2618 // Get log10(E/TeV) of true photon energy
2619 double srcLogEng = srcEng.log10TeV();
2620
2621 // Get maximum PSF radius [radians]. We assign here the measured theta
2622 // angle (eta) as the true theta angle between the source and the pointing
2623 // directions. As we only use the angle to determine the maximum PSF size,
2624 // this should be sufficient.
2625 double theta = pnt.dir().dist(obsDir);
2626 double phi = 0.0; //TODO: Implement IRF Phi dependence
2627 double delta_max = psf_delta_max(theta, phi, zenith, azimuth, srcLogEng);
2628
2629 // Get the ellipse boundary (radians). Note that these are NOT the
2630 // parameters of the ellipse but of a boundary ellipse that is used
2631 // for computing the relevant omega angle intervals for a given angle
2632 // rho. The boundary ellipse takes care of the possibility that the
2633 // semiminor axis is larger than the semimajor axis
2634 double semimajor; // Will be the larger axis
2635 double semiminor; // Will be the smaller axis
2636 double posangle; // Will be the corrected position angle
2637 double aspect_ratio; // Ratio between smaller/larger axis of model
2638 if (model->semimajor() >= model->semiminor()) {
2639 aspect_ratio = (model->semimajor() > 0.0) ?
2640 model->semiminor() / model->semimajor() : 0.0;
2641 posangle = model->posangle() * gammalib::deg2rad;
2642 }
2643 else {
2644 aspect_ratio = (model->semiminor() > 0.0) ?
2645 model->semimajor() / model->semiminor() : 0.0;
2646 posangle = model->posangle() * gammalib::deg2rad + gammalib::pihalf;
2647 }
2648 semimajor = model->theta_max();
2649 semiminor = semimajor * aspect_ratio;
2650
2651 // Set zenith angle integration range for elliptical model
2652 double rho_min = (rho_obs > delta_max) ? rho_obs - delta_max : 0.0;
2653 double rho_max = rho_obs + delta_max;
2654 if (rho_max > semimajor) {
2655 rho_max = semimajor;
2656 }
2657
2658 // Initialise IRF value
2659 double irf = 0.0;
2660
2661 // Perform zenith angle integration if interval is valid
2662 if (rho_max > rho_min) {
2663
2664 // Setup integration kernel
2665 cta_irf_elliptical_kern_rho integrand(this,
2666 model,
2667 semimajor,
2668 semiminor,
2669 posangle,
2670 zenith,
2671 azimuth,
2672 srcEng,
2673 srcTime,
2674 obsEng,
2675 rho_obs,
2676 posangle_obs,
2677 rho_pnt,
2678 omega_pnt,
2679 delta_max,
2680 iter_phi);
2681
2682 // Integrate over model's zenith angle
2683 GIntegral integral(&integrand);
2684 integral.fixed_iter(iter_rho);
2685
2686 // Setup integration boundaries
2687 std::vector<double> bounds;
2688 bounds.push_back(rho_min);
2689 bounds.push_back(rho_max);
2690
2691 // If the integration range includes the semiminor boundary, then
2692 // add an integration boundary at that location
2693 if (semiminor > rho_min && semiminor < rho_max) {
2694 bounds.push_back(semiminor);
2695 }
2696
2697 // Integrate kernel
2698 irf = integral.romberg(bounds, iter_rho);
2699
2700 // Compile option: Check for NaN/Inf
2701 #if defined(G_NAN_CHECK)
2703 std::cout << "*** ERROR: GCTAResponseIrf::irf_elliptical:";
2704 std::cout << " NaN/Inf encountered";
2705 std::cout << " (irf=" << irf;
2706 std::cout << ", rho_min=" << rho_min;
2707 std::cout << ", rho_max=" << rho_max << ")";
2708 std::cout << std::endl;
2709 }
2710 #endif
2711
2712 // Apply deadtime correction
2713 irf *= obs.deadc(srcTime);
2714
2715 } // endif: integration interval is valid
2716
2717 // Compile option: Show integration results
2718 #if defined(G_DEBUG_IRF_ELLIPTICAL)
2719 std::cout << "GCTAResponseIrf::irf_elliptical:";
2720 std::cout << " rho_min=" << rho_min;
2721 std::cout << " rho_max=" << rho_max;
2722 std::cout << " irf=" << irf << std::endl;
2723 #endif
2724
2725 // Return IRF value
2726 return irf;
2727}
2728
2729
2730/***********************************************************************//**
2731 * @brief Return value of diffuse source instrument response function
2732 *
2733 * @param[in] event Observed event.
2734 * @param[in] source Source.
2735 * @param[in] obs Observation.
2736 *
2737 * Integrates the product of the model and the IRF over the true photon
2738 * arrival direction using
2739 *
2740 * \f[
2741 * \int_{0}^{\theta_{\rm max}}
2742 * \sin \theta \times PSF(\theta)
2743 * \int_{0}^{2\pi}
2744 * S_{\rm p}(\theta, \phi | E, t) \,
2745 * Aeff(\theta, \phi) \,
2746 * Edisp(\theta, \phi) d\phi
2747 * \f]
2748 *
2749 * where
2750 * - \f$S_{\rm p}(\theta, \phi | E, t)\f$ is the diffuse model,
2751 * - \f$PSF(\theta)\f$ is the azimuthally symmetric Point Spread Function,
2752 * - \f$Aeff(\theta, \phi)\f$ is the effective area,
2753 * - \f$Edisp(\theta, \phi)\f$ is the energy dispersion,
2754 * - \f$\theta\f$ is the distance from the PSF centre, and
2755 * - \f$\phi\f$ is the azimuth angle.
2756 *
2757 * The integration is performed in the reference of the observed arrival
2758 * direction. Integration is done first over the azimuth angle \f$\phi\f$ and
2759 * then over the offset angle \f$\theta\f$.
2760 *
2761 * The integration kernels for this method are implemented by the response
2762 * helper classes cta_irf_diffuse_kern_theta and cta_irf_diffuse_kern_phi.
2763 ***************************************************************************/
2765 const GSource& source,
2766 const GObservation& obs) const
2767{
2768 // Set minimum and maximum number of iterations for Romberg integration.
2769 // These values have been determined after careful testing, see
2770 // https://cta-redmine.irap.omp.eu/issues/1248
2771 // https://cta-redmine.irap.omp.eu/issues/2458
2772 // https://cta-redmine.irap.omp.eu/issues/2860
2773 static const int min_iter_rho = 6;
2774 static const int min_iter_phi = 6;
2775 static const int max_iter_rho = 8;
2776 static const int max_iter_phi = 8;
2777
2778 // Initialise IRF value
2779 double irf = 0.0;
2780
2781 // Retrieve CTA pointing
2782 const GCTAPointing& pnt = gammalib::cta_pnt(G_IRF_DIFFUSE, obs);
2783
2784 // Get CTA instrument direction
2785 const GCTAInstDir& dir = gammalib::cta_dir(G_IRF_DIFFUSE, event);
2786
2787 // Get pointer on spatial model
2788 const GModelSpatial* model =
2789 dynamic_cast<const GModelSpatial*>(source.model());
2790 if (model == NULL) {
2791 std::string cls = std::string(typeid(source.model()).name());
2792 std::string msg = "Invalid spatial model type \""+cls+"\" specified. "
2793 "Please specify a \"GModelSpatial\" source "
2794 "model as argument.";
2796 }
2797
2798 // Get resolution of spatial model
2799 double resolution = gammalib::resolution(model);
2800
2801 // Get event attributes
2802 const GEnergy& obsEng = event.energy();
2803
2804 // Get source attributes
2805 const GEnergy& srcEng = source.energy();
2806 const GTime& srcTime = source.time();
2807
2808 // Get pointing direction zenith angle and azimuth [radians]
2809 double zenith = pnt.zenith();
2810 double azimuth = pnt.azimuth();
2811
2812 // Determine angular distance between measured photon direction and
2813 // pointing direction [radians]
2814 double eta = pnt.dir().dist(dir.dir());
2815
2816 // Get log10(E/TeV) of true photon energy
2817 double srcLogEng = srcEng.log10TeV();
2818
2819 // Assign the observed theta angle (eta) as the true theta angle
2820 // between the source and the pointing directions. This is a (not
2821 // too bad) approximation which helps to speed up computations.
2822 // If we want to do this correctly, however, we would need to move
2823 // the psf_dummy_sigma down to the integration kernel, and we would
2824 // need to make sure that psf_delta_max really gives the absolute
2825 // maximum (this is certainly less critical)
2826 double theta = eta;
2827 double phi = 0.0; //TODO: Implement Phi dependence
2828
2829 // Get maximum PSF radius in radians
2830 double delta_max = psf_delta_max(theta, phi, zenith, azimuth, srcLogEng);
2831
2832 // Perform zenith angle integration if interval is valid
2833 if (delta_max > 0.0) {
2834
2835 // Compute rotation matrix to convert from coordinates (theta,phi)
2836 // in the reference frame of the observed arrival direction into
2837 // celestial coordinates
2838 GMatrix ry;
2839 GMatrix rz;
2840 ry.eulery(dir.dir().dec_deg() - 90.0);
2841 rz.eulerz(-dir.dir().ra_deg());
2842 GMatrix rot = (ry * rz).transpose();
2843
2844 // Setup integration kernel
2845 cta_irf_diffuse_kern_theta integrand(this,
2846 model,
2847 &rot,
2848 theta,
2849 phi,
2850 zenith,
2851 azimuth,
2852 srcEng,
2853 srcTime,
2854 srcLogEng,
2855 obsEng,
2856 eta,
2857 min_iter_phi,
2858 max_iter_phi,
2859 resolution);
2860
2861 // Set number of radial integration iterations
2862 int iter = gammalib::iter_rho(delta_max, resolution,
2863 min_iter_rho, max_iter_rho);
2864
2865 // Integrate over Psf delta angle
2866 GIntegral integral(&integrand);
2867 integral.fixed_iter(iter);
2868 irf = integral.romberg(0.0, delta_max);
2869
2870 // Compile option: Check for NaN/Inf
2871 #if defined(G_NAN_CHECK)
2873 std::cout << "*** ERROR: GCTAResponseIrf::irf_diffuse:";
2874 std::cout << " NaN/Inf encountered";
2875 std::cout << " (irf=" << irf;
2876 std::cout << ", delta_max=" << delta_max << ")";
2877 std::cout << std::endl;
2878 }
2879 #endif
2880 }
2881
2882 // Apply deadtime correction
2883 irf *= obs.deadc(srcTime);
2884
2885 // Compile option: Show integration results
2886 #if defined(G_DEBUG_IRF_DIFFUSE)
2887 std::cout << "GCTAResponseIrf::irf_diffuse:";
2888 std::cout << " srcLogEng=" << srcLogEng;
2889 std::cout << " obsLogEng=" << obsLogEng;
2890 std::cout << " eta=" << eta;
2891 std::cout << " delta_max=" << delta_max;
2892 std::cout << " irf=" << irf << std::endl;
2893 #endif
2894
2895 // Return IRF value
2896 return irf;
2897}
2898
2899
2900/***********************************************************************//**
2901 * @brief Return spatial integral of point source model
2902 *
2903 * @param[in] model Sky Model.
2904 * @param[in] srcEng True photon energy.
2905 * @param[in] srcTime True photon arrival time.
2906 * @param[in] obsEng Observed event energy.
2907 * @param[in] obsTime Observed event arrival time.
2908 * @param[in] obs Observation.
2909 *
2910 * Computes the integral
2911 *
2912 * \f[
2913 * N_{\rm ROI}(E',t'|E,t) = \int_{\rm ROI} P(p',E',t'|E,t) dp'
2914 * \f]
2915 *
2916 * of
2917 *
2918 * \f[
2919 * P(p',E',t'|E,t) = \int
2920 * S(p,E,t) \times R(p',E',t'|p,E,t) \, dp
2921 * \f]
2922 *
2923 * over the Region of Interest (ROI) for a point source model \f$S(p,E,t)\f$
2924 * and the response function \f$R(p',E',t'|p,E,t)\f$.
2925 ***************************************************************************/
2927 const GEnergy& srcEng,
2928 const GTime& srcTime,
2929 const GEnergy& obsEng,
2930 const GTime& obsTime,
2931 const GObservation& obs) const
2932{
2933 // Get point source spatial model
2934 const GModelSpatialPointSource* src =
2935 static_cast<const GModelSpatialPointSource*>(model.spatial());
2936
2937 // Set Photon
2938 GPhoton photon(src->dir(), srcEng, srcTime);
2939
2940 // Compute Nroi
2941 double nroi = nirf(photon, obsEng, obsTime, obs);
2942
2943 // Return Nroi
2944 return nroi;
2945}
2946
2947
2948/***********************************************************************//**
2949 * @brief Return spatial integral of radial source model
2950 *
2951 * @param[in] model Sky Model.
2952 * @param[in] srcEng True photon energy.
2953 * @param[in] srcTime True photon arrival time.
2954 * @param[in] obsEng Observed event energy.
2955 * @param[in] obsTime Observed event arrival time.
2956 * @param[in] obs Observation.
2957 * @return Spatial integral of radial source model.
2958 *
2959 * @exception GException::invalid_argument
2960 * Invalid spatial model specified.
2961 *
2962 * Computes the integral
2963 *
2964 * \f[
2965 * N_{\rm ROI}(E',t'|E,t) = \int_{\rm ROI} P(p',E',t'|E,t) dp'
2966 * \f]
2967 *
2968 * of
2969 *
2970 * \f[
2971 * P(p',E',t'|E,t) = \int
2972 * S(p,E,t) \times R(p',E',t'|p,E,t) \, dp
2973 * \f]
2974 *
2975 * over the Region of Interest (ROI) for a radial source model \f$S(p,E,t)\f$
2976 * and the response function \f$R(p',E',t'|p,E,t)\f$.
2977 ***************************************************************************/
2979 const GEnergy& srcEng,
2980 const GTime& srcTime,
2981 const GEnergy& obsEng,
2982 const GTime& obsTime,
2983 const GObservation& obs) const
2984{
2985 // Set number of iterations for Romberg integration.
2986 // These values have been determined after careful testing, see
2987 // https://cta-redmine.irap.omp.eu/issues/1299
2988 static const int iter_rho = 6;
2989 static const int iter_phi = 6;
2990
2991 // Initialise Nroi value
2992 double nroi = 0.0;
2993
2994 // Retrieve CTA observation, ROI and pointing
2997 const GCTAPointing& pnt = cta.pointing();
2998
2999 // Get pointer on radial model
3000 const GModelSpatialRadial* spatial =
3001 dynamic_cast<const GModelSpatialRadial*>(model.spatial());
3002 if (spatial == NULL) {
3003 std::string cls = std::string(typeid(model.spatial()).name());
3004 std::string msg = "Invalid spatial model type \""+cls+"\" specified. "
3005 "Please specify a \"GModelSpatialRadial\" source "
3006 "model as argument.";
3008 }
3009
3010 // Get source attributes
3011 const GSkyDir& centre = spatial->dir();
3012
3013 // Get pointing direction zenith angle and azimuth [radians]
3014 double zenith = pnt.zenith();
3015 double azimuth = pnt.azimuth();
3016
3017 // Get log10(E/TeV) of true photon energy
3018 double srcLogEng = srcEng.log10TeV();
3019
3020 // Get maximum PSF radius (radians). We do this for the onaxis PSF only,
3021 // as this allows us doing this computation in the outer loop. This
3022 // should be sufficient here, unless the offaxis PSF becomes much worse
3023 // than the onaxis PSF. In this case, we may add a safety factor here
3024 // to make sure we encompass the entire PSF.
3025 double psf_max_radius = psf_delta_max(0.0, 0.0, zenith, azimuth, srcLogEng);
3026
3027 // Extract ROI radius (radians)
3028 double roi_radius = roi.radius() * gammalib::deg2rad;
3029
3030 // Compute distance between ROI and model centre (radians)
3031 double roi_model_distance = roi.centre().dir().dist(centre);
3032
3033 // Compute the ROI radius plus maximum PSF radius (radians). Any photon
3034 // coming from beyond this radius will not make it in the dataspace and
3035 // thus can be neglected.
3036 double roi_psf_radius = roi_radius + psf_max_radius;
3037
3038 // Set offset angle integration range. We take here the ROI+PSF into
3039 // account to make no integrations beyond the point where the
3040 // contribution drops to zero.
3041 double rho_min = (roi_model_distance > roi_psf_radius)
3042 ? roi_model_distance - roi_psf_radius: 0.0;
3043 double rho_max = spatial->theta_max();
3044
3045 // Perform offset angle integration only if interval is valid
3046 if (rho_max > rho_min) {
3047
3048 // Compute rotation matrix to convert from native model coordinates,
3049 // given by (rho,omega), into celestial coordinates.
3050 GMatrix ry;
3051 GMatrix rz;
3052 ry.eulery(spatial->dir().dec_deg() - 90.0);
3053 rz.eulerz(-spatial->dir().ra_deg());
3054 GMatrix rot = (ry * rz).transpose();
3055
3056 // Compute position angle of ROI centre with respect to model
3057 // centre (radians)
3058 double omega0 = centre.posang(roi.centre().dir()); // Celestial system
3059
3060 // Setup integration kernel
3061 cta_nroi_radial_kern_rho integrand(this,
3062 &cta,
3063 spatial,
3064 &rot,
3065 srcEng,
3066 srcTime,
3067 obsEng,
3068 obsTime,
3069 roi_model_distance,
3070 roi_psf_radius,
3071 omega0,
3072 iter_phi);
3073
3074 // Integrate over model's zenith angle
3075 GIntegral integral(&integrand);
3076 integral.fixed_iter(iter_rho);
3077
3078 // Setup integration boundaries
3079 std::vector<double> bounds;
3080 bounds.push_back(rho_min);
3081 bounds.push_back(rho_max);
3082
3083 // If the integration range includes a transition between full
3084 // containment of model within ROI and partial containment, then
3085 // add a boundary at this location
3086 double transition_point = roi_psf_radius - roi_model_distance;
3087 if (transition_point > rho_min && transition_point < rho_max) {
3088 bounds.push_back(transition_point);
3089 }
3090
3091 // If the integration range includes a transition between full
3092 // containment of ROI within model and partial containment, then
3093 // add a boundary at this location
3094 transition_point = roi_psf_radius + roi_model_distance;
3095 if (transition_point > rho_min && transition_point < rho_max) {
3096 bounds.push_back(transition_point);
3097 }
3098
3099 // If we have a shell model then add an integration boundary for the
3100 // shell radius as a function discontinuity will occur at this
3101 // location
3102 const GModelSpatialRadialShell* shell = dynamic_cast<const GModelSpatialRadialShell*>(spatial);
3103 if (shell != NULL) {
3104 double shell_radius = shell->radius() * gammalib::deg2rad;
3105 if (shell_radius > rho_min && shell_radius < rho_max) {
3106 bounds.push_back(shell_radius);
3107 }
3108 }
3109
3110 // If we have a ring model then add an integration boundary for the
3111 // ring radius as a function discontinuity will occur at this
3112 // location
3113 const GModelSpatialRadialRing* ring = dynamic_cast<const GModelSpatialRadialRing*>(spatial);
3114 if (ring != NULL) {
3115 double ring_radius = ring->radius() * gammalib::deg2rad;
3116 if (ring_radius > rho_min && ring_radius < rho_max) {
3117 bounds.push_back(ring_radius);
3118 }
3119 }
3120
3121 // Integrate kernel
3122 nroi = integral.romberg(bounds, iter_rho);
3123
3124 // Compile option: Show integration results
3125 #if defined(G_DEBUG_NROI_RADIAL)
3126 std::cout << "GCTAResponseIrf::nroi_radial:";
3127 std::cout << " rho_min=" << rho_min;
3128 std::cout << " rho_max=" << rho_max;
3129 std::cout << " nroi=" << nroi << std::endl;
3130 #endif
3131
3132 } // endif: offset angle range was valid
3133
3134 // Debug: Check for NaN
3135 #if defined(G_NAN_CHECK)
3137 std::cout << "*** ERROR: GCTAResponseIrf::nroi_radial:";
3138 std::cout << " NaN/Inf encountered";
3139 std::cout << " (nroi=" << nroi;
3140 std::cout << ", rho_min=" << rho_min;
3141 std::cout << ", rho_max=" << rho_max;
3142 std::cout << ")" << std::endl;
3143 }
3144 #endif
3145
3146 // Return Nroi
3147 return nroi;
3148}
3149
3150
3151/***********************************************************************//**
3152 * @brief Return spatial integral of elliptical source model
3153 *
3154 * @param[in] model Sky Model.
3155 * @param[in] srcEng True photon energy.
3156 * @param[in] srcTime True photon arrival time.
3157 * @param[in] obsEng Observed event energy.
3158 * @param[in] obsTime Observed event arrival time.
3159 * @param[in] obs Observation.
3160 * @return Spatial integral of elliptical source model.
3161 *
3162 * @exception GException::invalid_argument
3163 * Invalid spatial model specified.
3164 *
3165 * Computes the integral
3166 *
3167 * \f[
3168 * N_{\rm ROI}(E',t'|E,t) = \int_{\rm ROI} P(p',E',t'|E,t) dp'
3169 * \f]
3170 *
3171 * of
3172 *
3173 * \f[
3174 * P(p',E',t'|E,t) = \int
3175 * S(p,E,t) \times R(p',E',t'|p,E,t) \, dp
3176 * \f]
3177 *
3178 * over the Region of Interest (ROI) for an elliptical source model
3179 * \f$S(p,E,t)\f$ and the response function \f$R(p',E',t'|p,E,t)\f$.
3180 ***************************************************************************/
3182 const GEnergy& srcEng,
3183 const GTime& srcTime,
3184 const GEnergy& obsEng,
3185 const GTime& obsTime,
3186 const GObservation& obs) const
3187{
3188 // Set number of iterations for Romberg integration.
3189 // These values have been determined after careful testing, see
3190 // https://cta-redmine.irap.omp.eu/issues/1299
3191 static const int iter_rho = 6;
3192 static const int iter_phi = 6;
3193
3194 // Initialise Nroi value
3195 double nroi = 0.0;
3196
3197 // Retrieve CTA observation, ROI and pointing
3200 const GCTAPointing& pnt = cta.pointing();
3201
3202 // Get pointer on elliptical model
3203 const GModelSpatialElliptical* spatial =
3204 dynamic_cast<const GModelSpatialElliptical*>(model.spatial());
3205 if (spatial == NULL) {
3206 std::string cls = std::string(typeid(model.spatial()).name());
3207 std::string msg = "Invalid spatial model type \""+cls+"\" specified. "
3208 "Please specify a \"GModelSpatialElliptical\" source "
3209 "model as argument.";
3211 }
3212
3213 // Get source attributes
3214 const GSkyDir& centre = spatial->dir();
3215
3216 // Get pointing direction zenith angle and azimuth [radians]
3217 double zenith = pnt.zenith();
3218 double azimuth = pnt.azimuth();
3219
3220 // Get log10(E/TeV) of true photon energy
3221 double srcLogEng = srcEng.log10TeV();
3222
3223 // Get maximum PSF radius (radians). We do this for the onaxis PSF only,
3224 // as this allows us doing this computation in the outer loop. This
3225 // should be sufficient here, unless the offaxis PSF becomes much worse
3226 // than the onaxis PSF. In this case, we may add a safety factor here
3227 // to make sure we encompass the entire PSF.
3228 double psf_max_radius = psf_delta_max(0.0, 0.0, zenith, azimuth, srcLogEng);
3229
3230 // Extract ROI radius plus maximum PSF radius (radians). Any photon
3231 // coming from beyond this radius will not make it in the dataspace and
3232 // thus can be neglected.
3233 double radius_roi = roi.radius() * gammalib::deg2rad + psf_max_radius;
3234
3235 // Compute distance between ROI and model centre (radians)
3236 double rho_roi = roi.centre().dir().dist(centre);
3237
3238 // Get the ellipse boundary (radians). Note that these are NOT the
3239 // parameters of the ellipse but of a boundary ellipse that is used
3240 // for computing the relevant omega angle intervals for a given angle
3241 // rho. The boundary ellipse takes care of the possibility that the
3242 // semiminor axis is larger than the semimajor axis
3243 double semimajor; // Will be the larger axis
3244 double semiminor; // Will be the smaller axis
3245 double posangle; // Will be the corrected position angle
3246 double aspect_ratio; // Ratio between smaller/larger axis of model
3247 if (spatial->semimajor() >= spatial->semiminor()) {
3248 aspect_ratio = (spatial->semimajor() > 0.0) ?
3249 spatial->semiminor() / spatial->semimajor() : 0.0;
3250 posangle = spatial->posangle() * gammalib::deg2rad;
3251 }
3252 else {
3253 aspect_ratio = (spatial->semiminor() > 0.0) ?
3254 spatial->semimajor() / spatial->semiminor() : 0.0;
3255 posangle = spatial->posangle() * gammalib::deg2rad + gammalib::pihalf;
3256 }
3257 semimajor = spatial->theta_max();
3258 semiminor = semimajor * aspect_ratio;
3259
3260 // Set offset angle integration range. We take here the ROI+PSF into
3261 // account to make no integrations beyond the point where the
3262 // contribution drops to zero.
3263 double rho_min = (rho_roi > radius_roi) ? rho_roi - radius_roi: 0.0;
3264 double rho_max = rho_roi + radius_roi;
3265 if (rho_max > semimajor) {
3266 rho_max = semimajor;
3267 }
3268
3269 // Perform offset angle integration only if interval is valid
3270 if (rho_max > rho_min) {
3271
3272 // Compute rotation matrix to convert from native model coordinates,
3273 // given by (rho,omega), into celestial coordinates.
3274 GMatrix ry;
3275 GMatrix rz;
3276 ry.eulery(spatial->dir().dec_deg() - 90.0);
3277 rz.eulerz(-spatial->dir().ra_deg());
3278 GMatrix rot = (ry * rz).transpose();
3279
3280 // Compute position angle of ROI centre with respect to model
3281 // centre (radians)
3282 double posangle_roi = centre.posang(roi.centre().dir()); // Celestial system
3283
3284 // Setup integration kernel
3285 cta_nroi_elliptical_kern_rho integrand(this,
3286 &cta,
3287 spatial,
3288 &rot,
3289 semimajor,
3290 semiminor,
3291 posangle,
3292 srcEng,
3293 srcTime,
3294 obsEng,
3295 obsTime,
3296 rho_roi,
3297 posangle_roi,
3298 radius_roi,
3299 iter_phi);
3300
3301 // Integrate over model's zenith angle
3302 GIntegral integral(&integrand);
3303 integral.fixed_iter(iter_rho);
3304
3305 // Setup integration boundaries
3306 std::vector<double> bounds;
3307 bounds.push_back(rho_min);
3308 bounds.push_back(rho_max);
3309
3310 // If the integration range includes the semiminor boundary, then
3311 // add an integration boundary at that location
3312 if (semiminor > rho_min && semiminor < rho_max) {
3313 bounds.push_back(semiminor);
3314 }
3315
3316 // Integrate kernel
3317 nroi = integral.romberg(bounds, iter_rho);
3318
3319 // Compile option: Show integration results
3320 #if defined(G_DEBUG_NROI_ELLIPTICAL)
3321 std::cout << "GCTAResponseIrf::nroi_elliptical:";
3322 std::cout << " rho_min=" << rho_min;
3323 std::cout << " rho_max=" << rho_max;
3324 std::cout << " nroi=" << nroi << std::endl;
3325 #endif
3326
3327 } // endif: offset angle range was valid
3328
3329 // Debug: Check for NaN
3330 #if defined(G_NAN_CHECK)
3332 std::cout << "*** ERROR: GCTAResponseIrf::nroi_elliptical:";
3333 std::cout << " NaN/Inf encountered";
3334 std::cout << " (nroi=" << nroi;
3335 std::cout << ", rho_min=" << rho_min;
3336 std::cout << ", rho_max=" << rho_max;
3337 std::cout << ")" << std::endl;
3338 }
3339 #endif
3340
3341 // Return Nroi
3342 return nroi;
3343}
3344
3345
3346/***********************************************************************//**
3347 * @brief Return spatial integral of diffuse source model
3348 *
3349 * @param[in] model Sky Model.
3350 * @param[in] srcEng True photon energy.
3351 * @param[in] srcTime True photon arrival time.
3352 * @param[in] obsEng Observed event energy.
3353 * @param[in] obsTime Observed event arrival time.
3354 * @param[in] obs Observation.
3355 * @return Spatial integral of diffuse source model.
3356 *
3357 * @exception GException::invalid_argument
3358 * Invalid spatial model specified.
3359 *
3360 * Computes the integral
3361 *
3362 * \f[
3363 * N_{\rm ROI}(E',t'|E,t) = \int_{\rm ROI} P(p',E',t'|E,t) dp'
3364 * \f]
3365 *
3366 * of
3367 *
3368 * \f[
3369 * P(p',E',t'|E,t) = \int
3370 * S(p,E,t) \times R(p',E',t'|p,E,t) \, dp
3371 * \f]
3372 *
3373 * over the Region of Interest (ROI) for a diffuse source model
3374 * \f$S(p,E,t)\f$ and the response function \f$R(p',E',t'|p,E,t)\f$.
3375 ***************************************************************************/
3377 const GEnergy& srcEng,
3378 const GTime& srcTime,
3379 const GEnergy& obsEng,
3380 const GTime& obsTime,
3381 const GObservation& obs) const
3382{
3383 // Set number of iterations for Romberg integration.
3384 // These values have been determined after careful testing, see
3385 // https://cta-redmine.irap.omp.eu/issues/1248
3386 static const int iter_rho = 9;
3387 static const int iter_phi = 9;
3388
3389 // Initialise Nroi value
3390 double nroi = 0.0;
3391
3392 // Retrieve CTA observation, ROI and pointing
3395 const GCTAPointing& pnt = cta.pointing();
3396
3397 // Get pointer on spatial model
3398 const GModelSpatial* spatial =
3399 dynamic_cast<const GModelSpatial*>(model.spatial());
3400 if (spatial == NULL) {
3401 std::string cls = std::string(typeid(model.spatial()).name());
3402 std::string msg = "Invalid spatial model type \""+cls+"\" specified. "
3403 "Please specify a \"GModelSpatial\" source "
3404 "model as argument.";
3406 }
3407
3408 // Get pointing direction zenith angle and azimuth [radians]
3409 double zenith = pnt.zenith();
3410 double azimuth = pnt.azimuth();
3411
3412 // Get log10(E/TeV) of true photon energy
3413 double srcLogEng = srcEng.log10TeV();
3414
3415 // Get maximum PSF radius (radians). We do this for the onaxis PSF only,
3416 // as this allows us doing this computation in the outer loop. This
3417 // should be sufficient here, unless the offaxis PSF becomes much worse
3418 // than the onaxis PSF. In this case, we may add a safety factor here
3419 // to make sure we encompass the entire PSF.
3420 double psf_max_radius = psf_delta_max(0.0, 0.0, zenith, azimuth, srcLogEng);
3421
3422 // Extract ROI radius (radians)
3423 double roi_radius = roi.radius() * gammalib::deg2rad;
3424
3425 // Compute the ROI radius plus maximum PSF radius (radians). Any photon
3426 // coming from beyond this radius will not make it in the dataspace and
3427 // thus can be neglected.
3428 double roi_psf_radius = roi_radius + psf_max_radius;
3429
3430 // Perform offset angle integration only if interval is valid
3431 if (roi_psf_radius > 0.0) {
3432
3433 // Compute rotation matrix to convert from native ROI coordinates,
3434 // given by (theta,phi), into celestial coordinates.
3435 GMatrix ry;
3436 GMatrix rz;
3437 ry.eulery(roi.centre().dir().dec_deg() - 90.0);
3438 rz.eulerz(-roi.centre().dir().ra_deg());
3439 GMatrix rot = (ry * rz).transpose();
3440
3441 // Setup integration kernel
3442 cta_nroi_diffuse_kern_theta integrand(this,
3443 &cta,
3444 spatial,
3445 &rot,
3446 srcEng,
3447 srcTime,
3448 obsEng,
3449 obsTime,
3450 iter_phi);
3451
3452 // Integrate over model's zenith angle
3453 GIntegral integral(&integrand);
3454 integral.fixed_iter(iter_rho);
3455 nroi = integral.romberg(0.0, roi_psf_radius);
3456
3457 // Compile option: Show integration results
3458 #if defined(G_DEBUG_NROI_DIFFUSE)
3459 std::cout << "GCTAResponseIrf::nroi_diffuse:";
3460 std::cout << " roi_psf_radius=" << roi_psf_radius;
3461 std::cout << " nroi=" << nroi;
3462 std::cout << " Etrue=" << srcEng;
3463 std::cout << " Ereco=" << obsEng;
3464 std::cout << " id=" << id << std::endl;
3465 #endif
3466
3467 } // endif: offset angle range was valid
3468
3469 // Debug: Check for NaN
3470 #if defined(G_NAN_CHECK)
3472 std::cout << "*** ERROR: GCTAResponseIrf::nroi_diffuse:";
3473 std::cout << " NaN/Inf encountered";
3474 std::cout << " (nroi=" << nroi;
3475 std::cout << ", roi_psf_radius=" << roi_psf_radius;
3476 std::cout << ")" << std::endl;
3477 }
3478 #endif
3479
3480 // Return Nroi
3481 return nroi;
3482}
3483
3484
3485/***********************************************************************//**
3486 * @brief Return spatial integral of composite source model
3487 *
3488 * @param[in] model Sky Model.
3489 * @param[in] srcEng True photon energy.
3490 * @param[in] srcTime True photon arrival time.
3491 * @param[in] obsEng Observed event energy.
3492 * @param[in] obsTime Observed event arrival time.
3493 * @param[in] obs Observation.
3494 *
3495 * Computes the integral
3496 *
3497 * \f[
3498 * N_{\rm ROI}(E',t'|E,t) = \int_{\rm ROI} P(p',E',t'|E,t) dp'
3499 * \f]
3500 *
3501 * of
3502 *
3503 * \f[
3504 * P(p',E',t'|E,t) = \int
3505 * S(p,E,t) \times R(p',E',t'|p,E,t) \, dp
3506 * \f]
3507 *
3508 * over the Region of Interest (ROI) for a composite source model
3509 * \f$S(p,E,t)\f$ and the response function \f$R(p',E',t'|p,E,t)\f$.
3510 ***************************************************************************/
3512 const GEnergy& srcEng,
3513 const GTime& srcTime,
3514 const GEnergy& obsEng,
3515 const GTime& obsTime,
3516 const GObservation& obs) const
3517{
3518 // Initialise nroi
3519 double nroi = 0.0;
3520
3521 // Get composite model
3523 dynamic_cast<GModelSpatialComposite*>(model.spatial());
3524
3525 // Loop over model components
3526 for (int i = 0; i < comp->components(); ++i) {
3527
3528 // Create new sky model
3529 GModelSky sky = GModelSky(model);
3530
3531 // Assign spatial part
3532 sky.spatial(comp->component(i));
3533
3534 // Compute nroi
3535 nroi += this->nroi(sky, srcEng, srcTime, obsEng, obsTime, obs) *
3536 comp->scale(i);
3537
3538 }
3539
3540 // Divide by number of model components
3541 double sum = comp->sum_of_scales();
3542 if (sum > 0.0) {
3543 nroi /= sum;
3544 }
3545
3546 // Return nroi
3547 return nroi;
3548
3549}
#define G_WRITE
#define G_READ
XSPEC Auxiliary Response File class definition.
#define G_IRF
CTA 2D effective area class definition.
CTA ARF effective area class definition.
CTA performance table effective area class definition.
CTA effective area base class definition.
CTA 2D background class definition.
CTA 3D background class definition.
CTA performance table background class definition.
CTA background model base class definition.
CTA 2D energy dispersion class definition.
CTA performance table energy dispersion class definition.
CTA RMF energy dispersion class definition.
Abstract CTA energy dispersion base class definition.
CTA event atom class definition.
CTA event list class interface definition.
CTA observation class interface definition.
CTA pointing class interface definition.
CTA 2D point spread function class definition.
King profile CTA point spread function class definition.
CTA performance table point spread function class definition.
CTA point spread function table class definition.
CTA point spread function vector class definition.
CTA point spread function base class definition.
#define G_NROI_DIFFUSE
#define G_LOAD_EDISP
#define G_PSF
#define G_PSF_DELTA_MAX
#define G_LOAD_AEFF
#define G_NROI_RADIAL
#define G_NROI_ELLIPTICAL
#define G_LOAD_BACKGROUND
#define G_NIRF
#define G_LOAD_PSF
#define G_AEFF
CTA instrument response function class definition.
CTA response helper classes definition.
CTA region of interest class interface definition.
Definition of support function used by CTA classes.
Calibration database class interface definition.
Exception handler interface definition.
Filename class interface definition.
FITS file class interface definition.
Integration class interface definition.
Mathematical function definitions.
Sky model class interface definition.
Spatial composite model class interface definition.
Abstract elliptical spatial model base class interface definition.
Point source spatial model class interface definition.
Radial ring model class interface definition.
Radial shell model class interface definition.
Abstract radial spatial model base class interface definition.
Random number generator class definition.
#define G_IRF_ELLIPTICAL
Definition GResponse.cpp:65
#define G_IRF_RADIAL
Definition GResponse.cpp:63
#define G_IRF_DIFFUSE
Definition GResponse.cpp:67
Source class definition.
Gammalib tools definition.
GChatter
Definition GTypemaps.hpp:33
@ NORMAL
Definition GTypemaps.hpp:36
@ SILENT
Definition GTypemaps.hpp:34
@ GMODEL_SPATIAL_ELLIPTICAL
Definition GTypemaps.hpp:45
@ GMODEL_SPATIAL_RADIAL
Definition GTypemaps.hpp:44
@ GMODEL_SPATIAL_COMPOSITE
Definition GTypemaps.hpp:47
@ GMODEL_SPATIAL_DIFFUSE
Definition GTypemaps.hpp:46
@ GMODEL_SPATIAL_POINT_SOURCE
Definition GTypemaps.hpp:43
double sum(const GVector &vector)
Computes vector sum.
Definition GVector.cpp:944
CTA 2D effective area class.
CTA ARF effective area class.
void sigma(const double &sigma)
Set sigma for offset angle dependence.
void scale(const double &scale)
Set effective area scaling factor.
void remove_thetacut(const GCTAResponseIrf &rsp)
Remove thetacut.
void thetacut(const double &thetacut)
Set theta cut angle.
CTA performance table effective area class.
void sigma(const double &sigma)
Set sigma for offset angle dependence.
virtual std::string print(const GChatter &chatter=NORMAL) const =0
Print content of object.
virtual GFilename filename(void) const =0
virtual GCTAAeff * clone(void) const =0
Clones object.
CTA 2D background class.
CTA 3D background class.
CTA performance table background class.
void sigma(const double &sigma)
Set sigma for offset angle dependence.
virtual std::string print(const GChatter &chatter=NORMAL) const =0
Print content of object.
virtual GCTABackground * clone(void) const =0
Clones object.
CTA 2D energy dispersion class.
CTA performance table energy dispersion class.
CTA Redistribution Matrix File (RMF) energy dispersion class.
virtual 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 =0
virtual std::string print(const GChatter &chatter=NORMAL) const =0
Print content of object.
virtual GCTAEdisp * clone(void) const =0
Clones object.
virtual 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 =0
CTA event atom class.
const GCTAInstDir & dir(void) const
Return instrument direction.
virtual void roi(const GRoi &roi)
Set ROI.
CTA instrument direction class.
void dir(const GSkyDir &dir)
Set sky direction.
CTA observation class.
void pointing(const GCTAPointing &pointing)
Set CTA pointing.
CTA pointing class.
const double & zenith(void) const
Return pointing zenith angle.
GCTAInstDir instdir(const GSkyDir &skydir) const
Get instrument direction from sky direction.
const GSkyDir & dir(void) const
Return pointing sky direction.
const double & azimuth(void) const
Return pointing azimuth angle.
CTA 2D point spread function class.
Definition GCTAPsf2D.hpp:54
CTA point spread function class with a King profile.
CTA performance table point spread function class.
CTA point spread function table class.
CTA vector point spread function class.
virtual GCTAPsf * clone(void) const =0
Clones object.
virtual double delta_max(const double &logE, const double &theta=0.0, const double &phi=0.0, const double &zenith=0.0, const double &azimuth=0.0, const bool &etrue=true) const =0
virtual std::string print(const GChatter &chatter=NORMAL) const =0
Print content of object.
virtual double mc(GRan &ran, const double &logE, const double &theta=0.0, const double &phi=0.0, const double &zenith=0.0, const double &azimuth=0.0, const bool &etrue=true) const =0
CTA instrument response function class.
const GCTAAeff * aeff(void) const
Return pointer to effective area response.
virtual bool apply_edisp(void) const
Signal if energy dispersion should be applied.
double nroi_composite(const GModelSky &model, const GEnergy &srcEng, const GTime &srcTime, const GEnergy &obsEng, const GTime &obsTime, const GObservation &obs) const
Return spatial integral of composite source model.
void load_edisp(const GFilename &filename)
Load energy dispersion information.
double offset_sigma(void) const
Return offset angle dependence (degrees)
virtual bool use_edisp(void) const
Signal if response uses energy dispersion.
double nroi_diffuse(const GModelSky &model, const GEnergy &srcEng, const GTime &srcTime, const GEnergy &obsEng, const GTime &obsTime, const GObservation &obs) const
Return spatial integral of diffuse source model.
GCTAAeff * m_aeff
Effective area.
virtual std::string print(const GChatter &chatter=NORMAL) const
Print CTA response information.
void load_psf(const GFilename &filename)
Load CTA PSF vector.
bool m_apply_edisp
Apply energy dispersion.
double irf_ptsrc(const GEvent &event, const GSource &source, const GObservation &obs) const
Return value of point source instrument response function.
GCTABackground * m_background
Background.
GCTAEventAtom * mc(const double &area, const GPhoton &photon, const GObservation &obs, GRan &ran) const
Simulate event from photon.
GFilename irf_filename(const std::string &filename) const
Return filename with appropriate extension.
double psf_delta_max(const double &theta, const double &phi, const double &zenith, const double &azimuth, const double &srcLogEng) const
Return maximum angular separation (in radians)
const GCTABackground * background(void) const
Return pointer to background model.
virtual GCTAResponseIrf * clone(void) const
Clone instance.
std::string m_xml_rspname
Response name in XML file.
const GCTAPsf * psf(void) const
Return pointer to point spread function.
void copy_members(const GCTAResponseIrf &rsp)
Copy class members.
double irf_elliptical(const GEvent &event, const GSource &source, const GObservation &obs) const
Return Irf value for elliptical source model.
virtual double nroi(const GModelSky &model, const GEnergy &obsEng, const GTime &obsTime, const GObservation &obs) const
Return integral of event probability for a given sky model over ROI.
double m_lo_safe_thres
Safe low energy threshold.
void init_members(void)
Initialise class members.
GCTAEdisp * m_edisp
Energy dispersion.
void free_members(void)
Delete class members.
void load(const std::string &rspname)
Load CTA response.
void load_aeff(const GFilename &filename)
Load effective area.
double npsf(const GSkyDir &srcDir, const double &srcLogEng, const GTime &srcTime, const GCTAPointing &pnt, const GCTARoi &roi) const
Return result of PSF integration over ROI.
virtual void clear(void)
Clear instance.
virtual GEbounds ebounds(const GEnergy &obsEnergy) const
Return true energy boundaries for a specific observed energy.
virtual double irf(const GEvent &event, const GPhoton &photon, const GObservation &obs) const
Return value of instrument response function.
const GCaldb & caldb(void) const
Return calibration database.
GCaldb m_caldb
Calibration database.
double nroi_ptsrc(const GModelSky &model, const GEnergy &srcEng, const GTime &srcTime, const GEnergy &obsEng, const GTime &obsTime, const GObservation &obs) const
Return spatial integral of point source model.
double m_hi_safe_thres
Safe high energy threshold.
const std::string & rspname(void) const
Return response name.
std::string m_xml_caldb
Calibration database string in XML file.
void load_background(const GFilename &filename)
Load background model.
double irf_radial(const GEvent &event, const GSource &source, const GObservation &obs) const
Return IRF value for radial source model.
virtual void read(const GXmlElement &xml)
Read response from XML element.
double nirf(const GPhoton &photon, const GEnergy &obsEng, const GTime &obsTime, const GObservation &obs) const
Return spatial integral of Instrument Response Function.
GCTAResponseIrf(void)
Void constructor.
const GCTAEdisp * edisp(void) const
Return pointer to energy dispersion.
double irf_diffuse(const GEvent &event, const GSource &source, const GObservation &obs) const
Return value of diffuse source instrument response function.
double nroi_elliptical(const GModelSky &model, const GEnergy &srcEng, const GTime &srcTime, const GEnergy &obsEng, const GTime &obsTime, const GObservation &obs) const
Return spatial integral of elliptical source model.
virtual void write(GXmlElement &xml) const
Write response information into XML element.
GCTAPsf * m_psf
Point spread function.
double nroi_radial(const GModelSky &model, const GEnergy &srcEng, const GTime &srcTime, const GEnergy &obsEng, const GTime &obsTime, const GObservation &obs) const
Return spatial integral of radial source model.
std::string m_rspname
Name of the instrument response.
virtual ~GCTAResponseIrf(void)
Destructor.
virtual GCTAResponseIrf & operator=(const GCTAResponseIrf &rsp)
Assignment operator.
CTA instrument response function class.
void init_members(void)
Initialise class members.
virtual GCTAResponse & operator=(const GCTAResponse &rsp)
Assignment operator.
void free_members(void)
Delete class members.
Interface for the CTA region of interest class.
Definition GCTARoi.hpp:49
const double & radius(void) const
Returns radius of region of interest in degrees.
Definition GCTARoi.hpp:125
const GCTAInstDir & centre(void) const
Returns region of interest centre.
Definition GCTARoi.hpp:111
Calibration database class.
Definition GCaldb.hpp:66
std::string rootdir(void) const
Return path to CALDB root directory.
Definition GCaldb.cpp:257
const std::string & mission(void) const
Return mission.
Definition GCaldb.hpp:137
const std::string & instrument(void) const
Return instrument.
Definition GCaldb.hpp:149
GFilename filename(const std::string &detector, const std::string &filter, const std::string &codename, const std::string &date, const std::string &time, const std::string &expr)
Return calibration file name based on selection parameters.
Definition GCaldb.cpp:524
void clear(void)
Clear calibration database.
Definition GCaldb.cpp:194
std::string print(const GChatter &chatter=NORMAL) const
Print calibration database information.
Definition GCaldb.cpp:657
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 TeV(void) const
Return energy in TeV.
Definition GEnergy.cpp:348
Abstract interface for the event classes.
Definition GEvent.hpp:71
Filename class.
Definition GFilename.hpp:62
bool has_extname(void) const
Signal if filename has an extension name.
bool is_fits(void) const
Checks whether file is a FITS file.
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.
bool has_card(const int &cardno) const
Check existence of header card.
Definition GFitsHDU.hpp:233
double real(const std::string &keyname) const
Return card value as double precision.
Definition GFitsHDU.hpp:423
Abstract interface for FITS table.
bool contains(const std::string &colname) const
Checks the presence of a column in 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
void close(void)
Close FITS file.
Definition GFits.cpp:1342
GFitsTable * table(const int &extno)
Get pointer to table HDU.
Definition GFits.cpp:482
GIntegral class interface definition.
Definition GIntegral.hpp:46
double romberg(std::vector< double > bounds, const int &order=5)
Perform Romberg integration.
double trapzd(const double &a, const double &b, const int &n=1, double result=0.0)
Perform Trapezoidal integration.
void fixed_iter(const int &iter)
Set fixed number of iterations.
Generic matrix class definition.
Definition GMatrix.hpp:79
void eulerz(const double &angle)
Set Euler rotation matrix around z axis.
Definition GMatrix.cpp:1194
void eulery(const double &angle)
Set Euler rotation matrix around y axis.
Definition GMatrix.cpp:1157
Sky model class.
GModelSpectral * spectral(void) const
Return spectral model component.
GModelTemporal * temporal(void) const
Return temporal model component.
GModelSpatial * spatial(void) const
Return spatial model component.
Spatial composite model.
const GModelSpatial * component(const int &index) const
Returns pointer to spatial component element.
int components(void) const
Return number of model components.
double sum_of_scales(void) const
Returns sum of all model scales.
double scale(const int &index) const
Returns scale of spatial component.
Abstract elliptical spatial model base class.
double semimajor(void) const
Return semi-major axis of ellipse.
double posangle(void) const
Return Position Angle of model.
const GSkyDir & dir(void) const
Return position of elliptical spatial model.
double semiminor(void) const
Return semi-minor axis of ellipse.
virtual double theta_max(void) const =0
Point source spatial model.
const GSkyDir & dir(void) const
Return position of point source.
double radius(void) const
Return ring inner radius.
double radius(void) const
Return shell radius.
Abstract radial spatial model base class.
virtual double theta_max(void) const =0
const GSkyDir & dir(void) const
Return position of radial spatial model.
Abstract spatial model base class.
bool has_free_pars(void) const
Checks if the spatial model has free parameters.
virtual GClassCode code(void) const =0
virtual double eval(const GEnergy &srcEng, const GTime &srcTime=GTime(), const bool &gradients=false) const =0
virtual double eval(const GTime &srcTime, const bool &gradients=false) const =0
GModelPar & scale(const int &index)
Returns reference to scale parameter by index.
Definition GModel.cpp:369
bool has_scales(void) const
Signals that model has scales.
Definition GModel.hpp:355
const std::string & name(void) const
Return parameter name.
Definition GModel.hpp:261
Abstract observation base class.
virtual double deadc(const GTime &time=GTime()) const =0
virtual std::string instrument(void) const =0
void id(const std::string &id)
Set observation identifier.
double value(void) const
Return parameter value.
Class that handles photons.
Definition GPhoton.hpp:47
const GTime & time(void) const
Return photon time.
Definition GPhoton.hpp:134
const GSkyDir & dir(void) const
Return photon sky direction.
Definition GPhoton.hpp:110
const GEnergy & energy(void) const
Return photon energy.
Definition GPhoton.hpp:122
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
bool contains(const std::string &name, const GEnergy &ereco, const GEnergy &etrue, double *value=NULL) const
Check if cache contains a value for specific parameters.
std::string print(const GChatter &chatter=NORMAL) const
Print response cache.
void set(const std::string &name, const GEnergy &ereco, const GEnergy &etrue, const double &value)
Set cache value.
GResponseCache m_irf_cache
GResponseCache m_nroi_cache
bool m_use_nroi_cache
Control usage of nroi cache.
void init_members(void)
Initialise class members.
void free_members(void)
Delete class members.
Sky direction class.
Definition GSkyDir.hpp:62
void rotate_deg(const double &phi, const double &theta)
Rotate sky direction by zenith and azimuth angle.
Definition GSkyDir.cpp:424
double dec_deg(void) const
Returns Declination in degrees.
Definition GSkyDir.hpp:256
double ra_deg(void) const
Returns Right Ascension in degrees.
Definition GSkyDir.hpp:229
double dist(const GSkyDir &dir) const
Compute angular distance between sky directions in radians.
Definition GSkyDir.hpp:271
Class that handles gamma-ray sources.
Definition GSource.hpp:53
const GEnergy & energy(void) const
Return photon energy.
Definition GSource.hpp:142
const GTime & time(void) const
Return photon arrival time.
Definition GSource.hpp:156
const GModelSpatial * model(void) const
Return spatial model component.
Definition GSource.hpp:128
Time class.
Definition GTime.hpp:55
XML element node class.
const GXmlAttribute * attribute(const int &index) const
Return attribute.
bool has_attribute(const std::string &name) const
Check if element has a given attribute.
Kernel for IRF offest angle integration of the diffuse source model.
Kernel for elliptical model zenith angle integration of IRF.
Kernel for radial model zenith angle integration of IRF.
Integration kernel for npsf() method.
Kernel for Nroi offest angle integration of diffuse model.
Kernel for zenith angle Nroi integration of elliptical model.
Kernel for zenith angle Nroi integration of radial model.
std::string parformat(const std::string &s, const int &indent=0)
Convert string in parameter format.
Definition GTools.cpp:1143
const std::string extname_rmf
Definition GRmf.hpp:44
const GCTAObservation & cta_obs(const std::string &origin, const GObservation &obs)
Retrieve CTA observation from generic observation.
bool is_infinite(const double &x)
Signal if argument is infinite.
Definition GTools.hpp:184
const std::string extname_cta_background3d
bool is_notanumber(const double &x)
Signal if argument is not a number.
Definition GTools.hpp:201
const GCTAPointing & cta_pnt(const std::string &origin, const GObservation &obs)
Retrieve CTA pointing from generic observation.
std::string str(const unsigned short int &value)
Convert unsigned short integer value into string.
Definition GTools.cpp:489
const GXmlElement * xml_get_par(const std::string &origin, const GXmlElement &xml, const std::string &name)
Return pointer to parameter with given name in XML element.
Definition GTools.cpp:1689
double todouble(const std::string &arg)
Convert string into double precision value.
Definition GTools.cpp:926
const GCTAInstDir & cta_dir(const std::string &origin, const GEvent &event)
Retrieve CTA instrument direction from generic event.
std::string filepath(const std::string &pathname, const std::string &filename)
Build file path from path name and file name.
Definition GTools.cpp:393
const double pihalf
Definition GMath.hpp:38
std::string tolower(const std::string &s)
Convert string to lower case.
Definition GTools.cpp:955
GXmlElement * xml_need_par(const std::string &origin, GXmlElement &xml, const std::string &name)
Return pointer to parameter with given name in XML element.
Definition GTools.cpp:1637
std::string strip_whitespace(const std::string &arg)
Strip leading and trailing whitespace from string.
Definition GTools.cpp:80
const GCTAEventList & cta_event_list(const std::string &origin, const GObservation &obs)
Retrieve CTA event list from generic observation.
const double deg2rad
Definition GMath.hpp:43
const std::string extname_cta_aeff2d
const std::string extname_cta_background2d
double resolution(const GModelSpatial *model)
Determine resolution of spatial model.
const std::string extname_cta_edisp2d
const std::string extname_arf
Definition GArf.hpp:45
double acos(const double &arg)
Computes acos by avoiding NaN due to rounding errors.
Definition GMath.cpp:69
GChatter reduce(const GChatter &chatter)
Reduce chattiness by one level.
Definition GTypemaps.hpp:65
std::string gadf_hduclas4(const GFits &fits, const std::string &hduclas4)
Return extension name for GADF response table of given HDU class 4.
void warning(const std::string &origin, const std::string &message)
Emits warning.
Definition GTools.cpp:1386
bool xml_has_par(const GXmlElement &xml, const std::string &name)
Checks if parameter with given name in XML element exists.
Definition GTools.cpp:1596
int iter_rho(const double &rho_max, const double &resolution, const int &iter_min, const int &iter_max)
Determine number of radial Romberg iterations.
const double rad2deg
Definition GMath.hpp:44