/*************************************************************************** * GModelSpectralTable.cpp - Spectral table model class * * ----------------------------------------------------------------------- * * copyright (C) 2019-2020 by Juergen Knoedlseder * * ----------------------------------------------------------------------- * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ /** * @file GModelSpectralTable.cpp * @brief Spectral table model class implementation * @author Juergen Knoedlseder */ /* __ Includes ___________________________________________________________ */ #ifdef HAVE_CONFIG_H #include #endif #include #include "GException.hpp" #include "GTools.hpp" #include "GMath.hpp" #include "GRan.hpp" #include "GEnergy.hpp" #include "GFits.hpp" #include "GFitsBinTable.hpp" #include "GFitsTableStringCol.hpp" #include "GFitsTableLongCol.hpp" #include "GFitsTableFloatCol.hpp" #include "GModelSpectralTable.hpp" #include "GModelSpectralTablePar.hpp" #include "GModelSpectralTablePars.hpp" #include "GModelSpectralRegistry.hpp" /* __ Constants __________________________________________________________ */ /* __ Globals ____________________________________________________________ */ const GModelSpectralTable g_spectral_table_seed; const GModelSpectralRegistry g_spectral_table_registry(&g_spectral_table_seed); /* __ Method name definitions ____________________________________________ */ #define G_CONST "GModelSpectralTable(GEbounds&, GModelSpectralTablePars&, "\ "GNdarray&)" #define G_FLUX "GModelSpectralTable::flux(GEnergy&, GEnergy&)" #define G_EFLUX "GModelSpectralTable::eflux(GEnergy&, GEnergy&)" #define G_MC "GModelSpectralTable::mc(GEnergy&, GEnergy&, GTime&, GRan&)" #define G_READ "GModelSpectralTable::read(GXmlElement&)" #define G_WRITE "GModelSpectralTable::write(GXmlElement&)" #define G_LOAD "GModelSpectralTable::load(GFilename&)" #define G_TABLE_PAR "GModelSpectralTable::table_par(int&)" #define G_LOAD_PAR "GModelSpectralTable::load_par(GFits&)" #define G_PAR_INDEX "GModelSpectralTable::par_index(std::string&)" #define G_UPDATE "GModelSpectralTable::update()" /* __ Macros _____________________________________________________________ */ /* __ Coding definitions _________________________________________________ */ /* __ Debug definitions __________________________________________________ */ //#define G_DEBUG_LOAD_SPEC //!< Debug load_spec() method //#define G_DEBUG_UPDATE //!< Debug update() method /*========================================================================== = = = Constructors/destructors = = = ==========================================================================*/ /***********************************************************************//** * @brief Void constructor ***************************************************************************/ GModelSpectralTable::GModelSpectralTable(void) : GModelSpectral() { // Initialise members init_members(); // Return return; } /***********************************************************************//** * @brief File constructor * * @param[in] filename File name of table model. * @param[in] norm Normalization factor. * * Constructs a spectral table model from a FITS file. See the load() method * for more information about the expected structure of the FITS file. ***************************************************************************/ GModelSpectralTable::GModelSpectralTable(const GFilename& filename, const double& norm) : GModelSpectral() { // Initialise members init_members(); // Load table load(filename); // Set normalization m_norm.value(norm); // Return return; } /***********************************************************************//** * @brief Table model constructor * * @param[in] ebounds Energy boundaries. * @param[in] pars Table model parameters. * @param[in] spectra Spectra. * * Constructs a spectral table model from energy boundaries, table model * parameters, and spectra. ***************************************************************************/ GModelSpectralTable::GModelSpectralTable(const GEbounds& ebounds, const GModelSpectralTablePars& pars, const GNdarray& spectra) : GModelSpectral() { // Initialise members init_members(); // Check consistency of spectra dimension if (spectra.dim() != pars.size()+1) { std::string msg = "Spectra array has "+gammalib::str(spectra.dim())+ " dimensions but an array with "+ gammalib::str(pars.size()+1)+" dimensions is " "expected. Please specify a spectra array with the " "correct dimension."; throw GException::invalid_argument(G_CONST, msg); } // Check consistency of parameter dimensions for (int i = 0; i < pars.size(); ++i) { int npars = pars[i]->size(); int nspec = spectra.shape()[i]; if (npars != nspec) { std::string msg = "Parameter \""+pars[i]->par().name()+"\" has "+ gammalib::str(npars)+" values but there are "+ gammalib::str(nspec)+" spectra for table axis "+ gammalib::str(i)+". Please specify a spectra " "array with the correct number of spectra."; throw GException::invalid_argument(G_CONST, msg); } } // Check consistency of energy bins int nebins = ebounds.size(); int nspec = spectra.shape()[pars.size()]; if (nebins != nspec) { std::string msg = "Spectra array has "+gammalib::str(nspec)+" energy " "bins but there are "+gammalib::str(nebins)+ " energy boundaries. Please specify a spectra " "array with the correct number of energy bins."; throw GException::invalid_argument(G_CONST, msg); } // Set members m_ebounds = ebounds; m_table_pars = pars; m_spectra = spectra; // Set energy nodes set_energy_nodes(); // Set parameter pointers set_par_pointers(); // Return return; } /***********************************************************************//** * @brief XML constructor * * @param[in] xml XML element. * * Constructs a spectral table model by extracting information from an XML * element. See the read() method for more information about the expected * structure of the XML element. ***************************************************************************/ GModelSpectralTable::GModelSpectralTable(const GXmlElement& xml) : GModelSpectral() { // Initialise members init_members(); // Read information from XML element read(xml); // Return return; } /***********************************************************************//** * @brief Copy constructor * * @param[in] model Table model. ***************************************************************************/ GModelSpectralTable::GModelSpectralTable(const GModelSpectralTable& model) : GModelSpectral(model) { // Initialise members init_members(); // Copy members copy_members(model); // Return return; } /***********************************************************************//** * @brief Destructor ***************************************************************************/ GModelSpectralTable::~GModelSpectralTable(void) { // Free members free_members(); // Return return; } /*========================================================================== = = = Operators = = = ==========================================================================*/ /***********************************************************************//** * @brief Assignment operator * * @param[in] model Table model. * @return Table model. ***************************************************************************/ GModelSpectralTable& GModelSpectralTable::operator=(const GModelSpectralTable& model) { // Execute only if object is not identical if (this != &model) { // Copy base class members this->GModelSpectral::operator=(model); // Free members free_members(); // Initialise members init_members(); // Copy members copy_members(model); } // endif: object was not identical // Return return *this; } /*========================================================================== = = = Public methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Clear table model ***************************************************************************/ void GModelSpectralTable::clear(void) { // Free class members (base and derived classes, derived class first) free_members(); this->GModelSpectral::free_members(); // Initialise members this->GModelSpectral::init_members(); init_members(); // Return return; } /***********************************************************************//** * @brief Clone table model ***************************************************************************/ GModelSpectralTable* GModelSpectralTable::clone(void) const { // Clone table model return new GModelSpectralTable(*this); } /***********************************************************************//** * @brief Evaluate spectral table model * * @param[in] srcEng True photon energy. * @param[in] srcTime True photon arrival time. * @param[in] gradients Compute gradients? * @return Model value (ph/cm2/s/MeV). * * Evaluates * * \f[ * S_{\rm E}(E | t) = {\tt m\_norm} \times * \left( w_l F_l(p) + w_r F_r(p) \right) * \f] * * where * - \f${\tt m\_norm}\f$ is the normalization factor of the spectral table * model, * - \f$w_l\f$ is the weight of the spectral vector \f$F_l(p)\f$ with the * largest energy \f$E_l\f$ that satisfies \f$EE_r\f$. * * The weights are computed using * * \f[ * w_r = \frac{\log_{10} E - \log_{10} E_l}{\log_{10} E_r - \log_{10} E_l} * \f] * * and \f$w_l = 1 - w_r\f$. * * If @p gradient is true, the method also computes the parameter gradients * using * * \f[ * \frac{\delta S_{\rm E}(E | t)}{\delta {\tt m\_norm}} = * \frac{S_{\rm E}(E | t)}{{\tt m\_norm}} * \f] * * and * * \f[ * \frac{\delta S_{\rm E}(E | t)}{\delta p} = * {\tt m\_norm} \times * \left( w_l \frac{\delta F_l(p)}{\delta p} + * w_r \frac{\delta F_r(p)}{\delta p} \right) * \f] * * for all other parameters. * * For the computation of \f$F_l(p)\f$, \f$F_r(p)\f$, * \f$\frac{\delta F_l(p)}{\delta p}\f$, and * \f$\frac{\delta F_r(p)}{\delta p}\f$ see the update() method. ***************************************************************************/ double GModelSpectralTable::eval(const GEnergy& srcEng, const GTime& srcTime, const bool& gradients) const { // Update spectral function update(); // Interpolate function in log10 energy m_log_nodes.set_value(srcEng.log10MeV()); double wgt_left = m_log_nodes.wgt_left(); double wgt_right = m_log_nodes.wgt_right(); int inx_left = m_log_nodes.inx_left(); int inx_right = m_log_nodes.inx_right(); double func = wgt_left * m_lin_values(inx_left,0) + wgt_right * m_lin_values(inx_right,0); // Compute function value double value = m_norm.value() * func; // Optionally compute gradients if (gradients) { // Compute partial derivative of function normalisation double g_norm = (m_norm.is_free()) ? m_norm.scale() * func : 0.0; // Set normalisation gradient m_norm.factor_gradient(g_norm); // Compute partial derivatives of all other free parameters int dim = m_table_pars.size(); for (int i = 0; i < dim; ++i) { // Initialise gradient double grad = 0.0; // Get reference to model parameter (circumvent const correctness) GModelPar& par = const_cast(m_table_pars[i])->par(); // If parameter is free then compute gradient if (par.is_free()) { grad = (wgt_left * m_lin_values(inx_left,i+1) + wgt_right * m_lin_values(inx_right,i+1)) * par.scale() * m_norm.value(); } // Set gradient par.factor_gradient(grad); } // endfor: looped over all parameters } // endif: gradient computation was requested // Compile option: Check for NaN/Inf #if defined(G_NAN_CHECK) if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) { std::cout << "*** ERROR: GModelSpectralTable::eval"; std::cout << "(srcEng=" << srcEng; std::cout << ", srcTime=" << srcTime << "):"; std::cout << " NaN/Inf encountered"; std::cout << " (value=" << value; std::cout << ", norm=" << norm(); std::cout << ", func=" << func; std::cout << ")" << std::endl; } #endif // Return return value; } /***********************************************************************//** * @brief Returns model photon flux between [emin, emax] (units: ph/cm2/s) * * @param[in] emin Minimum photon energy. * @param[in] emax Maximum photon energy. * @return Photon flux (ph/cm2/s). * * Computes * * \f[ * \int_{\tt emin}^{\tt emax} S_{\rm E}(E | t) dE * \f] * * where * - [@p emin, @p emax] is an energy interval, and * - \f$S_{\rm E}(E | t)\f$ is the spectral model (ph/cm2/s/MeV). ***************************************************************************/ double GModelSpectralTable::flux(const GEnergy& emin, const GEnergy& emax) const { // Initialise flux double flux = 0.0; // Compute only if integration range is valid if (emin < emax) { // Update spectral function and flux cache update(); update_flux(); // Get energy range in MeV double e_min = emin.MeV(); double e_max = emax.MeV(); // Determine left node index for minimum energy m_lin_nodes.set_value(e_min); int inx_emin = m_lin_nodes.inx_left(); // Determine left node index for maximum energy m_lin_nodes.set_value(e_max); int inx_emax = m_lin_nodes.inx_left(); // If both energies are within the same nodes then simply // integrate over the energy interval using the appropriate power // law parameters if (inx_emin == inx_emax) { flux = m_prefactor[inx_emin] * gammalib::plaw_photon_flux(e_min, e_max, m_epivot[inx_emin], m_gamma[inx_emin]); } // ... otherwise integrate over the nodes where emin and emax // resides and all the remaining nodes else { // If we are requested to extrapolate beyond first node, // the use the first nodes lower energy and upper energy // boundary for the initial integration. int i_start = (e_min < m_lin_nodes[0]) ? inx_emin : inx_emin+1; // Integrate from emin to the node boundary flux = m_prefactor[inx_emin] * gammalib::plaw_photon_flux(e_min, m_lin_nodes[i_start], m_epivot[inx_emin], m_gamma[inx_emin]); // Integrate over all nodes between for (int i = i_start; i < inx_emax; ++i) { flux += m_flux[i]; } // Integrate from node boundary to emax flux += m_prefactor[inx_emax] * gammalib::plaw_photon_flux(m_lin_nodes[inx_emax], e_max, m_epivot[inx_emax], m_gamma[inx_emax]); } // endelse: emin and emax not between same nodes // Multiply flux by normalisation factor flux *= norm(); } // endif: integration range was valid // Return return flux; } /***********************************************************************//** * @brief Returns model energy flux between [emin, emax] (units: erg/cm2/s) * * @param[in] emin Minimum photon energy. * @param[in] emax Maximum photon energy. * @return Energy flux (erg/cm2/s). * * Computes * * \f[ * \int_{\tt emin}^{\tt emax} S_{\rm E}(E | t) E \, dE * \f] * * where * - [@p emin, @p emax] is an energy interval, and * - \f$S_{\rm E}(E | t)\f$ is the spectral model (ph/cm2/s/MeV). ***************************************************************************/ double GModelSpectralTable::eflux(const GEnergy& emin, const GEnergy& emax) const { // Initialise flux double eflux = 0.0; // Compute only if integration range is valid if (emin < emax) { // Update spectral function and flux cache update(); update_flux(); // Get energy range in MeV double e_min = emin.MeV(); double e_max = emax.MeV(); // Determine left node index for minimum energy m_lin_nodes.set_value(e_min); int inx_emin = m_lin_nodes.inx_left(); // Determine left node index for maximum energy m_lin_nodes.set_value(e_max); int inx_emax = m_lin_nodes.inx_left(); // If both energies are within the same nodes then simply // integrate over the energy interval using the appropriate power // law parameters if (inx_emin == inx_emax) { eflux = m_prefactor[inx_emin] * gammalib::plaw_energy_flux(e_min, e_max, m_epivot[inx_emin], m_gamma[inx_emin]) * gammalib::MeV2erg; } // ... otherwise integrate over the nodes where emin and emax // resides and all the remaining nodes else { // If we are requested to extrapolate beyond first node, // the use the first nodes lower energy and upper energy // boundary for the initial integration. int i_start = (e_min < m_lin_nodes[0]) ? inx_emin : inx_emin+1; // Integrate from emin to the node boundary eflux = m_prefactor[inx_emin] * gammalib::plaw_energy_flux(e_min, m_lin_nodes[i_start], m_epivot[inx_emin], m_gamma[inx_emin]) * gammalib::MeV2erg; // Integrate over all nodes between for (int i = i_start; i < inx_emax; ++i) { eflux += m_eflux[i]; } // Integrate from node boundary to emax eflux += m_prefactor[inx_emax] * gammalib::plaw_energy_flux(m_lin_nodes[inx_emax], e_max, m_epivot[inx_emax], m_gamma[inx_emax]) * gammalib::MeV2erg; } // endelse: emin and emax not between same nodes // Multiply flux by normalisation factor eflux *= norm(); } // endif: integration range was valid // Return flux return eflux; } /***********************************************************************//** * @brief Returns MC energy between [emin, emax] * * @param[in] emin Minimum photon energy. * @param[in] emax Maximum photon energy. * @param[in] time True photon arrival time. * @param[in,out] ran Random number generator. * @return Energy. * * @exception GException::invalid_argument * Energy range is invalid (emin < emax required). * * Returns Monte Carlo energy by randomly drawing from a broken power law * defined by the file function. ***************************************************************************/ GEnergy GModelSpectralTable::mc(const GEnergy& emin, const GEnergy& emax, const GTime& time, GRan& ran) const { // Throw an exception if energy range is invalid if (emin >= emax) { std::string msg = "Minimum energy "+emin.print()+" is equal or " "larger than maximum energy "+emax.print()+". " "Please provide a minimum energy that is smaller " "than the maximum energy."; throw GException::invalid_argument(G_MC, msg); } // Allocate energy GEnergy energy; // Update spectral function and flux cache update(); update_flux(); update_mc(emin, emax); // Determine in which bin we reside int inx = 0; if (m_mc_cum.size() > 1) { double u = ran.uniform(); for (inx = m_mc_cum.size()-1; inx > 0; --inx) { if (m_mc_cum[inx-1] <= u) { break; } } } // Get random energy for specific bin if (m_mc_exp[inx] != 0.0) { double e_min = m_mc_min[inx]; double e_max = m_mc_max[inx]; double u = ran.uniform(); double eng = (u > 0.0) ? std::exp(std::log(u * (e_max - e_min) + e_min) / m_mc_exp[inx]) : 0.0; energy.MeV(eng); } else { double e_min = m_mc_min[inx]; double e_max = m_mc_max[inx]; double u = ran.uniform(); double eng = std::exp(u * (e_max - e_min) + e_min); energy.MeV(eng); } // Return energy return energy; } /***********************************************************************//** * @brief Read model from XML element * * @param[in] xml XML element containing power law model information. * * @exception GException::model_invalid_parnum * Invalid number of model parameters found in XML element. * @exception GException::model_invalid_parnames * Invalid model parameter name found in XML element. * * Reads the spectral information from an XML element. The format of the XML * elements is * * * * * * Optionally, values for the model table parameters can also be provided. ***************************************************************************/ void GModelSpectralTable::read(const GXmlElement& xml) { // Load table model from file (do this first since method calls clear()) load(gammalib::xml_file_expand(xml, xml.attribute("file"))); // Get normalisation parameter pointer const GXmlElement* norm = gammalib::xml_get_par(G_READ, xml, m_norm.name()); // Read normalisation parameter m_norm.read(*norm); // Optionally read all table parameters for (int i = 0; i < m_table_pars.size(); ++i) { GModelPar& par = m_table_pars[i]->par(); if (gammalib::xml_has_par(xml, par.name())) { const GXmlElement* element = gammalib::xml_get_par(G_READ, xml, par.name()); par.read(*element); } } // Return return; } /***********************************************************************//** * @brief Write model into XML element * * @param[in] xml XML element into which model information is written. * * @exception GException::model_invalid_spectral * Existing XML element is not of type "FileFunction" * @exception GException::model_invalid_parnum * Invalid number of model parameters or nodes found in XML element. * @exception GException::model_invalid_parnames * Invalid model parameter names found in XML element. * * Writes the spectral information into an XML element. The format of the XML * element is * * * * * * In addition, the method writes the model table parameters into the XML * file. * * Note that the function nodes will not be written since they will not be * altered by any method. ***************************************************************************/ void GModelSpectralTable::write(GXmlElement& xml) const { // Set model type if (xml.attribute("type") == "") { xml.attribute("type", type()); } // Verify model type if (xml.attribute("type") != type()) { throw GException::model_invalid_spectral(G_WRITE, xml.attribute("type"), "Spectral model is not of type \""+type()+"\"."); } // Get normalisation parameter GXmlElement* norm = gammalib::xml_need_par(G_WRITE, xml, m_norm.name()); // Write normalisation parameter m_norm.write(*norm); // Write all table parameters for (int i = 0; i < m_table_pars.size(); ++i) { const GModelPar& par = m_table_pars[i]->par(); GXmlElement* element = gammalib::xml_need_par(G_WRITE, xml, par.name()); par.write(*element); } // Set file attribute xml.attribute("file", gammalib::xml_file_reduce(xml, m_filename)); // Return return; } /***********************************************************************//** * @brief Load table from file * * @param[in] filename File name. * * Loads table model from FITS file. The format of the FITS file complies * with https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_92_009/ogip_92_009.html ***************************************************************************/ void GModelSpectralTable::load(const GFilename& filename) { // Clear table model clear(); // Set filename m_filename = filename; // Continue only if filename is not empty if (!filename.is_empty()) { // Open FITS file GFits fits(filename); // Load data from extensions load_par(fits); load_eng(fits); load_spec(fits); // Close FITS file fits.close(); // Set energy nodes set_energy_nodes(); // Set parameter pointers set_par_pointers(); } // endif: filename was not empty // Return return; } /***********************************************************************//** * @brief Save table into file * * @param[in] filename File name. * @param[in] clobber Overwrite existing file? * * Save the table model into a FITS file. The FITS file will contain three * binary table extensions: * * * PARAMETERS - Table model parameters * * ENERGIES - Table model energies * * SPECTRA - Table model spectra * * The format of the FITS file complies with * https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_92_009/ogip_92_009.html ***************************************************************************/ void GModelSpectralTable::save(const GFilename& filename, const bool& clobber) const { // Create FITS file GFits fits; // Create binary tables GFitsBinTable par_table = create_par_table(); GFitsBinTable eng_table = create_eng_table(); GFitsBinTable spec_table = create_spec_table(); // Append binary tables fits.append(par_table); fits.append(eng_table); fits.append(spec_table); // Set keywords in primary header GFitsHDU* primary = fits[0]; primary->card("CONTENT", "MODEL", "Spectrum file"); primary->card("FILENAME", filename.url(), "FITS file name"); primary->card("ORIGIN", PACKAGE_NAME, "Origin of FITS file"); primary->card("MODLNAME", "model", "Model name"); primary->card("MODLUNIT", "photons/cm^2/s/MeV", "Model units"); primary->card("REDSHIFT", false, "If true then redshift will be included as a par"); primary->card("ADDMODEL", true, "If true then this is an additive table model"); primary->card("HDUCLASS", "OGIP", "Format conforms to OGIP standard"); primary->card("HDUCLAS1", "XSPEC TABLE MODEL", "Model spectra for XSPEC"); primary->card("HDUVERS", "1.0.0", "Version of format"); // Save to FITS file fits.saveto(filename, clobber); // Set filename m_filename = filename; // Return return; } /***********************************************************************//** * @brief Return reference to table parameter * * @param[in] index Table parameter index. * @return Reference to table parameter. ***************************************************************************/ GModelSpectralTablePar& GModelSpectralTable::table_par(const int& index) { // Raise exception if index is out of range if (index < 0 || index >= size()) { throw GException::out_of_range(G_TABLE_PAR, index, size()); } // Return reference return *(m_table_pars[index]); } /***********************************************************************//** * @brief Return const reference to table parameter * * @param[in] index Table parameter index. * @return Const reference to table parameter. ***************************************************************************/ const GModelSpectralTablePar& GModelSpectralTable::table_par(const int& index) const { // Raise exception if index is out of range if (index < 0 || index >= size()) { throw GException::out_of_range(G_TABLE_PAR, index, size()); } // Return reference return *(m_table_pars[index]); } /***********************************************************************//** * @brief Return reference to table parameter * * @param[in] name Table parameter name. * @return Reference to table parameter. ***************************************************************************/ GModelSpectralTablePar& GModelSpectralTable::table_par(const std::string& name) { // Get index from name int index = par_index(name); // Return reference return *(m_table_pars[index]); } /***********************************************************************//** * @brief Return const reference to table parameter * * @param[in] name Table parameter name. * @return Const reference to table parameter. ***************************************************************************/ const GModelSpectralTablePar& GModelSpectralTable::table_par(const std::string& name) const { // Get index from name int index = par_index(name); // Return reference return *(m_table_pars[index]); } /***********************************************************************//** * @brief Print table model information * * @param[in] chatter Chattiness. * @return String containing table model information. ***************************************************************************/ std::string GModelSpectralTable::print(const GChatter& chatter) const { // Initialise result string std::string result; // Continue only if chatter is not silent if (chatter != SILENT) { // Append header result.append("=== GModelSpectralTable ==="); // Append information result.append("\n"+gammalib::parformat("Table file")); result.append(m_filename.url()); result.append("\n"+gammalib::parformat("Number of parameters")); result.append(gammalib::str(this->size())); for (int i = 0; i < size(); ++i) { result.append("\n"+m_pars[i]->print(chatter)); } // Append parameter value intervals for (int i = 0; i < m_table_pars.size(); ++i) { std::string name = m_table_pars[i]->par().name(); int size = m_table_pars[i]->values().size(); result.append("\n"+gammalib::parformat(name+" values")); result.append(gammalib::str(size)); if (size > 0) { double min = m_table_pars[i]->values()[0]; double max = m_table_pars[i]->values()[0]; for (int k = 0; k < size; ++k) { double value = m_table_pars[i]->values()[k]; if (value < min) { min = value; } if (value > max) { max = value; } } result.append(" ["+gammalib::str(min)+", "+gammalib::str(max)+"]"); } if (m_table_pars[i]->method() == 0) { result.append(" (linear)"); } else if (m_table_pars[i]->method() == 1) { result.append(" (logarithmic)"); } } // Append energy boundaries result.append("\n"+gammalib::parformat("Energies")); result.append(gammalib::str(m_ebounds.size())); result.append(" ["); result.append(m_ebounds.emin().print()); result.append(", "); result.append(m_ebounds.emax().print()); result.append("]"); // Append shape of spectra array int nspectra = 0; int nebins = 0; if (m_spectra.dim() > 0) { nspectra = 1; for (int i = 0; i < m_spectra.dim()-1; ++i) { nspectra *= m_spectra.shape()[i]; } nebins = m_spectra.shape()[m_spectra.dim()-1]; } result.append("\n"+gammalib::parformat("Spectra array dimension")); result.append(gammalib::str(m_spectra.dim())); result.append("\n"+gammalib::parformat("Number of spectra")); result.append(gammalib::str(nspectra)); result.append("\n"+gammalib::parformat("Number of spectral bins")); result.append(gammalib::str(nebins)); } // endif: chatter was not silent // Return result return result; } /*========================================================================== = = = Private methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Initialise class members ***************************************************************************/ void GModelSpectralTable::init_members(void) { // Initialise normalisation m_norm.clear(); m_norm.name("Normalization"); m_norm.scale(1.0); m_norm.value(1.0); m_norm.range(0.0,1000.0); m_norm.free(); m_norm.gradient(0.0); m_norm.has_grad(true); // Initialize other members m_table_pars.clear(); m_spectra.clear(); m_ebounds.clear(); m_filename.clear(); // Initialize cache m_npars = 0; m_nebins = 0; m_last_values.clear(); m_lin_nodes.clear(); m_log_nodes.clear(); m_lin_values.clear(); m_log_values.clear(); // Initialise flux cache m_prefactor.clear(); m_gamma.clear(); m_epivot.clear(); m_flux.clear(); m_eflux.clear(); // Initialise MC cache m_mc_emin.clear(); m_mc_emax.clear(); m_mc_cum.clear(); m_mc_min.clear(); m_mc_max.clear(); m_mc_exp.clear(); // Set parameter pointer(s) set_par_pointers(); // Return return; } /***********************************************************************//** * @brief Copy class members * * @param[in] model Table model. ***************************************************************************/ void GModelSpectralTable::copy_members(const GModelSpectralTable& model) { // Copy members m_norm = model.m_norm; m_table_pars = model.m_table_pars; m_spectra = model.m_spectra; m_ebounds = model.m_ebounds; m_filename = model.m_filename; // Copy cache m_npars = model.m_npars; m_nebins = model.m_nebins; m_last_values = model.m_last_values; m_lin_nodes = model.m_lin_nodes; m_log_nodes = model.m_log_nodes; m_lin_values = model.m_lin_values; m_log_values = model.m_log_values; // Copy flux cache m_prefactor = model.m_prefactor; m_gamma = model.m_gamma; m_epivot = model.m_epivot; m_flux = model.m_flux; m_eflux = model.m_eflux; // Copy MC cache m_mc_emin = model.m_mc_emin; m_mc_emax = model.m_mc_emax; m_mc_cum = model.m_mc_cum; m_mc_min = model.m_mc_min; m_mc_max = model.m_mc_max; m_mc_exp = model.m_mc_exp; // Set energy nodes set_energy_nodes(); // Set parameter pointer(s) set_par_pointers(); // Return return; } /***********************************************************************//** * @brief Delete class members ***************************************************************************/ void GModelSpectralTable::free_members(void) { // Return return; } /***********************************************************************//** * @brief Set parameter pointers ***************************************************************************/ void GModelSpectralTable::set_par_pointers(void) { // Clear pointers m_pars.clear(); // Put normalisation parameter on stack m_pars.push_back(&m_norm); // Put table model parameters on stack for (int i = 0; i < m_table_pars.size(); ++i) { m_pars.push_back(&(m_table_pars[i]->par())); } // Return return; } /***********************************************************************//** * @brief Set energy nodes from energy boundaries ***************************************************************************/ void GModelSpectralTable::set_energy_nodes(void) { // Determine number of energy bins int nebins = m_ebounds.size(); // Continue only if there are energy bins if (nebins > 0) { // Initialise vectors for values m_lin_nodes = GNodeArray(); m_log_nodes = GNodeArray(); m_lin_values = GNdarray(nebins, 1); m_log_values = GNdarray(nebins, 1); // Compute node values for (int i = 0; i < nebins; ++i) { double energy_MeV = m_ebounds.elogmean(i).MeV(); double log10_energy_MeV = std::log10(energy_MeV); m_lin_nodes.append(energy_MeV); m_log_nodes.append(log10_energy_MeV); } } // endif: there were energy bins // Return return; } /***********************************************************************//** * @brief Create PARAMETERS FITS table * * @return Binary FITS table containing table model parameters * * The method creates a binary FITS table that is compliant with * https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_92_009/node6.html ***************************************************************************/ GFitsBinTable GModelSpectralTable::create_par_table(void) const { // Determine number of parameters int nrows = m_table_pars.size(); // Determine maximum number of parameters values int ncols = 0; for (int i = 0; i < nrows; ++i) { int n = m_table_pars[i]->size(); if (n > ncols) { ncols = n; } } // Create table columns GFitsTableStringCol col_name("NAME", nrows, 12); GFitsTableLongCol col_method("METHOD", nrows); GFitsTableFloatCol col_initial("INITIAL", nrows); GFitsTableFloatCol col_delta("DELTA", nrows); GFitsTableFloatCol col_minimum("MINIMUM", nrows); GFitsTableFloatCol col_bottom("BOTTOM", nrows); GFitsTableFloatCol col_top("TOP", nrows); GFitsTableFloatCol col_maximum("MAXIMUM", nrows); GFitsTableLongCol col_numbvals("NUMBVALS", nrows); GFitsTableFloatCol col_value("VALUE", nrows, ncols); // Fill columns for (int i = 0; i < nrows; ++i) { // Get model parameter const GModelPar& par = m_table_pars[i]->par(); // Set parameter name and initial value col_name(i) = par.name(); col_method(i) = m_table_pars[i]->method(); col_initial(i) = (float)par.value(); // Handle free/fixed parameter attribute if (par.is_fixed()) { col_delta(i) = -1.0; } else { col_delta(i) = 1.0; // Dummy value } // Set number of parameters col_numbvals(i) = m_table_pars[i]->size(); // Set parameter values double min_value = 0.0; double max_value = 0.0; if (m_table_pars[i]->size() > 0) { min_value = m_table_pars[i]->values()[0]; max_value = m_table_pars[i]->values()[0]; for (int k = 0; k < m_table_pars[i]->size(); ++k) { double value = m_table_pars[i]->values()[k]; if (value < min_value) { min_value = value; } if (value > max_value) { max_value = value; } col_value(i,k) = value; } } // Handle parameter minimum if (par.has_min()) { if (par.min() < min_value) { col_minimum(i) = (float)par.min(); // Hard minimum limit col_bottom(i) = min_value; // Soft minimum limit } else { col_minimum(i) = (float)par.min(); // Hard minimum limit col_bottom(i) = (float)par.min(); // Soft minimum limit } } else { col_minimum(i) = min_value; // Hard minimum limit col_bottom(i) = min_value; // Soft minimum limit } // Handle parameter maximum if (par.has_max()) { if (par.max() > max_value) { col_top(i) = max_value; // Soft maximum limit col_maximum(i) = (float)par.max(); // Hard maximum limit } else { col_top(i) = (float)par.max(); // Soft maximum limit col_maximum(i) = (float)par.max(); // Hard maximum limit } } else { col_top(i) = max_value; // Soft maximum limit col_maximum(i) = max_value; // Soft maximum limit } } // endfor: looped over parameters // Create binary table GFitsBinTable table; // Append columns to FITS table table.append(col_name); table.append(col_method); table.append(col_initial); table.append(col_delta); table.append(col_minimum); table.append(col_bottom); table.append(col_top); table.append(col_maximum); table.append(col_numbvals); table.append(col_value); // Set table extension name table.extname("PARAMETERS"); // Set table keywords table.card("HDUCLASS", "OGIP", "Format conforms to OGIP standard"); table.card("HDUCLAS1", "XSPEC TABLE MODEL", "Model spectra for XSPEC"); table.card("HDUCLAS2", "PARAMETERS", "Extension containing parameter info"); table.card("HDUVERS", "1.0.0", "Version of format"); table.card("NINTPARM", nrows, "Number of interpolation parameters"); table.card("NADDPARM", 0, "Number of additional parameters"); // Return table return table; } /***********************************************************************//** * @brief Create ENERGIES FITS table * * @return Binary FITS table containing table model energies * * The method creates a binary FITS table that is compliant with * https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_92_009/node7.html ***************************************************************************/ GFitsBinTable GModelSpectralTable::create_eng_table(void) const { // Create table columns GFitsTableFloatCol col_lo("ENERG_LO", m_ebounds.size()); GFitsTableFloatCol col_hi("ENERG_HI", m_ebounds.size()); // Fill energy boundary columns for (int i = 0; i < m_ebounds.size(); ++i) { col_lo(i) = m_ebounds.emin(i).keV(); col_hi(i) = m_ebounds.emax(i).keV(); } // Set energy units col_lo.unit("keV"); col_hi.unit("keV"); // Create binary table GFitsBinTable table; // Append columns to FITS table table.append(col_lo); table.append(col_hi); // Set table extension name table.extname("ENERGIES"); // Set table keywords table.card("HDUCLASS", "OGIP", "Format conforms to OGIP standard"); table.card("HDUCLAS1", "XSPEC TABLE MODEL", "Model spectra for XSPEC"); table.card("HDUCLAS2", "ENERGIES", "Extension containing energy bin info"); table.card("HDUVERS", "1.0.0", "Version of format"); // Return table return table; } /***********************************************************************//** * @brief Create SPECTRA FITS table * * @return Binary FITS table containing table model spectra * * The method creates a binary FITS table that is compliant with * https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_92_009/node8.html ***************************************************************************/ GFitsBinTable GModelSpectralTable::create_spec_table(void) const { // Compute number of rows int nrows = 1; for (int i = 0; i < m_spectra.dim()-1; ++i) { nrows *= m_spectra.shape()[i]; } // Compute number of parameters int npars = m_table_pars.size(); // Compute number of energy bins int nebins = m_ebounds.size(); // Create columns GFitsTableFloatCol col_pars("PARAMVAL", nrows, npars); GFitsTableFloatCol col_spec("INTPSPEC", nrows, nebins); // Fill columns std::vector inx(npars+1,0); for (int i = 0; i < nrows; ++i) { // Set parameter values for (int k = 0; k < npars; ++k) { col_pars(i,k) = m_table_pars[k]->values()[inx[k]]; } // Set current index std::vector index = inx; // Loop over energy bins for (int k = 0; k < nebins; ++k, ++index[npars]) { col_spec(i,k) = m_spectra(index); } // Increment parameter index. Last parameter index is changing fastest int ipar = npars-1; do { inx[ipar] += 1; if (inx[ipar] < m_spectra.shape()[ipar]) { break; } else { inx[ipar] = 0; ipar--; } } while (ipar >= 0); } // endfor: looped over rows // Create binary table GFitsBinTable table; // Append columns to FITS table table.append(col_pars); table.append(col_spec); // Set table extension name table.extname("SPECTRA"); // Set table keywords table.card("HDUCLASS", "OGIP", "Format conforms to OGIP standard"); table.card("HDUCLAS1", "XSPEC TABLE MODEL", "Model spectra for XSPEC"); table.card("HDUCLAS2", "MODEL SPECTRA", "Extension containing model spectra"); table.card("HDUVERS", "1.0.0", "Version of format"); // Return table return table; } /***********************************************************************//** * @brief Load data from PARAMETERS extension * * @param[in] fits FITS file. * * @exception GException::invalid_value * Non-positive parameter value encountered for logarithmic * parameters. * * The method loads data from the PARAMETERS binary table. The format of the * table needs to be compliant with * https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_92_009/node6.html ***************************************************************************/ void GModelSpectralTable::load_par(const GFits& fits) { // Get PARAMETERS extension const GFitsTable& table = *fits.table("PARAMETERS"); // Get number of parameters int npars = table.integer("NAXIS2"); // Loop over all parameters for (int i = 0; i < npars; ++i) { // Set model parameter GModelPar par(table["NAME"]->string(i), table["INITIAL"]->real(i)); // Apply hard minimum and maximum as parameter range. Note that the // minimum and maximum apply to the value factor, hence in case of // a negative scale factor the minimum becomes the maximum and vice // versa. This is actually a bug in GammaLib, see // https://cta-redmine.irap.omp.eu/issues/3072 double min = table["MINIMUM"]->real(i) / par.scale(); double max = table["MAXIMUM"]->real(i) / par.scale(); if (min > max) { par.factor_range(max, min); } else { par.factor_range(min, max); } // Fix or free parameter according to DELTA value if (table["DELTA"]->real(i) < 0.0) { par.fix(); par.has_grad(false); } else { par.free(); par.has_grad(true); } // Set vector of parameter values std::vector values; for (int k = 0; k < table["NUMBVALS"]->integer(i); ++k) { // Extract value double value = table["VALUE"]->real(i,k); // If method is logarithmic then store log of the parameter // values in the spectral table parameters /* if (table["METHOD"]->integer(i) == 1) { if (value > 0.0) { value = std::log(value); } else { std::string msg = "Non-positive value "+gammalib::str(value)+ " encountered for logarithmic parameter " "\""+table["NAME"]->string(i)+"\". Please " "provide only positive parameter " "values for logarithmic parameters."; throw GException::invalid_value(G_LOAD_PAR, msg); } } */ // Append value values.push_back(value); } // endfor: looped over parameter values // Set table model parameter GModelSpectralTablePar table_model_par(par, values); // Set interpolation method table_model_par.method(table["METHOD"]->integer(i)); // Append table model parameter m_table_pars.append(table_model_par); } // endfor: looped over all parameters // Return return; } /***********************************************************************//** * @brief Load data from ENERGIES extension * * @param[in] fits FITS file. * * The method loads data from the ENERGIES binary table. The format of the * table needs to be compliant with * https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_92_009/node7.html * * If energy units are provided in the ENERGIES extension the method decodes * the units and interprets the energy values correspondingly. ***************************************************************************/ void GModelSpectralTable::load_eng(const GFits& fits) { // Get ENERGIES extension const GFitsTable& table = *fits.table("ENERGIES"); // Get number of energy bins int nebins = table.integer("NAXIS2"); // Extract energy boundary information from FITS table if (nebins > 0) { // Get units std::string emin_unit = table["ENERG_LO"]->unit(); std::string emax_unit = table["ENERG_HI"]->unit(); if (emin_unit.empty()) { emin_unit = "keV"; } if (emax_unit.empty()) { emax_unit = "keV"; } // Read energy boundaries and append bins for (int i = 0; i < nebins; ++i) { GEnergy emin(table["ENERG_LO"]->real(i), emin_unit); GEnergy emax(table["ENERG_HI"]->real(i), emax_unit); m_ebounds.append(emin, emax); } } // endif: there were energy bins // Return return; } /***********************************************************************//** * @brief Load data from SPECTRA extension * * @param[in] fits FITS file. * * The method loads data from the SPECTRA binary table. The format of the * table needs to be compliant with * https://heasarc.gsfc.nasa.gov/docs/heasarc/ofwg/docs/general/ogip_92_009/node8.html ***************************************************************************/ void GModelSpectralTable::load_spec(const GFits& fits) { // Get number of parameters and energy bins int npars = fits.table("PARAMETERS")->integer("NAXIS2"); int nebins = fits.table("ENERGIES")->integer("NAXIS2"); // Setup dimension of spectra array std::vector naxis(npars+1, 0); for (int i = 0; i < npars; ++i) { naxis[i] = (*fits.table("PARAMETERS"))["NUMBVALS"]->integer(i); } naxis[npars] = nebins; // Setup spectra array m_spectra = GNdarray(naxis); // Get SPECTRA extension const GFitsTable& table = *fits.table("SPECTRA"); // Get number of rows int nrows = table.integer("NAXIS2"); // Loop over rows for (int i = 0; i < nrows; ++i) { // Initialise spectra array index of first energy bin std::vector index(npars+1, 0); // Setup spectra array index std::vector parval(npars, 0.0); for (int k = 0; k < npars; ++k) { // Get reference to node array const GNodeArray& nodes = m_table_pars[k]->values(); // Set interpolation value nodes.set_value(table["PARAMVAL"]->real(i,k)); // Get best index int inx = (nodes.wgt_left() > nodes.wgt_right()) ? nodes.inx_left() : nodes.inx_right(); // Store index index[k] = inx; } // endfor: looped over parameters // Debug: dump vector array #if defined(G_DEBUG_LOAD_SPEC) std::cout << i << ": ("; for (int k = 0; k < index.size(); ++k) { if (k > 0) { std::cout << ", "; } std::cout << index[k]; } std::cout << ")" << std::endl; #endif // Loop over energy bins and store spectrum for (int k = 0; k < nebins; ++k, ++index[npars]) { m_spectra(index) = table["INTPSPEC"]->real(i,k); } } // endfor: looped over rows // Return return; } /***********************************************************************//** * @brief Return index for parameter name * * @param[in] name Parameter name. * @return Parameter index. * * @exception GException::invalid_argument * Parameter name not found in spectral table. ***************************************************************************/ int GModelSpectralTable::par_index(const std::string& name) const { // Get parameter index int index = 0; for (; index < size(); ++index) { if (m_table_pars[index]->par().name() == name) { break; } } // Throw exception if parameter name was not found if (index >= size()) { std::string msg = "Parameter name \""+name+"\" not found in spectral " "table. Please specify one of the following parameter " "names:"; for (int i = 0; i < size(); ++i) { if (i > 0) { msg += ","; } msg += " \""+m_table_pars[i]->par().name()+"\""; } throw GException::invalid_argument(G_PAR_INDEX, msg); } // Return index return index; } /***********************************************************************//** * @brief Update cache for spectral table model computation * * Update interval vectors for function values and gradients. An update is * performed in case that some of the parameter values have changed. The * method sets the following cache members: * * m_npars (number of model parameters) * m_nebins (number of energy bins in spectra) * m_last_values (last model parameter values) * m_lin_values (function values) * * The array \f${\tt m\_lin\_values}\f$ holds the vector \f$F(E|p)\f$ as * function of energy \f$E\f$, computed for the current set of parameters * \f$p\f$. The computation is done using the N-dimensional linear * interpolation * * \f[ * F(E|p) = \sum_{k=1}^M \left( \prod_{i=1}^N w_x(p) \right) F_p(E) * \f] * * where * - \f$M=2^N\f$ is the number of parameter combinations, * - \f$N\f$ is the number of table model parameters, * - \f$w_x(p) = \{w_0^l, w_0^r, w_1^l, w_1^r, ...\}\f$ * is an array of subsequently arranged left and right weighting factors * for the linear interpolation of parameters, where \f$w_0^l\f$ and * \f$w_0^r\f$ are the left and right weighting factors for the first * parameter, \f$w_1^l\f$ and \f$w_1^r\f$ are the left and right weighting * factors for the second parameter, and so on, * - \f$x=2 k + i/2^k \mod 2\f$ is the index in the array of * weighting factors for parameter combination \f$k\f$ and parameter * \f$i\f$, and * - \f$F_p(E)\f$ are the table model spectra, computed for a grid of * possible parameter values. * * For each parameter \f$i\f$, the weighting factors \f$w_i^l(p)\f$ * and \f$w_i^r(p)\f$ are computed using * * \f[ * w_i^r(p) = \frac{p - p_l}{p_r - p_l} * \f] * * and \f$w_i^l(p) = 1 - w_i^r(p)\f$, where * \f$p_l\f$ is the largest parameter value that satisfies \f$pp_r\f$. * * The method also computes the gradients * * \f[ * \frac{\delta F(E|p)}{\delta p} = \sum_{k=1}^M \left( * \frac{\delta w_{x_p}(p)}{\delta p} \prod_{i=1 \land i \neq i_p}^N * w_x(p) \right) F_p(E) * \f] * * where * \f$x_p = 2 k + i_p/2^k \mod 2\f$. ***************************************************************************/ void GModelSpectralTable::update(void) const { // Get number of parameters and number of energy bins m_npars = m_table_pars.size(); m_nebins = ebounds().size(); // Initialise update flag bool need_update = false; // If dimension of last cached parameter values differ from dimension // of spectral table then reallocate cache and request update if (m_last_values.size() != m_npars) { m_last_values = std::vector(m_npars, 0.0); need_update = true; } // ... otherwise check if some parameter values have changed else { for (int i = 0; i < m_npars; ++i) { if (m_table_pars[i]->par().value() != m_last_values[i]) { need_update = true; break; } } } // Continue only if update is required if (need_update) { // Debug option: write header #if defined(G_DEBUG_UPDATE) std::cout << "GModelSpectralTable::update() required" << std::endl; #endif // Initialise vectors for weights, weight gradients and indices std::vector weights(2*m_npars, 0.0); std::vector weight_gradients(2*m_npars, 0.0); std::vector indices(2*m_npars, 0); // Loop over all parameters and extract left and right weights, // weight gradients and indices and put them into single arrays for (int i = 0; i < m_npars; ++i) { // Get pointers to node array and parameter const GNodeArray* nodes = &(m_table_pars[i]->values()); const GModelPar* par = &(m_table_pars[i]->par()); // Get parameter value double value = par->value(); // Cache parameter value m_last_values[i] = value; // Use log of value for logarithmic parameters /* if (m_table_pars[i]->method() == 1) { if (value > 0.0) { value = std::log(value); } else { std::string msg = "Non-positive value "+gammalib::str(value)+ " encountered for logarithmic parameter " "\""+m_table_pars[i]->par().name()+"\"."; throw GException::invalid_value(G_UPDATE, msg); } } */ // Set values for node array nodes->set_value(value); // Compute left and right indices int il = 2*i; int ir = il + 1; // Push back weigths, weight gradients and indices weights[il] = nodes->wgt_left(); weights[ir] = nodes->wgt_right(); weight_gradients[il] = nodes->wgt_grad_left(); weight_gradients[ir] = nodes->wgt_grad_right(); indices[il] = nodes->inx_left(); indices[ir] = nodes->inx_right(); // Debug option: print weights and indices #if defined(G_DEBUG_UPDATE) std::cout << " wgt_l=" << weights[il]; std::cout << " wgt_r=" << weights[ir]; std::cout << " wgt_grad_l=" << weight_gradients[il]; std::cout << " wgt_grad_r=" << weight_gradients[ir]; std::cout << " inx_l=" << indices[il]; std::cout << " inx_r=" << indices[ir] << std::endl; #endif } // endfor: looped over all parameters // Compute number of combinations int combinations = 1 << m_npars; // Initialise 2d arrays for values and gradients m_lin_values = GNdarray(m_nebins, m_npars+1); m_log_values = GNdarray(m_nebins, m_npars+1); // Debug option: initial sum of weights #if defined(G_DEBUG_UPDATE) double weight_sum = 0.0; #endif // Loop over combinations for (int i = 0; i < combinations; ++i) { // Debug option: start printing combination #if defined(G_DEBUG_UPDATE) std::cout << " " << i << ": "; #endif // Initialise weight and gradient weights double weight = 1.0; std::vector grad_weight(m_npars, 1.0); // Initialise index vector (including the energy dimension) std::vector index_shape(m_npars+1,0); // Loop over dimensions for (int k = 0, div = 1; k < m_npars; ++k, div *= 2) { // Compute index for each dimension int index = i/div % 2 + k * 2; // Update weight weight *= weights[index]; // Add index index_shape[k] = indices[index]; // Update gradient weights for (int j = 0; j < m_npars; ++j) { if (k == j) { grad_weight[j] *= weight_gradients[index]; } else { grad_weight[j] *= weights[index]; } } // endfor: update gradient weights // Debug option: print index and weight for current dimension #if defined(G_DEBUG_UPDATE) std::cout << index; std::cout << " (" << weights[index]; std::cout << " @ " << indices[index] << ") "; #endif } // endfor: looped over dimensions // Debug option: print total weight and weight gradient #if defined(G_DEBUG_UPDATE) std::cout << ": wgt=" << weight; std::cout << " ["; for (int k = 0; k < m_npars; ++k) { if (k > 0) { std::cout << ","; } std::cout << grad_weight[k]; } std::cout << "]" << std::endl; weight_sum += weight; #endif // Compute interpolated value and gradient for (int iebin = 0; iebin < m_nebins; ++iebin) { // Set energy index index_shape[m_npars] = iebin; // Get spectral value double value = m_spectra(index_shape); // Compute contribution and store in value slot m_lin_values(iebin,0) += weight * value; // Compute gradients and store in gradient slots for (int j = 0; j < m_npars; ++j) { m_lin_values(iebin,j+1) += grad_weight[j] * value; } } // endfor: looped over all energy bins } // endfor: looped over combinations // Compute log10 values of function values /* for (int iebin = 0; iebin < m_nebins; ++iebin) { if (m_lin_values(iebin,0) > 0.0) { m_log_values(iebin,0) = std::log10(m_lin_values(iebin,0)); } else { m_log_values(iebin,0) = -1000.0; // Set to a tiny value } } */ // Debug option: print sum of weights #if defined(G_DEBUG_UPDATE) std::cout << " sum(wgt)=" << weight_sum << std::endl; #endif } // endif: updated requested // Return return; } /***********************************************************************//** * @brief Update flux cache ***************************************************************************/ void GModelSpectralTable::update_flux(void) const { // Clear any existing values m_prefactor.clear(); m_gamma.clear(); m_epivot.clear(); m_flux.clear(); m_eflux.clear(); // Loop over all nodes-1 for (int i = 0; i < m_lin_nodes.size()-1; ++i) { // Get energies and function values double emin = m_lin_nodes[i]; double emax = m_lin_nodes[i+1]; double fmin = m_lin_values(i,0); double fmax = m_lin_values(i+1,0); // Compute pivot energy (MeV). We use here the geometric mean of // the node boundaries. double epivot = std::sqrt(emin*emax); // Compute spectral index double gamma = std::log(fmin/fmax) / std::log(emin/emax); // Compute power law normalisation double prefactor = fmin / std::pow(emin/epivot, gamma); // Compute photon flux between nodes double flux = prefactor * gammalib::plaw_photon_flux(emin, emax, epivot, gamma); // Compute energy flux between nodes double eflux = prefactor * gammalib::plaw_energy_flux(emin, emax, epivot, gamma); // Convert energy flux from MeV/cm2/s to erg/cm2/s eflux *= gammalib::MeV2erg; // Push back values on pre-computation cache m_prefactor.push_back(prefactor); m_gamma.push_back(gamma); m_epivot.push_back(epivot); m_flux.push_back(flux); m_eflux.push_back(eflux); } // endfor: looped over all nodes // Return return; } /***********************************************************************//** * @brief Update MC cache * * @param[in] emin Minimum energy. * @param[in] emax Maximum energy. * * This method sets up an array of indices and the cumulative distribution * function needed for MC simulations. ***************************************************************************/ void GModelSpectralTable::update_mc(const GEnergy& emin, const GEnergy& emax) const { // Check if we need to update the cache if (emin != m_mc_emin || emax != m_mc_emax) { // Store new energy interval m_mc_emin = emin; m_mc_emax = emax; // Initialise cache m_mc_cum.clear(); m_mc_min.clear(); m_mc_max.clear(); m_mc_exp.clear(); // Get energy range in MeV double e_min = emin.MeV(); double e_max = emax.MeV(); // Continue only if e_max > e_min if (e_max > e_min) { // Allocate flux double flux; // Determine left node index for minimum energy m_lin_nodes.set_value(e_min); int inx_emin = m_lin_nodes.inx_left(); // Determine left node index for maximum energy m_lin_nodes.set_value(e_max); int inx_emax = m_lin_nodes.inx_left(); // If both energies are within the same node then just // add this one node on the stack if (inx_emin == inx_emax) { flux = m_prefactor[inx_emin] * gammalib::plaw_photon_flux(e_min, e_max, m_epivot[inx_emin], m_gamma[inx_emin]); m_mc_cum.push_back(flux); m_mc_min.push_back(e_min); m_mc_max.push_back(e_max); m_mc_exp.push_back(m_gamma[inx_emin]); } // ... otherwise integrate over the nodes where emin and emax // resides and all the remaining nodes else { // If we are requested to extrapolate beyond first node, // the use the first nodes lower energy and upper energy // boundary for the initial integration. int i_start = (e_min < m_lin_nodes[0]) ? inx_emin : inx_emin+1; // Add emin to the node boundary flux = m_prefactor[inx_emin] * gammalib::plaw_photon_flux(e_min, m_lin_nodes[i_start], m_epivot[inx_emin], m_gamma[inx_emin]); m_mc_cum.push_back(flux); m_mc_min.push_back(e_min); m_mc_max.push_back(m_lin_nodes[i_start]); m_mc_exp.push_back(m_gamma[inx_emin]); // Add all nodes between for (int i = i_start; i < inx_emax; ++i) { flux = m_flux[i]; m_mc_cum.push_back(flux); m_mc_min.push_back(m_lin_nodes[i]); m_mc_max.push_back(m_lin_nodes[i+1]); m_mc_exp.push_back(m_gamma[i]); } // Add node boundary to emax flux = m_prefactor[inx_emax] * gammalib::plaw_photon_flux(m_lin_nodes[inx_emax], e_max, m_epivot[inx_emax], m_gamma[inx_emax]); m_mc_cum.push_back(flux); m_mc_min.push_back(m_lin_nodes[inx_emax]); m_mc_max.push_back(e_max); m_mc_exp.push_back(m_gamma[inx_emax]); } // endelse: emin and emax not between same nodes // Build cumulative distribution for (int i = 1; i < m_mc_cum.size(); ++i) { m_mc_cum[i] += m_mc_cum[i-1]; } double norm = m_mc_cum[m_mc_cum.size()-1]; if (norm > 0.0) { for (int i = 0; i < m_mc_cum.size(); ++i) { m_mc_cum[i] /= norm; } } // Set MC values for (int i = 0; i < m_mc_cum.size(); ++i) { // Compute exponent double exponent = m_mc_exp[i] + 1.0; // Exponent dependend computation if (std::abs(exponent) > 1.0e-11) { // If the exponent is too small then use lower energy // boundary if (exponent < -50.0) { m_mc_exp[i] = 0.0; m_mc_min[i] = std::log(m_mc_min[i]); m_mc_max[i] = m_mc_min[i]; } // ... otherwise if exponent is too large then use // upper energy boundary else if (exponent > +50.0) { m_mc_exp[i] = 0.0; m_mc_min[i] = std::log(m_mc_max[i]); m_mc_max[i] = m_mc_min[i]; } // ... otherwise use transformation formula else { m_mc_exp[i] = exponent; m_mc_min[i] = std::pow(m_mc_min[i], exponent); m_mc_max[i] = std::pow(m_mc_max[i], exponent); } } else { m_mc_exp[i] = 0.0; m_mc_min[i] = std::log(m_mc_min[i]); m_mc_max[i] = std::log(m_mc_max[i]); } } // endfor: set MC values } // endif: e_max > e_min } // endif: Update was required // Return return; }