/*************************************************************************** * GCOMResponse.cpp - COMPTEL Response class * * ----------------------------------------------------------------------- * * copyright (C) 2012-2021 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 GCOMResponse.cpp * @brief COMPTEL response class implementation * @author Juergen Knoedlseder */ /* __ Includes ___________________________________________________________ */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "GTools.hpp" #include "GMath.hpp" #include "GFits.hpp" #include "GCaldb.hpp" #include "GEvent.hpp" #include "GPhoton.hpp" #include "GSource.hpp" #include "GEnergy.hpp" #include "GTime.hpp" #include "GObservation.hpp" #include "GFitsImage.hpp" #include "GFitsImageFloat.hpp" #include "GModelSky.hpp" #include "GModelSpatialPointSource.hpp" #include "GModelSpectralConst.hpp" #include "GCOMResponse.hpp" #include "GCOMObservation.hpp" #include "GCOMEventCube.hpp" #include "GCOMEventBin.hpp" #include "GCOMInstDir.hpp" /* __ Method name definitions ____________________________________________ */ #define G_IRF "GCOMResponse::irf(GEvent&, GPhoton&, GObservation&)" #define G_IRF_SPATIAL "GCOMResponse::irf_spatial(GEvent&, GSource&, "\ "GObservation&)" #define G_NROI "GCOMResponse::nroi(GModelSky&, GEnergy&, GTime&, "\ "GObservation&)" #define G_EBOUNDS "GCOMResponse::ebounds(GEnergy&)" #define G_IRF_PTSRC "GCOMResponse::irf_ptsrc(GModelSky&, GObservation&, "\ "GMatrix*)" #define G_IRF_RADIAL "GCOMResponse::irf_radial(GModelSky&, GObservation&, "\ "GMatrix*)" #define G_IRF_ELLIPTICAL "GCOMResponse::irf_elliptical(GModelSky&, "\ " GObservation&, GMatrix*)" #define G_IRF_DIFFUSE "GCOMResponse::irf_diffuse(GModelSky&, GObservation&, "\ "GMatrix*)" /* __ Macros _____________________________________________________________ */ /* __ Coding definitions _________________________________________________ */ #define G_USE_DRM_CUBE /* __ Debug definitions __________________________________________________ */ /* __ Constants __________________________________________________________ */ /*========================================================================== = = = Constructors/destructors = = = ==========================================================================*/ /***********************************************************************//** * @brief Void constructor * * Creates an empty COMPTEL response. ***************************************************************************/ GCOMResponse::GCOMResponse(void) : GResponse() { // Initialise members init_members(); // Return return; } /***********************************************************************//** * @brief Copy constructor * * @param[in] rsp COM response. **************************************************************************/ GCOMResponse::GCOMResponse(const GCOMResponse& rsp) : GResponse(rsp) { // Initialise members init_members(); // Copy members copy_members(rsp); // Return return; } /***********************************************************************//** * @brief Response constructor * * @param[in] caldb Calibration database. * @param[in] iaqname IAQ file name. * * Create COMPTEL response by loading an IAQ file from a calibration * database. ***************************************************************************/ GCOMResponse::GCOMResponse(const GCaldb& caldb, const std::string& iaqname) : GResponse() { // Initialise members init_members(); // Set calibration database this->caldb(caldb); // Load IRF this->load(iaqname); // Return return; } /***********************************************************************//** * @brief Destructor * * Destroys instance of COMPTEL response object. ***************************************************************************/ GCOMResponse::~GCOMResponse(void) { // Free members free_members(); // Return return; } /*========================================================================== = = = Operators = = = ==========================================================================*/ /***********************************************************************//** * @brief Assignment operator * * @param[in] rsp COMPTEL response. * @return COMPTEL response. * * Assigns COMPTEL response object to another COMPTEL response object. The * assignment performs a deep copy of all information, hence the original * object from which the assignment has been performed can be destroyed after * this operation without any loss of information. ***************************************************************************/ GCOMResponse& GCOMResponse::operator=(const GCOMResponse& rsp) { // Execute only if object is not identical if (this != &rsp) { // Copy base class members this->GResponse::operator=(rsp); // Free members free_members(); // Initialise members init_members(); // Copy members copy_members(rsp); } // endif: object was not identical // Return this object return *this; } /*========================================================================== = = = Public methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Clear instance * * Clears COMPTEL response object by resetting all members to an initial * state. Any information that was present in the object before will be lost. ***************************************************************************/ void GCOMResponse::clear(void) { // Free class members (base and derived classes, derived class first) free_members(); this->GResponse::free_members(); // Initialise members this->GResponse::init_members(); init_members(); // Return return; } /***********************************************************************//** * @brief Clone instance * * @return Pointer to deep copy of COMPTEL response. ***************************************************************************/ GCOMResponse* GCOMResponse::clone(void) const { return new GCOMResponse(*this); } /***********************************************************************//** * @brief Return value of instrument response function * * @param[in] event Observed event. * @param[in] photon Incident photon. * @param[in] obs Observation. * @return Instrument response function (\f$cm^2 sr^{-1}\f$) * * @exception GException::invalid_argument * Observation is not a COMPTEL observation. * Event is not a COMPTEL event bin. * @exception GException::invalid_value * Response not initialised with a valid IAQ * * Returns the instrument response function for a given observed photon * direction as function of the assumed true photon direction. The result * is given by * * \f[ * {\tt IRF} = \frac{{\tt IAQ} \times {\tt DRG} \times {\tt DRX}} * {T \times {\tt TOFCOR}} * \f] * * where * - \f${\tt IRF}\f$ is the instrument response function (\f$cm^2 sr^{-1}\f$), * - \f${\tt IAQ}\f$ is the COMPTEL response matrix (\f$sr^{-1}\f$), * - \f${\tt DRG}\f$ is the geometry factor (probability), * - \f${\tt DRX}\f$ is the exposure (\f$cm^2 s\f$), * - \f$T\f$ is the ontime (\f$s\f$), and * - \f${\tt TOFCOR}\f$ is a correction that accounts for the Time of Flight * selection window. * * The observed photon direction is spanned by the three values \f$\Chi\f$, * \f$\Psi\f$, and \f$\bar{\varphi})\f$. \f$\Chi\f$ and \f$\Psi\f$ is the * scatter direction of the event, given in sky coordinates. * \f$\bar{\varphi}\f$ is the Compton scatter angle, computed from the * energy deposits in the two detector planes. ***************************************************************************/ double GCOMResponse::irf(const GEvent& event, const GPhoton& photon, const GObservation& obs) const { // Extract COMPTEL observation const GCOMObservation* observation = dynamic_cast(&obs); if (observation == NULL) { std::string cls = std::string(typeid(&obs).name()); std::string msg = "Observation of type \""+cls+"\" is not a COMPTEL " "observations. Please specify a COMPTEL observation " "as argument."; throw GException::invalid_argument(G_IRF, msg); } // Extract COMPTEL event cube const GCOMEventCube* cube = dynamic_cast(observation->events()); if (cube == NULL) { std::string cls = std::string(typeid(&cube).name()); std::string msg = "Event cube of type \""+cls+"\" is not a COMPTEL " "event cube. Please specify a COMPTEL event cube " "as argument."; throw GException::invalid_argument(G_IRF, msg); } // Extract COMPTEL event bin const GCOMEventBin* bin = dynamic_cast(&event); if (bin == NULL) { std::string cls = std::string(typeid(&event).name()); std::string msg = "Event of type \""+cls+"\" is not a COMPTEL event. " "Please specify a COMPTEL event as argument."; throw GException::invalid_argument(G_IRF, msg); } // Throw an exception if COMPTEL response is not set or if if (m_iaq.empty()) { std::string msg = "COMPTEL response is empty. Please initialise the " "response with an \"IAQ\"."; throw GException::invalid_value(G_IRF, msg); } else if (m_phigeo_bin_size == 0.0) { std::string msg = "COMPTEL response has a zero Phigeo bin size. " "Please initialise the response with a valid " "\"IAQ\"."; throw GException::invalid_value(G_IRF, msg); } // Extract event parameters const GCOMInstDir& obsDir = bin->dir(); // Extract photon parameters const GSkyDir& srcDir = photon.dir(); const GTime& srcTime = photon.time(); // Compute angle between true photon arrival direction and scatter // direction (Chi,Psi) double phigeo = srcDir.dist_deg(obsDir.dir()); // Compute scatter angle index int iphibar = int(obsDir.phibar() / m_phibar_bin_size); // Initialise IRF double iaq = 0.0; double irf = 0.0; // Extract IAQ value by linear inter/extrapolation in Phigeo if (iphibar < m_phibar_bins) { double phirat = phigeo / m_phigeo_bin_size; // 0.5 at bin centre int iphigeo = int(phirat); // index into which Phigeo falls double eps = phirat - iphigeo - 0.5; // 0.0 at bin centre [-0.5, 0.5[ if (iphigeo < m_phigeo_bins) { int i = iphibar * m_phigeo_bins + iphigeo; if (eps < 0.0) { // interpolate towards left if (iphigeo > 0) { iaq = (1.0 + eps) * m_iaq[i] - eps * m_iaq[i-1]; } else { iaq = (1.0 - eps) * m_iaq[i] + eps * m_iaq[i+1]; } } else { // interpolate towards right if (iphigeo < m_phigeo_bins-1) { iaq = (1.0 - eps) * m_iaq[i] + eps * m_iaq[i+1]; } else { iaq = (1.0 + eps) * m_iaq[i] - eps * m_iaq[i-1]; } } } } // Continue only if IAQ is positive if (iaq > 0.0) { // Get DRG value (unit: probability) double drg = observation->drg().map()(obsDir.dir(), iphibar); // Get DRX value (unit: cm^2 sec) double drx = observation->drx().map()(srcDir); // Get ontime (unit: s) double ontime = observation->ontime(); // Get ToF correction double tofcor = cube->dre().tof_correction(); // Compute IRF value irf = iaq * drg * drx / (ontime * tofcor); // Apply deadtime correction irf *= obs.deadc(srcTime); // Make sure that IRF is positive if (irf < 0.0) { irf = 0.0; } // Compile option: Check for NaN/Inf #if defined(G_NAN_CHECK) if (gammalib::is_notanumber(irf) || gammalib::is_infinite(irf)) { std::cout << "*** ERROR: GCOMResponse::irf:"; std::cout << " NaN/Inf encountered"; std::cout << " (irf=" << irf; std::cout << ", iaq=" << iaq; std::cout << ", drg=" << drg; std::cout << ", drx=" << drx; std::cout << ")"; std::cout << std::endl; } #endif } // endif: IAQ was positive // Return IRF value return irf; } /***********************************************************************//** * @brief Return integral of event probability for a given sky model over ROI * * @param[in] model Sky model. * @param[in] obsEng Observed photon energy. * @param[in] obsTime Observed photon arrival time. * @param[in] obs Observation. * @return 0.0 * * @exception GException::feature_not_implemented * Method is not implemented. ***************************************************************************/ double GCOMResponse::nroi(const GModelSky& model, const GEnergy& obsEng, const GTime& obsTime, const GObservation& obs) const { // Method is not implemented std::string msg = "Spatial integration of sky model over the data space " "is not implemented."; throw GException::feature_not_implemented(G_NROI, msg); // Return Npred return (0.0); } /***********************************************************************//** * @brief Return true energy boundaries for a specific observed energy * * @param[in] obsEnergy Observed Energy. * @return True energy boundaries for given observed energy. * * @exception GException::feature_not_implemented * Method is not implemented. ***************************************************************************/ GEbounds GCOMResponse::ebounds(const GEnergy& obsEnergy) const { // Initialise an empty boundary object GEbounds ebounds; // Throw an exception std::string msg = "Energy dispersion not implemented."; throw GException::feature_not_implemented(G_EBOUNDS, msg); // Return energy boundaries return ebounds; } /***********************************************************************//** * @brief Load COMPTEL response. * * @param[in] rspname COMPTEL response name. * * Loads the COMPTEL response with specified name @p rspname. * * The method first attempts to interpret @p rspname as a file name and to * load the corresponding response. * * If @p rspname is not a FITS file the method searches for an appropriate * response in the calibration database. If no appropriate response is found, * the method takes the CALDB root path and response name to build the full * path to the response file, and tries to load the response from these * paths. * * If also this fails an exception is thrown. ***************************************************************************/ void GCOMResponse::load(const std::string& rspname) { // Clear instance but conserve calibration database GCaldb caldb = m_caldb; clear(); m_caldb = caldb; // Save response name m_rspname = rspname; // Interpret response name as a FITS file name GFilename filename(rspname); // If the filename does not exist the try getting the response from the // calibration database if (!filename.is_fits()) { // Get GCaldb response filename = m_caldb.filename("","","IAQ","","",rspname); // If filename is empty then build filename from CALDB root path // and response name if (filename.is_empty()) { filename = gammalib::filepath(m_caldb.rootdir(), rspname); if (!filename.exists()) { GFilename testname = filename + ".fits"; if (testname.exists()) { filename = testname; } } } } // endif: response name is not a FITS file // Open FITS file GFits fits(filename); // Get IAQ image const GFitsImage& iaq = *fits.image(0); // Read IAQ read(iaq); // Close IAQ FITS file fits.close(); // Return return; } /***********************************************************************//** * @brief Read COMPTEL response from FITS image. * * @param[in] image FITS image. * * Read the COMPTEL response from IAQ FITS file and convert the IAQ values * into a probability per steradian. * * The IAQ values are divided by the solid angle of a Phigeo bin which is * given by * * \f{eqnarray*}{ * \Omega & = & 2 \pi \left[ * \left( * 1 - \cos \left( \varphi_{\rm geo} + * \frac{1}{2} \Delta \varphi_{\rm geo} \right) * \right) - * \left( * 1 - \cos \left( \varphi_{\rm geo} - * \frac{1}{2} \Delta \varphi_{\rm geo} \right) * \right) \right] \\ * &=& 2 \pi \left[ * \cos \left( \varphi_{\rm geo} - * \frac{1}{2} \Delta \varphi_{\rm geo} \right) - * \cos \left( \varphi_{\rm geo} + * \frac{1}{2} \Delta \varphi_{\rm geo} \right) * \right] \\ * &=& 4 \pi \sin \left( \varphi_{\rm geo} \right) * \sin \left( \frac{1}{2} \Delta \varphi_{\rm geo} \right) * \f} ***************************************************************************/ void GCOMResponse::read(const GFitsImage& image) { // Continue only if there are two image axis if (image.naxis() == 2) { // Store IAQ dimensions m_phigeo_bins = image.naxes(0); m_phibar_bins = image.naxes(1); // Store IAQ axes definitions m_phigeo_ref_value = image.real("CRVAL1"); m_phigeo_ref_pixel = image.real("CRPIX1"); m_phigeo_bin_size = image.real("CDELT1"); m_phibar_ref_value = image.real("CRVAL2"); m_phibar_ref_pixel = image.real("CRPIX2"); m_phibar_bin_size = image.real("CDELT2"); // Get axes minima (values of first bin) m_phigeo_min = m_phigeo_ref_value + (1.0-m_phigeo_ref_pixel) * m_phigeo_bin_size; m_phibar_min = m_phibar_ref_value + (1.0-m_phibar_ref_pixel) * m_phibar_bin_size; // Compute IAQ size. Continue only if size is positive int size = m_phigeo_bins * m_phibar_bins; if (size > 0) { // Allocate memory for IAQ m_iaq.assign(size, 0.0); // Copy over IAQ values for (int i = 0; i < size; ++i) { m_iaq[i] = image.pixel(i); } } // endif: size was positive // Precompute variable for conversion double phigeo_min = m_phigeo_min * gammalib::deg2rad; double phigeo_bin = m_phigeo_bin_size * gammalib::deg2rad; double omega0 = gammalib::fourpi * std::sin(0.5 * phigeo_bin); // Convert IAQ matrix from probability per Phigeo bin into a // probability per steradian for (int iphigeo = 0; iphigeo < m_phigeo_bins; ++iphigeo) { double phigeo = iphigeo * phigeo_bin + phigeo_min; double omega = omega0 * std::sin(phigeo); for (int iphibar = 0; iphibar < m_phibar_bins; ++iphibar) { m_iaq[iphigeo+iphibar*m_phigeo_bins] /= omega; } } } // endif: image had two axes // Return return; } /***********************************************************************//** * @brief Write COMPTEL response into FITS image. * * @param[in] image FITS image. * * Writes the COMPTEL response into an IAQ FITS file. ***************************************************************************/ void GCOMResponse::write(GFitsImageFloat& image) const { // Continue only if response is not empty if (m_phigeo_bins > 0 && m_phibar_bins > 0) { // Initialise image image = GFitsImageFloat(m_phigeo_bins, m_phibar_bins); // Convert IAQ matrix from probability per steradian into //probability per Phigeo bin double omega0 = gammalib::fourpi * std::sin(0.5 * m_phigeo_bin_size * gammalib::deg2rad); for (int iphigeo = 0; iphigeo < m_phigeo_bins; ++iphigeo) { double phigeo = iphigeo * m_phigeo_bin_size + m_phigeo_min; double omega = omega0 * std::sin(phigeo * gammalib::deg2rad); for (int iphibar = 0; iphibar < m_phibar_bins; ++iphibar) { int i = iphigeo + iphibar*m_phigeo_bins; image(iphigeo, iphibar) = m_iaq[i] * omega; } } // Set header keywords image.card("CTYPE1", "Phigeo", "Geometrical scatter angle"); image.card("CRVAL1", m_phigeo_ref_value, "[deg] Geometrical scatter angle for reference bin"); image.card("CDELT1", m_phigeo_bin_size, "[deg] Geometrical scatter angle bin size"); image.card("CRPIX1", m_phigeo_ref_pixel, "Reference bin of geometrical scatter angle"); image.card("CTYPE2", "Phibar", "Compton scatter angle"); image.card("CRVAL2", m_phibar_ref_value, "[deg] Compton scatter angle for reference bin"); image.card("CDELT2", m_phibar_bin_size, "[deg] Compton scatter angle bin size"); image.card("CRPIX2", m_phibar_ref_pixel, "Reference bin of Compton scatter angle"); image.card("BUNIT", "Probability per bin", "Unit of bins"); image.card("TELESCOP", "GRO", "Name of telescope"); image.card("INSTRUME", "COMPTEL", "Name of instrument"); image.card("ORIGIN", "GammaLib", "Origin of FITS file"); image.card("OBSERVER", "Unknown", "Observer that created FITS file"); } // endif: response was not empty // Return return; } /***********************************************************************//** * @brief Print COMPTEL response information * * @param[in] chatter Chattiness. * @return String containing COMPTEL response information. ***************************************************************************/ std::string GCOMResponse::print(const GChatter& chatter) const { // Initialise result string std::string result; // Continue only if chatter is not silent if (chatter != SILENT) { // Append header result.append("=== GCOMResponse ==="); // Append response name result.append("\n"+gammalib::parformat("Response name")+m_rspname); // EXPLICIT: Append detailed information if (chatter >= EXPLICIT) { // Append information result.append("\n"+gammalib::parformat("Number of Phigeo bins")); result.append(gammalib::str(m_phigeo_bins)); result.append("\n"+gammalib::parformat("Number of Phibar bins")); result.append(gammalib::str(m_phibar_bins)); result.append("\n"+gammalib::parformat("Phigeo reference value")); result.append(gammalib::str(m_phigeo_ref_value)+" deg"); result.append("\n"+gammalib::parformat("Phigeo reference pixel")); result.append(gammalib::str(m_phigeo_ref_pixel)); result.append("\n"+gammalib::parformat("Phigeo bin size")); result.append(gammalib::str(m_phigeo_bin_size)+" deg"); result.append("\n"+gammalib::parformat("Phigeo first bin value")); result.append(gammalib::str(m_phigeo_min)+" deg"); result.append("\n"+gammalib::parformat("Phibar reference value")); result.append(gammalib::str(m_phibar_ref_value)+" deg"); result.append("\n"+gammalib::parformat("Phibar reference pixel")); result.append(gammalib::str(m_phibar_ref_pixel)); result.append("\n"+gammalib::parformat("Phibar bin size")); result.append(gammalib::str(m_phibar_bin_size)+" deg"); result.append("\n"+gammalib::parformat("Phibar first bin value")); result.append(gammalib::str(m_phibar_min)+" deg"); } // VERBOSE: Append calibration database if (chatter == VERBOSE) { result.append("\n"+m_caldb.print(chatter)); } } // endif: chatter was not silent // Return result return result; } /*========================================================================== = = = Private methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Initialise class members ***************************************************************************/ void GCOMResponse::init_members(void) { // Initialise members m_caldb.clear(); m_rspname.clear(); m_iaq.clear(); m_phigeo_bins = 0; m_phibar_bins = 0; m_phigeo_ref_value = 0.0; m_phigeo_ref_pixel = 0.0; m_phigeo_bin_size = 0.0; m_phigeo_min = 0.0; m_phibar_ref_value = 0.0; m_phibar_ref_pixel = 0.0; m_phibar_bin_size = 0.0; m_phibar_min = 0.0; // Return return; } /***********************************************************************//** * @brief Copy class members * * @param[in] rsp COMPTEL response. ***************************************************************************/ void GCOMResponse::copy_members(const GCOMResponse& rsp) { // Copy attributes m_caldb = rsp.m_caldb; m_rspname = rsp.m_rspname; m_iaq = rsp.m_iaq; m_phigeo_bins = rsp.m_phigeo_bins; m_phibar_bins = rsp.m_phibar_bins; m_phigeo_ref_value = rsp.m_phigeo_ref_value; m_phigeo_ref_pixel = rsp.m_phigeo_ref_pixel; m_phigeo_bin_size = rsp.m_phigeo_bin_size; m_phigeo_min = rsp.m_phigeo_min; m_phibar_ref_value = rsp.m_phibar_ref_value; m_phibar_ref_pixel = rsp.m_phibar_ref_pixel; m_phibar_bin_size = rsp.m_phibar_bin_size; m_phibar_min = rsp.m_phibar_min; // Return return; } /***********************************************************************//** * @brief Delete class members ***************************************************************************/ void GCOMResponse::free_members(void) { // Return return; } /***********************************************************************//** * @brief Return instrument response to point source * * @param[in] model Sky model. * @param[in] obs Observation. * @param[out] gradients Gradients matrix. * @return Instrument response to point source for all events in * observation (\f$cm^2\f$). * * @exception GException::invalid_argument * Observation is not a COMPTEL observation. * Event is not a COMPTEL event bin. * @exception GException::invalid_value * Response not initialised with a valid IAQ * * Returns the instrument response to a point source for all events in the * observations. * * @p gradients is an optional matrix where the number of rows corresponds * to the number of events in the observation and the number of columns * corresponds to the number of spatial model parameters. Since for point * sources no gradients are computed, the method does not alter the * content of @p gradients. ***************************************************************************/ GVector GCOMResponse::irf_ptsrc(const GModelSky& model, const GObservation& obs, GMatrix* gradients) const { // Extract COMPTEL observation const GCOMObservation* obs_ptr = dynamic_cast(&obs); if (obs_ptr == NULL) { std::string cls = std::string(typeid(&obs).name()); std::string msg = "Observation of type \""+cls+"\" is not a COMPTEL " "observations. Please specify a COMPTEL observation " "as argument."; throw GException::invalid_argument(G_IRF_PTSRC, msg); } // Extract COMPTEL event cube const GCOMEventCube* cube = dynamic_cast(obs_ptr->events()); if (cube == NULL) { std::string msg = "Observation \""+obs.name()+"\" ("+obs.id()+") does " "not contain a COMPTEL event cube. Please specify " "a COMPTEL observation containing and event cube."; throw GException::invalid_argument(G_IRF_PTSRC, msg); } // Throw an exception if COMPTEL response is not set or if if (m_iaq.empty()) { std::string msg = "COMPTEL response is empty. Please initialise the " "response with an \"IAQ\"."; throw GException::invalid_value(G_IRF_PTSRC, msg); } else if (m_phigeo_bin_size == 0.0) { std::string msg = "COMPTEL response has a zero Phigeo bin size. " "Please initialise the response with a valid " "\"IAQ\"."; throw GException::invalid_value(G_IRF_PTSRC, msg); } // Get number of Chi/Psi pixels, Phibar layers and event bins int npix = cube->naxis(0) * cube->naxis(1); int nphibar = cube->naxis(2); int nevents = cube->size(); // Initialise result GVector irfs(nevents); // Get point source direction GSkyDir srcDir = static_cast(model.spatial())->dir(); // Get IAQ normalisation (cm2): DRX (cm2 s) * DEADC / ONTIME (s) double iaq_norm = obs_ptr->drx().map()(srcDir) * obs_ptr->deadc() / (obs_ptr->ontime() * cube->dre().tof_correction()); // Get pointer to DRG pixels const double* drg = obs_ptr->drg().map().pixels(); // Loop over Chi and Psi for (int ipix = 0; ipix < npix; ++ipix) { // Get pointer to event bin const GCOMEventBin* bin = (*cube)[ipix]; // Get reference to instrument direction const GCOMInstDir& obsDir = bin->dir(); // Get reference to Phigeo sky direction const GSkyDir& phigeoDir = obsDir.dir(); // Compute angle between true photon arrival direction and scatter // direction (Chi,Psi) double phigeo = srcDir.dist_deg(phigeoDir); // Compute interpolation factors double phirat = phigeo / m_phigeo_bin_size; // 0.5 at bin centre int iphigeo = int(phirat); // index into which Phigeo falls double eps = phirat - iphigeo - 0.5; // 0.0 at bin centre [-0.5, 0.5[ // If Phigeo is in range then compute the IRF value for all // Phibar layers if (iphigeo < m_phigeo_bins) { // Loop over Phibar for (int iphibar = 0; iphibar < nphibar; ++iphibar) { // Get IAQ index int i = iphibar * m_phigeo_bins + iphigeo; // Initialise IAQ double iaq = 0.0; // Compute IAQ if (eps < 0.0) { // interpolate towards left if (iphigeo > 0) { iaq = (1.0 + eps) * m_iaq[i] - eps * m_iaq[i-1]; } else { iaq = (1.0 - eps) * m_iaq[i] + eps * m_iaq[i+1]; } } else { // interpolate towards right if (iphigeo < m_phigeo_bins-1) { iaq = (1.0 - eps) * m_iaq[i] + eps * m_iaq[i+1]; } else { iaq = (1.0 + eps) * m_iaq[i] - eps * m_iaq[i-1]; } } // Continue only if IAQ is positive if (iaq > 0.0) { // Get DRI index int idri = ipix + iphibar * npix; // Compute IRF value double irf = iaq * drg[idri] * iaq_norm; // Make sure that IRF is positive if (irf < 0.0) { irf = 0.0; } // Store IRF value irfs[idri] = irf; } // endif: IAQ was positive } // endfor: looped over Phibar } // endif: Phigeo was in valid range } // endfor: looped over Chi and Psi // Return IRF vector return irfs; } /***********************************************************************//** * @brief Return instrument response to radial source * * @param[in] model Sky model. * @param[in] obs Observation. * @param[out] gradients Gradients matrix. * @return Instrument response to radial source for all events in * observation (\f$cm^2\f$). * * @todo Implement method. ***************************************************************************/ GVector GCOMResponse::irf_radial(const GModelSky& model, const GObservation& obs, GMatrix* gradients) const { // Throw exception std::string msg = "Response computation not yet implemented " "for spatial model type \""+ model.spatial()->type()+"\"."; throw GException::feature_not_implemented(G_IRF_RADIAL, msg); // Get number of events int nevents = obs.events()->size(); // Initialise result GVector irfs(nevents); // Return IRF value return irfs; } /***********************************************************************//** * @brief Return instrument response to elliptical source * * @param[in] model Sky model. * @param[in] obs Observation. * @param[out] gradients Gradients matrix. * @return Instrument response to elliptical source for all events in * observation (\f$cm^2\f$). * * @todo Implement method. ***************************************************************************/ GVector GCOMResponse::irf_elliptical(const GModelSky& model, const GObservation& obs, GMatrix* gradients) const { // Throw exception std::string msg = "Response computation not yet implemented " "for spatial model type \""+ model.spatial()->type()+"\"."; throw GException::feature_not_implemented(G_IRF_ELLIPTICAL, msg); // Get number of events int nevents = obs.events()->size(); // Initialise result GVector irfs(nevents); // Return IRF value return irfs; } /***********************************************************************//** * @brief Return instrument response to diffuse source * * @param[in] model Sky model. * @param[in] obs Observation. * @param[out] gradients Gradients matrix. * @return Instrument response to diffuse source for all events in * observation (\f$cm^2\f$). * * @exception GException::invalid_argument * Observation is not a COMPTEL observation. * @exception GException::invalid_value * Response not initialised with a valid IAQ * * Returns the instrument response to a diffuse source for all events in * the observations. The diffuse source may be energy dependent. * * The computation is done by integrating the diffuse model for each pixel * in Chi and Psi over a circular region centred on the Chi/Psi pixel with * a radius equal to the maximum Phigeo value. The radial integration is * done by looping over all Phigeo bins of the response. For each Phigeo * value, the azimuthal integration is done by stepping with an angular step * size that corresponds to the Phigeo step size (which typically is 1 deg). * * @p gradients is an optional matrix where the number of rows corresponds * to the number of events in the observation and the number of columns * corresponds to the number of spatial model parameters. Since for point * sources no gradients are computed, the method does not alter the * content of @p gradients. ***************************************************************************/ GVector GCOMResponse::irf_diffuse(const GModelSky& model, const GObservation& obs, GMatrix* gradients) const { // Extract COMPTEL observation const GCOMObservation* obs_ptr = dynamic_cast(&obs); if (obs_ptr == NULL) { std::string cls = std::string(typeid(&obs).name()); std::string msg = "Observation of type \""+cls+"\" is not a COMPTEL " "observations. Please specify a COMPTEL observation " "as argument."; throw GException::invalid_argument(G_IRF_DIFFUSE, msg); } // Extract COMPTEL event cube const GCOMEventCube* cube = dynamic_cast(obs_ptr->events()); if (cube == NULL) { std::string msg = "Observation \""+obs.name()+"\" ("+obs.id()+") does " "not contain a COMPTEL event cube. Please specify " "a COMPTEL observation containing and event cube."; throw GException::invalid_argument(G_IRF_DIFFUSE, msg); } // Throw an exception if COMPTEL response is not set or if if (m_iaq.empty()) { std::string msg = "COMPTEL response is empty. Please initialise the " "response with an \"IAQ\"."; throw GException::invalid_value(G_IRF_DIFFUSE, msg); } else if (m_phigeo_bin_size == 0.0) { std::string msg = "COMPTEL response has a zero Phigeo bin size. " "Please initialise the response with a valid " "\"IAQ\"."; throw GException::invalid_value(G_IRF_DIFFUSE, msg); } // Get number of Chi/Psi pixels, Phibar layers and event bins int npix = cube->naxis(0) * cube->naxis(1); int nphibar = cube->naxis(2); int nevents = cube->size(); // Initialise result GVector irfs(nevents); // Initialise some variables double phigeo_min = m_phigeo_min * gammalib::deg2rad; double phigeo_bin = m_phigeo_bin_size * gammalib::deg2rad; double omega0 = 2.0 * std::sin(0.5 * phigeo_bin); const GSkyMap& drx = obs_ptr->drx().map(); const double* drg = obs_ptr->drg().map().pixels(); // Compute IAQ normalisation (1/s): DEADC / ONTIME (s) double iaq_norm = obs_ptr->deadc() / (obs_ptr->ontime() * cube->dre().tof_correction()); // Loop over Chi and Psi for (int ipix = 0; ipix < npix; ++ipix) { // Get pointer to event bin const GCOMEventBin* bin = (*cube)[ipix]; // Get reference to instrument direction const GCOMInstDir& obsDir = bin->dir(); // Get reference to Phigeo sky direction const GSkyDir& phigeoDir = obsDir.dir(); // Loop over Phigeo for (int iphigeo = 0; iphigeo < m_phigeo_bins; ++iphigeo) { // Determine Phigeo value in radians double phigeo = phigeo_min + iphigeo * phigeo_bin; double sin_phigeo = std::sin(phigeo); // Determine number of azimuthal integration steps and step size // in radians double length = gammalib::twopi * sin_phigeo; int naz = int(length / phigeo_bin + 0.5); if (naz < 2) { naz = 2; } double az_step = gammalib::twopi / double(naz); // Computes solid angle of integration bin multiplied by Jaccobian double omega = omega0 * az_step * sin_phigeo; // Loop over azimuth angle double az = 0.0; for (int iaz = 0; iaz < naz; ++iaz, az += az_step) { // Get sky direction GSkyDir skyDir = phigeoDir; skyDir.rotate(az, phigeo); // Fall through if sky direction is not contained in DRX if (!drx.contains(skyDir)) { continue; } // Set photon GPhoton photon(skyDir, bin->energy(), bin->time()); // Get model sky intensity for photon (unit: sr^-1) double intensity = model.spatial()->eval(photon); // Fall through if intensity is zero if (intensity == 0.0) { continue; } // Multiply intensity by DRX value (unit: cm^2 s sr^-1) intensity *= drx(skyDir); // Multiply intensity by solid angle (unit: cm^2 s) intensity *= omega; // Loop over Phibar for (int iphibar = 0; iphibar < nphibar; ++iphibar) { // Get IAQ index int i = iphibar * m_phigeo_bins + iphigeo; // Get IAQ value double iaq = m_iaq[i]; // Fall through if IAQ is not positive if (iaq <= 0.0) { continue; } // Get DRI index int idri = ipix + iphibar * npix; // Compute IRF value (unit: cm^2) double irf = iaq * drg[idri] * iaq_norm * intensity; // Add IRF value if it is positive if (irf > 0.0) { irfs[idri] += irf; } } // endfor: looped over Phibar } // endfor: looped over azimuth angle around locus of IAQ } // endfor: looped over Phigeo angles } // endfor: looped over Chi and Psi pixels // Return IRF vector return irfs; }