/*************************************************************************** * ctfindvar - Time variability search tool * * ----------------------------------------------------------------------- * * copyright (C) 2018-2020 by Simon Bonnefoy * * ----------------------------------------------------------------------- * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ /** * @file ctfindvar.cpp * @brief Time variability search tool implementation * @author Simon Bonnefoy */ /* __ Includes ___________________________________________________________ */ #ifdef HAVE_CONFIG_H #include #endif #include "ctfindvar.hpp" #include "GammaLib.hpp" #include "GCTALib.hpp" #include /* __ OpenMP section _____________________________________________________ */ #ifdef _OPENMP #include #endif /* __ Method name definitions ____________________________________________ */ #define G_INX2GTI "ctfindvar::inx2gti(int&)" #define G_FILL_CUBE "ctfindvar::fill_cube(GCTAObservation*)" #define G_FILL_ALPHA_VECTOR "ctfindvar::fill_alpha_vector(const int&, "\ "vector&)" #define G_INIT_CUBE "ctfindvar::init_cube(void)" #define G_INIT_GTIS "ctfindvar::init_gtis(void)" /* __ Debug definitions __________________________________________________ */ /* __ Coding definitions _________________________________________________ */ /* __ Constants __________________________________________________________ */ /*========================================================================== = = = Constructors/destructors = = = ==========================================================================*/ /***********************************************************************//** * @brief Void constructor * * Constructs empty time variability search tool. ***************************************************************************/ ctfindvar::ctfindvar(void) : ctobservation(CTFINDVAR_NAME, VERSION) { // Initialise members init_members(); // Return return; } /***********************************************************************//** * @brief Observations constructor * * param[in] obs Observation container. * * Constructs time variability search tool from an observation container. ***************************************************************************/ ctfindvar::ctfindvar(const GObservations& obs) : ctobservation(CTFINDVAR_NAME, VERSION, obs) { // Initialise members init_members(); // Return return; } /***********************************************************************//** * @brief Command line constructor * * @param[in] argc Number of arguments in command line. * @param[in] argv Array of command line arguments. * * Constructs time variability search tool using command line arguments for * user parameter setting. ***************************************************************************/ ctfindvar::ctfindvar(int argc, char *argv[]) : ctobservation(CTFINDVAR_NAME, VERSION, argc, argv) { // Initialise members init_members(); // Return return; } /***********************************************************************//** * @brief Copy constructor * * @param[in] app search time variability tool. * * Constructs time variability search tool from another time variability * search tool. ***************************************************************************/ ctfindvar::ctfindvar(const ctfindvar& app) : ctobservation(app) { // Initialise members init_members(); // Copy members copy_members(app); // Return return; } /***********************************************************************//** * @brief Destructor * * Destructs search time variability tool. ***************************************************************************/ ctfindvar::~ctfindvar(void) { // Free members free_members(); // Return return; } /*========================================================================== = = = Operators = = = ==========================================================================*/ /***********************************************************************//** * @brief Assignment operator * * @param[in] app Time variability search tool. * @return Time variability search tool. * * Assigns time variability search tool. ***************************************************************************/ ctfindvar& ctfindvar::operator=(const ctfindvar& app) { // Execute only if object is not identical if (this != &app) { // Copy base class members this->ctobservation::operator=(app); // Free members free_members(); // Initialise members init_members(); // Copy members copy_members(app); } // endif: object was not identical // Return this object return *this; } /*========================================================================== = = = Public methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Clear time variability search tool * * Clears time variability search tool. ***************************************************************************/ void ctfindvar::clear(void) { // Free members free_members(); this->ctobservation::free_members(); this->ctool::free_members(); // Clear base class (needed to conserve tool name and version) this->GApplication::clear(); // Initialise members this->ctool::init_members(); this->ctobservation::init_members(); init_members(); // Write header into logger log_header(); // Return return; } /***********************************************************************//** * @brief Run time variability search tool ***************************************************************************/ void ctfindvar::run(void) { // If we're in debug mode then all output is also dumped on the screen if (logDebug()) { log.cout(true); } // Get task parameters get_parameters(); // Write input observation container into logger log_observations(NORMAL, m_obs, "Input observation"); // Create GTIs init_gtis(); // Create counts cube create_cube(); // Analyse cube analyse_cube(); // Return return; } /***********************************************************************//** * @brief Save peak significance map and significance distributions * * Saves the peak significance and source signficance distributions. * Optionally, the generated counts cube can also be saved if 'outcube' has * been specified. ***************************************************************************/ void ctfindvar::save(void) { // Write header log_header1(TERSE, "Saving results"); // Save counts cube if a valid filename is given and the counts cube is // not empty if ((*this)["outcube"].is_valid()) { // Get counts cube output filename GFilename outcube = (*this)["outcube"].filename(); // Save counts cube if it is not empty. Otherwise signal that no // counts cube was saved. if (!m_counts.is_empty()) { m_counts.save(outcube, (*this)["clobber"].boolean()); log_value(TERSE, "Counts cube file", outcube.url()); } else { log_value(TERSE, "Counts cube file", outcube.url()+" (cube is empty, no file created)"); } } // Get output map and model filenames GFilename outmap = (*this)["outmap"].filename(); GFilename outmodel = (*this)["outmodel"].filename(); // Save output map if the significance map is not empty if (!m_peaksigmap.is_empty()) { // Create a FITS file for storing the output GFits fits; // Write the most significant values for each pixel m_peaksigmap.write(fits, "PEAKSIGMAP"); // Write source histograms write_source_histograms(fits); // Save significance map fits.saveto(outmap, (*this)["clobber"].boolean()); // Log saving log_value(TERSE, "Output map file", outmap.url()); } else { log_value(TERSE, "Output map file", outmap.url()+" (map is empty, no file created)"); } // Save model definiton XML file if the significance map is not empty. // This may store an empty model in case that none of the pixels is // above threshold. if (!m_peaksigmap.is_empty()) { // Save model m_model_above_thr.save(outmodel); // Log saving log_value(TERSE, "Model definition file", outmodel.url()); } else { log_value(TERSE, "Model definition file", outmodel.url()+" (map is empty, no file created)"); } // Return return; } /*========================================================================== = = = Private methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Initialise class members ***************************************************************************/ void ctfindvar::init_members(void) { // Initialise members m_counts.clear(); m_gti.clear(); m_inmodel.clear(); m_max_sig_dir.clear(); m_minoff = 0.0; m_sig_threshold = 0.0; m_peaksigmap.clear(); m_pixsigsrc.clear(); m_tstart.clear(); m_tstop.clear(); m_emin.clear(); m_emax.clear(); m_model_above_thr.clear(); // Return return; } /***********************************************************************//** * @brief Copy class members * * @param[in] app search time variability tool. ***************************************************************************/ void ctfindvar::copy_members(const ctfindvar& app) { // Copy members m_counts = app.m_counts; m_gti = app.m_gti; m_inmodel = app.m_inmodel; m_max_sig_dir = app.m_max_sig_dir; m_minoff = app.m_minoff; m_sig_threshold = app.m_sig_threshold; m_peaksigmap = app.m_peaksigmap; m_pixsigsrc = app.m_pixsigsrc; m_tstart = app.m_tstart; m_tstop = app.m_tstop; m_emin = app.m_emin; m_emax = app.m_emax; m_model_above_thr = app.m_model_above_thr; // Return return; } /***********************************************************************//** * @brief Delete class members ***************************************************************************/ void ctfindvar::free_members(void) { // Return return; } /***********************************************************************//** * @brief Get application parameters ***************************************************************************/ void ctfindvar::get_parameters(void) { // Load the observations setup_observations(m_obs, true, true, false); // Either load model or query source position parameters if ((*this)["inmodel"].is_valid()) { m_inmodel = GModels((*this)["inmodel"].filename()); } else { (*this)["coordsys"].string(); (*this)["xsrc"].real(); (*this)["ysrc"].real(); } // Query time limit parameters (*this)["tinterval"].real(); if ((*this)["tmin"].is_valid()) { (*this)["tmin"].time(); } if ((*this)["tmax"].is_valid()) { (*this)["tmax"].time(); } // Create map to query spatial parameters create_map(m_obs); // Read energy limits m_emin = GEnergy((*this)["emin"].real(), "TeV"); m_emax = GEnergy((*this)["emax"].real(), "TeV"); // Get minimum counts for a bin to be considered in Noff calculation m_minoff = (*this)["minoff"].real(); // Get minimum significance to set a source as variable m_sig_threshold = (*this)["threshold"].real(); // Query (hidden) smoothing parameters (*this)["smooth_kernel"].string(); (*this)["smooth_rad"].real(); // If needed later, query output filenames now if (read_ahead()) { (*this)["outcube"].query(); (*this)["outmap"].query(); (*this)["outmodel"].query(); } // Set number of OpenMP threads #ifdef _OPENMP int nthreads = (*this)["nthreads"].integer(); if (nthreads > 0) { omp_set_num_threads(nthreads); } #endif // Write parameters into logger log_parameters(TERSE); // Return return; } /***********************************************************************//** * @brief Initialize Good Time Intervals * * @exception GException::invalid_value * Invalid start or stop time or insufficient time bins * * Initialise Good Time Intervals for the variability search. ***************************************************************************/ void ctfindvar::init_gtis(void) { // Log header log_header1(NORMAL, "Initialise Good Time Intervals"); // Clear time intervals m_gti.clear(); // Set start and stop times m_tstart = get_tstart(); m_tstop = get_tstop(); // Query time interval double tinterval = (*this)["tinterval"].real(); // Compute time bins double tstart_sec = m_tstart.secs(); double tstop_sec = m_tstop.secs(); int bins = (tstop_sec - tstart_sec) / tinterval + 0.5; // Log information log_value(NORMAL, "Reference time (mjd)", m_tstart.reference().mjdref()); log_value(NORMAL, "Start time (sec)", tstart_sec); log_value(NORMAL, "Stop time (sec)", tstop_sec); log_value(NORMAL, "Total time (sec)", tstop_sec-tstart_sec); log_value(NORMAL, "Time interval (sec)", tinterval); log_value(NORMAL, "Number of time bins", bins); // Throw exception if tstart == tstop if (m_tstart == m_tstop) { throw GException::invalid_value(G_INIT_GTIS, "Start time is equal to stop time"); } else if (bins < 2) { std::string msg = "Method requires at least two time bins (" + gammalib::str(bins) + " bins found)."; throw GException::invalid_value(G_INIT_GTIS, msg); } // Set up Good Time Intervals by appending all intervals that overlap // with at least one of the observations for (int i = 0; i < bins; ++i) { // Get time interval GTime tstart(m_tstart + i*tinterval); GTime tstop(m_tstart + (i+1.0)*tinterval); GGti gti(tstart, tstop); // Make sure there's an observation that overlaps with this GTI for (int k = 0; k < m_obs.size(); ++k) { // Get CTA observation GCTAObservation* obs = dynamic_cast(m_obs[k]); // Continue only if observation is valid if (obs != NULL) { // Add GTI is the time intervals overlap if (gti_overlap(obs->gti(), gti) > 0.0) { m_gti.push_back(gti); break; } } // endif: observation was valid } // endfor: looped over observations } // endfor: looped over Good Time Intervals // Return return; } /***********************************************************************//** * @brief Create counts cube * * Creates a counts cube comprising a sky map for each Good Time Interval. ***************************************************************************/ void ctfindvar::create_cube(void) { // Log header log_header1(NORMAL, "Create counts cube"); // Create the basic skymap m_counts = create_map(m_obs); // Resize to the appropriate number of time intervals m_counts.nmaps(m_gti.size()); // Create the peaksigmap m_peaksigmap = m_counts.extract(1); // Loop over all unbinned CTA observations in the container #pragma omp parallel for for (int i = 0; i < m_obs.size(); ++i) { // Get pointer to observation GCTAObservation* obs = dynamic_cast(m_obs[i]); // Fill the cube fill_cube(obs); // Dispose events to free memory obs->dispose_events(); } // endfor: looped over observations // Smooth the maps if requested if ((*this)["smooth_kernel"].string() != "NONE") { m_counts.smooth((*this)["smooth_kernel"].string(), (*this)["smooth_rad"].real()); } // Return return; } /***********************************************************************//** * @brief Fill the cube from the events in an observation * * @param[in] obs Pointer to CTA observation. * * Fills the cube from the events in an observation. ***************************************************************************/ void ctfindvar::fill_cube(GCTAObservation* obs) { // Make sure that the observation holds a CTA event list. If this // is not the case then throw an exception. const GCTAEventList* events = dynamic_cast (obs->events()); if (events == NULL) { std::string msg = "CTA Observation does not contain an event " "list. An event list is needed to fill the " "counts cube."; throw GException::invalid_value(G_FILL_CUBE, msg); } // Get the RoI const GCTARoi& roi = events->roi(); // Check for RoI sanity if (!roi.is_valid()) { std::string msg = "No valid RoI found in input observation " "\""+obs->name()+"\". Run ctselect to specify " "an RoI for this observation before running " "ctbin."; throw GException::invalid_value(G_FILL_CUBE, msg); } // Initialise binning statistics int num_outside_roi = 0; int num_invalid_wcs = 0; int num_outside_ecut = 0; int num_outside_map = 0; int num_outside_time = 0; int num_in_map = 0; // Fill counts sky map for (int i = 0; i < events->size(); ++i) { // Get event const GCTAEventAtom* event = (*events)[i]; // Check that the energy is valid if ((event->energy() < m_emin) || (event->energy() > m_emax)) { num_outside_ecut++; continue; } // Determine event time index int time_indx = time2inx(event->time()); // Check if event is in the GTIs if ((time_indx > -1) && (time_indx < m_counts.nmaps())) { // Get event direction GSkyDir evnt_dir = GCTAInstDir(event->dir()).dir(); // Skip event if it is outside the RoI if (roi.centre().dir().dist_deg(evnt_dir) > roi.radius()) { num_outside_roi++; continue; } // Determine sky pixel GSkyPixel pixel; try { pixel = m_counts.dir2pix(evnt_dir); } catch (std::exception &e) { num_invalid_wcs++; continue; } // Skip event if corresponding counts cube pixel is outside the // counts cube map range if (pixel.x() < -0.5 || pixel.x() > (m_counts.nx()-0.5) || pixel.y() < -0.5 || pixel.y() > (m_counts.ny()-0.5)) { num_outside_map++; continue; } // Fill event in skymap #pragma omp critical(ctfindvar_fill_cube) m_counts(pixel, time_indx) += 1.0; // Increment number of maps num_in_map++; } // ... otherwise, event falls outside the specified time range else { num_outside_time++; } } // endfor: looped over all events // Log filling results #pragma omp critical(ctfindvar_fill_cube) { log_header3(TERSE, get_obs_header(obs)); log_value(NORMAL, "Events in list", obs->events()->size()); log_value(NORMAL, "Events in cube", num_in_map); log_value(NORMAL, "Events outside RoI", num_outside_roi); log_value(NORMAL, "Events outside energy", num_outside_ecut); log_value(NORMAL, "Events with invalid WCS", num_invalid_wcs); log_value(NORMAL, "Events outside cube area", num_outside_map); log_value(NORMAL, "Events outside time bins", num_outside_time); } // Return return; } /***********************************************************************//** * @brief Analyse all pixels of counts cube ***************************************************************************/ void ctfindvar::analyse_cube(void) { // Log header log_header1(NORMAL, "Analyse sky map pixels"); // Get number of GTIs const int nbins = m_gti.size(); // Get relevant sky map pixels std::vector srcInxPix = get_pixels(); // Preparing the histogram with significance evolution for the source // center and the highest sig-pix m_pixsigsrc = GNdarray(srcInxPix.size(), nbins); m_pixsigmax = GNdarray(nbins); // Initialise maximum significance double max_sig = 0; // looping over all the pixels in the cube #pragma omp parallel for for (int ipix = 0; ipix < m_counts.npix(); ++ipix) { // Getting the variability significance for the current pixel GNdarray pixSig = get_variability_sig(ipix); // Getting the significance evolution for the source for (int src = 0; src < srcInxPix.size(); ++src) { // Store the distribution if the source is located at this position if (srcInxPix[src] == ipix) { #pragma omp critical(ctfindvar_analyse_cube) for (int i = 0; i < nbins; i++) { m_pixsigsrc(src,i) = pixSig(i); } } } // endfor: looped over sources // Make sure that this code is not executed in parallel #pragma omp critical(ctfindvar_analyse_cube) { // Determine the pixel with the highest significance if (max(pixSig) > max_sig) { max_sig = max(pixSig); m_max_sig_dir = m_counts.inx2dir(ipix); m_pixsigmax = pixSig; } // Storing sig in skymap m_peaksigmap(ipix) = max(pixSig); // If the significance is greater than the significance threshold, // the position is stored in a model if (max(pixSig) > m_sig_threshold) { GSkyDir dir_pix = m_counts.inx2dir(ipix); m_model_above_thr.append(sky_model(dir_pix)); } } } // endfor: looped over pixels // Log results log_value(NORMAL, "Maximum significance", max_sig); log_value(NORMAL, "Right Ascension of maximum", m_max_sig_dir.ra_deg()); log_value(NORMAL, "Declination of maximum", m_max_sig_dir.dec_deg()); // Return return; } /***********************************************************************//** * @brief Return pixel vector * * @return Vector of sky map pixels * * Returns a vector of sky map pixels that corresponds to the source model * positions. ***************************************************************************/ std::vector ctfindvar::get_pixels(void) { // Initialise pixel array std::vector srcInxPix; // If there are input models then extract if (m_inmodel.size() > 0) { // Loop over models for (int i = 0; i < m_inmodel.size(); ++i) { // Extract the model GModelSky* model = dynamic_cast(m_inmodel[i]); // If model is a sky model then extract the model centre. Note // that this only works for a model that results a GSkyRegionCircle // object as region. if (model != NULL) { // Get the source position const GModelSpatial* spatial = model->spatial(); const GSkyRegion* region = spatial->region(); const GSkyRegionCircle* circle = dynamic_cast(region); // If region circle is valid then if (circle != NULL) { int ipix = m_counts.dir2inx(circle->centre()); srcInxPix.push_back(ipix); continue; } } // endif: model was a sky model // If we are still alive then remove the model since it is neither // a sky model, nor does it have a region circle m_inmodel.remove(i); i--; } // endfor: looped over all models } // endif: there were input models // ... otherwise query the source position else { // Query source direction GSkyDir dir; if ((*this)["coordsys"].string() == "CEL") { dir.radec_deg((*this)["xsrc"].real(),(*this)["ysrc"].real()); } else { dir.lb_deg((*this)["xsrc"].real(),(*this)["ysrc"].real()); } // Store the index of this source int ipix = m_counts.dir2inx(dir); srcInxPix.push_back(ipix); } // endelse: queried source position // Return pixel array return srcInxPix; } /***********************************************************************//** * @brief Return sky model for a given sky direction * * @param[in] dir Sky direction * @return Sky model for a given sky direction * * Returns a point source sky model at the position of the current pixel * with a power law spectral model corresponding to 1% of the Crab flux. * * The model name is built from the celestial coordinates of the source. ***************************************************************************/ GModelSky ctfindvar::sky_model(const GSkyDir& dir) const { // Set point source spatial component from sky direction GModelSpatialPointSource spatial(dir); // Set fake spectral component (powerlaw with 1% of Crab) GModelSpectralPlaw spectral(5.7e-18, -2.48, GEnergy(0.3, "TeV")); // Create a sky model GModelSky model(spatial, spectral); // Build model name from celestial coordinates double ra = dir.ra_deg(); double dec = dir.dec_deg(); std::string name = gammalib::str(ra, 4); if (dec < 0.0) { name += "-"; } else { name += "+"; } name += gammalib::str(std::abs(dec), 4); // Set model name model.name(name); // Return model return model; } /***********************************************************************//** * @brief Get the significance of variability for a given skymap pixel * * @param[in] ipix Sky map pixel index. * @return Histogram of significances. * * Significance is computed according to Li & Ma equation 17: * see: https://doi.org/10.1086/161295 * Some modification is made in order to handle the case where Non = 0, * but Noff != 0. ***************************************************************************/ GNdarray ctfindvar::get_variability_sig(const int& ipix) { // Initialise result GNdarray sig_histogram(m_gti.size()); // Initialise vectors std::vector accepted_bin_bckg_vector(m_gti.size(), true); // Get alpha values for specified pixel std::vector alphas = get_alphas(ipix); // Exclude all bins from the background estimate for which the // number of events is below "minoff" or for which alpha is zero for (int i = 0; i < m_gti.size(); ++i) { if (m_counts(ipix, i) < m_minoff || alphas[i] == 0.0) { accepted_bin_bckg_vector[i] = false; } } // Loop over pixels until all background pixels are removed bool background_validated = false; while (background_validated == false) { // Signal that background was validated background_validated = true; // Loop over all the GTIs of the pixel for (int i = 0; i < m_gti.size(); ++i) { // The GTI is discarded from background calculation and not // checked again if (!accepted_bin_bckg_vector[i]) { continue; } // ... otherwise GTI is selected, and we loop over all the // others double noff = 0.0; double alpha = 0.0; for (int j = 0; j < m_gti.size(); ++j) { if (j != i && accepted_bin_bckg_vector[j]) { noff += m_counts(ipix, j); alpha += alphas[j]; } } // The background is averaged on the number of bins -1 double non = m_counts(ipix, i); alpha = alphas[i]/alpha; // Compute sensitivity in Gaussian sigma using Li & Ma double alpha1 = alpha + 1.0; double ntotal = non+noff; double arg1 = non/ntotal; double arg2 = noff/ntotal; double sig = 0.0; if (alpha != 0.0) { if (noff == 0.0) { sig = 0.0; } else if (non > 0.0) { double term1 = non * std::log((alpha1/alpha)*arg1); double term2 = noff * std::log(alpha1*arg2); sig = std::sqrt(2.0 * (term1 + term2)); } else { sig = std::sqrt(2.0 * noff * std::log(alpha1)); } sig *= (non < alpha*noff) ? -1.0 : 1.0; } // Update the significance sig_histogram(i) = sig; // If bin is significant, it's removed from the background and // we loop again if (sig > m_sig_threshold) { accepted_bin_bckg_vector[i] = false; background_validated = false; } } // endfor: looped over all GTIs } // endwhile: iterate until converged // Return significance histogram return sig_histogram; } /***********************************************************************//** * @brief Get alpha vector * * @param[in] ipix Sky map pixel index * @return Vector containing effective exposure ***************************************************************************/ std::vector ctfindvar::get_alphas(const int& ipix) const { // Initialise alpha vector std::vector alphas(m_gti.size(), 0.0); // Convert pixel index into sky direction GSkyDir pix_dir = m_counts.inx2dir(ipix); // Loop over all observations for (int i = 0; i < m_obs.size(); ++i) { // Get CTA observation const GCTAObservation* obs = dynamic_cast(m_obs[i]); // Fall through if observation is not valid if (obs == NULL) { continue; } // Get RoI. Fall through if observation does not overlap with this // pixel position GCTARoi roi = obs->roi(); GSkyDir roi_centre = roi.centre().dir(); if (roi_centre.dist_deg(pix_dir) > roi.radius()) { continue; } // Convert sky direction to instrument direction GCTAInstDir instdir = obs->pointing().instdir(pix_dir); // Extract the good time intervals of the observation const GGti& obs_gti(obs->gti()); // Loop over all time bins for (int j = 0; j < m_gti.size(); ++j) { // Make sure observation overlaps with this time interval double exposure = gti_overlap(m_gti[j], obs_gti); if (exposure > 0.0) { // Get IRF response const GCTAResponseIrf* rsp = dynamic_cast(obs->response()); // Throw an exception if observation has no instrument response function if (rsp == NULL) { std::string msg = "No response information available for "+ get_obs_header(obs)+" to compute IRF background. " "Please specify response information or use " "another background subtraction method."; throw GException::invalid_value(G_FILL_ALPHA_VECTOR, msg); } // Get IRF background template const GCTABackground* bkg = rsp->background(); // Throw an exception if observation has no IRF background template if (bkg == NULL) { std::string msg = "No IRF background template found in instrument " "response function for "+ get_obs_header(obs)+". Please specify an instrument " "response function containing a background template."; throw GException::invalid_value(G_FILL_ALPHA_VECTOR, msg); } // Add up background rate #pragma omp critical(ctfindvar_get_alphas) alphas[j] += exposure * bkg->rate_ebin(instdir, m_emin, m_emax); } // endif: exposure was positive } // endfor: looped over all time bins } // endfor: looped over all observations // Return alphas return alphas; } /***********************************************************************//** * @brief Returns number of seconds that two GTIs overlap * * @param[in] gti1 First GTI * @param[in] gti2 Second GTI * @return Number of seconds that @p gti1 and @p gti2 overlap ***************************************************************************/ double ctfindvar::gti_overlap(const GGti& gti1, const GGti& gti2) const { // Initialise no overlap double overlap = 0.0; // Set first and second GTI so that gti_1st starts before gti_2nd const GGti& gti_1st = (gti1.tstart() <= gti2.tstart()) ? gti1 : gti2; const GGti& gti_2nd = (gti1.tstart() > gti2.tstart()) ? gti1 : gti2; // Compute overlap if (gti_1st.tstop() > gti_2nd.tstart()) { if (gti_1st.tstop() <= gti_2nd.tstop()) { overlap = gti_1st.tstop() - gti_2nd.tstart(); } else if (gti_1st.tstop() > gti_2nd.tstop()) { overlap = gti_2nd.tstop() - gti_2nd.tstart(); } } // Return overlap return overlap; } /***********************************************************************//** * @brief Get start time * * @return Start time ***************************************************************************/ GTime ctfindvar::get_tstart(void) { // Initialise start time with large unreasonable Julian date GTime tstart("JD 999999999"); // If tmin parameter is valid then set the start time if ((*this)["tmin"].is_valid()) { // Query tmin parameter tstart = (*this)["tmin"].time(); // Log action log_string(NORMAL, "Setting start time \"tmin\" parameter"); } // endif: tmin parameter was valid // ... otherwise set start time from observations else { // Loop over all observations for (int k = 0; k < m_obs.size(); ++k) { // Get CTA observation GCTAObservation* obs = dynamic_cast(m_obs[k]); // Continue only if observation is valid if (obs != NULL) { // Update start time if the start time of the observation is // smaller than the current start time if (obs->gti().tstart() < tstart) { tstart = obs->gti().tstart(); } } // endif: observation was valid } // endfor: looped over observations // Log action log_string(NORMAL, "Setting start time from observations"); } // endelse: no valid time specified // Return start time return tstart; } /***********************************************************************//** * @brief Get stop time * * @return Stop time ***************************************************************************/ GTime ctfindvar::get_tstop(void) { // Initialise stop time with small unreasonable Julian date GTime tstop("JD 0"); // If tmax parameter is valid then set the stop time if ((*this)["tmax"].is_valid()) { // Query tmax parameter tstop = (*this)["tmax"].time(); // Log action log_string(NORMAL, "Setting stop time \"tmax\" parameter"); } // endif: tmax parameter was valid // ... otherwise set stop time from observations else { // Loop over all observations for (int k = 0; k < m_obs.size(); ++k) { // Get CTA observation GCTAObservation* obs = dynamic_cast(m_obs[k]); // Continue only if observation is valid if (obs != NULL) { // Update stop time if the stop time of the observation is // larger than the current stop time if (obs->gti().tstop() > tstop) { tstop = obs->gti().tstop(); } } // endif: observation was valid } // endfor: looped over observations // Log action log_string(NORMAL, "Setting stop time from observations"); } // endelse: no valid time specified // Return stop time return tstop; } /***********************************************************************//** * @brief Write source histograms in FITS format * * @param[in,out] fits FITS file * * This method creates two FITS binary table extensions and appends them to * the FITS file. * * The first extension is named "SOURCE SIGNIFICANCE" and contains the * significance of the variability as function of time bin for the pixel * with the maximum significance and for each source. * * The second extension is named "SOURCE POSITION" and contains the Right * Ascension and Declination for the maximum significance pixel and for each * source. ***************************************************************************/ void ctfindvar::write_source_histograms(GFits& fits) { // Setup data storage for time and source significances double tinterval = (*this)["tinterval"].real(); int nbins = (m_tstop-m_tstart) / tinterval + 0.5; int nsrc = m_pixsigsrc.shape()[0]; GNdarray time_info(2, nbins); GNdarray max_pixel_info(nbins); GNdarray src_info(nsrc, nbins); // Loop over time bins for (int i = 0; i < nbins; ++i) { // Set time bin mid point GTime tstart_bin(m_tstart + i * tinterval); GTime tstop_bin(m_tstart + (i+1) * tinterval); GTime midpoint((tstop_bin.secs()+tstart_bin.secs())*0.5); // Store the time of this bin time_info(0,i) = tstart_bin.mjd(); time_info(1,i) = tstop_bin.mjd(); // Get the index associated with this time int index = time2inx(midpoint); // Loop over every source for (int k = 0; k < nsrc; ++k) { src_info(k,i) = (index >= 0) ? m_pixsigsrc(k,index) : 0.0; } // Fill maximum pixel information max_pixel_info(i) = (index >= 0) ? m_pixsigmax(index) : 0.0; } // endfor: looped over time bins // Create binary tables GFitsBinTable table_signif(nbins); GFitsBinTable table_pos(nsrc+1); table_signif.extname("SOURCE SIGNIFICANCE"); table_pos.extname("SOURCE POSITION"); // Create columns for time bins GFitsTableDoubleCol time_start("TSTART", nbins); GFitsTableDoubleCol time_stop("TSTOP", nbins); GFitsTableDoubleCol time_sigma("MAXSIGPIXEL", nbins); time_start.unit("MJD"); time_stop.unit("MJD"); time_sigma.unit("sigma"); // Create columns for source position GFitsTableStringCol pos_name("SOURCE", nsrc+1, 20); GFitsTableDoubleCol pos_ra("RA", nsrc+1); GFitsTableDoubleCol pos_dec("DEC", nsrc+1); pos_ra.unit("deg"); pos_dec.unit("deg"); // Fill time bin columns for (int i = 0; i < nbins; ++i) { time_start(i) = time_info(0,i); time_stop(i) = time_info(1,i); time_sigma(i) = max_pixel_info(i); } // Fill source columns pos_name(0) = "MAXSIGPIXEL"; pos_ra(0) = m_max_sig_dir.ra_deg(); pos_dec(0) = m_max_sig_dir.dec_deg(); // Append columns to tables table_signif.append(time_start); table_signif.append(time_stop); table_signif.append(time_sigma); // Append the distribution for each source for (int k = 0; k < nsrc; ++k) { // Initialise source name and direction std::string src_name("SOURCE"); GSkyDir src_dir; // Get the name from the input model file if (m_inmodel.size() > k) { // Extract the model GModelSky* model = dynamic_cast(m_inmodel[k]); // If model is a sky model then extract the model centre. Note // that this only works for a model that results a GSkyRegionCircle // object as region. if (model != NULL) { // Get the source position const GModelSpatial* spatial = model->spatial(); const GSkyRegion* region = spatial->region(); const GSkyRegionCircle* circle = dynamic_cast(region); // If region circle is valid then extract source name and // sky direction if (circle != NULL) { src_name = m_inmodel[k]->name(); src_dir = circle->centre(); } } // endif: model was a sky model } // endif: there were enough models in the input model // ... otherwise set the position from the xsrc and ysrc parameters else { if ((*this)["coordsys"].string() == "CEL") { src_dir.radec_deg((*this)["xsrc"].real(), (*this)["ysrc"].real()); } else { src_dir.lb_deg((*this)["xsrc"].real(), (*this)["ysrc"].real()); } } // Create new time column for source GFitsTableDoubleCol column(src_name, nbins); column.unit("sigma"); // Fill column for (int i = 0; i < nbins; ++i) { column(i) = src_info(k, i); } // Append column to time table table_signif.append(column); // Store source infirmation pos_name(k+1) = src_name; pos_ra(k+1) = src_dir.ra_deg(); pos_dec(k+1) = src_dir.dec_deg(); } // endfor: looped over sources // Append columns to tables table_pos.append(pos_name); table_pos.append(pos_ra); table_pos.append(pos_dec); // Append tables to FITS file fits.append(table_signif); fits.append(table_pos); // Return return; } /***********************************************************************//** * @brief Get the map index associated with a given time * * @param[in] time Time * @return Index of map in counts cube ***************************************************************************/ int ctfindvar::time2inx(const GTime& time) const { // Set default return value int map_index = -1; // Loop over all GTIs for (int i = 0; i < m_gti.size(); ++i) { // If interval contains the time then break if (m_gti[i].contains(time)) { map_index = i; break; } } // Return map index return map_index; }