/*************************************************************************** * GModelTemporalPhaseCurve.cpp - Temporal phase curve model class * * ----------------------------------------------------------------------- * * copyright (C) 2017 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 GModelTemporalPhaseCurve.cpp * @brief Temporal phase curve model class interface implementation * @author Juergen Knoedlseder */ /* __ Includes ___________________________________________________________ */ #ifdef HAVE_CONFIG_H #include #endif #include #include "GTools.hpp" #include "GException.hpp" #include "GModelTemporalPhaseCurve.hpp" #include "GModelTemporalRegistry.hpp" #include "GFits.hpp" #include "GFitsTable.hpp" #include "GFitsTableCol.hpp" /* __ Constants __________________________________________________________ */ /* __ Globals ____________________________________________________________ */ const GModelTemporalPhaseCurve g_temporal_phase_seed; const GModelTemporalRegistry g_temporal_phase_registry(&g_temporal_phase_seed); /* __ Method name definitions ____________________________________________ */ #define G_READ "GModelTemporalPhaseCurve::read(GXmlElement&)" #define G_WRITE "GModelTemporalPhaseCurve::write(GXmlElement&)" #define G_LOAD_NODES "GModelTemporalPhaseCurve::load_nodes(GFilename&)" #define G_NORMALISE_NODES "GModelTemporalPhaseCurve::normalize_nodes()" /* __ Macros _____________________________________________________________ */ /* __ Coding definitions _________________________________________________ */ /* __ Debug definitions __________________________________________________ */ /*========================================================================== = = = Constructors/destructors = = = ==========================================================================*/ /***********************************************************************//** * @brief Void constructor ***************************************************************************/ GModelTemporalPhaseCurve::GModelTemporalPhaseCurve(void) : GModelTemporal() { // Initialise members init_members(); // Return return; } /***********************************************************************//** * @brief File constructor * * @param[in] filename File name of phase curve nodes. * @param[in] mjd Reference time. * @param[in] phase Phase at reference time. * @param[in] f0 Frequency at reference time (Hz). * @param[in] f1 First frequency derivative at reference time. * @param[in] f2 Second frequency derivative at reference time. * @param[in] norm Normalization factor. * @param[in] normalize Normalize phase curve? * * Constructs phase curve from a list of nodes that is found in the specified * FITS file, a reference time and phase information at the reference time. * See the load_nodes() method for more information about the expected * structure of the file. ***************************************************************************/ GModelTemporalPhaseCurve::GModelTemporalPhaseCurve(const GFilename& filename, const GTime& mjd, const double& phase, const double& f0, const double& f1, const double& f2, const double& norm, const bool& normalize) : GModelTemporal() { // Initialise members init_members(); // Set parameters this->mjd(mjd); this->phase(phase); this->f0(f0); this->f1(f1); this->f2(f2); this->norm(norm); // Set normalization flag m_normalize = normalize; // Load nodes load_nodes(filename); // Return return; } /***********************************************************************//** * @brief XML constructor * * @param[in] xml XML element. * * Constructs phase curve by extracting information from an XML element. See * the read() method for more information about the expected structure of the * XML element. ***************************************************************************/ GModelTemporalPhaseCurve::GModelTemporalPhaseCurve(const GXmlElement& xml) : GModelTemporal() { // Initialise members init_members(); // Read information from XML element read(xml); // Return return; } /***********************************************************************//** * @brief Copy constructor * * @param[in] model Phase curve. ***************************************************************************/ GModelTemporalPhaseCurve::GModelTemporalPhaseCurve(const GModelTemporalPhaseCurve& model) : GModelTemporal(model) { // Initialise members init_members(); // Copy members copy_members(model); // Return return; } /***********************************************************************//** * @brief Destructor ***************************************************************************/ GModelTemporalPhaseCurve::~GModelTemporalPhaseCurve(void) { // Free members free_members(); // Return return; } /*========================================================================== = = = Operators = = = ==========================================================================*/ /***********************************************************************//** * @brief Assignment operator * * @param[in] model Phase curve. * @return Phase curve. ***************************************************************************/ GModelTemporalPhaseCurve& GModelTemporalPhaseCurve::operator=(const GModelTemporalPhaseCurve& model) { // Execute only if object is not identical if (this != &model) { // Copy base class members this->GModelTemporal::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 phase curve ***************************************************************************/ void GModelTemporalPhaseCurve::clear(void) { // Free class members (base and derived classes, derived class first) free_members(); this->GModelTemporal::free_members(); // Initialise members this->GModelTemporal::init_members(); init_members(); // Return return; } /***********************************************************************//** * @brief Clone phase curve * * @return Pointer to deep copy of phase curve. ***************************************************************************/ GModelTemporalPhaseCurve* GModelTemporalPhaseCurve::clone(void) const { // Clone constant temporal model return new GModelTemporalPhaseCurve(*this); } /***********************************************************************//** * @brief Evaluate function * * @param[in] srcTime True photon arrival time. * @param[in] gradients Compute gradients? * @return Value of phase curve. * * Computes * * \f[ * S_{\rm t}(t) = r(\Phi(t)) \times {\tt m\_norm} * \f] * * where * * \f$r(\Phi(t))\f$ is the phase dependent rate, defined by linear * interpolation between the nodes in a FITS file, \f$\Phi(t)\f$ is the time * dependent phase, and \f${\tt m\_norm}\f$ is a normalisation constant. ***************************************************************************/ double GModelTemporalPhaseCurve::eval(const GTime& srcTime, const bool& gradients) const { // Get phase double phase = this->phase(srcTime); // Interpolate phase curve nodes double func = m_nodes.interpolate(phase, m_values); // Compute function value double value = m_norm.value() * func; // Optionally compute gradients if (gradients) { // Compute partial derivatives of the parameter values double g_norm = (m_norm.is_free()) ? m_norm.scale() * func : 0.0; // Set gradients m_norm.factor_gradient(g_norm); } // endif: gradient computation was requested // Return return value; } /***********************************************************************//** * @brief Return vector of random event times * * @param[in] rate Mean event rate (events per second). * @param[in] tmin Minimum event time. * @param[in] tmax Maximum event time. * @param[in,out] ran Random number generator. * * Returns a vector of random event times between @p tmin and @p tmax for * the phase curve. ***************************************************************************/ GTimes GModelTemporalPhaseCurve::mc(const double& rate, const GTime& tmin, const GTime& tmax, GRan& ran) const { // Allocates empty vector of times GTimes times; // Compute event rate (in events per seconds) double lambda = rate * norm() * m_scale; // Initialise start and stop times in seconds double time = tmin.secs(); double tstop = tmax.secs(); // Generate events until maximum event time is exceeded while (time <= tstop) { // Simulate next event time time += ran.exp(lambda); // Break if time is beyond the stop time if (time > tstop) { break; } // Set time object GTime srcTime; srcTime.secs(time); // Get value for current time double value = eval(srcTime); // Get uniform random number double uniform = ran.uniform() * m_scale; // If the random number is not larger than the phase curve value // then accept the time if (uniform <= value) { times.append(srcTime); } } // endwhile: loop until stop time is reached // Return vector of times return times; } /***********************************************************************//** * @brief Read model from XML element * * @param[in] xml XML element. * * Reads the temporal information from an XML element. The XML element should * have the format * * * * * * * * * ***************************************************************************/ void GModelTemporalPhaseCurve::read(const GXmlElement& xml) { // Get parameter pointers const GXmlElement* norm = gammalib::xml_get_par(G_READ, xml, m_norm.name()); const GXmlElement* mjd = gammalib::xml_get_par(G_READ, xml, m_mjd.name()); const GXmlElement* phase = gammalib::xml_get_par(G_READ, xml, m_phase.name()); const GXmlElement* f0 = gammalib::xml_get_par(G_READ, xml, m_f0.name()); const GXmlElement* f1 = gammalib::xml_get_par(G_READ, xml, m_f1.name()); const GXmlElement* f2 = gammalib::xml_get_par(G_READ, xml, m_f2.name()); // Read parameters m_norm.read(*norm); m_mjd.read(*mjd); m_phase.read(*phase); m_f0.read(*f0); m_f1.read(*f1); m_f2.read(*f2); // Get optional normalization attribute m_normalize = true; m_has_normalize = false; if (xml.has_attribute("normalize")) { m_has_normalize = true; std::string arg = xml.attribute("normalize"); if (arg == "0" || gammalib::tolower(arg) == "false") { m_normalize = false; } } // Load nodes from file load_nodes(gammalib::xml_file_expand(xml, xml.attribute("file"))); // Return return; } /***********************************************************************//** * @brief Write model into XML element * * @param[in] xml XML element. * * @exception GExpection::invalid_value * Invalid XML format encountered. * * Writes the temporal information into an XML element. The XML element will * have the format * * * * * * * * * ***************************************************************************/ void GModelTemporalPhaseCurve::write(GXmlElement& xml) const { // Set model type if (xml.attribute("type") == "") { xml.attribute("type", type()); } // Verify model type if (xml.attribute("type") != type()) { std::string msg = "Temporal model of type "+xml.attribute("type")+ " encountered while \""+type()+"\" was expected."; throw GException::invalid_value(G_WRITE, msg); } // Get XML parameters GXmlElement* norm = gammalib::xml_need_par(G_WRITE, xml, m_norm.name()); GXmlElement* mjd = gammalib::xml_need_par(G_WRITE, xml, m_mjd.name()); GXmlElement* phase = gammalib::xml_need_par(G_WRITE, xml, m_phase.name()); GXmlElement* f0 = gammalib::xml_need_par(G_WRITE, xml, m_f0.name()); GXmlElement* f1 = gammalib::xml_need_par(G_WRITE, xml, m_f1.name()); GXmlElement* f2 = gammalib::xml_need_par(G_WRITE, xml, m_f2.name()); // Write parameters m_norm.write(*norm); m_mjd.write(*mjd); m_phase.write(*phase); m_f0.write(*f0); m_f1.write(*f1); m_f2.write(*f2); // Set file attribute xml.attribute("file", gammalib::xml_file_reduce(xml, m_filename)); // Set optional normalization attribute if (m_has_normalize || !m_normalize) { if (m_normalize) { xml.attribute("normalize", "1"); } else { xml.attribute("normalize", "0"); } } // Return return; } /***********************************************************************//** * @brief Computes the phase for a given time * * @param[in] time Time. * * Computes * * \f[ * \Phi(t) = \Phi_0 + f(t-t_0) + \frac{1}{2}\dot{f} (t-t_0)^2 + * \frac{1}{6}\ddot{f} (t-t_0)^3 * \f] * * where * \f$t_0\f$ is a reference time, * \f$\Phi_0\f$ is the phase at the reference time, * \f$f\f$ is the variation frequency at the reference time, * \f$\dot{f}\f$ is the first derivative of the variation frequency at the * reference time, and * \f$\dot{f}\f$ is the second derivative of the variation frequency at the * reference time. * * The phase \f$\Phi(t)\f$ is in the interval [0,1]. ***************************************************************************/ double GModelTemporalPhaseCurve::phase(const GTime& time) const { // Set constants const double c1 = 0.5; const double c2 = 1.0 / 6.0; // Compute time since reference time in seconds double t = time - mjd(); // Computes phase double phase = this->phase() + t * (f0() + t * (c1 * f1() + c2 * f2() * t)); // Put phase into interval [0,1] phase -= floor(phase); // Return phase return phase; } /***********************************************************************//** * @brief Evaluate phase curve value for a given phase * * @param[in] phase Phase. * @return Value of phase curve. * * Computes * * \f[ * S_{\rm t}(\Phi) = r(\Phi) \times {\tt m\_norm} * \f] * * where * * \f$r(\Phi)\f$ is the phase dependent rate, defined by linear interpolation * between the nodes in a FITS file, \f$\Phi\f$ is the phase, and * \f${\tt m\_norm}\f$ is a normalisation constant. ***************************************************************************/ double GModelTemporalPhaseCurve::value(const double& phase) const { // Compute function value double value = m_norm.value() * m_nodes.interpolate(phase, m_values); // Return return value; } /***********************************************************************//** * @brief Print phase curve information * * @param[in] chatter Chattiness. * @return String containing phase curve information. ***************************************************************************/ std::string GModelTemporalPhaseCurve::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("=== GModelTemporalPhaseCurve ==="); // Append information result.append("\n"+gammalib::parformat("Function file")); result.append(m_filename.url()); if (normalize()) { result.append(" [normalized]"); } result.append("\n"+gammalib::parformat("Number of parameters")); result.append(gammalib::str(size())); for (int i = 0; i < size(); ++i) { result.append("\n"+m_pars[i]->print(chatter)); } result.append("\n"+gammalib::parformat("Number of phase nodes")); result.append(gammalib::str(m_nodes.size())); // EXPLICIT: Append node values if (chatter >= EXPLICIT) { for (int i = 0; i < m_nodes.size(); ++i) { result.append("\n"); result.append(gammalib::parformat(" Phase "+gammalib::str(m_nodes[i]))); result.append(gammalib::str(m_values[i])); } } } // endif: chatter was not silent // Return result return result; } /*========================================================================== = = = Private methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Initialise class members ***************************************************************************/ void GModelTemporalPhaseCurve::init_members(void) { // Initialise normalisation parameter m_norm.clear(); m_norm.name("Normalization"); m_norm.unit("(relative value)"); m_norm.scale(1.0); m_norm.value(1.0); m_norm.range(0.0,1000.0); m_norm.fix(); m_norm.gradient(0.0); m_norm.has_grad(true); // Initialise reference MJD parameter m_mjd.clear(); m_mjd.name("MJD"); m_mjd.unit("days"); m_mjd.scale(1.0); m_mjd.value(51544.5); m_mjd.range(1.0e-10,1.0e+10); m_mjd.fix(); m_mjd.gradient(0.0); m_mjd.has_grad(false); // Initialise phase at reference MJD parameter m_phase.clear(); m_phase.name("Phase"); m_phase.unit(""); m_phase.scale(1.0); m_phase.value(0.0); m_phase.range(-1.0,1.0); m_phase.free(); m_phase.gradient(0.0); m_phase.has_grad(false); // Initialise frequency at reference MJD parameter m_f0.clear(); m_f0.name("F0"); m_f0.unit("Hz"); m_f0.scale(1.0); m_f0.value(1.0); m_f0.range(0.0,1000.0); m_f0.free(); m_f0.gradient(0.0); m_f0.has_grad(false); // Initialise first frequency derivative at reference MJD parameter m_f1.clear(); m_f1.name("F1"); m_f1.unit("s^-2"); m_f1.scale(1.0); m_f1.value(0.1); m_f1.range(-1000.0,1000.0); m_f1.free(); m_f1.gradient(0.0); m_f1.has_grad(false); // Initialise second frequency derivative at reference MJD parameter m_f2.clear(); m_f2.name("F2"); m_f2.unit("s^-3"); m_f2.scale(1.0); m_f2.value(0.01); m_f2.range(-1000.0,1000.0); m_f2.free(); m_f2.gradient(0.0); m_f2.has_grad(false); // Set parameter pointer(s) m_pars.clear(); m_pars.push_back(&m_norm); m_pars.push_back(&m_mjd); m_pars.push_back(&m_phase); m_pars.push_back(&m_f0); m_pars.push_back(&m_f1); m_pars.push_back(&m_f2); // Initialise members m_nodes.clear(); m_values.clear(); m_filename.clear(); m_scale = 1.0; m_normalize = true; m_has_normalize = false; // Return return; } /***********************************************************************//** * @brief Copy class members * * @param[in] model Phase curve. ***************************************************************************/ void GModelTemporalPhaseCurve::copy_members(const GModelTemporalPhaseCurve& model) { // Copy members m_norm = model.m_norm; m_mjd = model.m_mjd; m_phase = model.m_phase; m_f0 = model.m_f0; m_f1 = model.m_f1; m_f2 = model.m_f2; m_nodes = model.m_nodes; m_values = model.m_values; m_filename = model.m_filename; m_scale = model.m_scale; m_normalize = model.m_normalize; m_has_normalize = model.m_has_normalize; // Set parameter pointer(s) m_pars.clear(); m_pars.push_back(&m_norm); m_pars.push_back(&m_mjd); m_pars.push_back(&m_phase); m_pars.push_back(&m_f0); m_pars.push_back(&m_f1); m_pars.push_back(&m_f2); // Return return; } /***********************************************************************//** * @brief Delete class members ***************************************************************************/ void GModelTemporalPhaseCurve::free_members(void) { // Return return; } /***********************************************************************//** * @brief Load nodes from file * * @param[in] filename File name. * * @exception GException::invalid_value * Phase curve FITS file is invalid * * Load the phase curve nodes from a FITS file. The information is expected * in the first extension of the FITS file, containing two mandatory columns * with names "PHASE" and "NORM". ***************************************************************************/ void GModelTemporalPhaseCurve::load_nodes(const GFilename& filename) { // Set maximum phase curve normalization value, including a small margin const double max_norm = 1.0 + 1.0e-8; // Clear nodes and values m_nodes.clear(); m_values.clear(); // Set filename m_filename = filename; // Load FITS file GFits fits = GFits(filename); // Extract binary table (so far always load extension 1 as table) GFitsTable* table = fits.table(1); // Extract columns GFitsTableCol* phase_col = (*table)["PHASE"]; GFitsTableCol* norm_col = (*table)["NORM"]; // Check that there are at least two nodes in table if (phase_col->nrows() < 2) { std::string msg = "\"PHASE\" column contains "+ gammalib::str(phase_col->nrows())+" rows but at " "least two rows are required. Please specify a valid " "phase curve file."; throw GException::invalid_value(G_LOAD_NODES, msg); } // Check that both columns are consistent if (phase_col->nrows() != norm_col->nrows()) { std::string msg = "\"PHASE\" and \"NORM\" columns have inconsistent " "number of rows ("+ gammalib::str(phase_col->nrows())+", "+ gammalib::str(norm_col->nrows())+"). Please " "specify a valid phase curve file."; throw GException::invalid_value(G_LOAD_NODES, msg); } // Set number of nodes int nodes = phase_col->nrows(); // Check that phase values are in ascending order and comprised between // 0 and 1, and that no node is larger than 1 double last_phase = -1.0; for (int i = 0; i < nodes; ++i) { // Check if phase has increased if (last_phase >= 0.0 && phase_col->real(i) <= last_phase) { std::string msg = "Phase value "+gammalib::str(phase_col->real(i))+ " in row "+gammalib::str(i+1)+" of \"PHASE\" " "column is equal to or smaller than preceeding " "value. Please provide a phase curve file with " "monotonically increasing phase values."; throw GException::invalid_value(G_LOAD_NODES, msg); } // Check if phase is within [0,1] if (phase_col->real(i) < 0.0 || phase_col->real(i) > 1.0) { std::string msg = "Phase value "+gammalib::str(phase_col->real(i))+ " outside range [0,1]. Please provide a phase " "curve file with phase values comprised in the " "interval [0,1]."; throw GException::invalid_value(G_LOAD_NODES, msg); } // Check if value is smaller than maximum allowed normalisation if (norm_col->real(i) > max_norm) { std::string msg = "Value "+gammalib::str(norm_col->real(i))+" at " "phase "+gammalib::str(phase_col->real(i))+" is " "larger than 1. Please provide a phase curve file " "with normalizations not exceeding 1."; throw GException::invalid_value(G_LOAD_NODES, msg); } } // endfor: looped over nodes // If first phase is larger than zero then add last node with phase-1 as // first node. This makes sure that a valid normalization exists for // phase=0. if (phase_col->real(0) > 0.0) { m_nodes.append(phase_col->real(nodes-1)-1.0); m_values.push_back(norm_col->real(nodes-1)); } // Extract nodes for (int i = 0; i < nodes; ++i) { m_nodes.append(phase_col->real(i)); m_values.push_back(norm_col->real(i)); } // If last node is smaller than one then add first node with phase+1 as // last node. This makes sure that a valid normalization exists for // phase=1. if (phase_col->real(nodes-1) < 1.0) { m_nodes.append(phase_col->real(0)+1.0); m_values.push_back(norm_col->real(0)); } // Make sure that no node exceeds 1 for (int i = 0; i < m_values.size(); ++i) { if (m_values[i] > 1.0) { m_values[i] = 1.0; } } // Close FITS file fits.close(); // Optionally normalize nodes if (m_normalize) { normalize_nodes(); } // Return return; } /***********************************************************************//** * @brief Normalise nodes * * @exception GException::invalid_value * Phase curve FITS file is invalid * * Normalise the node values so that the integral over the phase interval * [0,1] gives an average normalisation of 1. ***************************************************************************/ void GModelTemporalPhaseCurve::normalize_nodes(void) { // Initialise node integral double sum = 0.0; // Loop over all nodes-1 for (int i = 0; i < m_nodes.size()-1; ++i) { // Compute phase values double a = (m_nodes[i] < 0.0) ? 0.0 : m_nodes[i]; double b = (m_nodes[i+1] > 1.0) ? 1.0 : m_nodes[i+1]; // Compute normalisation at phase values double fa = m_nodes.interpolate(a, m_values); double fb = m_nodes.interpolate(b, m_values); // Compute intergal for this interval using the Trapezoid rule sum += 0.5 * (b - a) * (fa + fb); } // Throw an exception if the integral is not positive if (sum <= 0.0) { std::string msg = "Integral over phase curve is not positive ("+ gammalib::str(sum)+"). Please provide a valid phase " "curve file."; throw GException::invalid_value(G_NORMALISE_NODES, msg); } // Set scale factor m_scale = 1.0 / sum; // Normalise the node values for (int i = 0; i < m_values.size(); ++i) { m_values[i] *= m_scale; } // Return return; }