GammaLib 2.1.0.dev
Loading...
Searching...
No Matches
GModelSpectralTable.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * GModelSpectralTable.cpp - Spectral table model class *
3 * ----------------------------------------------------------------------- *
4 * copyright (C) 2019-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 GModelSpectralTable.cpp
23 * @brief Spectral table model class implementation
24 * @author Juergen Knoedlseder
25 */
26
27/* __ Includes ___________________________________________________________ */
28#ifdef HAVE_CONFIG_H
29#include <config.h>
30#endif
31#include <cmath>
32#include "GException.hpp"
33#include "GTools.hpp"
34#include "GMath.hpp"
35#include "GRan.hpp"
36#include "GEnergy.hpp"
37#include "GFits.hpp"
38#include "GFitsBinTable.hpp"
40#include "GFitsTableLongCol.hpp"
46
47/* __ Constants __________________________________________________________ */
48
49/* __ Globals ____________________________________________________________ */
51const GModelSpectralRegistry g_spectral_table_registry(&g_spectral_table_seed);
52
53/* __ Method name definitions ____________________________________________ */
54#define G_CONST "GModelSpectralTable(GEbounds&, GModelSpectralTablePars&, "\
55 "GNdarray&)"
56#define G_FLUX "GModelSpectralTable::flux(GEnergy&, GEnergy&)"
57#define G_EFLUX "GModelSpectralTable::eflux(GEnergy&, GEnergy&)"
58#define G_MC "GModelSpectralTable::mc(GEnergy&, GEnergy&, GTime&, GRan&)"
59#define G_READ "GModelSpectralTable::read(GXmlElement&)"
60#define G_WRITE "GModelSpectralTable::write(GXmlElement&)"
61#define G_LOAD "GModelSpectralTable::load(GFilename&)"
62#define G_TABLE_PAR "GModelSpectralTable::table_par(int&)"
63#define G_ENERGY_SCALE "GModelSpectralTable::energy_scale(std::string&)"
64#define G_LOAD_PAR "GModelSpectralTable::load_par(GFits&)"
65#define G_PAR_INDEX "GModelSpectralTable::par_index(std::string&)"
66#define G_UPDATE "GModelSpectralTable::update()"
67
68/* __ Macros _____________________________________________________________ */
69
70/* __ Coding definitions _________________________________________________ */
71
72/* __ Debug definitions __________________________________________________ */
73//#define G_DEBUG_LOAD_SPEC //!< Debug load_spec() method
74//#define G_DEBUG_ESCALE //!< Debug energy scaling
75//#define G_DEBUG_UPDATE //!< Debug update() method
76//#define G_DEBUG_CALLTREE //!< Debug call tree
77
78
79/*==========================================================================
80 = =
81 = Constructors/destructors =
82 = =
83 ==========================================================================*/
84
85/***********************************************************************//**
86 * @brief Void constructor
87 ***************************************************************************/
89{
90 // Initialise members
92
93 // Return
94 return;
95}
96
97
98/***********************************************************************//**
99 * @brief File constructor
100 *
101 * @param[in] filename File name of table model.
102 * @param[in] norm Normalization factor.
103 *
104 * Constructs a spectral table model from a FITS file. See the load() method
105 * for more information about the expected structure of the FITS file.
106 ***************************************************************************/
108 const double& norm) :
110{
111 // Initialise members
112 init_members();
113
114 // Load table
115 load(filename);
116
117 // Set normalization as the scale factor and limit change in
118 // normalisation to a factor of 1000
122 m_norm.range(0.0,1.0e3*norm);
123
124 // Return
125 return;
126}
127
128
129/***********************************************************************//**
130 * @brief Table model constructor
131 *
132 * @param[in] ebounds Energy boundaries.
133 * @param[in] pars Table model parameters.
134 * @param[in] spectra Spectra.
135 *
136 * Constructs a spectral table model from energy boundaries, table model
137 * parameters, and spectra.
138 ***************************************************************************/
140 const GModelSpectralTablePars& pars,
141 const GNdarray& spectra) :
143{
144 // Initialise members
145 init_members();
146
147 // Check consistency of spectra dimension
148 if (spectra.dim() != pars.size()+1) {
149 std::string msg = "Spectra array has "+gammalib::str(spectra.dim())+
150 " dimensions but an array with "+
151 gammalib::str(pars.size()+1)+" dimensions is "
152 "expected. Please specify a spectra array with the "
153 "correct dimension.";
155 }
156
157 // Check consistency of parameter dimensions
158 for (int i = 0; i < pars.size(); ++i) {
159 int npars = pars[i]->size();
160 int nspec = spectra.shape()[i];
161 if (npars != nspec) {
162 std::string msg = "Parameter \""+pars[i]->par().name()+"\" has "+
163 gammalib::str(npars)+" values but there are "+
164 gammalib::str(nspec)+" spectra for table axis "+
165 gammalib::str(i)+". Please specify a spectra "
166 "array with the correct number of spectra.";
168 }
169 }
170
171 // Check consistency of energy bins
172 int nebins = ebounds.size();
173 int nspec = spectra.shape()[pars.size()];
174 if (nebins != nspec) {
175 std::string msg = "Spectra array has "+gammalib::str(nspec)+" energy "
176 "bins but there are "+gammalib::str(nebins)+
177 " energy boundaries. Please specify a spectra "
178 "array with the correct number of energy bins.";
180 }
181
182 // Set members
184 m_table_pars = pars;
185 m_spectra = spectra;
186
187 // Set energy nodes
189
190 // Set parameter pointers
192
193 // Return
194 return;
195}
196
197
198/***********************************************************************//**
199 * @brief XML constructor
200 *
201 * @param[in] xml XML element.
202 *
203 * Constructs a spectral table model by extracting information from an XML
204 * element. See the read() method for more information about the expected
205 * structure of the XML element.
206 ***************************************************************************/
209{
210 // Initialise members
211 init_members();
212
213 // Read information from XML element
214 read(xml);
215
216 // Return
217 return;
218}
219
220
221/***********************************************************************//**
222 * @brief Copy constructor
223 *
224 * @param[in] model Table model.
225 ***************************************************************************/
227 GModelSpectral(model)
228{
229 // Initialise members
230 init_members();
231
232 // Copy members
233 copy_members(model);
234
235 // Return
236 return;
237}
238
239
240/***********************************************************************//**
241 * @brief Destructor
242 ***************************************************************************/
244{
245 // Free members
246 free_members();
247
248 // Return
249 return;
250}
251
252
253/*==========================================================================
254 = =
255 = Operators =
256 = =
257 ==========================================================================*/
258
259/***********************************************************************//**
260 * @brief Assignment operator
261 *
262 * @param[in] model Table model.
263 * @return Table model.
264 ***************************************************************************/
266{
267 // Execute only if object is not identical
268 if (this != &model) {
269
270 // Copy base class members
271 this->GModelSpectral::operator=(model);
272
273 // Free members
274 free_members();
275
276 // Initialise members
277 init_members();
278
279 // Copy members
280 copy_members(model);
281
282 } // endif: object was not identical
283
284 // Return
285 return *this;
286}
287
288
289/*==========================================================================
290 = =
291 = Public methods =
292 = =
293 ==========================================================================*/
294
295/***********************************************************************//**
296 * @brief Clear table model
297***************************************************************************/
299{
300 // Free class members (base and derived classes, derived class first)
301 free_members();
303
304 // Initialise members
306 init_members();
307
308 // Return
309 return;
310}
311
312
313/***********************************************************************//**
314 * @brief Clone table model
315***************************************************************************/
317{
318 // Clone table model
319 return new GModelSpectralTable(*this);
320}
321
322
323/***********************************************************************//**
324 * @brief Evaluate spectral table model
325 *
326 * @param[in] srcEng True photon energy.
327 * @param[in] srcTime True photon arrival time.
328 * @param[in] gradients Compute gradients?
329 * @return Model value (ph/cm2/s/MeV).
330 *
331 * Evaluates
332 *
333 * \f[
334 * S_{\rm E}(E | t) = {\tt m\_norm} \times
335 * \left( w_l F_l(p) + w_r F_r(p) \right)
336 * \f]
337 *
338 * where
339 * - \f${\tt m\_norm}\f$ is the normalization factor of the spectral table
340 * model,
341 * - \f$w_l\f$ is the weight of the spectral vector \f$F_l(p)\f$ with the
342 * largest energy \f$E_l\f$ that satisfies \f$E<E_l\f$, and
343 * - \f$w_r\f$ is the weight of the spectral vector \f$F_r(p)\f$ with the
344 * smallest energy \f$E_r\f$ that satisfies \f$E>E_r\f$.
345 *
346 * The weights are computed using
347 *
348 * \f[
349 * w_r = \frac{\log_{10} E - \log_{10} E_l}{\log_{10} E_r - \log_{10} E_l}
350 * \f]
351 *
352 * and \f$w_l = 1 - w_r\f$.
353 *
354 * If @p gradient is true, the method also computes the parameter gradients
355 * using
356 *
357 * \f[
358 * \frac{\delta S_{\rm E}(E | t)}{\delta {\tt m\_norm}} =
359 * \frac{S_{\rm E}(E | t)}{{\tt m\_norm}}
360 * \f]
361 *
362 * and
363 *
364 * \f[
365 * \frac{\delta S_{\rm E}(E | t)}{\delta p} =
366 * {\tt m\_norm} \times
367 * \left( w_l \frac{\delta F_l(p)}{\delta p} +
368 * w_r \frac{\delta F_r(p)}{\delta p} \right)
369 * \f]
370 *
371 * for all other parameters.
372 *
373 * For the computation of \f$F_l(p)\f$, \f$F_r(p)\f$,
374 * \f$\frac{\delta F_l(p)}{\delta p}\f$, and
375 * \f$\frac{\delta F_r(p)}{\delta p}\f$ see the update() method.
376 ***************************************************************************/
378 const GTime& srcTime,
379 const bool& gradients) const
380{
381 // Update spectral function
382 update();
383
384 // Get energy to interpolate. In case that the spectrum was scaled
385 // then use now to actual parameter value of the energy scale to
386 // get scaling factor
387 double log10energy = srcEng.log10MeV();
388 if (m_log10escale != 0.0) {
389 log10energy -= std::log10(table_par(m_escale_par).par().value());
390 #if defined(G_DEBUG_ESCALE)
391 std::cout << "GModelSpectralTable::eval";
392 std::cout << " " << srcEng.log10MeV();
393 std::cout << " " << log10energy << std::endl;
394 #endif
395 }
396
397 // Interpolate function in log10 energy
398 m_log_nodes.set_value(log10energy);
399 double wgt_left = m_log_nodes.wgt_left();
400 double wgt_right = m_log_nodes.wgt_right();
401 int inx_left = m_log_nodes.inx_left();
402 int inx_right = m_log_nodes.inx_right();
403 double val_left = m_lin_values(inx_left, 0);
404 double val_right = m_lin_values(inx_right,0);
405 double func = wgt_left * val_left + wgt_right * val_right;
406
407 // Compute function value
408 double value = m_norm.value() * func;
409
410 // Optionally compute gradients
411 if (gradients) {
412
413 // Compute partial derivative of function normalisation
414 double g_norm = (m_norm.is_free()) ? m_norm.scale() * func : 0.0;
415
416 // Set normalisation gradient
417 m_norm.factor_gradient(g_norm);
418
419 // Compute partial derivatives of all other free parameters
420 int dim = m_table_pars.size();
421 for (int i = 0; i < dim; ++i) {
422
423 // Initialise gradient
424 double grad = 0.0;
425
426 // Get reference to model parameter (circumvent const correctness)
427 GModelPar& par =
428 const_cast<GModelSpectralTablePar*>(m_table_pars[i])->par();
429
430 // If parameter is free then compute gradient
431 if (par.is_free()) {
432 double grad_left = m_lin_values(inx_left, i+1);
433 double grad_right = m_lin_values(inx_right,i+1);
434 if ((m_log10escale != 0.0) && (par.name() == m_escale_par)) {
435 double dxdp = -1.0 / (gammalib::ln10 * par.factor_value());
436 grad = ((m_log_nodes.wgt_grad_left() * dxdp * val_left) +
437 (m_log_nodes.wgt_grad_right() * dxdp * val_right) +
438 (wgt_left * grad_left + wgt_right * grad_right) *
439 par.scale()) *
440 m_norm.value();
441 }
442 else {
443 grad = (wgt_left * grad_left + wgt_right * grad_right) *
444 par.scale() * m_norm.value();
445 }
446 }
447
448 // Set gradient
449 par.factor_gradient(grad);
450
451 } // endfor: looped over all parameters
452
453 } // endif: gradient computation was requested
454
455 // Compile option: Check for NaN/Inf
456 #if defined(G_NAN_CHECK)
457 if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
458 std::cout << "*** ERROR: GModelSpectralTable::eval";
459 std::cout << "(srcEng=" << srcEng;
460 std::cout << ", srcTime=" << srcTime << "):";
461 std::cout << " NaN/Inf encountered";
462 std::cout << " (value=" << value;
463 std::cout << ", norm=" << norm();
464 std::cout << ", func=" << func;
465 std::cout << ")" << std::endl;
466 }
467 #endif
468
469 // Return
470 return value;
471}
472
473
474/***********************************************************************//**
475 * @brief Returns model photon flux between [emin, emax] (units: ph/cm2/s)
476 *
477 * @param[in] emin Minimum photon energy.
478 * @param[in] emax Maximum photon energy.
479 * @return Photon flux (ph/cm2/s).
480 *
481 * Computes
482 *
483 * \f[
484 * \int_{\tt emin}^{\tt emax} S_{\rm E}(E | t) dE
485 * \f]
486 *
487 * where
488 * - [@p emin, @p emax] is an energy interval, and
489 * - \f$S_{\rm E}(E | t)\f$ is the spectral model (ph/cm2/s/MeV).
490 ***************************************************************************/
492 const GEnergy& emax) const
493{
494 // Initialise flux
495 double flux = 0.0;
496
497 // Compute only if integration range is valid
498 if (emin < emax) {
499
500 // Update spectral function and flux cache
501 update();
502 update_flux();
503
504 // Get energy range in MeV
505 double e_min = emin.MeV();
506 double e_max = emax.MeV();
507
508 // Determine left node index for minimum energy
509 m_lin_nodes.set_value(e_min);
510 int inx_emin = m_lin_nodes.inx_left();
511
512 // Determine left node index for maximum energy
513 m_lin_nodes.set_value(e_max);
514 int inx_emax = m_lin_nodes.inx_left();
515
516 // If both energies are within the same nodes then simply
517 // integrate over the energy interval using the appropriate power
518 // law parameters
519 if (inx_emin == inx_emax) {
520 flux = m_prefactor[inx_emin] *
522 e_max,
523 m_epivot[inx_emin],
524 m_gamma[inx_emin]);
525 }
526
527 // ... otherwise integrate over the nodes where emin and emax
528 // resides and all the remaining nodes
529 else {
530
531 // If we are requested to extrapolate beyond first node,
532 // the use the first nodes lower energy and upper energy
533 // boundary for the initial integration.
534 int i_start = (e_min < m_lin_nodes[0]) ? inx_emin : inx_emin+1;
535
536 // Integrate from emin to the node boundary
537 flux = m_prefactor[inx_emin] *
539 m_lin_nodes[i_start],
540 m_epivot[inx_emin],
541 m_gamma[inx_emin]);
542
543 // Integrate over all nodes between
544 for (int i = i_start; i < inx_emax; ++i) {
545 flux += m_flux[i];
546 }
547
548 // Integrate from node boundary to emax
549 flux += m_prefactor[inx_emax] *
551 e_max,
552 m_epivot[inx_emax],
553 m_gamma[inx_emax]);
554
555 } // endelse: emin and emax not between same nodes
556
557 // Multiply flux by normalisation factor
558 flux *= norm();
559
560 } // endif: integration range was valid
561
562 // Return
563 return flux;
564}
565
566
567/***********************************************************************//**
568 * @brief Returns model energy flux between [emin, emax] (units: erg/cm2/s)
569 *
570 * @param[in] emin Minimum photon energy.
571 * @param[in] emax Maximum photon energy.
572 * @return Energy flux (erg/cm2/s).
573 *
574 * Computes
575 *
576 * \f[
577 * \int_{\tt emin}^{\tt emax} S_{\rm E}(E | t) E \, dE
578 * \f]
579 *
580 * where
581 * - [@p emin, @p emax] is an energy interval, and
582 * - \f$S_{\rm E}(E | t)\f$ is the spectral model (ph/cm2/s/MeV).
583 ***************************************************************************/
585 const GEnergy& emax) const
586{
587 // Initialise flux
588 double eflux = 0.0;
589
590 // Compute only if integration range is valid
591 if (emin < emax) {
592
593 // Update spectral function and flux cache
594 update();
595 update_flux();
596
597 // Get energy range in MeV
598 double e_min = emin.MeV();
599 double e_max = emax.MeV();
600
601 // Determine left node index for minimum energy
602 m_lin_nodes.set_value(e_min);
603 int inx_emin = m_lin_nodes.inx_left();
604
605 // Determine left node index for maximum energy
606 m_lin_nodes.set_value(e_max);
607 int inx_emax = m_lin_nodes.inx_left();
608
609 // If both energies are within the same nodes then simply
610 // integrate over the energy interval using the appropriate power
611 // law parameters
612 if (inx_emin == inx_emax) {
613 eflux = m_prefactor[inx_emin] *
615 e_max,
616 m_epivot[inx_emin],
617 m_gamma[inx_emin]) *
619 }
620
621 // ... otherwise integrate over the nodes where emin and emax
622 // resides and all the remaining nodes
623 else {
624
625 // If we are requested to extrapolate beyond first node,
626 // the use the first nodes lower energy and upper energy
627 // boundary for the initial integration.
628 int i_start = (e_min < m_lin_nodes[0]) ? inx_emin : inx_emin+1;
629
630 // Integrate from emin to the node boundary
631 eflux = m_prefactor[inx_emin] *
633 m_lin_nodes[i_start],
634 m_epivot[inx_emin],
635 m_gamma[inx_emin]) *
637
638 // Integrate over all nodes between
639 for (int i = i_start; i < inx_emax; ++i) {
640 eflux += m_eflux[i];
641 }
642
643 // Integrate from node boundary to emax
644 eflux += m_prefactor[inx_emax] *
646 e_max,
647 m_epivot[inx_emax],
648 m_gamma[inx_emax]) *
650
651 } // endelse: emin and emax not between same nodes
652
653 // Multiply flux by normalisation factor
654 eflux *= norm();
655
656 } // endif: integration range was valid
657
658 // Return flux
659 return eflux;
660}
661
662
663/***********************************************************************//**
664 * @brief Returns MC energy between [emin, emax]
665 *
666 * @param[in] emin Minimum photon energy.
667 * @param[in] emax Maximum photon energy.
668 * @param[in] time True photon arrival time.
669 * @param[in,out] ran Random number generator.
670 * @return Energy.
671 *
672 * Returns Monte Carlo energy by randomly drawing from a broken power law
673 * defined by the file function.
674 ***************************************************************************/
676 const GEnergy& emax,
677 const GTime& time,
678 GRan& ran) const
679{
680 // Check energy interval
682
683 // Allocate energy
684 GEnergy energy;
685
686 // Update spectral function and flux cache
687 update();
688 update_flux();
689 update_mc(emin, emax);
690
691 // Determine in which bin we reside
692 int inx = 0;
693 if (m_mc_cum.size() > 1) {
694 double u = ran.uniform();
695 for (inx = m_mc_cum.size()-1; inx > 0; --inx) {
696 if (m_mc_cum[inx-1] <= u) {
697 break;
698 }
699 }
700 }
701
702 // Get random energy for specific bin
703 if (m_mc_exp[inx] != 0.0) {
704 double e_min = m_mc_min[inx];
705 double e_max = m_mc_max[inx];
706 double u = ran.uniform();
707 double eng = (u > 0.0)
708 ? std::exp(std::log(u * (e_max - e_min) + e_min) / m_mc_exp[inx])
709 : 0.0;
710 energy.MeV(eng);
711 }
712 else {
713 double e_min = m_mc_min[inx];
714 double e_max = m_mc_max[inx];
715 double u = ran.uniform();
716 double eng = std::exp(u * (e_max - e_min) + e_min);
717 energy.MeV(eng);
718 }
719
720 // Return energy
721 return energy;
722}
723
724
725/***********************************************************************//**
726 * @brief Read model from XML element
727 *
728 * @param[in] xml XML element containing power law model information.
729 *
730 * Reads the spectral information from an XML element. The format of the XML
731 * elements is
732 *
733 * <spectrum type="TableModel" file="..">
734 * <parameter name="Normalization" scale=".." value=".." min=".." max=".." free=".."/>
735 * </spectrum>
736 *
737 * Optionally, values for the model table parameters can also be provided.
738 ***************************************************************************/
740{
741 // Load table model from file (do this first since method calls clear())
742 load(gammalib::xml_file_expand(xml, xml.attribute("file")));
743
744 // Get normalisation parameter pointer
746
747 // Read normalisation parameter
748 m_norm.read(*norm);
749
750 // Optionally read all table parameters
751 for (int i = 0; i < m_table_pars.size(); ++i) {
752 GModelPar& par = m_table_pars[i]->par();
753 if (gammalib::xml_has_par(xml, par.name())) {
754 const GXmlElement* element =
755 gammalib::xml_get_par(G_READ, xml, par.name());
756 par.read(*element);
757 }
758 }
759
760 // Optionally set energy scale
761 if (xml.has_attribute("energyscale")) {
762 energy_scale(xml.attribute("energyscale"));
763 }
764
765 // Return
766 return;
767}
768
769
770/***********************************************************************//**
771 * @brief Write model into XML element
772 *
773 * @param[in] xml XML element into which model information is written.
774 *
775 * Writes the spectral information into an XML element. The format of the XML
776 * element is
777 *
778 * <spectrum type="FileFunction" file="..">
779 * <parameter name="Normalization" scale=".." value=".." min=".." max=".." free=".."/>
780 * </spectrum>
781 *
782 * In addition, the method writes the model table parameters into the XML
783 * file.
784 *
785 * Note that the function nodes will not be written since they will not be
786 * altered by any method.
787 ***************************************************************************/
789{
790 // Verify model type
792
793 // Get normalisation parameter
795
796 // Write normalisation parameter
797 m_norm.write(*norm);
798
799 // Write all table parameters
800 for (int i = 0; i < m_table_pars.size(); ++i) {
801 const GModelPar& par = m_table_pars[i]->par();
802 GXmlElement* element = gammalib::xml_need_par(G_WRITE, xml, par.name());
803 par.write(*element);
804 }
805
806 // Set file attribute
808
809 // Optionally write energy scale attribute
810 if (!m_escale_par.empty()) {
811 xml.attribute("energyscale", m_escale_par);
812 }
813
814 // Return
815 return;
816}
817
818
819/***********************************************************************//**
820 * @brief Load table from file
821 *
822 * @param[in] filename File name.
823 *
824 * Loads table model from FITS file. The format of the FITS file complies
825 * with https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_92_009/ogip_92_009.html
826 ***************************************************************************/
828{
829 // Clear table model
830 clear();
831
832 // Set filename
834
835 // Continue only if filename is not empty
836 if (!filename.is_empty()) {
837
838 // Open FITS file
839 GFits fits(filename);
840
841 // Load data from extensions
842 load_par(fits);
843 load_eng(fits);
844 load_spec(fits);
845
846 // Close FITS file
847 fits.close();
848
849 // Set energy nodes
851
852 // Set parameter pointers
854
855 } // endif: filename was not empty
856
857 // Return
858 return;
859}
860
861
862/***********************************************************************//**
863 * @brief Save table into file
864 *
865 * @param[in] filename File name.
866 * @param[in] clobber Overwrite existing file?
867 *
868 * Save the table model into a FITS file. The FITS file will contain three
869 * binary table extensions:
870 *
871 * * PARAMETERS - Table model parameters
872 * * ENERGIES - Table model energies
873 * * SPECTRA - Table model spectra
874 *
875 * The format of the FITS file complies with
876 * https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_92_009/ogip_92_009.html
877 ***************************************************************************/
879 const bool& clobber) const
880{
881 // Create FITS file
882 GFits fits;
883
884 // Create binary tables
885 GFitsBinTable par_table = create_par_table();
886 GFitsBinTable eng_table = create_eng_table();
887 GFitsBinTable spec_table = create_spec_table();
888
889 // Append binary tables
890 fits.append(par_table);
891 fits.append(eng_table);
892 fits.append(spec_table);
893
894 // Set keywords in primary header
895 GFitsHDU* primary = fits[0];
896 primary->card("CONTENT", "MODEL", "Spectrum file");
897 primary->card("FILENAME", filename.url(), "FITS file name");
898 primary->card("ORIGIN", PACKAGE_NAME, "Origin of FITS file");
899 primary->card("MODLNAME", "model", "Model name");
900 primary->card("MODLUNIT", "photons/cm^2/s/MeV", "Model units");
901 primary->card("REDSHIFT", false, "If true then redshift will be included as a par");
902 primary->card("ADDMODEL", true, "If true then this is an additive table model");
903 primary->card("HDUCLASS", "OGIP", "Format conforms to OGIP standard");
904 primary->card("HDUCLAS1", "XSPEC TABLE MODEL", "Model spectra for XSPEC");
905 primary->card("HDUVERS", "1.0.0", "Version of format");
906
907 // Save to FITS file
908 fits.saveto(filename, clobber);
909
910 // Set filename
912
913 // Return
914 return;
915}
916
917
918/***********************************************************************//**
919 * @brief Return number of spectra in table model
920 *
921 * @return Number of spectra.
922 *
923 * If the table model is empty the method returns zero.
924 ***************************************************************************/
926{
927 // Initialise number of spectra
928 int nspectra = 0;
929
930 // If there are spectra then compute their number
931 if (m_table_pars.size() > 0) {
932
933 // Initialise number of spectra
934 nspectra = 1;
935
936 // Multiply number of values for all parameters
937 for (int i = 0; i < m_table_pars.size(); ++i) {
938 nspectra *= m_table_pars[i]->values().size();
939 }
940
941 } // endif: there were spectra
942
943 // Return number of spectra
944 return nspectra;
945}
946
947
948/***********************************************************************//**
949 * @brief Return reference to table parameter
950 *
951 * @param[in] index Table parameter index [0,...,size()[.
952 * @return Reference to table parameter.
953 ***************************************************************************/
955{
956 // Raise exception if index is out of range
957 if (index < 0 || index >= size()) {
958 throw GException::out_of_range(G_TABLE_PAR, "Table parameter index",
959 index, size());
960 }
961
962 // Return reference
963 return *(m_table_pars[index]);
964}
965
966
967/***********************************************************************//**
968 * @brief Return const reference to table parameter
969 *
970 * @param[in] index Table parameter index [0,...,size()[.
971 * @return Const reference to table parameter.
972 ***************************************************************************/
974{
975 // Raise exception if index is out of range
976 if (index < 0 || index >= size()) {
977 throw GException::out_of_range(G_TABLE_PAR, "Table parameter index",
978 index, size());
979 }
980
981 // Return reference
982 return *(m_table_pars[index]);
983}
984
985
986/***********************************************************************//**
987 * @brief Return reference to table parameter
988 *
989 * @param[in] name Table parameter name.
990 * @return Reference to table parameter.
991 ***************************************************************************/
993{
994 // Get index from name
995 int index = par_index(name);
996
997 // Return reference
998 return *(m_table_pars[index]);
999}
1000
1001
1002/***********************************************************************//**
1003 * @brief Return const reference to table parameter
1004 *
1005 * @param[in] name Table parameter name.
1006 * @return Const reference to table parameter.
1007 ***************************************************************************/
1008const GModelSpectralTablePar& GModelSpectralTable::table_par(const std::string& name) const
1009{
1010 // Get index from name
1011 int index = par_index(name);
1012
1013 // Return reference
1014 return *(m_table_pars[index]);
1015}
1016
1017
1018/***********************************************************************//**
1019 * @brief Set energy scale
1020 *
1021 * @param[in] name Table parameter name for energy scaling.
1022 *
1023 * @exception GException::invalid_argument
1024 * Table model parameter name not found.s
1025 *
1026 * Sets a given table parameter @p name as energy scale for table model
1027 * interpolation.
1028 ***************************************************************************/
1029void GModelSpectralTable::energy_scale(const std::string& name)
1030{
1031 // Continue only if energy scale is not yet set
1032 if ((m_escale_par.empty()) && (m_log10escale == 0)) {
1033
1034 // Find parameter name in list of table parameters
1035 for (int i = 0; i < m_table_pars.size(); ++i) {
1036 GModelPar& par = m_table_pars[i]->par();
1037 if (par.name() == name) {
1038 m_escale_par = name;
1039 break;
1040 }
1041 }
1042
1043 // Throw an exception if parameter name is not found
1044 if (m_escale_par.empty()) {
1045 std::string msg = "\"energyscale\" attribute specified but "
1046 "parameter \""+name+"\" not found in the "
1047 "table model. Please specify one of the "
1048 "table model parameters as energy scale.";
1050 }
1051
1052 // Optionally log parameter name
1053 #if defined(G_DEBUG_ESCALE)
1054 std::cout << "GModelSpectralTable::energy_scale(): ";
1055 std::cout << "energyscale=" << m_escale_par << std::endl;
1056 #endif
1057
1058 // Scale energy
1059 scale_energy();
1060
1061 } // endif: energy scale is not yet set
1062
1063 // Return
1064 return;
1065}
1066
1067
1068/***********************************************************************//**
1069 * @brief Print table model information
1070 *
1071 * @param[in] chatter Chattiness.
1072 * @return String containing table model information.
1073 ***************************************************************************/
1074std::string GModelSpectralTable::print(const GChatter& chatter) const
1075{
1076 // Initialise result string
1077 std::string result;
1078
1079 // Continue only if chatter is not silent
1080 if (chatter != SILENT) {
1081
1082 // Append header
1083 result.append("=== GModelSpectralTable ===");
1084
1085 // Append information
1086 result.append("\n"+gammalib::parformat("Table file"));
1087 result.append(m_filename.url());
1088 result.append("\n"+gammalib::parformat("Number of parameters"));
1089 result.append(gammalib::str(this->size()));
1090 for (int i = 0; i < size(); ++i) {
1091 result.append("\n"+m_pars[i]->print(chatter));
1092 }
1093
1094 // Append parameter value intervals
1095 for (int i = 0; i < m_table_pars.size(); ++i) {
1096 std::string name = m_table_pars[i]->par().name();
1097 int size = m_table_pars[i]->values().size();
1098 result.append("\n"+gammalib::parformat(name+" values"));
1099 result.append(gammalib::str(size));
1100 if (size > 0) {
1101 double min = m_table_pars[i]->values()[0];
1102 double max = m_table_pars[i]->values()[0];
1103 for (int k = 0; k < size; ++k) {
1104 double value = m_table_pars[i]->values()[k];
1105 if (value < min) {
1106 min = value;
1107 }
1108 if (value > max) {
1109 max = value;
1110 }
1111 }
1112 result.append(" ["+gammalib::str(min)+", "+gammalib::str(max)+"]");
1113 }
1114 if (m_table_pars[i]->method() == 0) {
1115 result.append(" (linear)");
1116 }
1117 else if (m_table_pars[i]->method() == 1) {
1118 result.append(" (logarithmic)");
1119 }
1120 }
1121
1122 // Append energy boundaries
1123 result.append("\n"+gammalib::parformat("Energies"));
1124 result.append(gammalib::str(m_ebounds.size()));
1125 result.append(" [");
1126 result.append(m_ebounds.emin().print());
1127 result.append(", ");
1128 result.append(m_ebounds.emax().print());
1129 result.append("]");
1130
1131 // Append shape of spectra array
1132 int nspectra = 0;
1133 int nebins = 0;
1134 if (m_spectra.dim() > 0) {
1135 nspectra = 1;
1136 for (int i = 0; i < m_spectra.dim()-1; ++i) {
1137 nspectra *= m_spectra.shape()[i];
1138 }
1139 nebins = m_spectra.shape()[m_spectra.dim()-1];
1140 }
1141 result.append("\n"+gammalib::parformat("Spectra array dimension"));
1142 result.append(gammalib::str(m_spectra.dim()));
1143 result.append("\n"+gammalib::parformat("Number of spectra"));
1144 result.append(gammalib::str(nspectra));
1145 result.append("\n"+gammalib::parformat("Number of spectral bins"));
1146 result.append(gammalib::str(nebins));
1147
1148 } // endif: chatter was not silent
1149
1150 // Return result
1151 return result;
1152}
1153
1154
1155/*==========================================================================
1156 = =
1157 = Private methods =
1158 = =
1159 ==========================================================================*/
1160
1161/***********************************************************************//**
1162 * @brief Initialise class members
1163 ***************************************************************************/
1165{
1166 // Initialise normalisation
1167 m_norm.clear();
1168 m_norm.name("Normalization");
1169 m_norm.scale(1.0);
1170 m_norm.value(1.0);
1171 m_norm.range(0.0,1000.0);
1172 m_norm.free();
1173 m_norm.gradient(0.0);
1174 m_norm.has_grad(true);
1175
1176 // Initialize other members
1178 m_spectra.clear();
1179 m_ebounds.clear();
1180 m_filename.clear();
1181 m_escale_par.clear();
1182 m_escale = 0.0;
1183 m_log10escale = 0.0;
1184
1185 // Initialize cache
1186 m_npars = 0;
1187 m_nebins = 0;
1188 m_last_values.clear();
1193
1194 // Initialise flux cache
1195 m_prefactor.clear();
1196 m_gamma.clear();
1197 m_epivot.clear();
1198 m_flux.clear();
1199 m_eflux.clear();
1200
1201 // Initialise MC cache
1202 m_mc_emin.clear();
1203 m_mc_emax.clear();
1204 m_mc_cum.clear();
1205 m_mc_min.clear();
1206 m_mc_max.clear();
1207 m_mc_exp.clear();
1208
1209 // Set parameter pointer(s)
1211
1212 // Return
1213 return;
1214}
1215
1216
1217/***********************************************************************//**
1218 * @brief Copy class members
1219 *
1220 * @param[in] model Table model.
1221 ***************************************************************************/
1223{
1224 // Copy members
1225 m_norm = model.m_norm;
1226 m_table_pars = model.m_table_pars;
1227 m_spectra = model.m_spectra;
1228 m_ebounds = model.m_ebounds;
1229 m_filename = model.m_filename;
1230 m_escale_par = model.m_escale_par;
1231 m_escale = model.m_escale;
1233
1234 // Copy cache
1235 m_npars = model.m_npars;
1236 m_nebins = model.m_nebins;
1238 m_lin_nodes = model.m_lin_nodes;
1239 m_log_nodes = model.m_log_nodes;
1240 m_lin_values = model.m_lin_values;
1241 m_log_values = model.m_log_values;
1242
1243 // Copy flux cache
1244 m_prefactor = model.m_prefactor;
1245 m_gamma = model.m_gamma;
1246 m_epivot = model.m_epivot;
1247 m_flux = model.m_flux;
1248 m_eflux = model.m_eflux;
1249
1250 // Copy MC cache
1251 m_mc_emin = model.m_mc_emin;
1252 m_mc_emax = model.m_mc_emax;
1253 m_mc_cum = model.m_mc_cum;
1254 m_mc_min = model.m_mc_min;
1255 m_mc_max = model.m_mc_max;
1256 m_mc_exp = model.m_mc_exp;
1257
1258 // Set energy nodes
1260
1261 // Set parameter pointer(s)
1263
1264 // Return
1265 return;
1266}
1267
1268
1269/***********************************************************************//**
1270 * @brief Delete class members
1271 ***************************************************************************/
1273{
1274 // Return
1275 return;
1276}
1277
1278
1279/***********************************************************************//**
1280 * @brief Set parameter pointers
1281 ***************************************************************************/
1283{
1284 // Debug option: write header
1285 #if defined(G_DEBUG_CALLTREE)
1286 std::cout << "GModelSpectralTable::set_par_pointers()" << std::endl;
1287 #endif
1288
1289 // Clear pointers
1290 m_pars.clear();
1291
1292 // Put normalisation parameter on stack
1293 m_pars.push_back(&m_norm);
1294
1295 // Put table model parameters on stack
1296 for (int i = 0; i < m_table_pars.size(); ++i) {
1297 m_pars.push_back(&(m_table_pars[i]->par()));
1298 }
1299
1300 // Return
1301 return;
1302}
1303
1304
1305/***********************************************************************//**
1306 * @brief Set energy nodes from energy boundaries
1307 ***************************************************************************/
1309{
1310 // Debug option: write header
1311 #if defined(G_DEBUG_CALLTREE)
1312 std::cout << "GModelSpectralTable::set_energy_nodes()";
1313 std::cout << " " << m_ebounds.size() << std::endl;
1314 #endif
1315
1316 // Determine number of energy bins
1317 int nebins = m_ebounds.size();
1318
1319 // Continue only if there are energy bins
1320 if (nebins > 0) {
1321
1322 // Initialise vectors for values
1325 m_lin_values = GNdarray(nebins, 1);
1326 m_log_values = GNdarray(nebins, 1);
1327
1328 // Compute node values
1329 for (int i = 0; i < nebins; ++i) {
1330 double energy_MeV = m_ebounds.elogmean(i).MeV();
1331 double log10_energy_MeV = std::log10(energy_MeV);
1332 m_lin_nodes.append(energy_MeV);
1333 m_log_nodes.append(log10_energy_MeV);
1334 }
1335
1336 // If energy scaling is enabled then divide energy nodes by energy
1337 // scale
1338 if (has_energy_scale()) {
1339 for (int i = 0; i < nebins; ++i) {
1340 m_lin_nodes[i] /= m_escale;
1342 }
1343 }
1344
1345 } // endif: there were energy bins
1346
1347 // Return
1348 return;
1349}
1350
1351
1352/***********************************************************************//**
1353 * @brief Scale energy
1354 *
1355 * Scales the energy axis so that all spectra are relative to the energy
1356 * scaling parameter. This scaling is useful for better interpolation of
1357 * spectra that have a feature that depends of an energy parameter.
1358 ***************************************************************************/
1360{
1361 // Debug options: write header
1362 #if defined(G_DEBUG_CALLTREE)
1363 std::cout << "GModelSpectralTable::scale_energy()";
1364 std::cout << " \"" << m_escale_par << "\"";
1365 std::cout << " " << m_log10escale << std::endl;
1366 #elif defined(G_DEBUG_ESCALE)
1367 std::cout << "GModelSpectralTable::scale_energy(): entry" << std::endl;
1368 #endif
1369
1370 // Continue only if energy scale parameter is not empty and if table
1371 // model was not yet scaled
1372 if (!(m_escale_par.empty()) && (m_escale == 0.0)) {
1373
1374 // Get energy scale values in table model
1375 const GNodeArray& escales = m_table_pars[m_escale_par]->values();
1376
1377 // Continue only if there are energy scales
1378 if (escales.size() > 0) {
1379
1380 // Get table model dimensions, number of spectra and energy
1381 // scale parameter index
1382 int npars = m_table_pars.size();
1383 int nebins = m_ebounds.size();
1384 int nspectra = this->nspectra();
1385 int ipar = par_index(m_escale_par);
1386
1387 // Get largest energy scale in table model and its base 10
1388 // logarithm
1389 m_escale = escales[escales.size()-1];
1390 m_log10escale = std::log10(m_escale);
1391
1392 // Debug option: write largest energy scale
1393 #if defined(G_DEBUG_ESCALE)
1394 std::cout << " Largest energy scale: " << escale0;
1395 std::cout << " MeV" << std::endl;
1396 #endif
1397
1398 // Adjust spectra according to energy scale of each spectrum
1399 for (int ispectrum = 0; ispectrum < nspectra; ++ispectrum) {
1400
1401 // Get parameter index vector
1402 std::vector<int> ispec = m_spectra.index(ispectrum);
1403
1404 // Get energy scale for spectrum
1405 double escale = table_par(m_escale_par).values()[ispec[ipar]];
1406 double log10escale = std::log10(escale);
1407
1408 // Debug option: write information for spectrum
1409 #if defined(G_DEBUG_ESCALE)
1410 std::cout << " " << ispectrum;
1411 std::cout << " " << ispec[ipar];
1412 std::cout << " " << escale;
1413 std::cout << " MeV" << std::endl;
1414 #endif
1415
1416 // Initialise scaled spectrum
1417 std::vector<double> spectrum(nebins,0.0);
1418
1419 // Initialise vector for spectrum access
1420 std::vector<int> index(npars+1,0);
1421 for (int k = 0; k < npars; ++k) {
1422 index[k] = ispec[k];
1423 }
1424
1425 // Setup scale spectrum
1426 for (int iebin = 0; iebin < nebins; ++iebin) {
1427
1428 // Get scaled log10 energy for energy bin
1429 double log10energy = m_log_nodes[iebin] - m_log10escale + log10escale;
1430
1431 // Interpolate function in log10 energy
1432 m_log_nodes.set_value(log10energy);
1433 double wgt_left = m_log_nodes.wgt_left();
1434 double wgt_right = m_log_nodes.wgt_right();
1435 index[npars] = m_log_nodes.inx_left();
1436 double val_left = m_spectra(index);
1437 index[npars] = m_log_nodes.inx_right();
1438 double val_right = m_spectra(index);
1439 spectrum[iebin] = wgt_left * val_left + wgt_right * val_right;
1440
1441 } // endfor: scaled spectrum
1442
1443 // Save scaled spectrum
1444 index[npars] = 0;
1445 for (int iebin = 0; iebin < nebins; ++iebin, ++index[npars]) {
1446 m_spectra(index) = spectrum[iebin];
1447 }
1448
1449 } // endfor: looped over all spectra
1450
1451 // Divide energy nodes by largest energy scale value
1452 for (int iebin = 0; iebin < nebins; ++iebin) {
1453 m_lin_nodes[iebin] /= m_escale;
1454 m_log_nodes[iebin] -= m_log10escale;
1455 }
1456
1457 } // endif: there were energy scales
1458
1459 // Force update
1460 m_last_values.clear();
1461
1462 } // endif: energy scale parameter was not empty
1463
1464 // Debug option: write trailer
1465 #if defined(G_DEBUG_ESCALE)
1466 std::cout << "GModelSpectralTable::update(): exit" << std::endl;
1467 #endif
1468
1469 // Return
1470 return;
1471}
1472
1473
1474/***********************************************************************//**
1475 * @brief Signal that energy scale was set
1476 *
1477 * @return True if energy scale was set
1478 ***************************************************************************/
1480{
1481 // Set energy scale flag
1482 bool flag = (m_escale != 0.0);
1483
1484 // Return flag
1485 return flag;
1486}
1487
1488
1489/***********************************************************************//**
1490 * @brief Create PARAMETERS FITS table
1491 *
1492 * @return Binary FITS table containing table model parameters
1493 *
1494 * The method creates a binary FITS table that is compliant with
1495 * https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_92_009/node6.html
1496 ***************************************************************************/
1498{
1499 // Determine number of parameters
1500 int nrows = m_table_pars.size();
1501
1502 // Determine maximum number of parameters values
1503 int ncols = 0;
1504 for (int i = 0; i < nrows; ++i) {
1505 int n = m_table_pars[i]->size();
1506 if (n > ncols) {
1507 ncols = n;
1508 }
1509 }
1510
1511 // Create table columns
1512 GFitsTableStringCol col_name("NAME", nrows, 12);
1513 GFitsTableLongCol col_method("METHOD", nrows);
1514 GFitsTableFloatCol col_initial("INITIAL", nrows);
1515 GFitsTableFloatCol col_delta("DELTA", nrows);
1516 GFitsTableFloatCol col_minimum("MINIMUM", nrows);
1517 GFitsTableFloatCol col_bottom("BOTTOM", nrows);
1518 GFitsTableFloatCol col_top("TOP", nrows);
1519 GFitsTableFloatCol col_maximum("MAXIMUM", nrows);
1520 GFitsTableLongCol col_numbvals("NUMBVALS", nrows);
1521 GFitsTableFloatCol col_value("VALUE", nrows, ncols);
1522
1523 // Fill columns
1524 for (int i = 0; i < nrows; ++i) {
1525
1526 // Get model parameter
1527 const GModelPar& par = m_table_pars[i]->par();
1528
1529 // Set parameter name and initial value
1530 col_name(i) = par.name();
1531 col_method(i) = m_table_pars[i]->method();
1532 col_initial(i) = (float)par.value();
1533
1534 // Handle free/fixed parameter attribute
1535 if (par.is_fixed()) {
1536 col_delta(i) = -1.0;
1537 }
1538 else {
1539 col_delta(i) = 1.0; // Dummy value
1540 }
1541
1542 // Set number of parameters
1543 col_numbvals(i) = m_table_pars[i]->size();
1544
1545 // Set parameter values
1546 double min_value = 0.0;
1547 double max_value = 0.0;
1548 if (m_table_pars[i]->size() > 0) {
1549 min_value = m_table_pars[i]->values()[0];
1550 max_value = m_table_pars[i]->values()[0];
1551 for (int k = 0; k < m_table_pars[i]->size(); ++k) {
1552 double value = m_table_pars[i]->values()[k];
1553 if (value < min_value) {
1554 min_value = value;
1555 }
1556 if (value > max_value) {
1557 max_value = value;
1558 }
1559 col_value(i,k) = value;
1560 }
1561 }
1562
1563 // Handle parameter minimum
1564 if (par.has_min()) {
1565 if (par.min() < min_value) {
1566 col_minimum(i) = (float)par.min(); // Hard minimum limit
1567 col_bottom(i) = min_value; // Soft minimum limit
1568 }
1569 else {
1570 col_minimum(i) = (float)par.min(); // Hard minimum limit
1571 col_bottom(i) = (float)par.min(); // Soft minimum limit
1572 }
1573 }
1574 else {
1575 col_minimum(i) = min_value; // Hard minimum limit
1576 col_bottom(i) = min_value; // Soft minimum limit
1577 }
1578
1579 // Handle parameter maximum
1580 if (par.has_max()) {
1581 if (par.max() > max_value) {
1582 col_top(i) = max_value; // Soft maximum limit
1583 col_maximum(i) = (float)par.max(); // Hard maximum limit
1584 }
1585 else {
1586 col_top(i) = (float)par.max(); // Soft maximum limit
1587 col_maximum(i) = (float)par.max(); // Hard maximum limit
1588 }
1589 }
1590 else {
1591 col_top(i) = max_value; // Soft maximum limit
1592 col_maximum(i) = max_value; // Soft maximum limit
1593 }
1594
1595 } // endfor: looped over parameters
1596
1597 // Create binary table
1598 GFitsBinTable table;
1599
1600 // Append columns to FITS table
1601 table.append(col_name);
1602 table.append(col_method);
1603 table.append(col_initial);
1604 table.append(col_delta);
1605 table.append(col_minimum);
1606 table.append(col_bottom);
1607 table.append(col_top);
1608 table.append(col_maximum);
1609 table.append(col_numbvals);
1610 table.append(col_value);
1611
1612 // Set table extension name
1613 table.extname("PARAMETERS");
1614
1615 // Set table keywords
1616 table.card("HDUCLASS", "OGIP", "Format conforms to OGIP standard");
1617 table.card("HDUCLAS1", "XSPEC TABLE MODEL", "Model spectra for XSPEC");
1618 table.card("HDUCLAS2", "PARAMETERS", "Extension containing parameter info");
1619 table.card("HDUVERS", "1.0.0", "Version of format");
1620 table.card("NINTPARM", nrows, "Number of interpolation parameters");
1621 table.card("NADDPARM", 0, "Number of additional parameters");
1622
1623 // Return table
1624 return table;
1625}
1626
1627
1628/***********************************************************************//**
1629 * @brief Create ENERGIES FITS table
1630 *
1631 * @return Binary FITS table containing table model energies
1632 *
1633 * The method creates a binary FITS table that is compliant with
1634 * https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_92_009/node7.html
1635 ***************************************************************************/
1637{
1638 // Create table columns
1639 GFitsTableFloatCol col_lo("ENERG_LO", m_ebounds.size());
1640 GFitsTableFloatCol col_hi("ENERG_HI", m_ebounds.size());
1641
1642 // Fill energy boundary columns
1643 for (int i = 0; i < m_ebounds.size(); ++i) {
1644 col_lo(i) = m_ebounds.emin(i).keV();
1645 col_hi(i) = m_ebounds.emax(i).keV();
1646 }
1647
1648 // Set energy units
1649 col_lo.unit("keV");
1650 col_hi.unit("keV");
1651
1652 // Create binary table
1653 GFitsBinTable table;
1654
1655 // Append columns to FITS table
1656 table.append(col_lo);
1657 table.append(col_hi);
1658
1659 // Set table extension name
1660 table.extname("ENERGIES");
1661
1662 // Set table keywords
1663 table.card("HDUCLASS", "OGIP", "Format conforms to OGIP standard");
1664 table.card("HDUCLAS1", "XSPEC TABLE MODEL", "Model spectra for XSPEC");
1665 table.card("HDUCLAS2", "ENERGIES", "Extension containing energy bin info");
1666 table.card("HDUVERS", "1.0.0", "Version of format");
1667
1668 // Return table
1669 return table;
1670}
1671
1672
1673/***********************************************************************//**
1674 * @brief Create SPECTRA FITS table
1675 *
1676 * @return Binary FITS table containing table model spectra
1677 *
1678 * The method creates a binary FITS table that is compliant with
1679 * https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_92_009/node8.html
1680 ***************************************************************************/
1682{
1683 // Compute number of rows
1684 int nrows = 0;
1685 if (m_spectra.dim() > 0) {
1686 nrows = 1;
1687 for (int i = 0; i < m_spectra.dim()-1; ++i) {
1688 nrows *= m_spectra.shape()[i];
1689 }
1690 }
1691
1692 // Compute number of parameters
1693 int npars = m_table_pars.size();
1694
1695 // Compute number of energy bins
1696 int nebins = m_ebounds.size();
1697
1698 // Create columns
1699 GFitsTableFloatCol col_pars("PARAMVAL", nrows, npars);
1700 GFitsTableFloatCol col_spec("INTPSPEC", nrows, nebins);
1701
1702 // Set units
1703 col_spec.unit("ph cm-2 s-1 MeV-1");
1704
1705 // Fill columns
1706 std::vector<int> inx(npars+1,0);
1707 for (int i = 0; i < nrows; ++i) {
1708
1709 // Set parameter values
1710 for (int k = 0; k < npars; ++k) {
1711 col_pars(i,k) = m_table_pars[k]->values()[inx[k]];
1712 }
1713
1714 // Set current index
1715 std::vector<int> index = inx;
1716
1717 // Loop over energy bins
1718 for (int k = 0; k < nebins; ++k, ++index[npars]) {
1719 col_spec(i,k) = m_spectra(index);
1720 }
1721
1722 // Increment parameter index. Last parameter index is changing fastest
1723 int ipar = npars-1;
1724 do {
1725 inx[ipar] += 1;
1726 if (inx[ipar] < m_spectra.shape()[ipar]) {
1727 break;
1728 }
1729 else {
1730 inx[ipar] = 0;
1731 ipar--;
1732 }
1733 } while (ipar >= 0);
1734
1735 } // endfor: looped over rows
1736
1737 // Create binary table
1738 GFitsBinTable table;
1739
1740 // Append columns to FITS table
1741 table.append(col_pars);
1742 table.append(col_spec);
1743
1744 // Set table extension name
1745 table.extname("SPECTRA");
1746
1747 // Set table keywords
1748 table.card("HDUCLASS", "OGIP", "Format conforms to OGIP standard");
1749 table.card("HDUCLAS1", "XSPEC TABLE MODEL", "Model spectra for XSPEC");
1750 table.card("HDUCLAS2", "MODEL SPECTRA", "Extension containing model spectra");
1751 table.card("HDUVERS", "1.0.0", "Version of format");
1752
1753 // Return table
1754 return table;
1755}
1756
1757
1758/***********************************************************************//**
1759 * @brief Load data from PARAMETERS extension
1760 *
1761 * @param[in] fits FITS file.
1762 *
1763 * @exception GException::invalid_value
1764 * Non-positive parameter value encountered for logarithmic
1765 * parameters.
1766 *
1767 * The method loads data from the PARAMETERS binary table. The format of the
1768 * table needs to be compliant with
1769 * https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_92_009/node6.html
1770 ***************************************************************************/
1772{
1773 // Get PARAMETERS extension
1774 const GFitsTable& table = *fits.table("PARAMETERS");
1775
1776 // Get number of parameters
1777 int npars = table.integer("NAXIS2");
1778
1779 // Loop over all parameters
1780 for (int i = 0; i < npars; ++i) {
1781
1782 // Set model parameter
1783 GModelPar par(table["NAME"]->string(i), table["INITIAL"]->real(i));
1784
1785 // Apply hard minimum and maximum as parameter range. Note that the
1786 // minimum and maximum apply to the value factor, hence in case of
1787 // a negative scale factor the minimum becomes the maximum and vice
1788 // versa. This is actually a bug in GammaLib, see
1789 // https://cta-redmine.irap.omp.eu/issues/3072
1790 double min = table["MINIMUM"]->real(i) / par.scale();
1791 double max = table["MAXIMUM"]->real(i) / par.scale();
1792 if (min > max) {
1793 par.factor_range(max, min);
1794 }
1795 else {
1796 par.factor_range(min, max);
1797 }
1798
1799 // Fix or free parameter according to DELTA value
1800 if (table["DELTA"]->real(i) < 0.0) {
1801 par.fix();
1802 par.has_grad(false);
1803 }
1804 else {
1805 par.free();
1806 par.has_grad(true);
1807 }
1808
1809 // Set vector of parameter values
1810 std::vector<double> values;
1811 for (int k = 0; k < table["NUMBVALS"]->integer(i); ++k) {
1812
1813 // Extract value
1814 double value = table["VALUE"]->real(i,k);
1815
1816 // If method is logarithmic then store log of the parameter
1817 // values in the spectral table parameters
1818 /*
1819 if (table["METHOD"]->integer(i) == 1) {
1820 if (value > 0.0) {
1821 value = std::log(value);
1822 }
1823 else {
1824 std::string msg = "Non-positive value "+gammalib::str(value)+
1825 " encountered for logarithmic parameter "
1826 "\""+table["NAME"]->string(i)+"\". Please "
1827 "provide only positive parameter "
1828 "values for logarithmic parameters.";
1829 throw GException::invalid_value(G_LOAD_PAR, msg);
1830 }
1831 }
1832 */
1833
1834 // Append value
1835 values.push_back(value);
1836
1837 } // endfor: looped over parameter values
1838
1839 // Set table model parameter
1840 GModelSpectralTablePar table_model_par(par, values);
1841
1842 // Set interpolation method
1843 table_model_par.method(table["METHOD"]->integer(i));
1844
1845 // Append table model parameter
1846 m_table_pars.append(table_model_par);
1847
1848 } // endfor: looped over all parameters
1849
1850 // Return
1851 return;
1852}
1853
1854
1855/***********************************************************************//**
1856 * @brief Load data from ENERGIES extension
1857 *
1858 * @param[in] fits FITS file.
1859 *
1860 * The method loads data from the ENERGIES binary table. The format of the
1861 * table needs to be compliant with
1862 * https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_92_009/node7.html
1863 *
1864 * If energy units are provided in the ENERGIES extension the method decodes
1865 * the units and interprets the energy values correspondingly.
1866 ***************************************************************************/
1868{
1869 // Get ENERGIES extension
1870 const GFitsTable& table = *fits.table("ENERGIES");
1871
1872 // Get number of energy bins
1873 int nebins = table.integer("NAXIS2");
1874
1875 // Extract energy boundary information from FITS table
1876 if (nebins > 0) {
1877
1878 // Get units
1879 std::string emin_unit = table["ENERG_LO"]->unit();
1880 std::string emax_unit = table["ENERG_HI"]->unit();
1881 if (emin_unit.empty()) {
1882 emin_unit = "keV";
1883 }
1884 if (emax_unit.empty()) {
1885 emax_unit = "keV";
1886 }
1887
1888 // Read energy boundaries and append bins
1889 for (int i = 0; i < nebins; ++i) {
1890 GEnergy emin(table["ENERG_LO"]->real(i), emin_unit);
1891 GEnergy emax(table["ENERG_HI"]->real(i), emax_unit);
1892 m_ebounds.append(emin, emax);
1893 }
1894
1895 } // endif: there were energy bins
1896
1897 // Return
1898 return;
1899}
1900
1901
1902/***********************************************************************//**
1903 * @brief Load data from SPECTRA extension
1904 *
1905 * @param[in] fits FITS file.
1906 *
1907 * The method loads data from the SPECTRA binary table. The format of the
1908 * table needs to be compliant with
1909 * https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_92_009/node8.html
1910 ***************************************************************************/
1912{
1913 // Get number of parameters and energy bins
1914 int npars = fits.table("PARAMETERS")->integer("NAXIS2");
1915 int nebins = fits.table("ENERGIES")->integer("NAXIS2");
1916
1917 // Setup dimension of spectra array
1918 std::vector<int> naxis(npars+1, 0);
1919 for (int i = 0; i < npars; ++i) {
1920 naxis[i] = (*fits.table("PARAMETERS"))["NUMBVALS"]->integer(i);
1921 }
1922 naxis[npars] = nebins;
1923
1924 // Setup spectra array
1925 m_spectra = GNdarray(naxis);
1926
1927 // Get SPECTRA extension
1928 const GFitsTable& table = *fits.table("SPECTRA");
1929
1930 // Get number of rows
1931 int nrows = table.integer("NAXIS2");
1932
1933 // Loop over rows
1934 for (int i = 0; i < nrows; ++i) {
1935
1936 // Initialise spectra array index of first energy bin
1937 std::vector<int> index(npars+1, 0);
1938
1939 // Setup spectra array index
1940 std::vector<double> parval(npars, 0.0);
1941 for (int k = 0; k < npars; ++k) {
1942
1943 // Get reference to node array
1944 const GNodeArray& nodes = m_table_pars[k]->values();
1945
1946 // Set interpolation value
1947 nodes.set_value(table["PARAMVAL"]->real(i,k));
1948
1949 // Get best index
1950 int inx = (nodes.wgt_left() > nodes.wgt_right()) ? nodes.inx_left()
1951 : nodes.inx_right();
1952
1953 // Store index
1954 index[k] = inx;
1955
1956 } // endfor: looped over parameters
1957
1958 // Debug: dump vector array
1959 #if defined(G_DEBUG_LOAD_SPEC)
1960 std::cout << i << ": (";
1961 for (int k = 0; k < index.size(); ++k) {
1962 if (k > 0) {
1963 std::cout << ", ";
1964 }
1965 std::cout << index[k];
1966 }
1967 std::cout << ")" << std::endl;
1968 #endif
1969
1970 // Loop over energy bins and store spectrum
1971 for (int k = 0; k < nebins; ++k, ++index[npars]) {
1972 m_spectra(index) = table["INTPSPEC"]->real(i,k);
1973 }
1974
1975 } // endfor: looped over rows
1976
1977 // Return
1978 return;
1979}
1980
1981
1982/***********************************************************************//**
1983 * @brief Return index for parameter name
1984 *
1985 * @param[in] name Parameter name.
1986 * @return Parameter index.
1987 *
1988 * @exception GException::invalid_argument
1989 * Parameter name not found in spectral table.
1990 ***************************************************************************/
1991int GModelSpectralTable::par_index(const std::string& name) const
1992{
1993 // Get parameter index
1994 int index = 0;
1995 for (; index < m_table_pars.size(); ++index) {
1996 if (m_table_pars[index]->par().name() == name) {
1997 break;
1998 }
1999 }
2000
2001 // Throw exception if parameter name was not found
2002 if (index >= m_table_pars.size()) {
2003 std::string msg = "Parameter name \""+name+"\" not found in spectral "
2004 "table. Please specify one of the following parameter "
2005 "names:";
2006 for (int i = 0; i < m_table_pars.size(); ++i) {
2007 if (i > 0) {
2008 msg += ",";
2009 }
2010 msg += " \""+m_table_pars[i]->par().name()+"\"";
2011 }
2013 }
2014
2015 // Return index
2016 return index;
2017}
2018
2019
2020/***********************************************************************//**
2021 * @brief Update cache for spectral table model computation
2022 *
2023 * Update interval vectors for function values and gradients. An update is
2024 * performed in case that some of the parameter values have changed. The
2025 * method sets the following cache members:
2026 *
2027 * m_npars (number of model parameters)
2028 * m_nebins (number of energy bins in spectra)
2029 * m_last_values (last model parameter values)
2030 * m_lin_values (function values)
2031 *
2032 * The array \f${\tt m\_lin\_values}\f$ holds the vector \f$F(E|p)\f$ as
2033 * function of energy \f$E\f$, computed for the current set of parameters
2034 * \f$p\f$. The computation is done using the N-dimensional linear
2035 * interpolation
2036 *
2037 * \f[
2038 * F(E|p) = \sum_{k=1}^M \left( \prod_{i=1}^N w_x(p) \right) F_p(E)
2039 * \f]
2040 *
2041 * where
2042 * - \f$M=2^N\f$ is the number of parameter combinations,
2043 * - \f$N\f$ is the number of table model parameters,
2044 * - \f$w_x(p) = \{w_0^l, w_0^r, w_1^l, w_1^r, ...\}\f$
2045 * is an array of subsequently arranged left and right weighting factors
2046 * for the linear interpolation of parameters, where \f$w_0^l\f$ and
2047 * \f$w_0^r\f$ are the left and right weighting factors for the first
2048 * parameter, \f$w_1^l\f$ and \f$w_1^r\f$ are the left and right weighting
2049 * factors for the second parameter, and so on,
2050 * - \f$x=2 k + i/2^k \mod 2\f$ is the index in the array of
2051 * weighting factors for parameter combination \f$k\f$ and parameter
2052 * \f$i\f$, and
2053 * - \f$F_p(E)\f$ are the table model spectra, computed for a grid of
2054 * possible parameter values.
2055 *
2056 * For each parameter \f$i\f$, the weighting factors \f$w_i^l(p)\f$
2057 * and \f$w_i^r(p)\f$ are computed using
2058 *
2059 * \f[
2060 * w_i^r(p) = \frac{p - p_l}{p_r - p_l}
2061 * \f]
2062 *
2063 * and \f$w_i^l(p) = 1 - w_i^r(p)\f$, where
2064 * \f$p_l\f$ is the largest parameter value that satisfies \f$p<p_l\f$ and
2065 * \f$p_r\f$ is the smallest parameter value that satisfies \f$p>p_r\f$.
2066 *
2067 * The method also computes the gradients
2068 *
2069 * \f[
2070 * \frac{\delta F(E|p)}{\delta p} = \sum_{k=1}^M \left(
2071 * \frac{\delta w_{x_p}(p)}{\delta p} \prod_{i=1 \land i \neq i_p}^N
2072 * w_x(p) \right) F_p(E)
2073 * \f]
2074 *
2075 * where
2076 * \f$x_p = 2 k + i_p/2^k \mod 2\f$.
2077 ***************************************************************************/
2079{
2080 // Get number of parameters and number of energy bins
2082 m_nebins = ebounds().size();
2083
2084 // Initialise update flag
2085 bool need_update = false;
2086
2087 // If dimension of last cached parameter values differ from dimension
2088 // of spectral table then reallocate cache and request update
2089 if (m_last_values.size() != m_npars) {
2090 m_last_values = std::vector<double>(m_npars, 0.0);
2091 need_update = true;
2092 }
2093
2094 // ... otherwise check if some parameter values have changed
2095 else {
2096 for (int i = 0; i < m_npars; ++i) {
2097 if (m_table_pars[i]->par().value() != m_last_values[i]) {
2098 need_update = true;
2099 break;
2100 }
2101 }
2102 }
2103
2104 // Continue only if update is required
2105 if (need_update) {
2106
2107 // Debug option: write header
2108 #if defined(G_DEBUG_UPDATE)
2109 std::cout << "GModelSpectralTable::update() required" << std::endl;
2110 #endif
2111
2112 // Initialise vectors for weights, weight gradients and indices
2113 std::vector<double> weights(2*m_npars, 0.0);
2114 std::vector<double> weight_gradients(2*m_npars, 0.0);
2115 std::vector<int> indices(2*m_npars, 0);
2116
2117 // Loop over all parameters and extract left and right weights,
2118 // weight gradients and indices and put them into single arrays
2119 for (int i = 0; i < m_npars; ++i) {
2120
2121 // Get pointers to node array and parameter
2122 const GNodeArray* nodes = &(m_table_pars[i]->values());
2123 const GModelPar* par = &(m_table_pars[i]->par());
2124
2125 // Get parameter value
2126 double value = par->value();
2127
2128 // Cache parameter value
2129 m_last_values[i] = value;
2130
2131 // Use log of value for logarithmic parameters
2132 /*
2133 if (m_table_pars[i]->method() == 1) {
2134 if (value > 0.0) {
2135 value = std::log(value);
2136 }
2137 else {
2138 std::string msg = "Non-positive value "+gammalib::str(value)+
2139 " encountered for logarithmic parameter "
2140 "\""+m_table_pars[i]->par().name()+"\".";
2141 throw GException::invalid_value(G_UPDATE, msg);
2142 }
2143 }
2144 */
2145
2146 // Set values for node array
2147 nodes->set_value(value);
2148
2149 // Compute left and right indices
2150 int il = 2*i;
2151 int ir = il + 1;
2152
2153 // Push back weigths, weight gradients and indices
2154 weights[il] = nodes->wgt_left();
2155 weights[ir] = nodes->wgt_right();
2156 weight_gradients[il] = nodes->wgt_grad_left();
2157 weight_gradients[ir] = nodes->wgt_grad_right();
2158 indices[il] = nodes->inx_left();
2159 indices[ir] = nodes->inx_right();
2160
2161 // Debug option: print weights and indices
2162 #if defined(G_DEBUG_UPDATE)
2163 std::cout << " wgt_l=" << weights[il];
2164 std::cout << " wgt_r=" << weights[ir];
2165 std::cout << " wgt_grad_l=" << weight_gradients[il];
2166 std::cout << " wgt_grad_r=" << weight_gradients[ir];
2167 std::cout << " inx_l=" << indices[il];
2168 std::cout << " inx_r=" << indices[ir] << std::endl;
2169 #endif
2170
2171 } // endfor: looped over all parameters
2172
2173 // Compute number of combinations
2174 int combinations = 1 << m_npars;
2175
2176 // Initialise 2d arrays for values and gradients
2179
2180 // Debug option: initial sum of weights
2181 #if defined(G_DEBUG_UPDATE)
2182 double weight_sum = 0.0;
2183 #endif
2184
2185 // Loop over combinations
2186 for (int i = 0; i < combinations; ++i) {
2187
2188 // Debug option: start printing combination
2189 #if defined(G_DEBUG_UPDATE)
2190 std::cout << " " << i << ": ";
2191 #endif
2192
2193 // Initialise weight and gradient weights
2194 double weight = 1.0;
2195 std::vector<double> grad_weight(m_npars, 1.0);
2196
2197 // Initialise index vector (including the energy dimension)
2198 std::vector<int> index_shape(m_npars+1,0);
2199
2200 // Loop over dimensions
2201 for (int k = 0, div = 1; k < m_npars; ++k, div *= 2) {
2202
2203 // Compute index for each dimension
2204 int index = i/div % 2 + k * 2;
2205
2206 // Update weight
2207 weight *= weights[index];
2208
2209 // Add index
2210 index_shape[k] = indices[index];
2211
2212 // Update gradient weights
2213 for (int j = 0; j < m_npars; ++j) {
2214 if (k == j) {
2215 grad_weight[j] *= weight_gradients[index];
2216 }
2217 else {
2218 grad_weight[j] *= weights[index];
2219 }
2220 } // endfor: update gradient weights
2221
2222 // Debug option: print index and weight for current dimension
2223 #if defined(G_DEBUG_UPDATE)
2224 std::cout << index;
2225 std::cout << " (" << weights[index];
2226 std::cout << " @ " << indices[index] << ") ";
2227 #endif
2228
2229 } // endfor: looped over dimensions
2230
2231 // Debug option: print total weight and weight gradient
2232 #if defined(G_DEBUG_UPDATE)
2233 std::cout << ": wgt=" << weight;
2234 std::cout << " [";
2235 for (int k = 0; k < m_npars; ++k) {
2236 if (k > 0) {
2237 std::cout << ",";
2238 }
2239 std::cout << grad_weight[k];
2240 }
2241 std::cout << "]" << std::endl;
2242 weight_sum += weight;
2243 #endif
2244
2245 // Compute interpolated value and gradient
2246 for (int iebin = 0; iebin < m_nebins; ++iebin) {
2247
2248 // Set energy index
2249 index_shape[m_npars] = iebin;
2250
2251 // Get spectral value
2252 double value = m_spectra(index_shape);
2253
2254 // Compute contribution and store in value slot
2255 m_lin_values(iebin,0) += weight * value;
2256
2257 // Compute gradients and store in gradient slots
2258 for (int j = 0; j < m_npars; ++j) {
2259 m_lin_values(iebin,j+1) += grad_weight[j] * value;
2260 }
2261
2262 } // endfor: looped over all energy bins
2263
2264 } // endfor: looped over combinations
2265
2266 // Compute log10 values of function values
2267 /*
2268 for (int iebin = 0; iebin < m_nebins; ++iebin) {
2269 if (m_lin_values(iebin,0) > 0.0) {
2270 m_log_values(iebin,0) = std::log10(m_lin_values(iebin,0));
2271 }
2272 else {
2273 m_log_values(iebin,0) = -1000.0; // Set to a tiny value
2274 }
2275 }
2276 */
2277
2278 // Debug option: print sum of weights
2279 #if defined(G_DEBUG_UPDATE)
2280 std::cout << " sum(wgt)=" << weight_sum << std::endl;
2281 #endif
2282
2283 } // endif: updated requested
2284
2285 // Return
2286 return;
2287}
2288
2289
2290/***********************************************************************//**
2291 * @brief Update flux cache
2292 ***************************************************************************/
2294{
2295 // Clear any existing values
2296 m_prefactor.clear();
2297 m_gamma.clear();
2298 m_epivot.clear();
2299 m_flux.clear();
2300 m_eflux.clear();
2301
2302 // Loop over all nodes-1
2303 for (int i = 0; i < m_lin_nodes.size()-1; ++i) {
2304
2305 // Get energies and function values
2306 double emin = m_lin_nodes[i];
2307 double emax = m_lin_nodes[i+1];
2308 double fmin = m_lin_values(i,0);
2309 double fmax = m_lin_values(i+1,0);
2310
2311 // Compute pivot energy (MeV). We use here the geometric mean of
2312 // the node boundaries.
2313 double epivot = std::sqrt(emin*emax);
2314
2315 // Compute spectral index, power law normalisation, photon and
2316 // energy flux between nodes
2317 double gamma = 0.0;
2318 double prefactor = 0.0;
2319 double flux = 0.0;
2320 double eflux = 0.0;
2321 if ((fmin > 0.0) && (fmax > 0.0)) {
2322 gamma = std::log(fmin/fmax) / std::log(emin/emax);
2323 prefactor = fmin / std::pow(emin/epivot, gamma);
2324 flux = prefactor *
2325 gammalib::plaw_photon_flux(emin, emax, epivot, gamma);
2326 eflux = prefactor *
2327 gammalib::plaw_energy_flux(emin, emax, epivot, gamma);
2328 }
2329
2330 // Convert energy flux from MeV/cm2/s to erg/cm2/s
2332
2333 // Push back values on pre-computation cache
2334 m_prefactor.push_back(prefactor);
2335 m_gamma.push_back(gamma);
2336 m_epivot.push_back(epivot);
2337 m_flux.push_back(flux);
2338 m_eflux.push_back(eflux);
2339
2340 } // endfor: looped over all nodes
2341
2342 // Return
2343 return;
2344}
2345
2346
2347/***********************************************************************//**
2348 * @brief Update MC cache
2349 *
2350 * @param[in] emin Minimum energy.
2351 * @param[in] emax Maximum energy.
2352 *
2353 * This method sets up an array of indices and the cumulative distribution
2354 * function needed for MC simulations.
2355 ***************************************************************************/
2357 const GEnergy& emax) const
2358{
2359 // Check if we need to update the cache
2360 if (emin != m_mc_emin || emax != m_mc_emax) {
2361
2362 // Store new energy interval
2363 m_mc_emin = emin;
2364 m_mc_emax = emax;
2365
2366 // Initialise cache
2367 m_mc_cum.clear();
2368 m_mc_min.clear();
2369 m_mc_max.clear();
2370 m_mc_exp.clear();
2371
2372 // Get energy range in MeV
2373 double e_min = emin.MeV();
2374 double e_max = emax.MeV();
2375
2376 // Continue only if e_max > e_min
2377 if (e_max > e_min) {
2378
2379 // Allocate flux
2380 double flux;
2381
2382 // Determine left node index for minimum energy
2383 m_lin_nodes.set_value(e_min);
2384 int inx_emin = m_lin_nodes.inx_left();
2385
2386 // Determine left node index for maximum energy
2387 m_lin_nodes.set_value(e_max);
2388 int inx_emax = m_lin_nodes.inx_left();
2389
2390 // If both energies are within the same node then just
2391 // add this one node on the stack
2392 if (inx_emin == inx_emax) {
2393 flux = m_prefactor[inx_emin] *
2395 e_max,
2396 m_epivot[inx_emin],
2397 m_gamma[inx_emin]);
2398 m_mc_cum.push_back(flux);
2399 m_mc_min.push_back(e_min);
2400 m_mc_max.push_back(e_max);
2401 m_mc_exp.push_back(m_gamma[inx_emin]);
2402 }
2403
2404 // ... otherwise integrate over the nodes where emin and emax
2405 // resides and all the remaining nodes
2406 else {
2407
2408 // If we are requested to extrapolate beyond first node,
2409 // the use the first nodes lower energy and upper energy
2410 // boundary for the initial integration.
2411 int i_start = (e_min < m_lin_nodes[0]) ? inx_emin : inx_emin+1;
2412
2413 // Add emin to the node boundary
2414 flux = m_prefactor[inx_emin] *
2416 m_lin_nodes[i_start],
2417 m_epivot[inx_emin],
2418 m_gamma[inx_emin]);
2419 m_mc_cum.push_back(flux);
2420 m_mc_min.push_back(e_min);
2421 m_mc_max.push_back(m_lin_nodes[i_start]);
2422 m_mc_exp.push_back(m_gamma[inx_emin]);
2423
2424 // Add all nodes between
2425 for (int i = i_start; i < inx_emax; ++i) {
2426 flux = m_flux[i];
2427 m_mc_cum.push_back(flux);
2428 m_mc_min.push_back(m_lin_nodes[i]);
2429 m_mc_max.push_back(m_lin_nodes[i+1]);
2430 m_mc_exp.push_back(m_gamma[i]);
2431 }
2432
2433 // Add node boundary to emax
2434 flux = m_prefactor[inx_emax] *
2436 e_max,
2437 m_epivot[inx_emax],
2438 m_gamma[inx_emax]);
2439 m_mc_cum.push_back(flux);
2440 m_mc_min.push_back(m_lin_nodes[inx_emax]);
2441 m_mc_max.push_back(e_max);
2442 m_mc_exp.push_back(m_gamma[inx_emax]);
2443
2444 } // endelse: emin and emax not between same nodes
2445
2446 // Build cumulative distribution
2447 for (int i = 1; i < m_mc_cum.size(); ++i) {
2448 m_mc_cum[i] += m_mc_cum[i-1];
2449 }
2450 double norm = m_mc_cum[m_mc_cum.size()-1];
2451 if (norm > 0.0) {
2452 for (int i = 0; i < m_mc_cum.size(); ++i) {
2453 m_mc_cum[i] /= norm;
2454 }
2455 }
2456
2457 // Set MC values
2458 for (int i = 0; i < m_mc_cum.size(); ++i) {
2459
2460 // Compute exponent
2461 double exponent = m_mc_exp[i] + 1.0;
2462
2463 // Exponent dependend computation
2464 if (std::abs(exponent) > 1.0e-11) {
2465
2466 // If the exponent is too small then use lower energy
2467 // boundary
2468 if (exponent < -50.0) {
2469 m_mc_exp[i] = 0.0;
2470 m_mc_min[i] = std::log(m_mc_min[i]);
2471 m_mc_max[i] = m_mc_min[i];
2472 }
2473
2474 // ... otherwise if exponent is too large then use
2475 // upper energy boundary
2476 else if (exponent > +50.0) {
2477 m_mc_exp[i] = 0.0;
2478 m_mc_min[i] = std::log(m_mc_max[i]);
2479 m_mc_max[i] = m_mc_min[i];
2480 }
2481
2482 // ... otherwise use transformation formula
2483 else {
2484 m_mc_exp[i] = exponent;
2485 m_mc_min[i] = std::pow(m_mc_min[i], exponent);
2486 m_mc_max[i] = std::pow(m_mc_max[i], exponent);
2487 }
2488 }
2489 else {
2490 m_mc_exp[i] = 0.0;
2491 m_mc_min[i] = std::log(m_mc_min[i]);
2492 m_mc_max[i] = std::log(m_mc_max[i]);
2493 }
2494
2495 } // endfor: set MC values
2496
2497 } // endif: e_max > e_min
2498
2499 } // endif: Update was required
2500
2501 // Return
2502 return;
2503}
#define G_WRITE
#define G_READ
Energy value class definition.
Exception handler interface definition.
FITS binary table class definition.
FITS table float column class interface definition.
FITS table long integer column class interface definition.
FITS table string column class interface definition.
FITS file class interface definition.
Mathematical function definitions.
Spectral model registry class definition.
Spectral table model parameter class definition.
Spectral table model parameter container class definition.
const GModelSpectralTable g_spectral_table_seed
#define G_PAR_INDEX
#define G_TABLE_PAR
#define G_CONST
Spectral table model class definition.
Random number generator class definition.
Gammalib tools definition.
GChatter
Definition GTypemaps.hpp:33
@ SILENT
Definition GTypemaps.hpp:34
double norm(const GVector &vector)
Computes vector norm.
Definition GVector.cpp:932
double min(const GVector &vector)
Computes minimum vector element.
Definition GVector.cpp:954
double max(const GVector &vector)
Computes maximum vector element.
Definition GVector.cpp:983
Energy boundaries container class.
Definition GEbounds.hpp:60
const GEnergy & emax(void) const
Return maximum energy of all intervals.
Definition GEbounds.hpp:199
void append(const GEnergy &emin, const GEnergy &emax)
Append energy interval.
Definition GEbounds.cpp:301
int size(void) const
Return number of energy boundaries.
Definition GEbounds.hpp:163
void clear(void)
Clear energy boundaries.
Definition GEbounds.cpp:268
GEnergy elogmean(const int &index) const
Returns logarithmic mean energy for a given energy interval.
const GEnergy & emin(void) const
Return minimum energy of all intervals.
Definition GEbounds.hpp:187
Class that handles energies in a unit independent way.
Definition GEnergy.hpp:48
double keV(void) const
Return energy in keV.
Definition GEnergy.cpp:327
double MeV(void) const
Return energy in MeV.
Definition GEnergy.cpp:342
double log10MeV(void) const
Return log10 of energy in MeV.
Definition GEnergy.cpp:458
std::string print(const GChatter &chatter=NORMAL) const
Print energy.
Definition GEnergy.cpp:822
void clear(void)
Clear instance.
Definition GEnergy.cpp:267
Filename class.
Definition GFilename.hpp:62
std::string url(void) const
Return Uniform Resource Locator (URL)
bool is_empty(void) const
Signal if filename is empty.
void clear(void)
Clear file name.
FITS binary table class.
Abstract FITS extension base class.
Definition GFitsHDU.hpp:51
const std::string & extname(void) const
Return extension name.
Definition GFitsHDU.hpp:162
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
int integer(const std::string &keyname) const
Return card value as integer.
Definition GFitsHDU.hpp:436
void unit(const std::string &unit)
Set column unit.
FITS table float column.
FITS table long integer column.
FITS table string column.
Abstract interface for FITS table.
GFitsTableCol * append(const GFitsTableCol &column)
Append column to the table.
FITS file class.
Definition GFits.hpp:63
void saveto(const GFilename &filename, const bool &clobber=false)
Saves to specified FITS file.
Definition GFits.cpp:1293
GFitsHDU * append(const GFitsHDU &hdu)
Append HDU to FITS file.
Definition GFits.cpp:678
void close(void)
Close FITS file.
Definition GFits.cpp:1342
GFitsTable * table(const int &extno)
Get pointer to table HDU.
Definition GFits.cpp:482
Model parameter class.
Definition GModelPar.hpp:87
void write(GXmlElement &xml) const
Set or update parameter attributes in XML element.
void read(const GXmlElement &xml)
Extract parameter attributes from XML element.
Interface definition for the spectral model registry class.
Spectral table model parameter class.
const GNodeArray & values(void) const
Return reference to table model parameter values as node array.
const int & method(void) const
Return reference to table model parameter interpolation method.
Spectral table model parameter container class.
GModelSpectralTablePar * append(const GModelSpectralTablePar &par)
Append table model parameter to container.
void clear(void)
Clear table model parameters.
int size(void) const
Return number of table model parameters in container.
Spectral table model class.
void scale_energy(void)
Scale energy.
GModelSpectralTable(void)
Void constructor.
virtual void write(GXmlElement &xml) const
Write model into XML element.
std::string m_escale_par
Energy scaling parameter.
std::vector< double > m_mc_cum
Cumulative distribution.
virtual double eval(const GEnergy &srcEng, const GTime &srcTime=GTime(), const bool &gradients=false) const
Evaluate spectral table model.
virtual void read(const GXmlElement &xml)
Read model from XML element.
std::vector< double > m_mc_min
Lower boundary for MC.
void save(const GFilename &filename, const bool &clobber=false) const
Save table into file.
GModelPar m_norm
Normalization factor.
void load_par(const GFits &fits)
Load data from PARAMETERS extension.
void load_spec(const GFits &fits)
Load data from SPECTRA extension.
GModelSpectralTablePar & table_par(const int &index)
Return reference to table parameter.
virtual std::string print(const GChatter &chatter=NORMAL) const
Print table model information.
GEnergy m_mc_emax
Maximum energy.
GEbounds m_ebounds
Energy boundaries.
GNodeArray m_log_nodes
log10(Energy) nodes of function
virtual GEnergy mc(const GEnergy &emin, const GEnergy &emax, const GTime &time, GRan &ran) const
Returns MC energy between [emin, emax].
std::vector< double > m_epivot
Power-law pivot energies.
virtual std::string type(void) const
Return model type.
void init_members(void)
Initialise class members.
double m_escale
Energy scale.
GFilename m_filename
Filename of table.
void set_par_pointers(void)
Set parameter pointers.
int nspectra(void) const
Return number of spectra in table model.
int par_index(const std::string &name) const
Return index for parameter name.
void set_energy_nodes(void)
Set energy nodes from energy boundaries.
void free_members(void)
Delete class members.
std::vector< double > m_mc_exp
Exponent for MC.
virtual double eflux(const GEnergy &emin, const GEnergy &emax) const
Returns model energy flux between [emin, emax] (units: erg/cm2/s)
int m_nebins
Number of energy bins.
bool has_energy_scale(void) const
Signal that energy scale was set.
void energy_scale(const std::string &name)
Set energy scale.
GNdarray m_log_values
log10(Function) values and grad's
GEnergy m_mc_emin
Minimum energy.
virtual ~GModelSpectralTable(void)
Destructor.
double norm(void) const
Return normalization factor.
GNodeArray m_lin_nodes
Energy nodes of function.
std::vector< double > m_flux
Photon fluxes.
void update_mc(const GEnergy &emin, const GEnergy &emax) const
Update MC cache.
void update(void) const
Update cache for spectral table model computation.
GNdarray m_lin_values
Function values and grad's.
GFitsBinTable create_eng_table(void) const
Create ENERGIES FITS table.
const GEbounds & ebounds(void) const
Return reference to energy boundaries.
virtual void clear(void)
Clear table model.
virtual GModelSpectralTable * clone(void) const
Clone table model.
void load(const GFilename &filename)
Load table from file.
double m_log10escale
log10 of energy scale
std::vector< double > m_mc_max
Upper boundary for MC.
std::vector< double > m_last_values
Last parameter values.
void copy_members(const GModelSpectralTable &model)
Copy class members.
GFitsBinTable create_par_table(void) const
Create PARAMETERS FITS table.
void update_flux(void) const
Update flux cache.
std::vector< double > m_prefactor
Power-law normalisations.
int m_npars
Number of parameters.
void load_eng(const GFits &fits)
Load data from ENERGIES extension.
virtual GModelSpectralTable & operator=(const GModelSpectralTable &model)
Assignment operator.
std::vector< double > m_gamma
Power-law indices.
GModelSpectralTablePars m_table_pars
Table model parameters.
virtual double flux(const GEnergy &emin, const GEnergy &emax) const
Returns model photon flux between [emin, emax] (units: ph/cm2/s)
const GFilename & filename(void) const
Return file name.
GFitsBinTable create_spec_table(void) const
Create SPECTRA FITS table.
std::vector< double > m_eflux
Energy fluxes.
Abstract spectral model base class.
void free_members(void)
Delete class members.
virtual GModelSpectral & operator=(const GModelSpectral &model)
Assignment operator.
std::vector< GModelPar * > m_pars
Parameter pointers.
int size(void) const
Return number of parameters.
void init_members(void)
Initialise class members.
N-dimensional array class.
Definition GNdarray.hpp:44
void clear(void)
Clear array.
Definition GNdarray.cpp:527
int dim(void) const
Return dimension of array.
Definition GNdarray.hpp:280
const std::vector< int > & shape(void) const
Return shape of array.
Definition GNdarray.hpp:308
int index(const std::vector< int > &i) const
Compute array element index.
Definition GNdarray.cpp:754
Node array class.
void set_value(const double &value) const
Set indices and weighting factors for interpolation.
const double & wgt_grad_left(void) const
Returns left node weight gradient.
const int & inx_right(void) const
Returns right node index.
const int & inx_left(void) const
Returns left node index.
const double & wgt_grad_right(void) const
Returns right node weight gradient.
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.
int size(void) const
Return number of nodes in node array.
void append(const double &node)
Append one node to array.
const double & factor_value(void) const
Return parameter factor value.
bool is_free(void) const
Signal if parameter is free.
void free(void)
Free a parameter.
const double & scale(void) const
Return parameter scale.
bool has_grad(void) const
Signal if parameter gradient is computed analytically.
void range(const double &min, const double &max)
Set minimum and maximum parameter boundaries.
bool has_min(void) const
Signal if parameter has minimum boundary.
double max(void) const
Return parameter maximum boundary.
bool is_fixed(void) const
Signal if parameter is fixed.
double min(void) const
Return parameter minimum boundary.
void remove_range(void)
Removes minimum and maximum boundary.
const double & factor_gradient(void) const
Return parameter factor gradient.
void fix(void)
Fix a parameter.
bool has_max(void) const
Signal if parameter has maximum boundary.
void factor_range(const double &min, const double &max)
Set minimum and maximum parameter boundary factors.
double gradient(void) const
Return parameter gradient.
void clear(void)
Clear parameter.
double value(void) const
Return parameter value.
const std::string & name(void) const
Return parameter name.
Random number generator class.
Definition GRan.hpp:44
double uniform(void)
Returns random double precision floating value in range 0 to 1.
Definition GRan.cpp:242
Time class.
Definition GTime.hpp:55
XML element node class.
const GXmlAttribute * attribute(const int &index) const
Return attribute.
bool has_attribute(const std::string &name) const
Check if element has a given attribute.
void check_energy_interval(const std::string &origin, const GEnergy &emin, const GEnergy &emax)
Checks energy interval.
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
double plaw_photon_flux(const double &emin, const double &emax, const double &epivot, const double &gamma)
Compute photon flux between two energies for a power law.
Definition GTools.cpp:1219
bool is_notanumber(const double &x)
Signal if argument is not a number.
Definition GTools.hpp:203
const double ln10
Definition GMath.hpp:46
std::string str(const unsigned short int &value)
Convert unsigned short integer value into string.
Definition GTools.cpp:508
const GXmlElement * xml_get_par(const std::string &origin, const GXmlElement &xml, const std::string &name)
Return pointer to parameter with given name in XML element.
Definition GTools.cpp:1708
GXmlElement * xml_need_par(const std::string &origin, GXmlElement &xml, const std::string &name)
Return pointer to parameter with given name in XML element.
Definition GTools.cpp:1656
GFilename xml_file_reduce(const GXmlElement &xml, const std::string &filename)
Reduce file name provided for writing as XML attribute.
Definition GTools.cpp:1965
double plaw_energy_flux(const double &emin, const double &emax, const double &epivot, const double &gamma)
Compute energy flux between two energies for a power law.
Definition GTools.cpp:1267
bool xml_has_par(const GXmlElement &xml, const std::string &name)
Checks if parameter with given name in XML element exists.
Definition GTools.cpp:1615
const double MeV2erg
Definition GTools.hpp:45
GFilename xml_file_expand(const GXmlElement &xml, const std::string &filename)
Expand file name provided as XML attribute for loading.
Definition GTools.cpp:1908
void xml_check_type(const std::string &origin, GXmlElement &xml, const std::string &type)
Checks the model type.
Definition GTools.cpp:1838