GammaLib  2.1.0.dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
GModelSpectralTable.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * GModelSpectralTable.cpp - Spectral table model class *
3  * ----------------------------------------------------------------------- *
4  * copyright (C) 2019-2022 by Juergen Knoedlseder *
5  * ----------------------------------------------------------------------- *
6  * *
7  * This program is free software: you can redistribute it and/or modify *
8  * it under the terms of the GNU General Public License as published by *
9  * the Free Software Foundation, either version 3 of the License, or *
10  * (at your option) any later version. *
11  * *
12  * This program is distributed in the hope that it will be useful, *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15  * GNU General Public License for more details. *
16  * *
17  * You should have received a copy of the GNU General Public License *
18  * along with this program. If not, see <http://www.gnu.org/licenses/>. *
19  * *
20  ***************************************************************************/
21 /**
22  * @file 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"
39 #include "GFitsTableStringCol.hpp"
40 #include "GFitsTableLongCol.hpp"
41 #include "GFitsTableFloatCol.hpp"
42 #include "GModelSpectralTable.hpp"
46 
47 /* __ Constants __________________________________________________________ */
48 
49 /* __ Globals ____________________________________________________________ */
51 const 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
91  init_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
120  m_norm.scale(norm);
121  m_norm.value(norm);
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
183  m_ebounds = ebounds;
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  ***************************************************************************/
377 double GModelSpectralTable::eval(const GEnergy& srcEng,
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  ***************************************************************************/
1008 const 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  ***************************************************************************/
1029 void 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  ***************************************************************************/
1074 std::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
1177  m_table_pars.clear();
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();
1189  m_lin_nodes.clear();
1190  m_log_nodes.clear();
1191  m_lin_values.clear();
1192  m_log_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)
1210  set_par_pointers();
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;
1232  m_log10escale = model.m_log10escale;
1233 
1234  // Copy cache
1235  m_npars = model.m_npars;
1236  m_nebins = model.m_nebins;
1237  m_last_values = model.m_last_values;
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
1259  set_energy_nodes();
1260 
1261  // Set parameter pointer(s)
1262  set_par_pointers();
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
1323  m_lin_nodes = GNodeArray();
1324  m_log_nodes = GNodeArray();
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;
1341  m_log_nodes[i] -= m_log10escale;
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];
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  ***************************************************************************/
1991 int GModelSpectralTable::par_index(const std::string& name) const
1992 {
1993  // Get parameter index
1994  int index = 0;
1995  for (; index < 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 >= 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 < 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
2177  m_lin_values = GNdarray(m_nebins, m_npars+1);
2178  m_log_values = GNdarray(m_nebins, m_npars+1);
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
2331  eflux *= gammalib::MeV2erg;
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 }
void unit(const std::string &unit)
Set column unit.
int size(void) const
Return number of nodes in node array.
Definition: GNodeArray.hpp:192
#define G_PAR_INDEX
GModelSpectralTablePar * append(const GModelSpectralTablePar &par)
Append table model parameter to container.
GFitsTable * table(const int &extno)
Get pointer to table HDU.
Definition: GFits.cpp:482
GModelSpectralTable(void)
Void constructor.
std::vector< double > m_mc_max
Upper boundary for MC.
const double & factor_gradient(void) const
Return parameter factor gradient.
double norm(const GVector &vector)
Computes vector norm.
Definition: GVector.cpp:864
Node array class.
Definition: GNodeArray.hpp:60
GFitsBinTable create_spec_table(void) const
Create SPECTRA FITS table.
void energy_scale(const std::string &name)
Set energy scale.
Energy value class definition.
const std::string & name(void) const
Return parameter name.
Random number generator class definition.
std::vector< double > m_mc_exp
Exponent for MC.
void clear(void)
Clear array.
Definition: GNdarray.cpp:527
Abstract spectral model base class.
std::string m_escale_par
Energy scaling parameter.
GModelPar m_norm
Normalization factor.
double gradient(void) const
Return parameter gradient.
GModelSpectralTablePars m_table_pars
Table model parameters.
GEnergy m_mc_emax
Maximum energy.
int size(void) const
Return number of parameters.
double m_escale
Energy scale.
const double & wgt_grad_left(void) const
Returns left node weight gradient.
Definition: GNodeArray.hpp:294
GVector abs(const GVector &vector)
Computes absolute of vector elements.
Definition: GVector.cpp:1253
virtual GModelSpectral & operator=(const GModelSpectral &model)
Assignment operator.
void write(GXmlElement &xml) const
Set or update parameter attributes in XML element.
Definition: GModelPar.cpp:351
virtual void read(const GXmlElement &xml)
Read model from XML element.
Abstract FITS extension base class.
Definition: GFitsHDU.hpp:51
int size(void) const
Return number of energy boundaries.
Definition: GEbounds.hpp:163
GNdarray m_lin_values
Function values and grad&#39;s.
void append(const GEnergy &emin, const GEnergy &emax)
Append energy interval.
Definition: GEbounds.cpp:301
virtual GEnergy mc(const GEnergy &emin, const GEnergy &emax, const GTime &time, GRan &ran) const
Returns MC energy between [emin, emax].
std::vector< GModelPar * > m_pars
Parameter pointers.
const GEbounds & ebounds(void) const
Return reference to energy boundaries.
void factor_range(const double &min, const double &max)
Set minimum and maximum parameter boundary factors.
bool is_empty(void) const
Signal if filename is empty.
Definition: GFilename.hpp:160
XML element node class.
Definition: GXmlElement.hpp:48
const double & wgt_left(void) const
Returns left node weight.
Definition: GNodeArray.hpp:264
virtual double flux(const GEnergy &emin, const GEnergy &emax) const
Returns model photon flux between emin, emax
Spectral model registry class definition.
double max(void) const
Return parameter maximum boundary.
void clear(void)
Clear node array.
Definition: GNodeArray.cpp:235
virtual double eflux(const GEnergy &emin, const GEnergy &emax) const
Returns model energy flux between emin, emax
Random number generator class.
Definition: GRan.hpp:44
GFitsTableCol * append(const GFitsTableCol &column)
Append column to the table.
Definition: GFitsTable.hpp:147
void free_members(void)
Delete class members.
virtual ~GModelSpectralTable(void)
Destructor.
double norm(void) const
Return normalization factor.
double MeV(void) const
Return energy in MeV.
Definition: GEnergy.cpp:321
const int & method(void) const
Return reference to table model parameter interpolation method.
Time class.
Definition: GTime.hpp:55
void set_energy_nodes(void)
Set energy nodes from energy boundaries.
Gammalib tools definition.
void update_mc(const GEnergy &emin, const GEnergy &emax) const
Update MC cache.
const std::vector< int > & shape(void) const
Return shape of array.
Definition: GNdarray.hpp:308
FITS table float column class interface definition.
GNodeArray m_log_nodes
log10(Energy) nodes of function
FITS file class.
Definition: GFits.hpp:63
std::vector< double > m_epivot
Power-law pivot energies.
FITS file class interface definition.
void set_value(const double &value) const
Set indices and weighting factors for interpolation.
Definition: GNodeArray.cpp:587
std::vector< double > m_flux
Photon fluxes.
double min(const GVector &vector)
Computes minimum vector element.
Definition: GVector.cpp:886
GFilename xml_file_reduce(const GXmlElement &xml, const std::string &filename)
Reduce file name provided for writing as XML attribute.
Definition: GTools.cpp:1946
void init_members(void)
Initialise class members.
double min(void) const
Return parameter minimum boundary.
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:1248
bool is_free(void) const
Signal if parameter is free.
double m_log10escale
log10 of energy scale
const double ln10
Definition: GMath.hpp:46
Spectral table model parameter container class.
double log10MeV(void) const
Return log10 of energy in MeV.
Definition: GEnergy.cpp:423
std::vector< double > m_prefactor
Power-law normalisations.
void update_flux(void) const
Update flux cache.
void set_par_pointers(void)
Set parameter pointers.
bool is_notanumber(const double &x)
Signal if argument is not a number.
Definition: GTools.hpp:201
std::vector< double > m_mc_cum
Cumulative distribution.
FITS table string column.
void load(const GFilename &filename)
Load table from file.
const double & scale(void) const
Return parameter scale.
bool is_infinite(const double &x)
Signal if argument is infinite.
Definition: GTools.hpp:184
void load_eng(const GFits &fits)
Load data from ENERGIES extension.
void saveto(const GFilename &filename, const bool &clobber=false)
Saves to specified FITS file.
Definition: GFits.cpp:1293
Model parameter class.
Definition: GModelPar.hpp:87
double real(const std::string &keyname) const
Return card value as double precision.
Definition: GFitsHDU.hpp:423
GFitsBinTable create_eng_table(void) const
Create ENERGIES FITS table.
void copy_members(const GModelSpectralTable &model)
Copy class members.
GVector sqrt(const GVector &vector)
Computes square root of vector elements.
Definition: GVector.cpp:1358
GNodeArray m_lin_nodes
Energy nodes of function.
bool has_grad(void) const
Signal if parameter gradient is computed analytically.
int nspectra(void) const
Return number of spectra in table model.
const GFilename & filename(void) const
Return file name.
bool has_energy_scale(void) const
Signal that energy scale was set.
const double & wgt_right(void) const
Returns right node weight.
Definition: GNodeArray.hpp:279
Energy boundaries container class.
Definition: GEbounds.hpp:60
#define G_CONST
const GXmlAttribute * attribute(const int &index) const
Return attribute.
std::string print(const GChatter &chatter=NORMAL) const
Print energy.
Definition: GEnergy.cpp:748
std::vector< double > m_last_values
Last parameter values.
void free(void)
Free a parameter.
virtual double eval(const GEnergy &srcEng, const GTime &srcTime=GTime(), const bool &gradients=false) const
Evaluate spectral table model.
Filename class.
Definition: GFilename.hpp:62
const GEnergy & emin(void) const
Return minimum energy of all intervals.
Definition: GEbounds.hpp:187
virtual void clear(void)
Clear table model.
void fix(void)
Fix a parameter.
GNdarray m_spectra
Spectra.
bool has_attribute(const std::string &name) const
Check if element has a given attribute.
GXmlElement * xml_need_par(const std::string &origin, GXmlElement &xml, const std::string &name)
Return pointer to parameter with given name in XML element.
Definition: GTools.cpp:1637
GVector log(const GVector &vector)
Computes natural logarithm of vector elements.
Definition: GVector.cpp:1274
Spectral table model parameter class definition.
double uniform(void)
Returns random double precision floating value in range 0 to 1.
Definition: GRan.cpp:242
void check_energy_interval(const std::string &origin, const GEnergy &emin, const GEnergy &emax)
Checks energy interval.
Definition: GException.cpp:364
const GModelSpectralTable g_spectral_table_seed
void clear(void)
Clear parameter.
int m_nebins
Number of energy bins.
GFilename m_filename
Filename of table.
FITS table string column class interface definition.
std::vector< double > m_gamma
Power-law indices.
Abstract interface for FITS table.
Definition: GFitsTable.hpp:44
bool is_fixed(void) const
Signal if parameter is fixed.
Spectral table model class.
GEbounds m_ebounds
Energy boundaries.
void remove_range(void)
Removes minimum and maximum boundary.
GChatter
Definition: GTypemaps.hpp:33
int size(void) const
Return number of table model parameters in container.
GEnergy elogmean(const int &index) const
Returns logarithmic mean energy for a given energy interval.
Definition: GEbounds.cpp:1126
int integer(const std::string &keyname) const
Return card value as integer.
Definition: GFitsHDU.hpp:436
const int & inx_left(void) const
Returns left node index.
Definition: GNodeArray.hpp:235
GModelSpectralTablePar & table_par(const int &index)
Return reference to table parameter.
int par_index(const std::string &name) const
Return index for parameter name.
bool has_max(void) const
Signal if parameter has maximum boundary.
std::vector< double > m_mc_min
Lower boundary for MC.
N-dimensional array class.
Definition: GNdarray.hpp:44
const std::string & extname(void) const
Return extension name.
Definition: GFitsHDU.hpp:162
void clear(void)
Clear energy boundaries.
Definition: GEbounds.cpp:268
double max(const GVector &vector)
Computes maximum vector element.
Definition: GVector.cpp:915
void load_par(const GFits &fits)
Load data from PARAMETERS extension.
Interface definition for the spectral model registry class.
FITS table long integer column.
bool xml_has_par(const GXmlElement &xml, const std::string &name)
Checks if parameter with given name in XML element exists.
Definition: GTools.cpp:1596
const int & inx_right(void) const
Returns right node index.
Definition: GNodeArray.hpp:249
virtual GModelSpectralTable * clone(void) const
Clone table model.
virtual std::string type(void) const
Return model type.
Spectral table model parameter container class definition.
std::string url(void) const
Return Uniform Resource Locator (URL)
Definition: GFilename.hpp:189
GNdarray m_log_values
log10(Function) values and grad&#39;s
void clear(void)
Clear file name.
Definition: GFilename.cpp:188
void init_members(void)
Initialise class members.
#define G_MC
int m_npars
Number of parameters.
double keV(void) const
Return energy in keV.
Definition: GEnergy.cpp:306
const double MeV2erg
Definition: GTools.hpp:45
void range(const double &min, const double &max)
Set minimum and maximum parameter boundaries.
void read(const GXmlElement &xml)
Extract parameter attributes from XML element.
Definition: GModelPar.cpp:229
double value(void) const
Return parameter value.
FITS binary table class.
void clear(void)
Clear table model parameters.
bool has_min(void) const
Signal if parameter has minimum boundary.
int index(const std::vector< int > &i) const
Compute array element index.
Definition: GNdarray.cpp:754
GVector pow(const GVector &vector, const double &power)
Computes tanh of vector elements.
Definition: GVector.cpp:1422
std::vector< double > m_eflux
Energy fluxes.
Exception handler interface definition.
FITS table long integer column class interface definition.
Spectral table model class definition.
GFitsHDU * append(const GFitsHDU &hdu)
Append HDU to FITS file.
Definition: GFits.cpp:678
FITS binary table class definition.
GVector exp(const GVector &vector)
Computes exponential of vector elements.
Definition: GVector.cpp:1232
void scale_energy(void)
Scale energy.
FITS table float column.
#define G_READ
const GEnergy & emax(void) const
Return maximum energy of all intervals.
Definition: GEbounds.hpp:199
void save(const GFilename &filename, const bool &clobber=false) const
Save table into file.
std::string parformat(const std::string &s, const int &indent=0)
Convert string in parameter format.
Definition: GTools.cpp:1143
void append(const double &node)
Append one node to array.
Definition: GNodeArray.cpp:351
GEnergy m_mc_emin
Minimum energy.
GFitsBinTable create_par_table(void) const
Create PARAMETERS FITS table.
const GNodeArray & values(void) const
Return reference to table model parameter values as node array.
GFitsHeaderCard & card(const int &cardno)
Return header card.
Definition: GFitsHDU.hpp:259
void close(void)
Close FITS file.
Definition: GFits.cpp:1342
int dim(void) const
Return dimension of array.
Definition: GNdarray.hpp:280
void update(void) const
Update cache for spectral table model computation.
virtual GModelSpectralTable & operator=(const GModelSpectralTable &model)
Assignment operator.
const GXmlElement * xml_get_par(const std::string &origin, const GXmlElement &xml, const std::string &name)
Return pointer to parameter with given name in XML element.
Definition: GTools.cpp:1689
Spectral table model parameter class.
void free_members(void)
Delete class members.
const double & wgt_grad_right(void) const
Returns right node weight gradient.
Definition: GNodeArray.hpp:309
void clear(void)
Clear instance.
Definition: GEnergy.cpp:261
virtual void write(GXmlElement &xml) const
Write model into XML element.
#define G_TABLE_PAR
void load_spec(const GFits &fits)
Load data from SPECTRA extension.
virtual std::string print(const GChatter &chatter=NORMAL) const
Print table model information.
#define G_WRITE
const double & factor_value(void) const
Return parameter factor value.
Mathematical function definitions.
GVector log10(const GVector &vector)
Computes base10 logarithm of vector elements.
Definition: GVector.cpp:1295
Class that handles energies in a unit independent way.
Definition: GEnergy.hpp:48
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:1200
GFilename xml_file_expand(const GXmlElement &xml, const std::string &filename)
Expand file name provided as XML attribute for loading.
Definition: GTools.cpp:1889
std::string str(const unsigned short int &value)
Convert unsigned short integer value into string.
Definition: GTools.cpp:489
void xml_check_type(const std::string &origin, GXmlElement &xml, const std::string &type)
Checks the model type.
Definition: GTools.cpp:1819