/*************************************************************************** * GCTAEdisp2D.cpp - CTA 2D energy dispersion class * * ----------------------------------------------------------------------- * * copyright (C) 2015-2016 by Florent Forest * * ----------------------------------------------------------------------- * * * * 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 GCTAEdisp2D.cpp * @brief CTA 2D energy dispersion class implementation * @author Florent Forest */ /* __ Includes ___________________________________________________________ */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "GTools.hpp" #include "GMath.hpp" #include "GException.hpp" #include "GIntegral.hpp" #include "GFilename.hpp" #include "GRan.hpp" #include "GFits.hpp" #include "GFitsTable.hpp" #include "GFitsBinTable.hpp" #include "GCTAEdisp2D.hpp" /* __ Method name definitions ____________________________________________ */ #define G_READ "GCTAEdisp2D::read(GFitsTable&)" /* __ Macros _____________________________________________________________ */ /* __ Coding definitions _________________________________________________ */ /* __ Debug definitions __________________________________________________ */ /* __ Constants __________________________________________________________ */ /*========================================================================== = = = Constructors/destructors = = = ==========================================================================*/ /***********************************************************************//** * @brief Void constructor * * Constructs empty energy dispersion. ***************************************************************************/ GCTAEdisp2D::GCTAEdisp2D(void) : GCTAEdisp() { // Initialise class members init_members(); // Return return; } /***********************************************************************//** * @brief File constructor * * @param[in] filename FITS file name. * * Constructs energy dispersion from a FITS file. ***************************************************************************/ GCTAEdisp2D::GCTAEdisp2D(const GFilename& filename) : GCTAEdisp() { // Initialise class members init_members(); // Load energy dispersion from file load(filename); // Return return; } /***********************************************************************//** * @brief Copy constructor * * @param[in] edisp Energy dispersion. * * Constructs energy dispersion by copying from another energy dispersion. ***************************************************************************/ GCTAEdisp2D::GCTAEdisp2D(const GCTAEdisp2D& edisp) : GCTAEdisp(edisp) { // Initialise class members init_members(); // Copy members copy_members(edisp); // Return return; } /***********************************************************************//** * @brief Destructor * * Destructs energy dispersion. ***************************************************************************/ GCTAEdisp2D::~GCTAEdisp2D(void) { // Free members free_members(); // Return return; } /*========================================================================== = = = Operators = = = ==========================================================================*/ /***********************************************************************//** * @brief Assignment operator * * @param[in] edisp Energy dispersion. * @return Energy dispersion. * * Assigns energy dispersion. ***************************************************************************/ GCTAEdisp2D& GCTAEdisp2D::operator=(const GCTAEdisp2D& edisp) { // Execute only if object is not identical if (this != &edisp) { // Copy base class members this->GCTAEdisp::operator=(edisp); // Free members free_members(); // Initialise private members init_members(); // Copy members copy_members(edisp); } // endif: object was not identical // Return this object return *this; } /***********************************************************************//** * @brief Return energy dispersion in units of s^-1 MeV^-1 * * @param[in] logEobs Log10 of the measured energy (TeV). * @param[in] logEsrc Log10 of the true photon energy (TeV). * @param[in] theta Offset angle in camera system (rad). Defaults to 0.0. * @param[in] phi Azimuth angle in camera system (rad). Not used in this method. * @param[in] zenith Zenith angle in Earth system (rad). Not used in this method. * @param[in] azimuth Azimuth angle in Earth system (rad). Not used in this method. * * Returns the energy dispersion in units of s^-1 MeV^-1 for a given observed * energy @p logEobs, true photon energy @p logEsrc and offset angle * @p theta. ***************************************************************************/ double GCTAEdisp2D::operator()(const double& logEobs, const double& logEsrc, const double& theta, const double& phi, const double& zenith, const double& azimuth) const { // Initalize edisp double edisp = 0.0; // Compute Eobs/Esrc double EobsOverEsrc = std::exp((logEobs-logEsrc) * gammalib::ln10); // Setup argument vector double arg[3]; arg[m_inx_etrue] = logEsrc; arg[m_inx_migra] = EobsOverEsrc; arg[m_inx_theta] = theta; // Compute edisp edisp = m_edisp(m_inx_matrix, arg[0], arg[1], arg[2]); // Return return edisp; } /*========================================================================== = = = Public methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Clear energy dispersion * * Clears energy dispersion. ***************************************************************************/ void GCTAEdisp2D::clear(void) { // Free class members free_members(); this->GCTAEdisp::free_members(); // Initialise members this->GCTAEdisp::init_members(); init_members(); // Return return; } /***********************************************************************//** * @brief Clone energy dispersion * * @return Deep copy of energy dispersion. * * Returns a pointer to a deep copy of the point spread function. ***************************************************************************/ GCTAEdisp2D* GCTAEdisp2D::clone(void) const { return new GCTAEdisp2D(*this); } /***********************************************************************//** * @brief Read energy dispersion from FITS table * * @param[in] table FITS table. * * @exception GException::invalid_value * Response table is not three-dimensional. * * Reads the energy dispersion form the FITS @p table. The following column * names are mandatory: * * ETRUE_LO - True energy lower bin boundaries * ETRUE_HI - True energy upper bin boundaries * MIGRA_LO - Migration lower bin boundaries * MIGRA_HI - Migration upper bin boundaries * THETA_LO - Offset angle lower bin boundaries * THETA_HI - Offset angle upper bin boundaries * MATRIX - Migration matrix * * The data are stored in the m_edisp member. The energy axis will be set to * log10, the offset angle axis to radians. * * The method assures that the energy dispersion is properly normalised. ***************************************************************************/ void GCTAEdisp2D::read(const GFitsTable& table) { // Clear response table m_edisp.clear(); // Read energy dispersion table m_edisp.read(table); // Get mandatory indices (throw exception if not found) m_inx_etrue = m_edisp.axis("ETRUE"); m_inx_migra = m_edisp.axis("MIGRA"); m_inx_theta = m_edisp.axis("THETA"); m_inx_matrix = m_edisp.table("MATRIX"); // Throw an exception if the table is not two-dimensional if (m_edisp.axes() != 3) { std::string msg = "Expected three-dimensional energy dispersion " "response table but found "+ gammalib::str(m_edisp.axes())+ " dimensions. Please specify a three-dimensional " "energy dispersion."; throw GException::invalid_value(G_READ, msg); } // Set true energy axis to logarithmic scale m_edisp.axis_log10(m_inx_etrue); // Set offset angle axis to radians m_edisp.axis_radians(m_inx_theta); // Get axes dimensions int etrue_size = m_edisp.axis_bins(m_inx_etrue); int migra_size = m_edisp.axis_bins(m_inx_migra); int theta_size = m_edisp.axis_bins(m_inx_theta); // Now normalize the migration matrix vectors. We do this by integrating // for each true energy and offset angle the measured energy. We perform // here two passes as the ebounds_obs() method depends on the content of // the matrix, and by having two passes we are sure that we get a // consistent energy interval. for (int pass = 0; pass < 2; ++pass) { // Initialise sums std::vector sums; sums.reserve(theta_size*etrue_size); // Loop over offset angle. for (int i_theta = 0; i_theta < theta_size; ++i_theta) { // Get offset angle (in radians) double theta = 0.5 * (m_edisp.axis_lo(m_inx_theta,i_theta) + m_edisp.axis_hi(m_inx_theta,i_theta)) * gammalib::deg2rad; // Loop over true photon energy for (int i_etrue = 0; i_etrue < etrue_size; ++i_etrue) { // Get energy double emin = std::log10(m_edisp.axis_lo(m_inx_etrue,i_etrue)); double emax = std::log10(m_edisp.axis_hi(m_inx_etrue,i_etrue)); double logEsrc = 0.5*(emin+emax); // Initialise integration double sum = 0.0; // Get integration boundaries GEbounds ebounds = ebounds_obs(logEsrc, theta); // Loop over all energy intervals for (int i = 0; i < ebounds.size(); ++i) { // Get energy boundaries emin = ebounds.emin(i).log10TeV(); emax = ebounds.emax(i).log10TeV(); // Setup integration function edisp_kern integrand(this, logEsrc, theta); GIntegral integral(&integrand); // Set integration precision integral.eps(1.0e-6); // Do Romberg integration sum += integral.romberg(emin, emax); } // endfor: looped over energy intervals // Store sum sums.push_back(sum); } // endfor: looped over Etrue } // endfor: looped over theta // Normalize vectors of migration matrix for all etrue and theta. for (int i_theta = 0, inx = 0; i_theta < theta_size; ++i_theta) { for (int i_etrue = 0; i_etrue < etrue_size; ++i_etrue, ++inx) { double sum = sums[inx]; if (sum > 0.0) { int offset = i_etrue + (i_theta*migra_size) * etrue_size; for (int k = 0, i = offset; k < migra_size; ++k, i += etrue_size) { m_edisp(m_inx_matrix,i) /= sum; } } } } } // endfor: made 2 passes // Set maximum energy dispersion value set_max_edisp(); // Return return; } /***********************************************************************//** * @brief Write energy dispersion into FITS binary table * * @param[in] table FITS binary table. * * Writes the energy dispersion into the FITS binary @p table. * * @todo Add keywords. ***************************************************************************/ void GCTAEdisp2D::write(GFitsBinTable& table) const { // Write background table m_edisp.write(table); // Return return; } /***********************************************************************//** * @brief Load energy dispersion from FITS file * * @param[in] filename FITS file name. * * Loads the energy dispersion from a FITS file. * * If no extension name is provided, the energy dispersion will be loaded * from the "ENERGY DISPERSION" extension. ***************************************************************************/ void GCTAEdisp2D::load(const GFilename& filename) { // Open FITS file GFits fits(filename); // Get energy dispersion table const GFitsTable& table = *fits.table(filename.extname("ENERGY DISPERSION")); // Read energy dispersion from table read(table); // Close FITS file fits.close(); // Store filename m_filename = filename; // Return return; } /***********************************************************************//** * @brief Save energy dispersion table into FITS file * * @param[in] filename FITS file name. * @param[in] clobber Overwrite existing file? (default: false) * * Saves energy dispersion into a FITS file. If a file with the given * @p filename does not yet exist it will be created, otherwise the method * opens the existing file. The method will create a (or replace an existing) * energy dispersion extension. The extension name can be specified as part * of the @p filename, or if no extension name is given, is assumed to be * "ENERGY DISPERSION". * * An existing file will only be modified if the @p clobber flag is set to * true. ***************************************************************************/ void GCTAEdisp2D::save(const GFilename& filename, const bool& clobber) const { // Get extension name std::string extname = filename.extname("ENERGY DISPERSION"); // Open or create FITS file (without extension name since the requested // extension may not yet exist in the file) GFits fits(filename.url(), true); // Remove extension if it exists already if (fits.contains(extname)) { fits.remove(extname); } // Create binary table GFitsBinTable table; // Write the background table write(table); // Set binary table extension name table.extname(extname); // Append table to FITS file fits.append(table); // Save to file fits.save(clobber); // Return return; } /***********************************************************************//** * @brief Simulate energy dispersion * * @param[in] ran Random number generator. * @param[in] logEsrc Log10 of the true photon energy (TeV). * @param[in] theta Offset angle in camera system (rad). * @param[in] phi Azimuth angle in camera system (rad). * @param[in] zenith Zenith angle in Earth system (rad). * @param[in] azimuth Azimuth angle in Earth system (rad). * * Draws observed energy value given a true energy @p logEsrc and offset * angle @p theta. ***************************************************************************/ GEnergy GCTAEdisp2D::mc(GRan& ran, const double& logEsrc, const double& theta, const double& phi, const double& zenith, const double& azimuth) const { // Get boundaries for observed energy GEbounds ebounds = ebounds_obs(logEsrc, theta, phi, zenith, azimuth); double emin = ebounds.emin().log10TeV(); double emax = ebounds.emax().log10TeV(); // Find energy by rejection method double ewidth = emax - emin; double logEobs = 0.5*(emin+emax); if (m_max_edisp > 0.0) { double f = 0.0; double ftest = 1.0; while (ftest > f) { logEobs = emin + ewidth * ran.uniform(); f = operator()(logEobs, logEsrc, theta, phi, zenith, azimuth); ftest = ran.uniform() * m_max_edisp; } } // Set observed energy GEnergy energy; energy.log10TeV(logEobs); // Return energy return energy; } /***********************************************************************//** * @brief Return observed energy interval that contains the energy dispersion. * * @param[in] logEsrc Log10 of the true photon energy (TeV). * @param[in] theta Offset angle in camera system (rad). * @param[in] phi Azimuth angle in camera system (rad). Not used. * @param[in] zenith Zenith angle in Earth system (rad). Not used. * @param[in] azimuth Azimuth angle in Earth system (rad). Not used. * * Returns the band of observed energies outside of which the energy * dispersion becomes negligible for a given true energy @p logEsrc and * offset angle @p theta. An energy is considered negligible if inferior * to 1e-6. ***************************************************************************/ GEbounds GCTAEdisp2D::ebounds_obs(const double& logEsrc, const double& theta, const double& phi, const double& zenith, const double& azimuth) const { // Compute only if parameters changed if (!m_ebounds_obs_computed || theta != m_last_theta_obs) { // Set computation flag m_ebounds_obs_computed = true; m_last_theta_obs = theta; // Compute ebounds_obs compute_ebounds_obs(theta, phi, zenith, azimuth); } // Search index only if logEsrc has changed if (logEsrc != m_last_logEsrc) { // Store last log(Esrc) m_last_logEsrc = logEsrc; // Find right index with bisection int low = 0; int high = m_ebounds_obs.size() - 1; while ((high-low) > 1) { int mid = (low+high) / 2; double e = m_edisp.axis_lo(m_inx_etrue, mid); if (logEsrc < std::log10(e)) { high = mid; } else { low = mid; } } // Index found m_index_obs = low; } // Return energy boundaries return m_ebounds_obs[m_index_obs]; } /***********************************************************************//** * @brief Return true energy interval that contains the energy dispersion. * * @param[in] logEobs Log10 of the observed event energy (TeV). * @param[in] theta Offset angle in camera system (rad). * @param[in] phi Azimuth angle in camera system (rad). Not used. * @param[in] zenith Zenith angle in Earth system (rad). Not used. * @param[in] azimuth Azimuth angle in Earth system (rad). Not used. * * Returns the band of true photon energies outside of which the energy * dispersion becomes negligible for a given observed energy @p logEobs and * offset angle @p theta. An energy in considered negligible if inferior to * 1e-6. ***************************************************************************/ GEbounds GCTAEdisp2D::ebounds_src(const double& logEobs, const double& theta, const double& phi, const double& zenith, const double& azimuth) const { // Compute only if parameters changed if (!m_ebounds_src_computed || theta != m_last_theta_src) { // Set computation flag m_ebounds_src_computed = true; m_last_theta_src = theta; // Compute ebounds_src compute_ebounds_src(theta, phi, zenith, azimuth); } // Search index only if logEobs has changed if (logEobs != m_last_logEobs) { // Store observed energy m_last_logEobs = logEobs; // Find right index with bisection int low = 0; int high = m_ebounds_src.size() - 1; while ((high-low) > 1) { int mid = (low+high) / 2; double e = m_edisp.axis_lo(m_inx_etrue, mid); if (logEobs < std::log10(e)) { high = mid; } else { low = mid; } } // Index found m_index_src = low; } // Return energy boundaries return m_ebounds_src[m_index_src]; } /***********************************************************************//** * @brief Print energy dispersion information * * @param[in] chatter Chattiness (defaults to NORMAL). * @return String containing energy dispersion information. ***************************************************************************/ std::string GCTAEdisp2D::print(const GChatter& chatter) const { // Initialise result string std::string result; // Continue only if chatter is not silent if (chatter != SILENT) { // Compute energy boundaries in TeV double emin = m_edisp.axis_lo(m_inx_etrue,0); double emax = m_edisp.axis_hi(m_inx_etrue, m_edisp.axis_bins(m_inx_etrue)-1); // Compute mingration double mmin = m_edisp.axis_lo(m_inx_migra,0); double mmax = m_edisp.axis_hi(m_inx_migra, m_edisp.axis_bins(m_inx_migra)-1); // Compute offset angle boundaries in deg double omin = m_edisp.axis_lo(m_inx_theta,0); double omax = m_edisp.axis_hi(m_inx_theta, m_edisp.axis_bins(m_inx_theta)-1); // Append header result.append("=== GCTAEdisp2D ==="); // Append information result.append("\n"+gammalib::parformat("Filename")+m_filename); result.append("\n"+gammalib::parformat("Number of energy bins") + gammalib::str(m_edisp.axis_bins(m_inx_etrue))); result.append("\n"+gammalib::parformat("Number of migration bins") + gammalib::str(m_edisp.axis_bins(m_inx_migra))); result.append("\n"+gammalib::parformat("Number of offset bins") + gammalib::str(m_edisp.axis_bins(m_inx_theta))); result.append("\n"+gammalib::parformat("Log10(Energy) range")); result.append(gammalib::str(emin)+" - "+gammalib::str(emax)+" TeV"); result.append("\n"+gammalib::parformat("Migration range")); result.append(gammalib::str(mmin)+" - "+gammalib::str(mmax)); result.append("\n"+gammalib::parformat("Offset angle range")); result.append(gammalib::str(omin)+" - "+gammalib::str(omax)+" deg"); } // endif: chatter was not silent // Return result return result; } /*========================================================================== = = = Private methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Initialise class members ***************************************************************************/ void GCTAEdisp2D::init_members(void) { // Initialise members m_filename.clear(); m_edisp.clear(); m_inx_etrue = 0; m_inx_migra = 1; m_inx_theta = 2; m_inx_matrix = 0; // Initialise computation cache m_ebounds_obs_computed = false; m_ebounds_src_computed = false; m_last_theta_obs = -1.0; m_last_theta_src = -1.0; m_last_logEsrc = -30.0; m_last_logEobs = -30.0; m_index_obs = 0; m_index_src = 0; m_max_edisp = 0.0; m_ebounds_obs.clear(); m_ebounds_src.clear(); // Return return; } /***********************************************************************//** * @brief Copy class members * * @param[in] edisp Energy dispersion. ***************************************************************************/ void GCTAEdisp2D::copy_members(const GCTAEdisp2D& edisp) { // Copy members m_filename = edisp.m_filename; m_edisp = edisp.m_edisp; m_inx_etrue = edisp.m_inx_etrue; m_inx_migra = edisp.m_inx_migra; m_inx_theta = edisp.m_inx_theta; m_inx_matrix = edisp.m_inx_matrix; // Copy computation cache m_ebounds_obs_computed = edisp.m_ebounds_obs_computed; m_ebounds_src_computed = edisp.m_ebounds_src_computed; m_last_theta_obs = edisp.m_last_theta_obs; m_last_theta_src = edisp.m_last_theta_src; m_last_logEsrc = edisp.m_last_logEsrc; m_last_logEobs = edisp.m_last_logEobs; m_index_obs = edisp.m_index_obs; m_index_src = edisp.m_index_src; m_max_edisp = edisp.m_max_edisp; m_ebounds_obs = edisp.m_ebounds_obs; m_ebounds_src = edisp.m_ebounds_src; // Return return; } /***********************************************************************//** * @brief Delete class members ***************************************************************************/ void GCTAEdisp2D::free_members(void) { // Return return; } /***********************************************************************//** * @brief Compute ebounds_obs vector * * @param[in] theta Offset angle (rad). * @param[in] phi Azimuth angle (rad). * @param[in] zenith Zenith angle (rad). * @param[in] azimuth Azimuth angle (rad). * * Computes for all true energies the energy boundaries of the observed * energies covered by valid migration matrix elements. Only matrix elements * with values >= 1.0e-12 are considered as valid elements. In case that no * matrix elements are found of a given true energy, the interval of observed * energies will be set to [1 TeV, 1 TeV] (i.e. an empty interval). ***************************************************************************/ void GCTAEdisp2D::compute_ebounds_obs(const double& theta, const double& phi, const double& zenith, const double& azimuth) const { // Clear ebounds_obs vector m_ebounds_obs.clear(); // Set epsilon const double eps = 1.0e-12; // Determine number of true energy and migration bins int n_bins = m_edisp.axis_bins(m_inx_etrue); int n_migra = m_edisp.axis_bins(m_inx_migra); // Loop over Esrc for (int isrc = 0; isrc < n_bins; ++isrc) { // Set Esrc double Esrc = std::sqrt(m_edisp.axis_hi(m_inx_etrue,isrc) * m_edisp.axis_lo(m_inx_etrue,isrc)); double logEsrc = std::log10(Esrc); // Initialise results double logEobsMin = 0.0; double logEobsMax = 0.0; bool minFound = false; bool maxFound = false; // Find minimum boundary for (int i = 0; i < n_migra; ++i) { // Compute EobsOverEsrc value double EobsOverEsrc = 0.5 * (m_edisp.axis_hi(m_inx_migra,i) + m_edisp.axis_lo(m_inx_migra,i)); // Get matrix term double arg[3]; arg[m_inx_etrue] = logEsrc; arg[m_inx_migra] = EobsOverEsrc; arg[m_inx_theta] = theta; double edisp = m_edisp(m_inx_matrix, arg[0], arg[1], arg[2]); // Find first non-negligible matrix term if (edisp >= eps) { minFound = true; logEobsMin = std::log10(EobsOverEsrc * Esrc); break; } } // endfor: find minimum boundary // Find maximum boundary for (int i = n_migra-1; i >= 0; i--) { // Compute EobsOverEsrc value double EobsOverEsrc = 0.5 * (m_edisp.axis_hi(m_inx_migra,i) + m_edisp.axis_lo(m_inx_migra,i)); // Get matrix term double arg[3]; arg[m_inx_etrue] = logEsrc; arg[m_inx_migra] = EobsOverEsrc; arg[m_inx_theta] = theta; double edisp = m_edisp(m_inx_matrix, arg[0], arg[1], arg[2]); // Find first non-negligible matrix term if (edisp >= eps) { maxFound = true; logEobsMax = std::log10(EobsOverEsrc * Esrc); break; } } // endfor: find maximum boundary // If we did not find boundaries then set the interval to // a zero interval for safety if (!minFound || !maxFound) { logEobsMin = 0.0; logEobsMax = 0.0; } // Set energy boundaries GEnergy emin; GEnergy emax; emin.log10TeV(logEobsMin); emax.log10TeV(logEobsMax); // Add energy boundaries to vector m_ebounds_obs.push_back(GEbounds(emin, emax)); } // endfor: looped over logEsrc // Return return; } /***********************************************************************//** * @brief Compute ebounds_src vector * * @param[in] theta Offset angle (rad). * @param[in] phi Azimuth angle (rad). * @param[in] zenith Zenith angle (rad). * @param[in] azimuth Azimuth angle (rad). * * Computes for all observed energies the energy boundaries of the true * energies covered by valid migration matrix elements. Only matrix elements * with values >= 1.0e-12 are considered as valid elements. In case that no * matrix elements are found of a given observed energy, the interval of true * energies will be set to [1 TeV, 1 TeV] (i.e. an empty interval). ***************************************************************************/ void GCTAEdisp2D::compute_ebounds_src(const double& theta, const double& phi, const double& zenith, const double& azimuth) const { // Clear ebounds_src vector m_ebounds_src.clear(); // Set epsilon const double eps = 1.0e-12; // Determine number of true energy bins int n_bins = m_edisp.axis_bins(m_inx_etrue); // Loop over Eobs for (int iobs = 0; iobs < n_bins; ++iobs) { // Set Eobs double Eobs = std::sqrt(m_edisp.axis_hi(m_inx_etrue,iobs) * m_edisp.axis_lo(m_inx_etrue,iobs)); double logEobs = std::log10(Eobs); // Initialise results double logEsrcMin = 0.0; double logEsrcMax = 0.0; bool minFound = false; bool maxFound = false; // Find minimum boundary for (int isrc = 0; isrc < n_bins; ++isrc) { // Set Esrc double Esrc = std::sqrt(m_edisp.axis_hi(m_inx_etrue,isrc) * m_edisp.axis_lo(m_inx_etrue,isrc)); double logEsrc = std::log10(Esrc); // Get matrix term double edisp = operator()(logEobs, logEsrc, theta); // Find first non-negligible matrix term if (edisp >= eps) { minFound = true; logEsrcMin = logEsrc; break; } } // endfor: find minimum boundary // Find maximum boundary for (int isrc = n_bins-1; isrc >= 0; isrc--) { // Set Esrc double Esrc = std::sqrt(m_edisp.axis_hi(m_inx_etrue,isrc) * m_edisp.axis_lo(m_inx_etrue,isrc)); double logEsrc = std::log10(Esrc); // Get matrix term double edisp = operator()(logEobs, logEsrc, theta); // Find first non-negligible matrix term if (edisp >= eps) { maxFound = true; logEsrcMax = logEsrc; break; } } // endfor: find maximum boundary // If we did not find boundaries then set the interval to // a zero interval for safety if (!minFound || !maxFound) { logEsrcMin = 0.0; logEsrcMax = 0.0; } // Set energy boundaries GEnergy emin; GEnergy emax; emin.log10TeV(logEsrcMin); emax.log10TeV(logEsrcMax); // Add energy boundaries to vector m_ebounds_src.push_back(GEbounds(emin, emax)); } // endfor : loopend over observed energy // Return return; } /***********************************************************************//** * @brief Set maximum energy dispersion value ***************************************************************************/ void GCTAEdisp2D::set_max_edisp(void) const { // Initialise maximum m_max_edisp = 0.0; // Loop over all response table elements for (int i = 0; i < m_edisp.elements(); ++i) { double value = m_edisp(m_inx_matrix,i); if (value > m_max_edisp) { m_max_edisp = value; } } // Return return; } /***********************************************************************//** * @brief Integration kernel for edisp_kern() class * * @param[in] x Function value. * * This method implements the integration kernel needed for the edisp_kern() * class. ***************************************************************************/ double GCTAEdisp2D::edisp_kern::eval(const double& x) { // Get function value double value = m_parent->operator()(x, m_logEsrc, m_theta); // Compile option: Check for NaN #if defined(G_NAN_CHECK) if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) { std::cout << "*** ERROR: GCTAEdisp2D::edisp_kern::eval"; std::cout << "(x=" << x << "): "; std::cout << " NaN/Inf encountered"; std::cout << " (value=" << value; std::cout << ")" << std::endl; } #endif // Return value return value; }