/*************************************************************************** * ctbutterfly - butterfly calculation tool * * ----------------------------------------------------------------------- * * copyright (C) 2014-2016 by Michael Mayer * * ----------------------------------------------------------------------- * * * * 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 ctbutterfly.cpp * @brief Butterfly computation tool * @author Michael Mayer */ /* __ Includes ___________________________________________________________ */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "ctbutterfly.hpp" #include "GTools.hpp" /* __ Method name definitions ____________________________________________ */ #define G_GET_PARAMETERS "ctbutterfly::get_parameters()" #define G_SAVE "ctbutterfly::save()" #define G_CHECK_MODEL "ctbutterfly::check_model()" /* __ Debug definitions __________________________________________________ */ /* __ Coding definitions _________________________________________________ */ /*========================================================================== = = = Constructors/destructors = = = ==========================================================================*/ /***********************************************************************//** * @brief Void constructor ***************************************************************************/ ctbutterfly::ctbutterfly(void) : ctlikelihood(CTBUTTERFLY_NAME, CTBUTTERFLY_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. ***************************************************************************/ ctbutterfly::ctbutterfly(const GObservations& obs) : ctlikelihood(CTBUTTERFLY_NAME, CTBUTTERFLY_VERSION, obs) { // Initialise members init_members(); // Return return; } /***********************************************************************//** * @brief Command line constructor * * @param[in] argc Number of arguments in command line. * @param[in] argv Array of command line arguments. ***************************************************************************/ ctbutterfly::ctbutterfly(int argc, char *argv[]) : ctlikelihood(CTBUTTERFLY_NAME, CTBUTTERFLY_VERSION, argc, argv) { // Initialise members init_members(); // Return return; } /***********************************************************************//** * @brief Copy constructor * * @param[in] app Application. ***************************************************************************/ ctbutterfly::ctbutterfly(const ctbutterfly& app) : ctlikelihood(app) { // Initialise members init_members(); // Copy members copy_members(app); // Return return; } /***********************************************************************//** * @brief Destructor ***************************************************************************/ ctbutterfly::~ctbutterfly(void) { // Free members free_members(); // Return return; } /*========================================================================== = = = Operators = = = ==========================================================================*/ /***********************************************************************//** * @brief Assignment operator * * @param[in] app Application. * @return Application. ***************************************************************************/ ctbutterfly& ctbutterfly::operator=(const ctbutterfly& app) { // Execute only if object is not identical if (this != &app) { // Copy base class members this->ctlikelihood::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 ctbutterfly tool * * Clears ctbutterfly tool. ***************************************************************************/ void ctbutterfly::clear(void) { // Free members free_members(); this->ctlikelihood::free_members(); this->ctobservation::free_members(); this->ctool::free_members(); // Clear base class (needed to conserve tool name and version) this->GApplication::clear(); // Initialise members this->ctool::init_members(); this->ctobservation::init_members(); this->ctlikelihood::init_members(); init_members(); // Write header into logger log_header(); // Return return; } /***********************************************************************//** * @brief Computes the butterfly * * This method takes the spectral fit and its covariance matrix to compute * the confidence band via Gaussian error propagation ***************************************************************************/ void ctbutterfly::run(void) { // If we're in debug mode then all output is also dumped on the screen if (logDebug()) { log.cout(true); } // Get task parameters get_parameters(); // 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 input observation container into logger log_observations(NORMAL, m_obs, "Input observation"); // If fit is selected then do maximum likelihood fit if (m_fit) { // Write header into logger log_header1(TERSE, "Compute best-fit likelihood"); // Optimize and compute errors m_obs.optimize(m_opt); m_obs.errors(m_opt); // Get covariance matrix by inverting the curvature matrix. Note that // the curvature matrix is for the unscaled parameters, hence we have // to scale them before we can proceed. GObservations::likelihood likelihood = m_obs.function(); m_covariance = likelihood.curvature()->invert(); // Write optimizer, optimised model and covariance matrix into logger log_string(NORMAL, m_opt.print(m_chatter)); log_string(NORMAL, m_obs.models().print(m_chatter)); log_string(EXPLICIT, m_covariance.print(m_chatter)); } // endif: fit selected // Get model instance for further computations GModels models = m_obs.models(); // If no fit is selected and no covariance matrix has been specified // then compute now the covariance matrix if (!m_fit && m_covariance.size() == 0) { // Write header into logger log_header1(TERSE, "Compute covariance matrix"); // Evaluate curvature matrix at the actual parameters GOptimizerPars pars = models.pars(); GObservations::likelihood likelihood = m_obs.function(); likelihood.eval(pars); // Get covariance matrix by inverting the curvature matrix. Note that // the curvature matrix is for the unscaled parameters, hence we have // to scale them before we can proceed. m_covariance = likelihood.curvature()->invert(); // Write covariance matrix into logger log_string(EXPLICIT, m_covariance.print(m_chatter)); } // endif: covariance computation needed // Write header into logger log_header1(TERSE, "Prepare butterfly computation"); // Find parameter indices. We do this by the memory location of the // model parameters. GModelSky* skymodel = dynamic_cast(models[m_srcname]); GModelPar* par_prefactor = &(*skymodel)["Prefactor"]; GModelPar* par_index = &(*skymodel)["Index"]; int inx_prefactor = -1; int inx_index = -1; for (int i = 0; i < models.npars(); ++i) { if (models.pars()[i] == par_prefactor) { inx_prefactor = i; } if (models.pars()[i] == par_index) { inx_index = i; } } // Store model values and scales double prefactor_mean = (*skymodel)["Prefactor"].value(); double index_mean = (*skymodel)["Index"].value(); double pivot_mean = (*skymodel)["PivotEnergy"].value(); double prefactor_scale = (*skymodel)["Prefactor"].scale(); double index_scale = (*skymodel)["Index"].scale(); GEnergy pivot(pivot_mean, "MeV"); // Write parameter indices into logger log_value(NORMAL, "Prefactor", gammalib::str(prefactor_mean)+ " ph/cm2/s/MeV ("+gammalib::str(inx_prefactor)+")"); log_value(NORMAL, "Index", gammalib::str(index_mean)+ " ("+gammalib::str(inx_index)+")"); log_value(NORMAL, "Pivot energy", pivot.print()); // Extract covariance elements double cov_pp = m_covariance(inx_prefactor,inx_prefactor); double cov_pg = m_covariance(inx_prefactor,inx_index); double cov_gg = m_covariance(inx_index,inx_index); // Compute eigenvectors and eigenvalues double lambda1; double lambda2; GVector vector1(2); GVector vector2(2); eigenvectors(cov_pp, cov_pg, cov_pg, cov_gg, &lambda1, &lambda2, &vector1, &vector2); // Write eigenvectors and eigenvalues into logger log_value(NORMAL, "Eigenvalue 1", lambda1); log_value(NORMAL, "Eigenvalue 2", lambda2); log_value(NORMAL, "Eigenvector 1", vector1.print()); log_value(NORMAL, "Eigenvector 2", vector2.print()); // Confidence scaling double sigma = gammalib::erfinv(m_confidence) * gammalib::sqrt_two; double scale = (sigma*sigma); // Write confidence level information into logger log_value(NORMAL, "Confidence level", m_confidence); log_value(NORMAL, "Corresponding scaling", scale); // Compute minor and major axes of error ellipse double major = scale*std::sqrt(lambda1); double minor = scale*std::sqrt(lambda2); // Write header into logger log_header1(TERSE, "Generate butterfly"); // Initialise result arrays m_energies.assign(m_ebounds.size(), 0.0); m_intensities.assign(m_ebounds.size(), 0.0); m_min_intensities.assign(m_ebounds.size(), 1e30); m_max_intensities.assign(m_ebounds.size(), 0.0); // Walk around the error ellipse int nt = 360; double dt = gammalib::twopi/double(nt); double t = 0.0; for (int i = 0; i < nt; ++i, t += dt) { // Pre-compute sin and cosine double sin_t = std::sin(t); double cos_t = std::cos(t); // Compute prefactor and index double prefactor = (major * cos_t * vector1[0] + minor * sin_t * vector2[0]) * prefactor_scale + prefactor_mean; double index = (major * cos_t * vector1[1] + minor * sin_t * vector2[1]) * index_scale + index_mean; // Write prefactor and index into logger log_value(EXPLICIT, "Angle "+gammalib::str(t*gammalib::rad2deg), gammalib::str(prefactor)+" ph/cm2/s/MeV; "+ gammalib::str(index)); // Setup power law model GModelSpectralPlaw plaw(prefactor, index, pivot); // Evaluate power law at each energy and extract minimum and // maximum intensity for (int k = 0; k < m_ebounds.size(); ++k) { // Get the energy of current bin GTime time; GEnergy energy = m_ebounds.elogmean(k); // Evaluate power law double intensity = plaw.eval(energy, time); // Get extremes if (intensity < m_min_intensities[k]) { m_min_intensities[k] = intensity; } if (intensity > m_max_intensities[k]) { m_max_intensities[k] = intensity; } } // endfor: looped over all energies } // endfor: looped over all angles // Setup power law model for mean prefactor and index GModelSpectralPlaw plaw(prefactor_mean, index_mean, pivot); // Compute energy and intensity vectors for mean power law for (int k = 0; k < m_ebounds.size(); ++k) { // Get the energy of current bin GTime time; GEnergy energy = m_ebounds.elogmean(k); // Evaluate power law double intensity = plaw.eval(energy, time); // Store results m_energies[k] = energy.MeV(); m_intensities[k] = intensity; } // endfor: looped over energies // Write header into logger log_header1(TERSE, "Results"); // Show results for (int k = 0; k < m_ebounds.size(); ++k) { // Write results into logger log_value(NORMAL, "Intensity at "+gammalib::str(m_energies[k])+" MeV", gammalib::str(m_intensities[k])+" ("+ gammalib::str(m_min_intensities[k])+", "+ gammalib::str(m_max_intensities[k])+") ph/cm2/s/MeV"); } // endfor: looped over energies // 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 butterfly diagram * * Saves the butterfly diagram into an ascii file using a column separated * value (CSV) format with blanks as separators. ***************************************************************************/ void ctbutterfly::save(void) { // Write header into logger log_header1(TERSE, "Save Butterfly diagram"); // Get output filename m_outfile = (*this)["outfile"].filename(); // Save only if filename is non-empty if (!m_outfile.is_empty()) { // Log butterfly diagram file name log_value(NORMAL, "Butterfly file", m_outfile.url()); // Create CSV table with 4 columns GCsv table(m_energies.size(), 4); // Fill CSV table for (int i = 0; i < m_energies.size(); ++i) { table.real(i, 0, m_energies[i]); table.real(i, 1, m_intensities[i]); table.real(i, 2, m_min_intensities[i]); table.real(i, 3, m_max_intensities[i]); } // Save CSV table table.save(m_outfile.url(), " ", clobber()); } // Return return; } /*========================================================================== = = = Private methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Initialise class members ***************************************************************************/ void ctbutterfly::init_members(void) { // Initialise members m_srcname.clear(); m_confidence = 0.68; m_max_iter = 50; m_apply_edisp = false; m_fit = false; m_chatter = static_cast(2); m_ebounds.clear(); m_outfile.clear(); // Initialise protected members m_covariance.clear(); m_energies.clear(); m_intensities.clear(); m_min_intensities.clear(); m_max_intensities.clear(); // Set optimizer parameters m_opt.max_iter(m_max_iter); m_opt.max_stalls(10); // Return return; } /***********************************************************************//** * @brief Copy class members * * @param[in] app Application. ***************************************************************************/ void ctbutterfly::copy_members(const ctbutterfly& app) { // Copy attributes m_srcname = app.m_srcname; m_confidence = app.m_confidence; m_max_iter = app.m_max_iter; m_apply_edisp = app.m_apply_edisp; m_fit = app.m_fit; m_chatter = app.m_chatter; m_ebounds = app.m_ebounds; m_outfile = app.m_outfile; // Copy protected members m_covariance = app.m_covariance; m_energies = app.m_energies; m_intensities = app.m_intensities; m_min_intensities = app.m_min_intensities; m_max_intensities = app.m_max_intensities; // Return return; } /***********************************************************************//** * @brief Delete class members ***************************************************************************/ void ctbutterfly::free_members(void) { // Return return; } /***********************************************************************//** * @brief Get application parameters * * @exception GException::feature_not_implemented * Loading of covariance matrix is not yet implemented. * * Get all task parameters from parameter file or (if required) by querying * the user. Most parameters are only required if no observation exists so * far in the observation container. In this case, a single CTA observation * will be added to the container, using the definition provided in the * parameter file. ***************************************************************************/ void ctbutterfly::get_parameters(void) { // Setup observations from "inobs" parameter setup_observations(m_obs); // Setup models from "inmodel" parameter setup_models(m_obs, (*this)["srcname"].string()); // Get name of test source m_srcname = (*this)["srcname"].string(); // Check model name and type check_model(); // Create energy boundaries from user parameters m_ebounds = create_ebounds(); // Get matrix file name and load if possible std::string matrixfilename = (*this)["matrix"].filename(); if (matrixfilename != "NONE") { std::string msg = "Loading of matrix from file not implemented yet. " "Use filename = \"NONE\" to induce a recomputation of " "the matrix internally."; throw GException::feature_not_implemented(G_GET_PARAMETERS, msg); // m_covariance.load(matrixfilename); } // Get remaining parameters m_apply_edisp = (*this)["edisp"].boolean(); m_confidence = (*this)["confidence"].real(); m_fit = (*this)["fit"].boolean(); m_chatter = static_cast((*this)["chatter"].integer()); // Optionally read ahead parameters so that they get correctly // dumped into the log file if (read_ahead()) { m_outfile = (*this)["outfile"].filename(); } // Write parameters into logger log_parameters(TERSE); // Return return; } /***********************************************************************//** * @brief Check if sky model is valid * * @exception GException::invalid_value * Did not find a valid model * * Checks whether the specified sky model exists and whether it is a power * law. ***************************************************************************/ void ctbutterfly::check_model(void) { // Continue only if source model exists if (m_obs.models().contains(m_srcname)) { // Get relevant model and parameter for upper limit computation. GModels& models = const_cast(m_obs.models()); GModelSky* model = dynamic_cast(models[m_srcname]); if (model == NULL) { std::string msg = "Source \""+m_srcname+"\" is not a sky model. " "Please specify the name of a sky model for " "butterfly computation."; throw GException::invalid_value(G_CHECK_MODEL, msg); } // Check that the spectral model is a power law if (model->spectral()->type() != "PowerLaw") { std::string msg = "\""+model->spectral()->type()+"\" cannot be " "used as spectral model for an butterfly " "computation. Please specify a power law model."; throw GException::invalid_value(G_CHECK_MODEL, msg); } } // endif: source model existed // Otherwise throw an exception else { std::string msg = "Source \""+m_srcname+"\" not found in model " "container. Please add a source with that name " "or check for possible typos."; throw GException::invalid_value(G_CHECK_MODEL, msg); } // Return return; } /***********************************************************************//** * @brief Compute normalized eigenvectors and eigenvalues * * @param[in] a Covariance matrix element (0,0) * @param[in] b Covariance matrix element (0,1) * @param[in] c Covariance matrix element (1,0) * @param[in] d Covariance matrix element (1,1) * @param[out] lambda1 First eigenavlue * @param[out] lambda2 Second eigenavlue * @param[out] vector1 First eigenvector (normalized) * @param[out] vector2 Second eigenvector (normalized) * * Computes the eigenvalues and eigenvectors for a 2x2 matrix. See * http://www.math.harvard.edu/archive/21b_fall_04/exhibits/2dmatrices/index.html ***************************************************************************/ void ctbutterfly::eigenvectors(const double& a, const double& b, const double& c, const double& d, double* lambda1, double* lambda2, GVector* vector1, GVector* vector2) { // Compute trace and determinant double trace = a + d; double det = a*d - b*c; // Compute eigenvalues double arg1 = 0.5*trace; double arg2 = std::sqrt(0.25*trace*trace - det); *lambda1 = arg1 + arg2; *lambda2 = arg1 - arg2; // Compute eigenvectors if (c != 0.0) { (*vector1)[0] = *lambda1 - det; (*vector1)[1] = c; (*vector2)[0] = *lambda2 - det; (*vector2)[1] = c; } else if (b != 0.0) { (*vector1)[0] = b; (*vector1)[1] = *lambda1 - a; (*vector2)[0] = b; (*vector2)[1] = *lambda2 - a; } else { (*vector1)[0] = 1.0; (*vector1)[1] = 0.0; (*vector2)[0] = 0.0; (*vector2)[1] = 1.0; } // Normalize eigenvectors *vector1 /= norm(*vector1); *vector2 /= norm(*vector2); // Return return; }