Example #1
0
/***********************************************************************//**
 * @brief Temporally integrates spatially and spectrally integrated Npred
 *        kernel
 *
 * @param[in] model Gamma-ray source model.
 *
 * @exception GException::gti_invalid
 *            Good Time Interval is invalid.
 *
 * Computes
 * \f[N_{\rm pred} = \int_{\rm GTI} \int_{E_{\rm bounds}} \int_{\rm ROI}
 *    S(\vec{p}, E, t) PSF(\vec{p'}, E', t' | \vec{d}, \vec{p}, E, t) \,
 *    {\rm d}\vec{p'} {\rm d}E' {\rm d}t'\f]
 * where
 * \f$S(\vec{p}, E, t)\f$ is the source model,
 * \f$PSF(\vec{p'}, E', t' | \vec{d}, \vec{p}, E, t)\f$ is the point
 * spread function,
 * \f$\vec{p'}\f$ is the measured photon direction,
 * \f$E'\f$ is the measured photon energy,
 * \f$t'\f$ is the measured photon arrival time,
 * \f$\vec{p}\f$ is the true photon arrival direction,
 * \f$E\f$ is the true photon energy,
 * \f$t\f$ is the true photon arrival time, and
 * \f$d\f$ is the instrument pointing.
 *
 * \f${\rm GTI}\f$ are the Good Time Intervals that are stored in the
 * GObservation::m_gti member.
 * Note that MET is used for the time integration interval. This, however,
 * is no specialisation since npred_grad_kern_spec::eval() converts the
 * argument back in a GTime object by assuming that the argument is in MET,
 * hence the correct time system will be used at the end by the method.
 ***************************************************************************/
double GObservation::npred_temp(const GModel& model) const
{
    // Initialise result
    double result = 0.0;

    // Loop over GTIs
    for (int i = 0; i < events()->gti().size(); ++i) {

        // Set integration interval in seconds
        double tstart = events()->gti().tstart(i).secs();
        double tstop  = events()->gti().tstop(i).secs();

        // Throw exception if time interval is not valid
        if (tstop <= tstart) {
            throw GException::gti_invalid(G_NPRED_TEMP, events()->gti().tstart(i),
                                          events()->gti().tstop(i));
        }

        // Setup integration function
        GObservation::npred_temp_kern integrand(this, &model);
        GIntegral                     integral(&integrand);

        // Do Romberg integration
        result += integral.romb(tstart, tstop);

    } // endfor: looped over GTIs

    // Return result
    return result;
}
Example #2
0
    /********************************************
     *     _:_     _:_     :  : /:\ :  :        *
     *    | : | * | : |  = :  /  :  \  :        *
     *    | : |   | : |    :/ :  :  : \:        *
     * r:-1 0 1     x   r:-2 -1  0  1  2        *
     *  j=-1:j=0             j=-1:j=0  :j=2     *
     *           j==section         :j=1        *
     * section <= x <= section+1                *
     ********************************************/
    Pol1Var Weighting( int order, int section ) {
        Pol1Var result("x"); /* need order+1 powers ! */
        if ( order == 1 )
        {
            if ( section == -1 or section == 0 )
                result = "1";
            else
                result = "0";
        }
        else
        {
            Pol1Var tmp("x");
            Pol1Var intoldvar("x");
            Pol2Vars integrand("x,r");

            integrand = Weighting( order-1, section-1 ).renameVariables("x","r");
            result    = integrand.integrate( "r", "x-1", toString(section) );

            integrand = Weighting( order-1, section ).renameVariables("x","r");
            result   += integrand.integrate( "r", toString(section), toString(section+1) );

            integrand = Weighting( order-1, section+1 ).renameVariables("x","r");
            result   += integrand.integrate( "r", toString(section+1), "x+1" );
        }
        return result;
    }
Example #3
0
/***********************************************************************//**
 * @brief Radial profile
 *
 * @param[in] theta Angular distance from DMBurkert centre (radians).
 * @return Profile value.
 ***************************************************************************/
double GModelSpatialRadialProfileDMBurkert::profile_value(const double& theta) const
{
    // Update precompuation cache
    update();

    // Initialize integral value
    double value = 0.0;
    
    // Set up integration limits
    double los_min = m_halo_distance.value() - m_mass_radius;
    double los_max = m_halo_distance.value() + m_mass_radius;
    
    // Handle case where observer is within halo mass radius
    if (los_min < 0.0) {
        los_min = 0.0;
    }
    
    // Set up integral
    halo_kernel_los integrand(m_scale_radius.value(),
                              m_halo_distance.value(),
                              theta,
                              m_core_radius.value());
    GIntegral integral(&integrand);
    
    // Compute value
    value = integral.romberg(los_min, los_max) * m_scale_density_squared;
    
    // Return value
    return value;
}
    Real ExtendedOrnsteinUhlenbeckProcess::expectation(
                                          Time t0, Real x0, Time dt) const {
        switch (discretization_) {
          case MidPoint:
            return ouProcess_->expectation(t0, x0, dt)
                    + b_(t0+0.5*dt)*(1.0 - std::exp(-speed_*dt));
            break;
          case Trapezodial:
            {
              const Time t = t0+dt;
              const Time u = t0;
              const Real bt = b_(t);
              const Real bu = b_(u);
              const Real ex = std::exp(-speed_*dt);

              return ouProcess_->expectation(t0, x0, dt)
                    + bt-ex*bu - (bt-bu)/(speed_*dt)*(1-ex);
            }
            break;
          case GaussLobatto:
              return ouProcess_->expectation(t0, x0, dt)
                  + speed_*std::exp(-speed_*(t0+dt))
                  * GaussLobattoIntegral(100000, intEps_)(integrand(b_, speed_),
                                                          t0, t0+dt);
            break;
          default:
            QL_FAIL("unknown discretization scheme");
        }
    }
/***********************************************************************//**
 * @brief Kernel for zenith angle Npred integration of background model
 *
 * @param[in] theta Offset angle from ROI centre (radians).
 *
 * Computes
 *
 * \f[
 *    K(\rho | E, t) = \sin \theta \times
 *                     \int_{\phi_{\rm min}}^{\phi_{\rm max}}
 *                     S_{\rm p}(\theta,\phi | E, t) \,
 *                     N_{\rm pred}(\theta,\phi) d\phi
 * \f]
 *
 * The azimuth angle integration range
 * \f$[\phi_{\rm min}, \phi_{\rm max}\f$
 * is limited to an arc around the vector connecting the model centre to
 * the ROI centre. This limitation assures proper converges properly.
 ***************************************************************************/
double GCTAModelBackground::npred_roi_kern_theta::eval(const double& theta)
{
    // Initialise value
    double value = 0.0;

    // Continue only if offset angle is positive
    if (theta > 0.0) {

        // Compute sine of offset angle
        double sin_theta = std::sin(theta);
        #if defined(G_NPRED_AROUND_ROI)
        double dphi = gammalib::pi;
        #else
        double dphi = 0.5 * gammalib::cta_roi_arclength(theta,
                                                        m_dist,
                                                        m_cosdist,
                                                        m_sindist,
                                                        m_roi,
                                                        m_cosroi);
        #endif

        // Continue only if arc length is positive
        if (dphi > 0.0) {

		   // Compute phi integration range
		   double phi_min = m_omega0 - dphi;
		   double phi_max = m_omega0 + dphi;

			// Setup phi integration kernel
            GCTAModelBackground::npred_roi_kern_phi integrand(m_model,
												              m_obsEng,
												              m_obsTime,
												              m_rot,
												              theta,
												              sin_theta);

			// Integrate over phi
			GIntegral integral(&integrand);
	        integral.eps(1e-3);
			value = integral.romb(phi_min, phi_max) * sin_theta;

			// Debug: Check for NaN
			#if defined(G_NAN_CHECK)
			if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
				std::cout << "*** ERROR: GCTAModelBackground::npred_roi_kern_theta::eval";
				std::cout << "(theta=" << theta << "):";
				std::cout << " NaN/Inf encountered";
				std::cout << " (value=" << value;
				std::cout << ", sin_theta=" << sin_theta;
				std::cout << ")" << std::endl;
			}
			#endif

        } // endif: arc length was positive

    } // endif: offset angle was positive

    // Return value
    return value;
}
/***********************************************************************//**
 * @brief Returns model energy flux between [emin, emax] (units: erg/cm2/s)
 *
 * @param[in] emin Minimum photon energy.
 * @param[in] emax Maximum photon energy.
 * @return Energy flux (erg/cm2/s).
 *
 * Computes
 *
 * \f[
 *    \int_{\tt emin}^{\tt emax} S_{\rm E}(E | t) E \, dE
 * \f]
 *
 * where
 * - [@p emin, @p emax] is an energy interval, and
 * - \f$S_{\rm E}(E | t)\f$ is the spectral model (ph/cm2/s/MeV).
 * The integration is done numerically.
 ***************************************************************************/
double GModelSpectralExpPlaw::eflux(const GEnergy& emin,
                                    const GEnergy& emax) const
{
    // Initialise flux
    double eflux = 0.0;
    
    // Compute only if integration range is valid
    if (emin < emax) {

        // Setup integration kernel
        eflux_kernel integrand(m_norm.value(),  m_index.value(),
                               m_pivot.value(), m_ecut.value());
        GIntegral integral(&integrand);

        // Get integration boundaries in MeV
        double e_min = emin.MeV();
        double e_max = emax.MeV();

        // Perform integration
        eflux = integral.romberg(e_min, e_max);

        // Convert from MeV/cm2/s to erg/cm2/s
        eflux *= gammalib::MeV2erg;

    } // endif: integration range was valid

    // Return
    return eflux;
}
/***********************************************************************//**
 * @brief Kernel for offset angle integration of background model
 *
 * @param[in] theta Offset angle from ROI centre (radians).
 *
 * Computes
 *
 * \f[
 *    K(\rho | E, t) = \sin \theta \times
 *                     \int_{0}^{2\pi}
 *                     B(\theta,\phi | E, t) d\phi
 * \f]
 *
 * where \f$B(\theta,\phi | E, t)\f$ is the background model for a specific
 * observed energy \f$E\f$ and time \f$t\f$.
 ***************************************************************************/
double GCTAModelIrfBackground::npred_roi_kern_theta::eval(const double& theta)
{
    // Initialise value
    double value = 0.0;

    // Continue only if offset angle is positive
    if (theta > 0.0) {

        // Setup phi integration kernel
        GCTAModelIrfBackground::npred_roi_kern_phi integrand(m_bgd,
                m_logE,
                theta);

        // Integrate over phi
        GIntegral integral(&integrand);
        integral.eps(g_cta_inst_background_npred_phi_eps);
        value = integral.romberg(0.0, gammalib::twopi) * std::sin(theta);

        // Debug: Check for NaN
#if defined(G_NAN_CHECK)
        if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
            std::string origin  = "GCTAModelIrfBackground::npred_roi_kern_theta::eval"
                                  "(" + gammalib::str(theta) + ")";
            std::string message = " NaN/Inf encountered (value=" +
                                  gammalib::str(value) + ")";
            gammalib::warning(origin, message);
        }
#endif

    } // endif: offset angle was positive

    // Return value
    return value;
}
Example #8
0
/***********************************************************************//**
 * @brief Remove thetacut
 *
 * @param[in] rsp CTA response.
 *
 * Removes thetacut from Aeff values read from a FITS file. Note that this
 * method should only be called once directly after loading all response
 * components.
 ***************************************************************************/
void GCTAAeffArf::remove_thetacut(const GCTAResponse& rsp)
{
    // Continue only if thetacut value has been set
    if (m_thetacut > 0.0) {

        // Get maximum integration radius
        double rmax = m_thetacut * gammalib::deg2rad;

        // Loop over Aeff array
        for (int i = 0; i < m_aeff.size(); ++i) {
    
            // Setup integration kernel for on-axis PSF
            cta_npsf_kern_rad_azsym integrand(rsp,
                                              rmax,
                                              0.0,
                                              m_logE[i],
                                              0.0,
                                              0.0,
                                              0.0,
                                              0.0);

            // Setup integration
            GIntegral integral(&integrand);
            integral.eps(rsp.eps());

            // Perform integration
            double fraction = integral.romb(0.0, rmax);

            // Set scale factor
            double scale = 1.0;
            if (fraction > 0.0) {
                scale /= fraction;
                #if defined(G_DEBUG_APPLY_THETACUT)
                std::cout << "GCTAAeffArf::apply_thetacut:";
                std::cout << " logE=" << m_logE[i];
                std::cout << " scale=" << scale;
                std::cout << " fraction=" << fraction;
                std::cout << std::endl;
                #endif
            }
            else {
                std::cout << "WARNING: GCTAAeffArf::apply_thetacut:";
                std::cout << " Non-positive integral occured in";
                std::cout << " PSF integration.";
                std::cout << std::endl;
            }

            // Apply scaling factor
            if (scale != 1.0) {
                m_aeff[i] *= scale;
            }

        } // endfor: looped over Aeff array

    } // endif: thetacut value was set

    // Return
    return;
}
Example #9
0
/***********************************************************************//**
 * @brief Integrates PSF for a specific set of parameters
 *
 * @param[in] energy Energy (MeV).
 * @param[in] index Parameter array index.
 *
 * Integrates PSF for a specific set of parameters.
 *
 * Compile option G_APPROXIMATE_PSF_INTEGRAL:
 * If defined, a numerical PSF integral is only performed for energies
 * < 120 MeV, while for larger energies the small angle approximation is
 * used. In not defined, a numerical PSF integral is performed for all
 * energies.
 * This option is kept for comparison with the Fermi/LAT ScienceTools who
 * select the integration method based on the true photon energy. As the
 * normalization is only performed once upon loading of the PSF, CPU time
 * is not really an issue here, and we can afford the more precise numerical
 * integration. Note that the uncertainties of the approximation at energies
 * near to 120 MeV reaches 0.1%.
 *
 * @todo Implement gcore and gtail checking
 ***************************************************************************/
double GLATPsfV3::integrate_psf(const double& energy, const int& index)
{
    // Initialise integral
    double psf = 0.0;

    // Get energy scaling
    double scale = scale_factor(energy);

    // Get parameters
    double ncore(m_ncore[index]);
    double ntail(m_ntail[index]);
    double score(m_score[index] * scale);
    double stail(m_stail[index] * scale);
    double gcore(m_gcore[index]);
    double gtail(m_gtail[index]);

    // Make sure that gcore and gtail are not negative
    //if (gcore < 0 || gtail < 0) {
    //}

    // Do we need an exact integral?
    #if defined(G_APPROXIMATE_PSF_INTEGRAL)
    if (energy < 120) {
    #endif

        // Allocate integrand
        GLATPsfV3::base_integrand integrand(ncore, ntail, score, stail, gcore, gtail);

        // Allocate integral
        GIntegral integral(&integrand);

        // Integrate radially from 0 to 90 degrees
        psf = integral.romb(0.0, pihalf) * twopi;
    
    #if defined(G_APPROXIMATE_PSF_INTEGRAL)
    } // endif: exact integral was performed

    // No, so we use the small angle approximation
    else {

        // Compute arguments
        double rc = pihalf / score;
        double uc = 0.5 * rc * rc;
        double sc = twopi * score * score;
        double rt = pihalf / stail;
        double ut = 0.5 * rt * rt;
        double st = twopi * stail * stail;

        // Evaluate PSF integral (from 0 to 90 degrees)
        psf = ncore * (base_int(uc, gcore) * sc +
                       base_int(ut, gtail) * st * ntail);
    
    }
    #endif

    // Return PSF integral
    return psf;
}
Example #10
0
/***********************************************************************//**
 * @brief Kernel for zenith angle Npred integration or radial model
 *
 * @param[in] theta Radial model zenith angle (radians).
 *
 * This method integrates a radial model for a given zenith angle theta over
 * all azimuth angles that fall within the ROI+PSF radius. The limitation to
 * an arc assures that the integration converges properly.
 ***************************************************************************/
double cta_npred_radial_kern_theta::eval(double theta)
{
    // Initialise Npred value
    double npred = 0.0;

    // Compute half length of arc that lies within ROI+PSF radius (radians)
    double dphi = 0.5 * cta_roi_arclength(theta,
                                          m_dist,
                                          m_cos_dist,
                                          m_sin_dist,
                                          m_radius,
                                          m_cos_radius);

    // Continue only if arc length is positive
    if (dphi > 0.0) {

        // Compute phi integration range
        double phi_min = m_phi - dphi;
        double phi_max = m_phi + dphi;

        // Get radial model value
        double model = m_model->eval(theta);

        // Compute sine of offset angle
        double sin_theta = std::sin(theta);

        // Setup phi integration kernel
        cta_npred_radial_kern_phi integrand(m_rsp,
                                            m_srcEng,
                                            m_srcTime,
                                            m_obs,
                                            m_rot,
                                            theta,
                                            sin_theta);

        // Integrate over phi
        GIntegral integral(&integrand);
        npred = integral.romb(phi_min, phi_max) * sin_theta * model;

        // Debug: Check for NaN
        #if defined(G_NAN_CHECK)
        if (isnotanumber(npred) || isinfinite(npred)) {
            std::cout << "*** ERROR: cta_npred_radial_kern_theta::eval";
            std::cout << "(theta=" << theta << "):";
            std::cout << " NaN/Inf encountered";
            std::cout << " (npred=" << npred;
            std::cout << ", model=" << model;
            std::cout << ", phi=[" << phi_min << "," << phi_max << "]";
            std::cout << ", sin_theta=" << sin_theta;
            std::cout << ")" << std::endl;
        }
        #endif

    } // endif: arc length was positive

    // Return Npred
    return npred;
}
Real calorimeterLSF::getLSF (Real deltaE1, Real deltaE2)
{
  //  Real answer = qags (deltaE2, deltaE1);
  //  Real answer = qng (deltaE2, deltaE1);
  setEpsRel (1.e-3); // desired accuracy of integral over energy bin
  Real limit = 1.e-10 / itsETau; // below a certain threshold throw out the tail
  if (compare (integrand (deltaE1), limit) == -1) {
    if (compare (integrand (deltaE2), limit) == -1) {
      return 0.;
    }
  }
  Real answer = qag (deltaE2, deltaE1, 4);
  int status = getStatus ();
  if (status) {
    cout << deltaE1 << " " << deltaE2 << " " << answer << "\n";
    cout << integrand (deltaE1) << " " << integrand (deltaE2) << "\n";
  }
  return qags (deltaE2, deltaE1);
}
/***********************************************************************//**
 * @brief Returns integral over radial model (in steradians)
 *
 * Computes
 * \f[\Omega = 2 \pi \int_0^{\pi} \sin \theta f(\theta) d\theta\f]
 * where
 * \f[f(\theta) = (1 + (\theta/c_0)^{c_1})^{-c_2/c_1}\f]
 * \f$\theta\f$ is the offset angle (in degrees),
 * \f$c_0\f$ is the width of the profile (width),
 * \f$c_1\f$ is the width of the central plateau (core), and
 * \f$c_2\f$ is the size of the tail (tail).
 *
 * The integration is performed numerically, and the upper integration bound
 * \f$\pi\f$
 * is fixed to 10 degrees.
 ***************************************************************************/
double GCTAModelRadialProfile::omega(void) const
{
    // Set constants
    const double offset_max_rad = 10.0 * deg2rad;

    // Allocate integrand
    GCTAModelRadialProfile::integrand integrand(this);

    // Allocate intergal
    GIntegral integral(&integrand);

    // Perform numerical integration
    double omega = integral.romb(0.0, offset_max_rad) * twopi;

    // Return integral
    return omega;
}
Example #13
0
/***********************************************************************//**
 * @brief Normalize PSF for all parameters
 *
 * Makes sure that PSF is normalized for all parameters. We assure this by
 * looping over all parameter nodes, integrating the PSF for each set of
 * parameters, and dividing the NCORE parameter by the integral.
 *
 * Compile option G_CHECK_PSF_NORM:
 * If defined, checks that the PSF is normalized correctly.
 ***************************************************************************/
void GLATPsfV3::normalize_psf(void)
{
    // Loop over all energy bins
    for (int ie = 0; ie < m_rpsf_bins.nenergies(); ++ie) {

        // Extract energy value (in MeV)
        double energy = m_rpsf_bins.energy(ie);

        // Loop over all cos(theta) bins
        for (int ic = 0; ic < m_rpsf_bins.ncostheta(); ++ic) {

            // Get parameter index
            int index = m_rpsf_bins.index(ie, ic);

            // Integrate PSF
            double norm = integrate_psf(energy, index);

            // Normalize PSF
            m_ncore[index] /= norm;

            // Compile option: check PSF normalization
            #if defined(G_CHECK_PSF_NORM)
            double scale = scale_factor(energy);
            double ncore(m_ncore[index]);
            double ntail(m_ntail[index]);
            double score(m_score[index] * scale);
            double stail(m_stail[index] * scale);
            double gcore(m_gcore[index]);
            double gtail(m_gtail[index]);
            GLATPsfV3::base_integrand integrand(ncore, ntail, score, stail, gcore, gtail);
            GIntegral integral(&integrand);
            double sum = integral.romb(0.0, pihalf) * twopi;
            std::cout << "Energy=" << energy;
            std::cout << " cos(theta)=" << m_rpsf_bins.costheta_lo(ic);
            std::cout << " error=" << sum-1.0 << std::endl;
            #endif

        } // endfor: looped over cos(theta)

    } // endfor: looped over energies
    
    // Return
    return;
}
Example #14
0
/***********************************************************************//**
 * @brief Kernel for Npred offest angle integration of diffuse model
 *
 * @param[in] theta Offset angle with respect to ROI centre (radians).
 *
 * This method integrates a diffuse model for a given offset angle with
 * respect to the ROI centre over all azimuth angles (from 0 to 2pi). The
 * integration is only performed for positive offset angles, otherwise 0 is
 * returned.
 *
 * Integration is done using the Romberg algorithm. The integration kernel
 * is defined by the helper class cta_npred_diffuse_kern_phi.
 *
 * Note that the integration precision was adjusted trading-off between
 * computation time and computation precision. A value of 1e-4 was judged
 * appropriate.
 ***************************************************************************/
double cta_npred_diffuse_kern_theta::eval(double theta)
{
    // Initialise Npred value
    double npred = 0.0;

    // Continue only if offset angle is positive
    if (theta > 0.0) {

        // Compute sine of offset angle
        double sin_theta = std::sin(theta);

        // Setup phi integration kernel
        cta_npred_diffuse_kern_phi integrand(m_rsp,
                                             m_model,
                                             m_srcEng,
                                             m_srcTime,
                                             m_obs,
                                             m_rot,
                                             theta,
                                             sin_theta);

        // Integrate over phi
        GIntegral integral(&integrand);
        integral.eps(1.0e-4);
        npred = integral.romb(0.0, twopi) * sin_theta;

        // Debug: Check for NaN
        #if defined(G_NAN_CHECK)
        if (isnotanumber(npred) || isinfinite(npred)) {
            std::cout << "*** ERROR: cta_npred_radial_kern_theta::eval";
            std::cout << "(theta=" << theta << "):";
            std::cout << " NaN/Inf encountered";
            std::cout << " (npred=" << npred;
            std::cout << ", sin_theta=" << sin_theta;
            std::cout << ")" << std::endl;
        }
        #endif
    
    } // endif: offset angle was positive

    // Return Npred
    return npred;
}
 void Interval::checkFlux() const 
 {
     if (_fluxIsReady) return;
     if (_isRadial) {
         // Integrate r*F
         RTimesF<FluxDensity> integrand(*_fluxDensityPtr);
         _flux = integ::int1d(integrand, 
                              _xLower, _xUpper,
                              odd::RELATIVE_ERROR,
                              odd::ABSOLUTE_ERROR);
     } else {
         // Integrate the input function
         _flux = integ::int1d(*_fluxDensityPtr, 
                              _xLower, _xUpper,
                              odd::RELATIVE_ERROR,
                              odd::ABSOLUTE_ERROR);
     }
     _fluxIsReady = true;
 }
 void Interval::checkFlux() const 
 {
     if (_fluxIsReady) return;
     if (_isRadial) {
         // Integrate r*F
         RTimesF<FluxDensity> integrand(*_fluxDensityPtr);
         _flux = integ::int1d(integrand, 
                              _xLower, _xUpper,
                              _gsparams->integration_relerr,
                              _gsparams->integration_abserr);
     } else {
         // Integrate the input function
         _flux = integ::int1d(*_fluxDensityPtr, 
                              _xLower, _xUpper,
                              _gsparams->integration_relerr,
                              _gsparams->integration_abserr);
     }
     _fluxIsReady = true;
 }
/***********************************************************************//**
 * @brief Returns integral over radial model (in steradians)
 *
 * Computes
 * \f[\Omega = 2 \pi \int_0^{\pi} \sin \theta f(\theta) d\theta\f]
 * where
 * \f[f(\theta) = \exp \left(-\frac{1}{2}
 *                     \left( \frac{\theta^2}{\sigma} \right)^2 \right)\f]
 * \f$\theta\f$ is the offset angle (in degrees), and
 * \f$\sigma\f$ is the Gaussian width (in degrees\f$^2\f$).
 *
 * The integration is performed numerically, and the upper integration bound
 * \f$\pi\f$
 * is set to
 * \f$\sqrt(10 \sigma)\f$
 * to reduce inaccuracies in the numerical integration.
 ***************************************************************************/
double GCTABackgroundPerfTable::solidangle(void) const
{
    // Allocate integrand
    GCTABackgroundPerfTable::integrand integrand(sigma() *
                                                 gammalib::deg2rad * 
                                                 gammalib::deg2rad);

    // Allocate intergal
    GIntegral integral(&integrand);

    // Set upper integration boundary
    double offset_max = std::sqrt(10.0*sigma()) * gammalib::deg2rad;
    if (offset_max > gammalib::pi) {
        offset_max = gammalib::pi;
    }

    // Perform numerical integration
    double solidangle = integral.romberg(0.0, offset_max) * gammalib::twopi;

    // Return integral
    return solidangle;
}
Example #18
0
/***********************************************************************//**
 * @brief Integrates spatially integrated Npred kernel spectrally
 *
 * @param[in] model Gamma-ray source model.
 * @param[in] obsTime Measured photon arrival time.
 *
 * @exception GException::erange_invalid
 *            Energy range is invalid.
 *
 * Computes
 * \f[N'_{\rm pred} = \int_{E_{\rm bounds}} \int_{\rm ROI}
 *    S(\vec{p}, E, t) PSF(\vec{p'}, E', t' | \vec{d}, \vec{p}, E, t) \,
 *    {\rm d}\vec{p'} {\rm d}E'\f]
 * where
 * \f$S(\vec{p}, E, t)\f$ is the source model,
 * \f$PSF(\vec{p'}, E', t' | \vec{d}, \vec{p}, E, t)\f$ is the point
 * spread function,
 * \f$\vec{p'}\f$ is the measured photon direction,
 * \f$E'\f$ is the measured photon energy,
 * \f$t'\f$ is the measured photon arrival time,
 * \f$\vec{p}\f$ is the true photon arrival direction,
 * \f$E\f$ is the true photon energy,
 * \f$t\f$ is the true photon arrival time, and
 * \f$d\f$ is the instrument pointing.
 *
 * \f$E_{\rm bounds}\f$ are the energy boundaries that are stored in the
 * GObservation::m_ebounds member.
 *
 * @todo Loop also over energy boundaries (is more general; there is no
 *       reason for not doing it).
 ***************************************************************************/
double GObservation::npred_spec(const GModel& model,
                                const GTime&  obsTime) const
{
    // Set integration energy interval in MeV
    double emin = events()->ebounds().emin().MeV();
    double emax = events()->ebounds().emax().MeV();

    // Throw exception if energy range is not valid
    if (emax <= emin) {
        throw GException::erange_invalid(G_NPRED_SPEC, emin, emax);
    }

    // Setup integration function
    GObservation::npred_spec_kern integrand(this, &model, &obsTime);
    GIntegral                     integral(&integrand);

    // Do Romberg integration
    #if defined(G_LN_ENERGY_INT)
    emin = log(emin);
    emax = log(emax);
    #endif
    double result = integral.romb(emin, emax);

    // Compile option: Check for NaN/Inf
    #if defined(G_NAN_CHECK)
    if (isnotanumber(result) || isinfinite(result)) {
        std::cout << "*** ERROR: GObservation::npred_spec:";
        std::cout << " NaN/Inf encountered";
        std::cout << " (result=" << result;
        std::cout << ", emin=" << emin;
        std::cout << ", emax=" << emax;
        std::cout << ")" << std::endl;
    }
    #endif

    // Return result
    return result;
}
Example #19
0
/***********************************************************************//**
 * @brief Kernel for radial model zenith angle integration of IRF
 *
 * @param[in] rho Zenith angle with respect to model centre [radians].
 *
 * This method evaluates the kernel \f$K(\rho)\f$ for the zenith angle
 * integration
 * \f[\int_{\rho_{\rm min}}^{\rho_{\rm max}} K(\rho) d\rho\f]
 * of the product between model and IRF, where
 * \f[K(\rho) = \int_{\omega_{\rm min}}^{\omega_{\rm max}} M(\rho)
 *              IRF(\rho, \omega) d\omega\f],
 * \f$M(\rho)\f$ is the azimuthally symmetric source model, and
 * \f$IRF(\rho, \omega)\f$ is the instrument response function.
 ***************************************************************************/
double cta_irf_radial_kern_rho::eval(double rho)
{
    // Compute half length of arc that lies within PSF validity circle
    // (in radians)
    double domega = 0.5 * cta_roi_arclength(rho,
                                            m_zeta,
                                            m_cos_zeta,
                                            m_sin_zeta,
                                            m_delta_max,
                                            m_cos_delta_max);

    // Initialise result
    double irf = 0.0;

    // Continue only if arc length is positive
    if (domega > 0.0) {

        // Compute omega integration range
        double omega_min = -domega;
        double omega_max = +domega;

        // Evaluate sky model M(rho)
        double model = m_model->eval(rho);

        // Precompute cosine and sine terms for azimuthal integration
        double cos_rho = std::cos(rho);
        double sin_rho = std::sin(rho);
        double cos_psf = cos_rho*m_cos_zeta;
        double sin_psf = sin_rho*m_sin_zeta;
        double cos_ph  = cos_rho*m_cos_lambda;
        double sin_ph  = sin_rho*m_sin_lambda;

        // Setup integration kernel
        cta_irf_radial_kern_omega integrand(m_rsp,
                                            m_zenith,
                                            m_azimuth,
                                            m_srcLogEng,
                                            m_obsLogEng,
                                            m_zeta,
                                            m_lambda,
                                            m_omega0,
                                            rho,
                                            cos_psf,
                                            sin_psf,
                                            cos_ph,
                                            sin_ph);

        // Integrate over phi
        GIntegral integral(&integrand);
        integral.eps(m_rsp->eps());
        irf = integral.romb(omega_min, omega_max) * model * sin_rho;

        // Compile option: Check for NaN/Inf
        #if defined(G_NAN_CHECK)
        if (isnotanumber(irf) || isinfinite(irf)) {
            std::cout << "*** ERROR: cta_irf_radial_kern_rho";
            std::cout << "(rho=" << rho << "):";
            std::cout << " NaN/Inf encountered";
            std::cout << " (irf=" << irf;
            std::cout << ", domega=" << domega;
            std::cout << ", model=" << model;
            std::cout << ", sin_rho=" << sin_rho << ")";
            std::cout << std::endl;
        }
        #endif

    } // endif: arc length was positive

    // Return result
    return irf;
}
Example #20
0
/***********************************************************************//**
 * @brief Integrate Psf over radial model
 *
 * @param[in] model Radial model.
 * @param[in] rho_obs Angle between model centre and measured photon direction
 *                    (radians).
 * @param[in] obsDir Observed event direction.
 * @param[in] srcEng True photon energy.
 * @param[in] srcTime True photon arrival time.
 *
 * Integrates the product of the spatial model and the point spread
 * function over the true photon arrival direction using
 * 
 * \f[
 *    \int_{\rho_{\rm min}}^{\rho_{\rm max}}
 *    \sin \rho \times S_{\rm p}(\rho | E, t) \times
 *    \int_{\omega_{\rm min}}^{\omega_{\rm max}} 
 *    {\rm Psf}(\rho, \omega) d\omega d\rho
 * \f]
 *
 * where
 * \f$S_{\rm p}(\rho | E, t)\f$ is the radial spatial model,
 * \f${\rm Psf}(\rho, \omega)\f$ is the point spread function,
 * \f$\rho\f$ is the radial distance from the model centre, and
 * \f$\omega\f$ is the position angle around the model centre measured
 * counterclockwise from the connecting line between the model centre and
 * the observed photon arrival direction.
 ***************************************************************************/
double GCTAResponseCube::psf_radial(const GModelSpatialRadial* model,
                                    const double&              rho_obs,
                                    const GSkyDir&             obsDir,
                                    const GEnergy&             srcEng,
                                    const GTime&               srcTime) const
{
    // Set number of iterations for Romberg integration.
    // These values have been determined after careful testing, see
    // https://cta-redmine.irap.omp.eu/issues/1299
    static const int iter_rho = 5;
    static const int iter_phi = 5;

    // Initialise value
    double irf = 0.0;

    // Get maximum PSF radius (radians)
    double delta_max = psf().delta_max();

    // Set zenith angle integration range for radial model (radians)
    double rho_min = (rho_obs > delta_max) ? rho_obs - delta_max : 0.0;
    double rho_max = rho_obs + delta_max;
    double src_max = model->theta_max();
    if (rho_max > src_max) {
        rho_max = src_max;
    }

    // Perform zenith angle integration if interval is valid
    if (rho_max > rho_min) {

        // Setup integration kernel. We take here the observed photon arrival
        // direction as the true photon arrival direction because the PSF does
        // not vary significantly over a small region.
        cta_psf_radial_kern_rho integrand(this,
                                          model,
                                          obsDir,
                                          srcEng,
                                          srcTime,
                                          rho_obs,
                                          delta_max,
                                          iter_phi);

        // Integrate over model's zenith angle
        GIntegral integral(&integrand);
        integral.fixed_iter(iter_rho);

        // Setup integration boundaries
        std::vector<double> bounds;
        bounds.push_back(rho_min);
        bounds.push_back(rho_max);

        // If the integration range includes a transition between full
        // containment of model within Psf and partial containment, then
        // add a boundary at this location
        double transition_point = delta_max - rho_obs;
        if (transition_point > rho_min && transition_point < rho_max) {
            bounds.push_back(transition_point);
        }

        // If we have a shell model then add an integration boundary for the
        // shell radius as a function discontinuity will occur at this
        // location
        const GModelSpatialRadialShell* shell = dynamic_cast<const GModelSpatialRadialShell*>(model);
        if (shell != NULL) {
            double shell_radius = shell->radius() * gammalib::deg2rad;
            if (shell_radius > rho_min && shell_radius < rho_max) {
                bounds.push_back(shell_radius);
            }
        }

        // Integrate kernel
        irf = integral.romberg(bounds, iter_rho);

        // Compile option: Check for NaN/Inf
        #if defined(G_NAN_CHECK)
        if (gammalib::is_notanumber(irf) || gammalib::is_infinite(irf)) {
            std::cout << "*** ERROR: GCTAResponseCube::psf_radial:";
            std::cout << " NaN/Inf encountered";
            std::cout << " (irf=" << irf;
            std::cout << ", rho_min=" << rho_min;
            std::cout << ", rho_max=" << rho_max << ")";
            std::cout << std::endl;
        }
        #endif

    } // endif: integration interval is valid

    // Return PSF
    return irf;
}
Example #21
0
/***********************************************************************//**
 * @brief Kernel for IRF offest angle integration of the diffuse source model
 *
 * @param[in] theta Offset angle with respect to observed photon direction
 *                  (radians).
 *
 * This method provides the kernel for the IRF offset angle integration of
 * the diffuse source model.
 *
 * It computes
 *
 * \f[irf(\theta) = \sin \theta \times
 *    \int_{0}^{2\pi} M(\theta, \phi) IRF(\theta, \phi) d\phi\f]
 *
 * where \f$M(\theta, \phi)\f$ is the spatial component of the diffuse
 * source model and \f$IRF(\theta, \phi)\f$ is the response function.
 *
 * As we assume so far an azimuthally symmetric point spread function, the
 * PSF computation is done outside the azimuthal integration kernel?
 *
 * Note that the integration is only performed for \f$\theta>0\f$. Otherwise
 * zero is returned.
 ***************************************************************************/
double cta_irf_diffuse_kern_theta::eval(double theta)
{
    // Initialise result
    double irf = 0.0;

    // Continue only if offset angle is positive
    if (theta > 0.0) {

        // Get PSF value. We can do this externally to the azimuthal
        // integration as the PSF is so far azimuthally symmetric. Once
        // we introduce asymmetries, we have to move this done into the
        // Phi kernel method/
        double psf = m_rsp->psf(theta, m_theta, m_phi, m_zenith, m_azimuth, m_srcLogEng);

        // Continue only if PSF is positive
        if (psf > 0.0) {

            // Precompute terms needed for the computation of the angular
            // distance between the true photon direction and the pointing
            // direction (i.e. the camera centre)
            double sin_theta = std::sin(theta);
            double cos_theta = std::cos(theta);
            double sin_ph    = sin_theta * m_sin_eta;
            double cos_ph    = cos_theta * m_cos_eta;

            // Setup kernel for azimuthal integration
            cta_irf_diffuse_kern_phi integrand(m_rsp,
                                               m_model,
                                               m_zenith,
                                               m_azimuth,
                                               m_srcLogEng,
                                               m_obsLogEng,
                                               m_rot,
                                               sin_theta,
                                               cos_theta,
                                               sin_ph,
                                               cos_ph);

            // Integrate over phi
            GIntegral integral(&integrand);
            integral.eps(1.0e-2);
            irf = integral.romb(0.0, twopi) * psf * sin_theta;

            // Compile option: Check for NaN/Inf
            #if defined(G_NAN_CHECK)
            if (isnotanumber(irf) || isinfinite(irf)) {
                std::cout << "*** ERROR: cta_irf_diffuse_kern_theta";
                std::cout << "(theta=" << theta << "):";
                std::cout << " NaN/Inf encountered";
                std::cout << " (irf=" << irf;
                std::cout << ", psf=" << psf;
                std::cout << ", sin_theta=" << sin_theta;
                std::cout << ", cos_theta=" << cos_theta;
                std::cout << ", sin_ph=" << sin_ph;
                std::cout << ", cos_ph=" << cos_ph;
                std::cout << ")";
                std::cout << std::endl;
            }
            #endif

        } // endif: PSF was positive

    } // endif: offset angle was positive

    // Return result
    return irf;
}
/***********************************************************************//**
 * @brief Return spatially integrated background model
 *
 * @param[in] obsEng Measured event energy.
 * @param[in] obsTime Measured event time.
 * @param[in] obs Observation.
 * @return Spatially integrated model.
 *
 * @exception GException::invalid_argument
 *            The specified observation is not a CTA observation.
 *
 * Spatially integrates the instrumental background model for a given
 * measured event energy and event time. This method also applies a deadtime
 * correction factor, so that the normalization of the model is a real rate
 * (counts/MeV/s).
 ***************************************************************************/
double GCTAModelIrfBackground::npred(const GEnergy&      obsEng,
                                     const GTime&        obsTime,
                                     const GObservation& obs) const
{
    // Initialise result
    double npred     = 0.0;
    bool   has_npred = false;

    // Build unique identifier
    std::string id = obs.instrument() + "::" + obs.id();

    // Check if Npred value is already in cache
#if defined(G_USE_NPRED_CACHE)
    if (!m_npred_names.empty()) {

        // Search for unique identifier, and if found, recover Npred value
        // and break
        for (int i = 0; i < m_npred_names.size(); ++i) {
            if (m_npred_names[i] == id && m_npred_energies[i] == obsEng) {
                npred     = m_npred_values[i];
                has_npred = true;
#if defined(G_DEBUG_NPRED)
                std::cout << "GCTAModelIrfBackground::npred:";
                std::cout << " cache=" << i;
                std::cout << " npred=" << npred << std::endl;
#endif
                break;
            }
        }

    } // endif: there were values in the Npred cache
#endif

    // Continue only if no Npred cache value has been found
    if (!has_npred) {

        // Evaluate only if model is valid
        if (valid_model()) {

            // Get pointer on CTA observation
            const GCTAObservation* cta = dynamic_cast<const GCTAObservation*>(&obs);
            if (cta == NULL) {
                std::string msg = "Specified observation is not a CTA"
                                  " observation.\n" + obs.print();
                throw GException::invalid_argument(G_NPRED, msg);
            }

            // Get pointer on CTA IRF response
            const GCTAResponseIrf* rsp = dynamic_cast<const GCTAResponseIrf*>(cta->response());
            if (rsp == NULL) {
                std::string msg = "Specified observation does not contain"
                                  " an IRF response.\n" + obs.print();
                throw GException::invalid_argument(G_NPRED, msg);
            }

            // Retrieve pointer to CTA background
            const GCTABackground* bgd = rsp->background();
            if (bgd == NULL) {
                std::string msg = "Specified observation contains no background"
                                  " information.\n" + obs.print();
                throw GException::invalid_argument(G_NPRED, msg);
            }

            // Get CTA event list
            const GCTAEventList* events = dynamic_cast<const GCTAEventList*>(obs.events());
            if (events == NULL) {
                std::string msg = "No CTA event list found in observation.\n" +
                                  obs.print();
                throw GException::invalid_argument(G_NPRED, msg);
            }

            // Get reference to ROI centre
            const GSkyDir& roi_centre = events->roi().centre().dir();

            // Get ROI radius in radians
            double roi_radius = events->roi().radius() * gammalib::deg2rad;

            // Get log10 of energy in TeV
            double logE = obsEng.log10TeV();

            // Setup integration function
            GCTAModelIrfBackground::npred_roi_kern_theta integrand(bgd, logE);

            // Setup integrator
            GIntegral integral(&integrand);
            integral.eps(g_cta_inst_background_npred_theta_eps);

            // Spatially integrate radial component
            npred = integral.romberg(0.0, roi_radius);

            // Store result in Npred cache
#if defined(G_USE_NPRED_CACHE)
            m_npred_names.push_back(id);
            m_npred_energies.push_back(obsEng);
            m_npred_times.push_back(obsTime);
            m_npred_values.push_back(npred);
#endif

            // Debug: Check for NaN
#if defined(G_NAN_CHECK)
            if (gammalib::is_notanumber(npred) || gammalib::is_infinite(npred)) {
                std::string origin  = "GCTAModelIrfBackground::npred";
                std::string message = " NaN/Inf encountered (npred=" +
                                      gammalib::str(npred) + ", roi_radius=" +
                                      gammalib::str(roi_radius) + ")";
                gammalib::warning(origin, message);
            }
#endif

        } // endif: model was valid

    } // endif: Npred computation required

    // Multiply in spectral and temporal components
    npred *= spectral()->eval(obsEng, obsTime);
    npred *= temporal()->eval(obsTime);

    // Apply deadtime correction
    npred *= obs.deadc(obsTime);

    // Return Npred
    return npred;
}
/***********************************************************************//**
 * @brief Spatially integrate effective area for given energy
 *
 * @param[in] obs Observation.
 * @param[in] logE Log10 of reference energy in TeV.
 * @return Spatially integrated effective area for given energy.
 *
 * @exception GException::invalid_argument
 *            Invalid observation encountered.
 *
 * Spatially integrates the effective area for a given reference energy
 * over the region of interest.
 ***************************************************************************/
double GCTAModelAeffBackground::aeff_integral(const GObservation& obs,
                                              const double&       logE) const
{
    // Initialise result
    double value = 0.0;

    // Set number of iterations for Romberg integration.
    static const int iter_theta = 6;
    static const int iter_phi   = 6;

    // Get pointer on CTA observation
    const GCTAObservation* cta = dynamic_cast<const GCTAObservation*>(&obs);
    if (cta == NULL) {
        std::string msg = "Specified observation is not a CTA"
                          " observation.\n" + obs.print();
        throw GException::invalid_argument(G_NPRED, msg);
    }

    // Get pointer on CTA IRF response
    const GCTAResponseIrf* rsp = dynamic_cast<const GCTAResponseIrf*>(cta->response());
    if (rsp == NULL) {
        std::string msg = "Specified observation does not contain"
                          " an IRF response.\n" + obs.print();
        throw GException::invalid_argument(G_NPRED, msg);
    }

    // Retrieve pointer to CTA effective area
    const GCTAAeff* aeff = rsp->aeff();
    if (aeff == NULL) {
        std::string msg = "Specified observation contains no effective area"
                          " information.\n" + obs.print();
        throw GException::invalid_argument(G_NPRED, msg);
    }

    // Get CTA event list
    const GCTAEventList* events = dynamic_cast<const GCTAEventList*>(obs.events());
    if (events == NULL) {
        std::string msg = "No CTA event list found in observation.\n" +
                          obs.print();
        throw GException::invalid_argument(G_NPRED, msg);
    }

    // Get ROI radius in radians
    double roi_radius = events->roi().radius() * gammalib::deg2rad;

    // Setup integration function
    GCTAModelAeffBackground::npred_roi_kern_theta integrand(aeff,
                                                            logE,
                                                            iter_phi);

    // Setup integration
    GIntegral integral(&integrand);

    // Set fixed number of iterations
    integral.fixed_iter(iter_theta);

    // Spatially integrate radial component
    value = integral.romberg(0.0, roi_radius);

    // Debug: Check for NaN
    #if defined(G_NAN_CHECK)
    if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
        std::string origin  = "GCTAModelAeffBackground::aeff_integral";
        std::string message = " NaN/Inf encountered (value=" +
                              gammalib::str(value) + ", roi_radius=" +
                              gammalib::str(roi_radius) + ")";
        gammalib::warning(origin, message);
    }
    #endif

    // Return
    return value;

}
Example #24
0
/***********************************************************************//**
 * @brief Convolve sky model with the instrument response
 *
 * @param[in] model Sky model.
 * @param[in] event Event.
 * @param[in] obs Observation.
 * @param[in] grad Should model gradients be computed? (default: true)
 * @return Event probability.
 *
 * Computes the event probability
 *
 * \f[
 *    P(p',E',t') = \int \int \int
 *                  S(p,E,t) \times R(p',E',t'|p,E,t) \, dp \, dE \, dt
 * \f]
 *
 * without taking into account any time dispersion. Energy dispersion is
 * correctly handled by this method. If time dispersion is indeed needed,
 * an instrument specific method needs to be provided.
 ***************************************************************************/
double GResponse::convolve(const GModelSky&    model,
                           const GEvent&       event,
                           const GObservation& obs,
                           const bool&         grad) const
{
    // Set number of iterations for Romberg integration.
    static const int iter = 6;

    // Initialise result
    double prob = 0.0;

    // Continue only if the model has a spatial component
    if (model.spatial() != NULL) {

        // Get source time (no dispersion)
        GTime srcTime = event.time();

        // Case A: Integration
        if (use_edisp()) {
    
            // Retrieve true energy boundaries
            GEbounds ebounds = this->ebounds(event.energy());
    
            // Loop over all boundaries
            for (int i = 0; i < ebounds.size(); ++i) {

                // Get boundaries in MeV
                double emin = ebounds.emin(i).MeV();
                double emax = ebounds.emax(i).MeV();

                // Continue only if valid
                if (emax > emin) {

                    // Setup integration function
                    edisp_kern integrand(this, &obs, &model, &event, srcTime, grad);
                    GIntegral  integral(&integrand);

                    // Set number of iterations
                    integral.fixed_iter(iter);

                    // Do Romberg integration
                    emin  = std::log(emin);
                    emax  = std::log(emax);
                    prob += integral.romberg(emin, emax);
    
                } // endif: interval was valid

            } // endfor: looped over intervals

        }

        // Case B: No integration (assume no energy dispersion)
        else {
    
            // Get source energy (no dispersion)
            GEnergy srcEng  = event.energy();

            // Evaluate probability
            prob = eval_prob(model, event, srcEng, srcTime, obs, grad);

        }

        // Compile option: Check for NaN/Inf
        #if defined(G_NAN_CHECK)
        if (gammalib::is_notanumber(prob) || gammalib::is_infinite(prob)) {
            std::cout << "*** ERROR: GResponse::convolve:";
            std::cout << " NaN/Inf encountered";
            std::cout << " (prob=" << prob;
            std::cout << ", event=" << event;
            std::cout << ", srcTime=" << srcTime;
            std::cout << ")" << std::endl;
        }
        #endif

    } // endif: spatial component valid

    // Return probability
    return prob;
}
/***********************************************************************//**
 * @brief Return spatially integrated data model
 *
 * @param[in] obsEng Measured event energy.
 * @param[in] obsTime Measured event time.
 * @param[in] obs Observation.
 * @return Spatially integrated model.
 *
 * @exception GException::invalid_argument
 *            No CTA event list found in observation.
 *            No CTA pointing found in observation.
 *
 * Spatially integrates the data model for a given measured event energy and
 * event time. This method also applies a deadtime correction factor, so that
 * the normalization of the model is a real rate (counts/exposure time).
 ***************************************************************************/
double GCTAModelBackground::npred(const GEnergy&      obsEng,
                                  const GTime&        obsTime,
                                  const GObservation& obs) const
{
    // Initialise result
    double npred     = 0.0;
    bool   has_npred = false;

    // Build unique identifier
    std::string id = obs.instrument() + "::" + obs.id();

    // Check if Npred value is already in cache
    #if defined(G_USE_NPRED_CACHE)
    if (!m_npred_names.empty()) {

        // Search for unique identifier, and if found, recover Npred value
		// and break
		for (int i = 0; i < m_npred_names.size(); ++i) {
			if (m_npred_names[i] == id && m_npred_energies[i] == obsEng) {
				npred     = m_npred_values[i];
				has_npred = true;
				#if defined(G_DEBUG_NPRED)
				std::cout << "GCTAModelBackground::npred:";
				std::cout << " cache=" << i;
				std::cout << " npred=" << npred << std::endl;
				#endif
				break;
			}
		}

    } // endif: there were values in the Npred cache
    #endif

    // Continue only if no Npred cache value was found
    if (!has_npred) {

        // Evaluate only if model is valid
        if (valid_model()) {

            // Get CTA event list
			const GCTAEventList* events = dynamic_cast<const GCTAEventList*>(obs.events());
            if (events == NULL) {
                std::string msg = "No CTA event list found in observation.\n" +
                                  obs.print();
                throw GException::invalid_argument(G_NPRED, msg);
            }

            #if !defined(G_NPRED_AROUND_ROI)
			// Get CTA pointing direction
			GCTAPointing* pnt = dynamic_cast<GCTAPointing*>(obs.pointing());
            if (pnt == NULL) {
                std::string msg = "No CTA pointing found in observation.\n" +
                                  obs.print();
                throw GException::invalid_argument(G_NPRED, msg);
            }
            #endif

            // Get reference to ROI centre
            const GSkyDir& roi_centre = events->roi().centre().dir();

			// Get ROI radius in radians
			double roi_radius = events->roi().radius() * gammalib::deg2rad;

			// Get distance from ROI centre in radians
            #if defined(G_NPRED_AROUND_ROI)
			double roi_distance = 0.0;
            #else
			double roi_distance = roi_centre.dist(pnt->dir());
            #endif

			// Initialise rotation matrix to transform from ROI system to
            // celestial coordinate system
			GMatrix ry;
			GMatrix rz;
			ry.eulery(roi_centre.dec_deg() - 90.0);
			rz.eulerz(-roi_centre.ra_deg());
			GMatrix rot = (ry * rz).transpose();

			// Compute position angle of ROI centre with respect to model
			// centre (radians)
            #if defined(G_NPRED_AROUND_ROI)
            double omega0 = 0.0;
            #else
			double omega0 = pnt->dir().posang(events->roi().centre().dir());
            #endif

			// Setup integration function
			GCTAModelBackground::npred_roi_kern_theta integrand(spatial(),
                                                                obsEng,
                                                                obsTime,
                                                                rot,
                                                                roi_radius,
                                                                roi_distance,
                                                                omega0);

			// Setup integrator
			GIntegral integral(&integrand);
			integral.eps(1e-3);

			// Setup integration boundaries
            #if defined(G_NPRED_AROUND_ROI)
			double rmin = 0.0;
			double rmax = roi_radius;
            #else
			double rmin = (roi_distance > roi_radius) ? roi_distance-roi_radius : 0.0;
			double rmax = roi_radius + roi_distance;
            #endif

			// Spatially integrate radial component
			npred = integral.romb(rmin, rmax);

	        // Store result in Npred cache
	        #if defined(G_USE_NPRED_CACHE)
	        m_npred_names.push_back(id);
	        m_npred_energies.push_back(obsEng);
	        m_npred_times.push_back(obsTime);
	        m_npred_values.push_back(npred);
	        #endif

	        // Debug: Check for NaN
	        #if defined(G_NAN_CHECK)
	        if (gammalib::is_notanumber(npred) || gammalib::is_infinite(npred)) {
	            std::cout << "*** ERROR: GCTAModelBackground::npred:";
	            std::cout << " NaN/Inf encountered";
	            std::cout << " (npred=" << npred;
	            std::cout << ", roi_radius=" << roi_radius;
	            std::cout << ")" << std::endl;
	        }
	        #endif

        } // endif: model was valid

    } // endif: Npred computation required

	// Multiply in spectral and temporal components
	npred *= spectral()->eval(obsEng, obsTime);
	npred *= temporal()->eval(obsTime);

	// Apply deadtime correction
	npred *= obs.deadc(obsTime);

    // Return Npred
    return npred;
}
Example #26
0
/***********************************************************************//**
 * @brief Integrate Psf over radial model
 *
 * @param[in] model Radial model.
 * @param[in] delta_mod Angle between model centre and measured photon
 *                      direction (radians).
 * @param[in] obsDir Observed event direction.
 * @param[in] srcEng True photon energy.
 * @param[in] srcTime True photon arrival time.
 *
 * Integrates the product of the spatial model and the point spread
 * function over the true photon arrival direction using
 * 
 * \f[
 *    \int_{\delta_{\rm min}}^{\delta_{\rm max}}
 *    \sin \delta \times {\rm Psf}(\delta) \times
 *    \int_{\phi_{\rm min}}^{\phi_{\rm max}} 
 *    S_{\rm p}(\delta, \phi | E, t) d\phi d\delta
 * \f]
 *
 * where
 * \f$S_{\rm p}(\delta, \phi | E, t)\f$ is the radial spatial model,
 * \f${\rm Psf}(\delta)\f$ is the point spread function,
 * \f$\delta\f$ is angular distance between the true and the measured
 * photon direction, and
 * \f$\phi\f$ is the position angle around the observed photon direction
 * measured counterclockwise from the connecting line between the model
 * centre and the observed photon arrival direction.
 ***************************************************************************/
double GCTAResponseCube::psf_radial(const GModelSpatialRadial* model,
                                    const double&              delta_mod,
                                    const GSkyDir&             obsDir,
                                    const GEnergy&             srcEng,
                                    const GTime&               srcTime) const
{
    // Set number of iterations for Romberg integration.
    // These values have been determined after careful testing, see
    // https://cta-redmine.irap.omp.eu/issues/1291
    static const int iter_delta = 5;
    static const int iter_phi   = 6;

    // Initialise value
    double value = 0.0;

    // Get maximum Psf radius (radians)
    double psf_max = psf().delta_max();

    // Get maximum model radius (radians)
    double theta_max = model->theta_max();

    // Set offset angle integration range (radians)
    double delta_min = (delta_mod > theta_max) ? delta_mod - theta_max : 0.0;
    double delta_max = delta_mod + theta_max;
    if (delta_max > psf_max) {
        delta_max = psf_max;
    }

    // Setup integration kernel. We take here the observed photon arrival
    // direction as the true photon arrival direction because the PSF does
    // not vary significantly over a small region.
    cta_psf_radial_kern_delta integrand(this,
                                        model,
                                        obsDir,
                                        srcEng,
                                        srcTime,
                                        delta_mod,
                                        theta_max,
                                        iter_phi);

    // Integrate over model's zenith angle
    GIntegral integral(&integrand);
    integral.fixed_iter(iter_delta);

    // Setup integration boundaries
    std::vector<double> bounds;
    bounds.push_back(delta_min);
    bounds.push_back(delta_max);

    // If the integration range includes a transition between full
    // containment of Psf within model and partial containment, then
    // add a boundary at this location
    double transition_point = theta_max - delta_mod;
    if (transition_point > delta_min && transition_point < delta_max) {
        bounds.push_back(transition_point);
    }

    // Integrate kernel
    value = integral.romberg(bounds, iter_delta);

    // Compile option: Check for NaN/Inf
    #if defined(G_NAN_CHECK)
    if (gammalib::is_notanumber(value) || gammalib::is_infinite(value)) {
        std::cout << "*** ERROR: GCTAResponseCube::psf_radial:";
        std::cout << " NaN/Inf encountered";
        std::cout << " (value=" << value;
        std::cout << ", delta_min=" << delta_min;
        std::cout << ", delta_max=" << delta_max << ")";
        std::cout << std::endl;
    }
    #endif

    // Return integral
    return value;
}
Example #27
0
/***********************************************************************//**
 * @brief Integrate Psf over elliptical model
 *
 * @param[in] model Elliptical model.
 * @param[in] rho_obs Angle between model centre and measured photon direction
 *                    (radians).
 * @param[in] posangle_obs Position angle of measured photon direction with
 *                         respect to model centre (radians).
 * @param[in] obsDir Observed event direction.
 * @param[in] srcEng True photon energy.
 * @param[in] srcTime True photon arrival time.
 *
 * Integrates the product of the spatial model and the point spread
 * function over the true photon arrival direction using
 * 
 * \f[
 *    \int_{\rho_{\rm min}}^{\rho_{\rm max}}
 *    \sin \rho \times 
 *    \int_{\omega} 
 *    S_{\rm p}(\rho, \omega | E, t) \times
 *    {\rm Psf}(\rho, \omega) d\omega d\rho
 * \f]
 *
 * where
 * \f$S_{\rm p}(\rho, \omega | E, t)\f$ is the elliptical spatial model,
 * \f${\rm Psf}(\rho, \omega)\f$ is the point spread function,
 * \f$\rho\f$ is the radial distance from the model centre, and
 * \f$\omega\f$ is the position angle around the model centre measured
 * counterclockwise from the connecting line between the model centre and
 * the observed photon arrival direction.
 ***************************************************************************/
double GCTAResponseCube::psf_elliptical(const GModelSpatialElliptical* model,
                                        const double&                  rho_obs,
                                        const double&                  posangle_obs,
                                        const GSkyDir&                 obsDir,
                                        const GEnergy&                 srcEng,
                                        const GTime&                   srcTime) const
{
    // Set number of iterations for Romberg integration.
    // These values have been determined after careful testing, see
    // https://cta-redmine.irap.omp.eu/issues/1299
    static const int iter_rho = 5;
    static const int iter_phi = 5;

    // Initialise value
    double irf = 0.0;

    // Get maximum PSF radius (radians)
    double delta_max = psf().delta_max();

    // Get the ellipse boundary (radians). Note that these are NOT the
    // parameters of the ellipse but of a boundary ellipse that is used
    // for computing the relevant omega angle intervals for a given angle
    // rho. The boundary ellipse takes care of the possibility that the
    // semiminor axis is larger than the semimajor axis
    double semimajor;    // Will be the larger axis
    double semiminor;    // Will be the smaller axis
    double posangle;     // Will be the corrected position angle
    double aspect_ratio; // Ratio between smaller/larger axis of model
    if (model->semimajor() >= model->semiminor()) {
        aspect_ratio = (model->semimajor() > 0.0) ?
                        model->semiminor() / model->semimajor() : 0.0;
        posangle     = model->posangle() * gammalib::deg2rad;
    }
    else {
        aspect_ratio = (model->semiminor() > 0.0) ?
                        model->semimajor() / model->semiminor() : 0.0;
        posangle     = model->posangle() * gammalib::deg2rad + gammalib::pihalf;
    }
    semimajor = model->theta_max();
    semiminor = semimajor * aspect_ratio;

    // Set zenith angle integration range for elliptical model
    double rho_min = (rho_obs > delta_max) ? rho_obs - delta_max : 0.0;
    double rho_max = rho_obs + delta_max;
    if (rho_max > semimajor) {
        rho_max = semimajor;
    }

    // Perform zenith angle integration if interval is valid
    if (rho_max > rho_min) {

        // Setup integration kernel. We take here the observed photon arrival
        // direction as the true photon arrival direction because the PSF does
        // not vary significantly over a small region.
        cta_psf_elliptical_kern_rho integrand(this,
                                              model,
                                              semimajor,
                                              semiminor,
                                              posangle,
                                              obsDir,
                                              srcEng,
                                              srcTime,
                                              rho_obs,
                                              posangle_obs,
                                              delta_max,
                                              iter_phi);

        // Integrate over model's zenith angle
        GIntegral integral(&integrand);
        integral.fixed_iter(iter_rho);

        // Setup integration boundaries
        std::vector<double> bounds;
        bounds.push_back(rho_min);
        bounds.push_back(rho_max);

        // Kluge: add this transition point as this allows to fit the test
        // case without any stalls. Not clear why this is the case, maybe
        // simply because the rho integral gets cut down into one more
        // sub-interval which may increase precision and smoothed the
        // likelihood contour
        double transition_point = delta_max - rho_obs;
        if (transition_point > rho_min && transition_point < rho_max) {
            bounds.push_back(transition_point);
        }

        // If the integration range includes the semiminor boundary, then
        // add an integration boundary at that location
        if (semiminor > rho_min && semiminor < rho_max) {
            bounds.push_back(semiminor);
        }

        // Integrate kernel
        irf = integral.romberg(bounds, iter_rho);

        // Compile option: Check for NaN/Inf
        #if defined(G_NAN_CHECK)
        if (gammalib::is_notanumber(irf) || gammalib::is_infinite(irf)) {
            std::cout << "*** ERROR: GCTAResponseCube::psf_elliptical:";
            std::cout << " NaN/Inf encountered";
            std::cout << " (irf=" << irf;
            std::cout << ", rho_min=" << rho_min;
            std::cout << ", rho_max=" << rho_max << ")";
            std::cout << std::endl;
        }
        #endif

    } // endif: integration interval is valid

    // Return PSF
    return irf;
}