GammaLib 2.0.0
Loading...
Searching...
No Matches
GCOMInstChars.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * GCOMInstChars.cpp - COMPTEL Instrument Characteristics class *
3 * ----------------------------------------------------------------------- *
4 * copyright (C) 2017-2021 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 GCOMInstChars.cpp
23 * @brief COMPTEL Instrument Characteristics class implementation
24 * @author Juergen Knoedlseder
25 */
26
27/* __ Includes ___________________________________________________________ */
28#ifdef HAVE_CONFIG_H
29#include <config.h>
30#endif
31#include <algorithm>
32#include "GMath.hpp"
33#include "GEnergy.hpp"
34#include "GCOMInstChars.hpp"
35#include "GCOMSupport.hpp"
36
37/* __ Method name definitions ____________________________________________ */
38
39/* __ Macros _____________________________________________________________ */
40
41/* __ Coding definitions _________________________________________________ */
42#define USE_ICT_MEAN_FREE_PATH
43
44/* __ Debug definitions __________________________________________________ */
45//#define G_DEBUG_READ_SELFVETO
46
47/* __ Constants __________________________________________________________ */
48
49
50/*==========================================================================
51 = =
52 = Constructors/destructors =
53 = =
54 ==========================================================================*/
55
56/***********************************************************************//**
57 * @brief Void constructor
58 *
59 * Creates empty COMPTEL instrument characteristics.
60 ***************************************************************************/
62{
63 // Initialise members
65
66 // Return
67 return;
68}
69
70
71/***********************************************************************//**
72 * @brief Copy constructor
73 *
74 * @param[in] ict COMPTEL instrument characteristics.
75 **************************************************************************/
77{
78 // Initialise members
80
81 // Copy members
82 copy_members(ict);
83
84 // Return
85 return;
86}
87
88
89/***********************************************************************//**
90 * @brief Response constructor
91 *
92 * @param[in] caldb Calibration database.
93 * @param[in] ictname ICT response name.
94 *
95 * Create COMPTEL instrument characteristics by loading an ICT file from a
96 * calibration database.
97 ***************************************************************************/
98GCOMInstChars::GCOMInstChars(const GCaldb& caldb, const std::string& ictname)
99{
100 // Initialise members
101 init_members();
102
103 // Set calibration database
104 this->caldb(caldb);
105
106 // Load instrument characteristics
107 this->load(ictname);
108
109 // Return
110 return;
111}
112
113
114/***********************************************************************//**
115 * @brief Destructor
116 *
117 * Destroys instance of COMPTEL instrument characteristics.
118 ***************************************************************************/
120{
121 // Free members
122 free_members();
123
124 // Return
125 return;
126}
127
128
129/*==========================================================================
130 = =
131 = Operators =
132 = =
133 ==========================================================================*/
134
135/***********************************************************************//**
136 * @brief Assignment operator
137 *
138 * @param[in] ict COMPTEL instrument characteristics.
139 * @return COMPTEL instrument characteristics.
140 ***************************************************************************/
142{
143 // Execute only if object is not identical
144 if (this != &ict) {
145
146 // Free members
147 free_members();
148
149 // Initialise members
150 init_members();
151
152 // Copy members
153 copy_members(ict);
154
155 } // endif: object was not identical
156
157 // Return this object
158 return *this;
159}
160
161
162/*==========================================================================
163 = =
164 = Public methods =
165 = =
166 ==========================================================================*/
167
168/***********************************************************************//**
169 * @brief Clear instance
170 *
171 * Clears COMPTEL instrument characteristics object by resetting all members
172 * to an initial state. Any information that was present in the object before
173 * will be lost.
174 ***************************************************************************/
176{
177 // Free class members
178 free_members();
179
180 // Initialise members
181 init_members();
182
183 // Return
184 return;
185}
186
187
188/***********************************************************************//**
189 * @brief Clone instance
190 *
191 * @return Pointer to deep copy of COMPTEL instrument characteristics.
192 ***************************************************************************/
194{
195 return new GCOMInstChars(*this);
196}
197
198
199/***********************************************************************//**
200 * @brief Load COMPTEL instrument characteristics.
201 *
202 * @param[in] ictname COMPTEL instrument characteristics.
203 *
204 * Loads the COMPTEL instrument characteristics with specified name
205 * @p ictname. The method first searchs for an appropriate response in the
206 * calibration database. If no appropriate response is found, the method
207 * takes the database root path and response name to build the full path to
208 * the response file, and tries to load the response from these paths.
209 ***************************************************************************/
210void GCOMInstChars::load(const std::string& ictname)
211{
212 // Clear instance but conserve calibration database
214 clear();
215 m_caldb = caldb;
216
217 // First attempt reading the response using the GCaldb interface
218 GFilename filename = m_caldb.filename("","","ICT","","",ictname);
219
220 // If filename is empty then build filename from CALDB root path and
221 // response name
222 if (filename.is_empty()) {
223 filename = gammalib::filepath(m_caldb.rootdir(), ictname);
224 if (!filename.exists()) {
225 GFilename testname = filename + ".fits";
226 if (testname.exists()) {
227 filename = testname;
228 }
229 }
230 }
231
232 // Open FITS file
233 GFits fits(filename);
234
235 // Get ICT tables
236 const GFitsTable& d1inter = *fits.table("D1INTER");
237 const GFitsTable& d1dens = *fits.table("D1DENS");
238 const GFitsTable& d1pos = *fits.table("D1POS");
239 const GFitsTable& d1rad = *fits.table("D1RAD");
240 const GFitsTable& d1thick = *fits.table("D1THICK");
241 const GFitsTable& d2inter = *fits.table("D2INTER");
242 const GFitsTable& d2dens = *fits.table("D2DENS");
243 const GFitsTable& d2pos = *fits.table("D2POS");
244 const GFitsTable& d2rad = *fits.table("D2RAD");
245 const GFitsTable& d2thick = *fits.table("D2THICK");
246 const GFitsTable& thbar = *fits.table("THBAR");
247 const GFitsTable& delz = *fits.table("DELZ");
248 const GFitsTable& alu = *fits.table("ALU");
249 const GFitsTable& aldens = *fits.table("ALDENS");
250 const GFitsTable& althick = *fits.table("ALTHICK");
251 const GFitsTable& aboved1 = *fits.table("ABOVED1");
252 const GFitsTable& abdens = *fits.table("ABDENS");
253 const GFitsTable& abthick = *fits.table("ABTHICK");
254 const GFitsTable& vetodome = *fits.table("VETODOME");
255 const GFitsTable& vetodens = *fits.table("VETODENS");
256 const GFitsTable& v1thick = *fits.table("V1THICK");
257 const GFitsTable& vthick = *fits.table("VTHICK");
258 const GFitsTable& selfveto = *fits.table("SELFVETO");
259 const GFitsTable& d1multi = *fits.table("D1MULTI");
260 const GFitsTable& d2multi = *fits.table("D2MULTI");
261
262 // Read information from ICT tables
272 read_selfveto(selfveto);
273
274 // Read constants
275 m_d1dens = d1dens["DENSITY"]->real(0);
276 m_d1rad = d1rad["RADIUS"]->real(0);
277 m_d1thick = d1thick["THICKNESS"]->real(0);
278 m_d2dens = d2dens["DENSITY"]->real(0);
279 m_d2rad = d2rad["RADIUS"]->real(0);
280 m_d2thick = d2thick["THICKNESS"]->real(0);
281 m_thbar = thbar["ANGLE"]->real(0);
282 m_delz = delz["DISTANCE"]->real(0);
283 m_aldens = aldens["DENSITY"]->real(0);
284 m_althick = althick["THICKNESS"]->real(0);
285 m_abdens = abdens["DENSITY"]->real(0);
286 m_abthick = abthick["THICKNESS"]->real(0);
287 m_vetodens = vetodens["DENSITY"]->real(0);
288 m_v1thick = v1thick["THICKNESS"]->real(0);
289 m_vthick = vthick["THICKNESS"]->real(0);
290
291 // Close ICT FITS file
292 fits.close();
293
294 // Return
295 return;
296}
297
298
299/***********************************************************************//**
300 * @brief Return transmission above D1
301 *
302 * @param[in] energy Input photon energy (MeV).
303 * @return Transmission above D1.
304 *
305 * Computes the transmission of material above D1 as function of energy
306 * using
307 *
308 * \f[
309 * T(E) = \exp \left(-\mu(E) l \right)
310 * \f]
311 *
312 * where
313 * \f$\mu(E)\f$ is the energy-dependent interaction coefficient for material
314 * above D1 in units of \f$cm^{-1}\f$ that is interpolated using a log-log
315 * interpolation of the ICT table values, and
316 * \f$l\f$ is the thickness of the material above D1 in \f$cm\f$.
317 ***************************************************************************/
318double GCOMInstChars::trans_D1(const double& energy) const
319{
320 // Initialise transmission
321 double transmission = 1.0;
322
323 // Continue only if there are energies
324 if (m_aboved1_energies.size() > 0) {
325
326 // Get log of energy
327 double logE = std::log(energy);
328
329 // Get attenuation coefficient
331 double coeff = std::exp(logc) * m_abthick;
332
333 // Compute transmission
334 transmission = std::exp(-coeff);
335
336 }
337
338 // Return transmission
339 return transmission;
340}
341
342
343/***********************************************************************//**
344 * @brief Return V1 veto dome transmission
345 *
346 * @param[in] energy Input photon energy (MeV).
347 * @return V1 veto dome transmission.
348 *
349 * Computes the V1 veto dome transmission as function of energy
350 * using
351 *
352 * \f[
353 * T(E) = \exp \left(-\mu(E) l \right)
354 * \f]
355 *
356 * where
357 * \f$\mu(E)\f$ is the energy-dependent interaction coefficient of the V1
358 * veto dome in units of \f$cm^{-1}\f$ that is interpolated using a log-log
359 * interpolation of the ICT table values, and
360 * \f$l\f$ is the thickness of the V1 veto dome in \f$cm\f$.
361 ***************************************************************************/
362double GCOMInstChars::trans_V1(const double& energy) const
363{
364 // Initialise transmission
365 double transmission = 1.0;
366
367 // Continue only if there are energies
368 if (m_veto_energies.size() > 0) {
369
370 // Get log of energy
371 double logE = std::log(energy);
372
373 // Get attenuation coefficient
374 double logc = m_veto_energies.interpolate(logE, m_veto_coeffs);
375 double coeff = std::exp(logc) * m_v1thick;
376
377 // Compute transmission
378 transmission = std::exp(-coeff);
379
380 }
381
382 // Return transmission
383 return transmission;
384}
385
386
387/***********************************************************************//**
388 * @brief Return D1 interaction probability
389 *
390 * @param[in] energy Input photon energy (MeV).
391 * @return D1 interaction probability.
392 *
393 * Computes the D1 interaction probability as function of energy using
394 *
395 * \f[
396 * P(E) = 1 - \exp \left(-\mu_m(E) \, l \right)
397 * \f]
398 *
399 * where
400 * \f$-\mu(E)\f$ is the energy-dependent D1 attenuation coefficient in units
401 * of \f$1/cm\f$ that is interpolated using a log-log interpolation of the
402 * ICT table values, and \f$l\f$ is the thickness of the D1 module in
403 * \f$cm\f$.
404 ***************************************************************************/
405double GCOMInstChars::prob_D1inter(const double& energy) const
406{
407 // Initialise probability
408 double prob = 0.0;
409
410 // Continue only if there are energies
411 if (m_d1inter_energies.size() > 0) {
412
413 // Get log of energy
414 double logE = std::log(energy);
415
416 // Get interaction coefficient
418 double coeff = std::exp(logc) * m_d1thick;
419
420 // Compute interaction probability
421 prob = 1.0 - std::exp(-coeff);
422
423 }
424
425 // Return probability
426 return prob;
427}
428
429
430/***********************************************************************//**
431 * @brief Return probability that no multihit occured
432 *
433 * @param[in] energy Input photon energy (MeV).
434 * @return Probability that no multihit occured.
435 *
436 * Returns the probability that there is no multihit. The probability is
437 * directly interpolated using a log-log interpolation from the D1 values
438 * that are given in the ICT table. The D2 values are not used.
439 ***************************************************************************/
440double GCOMInstChars::prob_no_multihit(const double& energy) const
441{
442 // Initialise probability
443 double prob = 1.0;
444
445 // Continue only if there are energies
446 if (m_d1multi_energies.size() > 0) {
447
448 // Get log of energy
449 double logE = std::log(energy);
450
451 // Compute probability that there is no multihit
452 prob = std::exp(m_d1multi_energies.interpolate(logE, m_d1multi_coeffs));
453
454 // Make sure that probability does not exceed 1
455 if (prob > 1.0) {
456 prob = 1.0;
457 }
458
459 }
460
461 // Return probability
462 return prob;
463}
464
465
466/***********************************************************************//**
467 * @brief Return probability that photon was not self vetoed
468 *
469 * @param[in] energy Input photon energy (MeV).
470 * @param[in] zenith Zenith angle (deg).
471 * @return Probability that photon was not self vetoed.
472 *
473 * Returns the probability that the photon was not self vetoed. The
474 * probability is directly bi-linearly interpolated from the values that
475 * are given in the ICT table.
476 ***************************************************************************/
477double GCOMInstChars::prob_no_selfveto(const double& energy, const double& zenith) const
478{
479 // Initialise probability
480 double prob = 1.0;
481
482 // Get number of energies and zenith angles in array
483 int n_energies = m_selfveto_energies.size();
484 int n_zeniths = m_selfveto_zeniths.size();
485
486 // Continue only if there are energies and zenith angles
487 if ((n_energies > 0) && (n_zeniths > 0)) {
488
489 // Set energy interpolation
491
492 // Set zenith angle interpolation
494
495 // Set array indices for bi-linear interpolation
496 int inx_zenith_left = m_selfveto_zeniths.inx_left() * n_energies;
497 int inx_zenith_right = m_selfveto_zeniths.inx_right() * n_energies;
498 int inx1 = m_selfveto_energies.inx_left() + inx_zenith_left;
499 int inx2 = m_selfveto_energies.inx_left() + inx_zenith_right;
500 int inx3 = m_selfveto_energies.inx_right() + inx_zenith_left;
501 int inx4 = m_selfveto_energies.inx_right() + inx_zenith_right;
502
503 // Set weighting factors for bi-linear interpolation
508
509 // Perform bi-linear interpolation
510 prob = wgt1 * m_selfveto_coeffs[inx1] +
511 wgt2 * m_selfveto_coeffs[inx2] +
512 wgt3 * m_selfveto_coeffs[inx3] +
513 wgt4 * m_selfveto_coeffs[inx4];
514
515 // Make sure that probability does is within [0,1]
516 if (prob < 0.0) {
517 prob = 0.0;
518 }
519 else if (prob > 1.0) {
520 prob = 1.0;
521 }
522
523 // And now convert into a no self veto probability
524 prob = 1.0 - prob;
525
526 }
527
528 // Return probability
529 return prob;
530}
531
532
533/***********************************************************************//**
534 * @brief Return transmission of material between D1 and D2
535 *
536 * @param[in] energy Input photon energy (MeV).
537 * @param[in] phigeo Geometrical scatter angle (deg).
538 * @return Transmission of material between D1 and D2.
539 *
540 * Computes the transmission of material between D1 and D2 as function of
541 * energy using
542 *
543 * \f[
544 * T(E) = \exp \left(\frac{-\mu(E_2) \, l \right)
545 * \f]
546 *
547 * with
548 *
549 * \f[
550 * E_2 = \frac{E_{\gamma}}
551 * {(1 - \cos \varphi_{\rm geo}) \frac{E_{\gamma}}{m_e c^2} + 1}
552 * \f]
553 *
554 * where
555 * \f$E_{\gamma}\f$ is the input photon @p energy in MeV,
556 * \f$\varphi_{\rm geo}\f$ is the geometrical scatter angle,
557 * \f$\mu(E_2)\f$ is the energy-dependent interaction coefficient of the
558 * material between D1 and D2 in units of \f$cm^{-1}\f$ that is interpolated
559 * using a log-log interpolation of the ICT table values, and
560 * \f$l\f$ is the thickness of the material between D1 and D2 in \f$cm\f$.
561 ***************************************************************************/
562double GCOMInstChars::trans_D2(const double& energy, const double& phigeo) const
563{
564 // Initialise transmission
565 double transmission = 1.0;
566
567 // Continue only if there are energies
568 if (m_alu_energies.size() > 0) {
569
570 // Compute log of D2 energy deposit
571 double logE2 = std::log(gammalib::com_energy2(energy, phigeo));
572
573 // Get attenuation coefficient
574 double logc = m_alu_energies.interpolate(logE2, m_alu_coeffs);
575 double coeff = std::exp(logc) * m_althick;
576
577 // Compute transmission
578 transmission = std::exp(-coeff);
579
580 }
581
582 // Return transmission
583 return transmission;
584}
585
586
587/***********************************************************************//**
588 * @brief Return V2+V3 veto dome transmission
589 *
590 * @param[in] energy Input photon energy (MeV).
591 * @param[in] phigeo Geometrical scatter angle (deg).
592 * @return V2+V3 veto dome transmission.
593 *
594 * Computes the V2+V3 veto dome transmission as function of energy
595 * using
596 *
597 * \f[
598 * T(E) = \exp \left(-\mu(E) \, l \right)
599 * \f]
600 *
601 * \f[
602 * E_2 = \frac{E_{\gamma}}
603 * {(1 - \cos \varphi_{\rm geo}) \frac{E_{\gamma}}{m_e c^2} + 1}
604 * \f]
605 *
606 * where
607 * \f$E_{\gamma}\f$ is the input photon @p energy in MeV,
608 * \f$\varphi_{\rm geo}\f$ is the geometrical scatter angle,
609 * \f$\mu(E_2)\f$ is the energy-dependent interaction coefficient of the V2
610 * and V3 veto domes in units of \f$cm^{-1}\f$ that is interpolated using a
611 * log-log interpolation of the ICT table values, and \f$l\f$ is the thickness
612 * of the V2+V3 veto domes in \f$cm\f$.
613 ***************************************************************************/
614double GCOMInstChars::trans_V23(const double& energy, const double& phigeo) const
615{
616 // Initialise transmission
617 double transmission = 1.0;
618
619 // Continue only if there are energies
620 if (m_veto_energies.size() > 0) {
621
622 // Compute log of D2 energy deposit
623 double logE2 = std::log(gammalib::com_energy2(energy, phigeo));
624
625 // Get attenuation coefficient
626 double logc = m_veto_energies.interpolate(logE2, m_veto_coeffs);
627 double coeff = std::exp(logc) * m_vthick;
628
629 // Compute transmission
630 transmission = std::exp(-coeff);
631
632 }
633
634 // Return transmission
635 return transmission;
636}
637
638
639/***********************************************************************//**
640 * @brief Return D2 interaction probability
641 *
642 * @param[in] energy Input photon energy (MeV).
643 * @param[in] phigeo Geometrical scatter angle (deg).
644 * @return D2 interaction probability.
645 *
646 * Computes the D2 interaction probability as function of energy using
647 *
648 * \f[
649 * P(E) = 1 - \exp \left(-\mu_m(E_2) \, l \right)
650 * \f]
651 *
652 * with
653 *
654 * \f[
655 * E_2 = \frac{E_{\gamma}}
656 * {(1 - \cos \varphi_{\rm geo}) \frac{E_{\gamma}}{m_e c^2} + 1}
657 * \f]
658 *
659 * where
660 * \f$E_{\gamma}\f$ is the input photon @p energy in MeV,
661 * \f$\varphi_{\rm geo}\f$ is the geometrical scatter angle,
662 * \f$\mu(E_2)\f$ is the energy-dependent D2 attenuation coefficient in units
663 * of \f$cm^{-1}\f$ that is interpolated using a log-log interpolation of the
664 * ICT table values, and \f$l\f$ is the thickness of the D2 module in
665 * \f$cm\f$.
666 ***************************************************************************/
667double GCOMInstChars::prob_D2inter(const double& energy, const double& phigeo) const
668{
669 // Initialise probability
670 double prob = 0.0;
671
672 // Continue only if there are energies
673 if (m_d2inter_energies.size() > 0) {
674
675 // Compute log of D2 energy deposit
676 double logE2 = std::log(gammalib::com_energy2(energy, phigeo));
677
678 // Get interaction coefficient
679 double logc = m_d2inter_energies.interpolate(logE2, m_d2inter_coeffs);
680 double coeff = std::exp(logc) * m_d2thick;
681
682 // Compute interaction probability
683 prob = 1.0 - std::exp(-coeff);
684
685 }
686
687 // Return probability
688 return prob;
689}
690
691
692/***********************************************************************//**
693 * @brief Return multi-scatter correction
694 *
695 * @param[in] energy Input photon energy (MeV).
696 * @param[in] phigeo Geometrical scatter angle (deg).
697 * @return Correction factor due to multi-scatter.
698 *
699 * Returns the fraction of photons that have undergone a single scatter
700 * and which leave the D1 module unattenuated (no second interaction when
701 * traversing the remaining path inside the same module).
702 *
703 * RES already calculates the fraction of photons which undergo a single
704 * scatter for VERTICALLY indicent photons, based on the Klein-Nishina
705 * cross-section and the composition of NE213A. Therefore the above mentioned
706 * transmission can be applied as a multiplicative correction per phigeo.
707 * However, in order to calculate that correction, an integral over the
708 * module must be performed, which properly takes into account the radiative
709 * transfer inside the module at all R and z. We again use the assumption
710 * of vertical photon incidence, which simplifies the calculation. The
711 * integral is done over the location of the first interaction.
712 *
713 * Note that the transmission calculated is conservative : in reality it will
714 * be a bit higher because of a fraction of the photons which undergo a
715 * second scatter have a final scatter angle after escaping the module within
716 * the phigeo-range of the PSF. These events will, however, mainly be located
717 * at large phibar (large D1E deposit).
718 *
719 * The code implementation is based on the COMPASS RESPSIT2 function
720 * MULTIS.F (release ?, 27-NOV-92).
721 ***************************************************************************/
722double GCOMInstChars::multi_scatter(const double& energy, const double& phigeo) const
723{
724 // Set integration step size
725 const int nz = 10; //!< Number of steps in z-direction
726 const int nphi = 20; //!< Number of azimuthal steps
727
728 // Compute stuff
729 double alpha = energy / gammalib::mec2;
730 double sth = std::sin(phigeo * gammalib::deg2rad);
731 double cth = std::cos(phigeo * gammalib::deg2rad);
732
733 // Get the attenuation coefficient for NE213A
734 double mu = 1.0 / ne213a_mfpath(energy);
735
736 // Compute the energy after scattering and the corresponding attenuation
737 double e_scattered = energy / (1.0 + alpha * (1.0 - cth));
738 double mu_scattered = 1.0 / ne213a_mfpath(e_scattered);
739
740 // Compute the full vertical optical depth of the module
741 double tau0 = mu * m_d1thick;
742
743 // Compute the fraction of photons that are interacting
744 double total_interactions = 1.0 - std::exp(-tau0);
745
746 // Perform integration per rho-value (symmetry!) over
747 // (1) z (the geometrical depth in the module)
748 // (2) phi (azimuth of scatter)
749 // at geometrical scatter angle phigeo.
750 double deltaz = m_d1thick / double(nz);
751 double deltau = deltaz * mu;
752 double dltphi = gammalib::pi / double(nphi);
753
754 // Initialise integration loop. The binning in radius is based on
755 // constant surface per cylinder
756 double rho = 1.0;
757 double delrho = 2.0 * rho;
758 double rholow = rho - 0.5 * delrho; // Should be 0
759 double rhoupp = rho + 0.5 * delrho; // Should be 2
760 double surface = 2.0 * delrho * rho; // Should be 4
761
762 // Compute the integration normalisation value
763 double kappa = deltau / double(nphi);
764
765 // Integration loop over the module radius
766 double contribution_rho = 0.0;
767 double total_surface = surface;
768 int klast = 0; // Will be >0 if the loop should be exited
769 for (int krho = 0; krho < 100; ++krho) {
770
771 // If we have reached the end then exit the loop now
772 if (klast > 0) {
773 break;
774 }
775
776 // Determine radius limits. If D1 module radius is reached or
777 // exceeded, limit the upper rho value to the D1 module radius,
778 // re-compute the surface, and signal to exit the loop in the
779 // next round.
780 if (krho > 0) {
781 rholow = rhoupp;
782 rhoupp = std::sqrt(surface + rholow*rholow);
783 rho = 0.5 * (rhoupp + rholow);
784 if (rhoupp > m_d1rad) {
785 rhoupp = m_d1rad;
786 rho = 0.5 * (rhoupp + rholow);
787 surface = (rhoupp*rhoupp - rholow*rholow);
788 klast = krho;
789 }
790 total_surface += surface;
791 }
792
793 // Initialise azimuthal results
794 std::vector<double> r(nphi, 0.0);
795
796 // Compute remaining radius as function of azimuth angle if the
797 // first interaction was at radius rho.
798 for (int lphi = 0; lphi < nphi; ++lphi) {
799 double phi = dltphi * (lphi + 0.5);
800 double term = rho * std::sin(phi);
801 r[lphi] = -rho * std::cos(phi) +
802 std::sqrt(m_d1rad*m_d1rad - term*term);
803 }
804
805 // Perform integration over depth
806 double contribution_z = 0.0;
807 for (int kz = 0; kz < nz; ++kz) {
808
809 // Calculate depth
810 double z = (kz + 0.5) * deltaz;
811 double tau = z * mu;
812
813 // Compute the remaining length in the module after the first
814 // interaction. Test for foward/backward scattering although
815 // this should not be relevant for any reasonable phigeo values.
816 double length0 = 1000.0;
817 if (sth < 0.99) { // True for all reasonable phigeo's
818 if (cth < 0.0) { // False for all reasonable phigeo's
819 length0 = z/std::abs(cth);
820 }
821 else {
822 length0 = (m_d1thick-z)/cth; // All reasonable phigeo's
823 }
824 }
825
826 // Perform integration over azimuth
827 double contribution_phi = 0.0;
828 for (int kphi = 0; kphi < nphi; ++kphi) {
829
830 // Compute the actual remaining length for a given azimuth
831 // angle phi. Limit the length to the remaining radius.
832 double length = length0;
833 if (length * sth > r[kphi]) {
834 length = r[kphi] / sth;
835 }
836
837 // Now compute the probability that the photon is not
838 // absorbed during the remaining path through the detector.
839 contribution_phi += std::exp(-length * mu_scattered);
840
841 } // endfor: looped over azimuth
842
843 // Average contribution must later be divided by nphi for
844 // phi-pixels and multiplied by deltau; this is done by
845 // multiplying with kappa. Do this at end, to save computation
846 contribution_z += contribution_phi * std::exp(-tau);
847
848 } // endfor: looped over depth
849
850 // Add the average contribution of radius rho
851 contribution_rho += contribution_z * kappa * surface;
852
853 } // endfor: looped over radius
854
855 // Compute average
856 double transmission = contribution_rho / (total_surface * total_interactions);
857
858 // Return transmission
859 return transmission;
860}
861
862
863/***********************************************************************//**
864 * @brief Return PSD correction
865 *
866 * @param[in] energy Input photon energy (MeV).
867 * @param[in] phigeo Geometrical scatter angle (deg).
868 * @return Correction factor due to PSD selection.
869 *
870 * Returns the D1 energy dependent PSD correction as described in
871 * COM-RP-ROL-DRG-016 and COM-RP-ROL-DRG-35. It applies to a standard PSD
872 * selection of 0-110.
873 *
874 * The acceptance probability fit formula
875 *
876 * \f[
877 * P_{\rm acc} = 1 - \frac{1}{a_1 \times E_1^{a_2} + 1}
878 * \f]
879 *
880 * where \f$a_1=1727.9\f$, \f$a_2=2.53\f$ and \f$E_1\f$ is the D1 energy
881 * deposit in MeV. Coefficients are taken from Boron calibration data
882 * (ZA=1.14) to remain consistent with Rob van Dijk's SIMPSF corrections.
883 *
884 * The code implementation is based on the COMPASS RESPSIT2 function
885 * PSDACP.F (release 1.0, 11-DEC-92).
886 ***************************************************************************/
887double GCOMInstChars::psd_correction(const double& energy, const double& phigeo) const
888{
889 // Set constants
890 const double a1 = 1727.9;
891 const double a2 = 2.530;
892
893 // Compute D1 energy deposit
894 double e1 = gammalib::com_energy1(energy, phigeo);
895
896 // Original COMPASS code
897 double psdacp = 1.0 - (1.0 / (a1 * std::pow(e1, a2) + 1.0));
898
899 // Return fraction
900 return psdacp;
901}
902
903
904/***********************************************************************//**
905 * @brief Print COMPTEL instrument characteristics
906 *
907 * @param[in] chatter Chattiness.
908 * @return String containing COMPTEL instrument characteristics.
909 ***************************************************************************/
910std::string GCOMInstChars::print(const GChatter& chatter) const
911{
912 // Initialise result string
913 std::string result;
914
915 // Continue only if chatter is not silent
916 if (chatter != SILENT) {
917
918 // Append header
919 result.append("=== GCOMInstChars ===");
920
921 // Append D1INTER information
922 result.append("\n"+gammalib::parformat("D1 interaction coeffs."));
923 if (m_d1inter_energies.size() > 0) {
924 int last = m_d1inter_energies.size()-1;
925 result.append(gammalib::str(std::exp(m_d1inter_energies[0]))+ " - ");
926 result.append(gammalib::str(std::exp(m_d1inter_energies[last]))+ " MeV");
927 result.append(" [");
928 result.append(gammalib::str(std::exp(min_coeff(m_d1inter_coeffs))));
929 result.append(" - ");
930 result.append(gammalib::str(std::exp(max_coeff(m_d1inter_coeffs))));
931 result.append("] cm^2/g");
932 }
933 else {
934 result.append("not defined");
935 }
936
937 // Append D2INTER information
938 result.append("\n"+gammalib::parformat("D2 interaction coeffs."));
939 if (m_d2inter_energies.size() > 0) {
940 int last = m_d2inter_energies.size()-1;
941 result.append(gammalib::str(std::exp(m_d2inter_energies[0]))+ " - ");
942 result.append(gammalib::str(std::exp(m_d2inter_energies[last]))+ " MeV");
943 result.append(" [");
944 result.append(gammalib::str(std::exp(min_coeff(m_d2inter_coeffs))));
945 result.append(" - ");
946 result.append(gammalib::str(std::exp(max_coeff(m_d2inter_coeffs))));
947 result.append("] cm^2/g");
948 }
949 else {
950 result.append("not defined");
951 }
952
953 // Append ALU information
954 result.append("\n"+gammalib::parformat("Al interaction coeffs."));
955 if (m_alu_energies.size() > 0) {
956 result.append(gammalib::str(std::exp(m_alu_energies[0]))+ " - ");
957 result.append(gammalib::str(std::exp(m_alu_energies[m_alu_energies.size()-1]))+ " MeV");
958 result.append(" [");
959 result.append(gammalib::str(std::exp(min_coeff(m_alu_coeffs))));
960 result.append(" - ");
961 result.append(gammalib::str(std::exp(max_coeff(m_alu_coeffs))));
962 result.append("] 1/cm");
963 }
964 else {
965 result.append("not defined");
966 }
967
968 // Append ABOVED1 information
969 result.append("\n"+gammalib::parformat("Above D1 atten. coeffs."));
970 if (m_aboved1_energies.size() > 0) {
971 result.append(gammalib::str(std::exp(m_aboved1_energies[0]))+ " - ");
972 result.append(gammalib::str(std::exp(m_aboved1_energies[m_aboved1_energies.size()-1]))+ " MeV");
973 result.append(" [");
974 result.append(gammalib::str(std::exp(min_coeff(m_aboved1_coeffs))));
975 result.append(" - ");
976 result.append(gammalib::str(std::exp(max_coeff(m_aboved1_coeffs))));
977 result.append("] 1/cm");
978 }
979 else {
980 result.append("not defined");
981 }
982
983 // Append VETODOME information
984 result.append("\n"+gammalib::parformat("Vetodome atten. coeffs."));
985 if (m_veto_energies.size() > 0) {
986 result.append(gammalib::str(std::exp(m_veto_energies[0]))+ " - ");
987 result.append(gammalib::str(std::exp(m_veto_energies[m_veto_energies.size()-1]))+ " MeV");
988 result.append(" [");
989 result.append(gammalib::str(std::exp(min_coeff(m_veto_coeffs))));
990 result.append(" - ");
991 result.append(gammalib::str(std::exp(max_coeff(m_veto_coeffs))));
992 result.append("] cm^2/g");
993 }
994 else {
995 result.append("not defined");
996 }
997
998 // Append SELFVETO information
999 result.append("\n"+gammalib::parformat("Selfveto probabilities"));
1000 if (m_selfveto_energies.size() > 0) {
1001 result.append(gammalib::str(m_selfveto_energies[0])+ " - ");
1002 result.append(gammalib::str(m_selfveto_energies[m_selfveto_energies.size()-1])+ " MeV x ");
1003 if (m_selfveto_zeniths.size() > 0) {
1004 result.append(gammalib::str(m_selfveto_zeniths[0])+ " - ");
1005 result.append(gammalib::str(m_selfveto_zeniths[m_selfveto_zeniths.size()-1])+ " deg");
1006 }
1007 result.append(" [");
1008 result.append(gammalib::str(min_coeff(m_selfveto_coeffs)));
1009 result.append(" - ");
1010 result.append(gammalib::str(max_coeff(m_selfveto_coeffs)));
1011 result.append("]");
1012 }
1013 else {
1014 result.append("not defined");
1015 }
1016
1017 // Append D1MULTI information
1018 result.append("\n"+gammalib::parformat("D1 multihit probabilities"));
1019 if (m_d1multi_energies.size() > 0) {
1020 result.append(gammalib::str(std::exp(m_d1multi_energies[0]))+ " - ");
1021 result.append(gammalib::str(std::exp(m_d1multi_energies[m_d1multi_energies.size()-1]))+ " MeV");
1022 result.append(" [");
1023 result.append(gammalib::str(std::exp(min_coeff(m_d1multi_coeffs))));
1024 result.append(" - ");
1025 result.append(gammalib::str(std::exp(max_coeff(m_d1multi_coeffs))));
1026 result.append("]");
1027 }
1028 else {
1029 result.append("not defined");
1030 }
1031
1032 // Append D2MULTI information
1033 result.append("\n"+gammalib::parformat("D2 multihit probabilities"));
1034 if (m_d2multi_energies.size() > 0) {
1035 result.append(gammalib::str(std::exp(m_d2multi_energies[0]))+ " - ");
1036 result.append(gammalib::str(std::exp(m_d2multi_energies[m_d2multi_energies.size()-1]))+ " MeV");
1037 result.append(" [");
1038 result.append(gammalib::str(std::exp(min_coeff(m_d2multi_coeffs))));
1039 result.append(" - ");
1040 result.append(gammalib::str(std::exp(max_coeff(m_d2multi_coeffs))));
1041 result.append("]");
1042 }
1043 else {
1044 result.append("not defined");
1045 }
1046
1047 // Append D1POS information
1048 result.append("\n"+gammalib::parformat("D1 positions"));
1049 result.append(gammalib::str(m_d1pos_x.size())+"x & ");
1050 result.append(gammalib::str(m_d1pos_y.size())+"y");
1051
1052 // Append D2POS information
1053 result.append("\n"+gammalib::parformat("D2 positions"));
1054 result.append(gammalib::str(m_d2pos_x.size())+"x & ");
1055 result.append(gammalib::str(m_d2pos_y.size())+"y");
1056
1057 // Append D1DENS information
1058 result.append("\n"+gammalib::parformat("D1 density"));
1059 result.append(gammalib::str(m_d1dens)+ " g/cm^-3");
1060
1061 // Append D1RAD information
1062 result.append("\n"+gammalib::parformat("D1 radius"));
1063 result.append(gammalib::str(m_d1rad)+ " cm");
1064
1065 // Append D1THICK information
1066 result.append("\n"+gammalib::parformat("D1 thickness"));
1067 result.append(gammalib::str(m_d1thick)+ " cm");
1068
1069 // Append D2DENS information
1070 result.append("\n"+gammalib::parformat("D2 density"));
1071 result.append(gammalib::str(m_d2dens)+ " g/cm^-3");
1072
1073 // Append D2RAD information
1074 result.append("\n"+gammalib::parformat("D2 radius"));
1075 result.append(gammalib::str(m_d2rad)+ " cm");
1076
1077 // Append D2THICK information
1078 result.append("\n"+gammalib::parformat("D2 thickness"));
1079 result.append(gammalib::str(m_d2thick)+ " cm");
1080
1081 // Append THBAR information
1082 result.append("\n"+gammalib::parformat("Average D2 incident angle"));
1083 result.append(gammalib::str(m_thbar)+ " deg");
1084
1085 // Append DELZ information
1086 result.append("\n"+gammalib::parformat("Distance between D1 and D2"));
1087 result.append(gammalib::str(m_delz)+ " cm");
1088
1089 // Append ALDENS information
1090 result.append("\n"+gammalib::parformat("Dens. of Al plate above D2"));
1091 result.append(gammalib::str(m_aldens)+ " g/cm^-3");
1092
1093 // Append ALTHICK information
1094 result.append("\n"+gammalib::parformat("Thk. of Al plate above D2"));
1095 result.append(gammalib::str(m_althick)+ " cm");
1096
1097 // Append ABDENS information
1098 result.append("\n"+gammalib::parformat("Density above D1"));
1099 result.append(gammalib::str(m_abdens)+ " g/cm^-3");
1100
1101 // Append ABTHICK information
1102 result.append("\n"+gammalib::parformat("Thickness above D1"));
1103 result.append(gammalib::str(m_abthick)+ " cm");
1104
1105 // Append VETODENS information
1106 result.append("\n"+gammalib::parformat("Density of veto domes"));
1107 result.append(gammalib::str(m_vetodens)+ " g/cm^-3");
1108
1109 // Append V1THICK information
1110 result.append("\n"+gammalib::parformat("Thickness of V1"));
1111 result.append(gammalib::str(m_v1thick)+ " cm");
1112
1113 // Append VTHICK information
1114 result.append("\n"+gammalib::parformat("Thickness of V2+V3"));
1115 result.append(gammalib::str(m_vthick)+ " cm");
1116
1117 // Append calibration database
1118 result.append("\n"+m_caldb.print(chatter));
1119
1120 // Append information
1121
1122 } // endif: chatter was not silent
1123
1124 // Return result
1125 return result;
1126}
1127
1128
1129/*==========================================================================
1130 = =
1131 = Private methods =
1132 = =
1133 ==========================================================================*/
1134
1135/***********************************************************************//**
1136 * @brief Initialise class members
1137 ***************************************************************************/
1139{
1140 // Initialise members
1141 m_caldb.clear();
1143 m_d1inter_coeffs.clear();
1145 m_d2inter_coeffs.clear();
1147 m_alu_coeffs.clear();
1149 m_aboved1_coeffs.clear();
1151 m_veto_coeffs.clear();
1154 m_selfveto_coeffs.clear();
1156 m_d1multi_coeffs.clear();
1158 m_d2multi_coeffs.clear();
1159 m_d1pos_x.clear();
1160 m_d1pos_y.clear();
1161 m_d2pos_x.clear();
1162 m_d2pos_y.clear();
1163 m_d1dens = 0.0;
1164 m_d1rad = 0.0;
1165 m_d1thick = 0.0;
1166 m_d2dens = 0.0;
1167 m_d2rad = 0.0;
1168 m_d2thick = 0.0;
1169 m_thbar = 0.0;
1170 m_delz = 0.0;
1171 m_aldens = 0.0;
1172 m_althick = 0.0;
1173 m_abdens = 0.0;
1174 m_abthick = 0.0;
1175 m_vetodens = 0.0;
1176 m_v1thick = 0.0;
1177 m_vthick = 0.0;
1178
1179 // Return
1180 return;
1181}
1182
1183
1184/***********************************************************************//**
1185 * @brief Copy class members
1186 *
1187 * @param[in] ict COMPTEL instrument characteristics.
1188 ***************************************************************************/
1190{
1191 // Copy attributes
1192 m_caldb = ict.m_caldb;
1210 m_d1pos_x = ict.m_d1pos_x;
1211 m_d1pos_y = ict.m_d1pos_y;
1212 m_d2pos_x = ict.m_d2pos_x;
1213 m_d2pos_y = ict.m_d2pos_y;
1214 m_d1dens = ict.m_d1dens;
1215 m_d1rad = ict.m_d1rad;
1216 m_d1thick = ict.m_d1thick;
1217 m_d2dens = ict.m_d2dens;
1218 m_d2rad = ict.m_d2rad;
1219 m_d2thick = ict.m_d2thick;
1220 m_thbar = ict.m_thbar;
1221 m_delz = ict.m_delz;
1222 m_aldens = ict.m_aldens;
1223 m_althick = ict.m_althick;
1224 m_abdens = ict.m_abdens;
1225 m_abthick = ict.m_abthick;
1226 m_vetodens = ict.m_vetodens;
1227 m_v1thick = ict.m_v1thick;
1228 m_vthick = ict.m_vthick;
1229
1230 // Return
1231 return;
1232}
1233
1234
1235/***********************************************************************//**
1236 * @brief Delete class members
1237 ***************************************************************************/
1239{
1240 // Return
1241 return;
1242}
1243
1244
1245/***********************************************************************//**
1246 * @brief Read energy dependent coefficients.
1247 *
1248 * @param[in] table FITS table.
1249 * @param[out] energies Energy node array.
1250 * @param[out] coeffs Coefficients.
1251 *
1252 * Read energy dependent coefficients from FITS table and store their natural
1253 * logarithm in the @p energies and @p coeffs vectors.
1254 ***************************************************************************/
1256 GNodeArray& energies,
1257 std::vector<double>& coeffs)
1258{
1259 // Initialise energies and coefficients
1260 energies.clear();
1261 coeffs.clear();
1262
1263 // Extract number of entries in table
1264 int num = table.nrows();
1265
1266 // If there are entries then read them
1267 if (num > 0) {
1268
1269 // By default use "COEFFICIENT" column for coefficients, but if
1270 // table contains a "PROBABILITY" column then use that column
1271 std::string coeff_name = "COEFFICIENT";
1272 if (table.contains("PROBABILITY")) {
1273 coeff_name = "PROBABILITY";
1274 }
1275
1276 // Get column pointers
1277 const GFitsTableCol* ptr_energy = table["ENERGY"];
1278 const GFitsTableCol* ptr_coeff = table[coeff_name];
1279
1280 // Copy data from table into vectors. Skip any non-positive values
1281 // since we store the logarithms for a log-log interpolation.
1282 for (int i = 0; i < num; ++i) {
1283 double energy = ptr_energy->real(i);
1284 double coeff = ptr_coeff->real(i);
1285 if ((energy > 0.0) && (coeff > 0.0)) {
1286 energies.append(std::log(energy));
1287 coeffs.push_back(std::log(coeff));
1288 }
1289 }
1290
1291 } // endif: there were entries
1292
1293 // Return
1294 return;
1295}
1296
1297
1298/***********************************************************************//**
1299 * @brief Read module positions.
1300 *
1301 * @param[in] table FITS table.
1302 * @param[out] x X-positions of module.
1303 * @param[out] y Y-positions of module.
1304 *
1305 * Read module positions from FITS table.
1306 ***************************************************************************/
1307void GCOMInstChars::read_pos(const GFitsTable& table, std::vector<double>& x,
1308 std::vector<double>& y)
1309{
1310 // Initialise positions
1311 x.clear();
1312 y.clear();
1313
1314 // Extract number of entries in table
1315 int num = table.nrows();
1316
1317 // If there are entries then read them
1318 if (num > 0) {
1319
1320 // Get column pointers
1321 const GFitsTableCol* ptr_detnum = table["DETNUM"];
1322 const GFitsTableCol* ptr_x = table["X"];
1323 const GFitsTableCol* ptr_y = table["Y"];
1324
1325 // Copy data from table into vectors
1326 for (int i = 0; i < num; ++i) {
1327 int detnum = ptr_detnum->integer(i) - 1;
1328 x.push_back(ptr_x->real(detnum));
1329 y.push_back(ptr_y->real(detnum));
1330 }
1331
1332 } // endif: there were entries
1333
1334 // Return
1335 return;
1336}
1337
1338
1339/***********************************************************************//**
1340 * @brief Read selfveto coefficients.
1341 *
1342 * @param[in] table FITS table.
1343 *
1344 * Read selfveto coefficients from FITS table. The selfveto coefficients
1345 * depend on energy and zenith angle, and the input data are not necessarily
1346 * regularly sampled. Therefore the method will form a regular grid from
1347 * the provided values and compute the interpolated values from that grid.
1348 ***************************************************************************/
1350{
1351 // Initialise selfveto vectors
1354 m_selfveto_coeffs.clear();
1355
1356 // Extract number of entries in table
1357 int num = table.nrows();
1358
1359 // If there are entries then read them
1360 if (num > 0) {
1361
1362 // Get column pointers
1363 const GFitsTableCol* ptr_energies = table["ENERGY"];
1364 const GFitsTableCol* ptr_zeniths = table["ZENITH"];
1365 const GFitsTableCol* ptr_coeffs = table["PROBABILITY"];
1366
1367 // Initialise energies and zenith vectors for sorting before creating
1368 // node arrays
1369 std::vector<double> energies;
1370 std::vector<double> zeniths;
1371
1372 // Determine all energies and zenith angles in table
1373 for (int i = 0; i < num; ++i) {
1374
1375 // Add energy if it is not yet in array
1376 double energy = ptr_energies->real(i);
1377 bool add = true;
1378 for (int k = 0; k < energies.size(); ++k) {
1379 if (energies[k] == energy) {
1380 add = false;
1381 break;
1382 }
1383 }
1384 if (add) {
1385 energies.push_back(energy);
1386 }
1387
1388 // Add zenith angle if it is not yet in array
1389 double zenith = ptr_zeniths->real(i);
1390 add = true;
1391 for (int k = 0; k < m_selfveto_zeniths.size(); ++k) {
1392 if (m_selfveto_zeniths[k] == zenith) {
1393 add = false;
1394 break;
1395 }
1396 }
1397 if (add) {
1398 zeniths.push_back(zenith);
1399 }
1400
1401 } // endfor: collected all energies and zenith angles
1402
1403 // Sort energies and zenith angles
1404 std::sort(energies.begin(), energies.end());
1405 std::sort(zeniths.begin(), zeniths.end());
1406
1407 // Set node arrays
1408 m_selfveto_energies.nodes(energies);
1409 m_selfveto_zeniths.nodes(zeniths);
1410
1411 // Get array size
1412 int neng = m_selfveto_energies.size();
1413 int nzenith = m_selfveto_zeniths.size();
1414
1415 // Fill all coefficients with -1, meaning that there is no value
1416 m_selfveto_coeffs.assign(neng*nzenith, -1.0);
1417
1418 // Fill all coefficients
1419 for (int i = 0; i < num; ++i) {
1420
1421 // Find nearest energy
1422 int ieng = 0;
1423 double energy = ptr_energies->real(i);
1424 double delta = std::abs(energy - m_selfveto_energies[0]);
1425 for (int k = 1; k < neng; ++k) {
1426 double d = std::abs(energy - m_selfveto_energies[k]);
1427 if (d < delta) {
1428 delta = d;
1429 ieng = k;
1430 }
1431 }
1432
1433 // Find nearest zenith angle
1434 int izenith = 0;
1435 double zenith = ptr_zeniths->real(i);
1436 delta = std::abs(zenith - m_selfveto_zeniths[0]);
1437 for (int k = 1; k < nzenith; ++k) {
1438 double d = std::abs(zenith - m_selfveto_zeniths[k]);
1439 if (d < delta) {
1440 delta = d;
1441 izenith = k;
1442 }
1443 }
1444
1445 // Compute index
1446 int index = ieng + neng*izenith;
1447
1448 // Set coefficient
1449 m_selfveto_coeffs[index] = ptr_coeffs->real(i);
1450
1451 } // endfor: filled all coefficients
1452
1453 // Now loop over all energies and fill the missing zenith angles
1454 for (int ieng = 0; ieng < neng; ++ieng) {
1455
1456 // Debug: print header
1457 #if defined(G_DEBUG_READ_SELFVETO)
1458 std::cout << "Energy: " << m_selfveto_energies[ieng] << std::endl;
1459 std::cout << " Zenith angles:";
1460 #endif
1461
1462 // Set up arrays for interpolation
1463 GNodeArray nodes;
1464 std::vector<double> values;
1465 for (int izenith = 0; izenith < nzenith; ++izenith) {
1466 int index = ieng + neng*izenith;
1467 if (m_selfveto_coeffs[index] > -0.5) {
1468 nodes.append(m_selfveto_zeniths[izenith]);
1469 values.push_back(m_selfveto_coeffs[index]);
1470 #if defined(G_DEBUG_READ_SELFVETO)
1471 std::cout << " " << m_selfveto_zeniths[izenith];
1472 std::cout << "(" << m_selfveto_coeffs[index] << ")";
1473 #endif
1474 }
1475 }
1476
1477 // Debug: print header
1478 #if defined(G_DEBUG_READ_SELFVETO)
1479 std::cout << std::endl;
1480 std::cout << " Interpolated zenith angles:";
1481 #endif
1482
1483 // Interpolate coefficients for zenith angles
1484 for (int izenith = 0; izenith < nzenith; ++izenith) {
1485 int index = ieng + neng*izenith;
1486 if (m_selfveto_coeffs[index] < -0.5) {
1487 double coeffs = nodes.interpolate(m_selfveto_zeniths[izenith], values);
1488 if (coeffs < 0.0) {
1489 coeffs = 0.0;
1490 }
1491 else if (coeffs > 1.0) {
1492 coeffs = 1.0;
1493 }
1494 m_selfveto_coeffs[index] = coeffs;
1495 #if defined(G_DEBUG_READ_SELFVETO)
1496 std::cout << " " << m_selfveto_zeniths[izenith];
1497 std::cout << "(" << m_selfveto_coeffs[index] << ")";
1498 #endif
1499 }
1500 }
1501
1502 // Debug: print line feed
1503 #if defined(G_DEBUG_READ_SELFVETO)
1504 std::cout << std::endl;
1505 #endif
1506
1507 } // endfor: looped over all energies
1508
1509 } // endif: there were entries
1510
1511 // Return
1512 return;
1513}
1514
1515
1516/***********************************************************************//**
1517 * @brief Return NE213A mean free path
1518 *
1519 * @param[in] energy Energy (MeV)
1520 * @return Mean free path (cm)
1521 *
1522 * The mean free path as a function of energy, linearly interpolated between
1523 * data-points. Table of mean free paths for NE213A taken from L. Kuiper
1524 * calculated in JOB ROL-SPDSD1-43. This includes (1) Compton scattering and
1525 * (2) Pair production.
1526 ***************************************************************************/
1527double GCOMInstChars::ne213a_mfpath(const double& energy) const
1528{
1529 #if defined(USE_ICT_MEAN_FREE_PATH)
1530 // Get log of energy
1531 double logE = std::log(energy);
1532
1533 // Get log of interaction coefficient
1534 double logc = m_d1inter_energies.interpolate(logE, m_d1inter_coeffs);
1535
1536 // Convert into mean free path
1537 double result = 1.0 / std::exp(logc);
1538
1539 #else
1540 // NE213A mean free path values for energies from 1,2,...,30 MeV
1541 const double mfpath[] = {16.19, 23.21, 29.01, 34.04, 38.46,
1542 42.35, 45.64, 48.81, 51.53, 54.17,
1543 56.15, 58.09, 59.96, 61.74, 63.41,
1544 64.62, 65.80, 66.94, 68.04, 69.09,
1545 69.86, 70.61, 71.33, 72.03, 72.70,
1546 73.33, 73.93, 74.50, 75.03, 75.51};
1547
1548 // Normalisation constant for low-energy extrapolation
1549 const double norm = mfpath[1] * kn_cross_section(2.0 / gammalib::mec2);
1550
1551 // Initialise mfpath
1552 double result = 0.0;
1553
1554 // Do a Klein-Nishina extrapolation for low energies
1555 if (energy < 2.0) {
1556 result = norm / kn_cross_section(energy / gammalib::mec2);
1557 }
1558
1559 // ... otherwise do a linear interpolation
1560 else {
1561 int index = int(energy) - 1;
1562 if (index >= 29) {
1563 index = 28;
1564 }
1565 double slope = mfpath[index+1] - mfpath[index];
1566 result = mfpath[index] + slope * (energy - double(index + 1));
1567 }
1568 #endif
1569
1570 // Return result
1571 return result;
1572}
1573
1574
1575/***********************************************************************//**
1576 * @brief Return integrated Klein-Nishina cross section
1577 *
1578 * @param[in] k \f$E/ m_e c^2\f$
1579 * @return Integrated Klein-Nishina cross section
1580 *
1581 * Computes
1582 *
1583 * \f[
1584 * \sigma_{\rm KN}(k) = \frac{1+k}{k^2}
1585 * \left[
1586 * \frac{2(1+k)}{1+2k} - \frac{\ln(1+2k)}{k}
1587 * \right] + \frac{\ln(1+2k)}{2k} -
1588 * \frac{1+3k}{(1+2k)^2}
1589 * \f]
1590 *
1591 * where \f$k = E/ m_e c^2\f$.
1592 ***************************************************************************/
1593double GCOMInstChars::kn_cross_section(const double& k) const
1594{
1595 // Compute integrated Klein-Nishina cross section
1596 double k1 = k + 1.0;
1597 double k2p1 = 1.0 + 2.0*k;
1598 double logk2p1 = std::log(k2p1);
1599 double kn = k1/(k*k) * (2.0*k1/k2p1 - logk2p1/k) +
1600 logk2p1/(2.0*k) - (1.0+3.0*k)/(k2p1*k2p1);
1601
1602 // Return
1603 return kn;
1604}
1605
1606
1607/***********************************************************************//**
1608 * @brief Return minimum coefficient.
1609 *
1610 * @param[in] coeffs Coefficients.
1611 * @return Minimum coefficient.
1612 *
1613 * Returns minimum coefficient.
1614 ***************************************************************************/
1615double GCOMInstChars::min_coeff(const std::vector<double>& coeffs) const
1616{
1617 // Initialise minimum
1618 double min = 0.0;
1619
1620 // Continue only if there are coefficients
1621 if (coeffs.size() > 0) {
1622
1623 // Set first value as minimum
1624 min = coeffs[0];
1625
1626 // Search for minimum
1627 for (int i = 1; i < coeffs.size(); ++i) {
1628 if (coeffs[i] < min) {
1629 min = coeffs[i];
1630 }
1631 }
1632
1633 }
1634
1635 // Return minimum
1636 return min;
1637}
1638
1639
1640/***********************************************************************//**
1641 * @brief Return maximum coefficient.
1642 *
1643 * @param[in] coeffs Coefficients.
1644 * @return Maximum coefficient.
1645 *
1646 * Returns maximum coefficient.
1647 ***************************************************************************/
1648double GCOMInstChars::max_coeff(const std::vector<double>& coeffs) const
1649{
1650 // Initialise maximum
1651 double max = 0.0;
1652
1653 // Continue only if there are coefficients
1654 if (coeffs.size() > 0) {
1655
1656 // Set first value as maximum
1657 max = coeffs[0];
1658
1659 // Search for maximum
1660 for (int i = 1; i < coeffs.size(); ++i) {
1661 if (coeffs[i] > max) {
1662 max = coeffs[i];
1663 }
1664 }
1665
1666 }
1667
1668 // Return maximum
1669 return max;
1670}
COMPTEL Instrument Characteristics interface definition.
Implementation of support function used by COMPTEL classes.
Energy value class definition.
Mathematical function definitions.
GChatter
Definition GTypemaps.hpp:33
@ SILENT
Definition GTypemaps.hpp:34
double norm(const GVector &vector)
Computes vector norm.
Definition GVector.cpp:864
double min(const GVector &vector)
Computes minimum vector element.
Definition GVector.cpp:886
double max(const GVector &vector)
Computes maximum vector element.
Definition GVector.cpp:915
Interface for the COMPTEL Instrument Characteristics class.
double m_d1rad
D1 radius (cm)
std::vector< double > m_selfveto_coeffs
Selfveto coefficients (probability)
double trans_V1(const double &energy) const
Return V1 veto dome transmission.
double max_coeff(const std::vector< double > &coeffs) const
Return maximum coefficient.
std::vector< double > m_d2pos_x
D2 x-position (cm)
double trans_V23(const double &energy, const double &phigeo) const
Return V2+V3 veto dome transmission.
GNodeArray m_veto_energies
Veto dome attenuation coefficient energies (MeV)
GNodeArray m_d2multi_energies
D2 multihit attenuation coefficient energies (MeV)
std::vector< double > m_veto_coeffs
Veto dome attenuation coefficients.
double m_v1thick
Thickness of V1 veto dome (cm)
double trans_D1(const double &energy) const
Return transmission above D1.
std::vector< double > m_alu_coeffs
Al interaction coefficients.
GNodeArray m_aboved1_energies
Above D1 attenuation coefficient energies (MeV)
void load(const std::string &ictname)
Load COMPTEL instrument characteristics.
double m_d2rad
D2 radius (cm)
GCOMInstChars(void)
Void constructor.
double m_d2dens
D2 density (g/cm^-3)
double multi_scatter(const double &energy, const double &phigeo) const
Return multi-scatter correction.
GNodeArray m_selfveto_energies
Selfveto energies (MeV)
double prob_no_multihit(const double &energy) const
Return probability that no multihit occured.
std::vector< double > m_aboved1_coeffs
Above D1 attenuation coefficients.
double prob_no_selfveto(const double &energy, const double &zenith) const
Return probability that photon was not self vetoed.
GNodeArray m_d2inter_energies
D2 interaction coefficient energies (MeV)
double m_d2thick
D2 thickness (cm)
double ne213a_mfpath(const double &energy) const
Return NE213A mean free path.
std::vector< double > m_d1pos_y
D1 y-position (cm)
std::vector< double > m_d2pos_y
D2 y-position (cm)
double m_thbar
Average D2 incident angle (deg)
GNodeArray m_d1multi_energies
D1 multihit attenuation coefficient energies (MeV)
double m_aldens
Density of aluminium plate above D2 (g/cm^-3)
GNodeArray m_selfveto_zeniths
Selfveto zenith angle (deg)
GCOMInstChars & operator=(const GCOMInstChars &ict)
Assignment operator.
std::vector< double > m_d1multi_coeffs
D1 multihit attenuation coefficients (probability)
double kn_cross_section(const double &k) const
Return integrated Klein-Nishina cross section.
double m_althick
Thickness of aluminium plate above D2 (cm)
double m_d1thick
D1 thickness (cm)
std::vector< double > m_d1pos_x
D1 x-position (cm)
GNodeArray m_d1inter_energies
D1 interaction coefficient energies (MeV)
double m_delz
Distance between D1 and D2 levels (cm)
double trans_D2(const double &energy, const double &phigeo) const
Return transmission of material between D1 and D2.
double m_vetodens
Density of veto domes (g/cm^-3)
void read_coeffs(const GFitsTable &table, GNodeArray &energies, std::vector< double > &coeffs)
Read energy dependent coefficients.
GNodeArray m_alu_energies
Al interaction coefficient energies (MeV)
const GCaldb & caldb(void) const
Return calibration database.
void read_selfveto(const GFitsTable &table)
Read selfveto coefficients.
void copy_members(const GCOMInstChars &ict)
Copy class members.
double psd_correction(const double &energy, const double &phigeo) const
Return PSD correction.
~GCOMInstChars(void)
Destructor.
void init_members(void)
Initialise class members.
double m_vthick
Thickness of V2 and V3 veto domes together (cm)
std::vector< double > m_d1inter_coeffs
D1 interaction coefficients.
std::vector< double > m_d2multi_coeffs
D2 multihit attenuation coefficients (probability)
void read_pos(const GFitsTable &table, std::vector< double > &x, std::vector< double > &y)
Read module positions.
void clear(void)
Clear instance.
void free_members(void)
Delete class members.
double prob_D1inter(const double &energy) const
Return D1 interaction probability.
double m_abthick
Thickness above D1 (cm)
double prob_D2inter(const double &energy, const double &phigeo) const
Return D2 interaction probability.
GCOMInstChars * clone(void) const
Clone instance.
std::vector< double > m_d2inter_coeffs
D2 interaction coefficients.
double m_abdens
Density above D1 (g/cm^-3)
double m_d1dens
D1 density (g/cm^-3)
std::string print(const GChatter &chatter=NORMAL) const
Print COMPTEL instrument characteristics.
GCaldb m_caldb
Calibration database.
double min_coeff(const std::vector< double > &coeffs) const
Return minimum coefficient.
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
Filename class.
Definition GFilename.hpp:62
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
Abstract interface for FITS table column.
virtual int integer(const int &row, const int &inx=0) const =0
virtual double real(const int &row, const int &inx=0) const =0
Abstract interface for FITS table.
bool contains(const std::string &colname) const
Checks the presence of a column in table.
const int & nrows(void) const
Return number of rows in table.
FITS file class.
Definition GFits.hpp:63
void close(void)
Close FITS file.
Definition GFits.cpp:1342
GFitsTable * table(const int &extno)
Get pointer to table HDU.
Definition GFits.cpp:482
Node array class.
void set_value(const double &value) const
Set indices and weighting factors for interpolation.
double interpolate(const double &value, const std::vector< double > &vector) const
Interpolate value.
const int & inx_right(void) const
Returns right node index.
const int & inx_left(void) const
Returns left node index.
const double & wgt_right(void) const
Returns right node weight.
const double & wgt_left(void) const
Returns left node weight.
void clear(void)
Clear node array.
void nodes(const int &num, const double *array)
Set node array.
int size(void) const
Return number of nodes in node array.
void append(const double &node)
Append one node to array.
std::string parformat(const std::string &s, const int &indent=0)
Convert string in parameter format.
Definition GTools.cpp:1143
double com_energy1(const double &energy, const double &phigeo)
Return D1 energy deposit.
std::string str(const unsigned short int &value)
Convert unsigned short integer value into string.
Definition GTools.cpp:489
const double pi
Definition GMath.hpp:35
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 deg2rad
Definition GMath.hpp:43
const double mec2
Definition GTools.hpp:52
double com_energy2(const double &energy, const double &phigeo)
Return D2 energy deposit.