/***************************************************************************
* ctlike - Maximum likelihood fitting tool *
* ----------------------------------------------------------------------- *
* copyright (C) 2010-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 ctlike.cpp
* @brief Maximum likelihood fitting tool implementation
* @author Juergen Knoedlseder
*/
/* __ Includes ___________________________________________________________ */
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include "ctlike.hpp"
#include "GTools.hpp"
/* __ Method name definitions ____________________________________________ */
#define G_GET_PARAMETERS "ctlike::get_parameters()"
/* __ Debug definitions __________________________________________________ */
/* __ Coding definitions _________________________________________________ */
/*==========================================================================
= =
= Constructors/destructors =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Void constructor
***************************************************************************/
ctlike::ctlike(void) : ctool(CTLIKE_NAME, CTLIKE_VERSION)
{
// Initialise members
init_members();
// Return
return;
}
/***********************************************************************//**
* @brief Observations constructor
*
* param[in] obs Observation container.
*
* This method creates an instance of the class by copying an existing
* observations container.
***************************************************************************/
ctlike::ctlike(const GObservations& obs) : ctool(CTLIKE_NAME, CTLIKE_VERSION)
{
// Initialise members
init_members();
// Set observations
m_obs = obs;
// Return
return;
}
/***********************************************************************//**
* @brief Command line constructor
*
* @param[in] argc Number of arguments in command line.
* @param[in] argv Array of command line arguments.
***************************************************************************/
ctlike::ctlike(int argc, char *argv[]) :
ctool(CTLIKE_NAME, CTLIKE_VERSION, argc, argv)
{
// Initialise members
init_members();
// Return
return;
}
/***********************************************************************//**
* @brief Copy constructor
*
* @param[in] app Application.
***************************************************************************/
ctlike::ctlike(const ctlike& app) : ctool(app)
{
// Initialise members
init_members();
// Copy members
copy_members(app);
// Return
return;
}
/***********************************************************************//**
* @brief Destructor
***************************************************************************/
ctlike::~ctlike(void)
{
// Free members
free_members();
// Return
return;
}
/*==========================================================================
= =
= Operators =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Assignment operator
*
* @param[in] app Application.
* @return Application.
***************************************************************************/
ctlike& ctlike::operator=(const ctlike& app)
{
// Execute only if object is not identical
if (this != &app) {
// Copy base class members
this->ctool::operator=(app);
// Free members
free_members();
// Initialise members
init_members();
// Copy members
copy_members(app);
} // endif: object was not identical
// Return this object
return *this;
}
/*==========================================================================
= =
= Public methods =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Clear instance
***************************************************************************/
void ctlike::clear(void)
{
// Free members
free_members();
this->ctool::free_members();
this->GApplication::free_members();
// Initialise members
this->GApplication::init_members();
this->ctool::init_members();
init_members();
// Return
return;
}
/***********************************************************************//**
* @brief Run maximum likelihood analysis
*
* The following analysis steps are performed:
* 1. Read the parameters (and write them into logger)
* 2. Load observation
* 3. Setup models for optimizing
* 4. Optimize model (and write result into logger)
***************************************************************************/
void ctlike::run(void)
{
// Switch screen logging on in debug mode
if (logDebug()) {
log.cout(true);
}
// Get parameters
get_parameters();
// Write parameters into logger
if (logTerse()) {
log_parameters();
log << std::endl;
}
// Set energy dispersion flag for all CTA observations and save old
// values in save_edisp vector
std::vector save_edisp;
save_edisp.assign(m_obs.size(), false);
for (int i = 0; i < m_obs.size(); ++i) {
GCTAObservation* obs = dynamic_cast(m_obs[i]);
if (obs != NULL) {
save_edisp[i] = obs->response()->apply_edisp();
obs->response()->apply_edisp(m_apply_edisp);
}
}
// Write observation(s) into logger
if (logTerse()) {
log << std::endl;
if (m_obs.size() > 1) {
log.header1("Observations");
}
else {
log.header1("Observation");
}
log << m_obs << std::endl;
}
// Optimize model parameters using LM optimizer
optimize_lm();
// Store Npred
double npred = m_obs.npred();
// Store models for which TS should be computed
std::vector ts_srcs;
GModels models_orig = m_obs.models();
for (int i = 0; i < models_orig.size(); ++i) {
GModel* model = models_orig[i];
if (model->tscalc()) {
ts_srcs.push_back(model->name());
}
}
// Compute TS values if requested
if (!ts_srcs.empty()) {
// Store original maximum likelihood and models
double logL_src = m_logL;
GModels models = m_obs.models();
// Fix spatial parameters if requested
if (m_fix_spat_for_ts) {
// Loop over all models
for (int i = 0; i < models.size(); ++i) {
// Continue only if model is skymodel
GModelSky* sky= dynamic_cast(models[i]);
if (sky != NULL) {
// Fix spatial parameters
GModelSpatial* spatial = sky->spatial();
for (int j = 0; j < spatial->size(); j++) {
(*spatial)[j].fix();
} // endfor: looped over spatial parameters
} // endif: there was a sky model
} // endfor: looped over models
} // endif: spatial parameter should be fixed
// Loop over stored models, remove source and refit
for (int i = 0; i < ts_srcs.size(); ++i) {
models.remove(ts_srcs[i]);
m_obs.models(models);
double logL_nosrc = reoptimize_lm();
double ts = 2.0 * (logL_src-logL_nosrc);
models_orig[ts_srcs[i]]->ts(ts);
models = models_orig;
}
// Restore best fit values
m_obs.models(models_orig);
}
// Compute number of observed events in all observations
double num_events = 0.0;
for (int i = 0; i < m_obs.size(); ++i) {
double data = m_obs[i]->events()->number();
if (data >= 0.0) {
num_events += data;
}
}
// Write results into logger
if (logTerse()) {
log << std::endl;
log.header1("Maximum likelihood optimisation results");
log << *m_opt << std::endl;
log << gammalib::parformat("Maximum log likelihood");
log << gammalib::str(m_logL,3) << std::endl;
log << gammalib::parformat("Observed events (Nobs)");
log << gammalib::str(num_events, 3) << std::endl;
log << gammalib::parformat("Predicted events (Npred)");
log << gammalib::str(npred, 3);
log << " (Nobs - Npred = ";
log << gammalib::str(num_events-npred);
log << ")" << std::endl;
log << m_obs.models() << std::endl;
}
// Restore energy dispersion flag for all CTA observations
for (int i = 0; i < m_obs.size(); ++i) {
GCTAObservation* obs = dynamic_cast(m_obs[i]);
if (obs != NULL) {
obs->response()->apply_edisp(save_edisp[i]);
}
}
// Return
return;
}
/***********************************************************************//**
* @brief Save results
*
* This method saves the fit results in a XML file.
***************************************************************************/
void ctlike::save(void)
{
// Write header
if (logTerse()) {
log << std::endl;
log.header1("Save results");
}
// Get output filename
m_outmodel = (*this)["outmodel"].filename();
// Save only if filename is non-empty
if (!m_outmodel.is_empty() &&
gammalib::toupper(m_outmodel.url()) != "NONE") {
// Log filename
if (logTerse()) {
log << "Save fitted parameters into file \""+m_outmodel+"\".";
log << std::endl;
}
// Write results out as XML model
m_obs.models().save(m_outmodel);
}
// Return
return;
}
/***********************************************************************//**
* @brief Get application parameters
*
* Get all required task parameters from the parameter file or (if specified)
* by querying the user. Observation dependent parameters will only be read
* if the observation container is actually empty. Observation dependent
* parameters are:
* "stat" (statistics to be used for observation),
* "caldb" (calibration database),
* "irf" (instrument response function), and
* "infile" (input file name).
* The model will only be loaded if no model components exist in the
* observation container.
*
* This method handles both loading of FITS files and of handling XML
* observation definition files.
***************************************************************************/
void ctlike::get_parameters(void)
{
// If there are no observations in container then load them via user
// parameters
if (m_obs.size() == 0) {
// Throw exception if no input observation file is given
require_inobs(G_GET_PARAMETERS);
// Get observation container
m_obs = get_observations();
} // endif: there was no observation in the container
// If only single observation is used, read statistics parameter
if (!m_use_xml) {
// Get other task parameters
std::string statistics = gammalib::toupper((*this)["stat"].string());
// Set statistics
(*m_obs[0]).statistics(statistics);
}
// If there is are no models associated with the observations then
// load now the model definition
if (m_obs.models().size() == 0) {
// Get models XML filename
std::string filename = (*this)["inmodel"].filename();
// Setup models for optimizing.
m_obs.models(GModels(filename));
} // endif: no models were associated with observations
// Get other parameters
m_refit = (*this)["refit"].boolean();
m_apply_edisp = (*this)["edisp"].boolean();
m_fix_spat_for_ts = (*this)["fix_spat_for_ts"].boolean();
// Optionally read ahead parameters so that they get correctly
// dumped into the log file
if (read_ahead()) {
m_outmodel = (*this)["outmodel"].filename();
}
// Set optimizer logger
if (logTerse()) {
static_cast(m_opt)->logger(&log);
}
else {
static_cast(m_opt)->logger(NULL);
}
// Return
return;
}
/***********************************************************************//**
* @brief Optimise model parameters
*
* Optimise model parameters using a maximum likelihood fit.
***************************************************************************/
void ctlike::optimize_lm(void)
{
// Write Header for optimization and indent for optimizer logging
if (logTerse()) {
log << std::endl;
log.header1("Maximum likelihood optimisation");
log.indent(1);
}
// Compute number of fitted parameters
int nfit = 0;
for (int i = 0; i < m_obs.models().size(); ++i) {
const GModel* model = m_obs.models()[i];
for (int k = 0; k < model->size(); ++k) {
if ((*model)[k].is_free()) {
nfit++;
}
}
}
// Notify if all parameters are fixed
if (nfit == 0) {
if (logTerse()) {
log << "WARNING: All model parameters are fixed!";
log << std::endl;
log << " ctlike will proceed without fitting parameters.";
log << std::endl;
log << " All curvature matrix elements will be zero.";
log << std::endl;
}
}
// Perform LM optimization
m_obs.optimize(*m_opt);
// Optionally refit
if (m_refit) {
// Dump new header
log.indent(0);
if (logTerse()) {
log << std::endl;
log.header1("Maximum likelihood re-optimisation");
log.indent(1);
}
// Optimise again
m_obs.optimize(*m_opt);
}
// Optionally show curvature matrix
if (logNormal()) {
log << std::endl;
log.header1("Curvature matrix");
log.indent(1);
log << *(const_cast(m_obs.function()).curvature());
log << std::endl;
}
// Compute errors
m_obs.errors(*m_opt);
// Store maximum log likelihood value
m_logL = -(m_opt->value());
// Remove indent
log.indent(0);
// Return
return;
}
/***********************************************************************//**
* @brief Re-optimise model parameters for TS computation
*
* Re-optimise the model parameters using a maximum likelihood fit for
* computation of the Test Statistic value for a given source.
***************************************************************************/
double ctlike::reoptimize_lm(void)
{
// Write Header for optimization and indent for optimizer logging
if (logTerse()) {
log << std::endl;
log.header1("Maximum likelihood re-optimisation");
log.indent(1);
}
// Create a clone of the optimizer for the re-optimisation
GOptimizer* opt = m_opt->clone();
// Perform LM optimization
m_obs.optimize(*opt);
// Optionally refit
if (m_refit) {
m_obs.optimize(*opt);
}
// Store maximum log likelihood value
double logL = -(opt->value());
// Write optimization results
log.indent(0);
if (logTerse()) {
log << std::endl;
log.header1("Maximum likelihood re-optimisation results");
log << *opt << std::endl;
}
// Return
return (logL);
}
/*==========================================================================
= =
= Private methods =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Initialise class members
***************************************************************************/
void ctlike::init_members(void)
{
// Initialise members
m_outmodel.clear();
m_obs.clear();
m_refit = false;
m_max_iter = 100; // Set maximum number of iterations
m_max_stall = 10; // Set maximum number of stalls
m_logL = 0.0;
m_opt = NULL;
m_apply_edisp = false;
m_fix_spat_for_ts = false;
// Set logger properties
log.date(true);
// Allocate LM optimizer
GOptimizerLM* opt = new GOptimizerLM();
// Set optimizer parameters
opt->max_iter(m_max_iter);
opt->max_stalls(m_max_stall);
// Set optimizer pointer
m_opt = opt;
// Return
return;
}
/***********************************************************************//**
* @brief Copy class members
*
* @param[in] app Application.
***************************************************************************/
void ctlike::copy_members(const ctlike& app)
{
// Copy attributes
m_refit = app.m_refit;
m_outmodel = app.m_outmodel;
m_obs = app.m_obs;
m_max_iter = app.m_max_iter;
m_max_stall = app.m_max_stall;
m_logL = app.m_logL;
m_opt = app.m_opt->clone();
m_apply_edisp = app.m_apply_edisp;
m_fix_spat_for_ts = app.m_fix_spat_for_ts;
// Return
return;
}
/***********************************************************************//**
* @brief Delete class members
***************************************************************************/
void ctlike::free_members(void)
{
// Free members
if (m_opt != NULL) delete m_opt;
// Mark pointers as free
m_opt = NULL;
// Return
return;
}