/***************************************************************************
* GCTAModelRadialGauss.cpp - Radial Gaussian 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 GCTAModelRadialGauss.cpp
* @brief Radial Gaussian 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 "GCTAModelRadialGauss.hpp"
#include "GCTAModelRadialRegistry.hpp"
/* __ Constants __________________________________________________________ */
/* __ Globals ____________________________________________________________ */
const GCTAModelRadialGauss g_cta_radial_gauss_seed;
const GCTAModelRadialRegistry g_cta_radial_gauss_registry(&g_cta_radial_gauss_seed);
/* __ Method name definitions ____________________________________________ */
#define G_READ "GCTAModelRadialGauss::read(GXmlElement&)"
#define G_WRITE "GCTAModelRadialGauss::write(GXmlElement&)"
/* __ Macros _____________________________________________________________ */
/* __ Coding definitions _________________________________________________ */
//#define G_DEBUG_MC //!< Debug MC method
/* __ Debug definitions __________________________________________________ */
/*==========================================================================
= =
= Constructors/destructors =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Void constructor
***************************************************************************/
GCTAModelRadialGauss::GCTAModelRadialGauss(void) : GCTAModelRadial()
{
// Initialise members
init_members();
// Return
return;
}
/***********************************************************************//**
* @brief Constructor
*
* @param[in] sigma Gaussian width (degrees\f$^2\f$).
***************************************************************************/
GCTAModelRadialGauss::GCTAModelRadialGauss(const double& sigma) : GCTAModelRadial()
{
// Initialise members
init_members();
// Assign sigma
this->sigma(sigma);
// Return
return;
}
/***********************************************************************//**
* @brief XML constructor
*
* @param[in] xml XML element.
*
* Creates instance of a radial Gaussian model by extracting information
* from an XML element. See GCTAModelRadialGauss::read() for more information
* about the expected structure of the XML element.
***************************************************************************/
GCTAModelRadialGauss::GCTAModelRadialGauss(const GXmlElement& xml) : GCTAModelRadial()
{
// Initialise members
init_members();
// Read information from XML element
read(xml);
// Return
return;
}
/***********************************************************************//**
* @brief Copy constructor
*
* @param[in] model Radial Gaussian model.
***************************************************************************/
GCTAModelRadialGauss::GCTAModelRadialGauss(const GCTAModelRadialGauss& model) :
GCTAModelRadial(model)
{
// Initialise members
init_members();
// Copy members
copy_members(model);
// Return
return;
}
/***********************************************************************//**
* @brief Destructor
***************************************************************************/
GCTAModelRadialGauss::~GCTAModelRadialGauss(void)
{
// Free members
free_members();
// Return
return;
}
/*==========================================================================
= =
= Operators =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Assignment operator
*
* @param[in] model Radial Gaussian model.
***************************************************************************/
GCTAModelRadialGauss& GCTAModelRadialGauss::operator=(const GCTAModelRadialGauss& 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 GCTAModelRadialGauss::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
***************************************************************************/
GCTAModelRadialGauss* GCTAModelRadialGauss::clone(void) const
{
return new GCTAModelRadialGauss(*this);
}
/***********************************************************************//**
* @brief Evaluate function
*
* @param[in] offset Offset angle [degrees].
* @param[in] gradients Compute gradients?
* @return Function value
*
* Evaluates the Gaussian model for a given offset. The Gaussian model is
* defined as
* \f[f(\theta) = \exp \left(-\frac{1}{2}
* \left( \frac{\theta^2}{\sigma} \right)^2 \right)\f]
* where
* \f$\theta\f$ is the offset angle (in degrees), and
* \f$\sigma\f$ is the Gaussian width (in degrees\f$^2\f$).
*
* If the @p gradients flag is true the method will also compute the partial
* derivatives of the parameters. The partial derivative of the Gaussian width
* is given by
* \f[\frac{df}{d\sigma_v} = f(\theta) \frac{\theta^4}{\sigma^3} \sigma_s\f]
* where
* \f$\sigma_v\f$ is the value part,
* \f$\sigma_s\f$ is the scaling part, and
* \f$\sigma = \sigma_v \sigma_s\f$.
*
* Note that this method implements a function which is unity for
* \f$\theta=0\f$.
***************************************************************************/
double GCTAModelRadialGauss::eval(const double& offset,
const bool& gradients) const
{
// Compute value
double arg = offset * offset / sigma();
double arg2 = arg * arg;
double value = std::exp(-0.5 * arg2);
// Optionally compute partial derivatives
if (gradients) {
// Compute partial derivatives of the sigma parameter.
double g_sigma = value * arg2 / sigma() * m_sigma.scale();
// Set factor gradient
m_sigma.factor_gradient(g_sigma);
} // endif: computed partial derivatives
// Compile option: Check for NaN/Inf
#if defined(G_NAN_CHECK)
if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
std::cout << "*** ERROR: GCTAModelRadialGauss::eval";
std::cout << "(offset=" << offset << "): NaN/Inf encountered";
std::cout << " (value=" << value;
std::cout << ", sigma=" << sigma();
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)
* \exp \left(-\frac{1}{2}\frac{\theta^4}{\sigma^2} \right)\f]
* where
* \f$\theta\f$ is the offset angle (in degrees), and
* \f$\sigma\f$ is the Gaussian width (in degrees\f$^2\f$),
* using the rejection method.
*
* @todo Method can be optimised by using a random deviate of sin instead
* of the uniform random deviate which leads to many unnecessary
* rejections.
***************************************************************************/
GCTAInstDir GCTAModelRadialGauss::mc(const GCTAInstDir& dir, GRan& ran) const
{
// Simulate offset from photon arrival direction
#if defined(G_DEBUG_MC)
int n_samples = 0;
#endif
double sigma_max = 4.0 * sqrt(sigma());
double u_max = sin(sigma_max * gammalib::deg2rad);
double value = 0.0;
double u = 1.0;
double offset = 0.0;
do {
offset = ran.uniform() * sigma_max;
double arg = offset * offset / sigma();
double arg2 = arg * arg;
value = sin(offset * gammalib::deg2rad) * exp(-0.5 * arg2);
u = ran.uniform() * u_max;
#if defined(G_DEBUG_MC)
n_samples++;
#endif
} while (u > 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) = \exp \left(-\frac{1}{2}
* \left( \frac{\theta^2}{\sigma} \right)^2 \right)\f]
* \f$\theta\f$ is the offset angle (in degrees), and
* \f$\sigma\f$ is the Gaussian width (in degrees\f$^2\f$).
*
* The integration is performed numerically, and the upper integration bound
* \f$\pi\f$
* is set to
* \f$\sqrt(10 \sigma)\f$
* to reduce inaccuracies in the numerical integration.
***************************************************************************/
double GCTAModelRadialGauss::omega(void) const
{
// Allocate integrand
GCTAModelRadialGauss::integrand integrand(sigma() *
gammalib::deg2rad *
gammalib::deg2rad);
// Allocate intergal
GIntegral integral(&integrand);
// Set upper integration boundary
double offset_max = sqrt(10.0*sigma()) * gammalib::deg2rad;
if (offset_max > gammalib::pi) {
offset_max = gammalib::pi;
}
// Perform numerical integration
double omega = integral.romberg(0.0, offset_max) * 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_parvalue
* Non-positive parameter value found.
* @exception GException::model_invalid_parlimit
* Missing or non-positive minimum parameter boundary.
* @exception GException::model_invalid_parnames
* Invalid model parameter names found in XML element.
*
* Read the Gaussian radial model information from an XML element. The XML
* element is required to have one parameter named "Sigma".
***************************************************************************/
void GCTAModelRadialGauss::read(const GXmlElement& xml)
{
// Verify that XML element has exactly 1 parameter
if (xml.elements() != 1 || xml.elements("parameter") != 1) {
throw GException::model_invalid_parnum(G_READ, xml,
"Radial Gaussian model requires exactly 1 parameter.");
}
// Extract model parameters
int npar[] = {0};
for (int i = 0; i < 1; ++i) {
// Get parameter element
const GXmlElement* par = xml.element("parameter", i);
// Handle Sigma
if (par->attribute("name") == "Sigma") {
// Read parameter
m_sigma.read(*par);
// Check parameter
if (m_sigma.value() <= 0.0) {
throw GException::model_invalid_parvalue(G_READ, xml,
"\"Sigma\" parameter is required to be positive.");
}
if (!m_sigma.has_min() || m_sigma.min() <= 0.0) {
throw GException::model_invalid_parlimit(G_READ, xml,
"\"Sigma\" parameter requires positive minimum boundary.");
}
// Increment parameter counter
npar[0]++;
}
} // endfor: looped over all parameters
// Verify that all parameters were found
if (npar[0] != 1) {
throw GException::model_invalid_parnames(G_READ, xml,
"Radial Gaussian model requires \"Sigma\" parameter.");
}
// 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 'GaussFunction'
* @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 Gaussian radial model information into an XML element. The XML
* element will have one parameter leaf named "Sigma".
***************************************************************************/
void GCTAModelRadialGauss::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_spatial(G_WRITE, xml.attribute("type"),
"Radial Gaussian model is not of type \""+type()+"\".");
}
// If XML element has 0 nodes then append 1 parameter node
if (xml.elements() == 0) {
xml.append(GXmlElement("parameter name=\"Sigma\""));
}
// Verify that XML element has exactly 1 parameter
if (xml.elements() != 1 || xml.elements("parameter") != 1) {
throw GException::model_invalid_parnum(G_WRITE, xml,
"Radial Gaussian model requires exactly 1 parameter.");
}
// Set or update model parameter attributes
int npar[] = {0};
for (int i = 0; i < 1; ++i) {
// Get parameter element
GXmlElement* par = xml.element("parameter", i);
// Handle sigma
if (par->attribute("name") == "Sigma") {
m_sigma.write(*par);
npar[0]++;
}
} // endfor: looped over all parameters
// Check of all required parameters are present
if (npar[0] != 1) {
throw GException::model_invalid_parnames(G_WRITE, xml,
"Radial Gaussian model requires \"Sigma\" parameter.");
}
// Return
return;
}
/***********************************************************************//**
* @brief Print point source information
*
* @param[in] chatter Chattiness (defaults to NORMAL).
* @return String containing point source information.
***************************************************************************/
std::string GCTAModelRadialGauss::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("=== GCTAModelRadialGauss ===");
// 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 GCTAModelRadialGauss::init_members(void)
{
// Initialise Gaussian sigma
m_sigma.clear();
m_sigma.name("Sigma");
m_sigma.unit("deg2");
m_sigma.value(7.71728e-8); // (1 arcsec)^2
m_sigma.min(7.71728e-8); // (1 arcsec)^2
m_sigma.free();
m_sigma.scale(1.0);
m_sigma.gradient(0.0);
m_sigma.has_grad(true);
// Set parameter pointer(s)
m_pars.clear();
m_pars.push_back(&m_sigma);
// Return
return;
}
/***********************************************************************//**
* @brief Copy class members
*
* @param[in] model Radial Gaussian model.
***************************************************************************/
void GCTAModelRadialGauss::copy_members(const GCTAModelRadialGauss& model)
{
// Copy members
m_sigma = model.m_sigma;
// Set parameter pointer(s)
m_pars.clear();
m_pars.push_back(&m_sigma);
// Return
return;
}
/***********************************************************************//**
* @brief Delete class members
***************************************************************************/
void GCTAModelRadialGauss::free_members(void)
{
// Return
return;
}