GammaLib 2.1.0.dev
Loading...
Searching...
No Matches
GCOMResponse.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * GCOMResponse.cpp - COMPTEL Response class *
3 * ----------------------------------------------------------------------- *
4 * copyright (C) 2012-2024 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 GCOMResponse.cpp
23 * @brief COMPTEL response class implementation
24 * @author Juergen Knoedlseder
25 */
26
27/* __ Includes ___________________________________________________________ */
28#ifdef HAVE_CONFIG_H
29#include <config.h>
30#endif
31#include <string>
32#include <typeinfo>
33#include "GTools.hpp"
34#include "GMath.hpp"
35#include "GVector.hpp"
36#include "GMatrix.hpp"
37#include "GIntegrals.hpp"
38#include "GFits.hpp"
39#include "GCaldb.hpp"
40#include "GEvent.hpp"
41#include "GPhoton.hpp"
42#include "GSource.hpp"
43#include "GEnergy.hpp"
44#include "GTime.hpp"
45#include "GObservation.hpp"
46#include "GFitsImage.hpp"
47#include "GFitsImageFloat.hpp"
48#include "GFilename.hpp"
49#include "GModelSky.hpp"
54#include "GCOMResponse.hpp"
55#include "GCOMObservation.hpp"
56#include "GCOMEventCube.hpp"
57#include "GCOMEventBin.hpp"
58#include "GCOMInstDir.hpp"
60
61
62/* __ Method name definitions ____________________________________________ */
63#define G_IRF "GCOMResponse::irf(GEvent&, GPhoton&, GObservation&)"
64#define G_IRF_SPATIAL "GCOMResponse::irf_spatial(GEvent&, GSource&, "\
65 "GObservation&)"
66#define G_NROI "GCOMResponse::nroi(GModelSky&, GEnergy&, GTime&, "\
67 "GObservation&)"
68#define G_EBOUNDS "GCOMResponse::ebounds(GEnergy&)"
69#define G_BACKPROJECT "GCOMResponse::backproject(GObservation&, GEvents*, "\
70 "GSkyMap*)"
71#define G_IRF_PTSRC "GCOMResponse::irf_ptsrc(GModelSky&, GObservation&, "\
72 "GMatrix*)"
73#define G_IRF_RADIAL "GCOMResponse::irf_radial(GModelSky&, GObservation&, "\
74 "GMatrix*)"
75#define G_IRF_ELLIPTICAL "GCOMResponse::irf_elliptical(GModelSky&, "\
76 " GObservation&, GMatrix*)"
77#define G_IRF_DIFFUSE "GCOMResponse::irf_diffuse(GModelSky&, GObservation&, "\
78 "GMatrix*)"
79
80/* __ Macros _____________________________________________________________ */
81
82/* __ Coding definitions _________________________________________________ */
83#define G_IRF_RADIAL_METHOD 2 //!< 0=integration, 1=direct, 2=mix
84#define G_IRF_ELLIPTICAL_METHOD 2 //!< 0=integration, 1=direct, 2=mix
85#define G_IRF_DIFFUSE_DIRECT //!< Use direct computation for diffuse response
86
87/* __ Debug definitions __________________________________________________ */
88//#define G_DEBUG_COMPUTE_FAQ //!< Debug FAQ computation
89//#define G_DEBUG_IRF_TIMING //!< Time response computation
90
91/* __ Constants __________________________________________________________ */
92
93
94/*==========================================================================
95 = =
96 = Constructors/destructors =
97 = =
98 ==========================================================================*/
99
100/***********************************************************************//**
101 * @brief Void constructor
102 *
103 * Creates an empty COMPTEL response.
104 ***************************************************************************/
106{
107 // Initialise members
108 init_members();
109
110 // Return
111 return;
112}
113
114
115/***********************************************************************//**
116 * @brief Copy constructor
117 *
118 * @param[in] rsp COM response.
119 **************************************************************************/
121{
122 // Initialise members
123 init_members();
124
125 // Copy members
126 copy_members(rsp);
127
128 // Return
129 return;
130}
131
132
133/***********************************************************************//**
134 * @brief Response constructor
135 *
136 * @param[in] caldb Calibration database.
137 * @param[in] iaqname IAQ file name.
138 *
139 * Create COMPTEL response by loading an IAQ file from a calibration
140 * database.
141 ***************************************************************************/
143 const std::string& iaqname) : GResponse()
144{
145 // Initialise members
146 init_members();
147
148 // Set calibration database
149 this->caldb(caldb);
150
151 // Load IRF
152 this->load(iaqname);
153
154 // Return
155 return;
156}
157
158
159/***********************************************************************//**
160 * @brief Destructor
161 *
162 * Destroys instance of COMPTEL response object.
163 ***************************************************************************/
165{
166 // Free members
167 free_members();
168
169 // Return
170 return;
171}
172
173
174/*==========================================================================
175 = =
176 = Operators =
177 = =
178 ==========================================================================*/
179
180/***********************************************************************//**
181 * @brief Assignment operator
182 *
183 * @param[in] rsp COMPTEL response.
184 * @return COMPTEL response.
185 *
186 * Assigns COMPTEL response object to another COMPTEL response object. The
187 * assignment performs a deep copy of all information, hence the original
188 * object from which the assignment has been performed can be destroyed after
189 * this operation without any loss of information.
190 ***************************************************************************/
192{
193 // Execute only if object is not identical
194 if (this != &rsp) {
195
196 // Copy base class members
197 this->GResponse::operator=(rsp);
198
199 // Free members
200 free_members();
201
202 // Initialise members
203 init_members();
204
205 // Copy members
206 copy_members(rsp);
207
208 } // endif: object was not identical
209
210 // Return this object
211 return *this;
212}
213
214
215/*==========================================================================
216 = =
217 = Public methods =
218 = =
219 ==========================================================================*/
220
221/***********************************************************************//**
222 * @brief Clear instance
223 *
224 * Clears COMPTEL response object by resetting all members to an initial
225 * state. Any information that was present in the object before will be lost.
226 ***************************************************************************/
228{
229 // Free class members (base and derived classes, derived class first)
230 free_members();
232
233 // Initialise members
235 init_members();
236
237 // Return
238 return;
239}
240
241
242/***********************************************************************//**
243 * @brief Clone instance
244 *
245 * @return Pointer to deep copy of COMPTEL response.
246 ***************************************************************************/
248{
249 return new GCOMResponse(*this);
250}
251
252
253/***********************************************************************//**
254 * @brief Return value of instrument response function
255 *
256 * @param[in] event Observed event.
257 * @param[in] photon Incident photon.
258 * @param[in] obs Observation.
259 * @return Instrument response function (\f$cm^2 sr^{-1}\f$)
260 *
261 * @exception GException::invalid_argument
262 * Observation is not a COMPTEL observation.
263 * Event is not a COMPTEL event bin.
264 * @exception GException::invalid_value
265 * Response not initialised with a valid IAQ
266 *
267 * Returns the instrument response function for a given observed photon
268 * direction as function of the assumed true photon direction. The result
269 * is given by
270 *
271 * \f[
272 * {\tt IRF} = \frac{{\tt IAQ} \times {\tt DRG} \times {\tt DRX}}
273 * {T \times {\tt TOFCOR}} \times {\tt PHASECOR}
274 * \f]
275 *
276 * where
277 * - \f${\tt IRF}\f$ is the instrument response function (\f$cm^2 sr^{-1}\f$),
278 * - \f${\tt IAQ}\f$ is the COMPTEL response matrix (\f$sr^{-1}\f$),
279 * - \f${\tt DRG}\f$ is the geometry factor (probability),
280 * - \f${\tt DRX}\f$ is the exposure (\f$cm^2 s\f$),
281 * - \f$T\f$ is the ontime (\f$s\f$), and
282 * - \f${\tt TOFCOR}\f$ is a correction that accounts for the Time of Flight
283 * selection window.
284 * - \f${\tt PHASECOR}\f$ is a correction that accounts for pulsar phase
285 * selection.
286 *
287 * The observed photon direction is spanned by the three values \f$\Chi\f$,
288 * \f$\Psi\f$, and \f$\bar{\varphi})\f$. \f$\Chi\f$ and \f$\Psi\f$ is the
289 * scatter direction of the event, given in sky coordinates.
290 * \f$\bar{\varphi}\f$ is the Compton scatter angle, computed from the
291 * energy deposits in the two detector planes.
292 ***************************************************************************/
293double GCOMResponse::irf(const GEvent& event,
294 const GPhoton& photon,
295 const GObservation& obs) const
296{
297 // Extract COMPTEL observation
298 const GCOMObservation* observation = dynamic_cast<const GCOMObservation*>(&obs);
299 if (observation == NULL) {
300 std::string cls = std::string(typeid(&obs).name());
301 std::string msg = "Observation of type \""+cls+"\" is not a COMPTEL "
302 "observations. Please specify a COMPTEL observation "
303 "as argument.";
305 }
306
307 // Extract COMPTEL event cube
308 const GCOMEventCube* cube = dynamic_cast<const GCOMEventCube*>(observation->events());
309 if (cube == NULL) {
310 std::string cls = std::string(typeid(&cube).name());
311 std::string msg = "Event cube of type \""+cls+"\" is not a COMPTEL "
312 "event cube. Please specify a COMPTEL event cube "
313 "as argument.";
315 }
316
317 // Extract COMPTEL event bin
318 const GCOMEventBin* bin = dynamic_cast<const GCOMEventBin*>(&event);
319 if (bin == NULL) {
320 std::string cls = std::string(typeid(&event).name());
321 std::string msg = "Event of type \""+cls+"\" is not a COMPTEL event. "
322 "Please specify a COMPTEL event as argument.";
324 }
325
326 // Throw an exception if COMPTEL response is not set or if
327 if (m_iaq.empty()) {
328 std::string msg = "COMPTEL response is empty. Please initialise the "
329 "response with an \"IAQ\".";
331 }
332 else if (m_phigeo_bin_size == 0.0) {
333 std::string msg = "COMPTEL response has a zero Phigeo bin size. "
334 "Please initialise the response with a valid "
335 "\"IAQ\".";
337 }
338
339 // Extract event parameters
340 const GCOMInstDir& obsDir = bin->dir();
341
342 // Extract photon parameters
343 const GSkyDir& srcDir = photon.dir();
344 const GTime& srcTime = photon.time();
345
346 // Compute angle between true photon arrival direction and scatter
347 // direction (Chi,Psi)
348 double phigeo = srcDir.dist_deg(obsDir.dir());
349
350 // Compute scatter angle index
351 int iphibar = int(obsDir.phibar() / m_phibar_bin_size);
352
353 // Initialise IRF
354 double iaq = 0.0;
355 double irf = 0.0;
356
357 // Extract IAQ value by linear inter/extrapolation in Phigeo
358 if (iphibar < m_phibar_bins) {
359 double phirat = phigeo / m_phigeo_bin_size; // 0.5 at bin centre
360 int iphigeo = int(phirat); // index into which Phigeo falls
361 double eps = phirat - iphigeo - 0.5; // 0.0 at bin centre [-0.5, 0.5[
362 if (iphigeo < m_phigeo_bins) {
363 int i = iphibar * m_phigeo_bins + iphigeo;
364 if (eps < 0.0) { // interpolate towards left
365 if (iphigeo > 0) {
366 iaq = (1.0 + eps) * m_iaq[i] - eps * m_iaq[i-1];
367 }
368 else {
369 iaq = (1.0 - eps) * m_iaq[i] + eps * m_iaq[i+1];
370 }
371 }
372 else { // interpolate towards right
373 if (iphigeo < m_phigeo_bins-1) {
374 iaq = (1.0 - eps) * m_iaq[i] + eps * m_iaq[i+1];
375 }
376 else {
377 iaq = (1.0 + eps) * m_iaq[i] - eps * m_iaq[i-1];
378 }
379 }
380 }
381 }
382
383 // Continue only if IAQ is positive
384 if (iaq > 0.0) {
385
386 // Get DRG value (unit: probability)
387 double drg = observation->drg().map()(obsDir.dir(), iphibar);
388
389 // Get DRX value (unit: cm^2 sec)
390 double drx = observation->drx().map()(srcDir);
391
392 // Get ontime (unit: s)
393 double ontime = observation->ontime();
394
395 // Get ToF correction
396 double tofcor = cube->dre().tof_correction();
397
398 // Get pulsar phase correction
399 double phasecor = cube->dre().phase_correction();
400
401 // Compute IRF value
402 irf = iaq * drg * drx / (ontime * tofcor) * phasecor;
403
404 // Apply deadtime correction
405 irf *= obs.deadc(srcTime);
406
407 // Make sure that IRF is positive
408 if (irf < 0.0) {
409 irf = 0.0;
410 }
411
412 // Compile option: Check for NaN/Inf
413 #if defined(G_NAN_CHECK)
415 std::cout << "*** ERROR: GCOMResponse::irf:";
416 std::cout << " NaN/Inf encountered";
417 std::cout << " (irf=" << irf;
418 std::cout << ", iaq=" << iaq;
419 std::cout << ", drg=" << drg;
420 std::cout << ", drx=" << drx;
421 std::cout << ")";
422 std::cout << std::endl;
423 }
424 #endif
425
426 } // endif: IAQ was positive
427
428 // Return IRF value
429 return irf;
430}
431
432
433/***********************************************************************//**
434 * @brief Return integral of event probability for a given sky model over ROI
435 *
436 * @param[in] model Sky model.
437 * @param[in] obsEng Observed photon energy.
438 * @param[in] obsTime Observed photon arrival time.
439 * @param[in] obs Observation.
440 * @return 0.0
441 *
442 * @exception GException::feature_not_implemented
443 * Method is not implemented.
444 ***************************************************************************/
445double GCOMResponse::nroi(const GModelSky& model,
446 const GEnergy& obsEng,
447 const GTime& obsTime,
448 const GObservation& obs) const
449{
450 // Method is not implemented
451 std::string msg = "Spatial integration of sky model over the data space "
452 "is not implemented.";
454
455 // Return Npred
456 return (0.0);
457}
458
459
460/***********************************************************************//**
461 * @brief Return true energy boundaries for a specific observed energy
462 *
463 * @param[in] obsEnergy Observed Energy.
464 * @return True energy boundaries for given observed energy.
465 *
466 * @exception GException::feature_not_implemented
467 * Method is not implemented.
468 ***************************************************************************/
470{
471 // Initialise an empty boundary object
473
474 // Throw an exception
475 std::string msg = "Energy dispersion not implemented.";
477
478 // Return energy boundaries
479 return ebounds;
480}
481
482
483/***********************************************************************//**
484 * @brief Load COMPTEL response.
485 *
486 * @param[in] rspname COMPTEL response name.
487 *
488 * Loads the COMPTEL response with specified name @p rspname.
489 *
490 * The method first attempts to interpret @p rspname as a file name and to
491 * load the corresponding response.
492 *
493 * If @p rspname is not a FITS file the method searches for an appropriate
494 * response in the calibration database. If no appropriate response is found,
495 * the method takes the CALDB root path and response name to build the full
496 * path to the response file, and tries to load the response from these
497 * paths.
498 *
499 * If also this fails an exception is thrown.
500 ***************************************************************************/
501void GCOMResponse::load(const std::string& rspname)
502{
503 // Clear instance but conserve calibration database
505 clear();
506 m_caldb = caldb;
507
508 // Save response name
510
511 // Interpret response name as a FITS file name
512 GFilename filename(rspname);
513
514 // If the filename does not exist the try getting the response from the
515 // calibration database
516 if (!filename.is_fits()) {
517
518 // Get GCaldb response
519 filename = m_caldb.filename("","","IAQ","","",rspname);
520
521 // If filename is empty then build filename from CALDB root path
522 // and response name
523 if (filename.is_empty()) {
525 if (!filename.exists()) {
526 GFilename testname = filename + ".fits";
527 if (testname.exists()) {
528 filename = testname;
529 }
530 }
531 }
532
533 } // endif: response name is not a FITS file
534
535 // Open FITS file
536 GFits fits(filename);
537
538 // Get IAQ image
539 const GFitsImage& iaq = *fits.image(0);
540
541 // Read IAQ
542 read(iaq);
543
544 // Close IAQ FITS file
545 fits.close();
546
547 // Return
548 return;
549}
550
551
552/***********************************************************************//**
553 * @brief Read COMPTEL response from FITS image.
554 *
555 * @param[in] image FITS image.
556 *
557 * Read the COMPTEL response from IAQ FITS file and convert the IAQ values
558 * into a probability per steradian.
559 *
560 * The IAQ values are divided by the solid angle of a Phigeo bin which is
561 * given by
562 *
563 * \f{eqnarray*}{
564 * \Omega & = & 2 \pi \left[
565 * \left(
566 * 1 - \cos \left( \varphi_{\rm geo} +
567 * \frac{1}{2} \Delta \varphi_{\rm geo} \right)
568 * \right) -
569 * \left(
570 * 1 - \cos \left( \varphi_{\rm geo} -
571 * \frac{1}{2} \Delta \varphi_{\rm geo} \right)
572 * \right) \right] \\
573 * &=& 2 \pi \left[
574 * \cos \left( \varphi_{\rm geo} -
575 * \frac{1}{2} \Delta \varphi_{\rm geo} \right) -
576 * \cos \left( \varphi_{\rm geo} +
577 * \frac{1}{2} \Delta \varphi_{\rm geo} \right)
578 * \right] \\
579 * &=& 4 \pi \sin \left( \varphi_{\rm geo} \right)
580 * \sin \left( \frac{1}{2} \Delta \varphi_{\rm geo} \right)
581 * \f}
582 ***************************************************************************/
584{
585 // Continue only if there are two image axis
586 if (image.naxis() == 2) {
587
588 // Store IAQ dimensions
589 m_phigeo_bins = image.naxes(0);
590 m_phibar_bins = image.naxes(1);
591
592 // Store IAQ axes definitions
593 m_phigeo_ref_value = image.real("CRVAL1");
594 m_phigeo_ref_pixel = image.real("CRPIX1");
595 m_phigeo_bin_size = image.real("CDELT1");
596 m_phibar_ref_value = image.real("CRVAL2");
597 m_phibar_ref_pixel = image.real("CRPIX2");
598 m_phibar_bin_size = image.real("CDELT2");
599
600 // Get axes minima (values of first bin)
605
606 // Compute IAQ size. Continue only if size is positive
607 int size = m_phigeo_bins * m_phibar_bins;
608 if (size > 0) {
609
610 // Allocate memory for IAQ
611 m_iaq.assign(size, 0.0);
612
613 // Copy over IAQ values
614 for (int i = 0; i < size; ++i) {
615 m_iaq[i] = image.pixel(i);
616 }
617
618 } // endif: size was positive
619
620 // Precompute variable for conversion
621 double phigeo_min = m_phigeo_min * gammalib::deg2rad;
622 double phigeo_bin = m_phigeo_bin_size * gammalib::deg2rad;
623 double omega0 = gammalib::fourpi * std::sin(0.5 * phigeo_bin);
624
625 // Convert IAQ matrix from probability per Phigeo bin into a
626 // probability per steradian
627 for (int iphigeo = 0; iphigeo < m_phigeo_bins; ++iphigeo) {
628 double phigeo = iphigeo * phigeo_bin + phigeo_min;
629 double omega = omega0 * std::sin(phigeo);
630 for (int iphibar = 0; iphibar < m_phibar_bins; ++iphibar) {
631 m_iaq[iphigeo+iphibar*m_phigeo_bins] /= omega;
632 }
633 }
634
635 // Compute FAQ
636 compute_faq();
637
638 } // endif: image had two axes
639
640 // Return
641 return;
642}
643
644
645/***********************************************************************//**
646 * @brief Write COMPTEL response into FITS image.
647 *
648 * @param[in] image FITS image.
649 *
650 * Writes the COMPTEL response into an IAQ FITS file.
651 ***************************************************************************/
653{
654 // Continue only if response is not empty
655 if (m_phigeo_bins > 0 && m_phibar_bins > 0) {
656
657 // Initialise image
659
660 // Convert IAQ matrix from probability per steradian into
661 //probability per Phigeo bin
662 double omega0 = gammalib::fourpi *
663 std::sin(0.5 * m_phigeo_bin_size * gammalib::deg2rad);
664 for (int iphigeo = 0; iphigeo < m_phigeo_bins; ++iphigeo) {
665 double phigeo = iphigeo * m_phigeo_bin_size + m_phigeo_min;
666 double omega = omega0 * std::sin(phigeo * gammalib::deg2rad);
667 for (int iphibar = 0; iphibar < m_phibar_bins; ++iphibar) {
668 int i = iphigeo + iphibar*m_phigeo_bins;
669 image(iphigeo, iphibar) = m_iaq[i] * omega;
670 }
671 }
672
673 // Set header keywords
674 image.card("CTYPE1", "Phigeo", "Geometrical scatter angle");
675 image.card("CRVAL1", m_phigeo_ref_value,
676 "[deg] Geometrical scatter angle for reference bin");
677 image.card("CDELT1", m_phigeo_bin_size,
678 "[deg] Geometrical scatter angle bin size");
679 image.card("CRPIX1", m_phigeo_ref_pixel,
680 "Reference bin of geometrical scatter angle");
681 image.card("CTYPE2", "Phibar", "Compton scatter angle");
682 image.card("CRVAL2", m_phibar_ref_value,
683 "[deg] Compton scatter angle for reference bin");
684 image.card("CDELT2", m_phibar_bin_size, "[deg] Compton scatter angle bin size");
685 image.card("CRPIX2", m_phibar_ref_pixel,
686 "Reference bin of Compton scatter angle");
687 image.card("BUNIT", "Probability per bin", "Unit of bins");
688 image.card("TELESCOP", "GRO", "Name of telescope");
689 image.card("INSTRUME", "COMPTEL", "Name of instrument");
690 image.card("ORIGIN", "GammaLib", "Origin of FITS file");
691 image.card("OBSERVER", "Unknown", "Observer that created FITS file");
692
693 } // endif: response was not empty
694
695 // Return
696 return;
697}
698
699
700/***********************************************************************//**
701 * @brief Load response cache.
702 *
703 * @param[in] filename Response cache filename.
704 *
705 * Loads response cache from FITS file.
706 ***************************************************************************/
708{
709 // Load response vector cache
710 m_irf_vector_cache.load(filename);
711
712 // Return
713 return;
714}
715
716
717/***********************************************************************//**
718 * @brief Save response cache.
719 *
720 * @param[in] filename Response cache filename.
721 *
722 * Saves response cache from FITS file.
723 ***************************************************************************/
724void GCOMResponse::save_cache(const GFilename& filename) const
725{
726 // Save response vector cache
727 m_irf_vector_cache.save(filename);
728
729 // Return
730 return;
731}
732
733
734/***********************************************************************//**
735 * @brief Backproject events using instrument response function to sky map
736 *
737 * @param[in] obs Observation.
738 * @param[in] events Events.
739 * @param[in,out] map Sky map.
740 *
741 * @exception GException::invalid_argument
742 * Events pointer invalid.
743 * Sky map pointer invalid.
744 * Observation is not a COMPTEL observation.
745 * Events are not a COMPTEL event cube.
746 * @exception GException::invalid_value
747 * Response not initialised with a valid IAQ
748 * Incompatible IAQ encountered
749 *
750 * Backprojects events using the instrument response function to sky map
751 * using
752 *
753 * \f[
754 * f_j = \sum_i n_i R_{ij}
755 * \f]
756 *
757 * where
758 * - \f$f_j\f$ is the sky map pixel \f$j\f$,
759 * - \f$n_i\f$ is the event bin \f$i\f$, and
760 * - \f$R_{ij}\f$ is the response function for event bin \f$i\f$ and
761 * sky map pixel \f$j\f$.
762 *
763 * If the sky map contains at least two maps, the second map will contain
764 * on output the normalisation map
765 *
766 * \f[
767 * f_j = \sum_i R_{ij}
768 * \f]
769 ***************************************************************************/
771 const GEvents* events,
772 GSkyMap* map) const
773{
774 // Throw an exception if the events pointer is invalid
775 if (events == NULL) {
776 std::string msg = "Invalid events pointer. Please specify a "
777 "valid COMPTEL events pointer as argument.";
779 }
780
781 // Throw an exception if the sky map pointer is invalid
782 if (map == NULL) {
783 std::string msg = "Invalid sky map pointer. Please specify a "
784 "pointer to a valid sky map as argument.";
786 }
787
788 // Extract COMPTEL observation
789 const GCOMObservation* obs_ptr = dynamic_cast<const GCOMObservation*>(&obs);
790 if (obs_ptr == NULL) {
791 std::string cls = std::string(typeid(&obs).name());
792 std::string msg = "Observation of type \""+cls+"\" is not a COMPTEL "
793 "observations. Please specify a COMPTEL observation "
794 "as argument.";
796 }
797
798 // Extract COMPTEL event cube
799 const GCOMEventCube* cube = dynamic_cast<const GCOMEventCube*>(events);
800 if (cube == NULL) {
801 std::string cls = std::string(typeid(*events).name());
802 std::string msg = "Events of type \""+cls+"\" is not a COMPTEL event "
803 "cube. Please specify an event cube.";
805 }
806
807 // Throw an exception if COMPTEL response is not set or if
808 if (m_iaq.empty()) {
809 std::string msg = "COMPTEL response is empty. Please initialise the "
810 "response with an \"IAQ\".";
812 }
813 else if (m_phigeo_bin_size == 0.0) {
814 std::string msg = "COMPTEL response has a zero Phigeo bin size. "
815 "Please initialise the response with a valid "
816 "\"IAQ\".";
818 }
819
820 // Get const reference to DRI
821 const GCOMDri& dri = cube->dre();
822
823 // Get number of Chi/Psi pixels, Phibar layers and event bins
824 int npix = dri.nchi() * dri.npsi();
825 int nphibar = dri.nphibar();
826 int nevents = dri.size();
827
828 // Throw an exception if the number of Phibar bins does not match the
829 // response
830 if (nphibar != m_phibar_bins) {
831 std::string msg = "DRI has "+gammalib::str(nphibar)+" Phibar layers "
832 "but IAQ has "+gammalib::str(m_phibar_bins)+" Phibar "
833 "bins. Please specify a compatible IAQ.";
835 }
836
837 // Initialise some variables
838 double phigeo_min = m_phigeo_min * gammalib::deg2rad;
839 double phigeo_bin = m_phigeo_bin_size * gammalib::deg2rad;
840 double omega0 = 2.0 * std::sin(0.5 * phigeo_bin);
841 const GSkyMap& drx = obs_ptr->drx().map();
842 const double* drg = obs_ptr->drg().map().pixels();
843
844 // Compute IAQ normalisation (1/s): DEADC / ONTIME (s)
845 double iaq_norm = obs_ptr->deadc() /
846 (obs_ptr->ontime() * cube->dre().tof_correction()) *
847 cube->dre().phase_correction();
848
849 // Pre-compute sky directions for all pixels and DRX values
850 std::vector<GSkyDir> dirs;
851 std::vector<double> drxs;
852 dirs.reserve(map->npix());
853 drxs.reserve(map->npix());
854 for (int isky = 0; isky < map->npix(); ++isky) {
855
856 // Compute sky direction
857 GSkyDir skyDir = map->inx2dir(isky);
858
859 // Push back sky direction
860 dirs.push_back(skyDir);
861
862 // Push back DRX value
863 if (drx.contains(skyDir)) {
864 drxs.push_back(drx(skyDir));
865 }
866 else {
867 drxs.push_back(0.0);
868 }
869
870 } // endfor: precomputation
871
872 // Signal presence of normalisation map
873 bool norm = (map->nmaps() > 1);
874
875 // Loop over Chi and Psi pixels
876 for (int ipix = 0; ipix < npix; ++ipix) {
877
878 // Get sky direction to (Chi,Psi)
879 GSkyDir chipsi = dri.map().inx2dir(ipix);
880
881 // Loop over sky map pixels
882 for (int isky = 0; isky < map->npix(); ++isky) {
883
884 // Get sky direction to skymap pixel
885 GSkyDir& skyDir = dirs[isky];
886
887 // Get distance between (Chi,Psi) and sky direction
888 double phigeo = chipsi.dist_deg(skyDir);
889
890 // Compute interpolation factors
891 double phirat = phigeo / m_phigeo_bin_size; // 0.5 at bin centre
892 int iphigeo = int(phirat); // index into which Phigeo falls
893 double eps = phirat - iphigeo - 0.5; // 0.0 at bin centre [-0.5, 0.5[
894
895 // Fall through if Phigeo is outside range
896 if (iphigeo >= m_phigeo_bins) {
897 continue;
898 }
899
900 // Get DRX value (units: cm^2 s)
901 double drx_value = drxs[isky];
902
903 // Loop over Phibar
904 for (int iphibar = 0; iphibar < nphibar; ++iphibar) {
905
906 // Get DRI index
907 int idri = ipix + iphibar * npix;
908
909 // Fall through if Chi and Psi pixel is empty
910 if (dri[idri] == 0.0) {
911 continue;
912 }
913
914 // Get IAQ index
915 int i = iphibar * m_phigeo_bins + iphigeo;
916
917 // Initialise IAQ
918 double iaq = 0.0;
919
920 // Compute IAQ
921 if (eps < 0.0) { // interpolate towards left
922 if (iphigeo > 0) {
923 iaq = (1.0 + eps) * m_iaq[i] - eps * m_iaq[i-1];
924 }
925 else {
926 iaq = (1.0 - eps) * m_iaq[i] + eps * m_iaq[i+1];
927 }
928 }
929 else { // interpolate towards right
930 if (iphigeo < m_phigeo_bins-1) {
931 iaq = (1.0 - eps) * m_iaq[i] + eps * m_iaq[i+1];
932 }
933 else {
934 iaq = (1.0 + eps) * m_iaq[i] - eps * m_iaq[i-1];
935 }
936 }
937
938 // Fall through if IAQ is not positive
939 if (iaq <= 0.0) {
940 continue;
941 }
942
943 // Compute IRF value (units: photons -> events)
944 double irf = iaq * drg[idri] * iaq_norm * drx_value;
945
946 // Add IRF value if it is positive
947 if (irf > 0.0) {
948 (*map)(isky) += irf * dri[idri];
949 if (norm) {
950 (*map)(isky,1) += irf;
951 }
952 }
953
954 } // endif: looped over Phibar
955
956 } // endfor: looped over sky map pixels
957
958 } // endfor: looped over Chi and Psi pixels
959
960 // Return
961 return;
962}
963
964
965/***********************************************************************//**
966 * @brief Print COMPTEL response information
967 *
968 * @param[in] chatter Chattiness.
969 * @return String containing COMPTEL response information.
970 ***************************************************************************/
971std::string GCOMResponse::print(const GChatter& chatter) const
972{
973 // Initialise result string
974 std::string result;
975
976 // Continue only if chatter is not silent
977 if (chatter != SILENT) {
978
979 // Append header
980 result.append("=== GCOMResponse ===");
981
982 // Append response name
983 result.append("\n"+gammalib::parformat("Response name")+m_rspname);
984
985 // EXPLICIT: Append detailed information
986 if (chatter >= EXPLICIT) {
987
988 // Append information
989 result.append("\n"+gammalib::parformat("Number of Phigeo bins"));
990 result.append(gammalib::str(m_phigeo_bins));
991 result.append("\n"+gammalib::parformat("Number of Phibar bins"));
992 result.append(gammalib::str(m_phibar_bins));
993 result.append("\n"+gammalib::parformat("Phigeo reference value"));
994 result.append(gammalib::str(m_phigeo_ref_value)+" deg");
995 result.append("\n"+gammalib::parformat("Phigeo reference pixel"));
996 result.append(gammalib::str(m_phigeo_ref_pixel));
997 result.append("\n"+gammalib::parformat("Phigeo bin size"));
998 result.append(gammalib::str(m_phigeo_bin_size)+" deg");
999 result.append("\n"+gammalib::parformat("Phigeo first bin value"));
1000 result.append(gammalib::str(m_phigeo_min)+" deg");
1001 result.append("\n"+gammalib::parformat("Phibar reference value"));
1002 result.append(gammalib::str(m_phibar_ref_value)+" deg");
1003 result.append("\n"+gammalib::parformat("Phibar reference pixel"));
1004 result.append(gammalib::str(m_phibar_ref_pixel));
1005 result.append("\n"+gammalib::parformat("Phibar bin size"));
1006 result.append(gammalib::str(m_phibar_bin_size)+" deg");
1007 result.append("\n"+gammalib::parformat("Phibar first bin value"));
1008 result.append(gammalib::str(m_phibar_min)+" deg");
1009
1010 }
1011
1012 // VERBOSE: Append calibration database
1013 if (chatter == VERBOSE) {
1014 result.append("\n"+m_caldb.print(chatter));
1015 }
1016
1017 } // endif: chatter was not silent
1018
1019 // Return result
1020 return result;
1021}
1022
1023
1024/*==========================================================================
1025 = =
1026 = Private methods =
1027 = =
1028 ==========================================================================*/
1029
1030/***********************************************************************//**
1031 * @brief Initialise class members
1032 ***************************************************************************/
1034{
1035 // Initialise members
1036 m_caldb.clear();
1037 m_rspname.clear();
1038 m_iaq.clear();
1039 m_faq.clear();
1040 m_faq_zero.clear();
1041 m_faq_native.clear();
1043 m_phigeo_bins = 0;
1044 m_phibar_bins = 0;
1045 m_faq_bins = 0;
1046 m_phigeo_ref_value = 0.0;
1047 m_phigeo_ref_pixel = 0.0;
1048 m_phigeo_bin_size = 0.0;
1049 m_phigeo_min = 0.0;
1050 m_phibar_ref_value = 0.0;
1051 m_phibar_ref_pixel = 0.0;
1052 m_phibar_bin_size = 0.0;
1053 m_phibar_min = 0.0;
1054
1055 // Return
1056 return;
1057}
1058
1059
1060/***********************************************************************//**
1061 * @brief Copy class members
1062 *
1063 * @param[in] rsp COMPTEL response.
1064 ***************************************************************************/
1066{
1067 // Copy attributes
1068 m_caldb = rsp.m_caldb;
1069 m_rspname = rsp.m_rspname;
1070 m_iaq = rsp.m_iaq;
1071 m_faq = rsp.m_faq;
1072 m_faq_zero = rsp.m_faq_zero;
1077 m_faq_bins = rsp.m_faq_bins;
1086
1087 // Return
1088 return;
1089}
1090
1091
1092/***********************************************************************//**
1093 * @brief Delete class members
1094 ***************************************************************************/
1096{
1097 // Return
1098 return;
1099}
1100
1101
1102/***********************************************************************//**
1103 * @brief Compute FAQ
1104 *
1105 * Computes FAQ from IAQ that is needed for direction projection computation
1106 ***************************************************************************/
1108{
1109 // Initialise FAQ members
1110 m_faq.clear();
1111 m_faq_zero.clear();
1113 m_faq_bins = 0;
1114
1115 // Continue only if there are phigeo and phibar bins
1116 if ((m_phigeo_bins > 0) && (m_phibar_bins > 0)) {
1117
1118 // Determine number of FAQ bins
1119 int bins = 2 * m_phigeo_bins - 1;
1120
1121 // Initialise vectors
1122 m_faq_bins = bins * bins;
1123 m_faq_zero = std::vector<bool>(m_faq_bins, true);
1124 m_faq_native = std::vector<GVector>(m_faq_bins);
1126
1127 // Initialise FAQ skymap in celestial coordinates
1128 m_faq = GSkyMap("ARC", "CEL", 0.0, 0.0,
1130 bins, bins, m_phibar_bins);
1131
1132 // Loop over Psi
1133 for (int ipsi = 0, ifaq = 0; ipsi < bins; ++ipsi) {
1134
1135 // Compute Psi
1136 double psi = (ipsi - m_phigeo_bins + 1) * m_phigeo_bin_size;
1137 double psi_rad = psi * gammalib::deg2rad;
1138 double psi2 = psi * psi;
1139
1140 // Loop over Chi
1141 for (int ichi = 0; ichi < bins; ++ichi, ++ifaq) {
1142
1143 // Compute Chi
1144 double chi = (ichi - m_phigeo_bins + 1) * m_phigeo_bin_size;
1145 double chi_rad = chi * gammalib::deg2rad;
1146
1147 // Compute phigeo (degrees)
1148 double phigeo = std::sqrt(chi*chi + psi2);
1149
1150 // Compute native FAQ vector
1151 double zenith = phigeo * gammalib::deg2rad;
1152 double cos_zenith = std::cos(zenith);
1153 double sin_zenith = std::sin(zenith);
1154 double azimuth = 0.0 - std::atan2(psi_rad, chi_rad);
1155 double cos_phi = std::cos(azimuth);
1156 double sin_phi = std::sin(azimuth);
1157 m_faq_native[ifaq] = GVector(-cos_phi * sin_zenith,
1158 sin_phi * sin_zenith,
1159 cos_zenith);
1160
1161 // Store solid angle of pixel
1162 m_faq_solidangle[ifaq] = m_faq.solidangle(ifaq);
1163
1164 // Compute interpolation factors
1165 double phirat = phigeo / m_phigeo_bin_size; // 0.5 at bin centre
1166 int iphigeo = int(phirat); // index into which Phigeo falls
1167 double eps = phirat - iphigeo - 0.5; // 0.0 at bin centre [-0.5, 0.5[
1168
1169 // If Phigeo is in range then compute the IRF value for all
1170 // Phibar layers
1171 if (iphigeo < m_phigeo_bins) {
1172
1173 // Loop over Phibar
1174 for (int iphibar = 0; iphibar < m_phibar_bins; ++iphibar) {
1175
1176 // Get IAQ index
1177 int i = iphibar * m_phigeo_bins + iphigeo;
1178
1179 // Initialise FAQ
1180 double faq = 0.0;
1181
1182 // Compute IAQ
1183 if (eps < 0.0) { // interpolate towards left
1184 if (iphigeo > 0) {
1185 faq = (1.0 + eps) * m_iaq[i] - eps * m_iaq[i-1];
1186 }
1187 else {
1188 faq = (1.0 - eps) * m_iaq[i] + eps * m_iaq[i+1];
1189 }
1190 }
1191 else { // interpolate towards right
1192 if (iphigeo < m_phigeo_bins-1) {
1193 faq = (1.0 - eps) * m_iaq[i] + eps * m_iaq[i+1];
1194 }
1195 else {
1196 faq = (1.0 + eps) * m_iaq[i] - eps * m_iaq[i-1];
1197 }
1198 }
1199
1200 // Continue only if FAQ is positive
1201 if (faq > 0.0) {
1202
1203 // Normalise FAQ value
1204 //faq *= m_phigeo_bin_size/(gammalib::twopi * phigeo);
1205 //faq *= m_phigeo_bin_size/(gammalib::twopi * phigeo)/m_faq.solidangle(ifaq);
1206 //faq /= m_faq.solidangle(ifaq);
1207
1208 // Store FAQ value
1209 m_faq(ifaq, iphibar) = faq;
1210
1211 // Signal that FAQ bin is not zero
1212 m_faq_zero[ifaq] = false;
1213
1214 } // endif: faq was positive
1215
1216 } // endfor: looped over Phibar
1217
1218 } // endif: Phigeo was in valid range
1219
1220 } // endfor: looped over Psi
1221
1222 } // endfor: looped over Chi
1223
1224 // Debug FAQ computation
1225 #if defined(G_DEBUG_COMPUTE_FAQ)
1226 std::cout << "GCOMResponse::compute_faq" << std::endl;
1227 m_faq.save("faq.fits", true);
1228 for (int iphibar = 0; iphibar < m_phibar_bins; ++iphibar) {
1229 double iaq_sum = 0.0;
1230 double faq_sum = 0.0;
1231 for (int iphigeo = 0; iphigeo < m_phigeo_bins; ++iphigeo) {
1232 int i = iphibar * m_phigeo_bins + iphigeo;
1233 iaq_sum += m_iaq[i];
1234 }
1235 for (int ipix = 0; ipix < m_faq.npix(); ++ipix) {
1236 faq_sum += m_faq(ipix, iphibar);
1237 }
1238 std::cout << "iphibar=" << iphibar;
1239 std::cout << " iaq=" << iaq_sum;
1240 std::cout << " faq=" << faq_sum;
1241 std::cout << " ratio=" << faq_sum/iaq_sum << std::endl;
1242 }
1243 #endif
1244
1245 } // endif: there were phigeo and phibar bins
1246
1247 // Return
1248 return;
1249}
1250
1251
1252/***********************************************************************//**
1253 * @brief Return instrument response to point source
1254 *
1255 * @param[in] model Sky model.
1256 * @param[in] obs Observation.
1257 * @param[out] gradients Gradients matrix.
1258 * @return Instrument response to point source for all events in
1259 * observation (\f$cm^2\f$).
1260 *
1261 * @exception GException::invalid_argument
1262 * Observation is not a COMPTEL observation.
1263 * Event is not a COMPTEL event bin.
1264 * @exception GException::invalid_value
1265 * Response not initialised with a valid IAQ
1266 * Incompatible IAQ encountered
1267 *
1268 * Returns the instrument response to a point source for all events in the
1269 * observations.
1270 *
1271 * @p gradients is an optional matrix where the number of rows corresponds
1272 * to the number of events in the observation and the number of columns
1273 * corresponds to the number of spatial model parameters. Since for point
1274 * sources no gradients are computed, the method does not alter the
1275 * content of @p gradients.
1276 ***************************************************************************/
1278 const GObservation& obs,
1279 GMatrix* gradients) const
1280{
1281 // Extract COMPTEL observation
1282 const GCOMObservation* obs_ptr = dynamic_cast<const GCOMObservation*>(&obs);
1283 if (obs_ptr == NULL) {
1284 std::string cls = std::string(typeid(&obs).name());
1285 std::string msg = "Observation of type \""+cls+"\" is not a COMPTEL "
1286 "observations. Please specify a COMPTEL observation "
1287 "as argument.";
1289 }
1290
1291 // Extract COMPTEL event cube
1292 const GCOMEventCube* cube = dynamic_cast<const GCOMEventCube*>(obs_ptr->events());
1293 if (cube == NULL) {
1294 std::string msg = "Observation \""+obs.name()+"\" ("+obs.id()+") does "
1295 "not contain a COMPTEL event cube. Please specify "
1296 "a COMPTEL observation containing and event cube.";
1298 }
1299
1300 // Throw an exception if COMPTEL response is not set or if
1301 if (m_iaq.empty()) {
1302 std::string msg = "COMPTEL response is empty. Please initialise the "
1303 "response with an \"IAQ\".";
1305 }
1306 else if (m_phigeo_bin_size == 0.0) {
1307 std::string msg = "COMPTEL response has a zero Phigeo bin size. "
1308 "Please initialise the response with a valid "
1309 "\"IAQ\".";
1311 }
1312
1313 // Get number of Chi/Psi pixels, Phibar layers and event bins
1314 int npix = cube->naxis(0) * cube->naxis(1);
1315 int nphibar = cube->naxis(2);
1316 int nevents = cube->size();
1317
1318 // Throw an exception if the number of Phibar bins does not match the
1319 // response
1320 if (nphibar != m_phibar_bins) {
1321 std::string msg = "DRE has "+gammalib::str(nphibar)+" Phibar layers "
1322 "but IAQ has "+gammalib::str(m_phibar_bins)+" Phibar "
1323 "bins. Please specify a compatible IAQ.";
1325 }
1326
1327 // Initialise result
1328 GVector irfs(nevents);
1329
1330 // Get point source direction
1331 GSkyDir srcDir =
1332 static_cast<const GModelSpatialPointSource*>(model.spatial())->dir();
1333
1334 // Get IAQ normalisation (cm2): DRX (cm2 s) * DEADC / ONTIME (s)
1335 double iaq_norm = obs_ptr->drx().map()(srcDir) * obs_ptr->deadc() /
1336 (obs_ptr->ontime() * cube->dre().tof_correction()) *
1337 cube->dre().phase_correction();
1338
1339 // Get pointer to DRG pixels
1340 const double* drg = obs_ptr->drg().map().pixels();
1341
1342 // Loop over Chi and Psi
1343 for (int ipix = 0; ipix < npix; ++ipix) {
1344
1345 // Get pointer to event bin
1346 const GCOMEventBin* bin = (*cube)[ipix];
1347
1348 // Get reference to instrument direction
1349 const GCOMInstDir& obsDir = bin->dir();
1350
1351 // Get reference to Phigeo sky direction
1352 const GSkyDir& phigeoDir = obsDir.dir();
1353
1354 // Compute angle between true photon arrival direction and scatter
1355 // direction (Chi,Psi)
1356 double phigeo = srcDir.dist_deg(phigeoDir);
1357
1358 // Compute interpolation factors
1359 double phirat = phigeo / m_phigeo_bin_size; // 0.5 at bin centre
1360 int iphigeo = int(phirat); // index into which Phigeo falls
1361 double eps = phirat - iphigeo - 0.5; // 0.0 at bin centre [-0.5, 0.5[
1362
1363 // If Phigeo is in range then compute the IRF value for all
1364 // Phibar layers
1365 if (iphigeo < m_phigeo_bins) {
1366
1367 // Loop over Phibar
1368 for (int iphibar = 0; iphibar < nphibar; ++iphibar) {
1369
1370 // Get IAQ index
1371 int i = iphibar * m_phigeo_bins + iphigeo;
1372
1373 // Initialise IAQ
1374 double iaq = 0.0;
1375
1376 // Compute IAQ
1377 if (eps < 0.0) { // interpolate towards left
1378 if (iphigeo > 0) {
1379 iaq = (1.0 + eps) * m_iaq[i] - eps * m_iaq[i-1];
1380 }
1381 else {
1382 iaq = (1.0 - eps) * m_iaq[i] + eps * m_iaq[i+1];
1383 }
1384 }
1385 else { // interpolate towards right
1386 if (iphigeo < m_phigeo_bins-1) {
1387 iaq = (1.0 - eps) * m_iaq[i] + eps * m_iaq[i+1];
1388 }
1389 else {
1390 iaq = (1.0 + eps) * m_iaq[i] - eps * m_iaq[i-1];
1391 }
1392 }
1393
1394 // Continue only if IAQ is positive
1395 if (iaq > 0.0) {
1396
1397 // Get DRI index
1398 int idri = ipix + iphibar * npix;
1399
1400 // Compute IRF value
1401 double irf = iaq * drg[idri] * iaq_norm;
1402
1403 // Make sure that IRF is positive
1404 if (irf < 0.0) {
1405 irf = 0.0;
1406 }
1407
1408 // Store IRF value
1409 irfs[idri] = irf;
1410
1411 } // endif: IAQ was positive
1412
1413 } // endfor: looped over Phibar
1414
1415 } // endif: Phigeo was in valid range
1416
1417 } // endfor: looped over Chi and Psi
1418
1419 // Return IRF vector
1420 return irfs;
1421}
1422
1423
1424/***********************************************************************//**
1425 * @brief Return instrument response to radial source
1426 *
1427 * @param[in] model Sky model.
1428 * @param[in] obs Observation.
1429 * @param[out] gradients Gradients matrix.
1430 * @return Instrument response to radial source for all events in
1431 * observation (\f$cm^2\f$).
1432 *
1433 * @exception GException::invalid_value
1434 * Response not initialised
1435 * Incompatible IAQ encountered
1436 *
1437 * Returns the instrument response to a radial source sky model for all
1438 * events. The methods works on response vectors that are computed per
1439 * pointing. The method assumes that the spatial model component is of
1440 * type GModelSpatialRadial, that the observations is of type
1441 * GCOMObservation and that the events are of type GCOMEventCube.
1442 * No checking of these assumptions is performed by the method, and not
1443 * respecting the assumptions will results in a segfault.
1444 *
1445 * The method toggles between two convolution methods that are used depending
1446 * on the extent of the radial source. Extent means here the value of the
1447 * radial parameter, which is the radius for a disk or the sigma parameter
1448 * for a Gaussian.
1449 *
1450 * For extents smaller than 10 degrees a numerical integration is performed
1451 * in the system of the radial model that is fast but that may become
1452 * inaccurate for larger extents as the model sampling will exceed the
1453 * resolution of the data space. The number of iterations in the zenith and
1454 * azimuth angle was fixed to 6.
1455 *
1456 * For extents larger than 15 degrees a direct convolution is performed in
1457 * the system of the instrument response function, that is slower, but that
1458 * samples fully the resolution of the data space. The direct convolution is
1459 * however not accurate for angles smaller than about 1 degree, hence it is
1460 * not appropriate to compute for the response for the determination of
1461 * upper limits.
1462 *
1463 * For intermediate extents (in the interval 10 - 15 degrees), both
1464 * convolutions are computed and combined according to a weight that varies
1465 * linearly with source extent. In principle the convolved results should be
1466 * identical in that intermediate range, but computing both and their
1467 * weighted combination assures that no steps occur when varying the source
1468 * extent, which is required for seamless log-likelihood fits.
1469 *
1470 * For details about the choice of the convolution method and its parameters
1471 * see https://gitlab.in2p3.fr/gammalib/gammalib/-/issues/14.
1472 *
1473 * @p gradients is an optional matrix where the number of rows corresponds
1474 * to the number of events in the observation and the number of columns
1475 * corresponds to the number of spatial model parameters. Since for
1476 * radial sources no gradients are computed, the method does not alter the
1477 * content of @p gradients.
1478 ***************************************************************************/
1480 const GObservation& obs,
1481 GMatrix* gradients) const
1482{
1483 // Set constants
1484 static const int iter_rho = 6; //!< 6 iterations
1485 static const int iter_omega = 6; //!< 6 iterations
1486 static const double extent_integration = 10.0; //!< Integration for extent < 10 deg
1487 static const double extent_direct = 15.0; //!< Direct for extent > 15 deg
1488
1489 // Get pointers
1490 const GModelSpatialRadial* radial = static_cast<const GModelSpatialRadial*>(model.spatial());
1491 const GCOMObservation* obs_ptr = static_cast<const GCOMObservation*>(&obs);
1492 const GCOMEventCube* cube = static_cast<const GCOMEventCube*>(obs.events());
1493
1494 // Set weights dependent on radial method
1495 #if G_IRF_RADIAL_METHOD == 0
1496 double wgt_integration = 1.0;
1497 double wgt_direct = 0.0;
1498 #elif G_IRF_RADIAL_METHOD == 1
1499 double wgt_integration = 0.0;
1500 double wgt_direct = 1.0;
1501 #else
1502 double extent = (*radial)[2].value(); // Assume 3rd parameter is extent
1503 double wgt_direct = (extent-extent_integration)/(extent_direct-extent_integration);
1504 double wgt_integration = 1.0 - wgt_direct;
1505 if (wgt_direct < 0.0) {
1506 wgt_direct = 0.0;
1507 wgt_integration = 1.0;
1508 }
1509 else if (wgt_direct > 1.0) {
1510 wgt_direct = 1.0;
1511 wgt_integration = 0.0;
1512 }
1513 #endif
1514
1515 // Timing option: store entry time and log parameters
1516 #if defined(G_DEBUG_IRF_TIMING)
1517 double cstart = gammalib::get_current_clock();
1518 std::cout << "GCOMResponse::irf_radial(";
1519 std::cout << "iter_rho=" << iter_rho;
1520 std::cout << ",iter_omega=" << iter_omega;
1521 #if G_IRF_RADIAL_METHOD == 2
1522 std::cout << ",extent=" << extent;
1523 std::cout << ",extent_integration=" << extent_integration;
1524 std::cout << ",extent_direct=" << extent_direct;
1525 #endif
1526 std::cout << ",wgt_direct=" << wgt_direct;
1527 std::cout << ",wgt_integration=" << wgt_integration << ")" << std::endl;
1528 #endif
1529
1530 // Get number of Chi/Psi pixels, Phibar layers and event bins
1531 int npix = cube->naxis(0) * cube->naxis(1);
1532 int nphibar = cube->naxis(2);
1533 int nevents = cube->size();
1534
1535 // Throw an exception if the number of Phibar bins does not match the
1536 // response
1537 if (nphibar != m_phibar_bins) {
1538 std::string msg = "DRE has "+gammalib::str(nphibar)+" Phibar layers "
1539 "but IAQ has "+gammalib::str(m_phibar_bins)+" Phibar "
1540 "bins. Please specify a compatible IAQ.";
1542 }
1543
1544 // Initialise result
1545 GVector irfs(nevents);
1546
1547 // Initialise some variables
1548 double phigeo_bin = m_phigeo_bin_size * gammalib::deg2rad;
1549 double phigeo_min = m_phigeo_min * gammalib::deg2rad - 0.5 * phigeo_bin;
1550 double phigeo_max = phigeo_min + phigeo_bin * m_phigeo_bins;
1551 const GSkyMap& drx = obs_ptr->drx().map();
1552 const double* drg = obs_ptr->drg().map().pixels();
1553
1554 // Compute IAQ normalisation (1/s): DEADC / ONTIME (s)
1555 double iaq_norm = obs_ptr->deadc() /
1556 (obs_ptr->ontime() * cube->dre().tof_correction()) *
1557 cube->dre().phase_correction();
1558
1559 // If weight for direct computation is non-zero then perform direct
1560 // computation and add response according to the weight to the IRF
1561 // vector
1562 if (wgt_direct > 0.0) {
1563
1564 // Throw an exception if COMPTEL FAQ response is not set
1565 if (m_faq.is_empty()) {
1566 std::string msg = "COMPTEL response is empty. Please initialise the "
1567 "response with an \"IAQ\".";
1569 }
1570
1571 // Compute weigthed IAQ normalisation
1572 double norm = iaq_norm * wgt_direct;
1573
1574 // Loop over Chi and Psi bins
1575 for (int ipix = 0; ipix < npix; ++ipix) {
1576
1577 // Get pointer to event bin
1578 const GCOMEventBin* bin = (*cube)[ipix];
1579
1580 // Get reference to instrument direction
1581 const GCOMInstDir& obsDir = bin->dir();
1582
1583 // Get reference to scatter sky direction
1584 const GSkyDir& scatter = obsDir.dir();
1585
1586 // Compute distance between scatter direction and centre of radial
1587 // model in radians
1588 double centre_distance = scatter.dist(radial->dir());
1589
1590 // Compute response in case that radial model overlaps with
1591 // COMPTEL field of view
1592 if (centre_distance < (phigeo_max + radial->theta_max())) {
1593
1594 // Setup rotation matrix for current scatter direction to
1595 // transform from FAQ system to the celestial coordinate
1596 // system
1597 GMatrix ry;
1598 GMatrix rz;
1599 ry.eulery(scatter.dec_deg() - 90.0);
1600 rz.eulerz(-scatter.ra_deg());
1601 GMatrix rot = (ry * rz).transpose();
1602
1603 // Loop over FAQ pixels
1604 for (int inx = 0; inx < m_faq_bins; ++inx) {
1605
1606 // Skip zero FAQ pixels
1607 if (m_faq_zero[inx]) {
1608 continue;
1609 }
1610
1611 // Get sky direction for FAQ pixel
1612 GVector vector = rot * m_faq_native[inx];
1613 GSkyDir dir(vector);
1614
1615 // Compute offset angle to centre of radial model
1616 double theta = radial->dir().dist(dir);
1617
1618 // Continue only if radial offset angle value is valid
1619 if (theta < radial->theta_max()) {
1620
1621 // Compute radial model value
1622 double model = radial->eval(theta, bin->energy(), bin->time());
1623
1624 // Continue only if model value is positive
1625 if (model > 0.0) {
1626
1627 // Multiply model value by solid angle of FAQ pixel
1628 model *= m_faq_solidangle[inx];
1629
1630 // Continue only if sky direction is within DRX
1631 if (drx.contains(dir)) {
1632
1633 // Multiply model value DRX value and norm
1634 model *= drx(dir) * norm;
1635
1636 // Loop over all phibar layers
1637 for (int iphibar = 0, idri = ipix; iphibar < nphibar; ++iphibar, idri += npix) {
1638 double faq = m_faq(inx, iphibar);
1639 if (faq > 0.0) {
1640 irfs[idri] += model * faq * drg[idri];
1641 }
1642 }
1643
1644 } // endif: sky direction within DRX
1645
1646 } // endif: model value was positive
1647
1648 } // endif: radial offset angle was valid
1649
1650 } // endfor: looped over FAQ pixels
1651
1652 } // endif: radial model overlaped with COMPTEL field of view
1653
1654 } // endfor: looped over Chi and Psi bins
1655
1656 } // endif: direct response computation requested
1657
1658 // If weight for numerical integration is non-zero then perform numerical
1659 // integration and add response according to the weight to the IRF
1660 // vector
1661 if (wgt_integration > 0.0) {
1662
1663 // Throw an exception if COMPTEL response is not set or if
1664 if (m_iaq.empty()) {
1665 std::string msg = "COMPTEL response is empty. Please initialise the "
1666 "response with an \"IAQ\".";
1668 }
1669 else if (m_phigeo_bin_size == 0.0) {
1670 std::string msg = "COMPTEL response has a zero Phigeo bin size. "
1671 "Please initialise the response with a valid "
1672 "\"IAQ\".";
1674 }
1675
1676 // Compute weigthed IAQ normalisation
1677 double norm = iaq_norm * wgt_integration;
1678
1679 // Get maximum model radius, reduced by a small amount to avoid
1680 // rounding problems at the boundary of a sharp edged model
1681 double theta_max = radial->theta_max() - 1.0e-12;
1682
1683 // Setup rotation matrix for conversion towards sky coordinates
1684 GMatrix ry;
1685 GMatrix rz;
1686 ry.eulery(radial->dir().dec_deg() - 90.0);
1687 rz.eulerz(-radial->dir().ra_deg());
1688 GMatrix rot = (ry * rz).transpose();
1689
1690 // Loop over Chi and Psi
1691 for (int ipix = 0; ipix < npix; ++ipix) {
1692
1693 // Get pointer to event bin
1694 const GCOMEventBin* bin = (*cube)[ipix];
1695
1696 // Get reference to instrument direction
1697 const GCOMInstDir& obsDir = bin->dir();
1698
1699 // Get reference to Phigeo sky direction
1700 const GSkyDir& phigeoDir = obsDir.dir();
1701
1702 // Compute angle between model centre and Phigeo sky direction (radians)
1703 // and position angle of model with respect to Phigeo sky direction (radians)
1704 double zeta = phigeoDir.dist(radial->dir());
1705
1706 // Set radial model zenith angle range
1707 double rho_min = (zeta > phigeo_max) ? zeta - phigeo_max : 0.0;
1708 double rho_max = zeta + phigeo_max;
1709 if (rho_min > theta_max) {
1710 rho_min = theta_max;
1711 }
1712 if (rho_max > theta_max) {
1713 rho_max = theta_max;
1714 }
1715
1716 // Perform model zenith angle integration if interval is valid
1717 if (rho_max > rho_min) {
1718
1719 // Initialise IRF vector
1720 GVector irf(nphibar);
1721
1722 // Setup integration kernel
1723 com_radial_kerns_rho integrands(m_iaq,
1724 *radial,
1725 irf,
1726 bin,
1727 rot,
1728 drx,
1729 phigeo_bin,
1731 nphibar,
1732 iter_omega);
1733
1734 // Setup integrator
1735 GIntegrals integral(&integrands);
1736 integral.fixed_iter(iter_rho);
1737
1738 // Integrate over Phigeo
1739 irf = integral.romberg(rho_min, rho_max, iter_rho);
1740
1741 // Add IRF to result
1742 for (int iphibar = 0, idri = ipix; iphibar < nphibar; ++iphibar, idri += npix) {
1743 irfs[idri] += irf[iphibar] * drg[idri] * norm;
1744 }
1745
1746 } // endif: Phigeo angle interval was valid
1747
1748 } // endfor: looped over Chi and Psi pixels
1749
1750 } // endif: numerical integration computation requested
1751
1752 // Dump CPU consumption
1753 #if defined(G_DEBUG_IRF_TIMING)
1754 double celapse = gammalib::get_current_clock() - cstart;
1755 std::cout << "GCOMResponse::irf_radial consumed " << celapse;
1756 std::cout << " seconds of CPU time." << std::endl;
1757 #endif
1758
1759 // Return IRF vector
1760 return irfs;
1761}
1762
1763
1764/***********************************************************************//**
1765 * @brief Return instrument response to elliptical source
1766 *
1767 * @param[in] model Sky model.
1768 * @param[in] obs Observation.
1769 * @param[out] gradients Gradients matrix.
1770 * @return Instrument response to elliptical source for all events in
1771 * observation (\f$cm^2\f$).
1772 *
1773 * @exception GException::invalid_value
1774 * Response not initialised with a valid IAQ
1775 * Incompatible IAQ encountered
1776 *
1777 * Returns the instrument response to an elliptical source sky model for all
1778 * events. The methods works on response vectors that are computed per
1779 * pointing. The method assumes that the spatial model component is of
1780 * type GModelSpatialElliptical, that the observations is of type
1781 * GCOMObservation and that the events are of type GCOMEventCube.
1782 * No checking of these assumptions is performed by the method, and not
1783 * respecting the assumptions will results in a segfault.
1784 *
1785 * The method toggles between two convolution methods that are used depending
1786 * on the extent of the elliptical source. Extent means here the larger of
1787 * the minor and major "radius" parameters, which is the radius for a disk
1788 * or the sigma parameter for a Gaussian.
1789 *
1790 * For extents smaller than 10 degrees a numerical integration is performed
1791 * in a radial system around the elliptical model that is fast but that may
1792 * become inaccurate for larger extents as the model sampling will exceed the
1793 * resolution of the data space. The number of iterations in the zenith and
1794 * azimuth angle was fixed to 6.
1795 *
1796 * For extents larger than 15 degrees a direct convolution is performed in
1797 * the system of the instrument response function, that is slower, but that
1798 * samples fully the resolution of the data space. The direct convolution is
1799 * however not accurate for angles smaller than about 1 degree, hence it is
1800 * not appropriate to compute for the response for the determination of
1801 * upper limits.
1802 *
1803 * For intermediate extents (in the interval 10 - 15 degrees), both
1804 * convolutions are computed and combined according to a weight that varies
1805 * linearly with source extent. In principle the convolved results should be
1806 * identical in that intermediate range, but computing both and their
1807 * weighted combination assures that no steps occur when varying the source
1808 * extent, which is required for seamless log-likelihood fits.
1809 *
1810 * For details about the choice of the convolution method and its parameters
1811 * see https://gitlab.in2p3.fr/gammalib/gammalib/-/issues/14.
1812 *
1813 * @p gradients is an optional matrix where the number of rows corresponds
1814 * to the number of events in the observation and the number of columns
1815 * corresponds to the number of spatial model parameters. Since for
1816 * elliptical sources no gradients are computed, the method does not alter
1817 * the content of @p gradients.
1818 ***************************************************************************/
1820 const GObservation& obs,
1821 GMatrix* gradients) const
1822{
1823 // Set constants
1824 static const int iter_rho = 6; //!< 6 iterations
1825 static const int iter_omega = 6; //!< 6 iterations
1826 static const double extent_integration = 10.0; //!< Integration for extent < 10 deg
1827 static const double extent_direct = 15.0; //!< Direct for extent > 15 deg
1828
1829 // Get pointers
1830 const GModelSpatialElliptical* elliptical = static_cast<const GModelSpatialElliptical*>(model.spatial());
1831 const GCOMObservation* obs_ptr = static_cast<const GCOMObservation*>(&obs);
1832 const GCOMEventCube* cube = static_cast<const GCOMEventCube*>(obs.events());
1833
1834 // Set weights dependent on elliptical method
1835 #if G_IRF_ELLIPTICAL_METHOD == 0
1836 double wgt_integration = 1.0;
1837 double wgt_direct = 0.0;
1838 #elif G_IRF_ELLIPTICAL_METHOD == 1
1839 double wgt_integration = 0.0;
1840 double wgt_direct = 1.0;
1841 #else
1842 double extent_maj = (*elliptical)[3].value(); // Assume 4th parameter is major radius
1843 double extent_min = (*elliptical)[4].value(); // Assume 5th parameter is minor radius
1844 double extent = (extent_maj > extent_min) ? extent_maj : extent_min;
1845 double wgt_direct = (extent-extent_integration)/(extent_direct-extent_integration);
1846 double wgt_integration = 1.0 - wgt_direct;
1847 if (wgt_direct < 0.0) {
1848 wgt_direct = 0.0;
1849 wgt_integration = 1.0;
1850 }
1851 else if (wgt_direct > 1.0) {
1852 wgt_direct = 1.0;
1853 wgt_integration = 0.0;
1854 }
1855 #endif
1856
1857 // Timing option: store entry time and log parameters
1858 #if defined(G_DEBUG_IRF_TIMING)
1859 double cstart = gammalib::get_current_clock();
1860 std::cout << "GCOMResponse::irf_elliptical(";
1861 std::cout << "iter_rho=" << iter_rho;
1862 std::cout << ",iter_omega=" << iter_omega;
1863 #if G_IRF_ELLIPTICAL_METHOD == 2
1864 std::cout << ",extent=" << extent;
1865 std::cout << ",extent_integration=" << extent_integration;
1866 std::cout << ",extent_direct=" << extent_direct;
1867 #endif
1868 std::cout << ",wgt_direct=" << wgt_direct;
1869 std::cout << ",wgt_integration=" << wgt_integration << ")" << std::endl;
1870 #endif
1871
1872 // Get number of Chi/Psi pixels, Phibar layers and event bins
1873 int npix = cube->naxis(0) * cube->naxis(1);
1874 int nphibar = cube->naxis(2);
1875 int nevents = cube->size();
1876
1877 // Throw an exception if the number of Phibar bins does not match the
1878 // response
1879 if (nphibar != m_phibar_bins) {
1880 std::string msg = "DRE has "+gammalib::str(nphibar)+" Phibar layers "
1881 "but IAQ has "+gammalib::str(m_phibar_bins)+" Phibar "
1882 "bins. Please specify a compatible IAQ.";
1884 }
1885
1886 // Initialise result
1887 GVector irfs(nevents);
1888
1889 // Initialise some variables
1890 double phigeo_bin = m_phigeo_bin_size * gammalib::deg2rad;
1891 double phigeo_min = m_phigeo_min * gammalib::deg2rad - 0.5 * phigeo_bin;
1892 double phigeo_max = phigeo_min + phigeo_bin * m_phigeo_bins;
1893 const GSkyMap& drx = obs_ptr->drx().map();
1894 const double* drg = obs_ptr->drg().map().pixels();
1895
1896 // Compute IAQ normalisation (1/s): DEADC / ONTIME (s)
1897 double iaq_norm = obs_ptr->deadc() /
1898 (obs_ptr->ontime() * cube->dre().tof_correction()) *
1899 cube->dre().phase_correction();
1900
1901 // If weight for direct computation is non-zero then perform direct
1902 // computation and add response according to the weight to the IRF
1903 // vector
1904 if (wgt_direct > 0.0) {
1905
1906 // Throw an exception if COMPTEL FAQ response is not set
1907 if (m_faq.is_empty()) {
1908 std::string msg = "COMPTEL response is empty. Please initialise the "
1909 "response with an \"IAQ\".";
1911 }
1912
1913 // Compute weigthed IAQ normalisation
1914 double norm = iaq_norm * wgt_direct;
1915
1916 // Loop over Chi and Psi bins
1917 for (int ipix = 0; ipix < npix; ++ipix) {
1918
1919 // Get pointer to event bin
1920 const GCOMEventBin* bin = (*cube)[ipix];
1921
1922 // Get reference to instrument direction
1923 const GCOMInstDir& obsDir = bin->dir();
1924
1925 // Get reference to scatter sky direction
1926 const GSkyDir& scatter = obsDir.dir();
1927
1928 // Compute distance between scatter direction and centre of radial
1929 // model in radians
1930 double centre_distance = scatter.dist(elliptical->dir());
1931
1932 // Compute response in case that elliptical model overlaps with COMPTEL
1933 // field of view
1934 if (centre_distance < (phigeo_max + elliptical->theta_max())) {
1935
1936 // Setup rotation matrix for current scatter direction to
1937 // transform from FAQ system to the celestial coordinate
1938 // system
1939 GMatrix ry;
1940 GMatrix rz;
1941 ry.eulery(scatter.dec_deg() - 90.0);
1942 rz.eulerz(-scatter.ra_deg());
1943 GMatrix rot = (ry * rz).transpose();
1944
1945 // Loop over FAQ pixels
1946 for (int inx = 0; inx < m_faq_bins; ++inx) {
1947
1948 // Skip zero FAQ pixels
1949 if (m_faq_zero[inx]) {
1950 continue;
1951 }
1952
1953 // Get sky direction for FAQ pixel
1954 GVector vector = rot * m_faq_native[inx];
1955 GSkyDir dir(vector);
1956
1957 // Compute offset angle to centre of elliptical model
1958 double theta = elliptical->dir().dist(dir);
1959
1960 // Continue only if offset angle value is valid
1961 if (theta < elliptical->theta_max()) {
1962
1963 // Compute position angle of elliptical model in the
1964 // coordinate system of the elliptical model
1965 double posang = elliptical->dir().posang(dir, elliptical->coordsys());
1966
1967 // Compute elliptical model value
1968 double model = elliptical->eval(theta, posang, bin->energy(), bin->time());
1969
1970 // Continue only if model value is positive
1971 if (model > 0.0) {
1972
1973 // Multiply model value by solid angle of FAQ pixel
1974 model *= m_faq_solidangle[inx];
1975
1976 // Continue only if sky direction is within DRX
1977 if (drx.contains(dir)) {
1978
1979 // Multiply model value by DRX value and IAQ norm
1980 model *= drx(dir) * norm;
1981
1982 // Loop over all phibar layers
1983 for (int iphibar = 0, idri = ipix; iphibar < nphibar; ++iphibar, idri+=npix) {
1984 double faq = m_faq(inx, iphibar);
1985 if (faq > 0.0) {
1986 irfs[idri] += model * faq * drg[idri];
1987 }
1988 }
1989
1990 } // endif: sky direction within DRX
1991
1992 } // endif: model value was positive
1993
1994 } // endif: offset angle was valid
1995
1996 } // endfor: looped over FAQ pixels
1997
1998 } // endif: elliptical model overlaped with COMPTEL field of view
1999
2000 } // endfor: looped over Chi and Psi bins
2001
2002 } // endif: direct response computation requested
2003
2004 // If weight for numerical integration is non-zero then perform numerical
2005 // integration and add response according to the weight to the IRF
2006 // vector
2007 if (wgt_integration > 0.0) {
2008
2009 // Throw an exception if COMPTEL response is not set or if
2010 if (m_iaq.empty()) {
2011 std::string msg = "COMPTEL response is empty. Please initialise the "
2012 "response with an \"IAQ\".";
2014 }
2015 else if (m_phigeo_bin_size == 0.0) {
2016 std::string msg = "COMPTEL response has a zero Phigeo bin size. "
2017 "Please initialise the response with a valid "
2018 "\"IAQ\".";
2020 }
2021
2022 // Compute weigthed IAQ normalisation
2023 double norm = iaq_norm * wgt_integration;
2024
2025 // Get maximum model radius, reduced by a small amount to avoid
2026 // rounding problems at the boundary of a sharp edged model
2027 double theta_max = elliptical->theta_max() - 1.0e-12;
2028
2029 // Setup rotation matrix for conversion towards sky coordinates
2030 GMatrix ry;
2031 GMatrix rz;
2032 ry.eulery(elliptical->dir().dec_deg() - 90.0);
2033 rz.eulerz(-elliptical->dir().ra_deg());
2034 GMatrix rot = (ry * rz).transpose();
2035
2036 // Loop over Chi and Psi
2037 for (int ipix = 0; ipix < npix; ++ipix) {
2038
2039 // Get pointer to event bin
2040 const GCOMEventBin* bin = (*cube)[ipix];
2041
2042 // Get reference to instrument direction
2043 const GCOMInstDir& obsDir = bin->dir();
2044
2045 // Get reference to Phigeo sky direction
2046 const GSkyDir& phigeoDir = obsDir.dir();
2047
2048 // Compute angle between model centre and Phigeo sky direction (radians)
2049 // and position angle of model with respect to Phigeo sky direction (radians)
2050 double zeta = phigeoDir.dist(elliptical->dir());
2051
2052 // Set radial model zenith angle range
2053 double rho_min = (zeta > phigeo_max) ? zeta - phigeo_max : 0.0;
2054 double rho_max = zeta + phigeo_max;
2055 if (rho_min > theta_max) {
2056 rho_min = theta_max;
2057 }
2058 if (rho_max > theta_max) {
2059 rho_max = theta_max;
2060 }
2061
2062 // Perform model zenith angle integration if interval is valid
2063 if (rho_max > rho_min) {
2064
2065 // Initialise IRF vector
2066 GVector irf(nphibar);
2067
2068 // Setup integration kernel
2070 model,
2071 irf,
2072 bin,
2073 rot,
2074 drx,
2075 phigeo_bin,
2077 nphibar,
2078 iter_omega);
2079
2080 // Setup integrator
2081 GIntegrals integral(&integrands);
2082 integral.fixed_iter(iter_rho);
2083
2084 // Integrate over Phigeo
2085 irf = integral.romberg(rho_min, rho_max, iter_rho);
2086
2087 // Add IRF to result
2088 for (int iphibar = 0, idri = ipix; iphibar < nphibar; ++iphibar, idri+=npix) {
2089 irfs[idri] += irf[iphibar] * drg[idri] * norm;
2090 }
2091
2092 } // endif: Phigeo angle interval was valid
2093
2094 } // endfor: looped over Chi and Psi pixels
2095
2096 } // endif: numerical integration computation requested
2097
2098 // Dump CPU consumption
2099 #if defined(G_DEBUG_IRF_TIMING)
2100 double celapse = gammalib::get_current_clock() - cstart;
2101 std::cout << "GCOMResponse::irf_elliptical consumed " << celapse;
2102 std::cout << " seconds of CPU time." << std::endl;
2103 #endif
2104
2105 // Return IRF vector
2106 return irfs;
2107}
2108
2109
2110/***********************************************************************//**
2111 * @brief Return instrument response to diffuse source
2112 *
2113 * @param[in] model Sky model.
2114 * @param[in] obs Observation.
2115 * @param[out] gradients Gradients matrix.
2116 * @return Instrument response to diffuse source for all events in
2117 * observation (\f$cm^2\f$).
2118 *
2119 * @exception GException::invalid_value
2120 * Response not initialised with a valid IAQ
2121 * Incompatible IAQ encountered
2122 *
2123 * Returns the instrument response to a diffuse source sky model for all
2124 * events. The methods works on response vectors that are computed per
2125 * pointing. The method assumes that the spatial model component is of
2126 * type GModelSpatialDiffuse, that the observations is of type
2127 * GCOMObservation and that the events are of type GCOMEventCube.
2128 * No checking of these assumptions is performed by the method, and not
2129 * respecting the assumptions will results in a segfault.
2130 *
2131 * The computation is done using a direct convolution in the system of the
2132 * instrument response function, sampling fully the resolution of the data
2133 * space.
2134 *
2135 * For details about the choice of the convolution method and its parameters
2136 * see https://gitlab.in2p3.fr/gammalib/gammalib/-/issues/14.
2137 *
2138 * @p gradients is an optional matrix where the number of rows corresponds
2139 * to the number of events in the observation and the number of columns
2140 * corresponds to the number of spatial model parameters. Since for diffuse
2141 * sources no gradients are computed, the method does not alter the
2142 * content of @p gradients.
2143 ***************************************************************************/
2145 const GObservation& obs,
2146 GMatrix* gradients) const
2147{
2148 // Store entry time
2149 #if defined(G_DEBUG_IRF_TIMING)
2150 double cstart = gammalib::get_current_clock();
2151 #endif
2152
2153 // Get pointers
2154 const GModelSpatialDiffuse* diffuse = static_cast<const GModelSpatialDiffuse*>(model.spatial());
2155 const GCOMObservation* obs_ptr = static_cast<const GCOMObservation*>(&obs);
2156 const GCOMEventCube* cube = static_cast<const GCOMEventCube*>(obs.events());
2157
2158 // Get number of Chi/Psi pixels, Phibar layers and event bins
2159 int npix = cube->naxis(0) * cube->naxis(1);
2160 int nphibar = cube->naxis(2);
2161 int nevents = cube->size();
2162
2163 // Throw an exception if the number of Phibar bins does not match the
2164 // response
2165 if (nphibar != m_phibar_bins) {
2166 std::string msg = "DRE has "+gammalib::str(nphibar)+" Phibar layers "
2167 "but IAQ has "+gammalib::str(m_phibar_bins)+" Phibar "
2168 "bins. Please specify a compatible IAQ.";
2170 }
2171
2172 // Initialise result
2173 GVector irfs(nevents);
2174
2175 // Initialise some variables
2176 double phigeo_bin = m_phigeo_bin_size * gammalib::deg2rad;
2177 double phigeo_min = m_phigeo_min * gammalib::deg2rad - 0.5 * phigeo_bin;
2178 double phigeo_max = phigeo_min + phigeo_bin * m_phigeo_bins;
2179 const GSkyMap& drx = obs_ptr->drx().map();
2180 const double* drg = obs_ptr->drg().map().pixels();
2181
2182 // Compute IAQ normalisation (1/s): DEADC / ONTIME (s)
2183 double iaq_norm = obs_ptr->deadc() /
2184 (obs_ptr->ontime() * cube->dre().tof_correction()) *
2185 cube->dre().phase_correction();
2186
2187 #if defined(G_IRF_DIFFUSE_DIRECT)
2188 #if defined(G_DEBUG_IRF_TIMING)
2189 std::cout << "GCOMResponse::irf_diffuse - direct computation" << std::endl;
2190 #endif
2191
2192 // Throw an exception if COMPTEL FAQ response is not set
2193 if (m_faq.is_empty()) {
2194 std::string msg = "COMPTEL response is empty. Please initialise the "
2195 "response with an \"IAQ\".";
2197 }
2198
2199 // Loop over Chi and Psi bins
2200 for (int ipix = 0; ipix < npix; ++ipix) {
2201
2202 // Get pointer to event bin
2203 const GCOMEventBin* bin = (*cube)[ipix];
2204
2205 // Get reference to instrument direction
2206 const GCOMInstDir& obsDir = bin->dir();
2207
2208 // Get reference to scatter sky direction
2209 const GSkyDir& scatter = obsDir.dir();
2210
2211 // transform from FAQ system to the celestial coordinate system
2212 GMatrix ry;
2213 GMatrix rz;
2214 ry.eulery(scatter.dec_deg() - 90.0);
2215 rz.eulerz(-scatter.ra_deg());
2216 GMatrix rot = (ry * rz).transpose();
2217
2218 // Loop over FAQ pixels
2219 for (int inx = 0; inx < m_faq_bins; ++inx) {
2220
2221 // Skip zero FAQ pixels
2222 if (m_faq_zero[inx]) {
2223 continue;
2224 }
2225
2226 // Get sky direction for FAQ pixel
2227 GVector vector = rot * m_faq_native[inx];
2228 GSkyDir dir(vector);
2229
2230 // Setup photon
2231 GPhoton photon(dir, bin->energy(), bin->time());
2232
2233 // Compute model value
2234 double model = diffuse->eval(photon);
2235
2236 // Continue only if model value is positive
2237 if (model > 0.0) {
2238
2239 // Multiply model value by solid angle of FAQ pixel
2240 model *= m_faq_solidangle[inx];
2241
2242 // Continue only if sky direction is within DRX
2243 if (drx.contains(dir)) {
2244
2245 // Multiply model value by DRX value and IAQ norm
2246 model *= drx(dir) * iaq_norm;
2247
2248 // Loop over all phibar layers
2249 for (int iphibar = 0, idri = ipix; iphibar < nphibar; ++iphibar, idri+=npix) {
2250 double faq = m_faq(inx, iphibar);
2251 if (faq > 0.0) {
2252 irfs[idri] += model * faq * drg[idri];
2253 }
2254 }
2255
2256 } // endif: sky direction within DRX
2257
2258 } // endif: model value was positive
2259
2260 } // endfor: looped over FAQ pixels
2261
2262 } // endfor: looped over Chi and Psi bins
2263 #else
2264 #if defined(G_DEBUG_IRF_TIMING)
2265 std::cout << "GCOMResponse::irf_diffuse - numerical integration" << std::endl;
2266 #endif
2267
2268 // Throw an exception if COMPTEL response is not set or if
2269 if (m_iaq.empty()) {
2270 std::string msg = "COMPTEL response is empty. Please initialise the "
2271 "response with an \"IAQ\".";
2273 }
2274 else if (m_phigeo_bin_size == 0.0) {
2275 std::string msg = "COMPTEL response has a zero Phigeo bin size. "
2276 "Please initialise the response with a valid "
2277 "\"IAQ\".";
2279 }
2280
2281 // Initilise some variables
2282 double omega0 = 2.0 * std::sin(0.5 * phigeo_bin);
2283
2284 // Loop over Chi and Psi
2285 for (int ipix = 0; ipix < npix; ++ipix) {
2286
2287 // Get pointer to event bin
2288 const GCOMEventBin* bin = (*cube)[ipix];
2289
2290 // Get reference to instrument direction
2291 const GCOMInstDir& obsDir = bin->dir();
2292
2293 // Get reference to Phigeo sky direction
2294 const GSkyDir& phigeoDir = obsDir.dir();
2295
2296 // Loop over Phigeo
2297 for (int iphigeo = 0; iphigeo < m_phigeo_bins; ++iphigeo) {
2298
2299 // Determine Phigeo value in radians. Note that phigeo_min is the value
2300 // for bin zero.
2301 double phigeo = phigeo_min + double(iphigeo) * phigeo_bin;
2302 double sin_phigeo = std::sin(phigeo);
2303
2304 // Determine number of azimuthal integration steps and step size
2305 // in radians
2306 double length = gammalib::twopi * sin_phigeo;
2307 int naz = int(length / phigeo_bin + 0.5);
2308 if (naz < 2) {
2309 naz = 2;
2310 }
2311 double az_step = gammalib::twopi / double(naz);
2312
2313 // Computes solid angle of integration bin multiplied by Jaccobian
2314 double omega = omega0 * az_step * sin_phigeo;
2315
2316 // Loop over azimuth angle
2317 double az = 0.0;
2318 for (int iaz = 0; iaz < naz; ++iaz, az += az_step) {
2319
2320 // Get sky direction
2321 GSkyDir skyDir = phigeoDir;
2322 skyDir.rotate(az, phigeo);
2323
2324 // Fall through if sky direction is not contained in DRX
2325 if (!drx.contains(skyDir)) {
2326 continue;
2327 }
2328
2329 // Set photon
2330 GPhoton photon(skyDir, bin->energy(), bin->time());
2331
2332 // Get model sky intensity for photon (unit: sr^-1)
2333 double intensity = model.spatial()->eval(photon);
2334
2335 // Fall through if intensity is zero
2336 if (intensity == 0.0) {
2337 continue;
2338 }
2339
2340 // Multiply intensity by DRX value (unit: cm^2 s sr^-1)
2341 intensity *= drx(skyDir);
2342
2343 // Multiply intensity by solid angle (unit: cm^2 s)
2344 intensity *= omega;
2345
2346 // Loop over Phibar
2347 for (int iphibar = 0; iphibar < nphibar; ++iphibar) {
2348
2349 // Get IAQ index
2350 int i = iphibar * m_phigeo_bins + iphigeo;
2351
2352 // Get IAQ value
2353 double iaq = m_iaq[i];
2354
2355 // Fall through if IAQ is not positive
2356 if (iaq <= 0.0) {
2357 continue;
2358 }
2359
2360 // Get DRI index
2361 int idri = ipix + iphibar * npix;
2362
2363 // Compute IRF value (unit: cm^2)
2364 double irf = iaq * drg[idri] * iaq_norm * intensity;
2365
2366 // Add IRF value if it is positive
2367 if (irf > 0.0) {
2368 irfs[idri] += irf;
2369 }
2370
2371 } // endfor: looped over Phibar
2372
2373 } // endfor: looped over azimuth angle around locus of IAQ
2374
2375 } // endfor: looped over Phigeo angles
2376
2377 } // endfor: looped over Chi and Psi pixels
2378 #endif
2379
2380 // Dump CPU consumption
2381 #if defined(G_DEBUG_IRF_TIMING)
2382 double celapse = gammalib::get_current_clock() - cstart;
2383 std::cout << "GCOMResponse::irf_diffuse consumed " << celapse;
2384 std::cout << " seconds of CPU time." << std::endl;
2385 #endif
2386
2387 // Return IRF vector
2388 return irfs;
2389}
COMPTEL event bin class interface definition.
COMPTEL event bin container class interface definition.
COMPTEL instrument direction class definition.
COMPTEL observation class interface definition.
#define G_IRF
#define G_EBOUNDS
#define G_NROI
#define G_IRF_PTSRC
#define G_BACKPROJECT
COMPTEL instrument response function class interface definition.
Calibration database class interface definition.
Energy value class definition.
Abstract event base class definition.
Filename class interface definition.
Single precision FITS image class definition.
Abstract FITS image base class definition.
FITS file class interface definition.
Integration class for set of functions interface definition.
Mathematical function definitions.
Generic matrix class definition.
Sky model class interface definition.
Abstract diffuse spatial model base class interface definition.
Abstract elliptical spatial model base class interface definition.
Point source spatial model class interface definition.
Abstract radial spatial model base class interface definition.
Abstract observation base class interface definition.
Photon 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.
Time class interface definition.
Gammalib tools definition.
GChatter
Definition GTypemaps.hpp:33
@ EXPLICIT
Definition GTypemaps.hpp:37
@ SILENT
Definition GTypemaps.hpp:34
@ VERBOSE
Definition GTypemaps.hpp:38
double norm(const GVector &vector)
Computes vector norm.
Definition GVector.cpp:932
Vector class interface definition.
COMPTEL Data Space class.
Definition GCOMDri.hpp:62
const double & phase_correction(void) const
Return pulsar phase correction factor.
Definition GCOMDri.hpp:429
int nchi(void) const
Return number of Chi bins.
Definition GCOMDri.hpp:242
const double & tof_correction(void) const
Return ToF correction factor.
Definition GCOMDri.hpp:398
const GSkyMap & map(void) const
Return DRI sky map.
Definition GCOMDri.hpp:278
int npsi(void) const
Return number of Psi bins.
Definition GCOMDri.hpp:254
int size(void) const
Return number of bins.
Definition GCOMDri.hpp:230
int nphibar(void) const
Return number of Phibar bins.
Definition GCOMDri.hpp:266
COMPTEL event bin class.
virtual const GTime & time(void) const
Return time of event bin.
virtual const GCOMInstDir & dir(void) const
Return instrument direction of event bin.
virtual const GEnergy & energy(void) const
Return energy of event bin.
COMPTEL event bin container class.
virtual int size(void) const
Return number of bins in event cube.
const GCOMDri & dre(void) const
Return reference to DRE data.
virtual int naxis(const int &axis) const
Return number of bins in axis.
Interface for the COMPTEL instrument direction class.
void dir(const GSkyDir &dir)
Set event scatter direction.
void phibar(const double &phibar)
Set event Compton scatter angle.
Interface class for COMPTEL observations.
virtual double ontime(void) const
Return ontime.
virtual double deadc(const GTime &time=GTime()) const
Return deadtime correction factor.
const GCOMDri & drx(void) const
Return exposure.
const GCOMDri & drg(void) const
Return geometry factors.
Interface for the COMPTEL instrument response function.
virtual double irf(const GEvent &event, const GPhoton &photon, const GObservation &obs) const
Return value of instrument response function.
void free_members(void)
Delete class members.
void save_cache(const GFilename &filename) const
Save response cache.
virtual std::string print(const GChatter &chatter=NORMAL) const
Print COMPTEL response information.
virtual GVector irf_ptsrc(const GModelSky &model, const GObservation &obs, GMatrix *gradients=NULL) const
Return instrument response to point source.
int m_phigeo_bins
Number of Phigeo bins.
std::vector< GVector > m_faq_native
Array of native FAQ vectors.
double m_phibar_min
Phigeo value of first bin (deg)
void write(GFitsImageFloat &image) const
Write COMPTEL response into FITS image.
GCOMResponse(void)
Void constructor.
virtual GVector irf_radial(const GModelSky &model, const GObservation &obs, GMatrix *gradients=NULL) const
Return instrument response to radial source.
int m_phibar_bins
Number of Phibar bins.
GVector m_faq_solidangle
Array of FAQ solid angles.
const std::string & rspname(void) const
Return response name.
double m_phibar_bin_size
Phigeo binsize (deg)
void copy_members(const GCOMResponse &rsp)
Copy class members.
std::vector< double > m_iaq
IAQ array.
double m_phigeo_min
Phigeo value of first bin (deg)
void backproject(const GObservation &obs, const GEvents *events, GSkyMap *map) const
Backproject events using instrument response function to sky map.
virtual ~GCOMResponse(void)
Destructor.
GSkyMap m_faq
FAQ array.
void compute_faq(void)
Compute FAQ.
void load_cache(const GFilename &filename)
Load response cache.
virtual GVector irf_elliptical(const GModelSky &model, const GObservation &obs, GMatrix *gradients=NULL) const
Return instrument response to elliptical source.
const GCaldb & caldb(void) const
Return calibration database.
double m_phibar_ref_value
Phigeo reference value (deg)
void read(const GFitsImage &hdu)
Read COMPTEL response from FITS image.
int m_faq_bins
Number of FAQ bins.
double m_phigeo_ref_value
Phigeo reference value (deg)
std::string m_rspname
Response name.
virtual GEbounds ebounds(const GEnergy &obsEnergy) const
Return true energy boundaries for a specific observed energy.
GCaldb m_caldb
Calibration database.
void init_members(void)
Initialise class members.
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.
virtual void clear(void)
Clear instance.
double m_phigeo_ref_pixel
Phigeo reference pixel (starting from 1)
double m_phigeo_bin_size
Phigeo binsize (deg)
virtual GVector irf_diffuse(const GModelSky &model, const GObservation &obs, GMatrix *gradients=NULL) const
Return instrument response to diffuse source.
virtual GCOMResponse * clone(void) const
Clone instance.
double m_phibar_ref_pixel
Phigeo reference pixel (starting from 1)
virtual GCOMResponse & operator=(const GCOMResponse &rsp)
Assignment operator.
std::vector< bool > m_faq_zero
Array of zero FAQ bins.
void load(const std::string &rspname)
Load COMPTEL response.
Calibration database class.
Definition GCaldb.hpp:66
std::string rootdir(void) const
Return path to CALDB root directory.
Definition GCaldb.cpp:257
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
Class that handles energies in a unit independent way.
Definition GEnergy.hpp:48
Abstract interface for the event classes.
Definition GEvent.hpp:71
Abstract event container class.
Definition GEvents.hpp:66
Filename class.
Definition GFilename.hpp:62
bool is_fits(void) const
Checks whether file is a FITS file.
bool exists(void) const
Checks whether file exists.
bool is_empty(void) const
Signal if filename is empty.
double real(const std::string &keyname) const
Return card value as double precision.
Definition GFitsHDU.hpp:423
GFitsHeaderCard & card(const int &cardno)
Return header card.
Definition GFitsHDU.hpp:259
Single precision FITS image class.
Abstract FITS image base class.
virtual double pixel(const int &ix) const =0
int naxes(const int &axis) const
Return dimension of an image axis.
const int & naxis(void) const
Return dimension of image.
FITS file class.
Definition GFits.hpp:63
GFitsImage * image(const int &extno)
Get pointer to image HDU.
Definition GFits.cpp:368
void close(void)
Close FITS file.
Definition GFits.cpp:1342
Integration class for set of functions.
GVector romberg(std::vector< double > bounds, const int &order=5)
Perform Romberg 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.
GModelSpatial * spatial(void) const
Return spatial model component.
Abstract diffuse spatial model base class.
virtual double eval(const GPhoton &photon, const bool &gradients=false) const =0
Abstract elliptical spatial model base class.
virtual double eval(const double &theta, const double &posangle, const GEnergy &energy, const GTime &time, const bool &gradients=false) const =0
const GSkyDir & dir(void) const
Return position of elliptical spatial model.
std::string coordsys(void) const
Return coordinate system.
virtual double theta_max(void) const =0
Point source spatial model.
Abstract radial spatial model base class.
virtual double theta_max(void) const =0
virtual double eval(const double &theta, const GEnergy &energy, const GTime &time, const bool &gradients=false) const =0
const GSkyDir & dir(void) const
Return position of radial spatial model.
virtual double eval(const GPhoton &photon, const bool &gradients=false) const =0
Abstract observation base class.
virtual GEvents * events(void)
Return events.
virtual double deadc(const GTime &time=GTime()) const =0
void name(const std::string &name)
Set observation name.
void id(const std::string &id)
Set observation identifier.
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
void save(const GFilename &filename, const bool &clobber=false) const
Save the response vector cache into FITS file.
void load(const GFilename &filename)
Load response vector cache from FITS file.
Abstract instrument response base class.
Definition GResponse.hpp:77
GResponseVectorCache m_irf_vector_cache
void init_members(void)
Initialise class members.
void free_members(void)
Delete class members.
virtual GResponse & operator=(const GResponse &rsp)
Assignment operator.
Sky direction class.
Definition GSkyDir.hpp:62
double dec_deg(void) const
Returns Declination in degrees.
Definition GSkyDir.hpp:258
double ra_deg(void) const
Returns Right Ascension in degrees.
Definition GSkyDir.hpp:231
double dist_deg(const GSkyDir &dir) const
Compute angular distance between sky directions in degrees.
Definition GSkyDir.hpp:288
void rotate(const double &phi, const double &theta)
Rotate sky direction by zenith and azimuth angle.
Definition GSkyDir.cpp:527
double dist(const GSkyDir &dir) const
Compute angular distance between sky directions in radians.
Definition GSkyDir.hpp:273
double posang(const GSkyDir &dir, const std::string &coordsys="CEL") const
Compute position angle between sky directions in radians.
Definition GSkyDir.cpp:1300
Sky map class.
Definition GSkyMap.hpp:89
double solidangle(const int &index) const
Returns solid angle of pixel.
Definition GSkyMap.cpp:1858
const int & nmaps(void) const
Returns number of maps.
Definition GSkyMap.hpp:427
void clear(void)
Clear instance.
Definition GSkyMap.cpp:1100
GSkyDir inx2dir(const int &index) const
Returns sky direction of pixel.
Definition GSkyMap.cpp:1331
bool is_empty(void) const
Signals if sky map is empty.
Definition GSkyMap.hpp:369
void save(const GFilename &filename, const bool &clobber=false) const
Save sky map into FITS file.
Definition GSkyMap.cpp:2611
const int & npix(void) const
Returns number of pixels.
Definition GSkyMap.hpp:383
bool contains(const GSkyDir &dir) const
Checks if sky direction falls in map.
Definition GSkyMap.cpp:2023
const double * pixels(void) const
Returns pointer to pixel data.
Definition GSkyMap.hpp:513
Time class.
Definition GTime.hpp:55
Vector class.
Definition GVector.hpp:46
void clear(void)
Clear vector.
Definition GVector.cpp:557
Kernel for rho angle integration of elliptical models.
Kernel for rho angle integration of radial models.
Defintion of COMPTEL helper classes for vector response.
std::string parformat(const std::string &s, const int &indent=0)
Convert string in parameter format.
Definition GTools.cpp:1162
bool is_infinite(const double &x)
Signal if argument is infinite.
Definition GTools.hpp:186
bool is_notanumber(const double &x)
Signal if argument is not a number.
Definition GTools.hpp:203
std::string str(const unsigned short int &value)
Convert unsigned short integer value into string.
Definition GTools.cpp:508
std::string filepath(const std::string &pathname, const std::string &filename)
Build file path from path name and file name.
Definition GTools.cpp:412
const double deg2rad
Definition GMath.hpp:43
double get_current_clock(void)
Get current clock in seconds.
Definition GTools.cpp:2587
const double fourpi
Definition GMath.hpp:37
const double twopi
Definition GMath.hpp:36