/***********************************************************************//** * @brief Returns MC sky direction * * @param[in] energy Photon energy. * @param[in] time Photon arrival time. * @param[in,out] ran Random number generator. * @return Sky direction. * * Draws an arbitrary sky position from the 2D disk distribution. * * @todo Test function ***************************************************************************/ GSkyDir GModelSpatialEllipticalDisk::mc(const GEnergy& energy, const GTime& time, GRan& ran) const { // Update precomputation cache update(); // Initialise photon GPhoton photon; photon.energy(energy); photon.time(time); // Draw randomly from the radial disk // and reject the value if its outside the ellipse do { // Simulate offset from photon arrival direction double cosrad = std::cos(semimajor() * gammalib::deg2rad); double theta = std::acos(1.0 - ran.uniform() * (1.0 - cosrad)) * gammalib::rad2deg; double phi = 360.0 * ran.uniform(); // Rotate sky direction by offset GSkyDir sky_dir = dir(); sky_dir.rotate_deg(phi, theta); // Set photon sky direction photon.dir(sky_dir); } while(GModelSpatialElliptical::eval(photon) <= 0.0); // Return photon direction return (photon.dir()); }
/***********************************************************************//** * @brief Evaluate function * * @param[in] photon Incident photon. * @return Model value. * * Computes the spatial diffuse model as function of photon parameters. ***************************************************************************/ double GModelSpatialDiffuseCube::eval(const GPhoton& photon) const { // Initialise value double value = 0.0; // Fetch cube fetch_cube(); // Continue only if there is energy information for the map cube if (m_logE.size() > 0) { // Compute diffuse model value by interpolation in log10(energy) m_logE.set_value(photon.energy().log10MeV()); double intensity = m_logE.wgt_left() * m_cube(photon.dir(), m_logE.inx_left()) + m_logE.wgt_right() * m_cube(photon.dir(), m_logE.inx_right()); // Set the intensity times the scaling factor as model value value = intensity * m_value.value(); // Make sure that value is not negative if (value < 0.0) { value = 0.0; } } // endif: energy information was available // Return value return value; }
/***********************************************************************//** * @brief Return model value and set analytical gradients * * @param[in] photon Incident Photon. * * Evaluates the radial spatial model value and analytical model parameter * gradients for a specific incident @p photon. ***************************************************************************/ double GModelSpatialRadial::eval_gradients(const GPhoton& photon) const { // Compute distance from source (in radians) double theta = photon.dir().dist(dir()); // Evaluate model and set gradients double value = eval_gradients(theta, photon.energy(), photon.time()); // Return result return value; }
/***********************************************************************//** * @brief Return model value * * @param[in] photon Incident Photon. * @param[in] gradients Compute gradients? * @return Value of spatial elliptical model. * * Evaluates the elliptical spatial model value for a specific incident * @p photon. * * If the @p gradients flag is true the method will also compute the * parameter gradients for all model parameters. ***************************************************************************/ double GModelSpatialElliptical::eval(const GPhoton& photon, const bool& gradients) const { // Compute distance from source and position angle (in radians) const GSkyDir& srcDir = photon.dir(); double theta = dir().dist(srcDir); double posang = dir().posang(srcDir); // Evaluate model double value = eval(theta, posang, photon.energy(), photon.time(), gradients); // Return result return value; }
/***********************************************************************//** * @brief Returns MC sky direction * * @param[in] energy Photon energy. * @param[in] time Photon arrival time. * @param[in,out] ran Random number generator. * @return Sky direction. * * Draws an arbitrary sky direction from the elliptical Gaussian model. * * @warning * For numerical reasons the elliptical Gaussian will be truncated for * \f$\theta\f$ angles that correspond to 3.0 times the effective ellipse * radius. ***************************************************************************/ GSkyDir GModelSpatialEllipticalGauss::mc(const GEnergy& energy, const GTime& time, GRan& ran) const { // Update precomputation cache update(); // Initialise photon GPhoton photon; photon.energy(energy); photon.time(time); // Draw gaussian offset from each axis double ran_major; double ran_minor; do { ran_major = ran.normal(); } while (ran_major > c_theta_max); do { ran_minor = ran.normal(); } while (ran_minor > c_theta_max); double theta1 = semimajor() * ran_major; double theta2 = semiminor() * ran_minor; // Compute total offset from model centre in small angle approximation double theta = std::sqrt(theta1 * theta1 + theta2 * theta2); // Compute rotation angle, taking into account given position angle double phi = gammalib::atan2d(theta2, theta1) + posangle(); // Rotate sky direction by offset GSkyDir sky_dir = dir(); sky_dir.rotate_deg(phi , theta); // Set photon sky direction photon.dir(sky_dir); // Return photon direction return (photon.dir()); }
/***********************************************************************//** * @brief Evaluate function and gradients * * @param[in] photon Incident photon. * @return Model value. * * Computes the spatial diffuse model as function of photon parameters and * sets the value gradient. ***************************************************************************/ double GModelSpatialDiffuseCube::eval_gradients(const GPhoton& photon) const { // Initialise intensity double intensity = 0.0; // Fetch cube fetch_cube(); // Continue only if there is energy information for the map cube if (m_logE.size() > 0) { // Compute diffuse model value by interpolation in log10(energy) m_logE.set_value(photon.energy().log10MeV()); intensity = m_logE.wgt_left() * m_cube(photon.dir(), m_logE.inx_left()) + m_logE.wgt_right() * m_cube(photon.dir(), m_logE.inx_right()); } // endif: energy information was available // Compute the model value double value = intensity * m_value.value(); // Compute partial derivatives of the parameter value double g_value = (m_value.is_free()) ? intensity * m_value.scale() : 0.0; // Make sure that value is not negative if (value < 0.0) { value = 0.0; g_value = 0.0; } // Set gradient to 0 (circumvent const correctness) const_cast<GModelSpatialDiffuseCube*>(this)->m_value.factor_gradient(g_value); // Return value return value; }
/***********************************************************************//** * @brief Return simulated list of photons * * @param[in] area Simulation surface area (cm2). * @param[in] dir Centre of simulation cone. * @param[in] radius Radius of simulation cone (deg). * @param[in] emin Minimum photon energy. * @param[in] emax Maximum photon energy. * @param[in] tmin Minimum photon arrival time. * @param[in] tmax Maximum photon arrival time. * @param[in] ran Random number generator. * * This method returns a list of photons that has been derived by Monte Carlo * simulation from the model. A simulation region is define by specification * of a simulation cone (a circular region on the sky), * of an energy range [emin, emax], and * of a time interval [tmin, tmax]. * The simulation cone may eventually cover the entire sky (by setting * the radius to 180 degrees), yet simulations will be more efficient if * only the sky region will be simulated that is actually observed by the * telescope. * * @todo Check usage for diffuse models * @todo Implement photon arrival direction simulation for diffuse models * @todo Implement unique model ID to assign as Monte Carlo ID ***************************************************************************/ GPhotons GModelSky::mc(const double& area, const GSkyDir& dir, const double& radius, const GEnergy& emin, const GEnergy& emax, const GTime& tmin, const GTime& tmax, GRan& ran) const { // Allocate photons GPhotons photons; // Continue only if model is valid) if (valid_model()) { // Get point source pointer GModelSpatialPtsrc* ptsrc = dynamic_cast<GModelSpatialPtsrc*>(m_spatial); // Check if model will produce any photons in the specified // simulation region. If the model is a point source we check if the // source is located within the simulation code. If the model is a // diffuse source we check if the source overlaps with the simulation // code bool use_model = true; if (ptsrc != NULL) { if (dir.dist(ptsrc->dir()) > radius) { use_model = false; } } else { //TODO } // Continue only if model overlaps with simulation region if (use_model) { // Compute flux within [emin, emax] in model from spectral // component (units: ph/cm2/s) double flux = m_spectral->flux(emin, emax); // Derive expecting counting rate within simulation surface // (units: ph/s) double rate = flux * area; // Debug option: dump rate #if G_DUMP_MC std::cout << "GModelSky::mc(\"" << name() << "\": "; std::cout << "flux=" << flux << " ph/cm2/s, "; std::cout << "rate=" << rate << " ph/s)" << std::endl; #endif // Get photon arrival times from temporal model GTimes times = m_temporal->mc(rate, tmin, tmax, ran); // Reserve space for photons if (times.size() > 0) { photons.reserve(times.size()); } // Loop over photons for (int i = 0; i < times.size(); ++i) { // Allocate photon GPhoton photon; // Set photon arrival time photon.time(times[i]); // Set incident photon direction photon.dir(m_spatial->mc(ran)); // Set photon energy photon.energy(m_spectral->mc(emin, emax, ran)); // Append photon photons.append(photon); } // endfor: looped over photons } // endif: model was used } // endif: model was valid // Return photon list return photons; }
/***********************************************************************//** * @brief Return instrument response * * @param[in] event Observed event. * @param[in] photon Incident photon. * @param[in] obs Observation (not used). * @return Instrument response. ***************************************************************************/ double GCTAResponseCube::irf(const GEvent& event, const GPhoton& photon, const GObservation& obs) const { // Retrieve event instrument direction const GCTAInstDir& dir = retrieve_dir(G_IRF, event); // Get event attributes const GSkyDir& obsDir = dir.dir(); //const GEnergy& obsEng = event.energy(); // Get photon attributes const GSkyDir& srcDir = photon.dir(); const GEnergy& srcEng = photon.energy(); //const GTime& srcTime = photon.time(); // Determine angular separation between true and measured photon // direction in radians double delta = obsDir.dist(srcDir); // Get maximum angular separation for PSF (in radians) double delta_max = psf().delta_max(); // Initialise IRF value double irf = 0.0; // Get livetime (in seconds) double livetime = exposure().livetime(); // Continue only if livetime is >0 and if we're sufficiently close // to the PSF if ((livetime > 0.0) && (delta <= delta_max)) { // Get exposure irf = exposure()(srcDir, srcEng); // Multiply-in PSF if (irf > 0.0) { // Get PSF component irf *= psf()(srcDir, delta, srcEng); // Divide by livetime irf /= livetime; // Apply deadtime correction irf *= exposure().deadc(); } // endif: Aeff was non-zero } // endif: we were sufficiently close to PSF and livetime was >0 // Compile option: Check for NaN/Inf #if defined(G_NAN_CHECK) if (gammalib::is_notanumber(irf) || gammalib::is_infinite(irf)) { std::cout << "*** ERROR: GCTAResponseCube::irf:"; std::cout << " NaN/Inf encountered"; std::cout << " irf=" << irf; std::cout << std::endl; } #endif // Return IRF value return irf; }
/***********************************************************************//** * @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 (cm2 sr-1) * * @exception GException::invalid_argument * Observation is not a COMPTEL observation. * Event is not a COMPTEL event bin. * * 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[IRF = \frac{IAQ \times DRG \times DRX}{ontime \times ewidth}\f] * where * \f$IRF\f$ is the instrument response function, * \f$IAQ\f$ is the COMPTEL response matrix (sr-1), * \f$DRG\f$ is the geometry factor (cm2), * \f$DRX\f$ is the exposure (s), * \f$ontime\f$ is the ontime (s), and * \f$ewidth\f$ is the energy width (MeV). * * The observed photon direction is spanned by the 3 values (Chi,Psi,Phibar). * (Chi,Psi) is the scatter direction of the event, given in sky coordinates. * Phibar is the Compton scatter angle, computed from the energy deposits. ***************************************************************************/ double GCOMResponse::irf(const GEvent& event, const GPhoton& photon, const GObservation& obs) const { // Extract COMPTEL observation const GCOMObservation* observation = dynamic_cast<const GCOMObservation*>(&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 bin const GCOMEventBin* bin = dynamic_cast<const GCOMEventBin*>(&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); } // 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); // Extract IAQ value by linear inter/extrapolation in Phigeo double iaq = 0.0; 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 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]; } } } } // Get DRG value (units: cm2) double drg = observation->drg()(obsDir.dir(), iphibar); // Get DRX value (units: sec) double drx = observation->drx()(srcDir); // Get ontime double ontime = observation->ontime(); // sec // Compute IRF value double irf = iaq * drg * drx / ontime; // Apply deadtime correction irf *= obs.deadc(srcTime); // 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 // Return IRF value return irf; }