/***************************************************************************
* GObservations.cpp - Observation container class *
* ----------------------------------------------------------------------- *
* copyright (C) 2009-2014 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 GObservations.cpp
* @brief Observations container class implementation
* @author Juergen Knoedlseder
*/
/* __ Includes ___________________________________________________________ */
#ifdef HAVE_CONFIG_H
#include
#endif
#include "GTools.hpp"
#include "GException.hpp"
#include "GObservations.hpp"
#include "GObservationRegistry.hpp"
/* __ Method name definitions ____________________________________________ */
#define G_AT "GObservations::at(int&)"
#define G_SET "GObservations::set(int&, GObservation&)"
#define G_APPEND "GObservations::append(GObservation&)"
#define G_INSERT "GObservations::insert(int&, GObservation&)"
#define G_REMOVE "GObservations::remove(int&)"
#define G_EXTEND "GObservations::extend(GObservations&)"
#define G_READ "GObservations::read(GXml&)"
/* __ Macros _____________________________________________________________ */
/* __ Coding definitions _________________________________________________ */
/* __ Debug definitions __________________________________________________ */
/*==========================================================================
= =
= Constructors/destructors =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Void constructor
***************************************************************************/
GObservations::GObservations(void)
{
// Initialise members
init_members();
// Return
return;
}
/***********************************************************************//**
* @brief Copy constructor
*
* @param obs Observation container.
***************************************************************************/
GObservations::GObservations(const GObservations& obs)
{
// Initialise members
init_members();
// Copy members
copy_members(obs);
// Return
return;
}
/***********************************************************************//**
* @brief Load constructor
*
* @param[in] filename XML filename.
*
* Construct the observation container by loading all relevant information
* from a XML file. Please refer to the read() method for more information
* about the structure of the XML file.
***************************************************************************/
GObservations::GObservations(const std::string& filename)
{
// Initialise members
init_members();
// Load XML file
load(filename);
// Return
return;
}
/***********************************************************************//**
* @brief Destructor
***************************************************************************/
GObservations::~GObservations(void)
{
// Free members
free_members();
// Return
return;
}
/*==========================================================================
= =
= Operators =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Assignment operator
*
* @param[in] obs Observation container.
* @return Observation container.
***************************************************************************/
GObservations& GObservations::operator=(const GObservations& obs)
{
// Execute only if object is not identical
if (this != &obs) {
// Free members
free_members();
// Initialise members
init_members();
// Copy members
copy_members(obs);
} // endif: object was not identical
// Return this object
return *this;
}
/*==========================================================================
= =
= Public methods =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Clear observations
***************************************************************************/
void GObservations::clear(void)
{
// Free members
free_members();
// Initialise members
init_members();
// Return
return;
}
/***********************************************************************//**
* @brief Clone observations
*
* @return Pointer to deep copy of observation container.
***************************************************************************/
GObservations* GObservations::clone(void) const
{
// Clone observations
return new GObservations(*this);
}
/***********************************************************************//**
* @brief Return pointer to observation
*
* @param[in] index Observation index [0,...,size()-1].
* @return Observation.
*
* @exception GException::out_of_range
* Operation index is out of range.
*
* Returns a pointer to the observation with specified @p index.
***************************************************************************/
GObservation* GObservations::at(const int& index)
{
// Raise exception if index is out of range
if (index < 0 || index >= size()) {
throw GException::out_of_range(G_AT, index, 0, size()-1);
}
// Return pointer
return m_obs[index];
}
/***********************************************************************//**
* @brief Return pointer to observation (const version)
*
* @param[in] index Observation index [0,...,size()-1].
*
* @exception GException::out_of_range
* Operation index is out of range.
*
* Returns a const pointer to the observation with specified @p index.
***************************************************************************/
const GObservation* GObservations::at(const int& index) const
{
// Raise exception if index is out of range
if (index < 0 || index >= size()) {
throw GException::out_of_range(G_AT, index, 0, size()-1);
}
// Return pointer
return m_obs[index];
}
/***********************************************************************//**
* @brief Set observation in container
*
* @param[in] index Observation index [0,...,size()-1].
* @param[in] obs Observation.
* @return Pointer to deep copy of observation.
*
* @exception GException::out_of_range
* Observation index is out of range.
* @exception GException::invalid_value
* Observation with same instrument and identifier already
* exists in container.
*
* Set a deep copy and observation @p obs at the specified @p index in the
* container.
***************************************************************************/
GObservation* GObservations::set(const int& index, const GObservation& obs)
{
// Compile option: raise exception if index is out of range
#if defined(G_RANGE_CHECK)
if (index < 0 || index >= size()) {
throw GException::out_of_range(G_SET, index, 0, size()-1);
}
#endif
// Raise an exception if an observation with specified instrument and
// identifier already exists
int inx = get_index(obs.instrument(), obs.id());
if (inx != -1 && inx != index) {
std::string msg =
"Attempt to set \""+obs.instrument()+"\" observation with"
" identifier \""+obs.id()+"\" in observation container at"
" index "+gammalib::str(index)+", but an observation with the"
" same attributes exists already at index "+gammalib::str(inx)+
" in the container.\n"
"Every observation for a given instrument in the observation"
" container needs a unique identifier.";
throw GException::invalid_value(G_SET, msg);
}
// Delete existing observation
if (m_obs[index] != NULL) delete m_obs[index];
// Store pointer to a deep copy of the observation
m_obs[index] = obs.clone();
// Return pointer
return m_obs[index];
}
/***********************************************************************//**
* @brief Append observation to container
*
* @param[in] obs Observation.
* @return Pointer to deep copy of observation.
*
* @exception GException::invalid_value
* Observation with same instrument and identifier already
* exists in container.
*
* Appends a deep copy of an observation to the container.
***************************************************************************/
GObservation* GObservations::append(const GObservation& obs)
{
// Raise an exception if an observation with specified instrument and
// identifier already exists
int inx = get_index(obs.instrument(), obs.id());
if (inx != -1) {
std::string msg =
"Attempt to append \""+obs.instrument()+"\" observation with"
" identifier \""+obs.id()+"\" to observation container, but an"
" observation with the same attributes exists already at"
" index "+gammalib::str(inx)+" in the container.\n"
"Every observation for a given instrument in the observation"
" container needs a unique identifier.";
throw GException::invalid_value(G_APPEND, msg);
}
// Clone observation
GObservation* ptr = obs.clone();
// Append to list
m_obs.push_back(ptr);
// Return pointer
return ptr;
}
/***********************************************************************//**
* @brief Insert observation into container
*
* @param[in] index Observation index [0,...,size()-1].
* @param[in] obs Observation.
* @return Pointer to deep copy of observation.
*
* @exception GException::out_of_range
* Observation index is out of range.
* @exception GException::invalid_value
* Observation with same instrument and identifier already
* exists in container.
*
* Inserts a deep copy of an observation into the container before the
* observation with the specified @p index.
***************************************************************************/
GObservation* GObservations::insert(const int& index, const GObservation& obs)
{
// Compile option: raise exception if index is out of range
#if defined(G_RANGE_CHECK)
if (is_empty()) {
if (index > 0) {
throw GException::out_of_range(G_INSERT, index, 0, size()-1);
}
}
else {
if (index < 0 || index >= size()) {
throw GException::out_of_range(G_INSERT, index, 0, size()-1);
}
}
#endif
// Raise an exception if an observation with specified instrument and
// identifier already exists
int inx = get_index(obs.instrument(), obs.id());
if (inx != -1 && inx != index) {
std::string msg =
"Attempt to insert \""+obs.instrument()+"\" observation with"
" identifier \""+obs.id()+"\" in observation container before"
" index "+gammalib::str(index)+", but an observation with the"
" same attributes exists already at index "+gammalib::str(inx)+
" in the container.\n"
"Every observation for a given instrument in the observation"
" container needs a unique identifier.";
throw GException::invalid_value(G_INSERT, msg);
}
// Clone observation
GObservation* ptr = obs.clone();
// Clone observation and insert into list
m_obs.insert(m_obs.begin()+index, ptr);
// Return pointer
return ptr;
}
/***********************************************************************//**
* @brief Remove observation from container
*
* @param[in] index Observation index [0,...,size()-1].
*
* @exception GException::out_of_range
* Observation index is out of range.
*
* Removes observation of specified @p index from the container.
***************************************************************************/
void GObservations::remove(const int& index)
{
// Compile option: If index is outside boundary then raise exception
#if defined(G_RANGE_CHECK)
if (index < 0 || index >= size()) {
throw GException::out_of_range(G_REMOVE, index, 0, size()-1);
}
#endif
// Delete observation
delete m_obs[index];
// Erase observation component from container
m_obs.erase(m_obs.begin() + index);
// Return
return;
}
/***********************************************************************//**
* @brief Append observations from observation container
*
* @param[in] obs Observations.
*
* @exception GException::invalid_value
* Observation with same instrument and identifier already
* exists in container.
*
* Appends deep copies of observations to the container.
***************************************************************************/
void GObservations::extend(const GObservations& obs)
{
// Do nothing if observation container is empty
if (!obs.is_empty()) {
// Get size. Note that we extract the size first to avoid an
// endless loop that arises when a container is appended to
// itself.
int num = obs.size();
// Reserve enough space
reserve(size() + num);
// Loop over all observations, clone them and append them to the
// list
for (int i = 0; i < num; ++i) {
// Raise an exception if an observation with specified
// instrument and identifier already exists
int inx = get_index(obs[i]->instrument(), obs[i]->id());
if (inx != -1) {
std::string msg =
"Attempt to append \""+obs[i]->instrument()+"\""
" observation with identifier \""+obs[i]->id()+"\""
" to observation container, but an observation with the"
" same attributes exists already at index "+
gammalib::str(inx)+" in the container.\n"
"Every observation for a given instrument in the"
" observation container needs a unique identifier.";
throw GException::invalid_value(G_EXTEND, msg);
}
// Append observation to container
m_obs.push_back(obs[i]->clone());
}
} // endif: observation container was not empty
// Return
return;
}
/***********************************************************************//**
* @brief Signals if observation exists
*
* @param[in] instrument Instrument.
* @param[in] id Observation identifier.
* @return True if observation with specified @p instrument and identifier
* @p id exists.
*
* Searches all observations for a match with the specified @p instrument
* and @p identifier. If the specified attributes have been found, true is
* returned.
***************************************************************************/
bool GObservations::contains(const std::string& instrument,
const std::string& id) const
{
// Get observation index
int index = get_index(instrument, id);
// Return
return (index != -1);
}
/***********************************************************************//**
* @brief Load observations from XML file
*
* @param[in] filename XML filename.
*
* Loads observation from a XML file into the container. Please refer to the
* read() method for more information about the structure of the XML file.
***************************************************************************/
void GObservations::load(const std::string& filename)
{
// Clear any existing observations
clear();
// Load XML document
GXml xml(filename);
// Read observations from XML document
read(xml);
// Return
return;
}
/***********************************************************************//**
* @brief Save observations into XML file
*
* @param[in] filename XML filename.
*
* Saves observations into a XML file. Please refer to the read() method for
* more information about the structure of the XML file.
***************************************************************************/
void GObservations::save(const std::string& filename) const
{
// Declare empty XML document
GXml xml;
// Write observations into XML file
write(xml);
// Save XML document
xml.save(filename);
// Return
return;
}
/***********************************************************************//**
* @brief Read observations from XML document
*
* @param[in] xml XML document.
*
* @exception GException::invalid_instrument
* Invalid instrument encountered in XML file.
*
* Reads observations from the first observation list that is found in the
* XML document. The decoding of the instrument specific observation
* definition is done within the observation's GObservation::read() method.
* The following file structure is expected:
*
*
*
* ...
*
*
* ...
*
* ...
*
*
* The @p name and @p id attributes allow for a unique identification of an
* observation within the observation container. The @p instrument
* attributes specifies the instrument to which the observation applies.
* This attribute will be used to allocate the appropriate instrument
* specific GObservation class variant by using the GObservationRegistry
* class.
*
* The structure within the @p observation tag is defined by the instrument
* specific GObservation class.
*
* @todo Observation names and IDs are not verified so far for uniqueness.
* This would be required to achieve an unambiguous update of parameters
* in an already existing XML file when using the write method.
***************************************************************************/
void GObservations::read(const GXml& xml)
{
// Get pointer on observation library
const GXmlElement* lib = xml.element("observation_list", 0);
// Loop over all observations
int n = lib->elements("observation");
for (int i = 0; i < n; ++i) {
// Get pointer on observation
const GXmlElement* obs = lib->element("observation", i);
// Get attributes
std::string name = obs->attribute("name");
std::string id = obs->attribute("id");
std::string instrument = obs->attribute("instrument");
// Allocate instrument specific observation
GObservationRegistry registry;
GObservation* ptr = registry.alloc(instrument);
// If observation is valid then read its definition from XML file
if (ptr != NULL) {
// Read definition
ptr->read(*obs);
// Set attributes
ptr->name(name);
ptr->id(id);
} // endif: observation was valid
// ... otherwise throw an exception
else {
throw GException::invalid_instrument(G_READ, instrument);
}
// Append observation to container
append(*ptr);
// Free observation (the append method made a deep copy)
delete ptr;
} // endfor: looped over all observations
// Return
return;
}
/***********************************************************************//**
* @brief Write observations into XML document
*
* @param[in] xml XML document.
*
* Write observations into the first observation library that is found in the
* XML document. In case that no observation library exists, one is added to
* the document. Please refer to the read() method for more information
* about the structure of the XML document.
***************************************************************************/
void GObservations::write(GXml& xml) const
{
// If there is no observation library then append one
if (xml.elements("observation_list") == 0) {
xml.append(GXmlElement("observation_list title=\"observation list\""));
}
// Get pointer on observation library
GXmlElement* lib = xml.element("observation_list", 0);
// Write all observations into library
for (int i = 0; i < size(); ++i) {
// Initialise pointer on observation
GXmlElement* obs = NULL;
// Search corresponding observation
int n = xml.elements("observation");
for (int k = 0; k < n; ++k) {
GXmlElement* element = xml.element("observation", k);
if (element->attribute("name") == m_obs[i]->name() &&
element->attribute("id") == m_obs[i]->id() &&
element->attribute("instrument") == m_obs[i]->instrument()) {
obs = element;
break;
}
}
// If no observation with corresponding name, ID and instrument was
// found then append one now
if (obs == NULL) {
obs = lib->append("observation");
obs->attribute("name", m_obs[i]->name());
obs->attribute("id", m_obs[i]->id());
obs->attribute("instrument", m_obs[i]->instrument());
}
// Write now observation
m_obs[i]->write(*obs);
} // endfor: looped over all observaitons
// Return
return;
}
/***********************************************************************//**
* @brief Load models from XML file
*
* @param[in] filename XML filename.
*
* Loads the models from a XML file. Please refer to the GModels::read()
* method for more information about the expected structure of the XML
* file.
***************************************************************************/
void GObservations::models(const std::string& filename)
{
// Load models
m_models.load(filename);
// Return
return;
}
/***********************************************************************//**
* @brief Optimize model parameters using optimizer
*
* @param[in] opt Optimizer.
*
* Optimizes the free parameters of the models by using the optimizer
* that has been provided by the @p opt argument.
***************************************************************************/
void GObservations::optimize(GOptimizer& opt)
{
// Extract optimizer parameter container from model container
GOptimizerPars pars = m_models.pars();
// Optimize model parameters
opt.optimize(m_fct, pars);
// Return
return;
}
/***********************************************************************//**
* @brief Computes parameter errors using optimizer
*
* @param[in] opt Optimizer.
*
* Calculates the errors of the free parameters of the models by using
* the optimizer that has been provided by the @p opt argument.
***************************************************************************/
void GObservations::errors(GOptimizer& opt)
{
// Extract optimizer parameter container from model container
GOptimizerPars pars = m_models.pars();
// Compute model parameter errors
opt.errors(m_fct, pars);
// Return
return;
}
/***********************************************************************//**
* @brief Evaluate function
*
* Evaluates the likelihood funtion at the actual set of parameters.
***************************************************************************/
void GObservations::eval(void)
{
// Extract optimizer parameter container from model container
GOptimizerPars pars = m_models.pars();
// Compute log-likelihood
m_fct.eval(pars);
// Return
return;
}
/***********************************************************************//**
* @brief Print observation list information
*
* @param[in] chatter Chattiness (defaults to NORMAL).
* @return String containing observation list information
***************************************************************************/
std::string GObservations::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("=== GObservations ===");
// Append information
result.append("\n"+gammalib::parformat("Number of observations"));
result.append(gammalib::str(size()));
result.append("\n"+gammalib::parformat("Number of predicted events"));
result.append(gammalib::str(npred()));
// NORMAL: Append observations
if (chatter >= NORMAL) {
for (int i = 0; i < size(); ++i) {
result.append("\n");
result.append((*this)[i]->print());
}
} // endif: chatter was normal
// EXPLICIT: Append models
if (chatter >= EXPLICIT) {
result.append("\n"+m_models.print());
} // endif: chatter was explicit
} // endif: chatter was not silent
// Return result
return result;
}
/*==========================================================================
= =
= Private methods =
= =
==========================================================================*/
/***********************************************************************//**
* @brief Initialise class members
***************************************************************************/
void GObservations::init_members(void)
{
// Initialise members
m_obs.clear();
m_models.clear();
m_fct.set(this); //!< Makes sure that optimizer points to this instance
// Return
return;
}
/***********************************************************************//**
* @brief Copy class members
*
* @param[in] obs Observation container.
*
* Copies all class members. Deep copies are created for the observations,
* which is what is needed to have a real physical copy of the members.
***************************************************************************/
void GObservations::copy_members(const GObservations& obs)
{
// Copy attributes. WARNING: The member m_fct SHALL not be copied to not
// corrupt its m_this pointer which should always point to the proper
// observation. See note in init_members().
m_models = obs.m_models;
// Copy observations
m_obs.clear();
for (int i = 0; i < obs.m_obs.size(); ++i) {
m_obs.push_back((obs.m_obs[i]->clone()));
}
// Return
return;
}
/***********************************************************************//**
* @brief Delete class members
*
* Deallocates all observations. Since container classes that hold pointers
* need to handle the proper deallocation of memory, we loop here over all
* pointers and make sure that we deallocate all observations in the
* container.
***************************************************************************/
void GObservations::free_members(void)
{
// Free observations
for (int i = 0; i < m_obs.size(); ++i) {
delete m_obs[i];
m_obs[i] = NULL;
}
// Return
return;
}
/***********************************************************************//**
* @brief Return observation index by instrument and identifier
*
* @param[in] instrument Instrument.
* @param[in] id Observation identifier.
* @return Observation index (-1 of instrument and observation identifier
* has not been found)
*
* Returns observation index based on the specified @p instrument and
* observation identifier @p id. If no observation with the specified
* attributes has been found, the method returns -1.
***************************************************************************/
int GObservations::get_index(const std::string& instrument,
const std::string& id) const
{
// Initialise index
int index = -1;
// Search observation with specified instrument and id
for (int i = 0; i < size(); ++i) {
if ((m_obs[i]->instrument() == instrument) &&
(m_obs[i]->id() == id)) {
index = i;
break;
}
}
// Return index
return index;
}