/***********************************************************************//** * @brief Return simulated list of events * * @param[in] obs Observation. * @param[in] ran Random number generator. * @return Pointer to list of simulated events (needs to be de-allocated by * client) * * @exception GException::invalid_argument * Specified observation is not a CTA observation. * * Draws a sample of events from the background model using a Monte * Carlo simulation. The region of interest, the energy boundaries and the * good time interval for the sampling will be extracted from the observation * argument that is passed to the method. The method also requires a random * number generator of type GRan which is passed by reference, hence the * state of the random number generator will be changed by the method. * * The method also applies a deadtime correction using a Monte Carlo process, * taking into account temporal deadtime variations. For this purpose, the * method makes use of the time dependent GObservation::deadc method. * * For each event in the returned event list, the sky direction, the nominal * coordinates (DETX and DETY), the energy and the time will be set. ***************************************************************************/ GCTAEventList* GCTAModelAeffBackground::mc(const GObservation& obs, GRan& ran) const { // Initialise new event list GCTAEventList* list = new GCTAEventList; // Continue only if model is valid) if (valid_model()) { // Retrieve 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_MC, 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_MC, msg); } // Retrieve CTA response and pointing const GCTAPointing& pnt = cta->pointing(); // Get 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_MC, msg); } // Retrieve event list to access the ROI, energy boundaries and GTIs 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_MC, msg); } // Get simulation region const GCTARoi& roi = events->roi(); const GEbounds& ebounds = events->ebounds(); const GGti& gti = events->gti(); // Get maximum offset value for simulations double max_theta = pnt.dir().dist(roi.centre().dir()) + roi.radius() * gammalib::deg2rad; double cos_max_theta = std::cos(max_theta); // Set simulation region for result event list list->roi(roi); list->ebounds(ebounds); list->gti(gti); // Set up spectral model to draw random energies from. Here we use // a fixed energy sampling for an instance of GModelSpectralNodes. // This is analogous to to the GCTAModelIrfBackground::mc method. // We make sure that only non-negative nodes get appended. GEbounds spectral_ebounds = GEbounds(m_n_mc_energies, ebounds.emin(), ebounds.emax(), true); GModelSpectralNodes spectral; for (int i = 0; i < spectral_ebounds.size(); ++i) { GEnergy energy = spectral_ebounds.elogmean(i); double intensity = aeff_integral(obs, energy.log10TeV()); double norm = m_spectral->eval(energy, events->tstart()); double arg = norm * intensity; if (arg > 0.0) { spectral.append(energy, arg); } } // Loop over all energy bins for (int ieng = 0; ieng < ebounds.size(); ++ieng) { // Compute the background rate in model within the energy // boundaries from spectral component (units: cts/s). // Note that the time here is ontime. Deadtime correction will // be done later. double rate = spectral.flux(ebounds.emin(ieng), ebounds.emax(ieng)); // Debug option: dump rate #if defined(G_DUMP_MC) std::cout << "GCTAModelAeffBackground::mc(\"" << name() << "\": "; std::cout << "rate=" << rate << " cts/s)" << std::endl; #endif // If the rate is not positive then skip this energy bins if (rate <= 0.0) { continue; } // Loop over all good time intervals for (int itime = 0; itime < gti.size(); ++itime) { // Get Monte Carlo event arrival times from temporal model GTimes times = m_temporal->mc(rate, gti.tstart(itime), gti.tstop(itime), ran); // Get number of events int n_events = times.size(); // Reserve space for events if (n_events > 0) { list->reserve(n_events); } // Debug option: provide number of times and initialize // statisics #if defined(G_DUMP_MC) std::cout << " Interval " << itime; std::cout << " times=" << n_events << std::endl; int n_killed_by_deadtime = 0; int n_killed_by_roi = 0; #endif // Loop over events for (int i = 0; i < n_events; ++i) { // Apply deadtime correction double deadc = obs.deadc(times[i]); if (deadc < 1.0) { if (ran.uniform() > deadc) { #if defined(G_DUMP_MC) n_killed_by_deadtime++; #endif continue; } } // Get Monte Carlo event energy from spectral model GEnergy energy = spectral.mc(ebounds.emin(ieng), ebounds.emax(ieng), times[i], ran); // Get maximum effective area for rejection method double max_aeff = aeff->max(energy.log10TeV(), pnt.zenith(), pnt.azimuth(), false); // Skip event if the maximum effective area is not positive if (max_aeff <= 0.0) { continue; } // Initialise randomised coordinates double offset = 0.0; double phi = 0.0; // Initialise acceptance fraction and counter of zeros for // rejection method double acceptance_fraction = 0.0; int zeros = 0; // Start rejection method loop do { // Throw random offset and azimuth angle in // considered range offset = std::acos(1.0 - ran.uniform() * (1.0 - cos_max_theta)); phi = ran.uniform() * gammalib::twopi; // Compute function value at this offset angle double value = (*aeff)(energy.log10TeV(), offset, phi, pnt.zenith(), pnt.azimuth(), false); // If the value is not positive then increment the // zeros counter and fall through. The counter assures // that this loop does not lock up. if (value <= 0.0) { zeros++; continue; } // Value is non-zero so reset the zeros counter zeros = 0; // Compute acceptance fraction acceptance_fraction = value / max_aeff; } while ((ran.uniform() > acceptance_fraction) && (zeros < 1000)); // If the zeros counter is non-zero then the loop was // exited due to exhaustion and the event is skipped if (zeros > 0) { continue; } // Convert CTA pointing direction in instrument system GCTAInstDir mc_dir(pnt.dir()); // Rotate pointing direction by offset and azimuth angle mc_dir.dir().rotate_deg(phi * gammalib::rad2deg, offset * gammalib::rad2deg); // Compute DETX and DETY coordinates double detx(0.0); double dety(0.0); if (offset > 0.0 ) { detx = offset * std::cos(phi); dety = offset * std::sin(phi); } // Set DETX and DETY coordinates mc_dir.detx(detx); mc_dir.dety(dety); // Allocate event GCTAEventAtom event; // Set event attributes event.dir(mc_dir); event.energy(energy); event.time(times[i]); // Append event to list if it falls in ROI if (events->roi().contains(event)) { list->append(event); } #if defined(G_DUMP_MC) else { n_killed_by_roi++; } #endif } // endfor: looped over all events // Debug option: provide statisics #if defined(G_DUMP_MC) std::cout << " Killed by deadtime="; std::cout << n_killed_by_deadtime << std::endl; std::cout << " Killed by ROI="; std::cout << n_killed_by_roi << std::endl; #endif } // endfor: looped over all GTIs } // endfor: looped over all energy boundaries } // endif: model was valid // Return return list; }