/*************************************************************************** * ctool - ctool base class * * ----------------------------------------------------------------------- * * copyright (C) 2014-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 ctool.hpp * @brief ctool base class implementation * @author Juergen Knoedlseder */ /* __ Includes ___________________________________________________________ */ #ifdef HAVE_CONFIG_H #include #endif #include // std::getenv() function #include // std::fopen(), etc. functions #include "ctool.hpp" #include "GTools.hpp" /* __ Includes for memory usage determination ____________________________ */ #if defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__)) #include #include #if defined(__APPLE__) && defined(__MACH__) #include #elif (defined(_AIX) || defined(__TOS__AIX__)) || (defined(__sun__) || defined(__sun) || defined(sun) && (defined(__SVR4) || defined(__svr4__))) #include #include #elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__) #include #endif #endif /* __ Method name definitions ____________________________________________ */ #define G_SETUP_OBSERVATION "ctool::setup_observations(GObservations&)" #define G_SETUP_MODELS "ctool::setup_models(GObservations&, std::string&)" #define G_GET_MEAN_POINTING "ctool::get_mean_pointing(GObservations&)" #define G_CREATE_EBOUNDS "ctool::create_ebounds()" #define G_RESTORE_EDISP "ctool::restore_edisp(GObservations&, "\ "std::vector&)" #define G_SET_OBS_RESPONSE "ctool::set_obs_response(GCTAObservation*)" #define G_PROVIDE_HELP "ctool::provide_help()" /* __ Debug definitions __________________________________________________ */ /* __ Coding definitions _________________________________________________ */ /*========================================================================== = = = Constructors/destructors = = = ==========================================================================*/ /***********************************************************************//** * @brief Name constructor * * @param[in] name Application name. * @param[in] version Application version. * * Constructs a ctool from the @p name and @p version. The constructor uses * the equivalent GApplication constructor to set the parameter filename to * ".par" and the log filename to "".log. The parameters will be * loaded from the parameter file. * * No log file will be opened. To open the log file an explicit call to the * logFileOpen() method is required. * * This constructor should be used for using a ctool from Python. ***************************************************************************/ ctool::ctool(const std::string& name, const std::string& version) : GApplication(name, version) { // Initialise members init_members(); // Return return; } /***********************************************************************//** * @brief Command line constructor * * @param[in] name Application name. * @param[in] version Application version. * @param[in] argc Number of arguments in command line. * @param[in] argv Array of command line arguments. * * Constructs a ctool from the @p name, @p version and command line * arguments. The constructor uses the equivalent GApplication constructor * to set the parameter filename to ".par" and the log filename to * "".log. The parameters will be loaded from the parameter file. * In addition, the constructor opens the log file. * * If the "--help" option is provided as command line argument a help text * about the usage of the ctool will be shown in the console and the ctool * will exit. No log file will be opened in that case. ***************************************************************************/ ctool::ctool(const std::string& name, const std::string& version, int argc, char *argv[]) : GApplication(name, version, argc, argv) { // Catch --help option before doing anything else if (need_help()) { provide_help(); exit(0); } // Initialise members init_members(); // Open the log file logFileOpen(); // Return return; } /***********************************************************************//** * @brief Copy constructor * * @param[in] app Application. * * Construct a ctools from another ctool instance. ***************************************************************************/ ctool::ctool(const ctool& app) : GApplication(app) { // Initialise members init_members(); // Copy members copy_members(app); // Return return; } /***********************************************************************//** * @brief Destructor ***************************************************************************/ ctool::~ctool(void) { // Free members free_members(); // Return return; } /*========================================================================== = = = Operators = = = ==========================================================================*/ /***********************************************************************//** * @brief Assignment operator * * @param[in] app Application. * * Assigns one ctool to another. ***************************************************************************/ ctool& ctool::operator=(const ctool& app) { // Execute only if object is not identical if (this != &app) { // Copy base class members this->GApplication::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 Execute application * * This is the main execution method of a ctool. The method is invoked when * the executable is called from the command line. It signals that output * parameters should be read ahead, runs the tools, and saves the results. ***************************************************************************/ void ctool::execute(void) { // Signal that some parameters should be read ahead m_read_ahead = true; // Run the tool run(); // Save the results save(); // Return return; } /*========================================================================== = = = Private methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Initialise class members ***************************************************************************/ void ctool::init_members(void) { // Initialise members m_read_ahead = false; m_use_xml = false; // Set CTA time reference. G_CTA_MJDREF is the CTA reference MJD, // which is defined in GCTALib.hpp. This is somehow a kluge. We need // a better mechanism to implement the CTA reference MJD. m_cta_ref.set(G_CTA_MJDREF, "s", "TT", "LOCAL"); // Set logger properties log.date(true); // Return return; } /***********************************************************************//** * @brief Copy class members * * @param[in] app Application. ***************************************************************************/ void ctool::copy_members(const ctool& app) { // Copy members m_read_ahead = app.m_read_ahead; m_use_xml = app.m_use_xml; m_cta_ref = app.m_cta_ref; // Return return; } /***********************************************************************//** * @brief Delete class members ***************************************************************************/ void ctool::free_members(void) { // Return return; } /***********************************************************************//** * @brief Setup observation container * * @param[in] obs Observation container * @param[in] response Require response * @param[in] list Accept event list as "inobs" parameter * @param[in] cube Accept counts cube as "inobs" parameter * * @exception GException::invalid_value * Invalid "inobs" parameter encountered. * * Setup an observation container by extracting all required missing * information from user parameters. * * If the observation container @p obs is empty, the method will extract the * filename from the "inobs" parameter and load the observation container * from the specified file. If the filename is either empty or "NONE" the * method will throw an exception. * * If the "inobs" file is a FITS file, and depending on the information that * is found in the FITS file, the method will append a single CTA observation * to the observation container, containing either an event list or a counts * cube. It will set the m_use_xml member to false to signal that no * observation XML file should be used for saving. The @p list and @p cube * parameters can be used to specify whether the method actually should accept * event lists or counts cube. If it shouldn't the method will throw an * exception. * * If the "inobs" file is not a FITS file, the method assumes that an * observation definition XML file has been specified, and will load that * file into the observation container. It will set the m_use_xml member to * true to signal that an observation XML file should be used for saving. No * checking on event list or counts cubes will be done on the observation * container. * * By default, the method will also setup the response for all CTA * observations in the container. In case that no response information is * required, the @p response argument should be set to @p false. See the * set_response() method for details. * * * The method will also setup the observation boundaries for all CTA * observations in the container that contain event lists. For details, see * the set_obs_bounds() method. ***************************************************************************/ void ctool::setup_observations(GObservations& obs, const bool& response, const bool& list, const bool& cube) { // Load observations if there are none in the container if (obs.size() == 0) { // Get the filename from the "inobs" parameter GFilename filename = (*this)["inobs"].filename(); // Throw an exception if the "inobs" parameter is not a valid // filename if (!is_valid_filename(filename)) { std::string msg = "The \"inobs\" parameter \""+filename.url()+ "\" is not a valid filename. Please specify " "a valid event list, counts cube or " "observation definition XML file."; throw GException::invalid_value(G_SETUP_OBSERVATION, msg); } // If file does not exist then throw an exceptions if (!filename.exists()) { std::string msg = "The \"inobs\" parameter \""+filename.url()+ "\" specifies a file that does not " "exist. Please specify a valid event " "list, counts cube or observation " "definition XML file."; throw GException::invalid_value(G_SETUP_OBSERVATION, msg); } // If the file is a FITS file then load it into a CTA // observation and append that observation to the container ... else if (filename.is_fits()) { // Load CTA observation GCTAObservation cta(filename); // If no event list is accepted but an event list has been // loaded then throw an exception if (!list && cta.eventtype() == "EventList") { std::string msg = "The \"inobs\" parameter \""+filename.url()+ "\" specifies an event list, but this " "tool does not accept event lists."; throw GException::invalid_value(G_SETUP_OBSERVATION, msg); } // If no counts cube is accepted but a conts cube has been // loaded then throw an exception if (!cube && cta.eventtype() == "CountsCube") { std::string msg = "The \"inobs\" parameter \""+filename.url()+ "\" specifies a counts cube, but this " "tool does not accept counts cubes."; throw GException::invalid_value(G_SETUP_OBSERVATION, msg); } // Append CTA observation to observation container obs.append(cta); // Signal that no XML file should be used for storage m_use_xml = false; } // endelse: file was a FITS file // ... otherwise it is assumed that the file is an observation // definition XML file and the file is loaded into the // observation container else { // Load observation definition file obs.load(filename); // Signal that XML file should be used for storage m_use_xml = true; } // endelse: file was an XML file } // endif: there were no observations in the container // Setup the response for all CTA observations in the observation // container if response information is required if (response) { set_response(obs); } // Setup boundaries for all CTA observations containing event lists set_obs_bounds(obs); // Return return; } /***********************************************************************//** * @brief Setup model container * * @param[in] obs Observation container * @param[in] name Mandatory model name * * @exception GException::invalid_value * Model definiton XML filename not valid or mandatory model name * not found in model container. * * Setup a model container by loading the models from the "inmodel" * parameter. * * If the model container in the observation container @p obs is empty, the * method will extract the model container filename from the "inmodel" * parameter, load the model container and assign it to the observations * container. If the filename is either empty or "NONE" the method will * throw an exception. * * If a mandatory model name is specified, the method will further check * whether a model with the name exists in the model container. If it does * not exist, an exception is thrown. ***************************************************************************/ void ctool::setup_models(GObservations& obs, const std::string& name) { // If there are no models in the observation container then load model // container from the "inmodel" parameter and attach it to the // observation container if (obs.models().size() == 0) { // Get models XML filename GFilename filename = (*this)["inmodel"].filename(); // Throw an exception if the "inmodel" parameter is not a valid // filename if (!is_valid_filename(filename)) { std::string msg = "The \"inmodel\" parameter \""+filename.url()+ "\" is not a valid filename. Please specify " "a valid model definition XML file."; throw GException::invalid_value(G_SETUP_MODELS, msg); } // Load model container and assign it to observations container obs.models(GModels(filename.url())); } // endif: no models were in observations container // If a mandatory model name exists then check whether this model is // part of the observation container. Throw an exception if the model // does not exist if (!name.empty()) { // Throw an exception if the model name does not exist if (!obs.models().contains(name)) { std::string msg = "Model \""+name+"\" not found in model " "container. Please add a model with that name " "or check for possible typos."; throw GException::invalid_value(G_SETUP_MODELS, msg); } } // endif: mandatory model name existed // Return return; } /***********************************************************************//** * @brief Create energy boundaries from user parameters * * @exception GException::invalid_value * No valid energy boundary extension found. * * Get the energy boundaries according to the user parameters. The method * supports loading of energy boundary information from the EBOUNDS or * ENERGYBINS extension, or setting energy boundaries using a linear * or logarithmical spacing. * * The following parameters are read: * ebinalg - Energy binning algorithm * emin - Minimum energy (if ebinalg != FILE) * emax - Maximum energy (if ebinalg != FILE) * enumbins - Number of energy bins (if ebinalg != FILE) ***************************************************************************/ GEbounds ctool::create_ebounds(void) { // Allocate energy boundaries GEbounds ebounds; // Get energy binning algorithm std::string ebinalg = (*this)["ebinalg"].string(); // If energy binning algorithm is of type "FILE" (case sensitive), then // read energy boundaries from FITS file ... if (ebinalg == "FILE") { // Get filename std::string ebinfile = (*this)["ebinfile"].filename(); // Create filename instance GFilename fname(ebinfile); // Check if extension name was provided if (!fname.has_extname()) { // Open energy boundary file using the EBOUNDS or ENERGYBINS // extension. Throw an exception if opening fails. GFits file(ebinfile); if (file.contains("EBOUNDS")) { file.close(); ebounds.load(ebinfile); } else if (file.contains("ENERGYBINS")) { file.close(); ebinfile += "[ENERGYBINS]"; ebounds.load(ebinfile); } else { file.close(); std::string msg = "No extension with name \"EBOUNDS\" or" " \"ENERGYBINS\" found in FITS file" " \""+ebinfile+"\". An \"EBOUNDS\" or " "\"ENERGYBINS\" extension is required if " "the parameter \"ebinalg\" is set to " "\"FILE\"."; throw GException::invalid_value(G_CREATE_EBOUNDS, msg); } } else { // Load ebounds from filename including extension ebounds.load(ebinfile); } } // endif: ebinalg was "FILE" // ... otherwise use a linear or a logarithmically-spaced energy binning else { // Get task parameters double emin = (*this)["emin"].real(); double emax = (*this)["emax"].real(); int enumbins = (*this)["enumbins"].integer(); // Initialise log mode for ebinning bool log = true; // Check if algorithm is linear if (ebinalg == "LIN") { log = false; } // Setup energy bins ebounds = GEbounds(enumbins, GEnergy(emin, "TeV"), GEnergy(emax, "TeV"), log); } // endelse: ebinalg was not "FILE" // Return energy boundaries return ebounds; } /***********************************************************************//** * @brief Create a skymap from user parameters * * @param[in] obs Observation container. * * Creates an empty skymap from input user parameters. The reference * coordinate is extracted from the mean pointing of the CTA observations * in the observation container (see get_mean_pointing()). * * The following parameters are read: * usepnt - Use pointing for reference coordinate? * xref - Reference in Right Ascension or longitude (if usepnt=no) * yref - Reference in Declination or latitude (if usepnt=no) * proj - Coordinate projection * coordsys - Coordinate system * binsz - Bin size (deg) * nxpix - Number of pixels in Right Ascension or longitude * nypix - Number of pixels in Declination or latitude ***************************************************************************/ GSkyMap ctool::create_map(const GObservations& obs) { // Read task parameters double xref = 0.0; double yref = 0.0; bool usepnt = (*this)["usepnt"].boolean(); if (!usepnt) { xref = (*this)["xref"].real(); yref = (*this)["yref"].real(); } std::string proj = (*this)["proj"].string(); std::string coordsys = (*this)["coordsys"].string(); double binsz = (*this)["binsz"].real(); int nxpix = (*this)["nxpix"].integer(); int nypix = (*this)["nypix"].integer(); // If requested, get pointing from observations if (usepnt) { // Get mean pointing GSkyDir pnt = get_mean_pointing(obs); // Use correct coordinate system if (gammalib::toupper(coordsys) == "GAL") { xref = pnt.l_deg(); yref = pnt.b_deg(); } else { xref = pnt.ra_deg(); yref = pnt.dec_deg(); } } // endif: got mean pointing as reference // Initialise sky map GSkyMap map = GSkyMap(proj, coordsys, xref, yref, -binsz, binsz, nxpix, nypix, 1); // Return sky map return map; } /***********************************************************************//** * @brief Create a CTA event cube from user parameters * * @param[in] obs Observation container. * * Creates an empty CTA event cube from input user parameters. The reference * coordinate is extracted from the mean pointing of the CTA observations * in the observation container (see get_mean_pointing()). This method will * be used if a ctool has the input parameter usepnt=true. The method * appends a dummy GTI to the event cube. * * The following parameters are read: * proj - Coordinate projection * coordsys - Coordinate system * binsz - Bin size (deg) * nxpix - Number of pixels in Right Ascension or longitude * nypix - Number of pixels in Declination or latitude * ebinalg - Energy binning algorithm * emin - Minimum energy (if ebinalg != FILE) * emax - Maximum energy (if ebinalg != FILE) * enumbins - Number of energy bins (if ebinalg != FILE) ***************************************************************************/ GCTAEventCube ctool::create_cube(const GObservations& obs) { // Get skymap GSkyMap map = create_map(obs); // Set energy boundaries GEbounds ebounds = create_ebounds(); // Set dummy GTI that is needed for event cube creation GGti gti; gti.append(GTime(0.0), GTime(0.1234)); // Extend skymap to the requested number of maps map.nmaps(ebounds.size()); // Initialise event cube from all parameters GCTAEventCube cube = GCTAEventCube(map, ebounds, gti); // Return event cube return cube; } /***********************************************************************//** * @brief Create a CTA observation from user parameters * * Creates an empty CTA observation from user parameters. An empty event list * including RoI, GTI and energy boundary information is attached to the * observation. The method also sets the pointing direction using the ra and * dec parameter, the ROI based on ra, dec and rad, a single GTI based on * tmin and tmax, and a single energy boundary based on emin and emax. The * method furthermore sets the ontime, livetime and deadtime correction * factor. * * The following parameters are read * * ra: Right Ascension of pointing and RoI centre (deg) * dec: Declination of pointing and RoI centre (deg) * rad: Radius of RoI (deg) * deadc: Deadtime correction factor * tmin: Start time * tmax: Stop time * emin: Minimum energy (TeV) * emax: Maximum energy (TeV) ***************************************************************************/ GCTAObservation ctool::create_cta_obs(void) { // Get CTA observation parameters double ra = (*this)["ra"].real(); double dec = (*this)["dec"].real(); double rad = (*this)["rad"].real(); double deadc = (*this)["deadc"].real(); double t_min = (*this)["tmin"].real(); double t_max = (*this)["tmax"].real(); double e_min = (*this)["emin"].real(); double e_max = (*this)["emax"].real(); // Allocate CTA observation and empty event list GCTAObservation obs; GCTAEventList list; // Set pointing direction GCTAPointing pnt; GSkyDir skydir; skydir.radec_deg(ra, dec); pnt.dir(skydir); // Set ROI GCTAInstDir instdir(skydir); GCTARoi roi(instdir, rad); // Set GTI GGti gti(m_cta_ref); GTime tstart; GTime tstop; tstart.set(t_min, m_cta_ref); tstop.set(t_max, m_cta_ref); gti.append(tstart, tstop); // Set energy boundaries GEbounds ebounds; GEnergy emin; GEnergy emax; emin.TeV(e_min); emax.TeV(e_max); ebounds.append(emin, emax); // Set CTA event list attributes list.roi(roi); list.gti(gti); list.ebounds(ebounds); // Attach empty event list to CTA observation obs.events(list); // Set observation ontime, livetime and deadtime correction factor obs.pointing(pnt); obs.ontime(gti.ontime()); obs.livetime(gti.ontime()*deadc); obs.deadc(deadc); // Return CTA observation return obs; } /***********************************************************************//** * @brief Throws exception if inobs parameter is not valid * * @param[in] method Method name. * * Throw an exception if the "inobs" parameter is either "NONE" or an empty * string. * * @todo This method is only used by csobsinfo, csresmap and csspec. We * should try to get rid of it ***************************************************************************/ void ctool::require_inobs(const std::string& method) { // Get inobs filename GFilename filename = (*this)["inobs"].filename(); // Throw exception if no infile is given if (!is_valid_filename(filename)) { std::string msg = "A valid file needs to be specified for the " "\"inobs\" parameter, yet \""+filename.url()+ "\" was given." " Specify a valid observation definition or " "FITS file to proceed."; throw GException::invalid_value(method, msg); } // Return return; } /***********************************************************************//** * @brief Throws exception if inobs parameter is a counts cube * * @param[in] method Method name. * * Throw an exception if the inobs parameter is a counts cube. * * @todo This method is only used by ctobssim. We should try to get rid of it ***************************************************************************/ void ctool::require_inobs_nocube(const std::string& method) { // Get inobs filename GFilename filename = (*this)["inobs"].filename(); // Continue only if we have a FITS file if (filename.is_fits()) { // Signal no cube bool is_cube = false; // Try loading file as counts cube. If this is successful then // throw an exception try { // Load cube from file GCTAEventCube cube(filename); // If we're still alive then signal that we have a cube is_cube = true; } // Catch any exceptions catch (...) { ; } // If we have a counts cube then throw an exception if (is_cube) { std::string msg = "A counts cube has been specified for the " "\"inobs\" parameter, yet no counts cube " "can be specified as input observation." " Instead, specify an event list or an " "observation definition file."; throw GException::invalid_value(method, msg); } } // endif: we had a FITS file // Return return; } /***********************************************************************//** * @brief Log observation container * * @param[in] chatter Minimum required chattiness * @param[in] obs Observation container * @param[in] what String specifying the container content * * Log observations if chattiness is at least @p chatter. ***************************************************************************/ void ctool::log_observations(const GChatter& chatter, const GObservations& obs, const std::string& what) { // Get chattiness of ctool GChatter chattiness = static_cast((*this)["chatter"].integer()); // Only write message if chattiness is at least equal to the minimum // required chattiness if (chattiness >= chatter) { // Log observation header log << std::endl; log.header1(gammalib::number(what, obs.size())); // Log observation content dependent on chattiness log << obs.print(chattiness) << std::endl; } // endif: Chattiness satisfied minimum required level // Return return; } /***********************************************************************//** * @brief Log model container * * @param[in] chatter Minimum required chattiness * @param[in] models Model container * @param[in] what String specifying the container content * * Log models if chattiness is at least @p chatter. ***************************************************************************/ void ctool::log_models(const GChatter& chatter, const GModels& models, const std::string& what) { // Get chattiness of ctool GChatter chattiness = static_cast((*this)["chatter"].integer()); // Only write message if chattiness is at least equal to the minimum // required chattiness if (chattiness >= chatter) { // Log observation header log << std::endl; log.header1(gammalib::number(what, models.size())); // Log observation content dependent on chattiness log << models.print(chattiness) << std::endl; } // endif: Chattiness satisfied minimum required level // Return return; } /***********************************************************************//** * @brief Return RoI from user parameters * * @param[in] obs Observation container. * @return Region of interest. * * Returns region of interest from the user parameters "ra", "dec" and "rad". * If the "usepnt" parameter exists and is "yes" then postpone then assume * that the Right Ascension and Declination of the RoI centre will be set * from the pointing direction somewhere else. ***************************************************************************/ GCTARoi ctool::get_roi(void) { // Initialise an empty RoI GCTARoi roi; // Signal whether the pointing instead of the user parameters should be // use for the RoI centre bool usepnt = (has_par("usepnt") && (*this)["usepnt"].boolean()); // If pointing should not bet used the extract the RoI centre from the // "ra" and "dec" parameters if (!usepnt) { // Read "ra" and "dec" parameters only if both are valid if ((*this)["ra"].is_valid() && (*this)["dec"].is_valid()) { // Read parameters double ra = (*this)["ra"].real(); double dec = (*this)["dec"].real(); // Set RoI GCTAInstDir instdir; instdir.dir().radec_deg(ra, dec); roi.centre(instdir); // Signal that we have a pointing direction usepnt = true; } // endif: "ra" and "dec" were valid } // endif: pointing was not used for RoI centre // If we have a RoI centre and if the "rad" parameter is valid then // extract the RoI radius from the parameter if (usepnt && (*this)["rad"].is_valid()) { roi.radius((*this)["rad"].real()); } // Return RoI return roi; } /***********************************************************************//** * @brief Set response for all CTA observations in container * * @param[in,out] obs Observation container * * Set the response for all CTA observations in the container that so far * have no response information available. See the set_obs_response() * method for more information about the user parameters that are read. ***************************************************************************/ void ctool::set_response(GObservations& obs) { // Setup response for all observations for (int i = 0; i < obs.size(); ++i) { // Is this observation a CTA observation? GCTAObservation* cta = dynamic_cast(obs[i]); // Yes, the set the response if the observation does not yet have one if (cta != NULL) { // Set response if we don't have one if (!cta->has_response()) { set_obs_response(cta); } } // endif: observation was a CTA observation } // endfor: looped over all observations // Return return; } /***********************************************************************//** * @brief Set energy dispersion to CTA observations * * @param[in] obs Observation container. * @param[in] edisp Requested energy dispersion flag. * @return Vector of old energy dispersion flags. * * Applies energy dispersion to all CTA observations. The method returns a * vector with the old energy dispersion flags. ***************************************************************************/ std::vector ctool::set_edisp(GObservations& obs, const bool& edisp) const { // Allocate vector for energy dispersion flags of all observation in // the container std::vector old_edisp(obs.size(), false); // Loop over all observations in observation container for (int i = 0; i < obs.size(); ++i) { // Is this observation a CTA observation? GCTAObservation* cta = dynamic_cast(obs[i]); // Yes, then set the energy dispersion flag if (cta != NULL) { // Save old energy dispersion flag old_edisp[i] = cta->response()->apply_edisp(); // Set energy dispersion flag according to input parameter cta->response()->apply_edisp(edisp); } // endif: observation was CTA observation } // endfor: loop over all observations in container // Return old energy dispersion flags return old_edisp; } /***********************************************************************//** * @brief Restore energy dispersion flags of CTA observations * * @param[in] obs Observation container. * @param[in] edisp Vector of energy dispersion flags. * * Restores energy dispersion flags that have previously been returned by * the set_edisp() method. The number of observations between the calls * of the set_edisp() and this method has to be the same. ***************************************************************************/ void ctool::restore_edisp(GObservations& obs, const std::vector& edisp) const { // Check that energy dispersion flag vector has the same size as the // observation container if (edisp.size() != obs.size()) { std::string msg = "Size of energy dispersion flag vector ("+ gammalib::str(edisp.size())+" elements) is " "incompatible with number of observations in the " "observation container ("+gammalib::str(obs.size())+ " observation)."; throw GException::invalid_value(G_RESTORE_EDISP, msg); } // Loop over all observations in observation container for (int i = 0; i < obs.size(); ++i) { // Is this observation a CTA observation? GCTAObservation* cta = dynamic_cast(obs[i]); // Yes, then restore the energy dispersion flag if (cta != NULL) { cta->response()->apply_edisp(edisp[i]); } } // endfor: loop over all observations in container // Return return; } /***********************************************************************//** * @brief Set response for CTA observation * * @param[in,out] obs CTA observation * * @exception GException::invalid_value * Energy dispersion requested but not energy dispersion cube given * * Set the response for one CTA observation. If the CTA observation contains * a counts cube the method first attempts to set the response information * using the following user parameters * * expcube: Exposure cube file * psfcube: Point spread function cube file * edispcube: Energy dispersion cube file (optional) * bkgcube: Background cube file * * If none of the @p expcube, @p psfcube and @p bkgcube parameters is * "NONE" or empty, the method will allocate a stacked response for the * observation. In case that an energy dispersion cube is provided using * the @p edispcube parameter, this energy dispersion cube is also loaded * and attached to the stacked response function. * * In case that any of the @p expcube, @p psfcube and @p bkgcube parameters * is "NONE" or empty, the method will assume that a IRF response should * be used, and will attempty to set the response information using the * following user parameters * * caldb: Calibration database * irf: Instrument response function * ***************************************************************************/ void ctool::set_obs_response(GCTAObservation* obs) { // Initialise response flag bool has_response = false; // If the observation contains a counts cube, then check whether // response information for a stacked analysis is provided, and if so, // set that response information for the CTA observation if (dynamic_cast(obs->events()) != NULL) { // Check if response cube parameters are provided. We need all four // response cube parameters. if (has_par("expcube") && has_par("psfcube") && has_par("bkgcube") && has_par("edispcube")) { // Check if we need to query the energy dispersion cube. We do // not need to query the cube is an "edisp" parameter exists and // if this parameter is set to "no". bool query_edisp = true; if (has_par("edisp")) { query_edisp = (*this)["edisp"].boolean(); } // Get filenames GFilename expcube((*this)["expcube"].filename()); GFilename psfcube((*this)["psfcube"].filename()); GFilename edispcube; if (query_edisp) { edispcube = (*this)["edispcube"].filename(); } GFilename bkgcube((*this)["bkgcube"].filename()); // Extract stacked response information if available if (is_valid_filename(expcube) && is_valid_filename(psfcube) && is_valid_filename(bkgcube)) { // Load exposure, PSF and background cubes GCTACubeExposure exposure(expcube); GCTACubePsf psf(psfcube); GCTACubeBackground background(bkgcube); // If querying of energy dispersion cube is requested then // query it now if (query_edisp) { // If filename is valid then use energy dispersion ... if (is_valid_filename(edispcube)) { // Load energy dispersion cube GCTACubeEdisp edisp(edispcube); // Set response with all four cubes obs->response(exposure, psf, edisp, background); } // endif: energy dispersion cube was provided // ... otherwise work without energy dispersion else { // If energy dispersion was requested but no cube // was provided then throw an exception if (has_par("edisp") && (*this)["edisp"].boolean()) { std::string msg = "Energy dispersion requested but " "no energy dispersion cube was " "specified."; throw GException::invalid_value(G_SET_OBS_RESPONSE, msg); } // Set response without energy dispersion obs->response(exposure, psf, background); } // endelse: no energy dispersion was available } // endif: energy dispersion needed // ... otherwise work without energy dispersion else { obs->response(exposure, psf, background); } // Signal that response is available has_response = true; } // endif: filenames were available } // endif: expcube, psfcube and bkdcube parameters valid } // endif: observation contains a counts cube // If we have not yet response information then get it now from the // caldb and irf user parameters if (!has_response) { // Load response information std::string database = (*this)["caldb"].string(); std::string irf = (*this)["irf"].string(); // Create an XML element containing the database and IRF name. This // kluge will make sure that the information is later written // to the observation definition XML file, in case an observation // definition XML file is written. std::string parameter = "parameter name=\"Calibration\"" " database=\""+database+"\"" " response=\""+irf+"\""; GXmlElement xml; xml.append(parameter); // Create CTA response GCTAResponseIrf response(xml); // Attach response to observation obs->response(response); // Signal response availability has_response = true; } // endif: no response information was available // Return return; } /***********************************************************************//** * @brief Set observation boundaries for all CTA observations * * @param[in,out] obs Observation container * * Sets the observation boundaries for all CTA observations in the container * that contain event lists. If the energy boundaries of an event list are * empty, the method gets the * * emin: Minimum energy (TeV) * emax: Maximum energy (TeV) * * user parameters and sets a single energy boundary element @p [emin,emax]. * * If the RoI radius of the event list is invalid, the method gets the * * rad: Region of Interest radius (deg) * * user parameter to set the radius of the RoI, and uses the CTA pointing * direction to set the centre of the RoI. ***************************************************************************/ void ctool::set_obs_bounds(GObservations& obs) { // Loop over all observations in the container for (int i = 0; i < obs.size(); ++i) { // Get pointer to CTA observation GCTAObservation* cta = dynamic_cast(obs[i]); // Fall through if observation is not a CTA observation or if it // does not contain events if ((cta == NULL) || (!cta->has_events())) { continue; } // Get pointer on CTA event list GCTAEventList* list = const_cast (dynamic_cast(cta->events())); // Fall through if CTA observation does not contain an event list if (list == NULL) { continue; } // If there are no energy boundaries then read the "emin" and "emax" // user parameters and add them if (list->ebounds().is_empty()) { if (has_par("emin") && has_par("emax")) { double emin((*this)["emin"].real()); double emax((*this)["emax"].real()); GEbounds ebounds(GEnergy(emin, "TeV"), GEnergy(emax, "TeV")); list->ebounds(ebounds); } } // If there is no RoI then read the "rad" user parameters and use // the pointing direction to set the RoI if (list->roi().radius() == 0) { if (has_par("rad")) { double rad = (*this)["rad"].real(); GCTARoi roi(GCTAInstDir(cta->pointing().dir()), rad); list->roi(roi); } } } // endfor: looped over observations // Return return; } /***********************************************************************//** * @brief Get observation container * * @param[in] get_response Indicates whether response information should * be loaded (default: true) * * Get an observation container according to the user parameters. The method * supports loading of a individual FITS file or an observation definition * file in XML format. If the input filename is empty, parameters are read * to build a CTA observation from scratch. ***************************************************************************/ GObservations ctool::get_observations(const bool& get_response) { // Initialise empty observation container GObservations obs; // Get the filename from the input parameters std::string filename = (*this)["inobs"].filename(); // If no observation definition file has been specified then read all // parameters that are necessary to create an observation from scratch if (!is_valid_filename(filename)) { // Setup a new CTA observation GCTAObservation cta_obs = create_cta_obs(); // Get response if required if (get_response) { // Set response set_obs_response(&cta_obs); } // endif: response was required // Append observation to container obs.append(cta_obs); } // endif: filename was valid // ... otherwise we have a file name else { // If file is a FITS file then create an empty CTA observation // and load file into observation if (GFilename(filename).is_fits()) { // Allocate empty CTA observation GCTAObservation cta_obs; // Load data cta_obs.load(filename); // Get response if required if (get_response) { // Set response set_obs_response(&cta_obs); } // endif: response was required // Append observation to container obs.append(cta_obs); // Signal that no XML file should be used for storage m_use_xml = false; } // ... otherwise load file into observation container else { // Load observations from XML file obs.load(filename); // Get response if required if (get_response) { // For all observations that have no response, set the response // from the task parameters set_response(obs); } // endif: response was required // Set observation boundary parameters (emin, emax, rad) set_obs_bounds(obs); // Signal that XML file should be used for storage m_use_xml = true; } // endelse: file was an XML file } // Return observation container return obs; } /***********************************************************************//** * @brief Derives mean pointing from CTA observations * * @param[in] obs Observation container. * @return Pointing direction. * * Computes the mean pointing direction from all CTA observations in the * observation container. * * @todo This method does not work properly for wrap arounds in Right * Ascension. It certainly will also lead to bad results near the * celestial pole. ***************************************************************************/ GSkyDir ctool::get_mean_pointing(const GObservations& obs) { // Initialise values double xref = 0.0; double yref = 0.0; int n_pnt = 0; // Loop over all observations to compute mean pointings for (int i = 0; i < obs.size(); ++i) { // If we have a CTA observation then extract the pointing direction const GCTAObservation* cta_obs = dynamic_cast(obs[i]); if (cta_obs != NULL) { // Get pointing const GCTAPointing& pnt = cta_obs->pointing(); // Add to coordinates xref += pnt.dir().ra_deg(); yref += pnt.dir().dec_deg(); n_pnt += 1; } // endif: cta observation was valid } // endfor: loop over all observations // Signal if no pointing is found if (n_pnt < 1) { std::string msg = "No valid CTA observation has been found in " "observation list, hence no pointing information " "could be extracted. Use the \"usepnt=no\" " "option and specify the pointing explicitly."; throw GException::invalid_value(G_GET_MEAN_POINTING, msg); } // Calculate mean coordinates double ra = xref / double(n_pnt); double dec = yref / double(n_pnt); // Initialise sky dir GSkyDir dir = GSkyDir(); dir.radec_deg(ra,dec); // Return sky direction return dir; } /***********************************************************************//** * @brief Get current resident set size (physical memory use) in Bytes * * @return Physical memory use in Bytes. * * @todo This method is currently not used and can be removed if we do not * use it ***************************************************************************/ size_t ctool::get_current_rss(void) { // Initialize resident set size size_t rss = 0; // Determine resident set size (architecture dependent) // OSX #if defined(__APPLE__) && defined(__MACH__) #ifdef MACH_TASK_BASIC_INFO struct mach_task_basic_info info; mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT; if (task_info(mach_task_self( ), MACH_TASK_BASIC_INFO, (task_info_t)&info, &infoCount) == KERN_SUCCESS) { rss = (size_t)info.resident_size; } #else struct task_basic_info info; mach_msg_type_number_t info_count = TASK_BASIC_INFO_COUNT; if (task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&info, &info_count) == KERN_SUCCESS) { rss = (size_t)info.resident_size; } #endif // Linux #elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__) FILE* fp = NULL; if ((fp = fopen( "/proc/self/statm", "r" )) != NULL) { if (fscanf( fp, "%*s%ld", &rss ) == 1) { rss *= (size_t)sysconf(_SC_PAGESIZE); } fclose(fp); } // AIX, BSD, Solaris, and Unknown OS #else rss = 0; #endif // Return resident set size return rss; } /***********************************************************************//** * @brief Return observation header string * * @param[in] obs Pointer to observation. * @return String containing observation information. * * Returns a string that contains observation information, including the * instrument name, the observation name, and the observation ID. The format * of the string is * * XXX observation "name" (id=YYYYY) * ***************************************************************************/ std::string ctool::get_obs_header(const GObservation* obs) const { // Initialise header string std::string header = obs->instrument() + " observation"; // If observation name is not empty then add name if (!obs->name().empty()) { header += " \"" + obs->name() + "\""; } // If observation ID is not empty then add ID if (!obs->id().empty()) { header += " (id=" + obs->id() +")"; } // Return header return header; } /***********************************************************************//** * @brief Insert observation energy boundaries into list of energies * * @param[in] energies Energies. * @param[in] obs Observation container. * @return Energies. * * Inserts the energy boundaries of an observation in a list of @p energies. ***************************************************************************/ GEnergies ctool::insert_energy_boundaries(const GEnergies& energies, const GCTAObservation& obs) { // Create copy of input energies GEnergies engs = energies; // Get the energy boundaries of the event list GEbounds ebounds = obs.ebounds(); // Loop over all boundary energies for (int iebin = 0; iebin <= ebounds.size(); ++iebin) { // Get boundary energy GEnergy energy; if (iebin < ebounds.size()) { energy = ebounds.emin(iebin); } else { energy = ebounds.emax(); } // Get index before which the energy should be appended bool insert = true; int index = -1; for (int k = 0; k < engs.size(); ++k) { // If energy exists already then skip the boundary and examine the // next one. We consider here 1 MeV as being sufficiently close. if (std::abs(engs[k].MeV()-energy.MeV()) < 1.0) { insert = false; break; } // If energy is above the boundary energy we found the index if (engs[k] > energy) { index = k; break; } } // endfor: search index for insertion // Insert energy if requested if (insert) { // Insert or append energy if (index != -1) { engs.insert(index, energy); } else { engs.append(energy); } // Log energy insertion. Circumvent const correctness log_value(NORMAL, "Insert energy", energy.print()); } // endif: energy insertion requested } // endfor: looped over all energy boundaries // Return energies return engs; } /***********************************************************************//** * @brief Determine the counts cube layer usage. * * @param[in] cube_ebounds Energy boundaries of the counts cube. * @param[in] list_ebounds Energy boundaries of the event list. * @return Vector of usage flags. * * Determines a vector of counts cube layer usage flags that signal whether * an event should actually be filled in the counts cube or not. This makes * sure that no partially filled bins will exist in the counts cube. ***************************************************************************/ std::vector ctool::cube_layer_usage(const GEbounds& cube_ebounds, const GEbounds& list_ebounds) const { // Set energy margin const GEnergy energy_margin(0.01, "GeV"); // Initialise usage vector std::vector usage(cube_ebounds.size(), true); // Loop over all energy bins of the cube for (int iebin = 0; iebin < cube_ebounds.size(); ++iebin) { // If the counts cube energy bin is fully contained within the // energy boundaries of the event list the signal usage of this // counts cube bin. Partially overlapping energy bins are signaled // for non-usage, which avoids having partially filled bins in the // counts cube. Some margin is applied that effectively reduces the // width of the counts cube energy bin. This should cope with any // rounding errors that come from reading and writing the energy // boundary information to a FITS file. usage[iebin] = list_ebounds.contains(cube_ebounds.emin(iebin) + energy_margin, cube_ebounds.emax(iebin) - energy_margin); } // endfor: looped over all energy bins // Return usage return usage; } /***********************************************************************//** * @brief Set warning string if there are too few energies * * @param[in] energies Energies. * @return Warning string. * * Sets a warning string if there are too few @p energies in the container. * For a binned or stacked analysis to be accurate, at least 25 bins per * decade are required. If the provided number of energies is less than this * number a warning string will be returned. Otherwise an empty string will * be returned. ***************************************************************************/ std::string ctool::warn_too_few_energies(const GEnergies& energies) const { // Initialise warning std::string warning; // Retrieve the number of energies int n = energies.size(); // Compute the required number of energy bins double logEmin = std::log10(energies[0].TeV()); double logEmax = std::log10(energies[n-1].TeV()); int nrequired = int((logEmax - logEmin) * 25.0); // Warn if there are not enough energy bins if (n < nrequired) { warning.append("\nWARNING: Only "+gammalib::str(n-1)+" energy bins " "have been requested. This may be too few energy " "bins." "\n At least 25 bins per decade in energy are " "recommended, which for the given" "\n energy range would be "+ gammalib::str(nrequired)+" bins. Consider increasing " "the number of energy bins."); } // Return warning return warning; } /***********************************************************************//** * @brief Set warning string if file has no .xml suffix * * @param[in] filename Filename. * @return Warning string. * * Sets a warning string if the filename has not ".xml" suffix. ***************************************************************************/ std::string ctool::warn_xml_suffix(const GFilename& filename) const { // Initialise warning std::string warning; // Get filename suffix std::string fname = std::string(filename); std::string suffix = gammalib::tolower(fname.substr(fname.length()-4,4)); // Warn if there are not enough energy bins if (suffix != ".xml") { warning.append("WARNING: Name of observation definition output file " "\""+filename+"\" does not terminate with \".xml\"." "\n This is not an error, but may be misleading. " "It is recommended to use the suffix" "\n \".xml\" for observation definition files."); } // Return warning return warning; } /*========================================================================== = = = Private methods for SWIG = = = ==========================================================================*/ /***********************************************************************//** * @brief Dump help text in the console * * Dumps the help text for the ctool into the console. The help text is * located in the folder * * $CTOOLS//share/help/ * * of the ctools installation and the help file has the name "name().txt", * where name() stands for the name of the ctool. ***************************************************************************/ void ctool::provide_help(void) const { // Allocate line buffer const int n = 1000; char line[n]; // Build help file name std::string helpfile = name()+".txt"; // Get ctools environment variable char* ptr = std::getenv("CTOOLS"); if (ptr == NULL) { std::string msg = "CTOOLS environment variable not set, cannot " "display help file. Please set the CTOOLS " "environment variable."; throw GException::invalid_value(G_PROVIDE_HELP, msg); } // If help file exists then display it, otherwise notify that no // help is available std::string fname = std::string(ptr) + "/share/help/" + helpfile; FILE* fptr = fopen(fname.c_str(), "r"); if (fptr != NULL) { while (fgets(line, n, fptr) != NULL) { std::cout << std::string(line); } fclose(fptr); } else { std::cout << "No help available for "+name()+"." << std::endl; } // Return return; }