/***********************************************************************//** * @brief Sum effective area multiplied by livetime over zenith and * (optionally) azimuth angles * * @param[in] dir True sky direction. * @param[in] energy True photon energy. * @param[in] aeff Effective area. * * Computes * \f[\sum_{\cos \theta, \phi} T_{\rm live}(\cos \theta, \phi) * A_{\rm eff}(\log E, \cos \theta, \phi)\f] * where * \f$T_{\rm live}(\cos \theta, \phi)\f$ is the livetime as a function of * the cosine of the zenith and the azimuth angle, and * \f$A_{\rm eff}(\log E, \cos \theta, \phi)\f$ is the effective area that * depends on * the log10 of the energy (in MeV), * the cosine of the zenith angle, and * the azimuth angle. * This method assumes that \f$T_{\rm live}(\cos \theta, \phi)\f$ is * stored as a set of maps in a 2D array with \f$\cos \theta\f$ being the * most rapidely varying parameter and with the first map starting at * index m_num_ctheta (the first m_num_ctheta maps are the livetime cube * maps without any \f$\phi\f$ dependence). ***************************************************************************/ double GLATLtCubeMap::operator()(const GSkyDir& dir, const GEnergy& energy, const GLATAeff& aeff) const { // Get map index int pixel = m_map.dir2pix(dir); // Initialise sum double sum = 0.0; // Circumvent const correctness GLATAeff* fct = ((GLATAeff*)&aeff); // If livetime cube and response have phi dependence then sum over // zenith and azimuth. Note that the map index starts with m_num_ctheta // as the first m_num_ctheta maps correspond to an evaluation without // any phi-dependence. if (has_phi() && aeff.has_phi()) { for (int iphi = 0, i = m_num_ctheta; iphi < m_num_phi; ++iphi) { double p = phi(iphi); for (int itheta = 0; itheta < m_num_ctheta; ++itheta, ++i) { sum += m_map(pixel, i) * (*fct)(energy.log10MeV(), costheta(i), p); } } } // ... otherwise sum only over zenith angle else { for (int i = 0; i < m_num_ctheta; ++i) { sum += m_map(pixel, i) * (*fct)(energy.log10MeV(), costheta(i)); } } // Return sum return sum; }
/***********************************************************************//** * @brief Evaluate function and gradients * * @param[in] srcEng True energy of photon. * * The spectral model is defined as * \f[I(E)=norm f(E)\f] * where * \f$norm=n_s n_v\f$ is the normalization of the function. * Note that the normalization is factorised into a scaling factor and a * value and that the method is expected to return the gradient with respect * to the parameter value \f$n_v\f$. * * The partial derivative of the normalization value is given by * \f[dI/dn_v=n_s f(E)\f] ***************************************************************************/ double GModelSpectralFunc::eval_gradients(const GEnergy& srcEng) const { // Interpolate function. This is done in log10-log10 space, but the // linear value is returned. double arg = m_log_nodes.interpolate(srcEng.log10MeV(), m_log_values); double func = std::pow(10.0, arg); // Compute function value double value = norm() * func; // Compute partial derivatives of the parameter values double g_norm = (m_norm.isfree()) ? m_norm.scale() * func : 0.0; // Set gradients (circumvent const correctness) const_cast<GModelSpectralFunc*>(this)->m_norm.gradient(g_norm); // Compile option: Check for NaN/Inf #if defined(G_NAN_CHECK) if (isnotanumber(value) || isinfinite(value)) { std::cout << "*** ERROR: GModelSpectralFunc::eval_gradients"; std::cout << "(srcEng=" << srcEng << "):"; std::cout << " NaN/Inf encountered"; std::cout << " (value=" << value; std::cout << ", norm=" << norm(); std::cout << ", func=" << func; std::cout << ", g_norm=" << g_norm; std::cout << ")" << std::endl; } #endif // Return return value; }
/***********************************************************************//** * @brief Evaluate function * * @param[in] srcEng True energy of photon. * * The spectral model is defined as * \f[I(E)=norm f(E)\f] * where * \f$norm\f$ is the normalization of the function. * Note that the node energies are stored as log10 of energy in units of * MeV. ***************************************************************************/ double GModelSpectralFunc::eval(const GEnergy& srcEng) const { // Interpolate function. This is done in log10-log10 space, but the // linear value is returned. double arg = m_log_nodes.interpolate(srcEng.log10MeV(), m_log_values); double func = std::pow(10.0, arg); // Compute function value double value = norm() * func; // Compile option: Check for NaN/Inf #if defined(G_NAN_CHECK) if (isnotanumber(value) || isinfinite(value)) { std::cout << "*** ERROR: GModelSpectralFunc::eval"; std::cout << "(srcEng=" << srcEng << "):"; std::cout << " NaN/Inf encountered"; std::cout << " (value=" << value; std::cout << ", norm=" << norm(); std::cout << ", func=" << func; std::cout << ")" << std::endl; } #endif // Return return value; }
/***********************************************************************//** * @brief Evaluate function and gradients * * @param[in] srcEng True photon energy. * @param[in] srcTime True photon arrival time. * @return Model value (ph/cm2/s/MeV). * * Evaluates * * \f[ * S_{\rm E}(E | t) = {\tt m\_norm} * \f] * * where * - \f${\tt m\_norm}\f$ is the normalization factor. * * The method also evaluates the partial derivatives of the model with * respect to the normalization parameter using * * \f[ * \frac{\delta S_{\rm E}(E | t)}{\delta {\tt m\_norm}} = * \frac{S_{\rm E}(E | t)}{{\tt m\_norm}} * \f] ***************************************************************************/ double GModelSpectralFunc::eval_gradients(const GEnergy& srcEng, const GTime& srcTime) { // Interpolate function. This is done in log10-log10 space, but the // linear value is returned. double arg = m_log_nodes.interpolate(srcEng.log10MeV(), m_log_values); double func = std::pow(10.0, arg); // Compute function value double value = m_norm.value() * func; // Compute partial derivatives of the parameter values double g_norm = (m_norm.is_free()) ? m_norm.scale() * func : 0.0; // Set gradients m_norm.factor_gradient(g_norm); // Compile option: Check for NaN/Inf #if defined(G_NAN_CHECK) if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) { std::cout << "*** ERROR: GModelSpectralFunc::eval_gradients"; std::cout << "(srcEng=" << srcEng; std::cout << ", srcTime=" << srcTime << "):"; std::cout << " NaN/Inf encountered"; std::cout << " (value=" << value; std::cout << ", norm=" << norm(); std::cout << ", func=" << func; std::cout << ", g_norm=" << g_norm; std::cout << ")" << std::endl; } #endif // Return return value; }
/***********************************************************************//** * @brief Returns map cube energies * * @return Map cube energies. * * Returns the energies for the map cube in a vector. ***************************************************************************/ GEnergies GModelSpatialDiffuseCube::energies(void) { // Initialise energies container GEnergies energies; // Fetch cube fetch_cube(); // Get number of map energies int num = m_logE.size(); // Continue only if there are maps in the cube if (num > 0) { // Reserve space for all energies energies.reserve(num); // Set log10(energy) nodes, where energy is in units of MeV for (int i = 0; i < num; ++i) { GEnergy energy; energy.log10MeV(m_logE[i]); energies.append(energy); } } // endif: there were maps in the cube // Return energies return energies; }
/***********************************************************************//** * @brief Set energy boundaries * * Computes energy boundaries from the energy nodes. The boundaries will be * computed as the centre in log10(energy) between the nodes. If only a * single map exists, no boundaries will be computed. ***************************************************************************/ void GModelSpatialDiffuseCube::set_energy_boundaries(void) { // Initialise energy boundaries m_ebounds.clear(); // Determine number of energy bins int num = m_logE.size(); // Continue only if there are at least two energy nodes if (num > 1) { // Loop over all nodes for (int i = 0; i < num; ++i) { // Calculate minimum energy double e_min = (i == 0) ? m_logE[i] - 0.5 * (m_logE[i+1] - m_logE[i]) : m_logE[i] - 0.5 * (m_logE[i] - m_logE[i-1]); // Calculate maximum energy double e_max = (i < num-1) ? m_logE[i] + 0.5 * (m_logE[i+1] - m_logE[i]) : m_logE[i] + 0.5 * (m_logE[i] - m_logE[i-1]); // Set energy boundaries GEnergy emin; GEnergy emax; emin.log10MeV(e_min); emax.log10MeV(e_max); // Append energy bin to energy boundary arra m_ebounds.append(emin, emax); } // endfor: looped over energy nodes } // endif: there were at least two energy nodes // Return return; }
/***********************************************************************//** * @brief Returns efficiency factor 2 * * @param[in] srcEng True energy of photon. * * Returns the efficiency factor 2 as function of the true photon energy. * * If no efficiency factors are present returns 2. * * @todo Implement cache to save computation time if called with same energy * value (happens for binned analysis for example) ***************************************************************************/ double GLATAeff::efficiency_factor2(const GEnergy& srcEng) const { // Initialise factor double factor = 0.0; // Compute efficiency factor. Note that the factor 2 uses the functor 1, // following the philosophy implemented in the ScienceTools method // EfficiencyFactor::getLivetimeFactors if (m_eff_func1 != NULL) { factor = (*m_eff_func1)(srcEng.log10MeV()); } // Return factor return factor; }
/***********************************************************************//** * @brief Evaluate function and gradients * * @param[in] srcEng True photon energy. * @param[in] srcTime True photon arrival time. * @return Model value (ph/cm2/s/MeV). * * Computes * * \f[ * S_{\rm E}(E | t) = {\tt m\_integral} * \frac{{\tt m\_index}+1} * {{\tt e\_max}^{{\tt m\_index}+1} - * {\tt e\_min}^{{\tt m\_index}+1}} * E^{\tt m\_index} * \f] * * for \f${\tt m\_index} \ne -1\f$ and * * \f[ * S_{\rm E}(E | t) = * \frac{{\tt m\_integral}} * {\log {\tt e\_max} - \log {\tt e\_min}} * E^{\tt m\_index} * \f] * * for \f${\tt m\_index} = -1\f$, where * - \f${\tt e\_min}\f$ is the minimum energy of an interval, * - \f${\tt e\_max}\f$ is the maximum energy of an interval, * - \f${\tt m\_integral}\f$ is the integral flux between * \f${\tt e\_min}\f$ and \f${\tt e\_max}\f$, and * - \f${\tt m\_index}\f$ is the spectral index. * * The method also evaluates the partial derivatives of the model with * respect to the parameters using * * \f[ * \frac{\delta S_{\rm E}(E | t)}{\delta {\tt m\_integral}} = * \frac{S_{\rm E}(E | t)}{{\tt m\_integral}} * \f] * * \f[ * \frac{\delta S_{\rm E}(E | t)}{\delta {\tt m\_index}} = * S_{\rm E}(E | t) \, * \left( \frac{1}{{\tt m\_index}+1} - * \frac{\log({\tt e\_max}) {\tt e\_max}^{{\tt m\_index}+1} - * \log({\tt e\_min}) {\tt e\_min}^{{\tt m\_index}+1}} * {{\tt e\_max}^{{\tt m\_index}+1} - * {\tt e\_min}^{{\tt m\_index}+1}} * + \ln(E) \right) * \f] * * for \f${\tt m\_index} \ne -1\f$ and * * \f[ * \frac{\delta S_{\rm E}(E | t)}{\delta {\tt m\_index}} = * S_{\rm E}(E | t) \, \ln(E) * \f] * * for \f${\tt m\_index} = -1\f$. * * No partial derivatives are supported for the energy boundaries. ***************************************************************************/ double GModelSpectralPlaw2::eval_gradients(const GEnergy& srcEng, const GTime& srcTime) { // Initialise gradients double g_integral = 0.0; double g_index = 0.0; // Update precomputed values update(srcEng); // Compute function value double value = m_integral.value() * m_norm * m_power; // Integral flux gradient if (m_integral.is_free()) { g_integral = value / m_integral.factor_value(); } // Index gradient if (m_index.is_free()) { g_index = value * (m_g_norm + gammalib::ln10 * srcEng.log10MeV()) * m_index.scale(); } // Set gradients m_integral.factor_gradient(g_integral); m_index.factor_gradient(g_index); // Compile option: Check for NaN/Inf #if defined(G_NAN_CHECK) if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) { std::cout << "*** ERROR: GModelSpectralPlaw2::eval_gradients"; std::cout << "(srcEng=" << srcEng; std::cout << ", srcTime=" << srcTime << "):"; std::cout << " NaN/Inf encountered"; std::cout << " (value=" << value; std::cout << ", integral=" << integral(); std::cout << ", m_norm=" << m_norm; std::cout << ", m_power=" << m_power; std::cout << ", g_integral=" << g_integral; std::cout << ", g_index=" << g_index; std::cout << ")" << std::endl; } #endif // Return return value; }
/***********************************************************************//** * @brief Set Monte Carlo simulation cone * * @param[in] centre Simulation cone centre. * @param[in] radius Simulation cone radius (degrees). * * Sets the simulation cone centre and radius that defines the directions * that will be simulated using the mc() method. ***************************************************************************/ void GModelSpatialDiffuseCube::set_mc_cone(const GSkyDir& centre, const double& radius) { // Initialise cache m_mc_cache.clear(); m_mc_spectrum.clear(); // Fetch cube fetch_cube(); // Determine number of cube pixels and maps int npix = pixels(); int nmaps = maps(); // Continue only if there are pixels and maps if (npix > 0 && nmaps > 0) { // Reserve space for all pixels in cache m_mc_cache.reserve((npix+1)*nmaps); // Loop over all maps for (int i = 0; i < nmaps; ++i) { // Compute pixel offset int offset = i * (npix+1); // Set first cache value to 0 m_mc_cache.push_back(0.0); // Initialise cache with cumulative pixel fluxes and compute // total flux in skymap for normalization. Negative pixels are // excluded from the cumulative map. double total_flux = 0.0; for (int k = 0; k < npix; ++k) { // Derive effective pixel radius from half opening angle // that corresponds to the pixel's solid angle. For security, // the radius is enhanced by 50%. double pixel_radius = std::acos(1.0 - m_cube.solidangle(k)/gammalib::twopi) * gammalib::rad2deg * 1.5; // Add up flux with simulation cone radius + effective pixel // radius. The effective pixel radius is added to make sure // that all pixels that overlap with the simulation cone are // taken into account. There is no problem of having even // pixels outside the simulation cone taken into account as // long as the mc() method has an explicit test of whether a // simulated event is contained in the simulation cone. double distance = centre.dist_deg(m_cube.pix2dir(k)); if (distance <= radius+pixel_radius) { double flux = m_cube(k,i) * m_cube.solidangle(k); if (flux > 0.0) { total_flux += flux; } } // Push back flux m_mc_cache.push_back(total_flux); // units: ph/cm2/s/MeV } // Normalize cumulative pixel fluxes so that the values in the // cache run from 0 to 1 if (total_flux > 0.0) { for (int k = 0; k < npix; ++k) { m_mc_cache[k+offset] /= total_flux; } } // Make sure that last pixel in the cache is >1 m_mc_cache[npix+offset] = 1.0001; // Store centre flux in node array if (m_logE.size() == nmaps) { GEnergy energy; energy.log10MeV(m_logE[i]); // Only append node if flux > 0 if (total_flux > 0.0) { m_mc_spectrum.append(energy, total_flux); } } } // endfor: looped over all maps // Dump cache values for debugging #if defined(G_DEBUG_CACHE) for (int i = 0; i < m_mc_cache.size(); ++i) { std::cout << "i=" << i; std::cout << " c=" << m_mc_cache[i] << std::endl; } #endif } // endif: there were cube pixels and maps // Return return; }