/*************************************************************************** * GCTAModelRadialPolynom.cpp - Radial Polynom CTA model class * * ----------------------------------------------------------------------- * * copyright (C) 2011-2016 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 GCTAModelRadialPolynom.cpp * @brief Radial Polynom model class implementation * @author Juergen Knoedlseder */ /* __ Includes ___________________________________________________________ */ #ifdef HAVE_CONFIG_H #include #endif #include #include "GException.hpp" #include "GTools.hpp" #include "GMath.hpp" #include "GIntegral.hpp" #include "GCTAModelRadialPolynom.hpp" #include "GCTAModelRadialRegistry.hpp" /* __ Constants __________________________________________________________ */ const double g_cta_radial_polynom_offset_max = 4.0; /* __ Globals ____________________________________________________________ */ const GCTAModelRadialPolynom g_cta_radial_polynom_seed; const GCTAModelRadialRegistry g_cta_radial_polynom_registry(&g_cta_radial_polynom_seed); /* __ Method name definitions ____________________________________________ */ #define G_READ "GCTAModelRadialPolynom::read(GXmlElement&)" #define G_WRITE "GCTAModelRadialPolynom::write(GXmlElement&)" /* __ Macros _____________________________________________________________ */ /* __ Coding definitions _________________________________________________ */ //#define G_DEBUG_MC //!< Debug MC method /* __ Debug definitions __________________________________________________ */ /*========================================================================== = = = Constructors/destructors = = = ==========================================================================*/ /***********************************************************************//** * @brief Void constructor ***************************************************************************/ GCTAModelRadialPolynom::GCTAModelRadialPolynom(void) : GCTAModelRadial() { // Initialise members init_members(); // Return return; } /***********************************************************************//** * @brief Constructor * * @param[in] coeffs Vector of polynomial coefficients. ***************************************************************************/ GCTAModelRadialPolynom::GCTAModelRadialPolynom(const std::vector& coeffs) : GCTAModelRadial() { // Initialise members init_members(); // Assign coefficients for (int i = 0; i < coeffs.size(); ++i) { // Allocate parameter GModelPar par; // Set value par.value(coeffs[i]); // Set other attributes std::string name = "coeff" + gammalib::str(i); par.name(name); par.unit(""); par.free(); par.scale(1.0); par.gradient(0.0); par.has_grad(true); // Push coefficient on list m_coeffs.push_back(par); } // endfor: looped over coefficients // Update parameter mapping update_pars(); // Return return; } /***********************************************************************//** * @brief XML constructor * * @param[in] xml XML element. * * Creates instance of a radial Polynom model by extracting information * from an XML element. See GCTAModelRadialPolynom::read() for more * information about the expected structure of the XML element. ***************************************************************************/ GCTAModelRadialPolynom::GCTAModelRadialPolynom(const GXmlElement& xml) : GCTAModelRadial() { // Initialise members init_members(); // Read information from XML element read(xml); // Return return; } /***********************************************************************//** * @brief Copy constructor * * @param[in] model Radial Polynom model. ***************************************************************************/ GCTAModelRadialPolynom::GCTAModelRadialPolynom(const GCTAModelRadialPolynom& model) : GCTAModelRadial(model) { // Initialise members init_members(); // Copy members copy_members(model); // Return return; } /***********************************************************************//** * @brief Destructor ***************************************************************************/ GCTAModelRadialPolynom::~GCTAModelRadialPolynom(void) { // Free members free_members(); // Return return; } /*========================================================================== = = = Operators = = = ==========================================================================*/ /***********************************************************************//** * @brief Assignment operator * * @param[in] model Radial Polynom model. ***************************************************************************/ GCTAModelRadialPolynom& GCTAModelRadialPolynom::operator=(const GCTAModelRadialPolynom& model) { // Execute only if object is not identical if (this != &model) { // Copy base class members this->GCTAModelRadial::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 instance ***************************************************************************/ void GCTAModelRadialPolynom::clear(void) { // Free class members (base and derived classes, derived class first) free_members(); this->GCTAModelRadial::free_members(); // Initialise members this->GCTAModelRadial::init_members(); init_members(); // Return return; } /***********************************************************************//** * @brief Clone instance ***************************************************************************/ GCTAModelRadialPolynom* GCTAModelRadialPolynom::clone(void) const { return new GCTAModelRadialPolynom(*this); } /***********************************************************************//** * @brief Evaluate function * * @param[in] offset Offset angle [degrees]. * @param[in] gradients Compute gradients? * @return Function value * * Evaluates the radial polynomial model for a given offset. The model is * defined as * \f[f(\theta) = \sum_{i=0}^m c_i \theta^i\f] * where * \f$\theta\f$ is the offset angle (in degrees), and * \f$c_i\f$ are the polynomial coefficients. * * If the @p gradients flag is true the method will also compute the partial * derivatives of the parameters. The partial derivative of the Polynom are * given by * \f[\frac{df}{d{c_i}_v} = {c_i}_s \theta^i\f] * where * \f${c_i}_v\f$ is the value part, * \f${c_i}_s\f$ is the scaling part, and * \f$c_i = {c_i}_v {c_i}_s\f$. ***************************************************************************/ double GCTAModelRadialPolynom::eval(const double& offset, const bool& gradients) const { // Initialise result double value = 0.0; // Determine polynomial degree int ncoeffs = m_coeffs.size(); // Compute value and gradients (only if there are coefficients) if (ncoeffs > 0) { // Compute value value = m_coeffs[ncoeffs-1].value(); for (int i = ncoeffs-2; i >= 0; i--) { value = value * offset + m_coeffs[i].value(); } // Optionally compute partial derivatives if (gradients) { // Initialise theta^i for the first coefficient double offset_power = 1.0; // Compute gradients for all coefficients for (int i = 0; i < ncoeffs; ++i) { // Compute gradient double grad = offset_power * m_coeffs[i].scale(); // Store gradient m_coeffs[i].factor_gradient(grad); // Increase offset power for next coefficient offset_power *= offset; } // endfor: looped over all coefficients } // endif: computed partial derivatives } // endif: there were coefficients // Compile option: Check for NaN/Inf #if defined(G_NAN_CHECK) if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) { std::cout << "*** ERROR: GCTAModelRadialPolynom::eval"; std::cout << "(offset=" << offset << "): NaN/Inf encountered"; std::cout << " (value=" << value; for (int i = 0; i < ncoeffs; ++i) { std::cout << ", c" << i << "=" << m_coeffs[i].value(); } std::cout << ")" << std::endl; } #endif // Return value return value; } /***********************************************************************//** * @brief Returns MC instrument direction * * @param[in] dir Pointing direction. * @param[in] ran Random number generator. * * Draws an arbitrary CTA instrument position from * \f[f(\theta) = \sin(\theta) \left( \sum_{i=0}^m c_i \theta^i \right)\f] * where * \f$\theta\f$ is the offset angle (in degrees), and * \f$c_i\f$ are the polynomial coefficients, * using the rejection method. * * Note that the maximum offset angle is fixed by the constant * g_cta_radial_polynom_offset_max. * This needs eventually adjusting on real data. The main reason for the * tight limit is to avoid divergence at large offset angles. * * @todo This method actually assumes that the polynom is always < 1, which * may not be necessarily the case. Ideally, the method should * determine the maximum of the polynomial to throw events. This is * a severe limitation and should rapidly be corrected. ***************************************************************************/ GCTAInstDir GCTAModelRadialPolynom::mc(const GCTAInstDir& dir, GRan& ran) const { // Set constants const double u_max = sin(g_cta_radial_polynom_offset_max * gammalib::deg2rad); // Debug option: initialise number if samples thrown for one value #if defined(G_DEBUG_MC) int n_samples = 0; #endif // Simulate offset from photon arrival direction until we're not // rejected anymore double value = 0.0; double u = 1.0; double offset = 0.0; do { // Throw offset angle offset = ran.uniform() * g_cta_radial_polynom_offset_max; // Compute function value at this offset angle value = sin(offset * gammalib::deg2rad) * eval(offset); // Throw value for rejection method u = ran.uniform() * u_max; // Debug option: update number of samples #if defined(G_DEBUG_MC) n_samples++; #endif } while (u > value); // Debug option: print number if samples thrown for one value #if defined(G_DEBUG_MC) std::cout << "#=" << n_samples << " "; #endif // Simulate azimuth angle double phi = 360.0 * ran.uniform(); // Rotate pointing direction by offset and azimuth angle GCTAInstDir mc_dir = dir; mc_dir.dir().rotate_deg(phi, offset); // Compute DETX and DETY coordinates double detx(0.0); double dety(0.0); if (offset > 0.0 ) { detx = offset*gammalib::deg2rad * std::cos(phi*gammalib::deg2rad); dety = offset*gammalib::deg2rad * std::sin(phi*gammalib::deg2rad); } mc_dir.detx(detx); mc_dir.dety(dety); // Return MC direction return mc_dir; } /***********************************************************************//** * @brief Returns integral over radial model (in steradians) * * Computes * \f[\Omega = 2 \pi \int_0^{\pi} \sin \theta f(\theta) d\theta\f] * where * \f[f(\theta) = \sum_{i=0}^m c_i \theta^i\f] * \f$\theta\f$ is the offset angle (in degrees), and * \f$c_i\f$ are the polynomial coefficients. * * The integration is performed numerically, and the upper integration bound * \f$\pi\f$ * is fixed to < g_cta_radial_polynom_offset_max. * This needs eventually adjusting on real data. The main reason for the * tight limit is to avoid divergence at large offset angles. ***************************************************************************/ double GCTAModelRadialPolynom::omega(void) const { // Set constants const double offset_max_rad = g_cta_radial_polynom_offset_max * gammalib::deg2rad; // Allocate integrand GCTAModelRadialPolynom::integrand integrand(this); // Allocate intergal GIntegral integral(&integrand); // Perform numerical integration double omega = integral.romberg(0.0, offset_max_rad) * gammalib::twopi; // Return integral return omega; } /***********************************************************************//** * @brief Read model from XML element * * @param[in] xml XML element. * * @exception GException::model_invalid_parnum * Invalid number of model parameters found in XML element. * @exception GException::model_invalid_parnames * Invalid model parameter names found in XML element. * * Read the radial Polynom model information from an XML element. The XML * element is required to have at least one parameter. Parameters are named * "CoeffX", where "X" starts from "0". * * @todo Implement a test of the coefficient boundaries. ***************************************************************************/ void GCTAModelRadialPolynom::read(const GXmlElement& xml) { // Free space for coefficients m_coeffs.clear(); // Get maximum number of coefficients from XML file int max_coeffs = xml.elements("parameter"); // Throw an error if no parameters were found if (max_coeffs < 1) { std::string message = "Radial polynomial model requires at least" " one coefficient."; throw GException::model_invalid_parnum(G_READ, xml, message); } // Verify XML file consistency and determine number of coefficients int ncoeffs = 0; std::vector npar; npar.assign(max_coeffs, 0); for (int i = 0; i < max_coeffs; ++i) { // Get parameter element const GXmlElement* par = xml.element("parameter", i); // Verify that parameter is indeed a coefficient if (par->attribute("name").compare(0,5,"Coeff") != 0) { throw GException::model_invalid_parnames(G_READ, xml, par->attribute("name")); } // Verify that parameter coefficient has index in valid range int index = -1; size_t nchars = par->attribute("name").length() - 5; if (nchars > 0) { index = gammalib::toint(par->attribute("name").substr(5, nchars)); } else { throw GException::model_invalid_parnames(G_READ, xml, "Radial polynomial \"Coeff\" parameter has no index."); } if (index < 0) { throw GException::model_invalid_parnames(G_READ, xml, "Radial polynomial \"Coeff\" parameter index < 0."); } if (index >= max_coeffs) { std::string message = "There are "+gammalib::str(max_coeffs)+" parameters," " hence polynomial coefficients are expected" " to run from 0 to "+gammalib::str(max_coeffs-1)+", yet" " a coefficient with index "+gammalib::str(index)+" was" " encountered."; throw GException::model_invalid_parnames(G_READ, xml, message); } // Increment parameter counter npar[index]++; // Update number of coefficients if (index+1 > ncoeffs) { ncoeffs = index + 1; } } // endfor: verified XML file consistency // Verify that the number of coefficients is between 1 and max_coeffs if (ncoeffs < 0 || ncoeffs > max_coeffs) { std::string message = "Radial polynomial model requires at between" " 1 and "+gammalib::str(max_coeffs)+" parameters."; throw GException::model_invalid_parnum(G_READ, xml, message); } // Verify that all parameters were found for (int i = 0; i < ncoeffs; ++i) { if (npar[i] == 0) { std::string message = "Parameter \"Coeff"+gammalib::str(i)+"\" required," " but not found in XML file."; throw GException::model_invalid_parnames(G_READ, xml, message); } else if (npar[i] > 1) { std::string message = "Multiple parameters \"Coeff"+gammalib::str(i)+"\"" " found in XML file."; throw GException::model_invalid_parnames(G_READ, xml, message); } } // Finally get all coefficients in order for (int i = 0; i < ncoeffs; ++i) { // Set parameter name std::string name = "Coeff"+gammalib::str(i); // Get corresponding parameter element const GXmlElement* par = NULL; for (int k = 0; k < ncoeffs; ++k) { const GXmlElement* element = xml.element("parameter", k); if (element->attribute("name") == name) { par = element; break; } } // Make sure that we really have one (just a double check, this should // never fail) if (par == NULL) { std::string message = "Required parameter \""+name+"\" not found."; throw GException::model_invalid_parnames(G_READ, xml, message); } // Now read that parameter ... GModelPar coeff; coeff.read(*par); // ... set other attributes ... coeff.name(name); coeff.unit(""); //TODO: Check parameter // ... and push it on the list m_coeffs.push_back(coeff); } // endfor: looped over all parameters // Update parameter mapping update_pars(); // Return return; } /***********************************************************************//** * @brief Write model into XML element * * @param[in] xml XML element. * * @exception GException::model_invalid_spatial * Existing XML element is not of type 'Polynom' * @exception GException::model_invalid_parnum * Invalid number of model parameters found in XML element. * @exception GException::model_invalid_parnames * Invalid model parameter names found in XML element. * * Write the polynomial radial model information into an XML element. The XML * element will have 1-10 parameter leafs named "CoeffX", where X runs from * 0 to 9. ***************************************************************************/ void GCTAModelRadialPolynom::write(GXmlElement& xml) const { // Determine number of coefficients int ncoeffs = m_coeffs.size(); // Set model type if (xml.attribute("type") == "") { xml.attribute("type", type()); } // Verify model type if (xml.attribute("type") != type()) { throw GException::model_invalid_spatial(G_WRITE, xml.attribute("type"), "Radial polynomial model is not of type \""+type()+"\"."); } // If XML element has 0 nodes then append nodes for coefficients if (xml.elements() == 0) { for (int i = 0; i < ncoeffs; ++i) { std::string name = "parameter name=\"Coeff"+gammalib::str(i)+"\""; xml.append(GXmlElement(name)); } } // Verify that XML element has one parameter per coefficient if (xml.elements() != ncoeffs || xml.elements("parameter") != ncoeffs) { std::string message = "Radial polynomial model requires exactly " + gammalib::str(ncoeffs) + " parameters."; throw GException::model_invalid_parnum(G_WRITE, xml, message); } // Set or update model parameter attributes std::vector npar; npar.assign(ncoeffs, 0); for (int i = 0; i < ncoeffs; ++i) { // Set parameter name std::string name = "Coeff"+gammalib::str(i); // Get corresponding parameter element GXmlElement* par = NULL; for (int k = 0; k < ncoeffs; ++k) { GXmlElement* element = xml.element("parameter", k); if (element->attribute("name") == name) { par = element; break; } } // Make sure that we really have one (just a double check, this should // never fail) if (par == NULL) { std::string message = "Required parameter \""+name+"\" not found."; throw GException::model_invalid_parnames(G_WRITE, xml, message); } // Write parameter m_coeffs[i].write(*par); // Increment parameter counter npar[i]++; } // endfor: looped over all parameters // Verify that all parameters were present for (int i = 0; i < ncoeffs; ++i) { if (npar[i] != 1) { std::string message = "Parameter \"Coeff"+gammalib::str(i)+"\" required," " but not found in XML file."; throw GException::model_invalid_parnames(G_WRITE, xml, message); } } // Return return; } /***********************************************************************//** * @brief Print point source information * * @param[in] chatter Chattiness (defaults to NORMAL). * @return String containing point source information. ***************************************************************************/ std::string GCTAModelRadialPolynom::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("=== GCTAModelRadialPolynom ==="); // Append information result.append("\n"+gammalib::parformat("Number of parameters") + gammalib::str(size())); for (int i = 0; i < size(); ++i) { result.append("\n"+m_pars[i]->print(chatter)); } } // endif: chatter was not silent // Return result return result; } /*========================================================================== = = = Private methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Initialise class members ***************************************************************************/ void GCTAModelRadialPolynom::init_members(void) { // Initialise polynomial coefficients m_coeffs.clear(); // Update parameter mapping update_pars(); // Return return; } /***********************************************************************//** * @brief Copy class members * * @param[in] model Radial Polynomian model. ***************************************************************************/ void GCTAModelRadialPolynom::copy_members(const GCTAModelRadialPolynom& model) { // Copy members m_coeffs = model.m_coeffs; // Update parameter mapping update_pars(); // Return return; } /***********************************************************************//** * @brief Delete class members ***************************************************************************/ void GCTAModelRadialPolynom::free_members(void) { // Return return; } /***********************************************************************//** * @brief Update parameter mapping ***************************************************************************/ void GCTAModelRadialPolynom::update_pars(void) { // Clear parameter pointer(s) m_pars.clear(); // Get number of coefficients int ncoeffs = m_coeffs.size(); // Set parameter pointers for all coefficients for (int i = 0; i < ncoeffs; ++i) { // Signal that we have gradients m_coeffs[i].has_grad(true); // Set pointer m_pars.push_back(&(m_coeffs[i])); } // endfor: looped over coefficients // Return return; } /*========================================================================== = = = Friends = = = ==========================================================================*/