ctools  2.0.0
 All Classes Namespaces Files Functions Variables Macros Pages
ctfindvar.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * ctfindvar - Time variability search tool *
3  * ----------------------------------------------------------------------- *
4  * copyright (C) 2018-2022 by Simon Bonnefoy *
5  * ----------------------------------------------------------------------- *
6  * *
7  * This program is free software: you can redistribute it and/or modify *
8  * it under the terms of the GNU General Public License as published by *
9  * the Free Software Foundation, either version 3 of the License, or *
10  * (at your option) any later version. *
11  * *
12  * This program is distributed in the hope that it will be useful, *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15  * GNU General Public License for more details. *
16  * *
17  * You should have received a copy of the GNU General Public License *
18  * along with this program. If not, see <http://www.gnu.org/licenses/>. *
19  * *
20  ***************************************************************************/
21 /**
22  * @file ctfindvar.cpp
23  * @brief Time variability search tool implementation
24  * @author Simon Bonnefoy
25  */
26 
27 /* __ Includes ___________________________________________________________ */
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31 #include "ctfindvar.hpp"
32 #include "GammaLib.hpp"
33 #include "GCTALib.hpp"
34 #include <cmath>
35 
36 /* __ OpenMP section _____________________________________________________ */
37 #ifdef _OPENMP
38 #include <omp.h>
39 #endif
40 
41 /* __ Method name definitions ____________________________________________ */
42 #define G_INX2GTI "ctfindvar::inx2gti(int&)"
43 #define G_FILL_CUBE "ctfindvar::fill_cube(GCTAObservation*)"
44 #define G_FILL_ALPHA_VECTOR "ctfindvar::fill_alpha_vector(const int&, "\
45  "vector<double>&)"
46 #define G_INIT_CUBE "ctfindvar::init_cube(void)"
47 #define G_INIT_GTIS "ctfindvar::init_gtis(void)"
48 
49 /* __ Debug definitions __________________________________________________ */
50 
51 /* __ Coding definitions _________________________________________________ */
52 
53 /* __ Constants __________________________________________________________ */
54 
55 
56 /*==========================================================================
57  = =
58  = Constructors/destructors =
59  = =
60  ==========================================================================*/
61 
62 /***********************************************************************//**
63  * @brief Void constructor
64  *
65  * Constructs empty time variability search tool.
66  ***************************************************************************/
68 {
69  // Initialise members
70  init_members();
71 
72  // Return
73  return;
74 }
75 
76 
77 /***********************************************************************//**
78  * @brief Observations constructor
79  *
80  * param[in] obs Observation container.
81  *
82  * Constructs time variability search tool from an observation container.
83  ***************************************************************************/
84 ctfindvar::ctfindvar(const GObservations& obs) :
85  ctobservation(CTFINDVAR_NAME, VERSION, obs)
86 {
87  // Initialise members
88  init_members();
89 
90  // Return
91  return;
92 }
93 
94 
95 /***********************************************************************//**
96  * @brief Command line constructor
97  *
98  * @param[in] argc Number of arguments in command line.
99  * @param[in] argv Array of command line arguments.
100  *
101  * Constructs time variability search tool using command line arguments for
102  * user parameter setting.
103  ***************************************************************************/
104 ctfindvar::ctfindvar(int argc, char *argv[]) :
105  ctobservation(CTFINDVAR_NAME, VERSION, argc, argv)
106 {
107  // Initialise members
108  init_members();
109 
110  // Return
111  return;
112 }
113 
114 
115 /***********************************************************************//**
116  * @brief Copy constructor
117  *
118  * @param[in] app search time variability tool.
119  *
120  * Constructs time variability search tool from another time variability
121  * search tool.
122  ***************************************************************************/
124 {
125  // Initialise members
126  init_members();
127 
128  // Copy members
129  copy_members(app);
130 
131  // Return
132  return;
133 }
134 
135 
136 /***********************************************************************//**
137  * @brief Destructor
138  *
139  * Destructs search time variability tool.
140  ***************************************************************************/
142 {
143  // Free members
144  free_members();
145 
146  // Return
147  return;
148 }
149 
150 
151 /*==========================================================================
152  = =
153  = Operators =
154  = =
155  ==========================================================================*/
156 
157 /***********************************************************************//**
158  * @brief Assignment operator
159  *
160  * @param[in] app Time variability search tool.
161  * @return Time variability search tool.
162  *
163  * Assigns time variability search tool.
164  ***************************************************************************/
166 {
167  // Execute only if object is not identical
168  if (this != &app) {
169 
170  // Copy base class members
171  this->ctobservation::operator=(app);
172 
173  // Free members
174  free_members();
175 
176  // Initialise members
177  init_members();
178 
179  // Copy members
180  copy_members(app);
181 
182  } // endif: object was not identical
183 
184  // Return this object
185  return *this;
186 }
187 
188 
189 /*==========================================================================
190  = =
191  = Public methods =
192  = =
193  ==========================================================================*/
194 
195 /***********************************************************************//**
196  * @brief Clear time variability search tool
197  *
198  * Clears time variability search tool.
199  ***************************************************************************/
201 {
202  // Free members
203  free_members();
205  this->ctool::free_members();
206 
207  // Clear base class (needed to conserve tool name and version)
208  this->GApplication::clear();
209 
210  // Initialise members
211  this->ctool::init_members();
213  init_members();
214 
215  // Write header into logger
216  log_header();
217 
218  // Return
219  return;
220 }
221 
222 
223 /***********************************************************************//**
224  * @brief Process time variability search tool
225  ***************************************************************************/
227 {
228  // Get task parameters
229  get_parameters();
230 
231  // Write input observation container into logger
232  log_observations(NORMAL, m_obs, "Input observation");
233 
234  // Create GTIs
235  init_gtis();
236 
237  // Create counts cube
238  create_cube();
239 
240  // Analyse cube
241  analyse_cube();
242 
243  // Return
244  return;
245 }
246 
247 
248 /***********************************************************************//**
249  * @brief Save peak significance map and significance distributions
250  *
251  * Saves the peak significance and source signficance distributions.
252  * Optionally, the generated counts cube can also be saved if 'outcube' has
253  * been specified.
254  ***************************************************************************/
255 void ctfindvar::save(void)
256 {
257  // Write header
258  log_header1(TERSE, "Saving results");
259 
260  // Save counts cube if a valid filename is given and the counts cube is
261  // not empty
262  if ((*this)["outcube"].is_valid()) {
263 
264  // Get counts cube output filename
265  GFilename outcube = (*this)["outcube"].filename();
266 
267  // Save counts cube if it is not empty. Otherwise signal that no
268  // counts cube was saved.
269  if (!m_counts.is_empty()) {
270  m_counts.save(outcube, (*this)["clobber"].boolean());
271  stamp(outcube);
272  log_value(TERSE, "Counts cube file", outcube.url());
273  }
274  else {
275  log_value(TERSE, "Counts cube file",
276  outcube.url()+" (cube is empty, no file created)");
277  }
278 
279  }
280 
281  // Get output map and model filenames
282  GFilename outmap = (*this)["outmap"].filename();
283  GFilename outmodel = (*this)["outmodel"].filename();
284 
285  // Save output map if the significance map is not empty
286  if (!m_peaksigmap.is_empty()) {
287 
288  // Create a FITS file for storing the output
289  GFits fits;
290 
291  // Write the most significant values for each pixel
292  m_peaksigmap.write(fits, "PEAKSIGMAP");
293 
294  // Write source histograms
296 
297  // Stamp FITS file
298  stamp(fits);
299 
300  // Save significance map
301  fits.saveto(outmap, (*this)["clobber"].boolean());
302 
303  // Log saving
304  log_value(TERSE, "Output map file", outmap.url());
305 
306  }
307  else {
308  log_value(TERSE, "Output map file",
309  outmap.url()+" (map is empty, no file created)");
310  }
311 
312  // Save model definiton XML file if the significance map is not empty.
313  // This may store an empty model in case that none of the pixels is
314  // above threshold.
315  if (!m_peaksigmap.is_empty()) {
316 
317  // Save model
318  m_model_above_thr.save(outmodel);
319 
320  // Log saving
321  log_value(TERSE, "Model definition file", outmodel.url());
322 
323  }
324  else {
325  log_value(TERSE, "Model definition file",
326  outmodel.url()+" (map is empty, no file created)");
327  }
328 
329  // Return
330  return;
331 }
332 
333 
334 /*==========================================================================
335  = =
336  = Private methods =
337  = =
338  ==========================================================================*/
339 
340 /***********************************************************************//**
341  * @brief Initialise class members
342  ***************************************************************************/
344 {
345  // Initialise members
346  m_counts.clear();
347  m_gti.clear();
348  m_inmodel.clear();
349  m_max_sig_dir.clear();
350  m_minoff = 0.0;
351  m_sig_threshold = 0.0;
352  m_peaksigmap.clear();
353  m_pixsigsrc.clear();
354  m_tstart.clear();
355  m_tstop.clear();
356  m_emin.clear();
357  m_emax.clear();
358  m_model_above_thr.clear();
359 
360  // Return
361  return;
362 }
363 
364 
365 /***********************************************************************//**
366  * @brief Copy class members
367  *
368  * @param[in] app search time variability tool.
369  ***************************************************************************/
371 {
372  // Copy members
373  m_counts = app.m_counts;
374  m_gti = app.m_gti;
375  m_inmodel = app.m_inmodel;
377  m_minoff = app.m_minoff;
380  m_pixsigsrc = app.m_pixsigsrc;
381  m_tstart = app.m_tstart;
382  m_tstop = app.m_tstop;
383  m_emin = app.m_emin;
384  m_emax = app.m_emax;
386 
387  // Return
388  return;
389 }
390 
391 
392 /***********************************************************************//**
393  * @brief Delete class members
394  ***************************************************************************/
396 {
397  // Return
398  return;
399 }
400 
401 
402 /***********************************************************************//**
403  * @brief Get application parameters
404  ***************************************************************************/
406 {
407  // Load the observations
408  setup_observations(m_obs, true, true, false);
409 
410  // Either load model or query source position parameters
411  if ((*this)["inmodel"].is_valid()) {
412  m_inmodel = GModels((*this)["inmodel"].filename());
413  }
414  else {
415  (*this)["coordsys"].string();
416  (*this)["xsrc"].real();
417  (*this)["ysrc"].real();
418  }
419 
420  // Query time limit parameters
421  (*this)["tinterval"].real();
422  if ((*this)["tmin"].is_valid()) {
423  (*this)["tmin"].time();
424  }
425  if ((*this)["tmax"].is_valid()) {
426  (*this)["tmax"].time();
427  }
428 
429  // Create map to query spatial parameters
430  create_map(m_obs);
431 
432  // Read energy limits
433  m_emin = GEnergy((*this)["emin"].real(), "TeV");
434  m_emax = GEnergy((*this)["emax"].real(), "TeV");
435 
436  // Get minimum counts for a bin to be considered in Noff calculation
437  m_minoff = (*this)["minoff"].real();
438 
439  // Get minimum significance to set a source as variable
440  m_sig_threshold = (*this)["threshold"].real();
441 
442  // Query (hidden) smoothing parameters
443  (*this)["smooth_kernel"].string();
444  (*this)["smooth_rad"].real();
445 
446  // If needed later, query output filenames now
447  if (read_ahead()) {
448  (*this)["outcube"].query();
449  (*this)["outmap"].query();
450  (*this)["outmodel"].query();
451  }
452 
453  // Set number of OpenMP threads
454  #ifdef _OPENMP
455  int nthreads = (*this)["nthreads"].integer();
456  if (nthreads > 0) {
457  omp_set_num_threads(nthreads);
458  }
459  #endif
460 
461  // Write parameters into logger
462  log_parameters(TERSE);
463 
464  // Return
465  return;
466 }
467 
468 
469 /***********************************************************************//**
470  * @brief Initialize Good Time Intervals
471  *
472  * @exception GException::invalid_value
473  * Invalid start or stop time or insufficient time bins
474  *
475  * Initialise Good Time Intervals for the variability search.
476  ***************************************************************************/
478 {
479  // Log header
480  log_header1(NORMAL, "Initialise Good Time Intervals");
481 
482  // Clear time intervals
483  m_gti.clear();
484 
485  // Set start and stop times
486  m_tstart = get_tstart();
487  m_tstop = get_tstop();
488 
489  // Query time interval
490  double tinterval = (*this)["tinterval"].real();
491 
492  // Compute time bins
493  double tstart_sec = m_tstart.secs();
494  double tstop_sec = m_tstop.secs();
495  int bins = (tstop_sec - tstart_sec) / tinterval + 0.5;
496 
497  // Log information
498  log_value(NORMAL, "Reference time (mjd)", m_tstart.reference().mjdref());
499  log_value(NORMAL, "Start time (sec)", tstart_sec);
500  log_value(NORMAL, "Stop time (sec)", tstop_sec);
501  log_value(NORMAL, "Total time (sec)", tstop_sec-tstart_sec);
502  log_value(NORMAL, "Time interval (sec)", tinterval);
503  log_value(NORMAL, "Number of time bins", bins);
504 
505  // Throw exception if tstart == tstop
506  if (m_tstart == m_tstop) {
507  throw GException::invalid_value(G_INIT_GTIS,
508  "Start time is equal to stop time");
509  }
510  else if (bins < 2) {
511  std::string msg = "Method requires at least two time bins (" +
512  gammalib::str(bins) + " bins found).";
513  throw GException::invalid_value(G_INIT_GTIS, msg);
514  }
515 
516  // Set up Good Time Intervals by appending all intervals that overlap
517  // with at least one of the observations
518  for (int i = 0; i < bins; ++i) {
519 
520  // Get time interval
521  GTime tstart(m_tstart + i*tinterval);
522  GTime tstop(m_tstart + (i+1.0)*tinterval);
523  GGti gti(tstart, tstop);
524 
525  // Make sure there's an observation that overlaps with this GTI
526  for (int k = 0; k < m_obs.size(); ++k) {
527 
528  // Get CTA observation
529  GCTAObservation* obs = dynamic_cast<GCTAObservation*>(m_obs[k]);
530 
531  // Continue only if observation is valid
532  if (obs != NULL) {
533 
534  // Add GTI is the time intervals overlap
535  if (gti_overlap(obs->gti(), gti) > 0.0) {
536  m_gti.push_back(gti);
537  break;
538  }
539 
540  } // endif: observation was valid
541 
542  } // endfor: looped over observations
543 
544  } // endfor: looped over Good Time Intervals
545 
546  // Return
547  return;
548 }
549 
550 
551 /***********************************************************************//**
552  * @brief Create counts cube
553  *
554  * Creates a counts cube comprising a sky map for each Good Time Interval.
555  ***************************************************************************/
557 {
558  // Log header
559  log_header1(NORMAL, "Create counts cube");
560 
561  // Create the basic skymap
563 
564  // Resize to the appropriate number of time intervals
565  m_counts.nmaps(m_gti.size());
566 
567  // Create the peaksigmap
568  m_peaksigmap = m_counts.extract(1);
569 
570  // Loop over all unbinned CTA observations in the container
571  #pragma omp parallel for
572  for (int i = 0; i < m_obs.size(); ++i) {
573 
574  // Get pointer to observation
575  GCTAObservation* obs = dynamic_cast<GCTAObservation*>(m_obs[i]);
576 
577  // Fill the cube
578  fill_cube(obs);
579 
580  // Dispose events to free memory
581  obs->dispose_events();
582 
583  } // endfor: looped over observations
584 
585  // Smooth the maps if requested
586  if ((*this)["smooth_kernel"].string() != "NONE") {
587  m_counts.smooth((*this)["smooth_kernel"].string(),
588  (*this)["smooth_rad"].real());
589  }
590 
591  // Return
592  return;
593 }
594 
595 
596 /***********************************************************************//**
597  * @brief Fill the cube from the events in an observation
598  *
599  * @param[in] obs Pointer to CTA observation.
600  *
601  * Fills the cube from the events in an observation.
602  ***************************************************************************/
603 void ctfindvar::fill_cube(GCTAObservation* obs)
604 {
605  // Make sure that the observation holds a CTA event list. If this
606  // is not the case then throw an exception.
607  const GCTAEventList* events = dynamic_cast<const GCTAEventList*>
608  (obs->events());
609  if (events == NULL) {
610  std::string msg = "CTA Observation does not contain an event "
611  "list. An event list is needed to fill the "
612  "counts cube.";
613  throw GException::invalid_value(G_FILL_CUBE, msg);
614  }
615 
616  // Get the RoI
617  const GCTARoi& roi = events->roi();
618 
619  // Check for RoI sanity
620  if (!roi.is_valid()) {
621  std::string msg = "No valid RoI found in input observation "
622  "\""+obs->name()+"\". Run ctselect to specify "
623  "an RoI for this observation before running "
624  "ctbin.";
625  throw GException::invalid_value(G_FILL_CUBE, msg);
626  }
627 
628  // Initialise binning statistics
629  int num_outside_roi = 0;
630  int num_invalid_wcs = 0;
631  int num_outside_ecut = 0;
632  int num_outside_map = 0;
633  int num_outside_time = 0;
634  int num_in_map = 0;
635 
636  // Fill counts sky map
637  for (int i = 0; i < events->size(); ++i) {
638 
639  // Get event
640  const GCTAEventAtom* event = (*events)[i];
641 
642  // Check that the energy is valid
643  if ((event->energy() < m_emin) || (event->energy() > m_emax)) {
644  num_outside_ecut++;
645  continue;
646  }
647 
648  // Determine event time index
649  int time_indx = time2inx(event->time());
650 
651  // Check if event is in the GTIs
652  if ((time_indx > -1) && (time_indx < m_counts.nmaps())) {
653 
654  // Get event direction
655  GSkyDir evnt_dir = GCTAInstDir(event->dir()).dir();
656 
657  // Skip event if it is outside the RoI
658  if (roi.centre().dir().dist_deg(evnt_dir) > roi.radius()) {
659  num_outside_roi++;
660  continue;
661  }
662 
663  // Determine sky pixel
664  GSkyPixel pixel;
665  try {
666  pixel = m_counts.dir2pix(evnt_dir);
667  }
668  catch (std::exception &e) {
669  num_invalid_wcs++;
670  continue;
671  }
672 
673  // Skip event if corresponding counts cube pixel is outside the
674  // counts cube map range
675  if (pixel.x() < -0.5 || pixel.x() > (m_counts.nx()-0.5) ||
676  pixel.y() < -0.5 || pixel.y() > (m_counts.ny()-0.5)) {
677  num_outside_map++;
678  continue;
679  }
680 
681  // Fill event in skymap
682  #pragma omp critical(ctfindvar_fill_cube)
683  m_counts(pixel, time_indx) += 1.0;
684 
685  // Increment number of maps
686  num_in_map++;
687 
688  }
689 
690  // ... otherwise, event falls outside the specified time range
691  else {
692  num_outside_time++;
693  }
694 
695  } // endfor: looped over all events
696 
697  // Log filling results
698  #pragma omp critical(ctfindvar_fill_cube)
699  {
700  log_header3(TERSE, get_obs_header(obs));
701  log_value(NORMAL, "Events in list", obs->events()->size());
702  log_value(NORMAL, "Events in cube", num_in_map);
703  log_value(NORMAL, "Events outside RoI", num_outside_roi);
704  log_value(NORMAL, "Events outside energy", num_outside_ecut);
705  log_value(NORMAL, "Events with invalid WCS", num_invalid_wcs);
706  log_value(NORMAL, "Events outside cube area", num_outside_map);
707  log_value(NORMAL, "Events outside time bins", num_outside_time);
708  }
709 
710  // Return
711  return;
712 }
713 
714 
715 /***********************************************************************//**
716  * @brief Analyse all pixels of counts cube
717  ***************************************************************************/
719 {
720  // Log header
721  log_header1(NORMAL, "Analyse sky map pixels");
722 
723  // Get number of GTIs
724  const int nbins = m_gti.size();
725 
726  // Get relevant sky map pixels
727  std::vector<int> srcInxPix = get_pixels();
728 
729  // Preparing the histogram with significance evolution for the source
730  // center and the highest sig-pix
731  m_pixsigsrc = GNdarray(srcInxPix.size(), nbins);
732  m_pixsigmax = GNdarray(nbins);
733 
734  // Initialise maximum significance
735  double max_sig = 0;
736 
737  // looping over all the pixels in the cube
738  #pragma omp parallel for
739  for (int ipix = 0; ipix < m_counts.npix(); ++ipix) {
740 
741  // Getting the variability significance for the current pixel
742  GNdarray pixSig = get_variability_sig(ipix);
743 
744  // Getting the significance evolution for the source
745  for (int src = 0; src < srcInxPix.size(); ++src) {
746 
747  // Store the distribution if the source is located at this position
748  if (srcInxPix[src] == ipix) {
749 
750  #pragma omp critical(ctfindvar_analyse_cube)
751  for (int i = 0; i < nbins; i++) {
752  m_pixsigsrc(src,i) = pixSig(i);
753  }
754  }
755 
756  } // endfor: looped over sources
757 
758  // Make sure that this code is not executed in parallel
759  #pragma omp critical(ctfindvar_analyse_cube)
760  {
761  // Determine the pixel with the highest significance
762  if (max(pixSig) > max_sig) {
763  max_sig = max(pixSig);
764  m_max_sig_dir = m_counts.inx2dir(ipix);
765  m_pixsigmax = pixSig;
766  }
767 
768  // Storing sig in skymap
769  m_peaksigmap(ipix) = max(pixSig);
770 
771  // If the significance is greater than the significance threshold,
772  // the position is stored in a model
773  if (max(pixSig) > m_sig_threshold) {
774  GSkyDir dir_pix = m_counts.inx2dir(ipix);
775  m_model_above_thr.append(sky_model(dir_pix));
776  }
777  }
778 
779  } // endfor: looped over pixels
780 
781  // Log results
782  log_value(NORMAL, "Maximum significance", max_sig);
783  log_value(NORMAL, "Right Ascension of maximum", m_max_sig_dir.ra_deg());
784  log_value(NORMAL, "Declination of maximum", m_max_sig_dir.dec_deg());
785 
786  // Return
787  return;
788 }
789 
790 
791 /***********************************************************************//**
792  * @brief Return pixel vector
793  *
794  * @return Vector of sky map pixels
795  *
796  * Returns a vector of sky map pixels that corresponds to the source model
797  * positions.
798  ***************************************************************************/
799 std::vector<int> ctfindvar::get_pixels(void)
800 {
801  // Initialise pixel array
802  std::vector<int> srcInxPix;
803 
804  // If there are input models then extract
805  if (m_inmodel.size() > 0) {
806 
807  // Loop over models
808  for (int i = 0; i < m_inmodel.size(); ++i) {
809 
810  // Extract the model
811  GModelSky* model = dynamic_cast<GModelSky*>(m_inmodel[i]);
812 
813  // If model is a sky model then extract the model centre. Note
814  // that this only works for a model that results a GSkyRegionCircle
815  // object as region.
816  if (model != NULL) {
817 
818  // Get the source position
819  const GModelSpatial* spatial = model->spatial();
820  const GSkyRegion* region = spatial->region();
821  const GSkyRegionCircle* circle = dynamic_cast<const GSkyRegionCircle*>(region);
822 
823  // If region circle is valid then
824  if (circle != NULL) {
825  int ipix = m_counts.dir2inx(circle->centre());
826  srcInxPix.push_back(ipix);
827  continue;
828  }
829 
830  } // endif: model was a sky model
831 
832  // If we are still alive then remove the model since it is neither
833  // a sky model, nor does it have a region circle
834  m_inmodel.remove(i);
835  i--;
836 
837  } // endfor: looped over all models
838 
839  } // endif: there were input models
840 
841  // ... otherwise query the source position
842  else {
843 
844  // Query source direction
845  GSkyDir dir;
846  if ((*this)["coordsys"].string() == "CEL") {
847  dir.radec_deg((*this)["xsrc"].real(),(*this)["ysrc"].real());
848  }
849  else {
850  dir.lb_deg((*this)["xsrc"].real(),(*this)["ysrc"].real());
851  }
852 
853  // Store the index of this source
854  int ipix = m_counts.dir2inx(dir);
855  srcInxPix.push_back(ipix);
856 
857  } // endelse: queried source position
858 
859  // Return pixel array
860  return srcInxPix;
861 }
862 
863 
864 /***********************************************************************//**
865  * @brief Return sky model for a given sky direction
866  *
867  * @param[in] dir Sky direction
868  * @return Sky model for a given sky direction
869  *
870  * Returns a point source sky model at the position of the current pixel
871  * with a power law spectral model corresponding to 1% of the Crab flux.
872  *
873  * The model name is built from the celestial coordinates of the source.
874  ***************************************************************************/
875 GModelSky ctfindvar::sky_model(const GSkyDir& dir) const
876 {
877  // Set point source spatial component from sky direction
878  GModelSpatialPointSource spatial(dir);
879 
880  // Set fake spectral component (powerlaw with 1% of Crab)
881  GModelSpectralPlaw spectral(5.7e-18, -2.48, GEnergy(0.3, "TeV"));
882 
883  // Create a sky model
884  GModelSky model(spatial, spectral);
885 
886  // Build model name from celestial coordinates
887  double ra = dir.ra_deg();
888  double dec = dir.dec_deg();
889  std::string name = gammalib::str(ra, 4);
890  if (dec < 0.0) {
891  name += "-";
892  }
893  else {
894  name += "+";
895  }
896  name += gammalib::str(std::abs(dec), 4);
897 
898  // Set model name
899  model.name(name);
900 
901  // Return model
902  return model;
903 }
904 
905 
906 /***********************************************************************//**
907  * @brief Get the significance of variability for a given skymap pixel
908  *
909  * @param[in] ipix Sky map pixel index.
910  * @return Histogram of significances.
911  *
912  * Significance is computed according to Li & Ma equation 17:
913  * see: https://doi.org/10.1086/161295
914  * Some modification is made in order to handle the case where Non = 0,
915  * but Noff != 0.
916  ***************************************************************************/
917 GNdarray ctfindvar::get_variability_sig(const int& ipix)
918 {
919  // Initialise result
920  GNdarray sig_histogram(m_gti.size());
921 
922  // Initialise vectors
923  std::vector<bool> accepted_bin_bckg_vector(m_gti.size(), true);
924 
925  // Get alpha values for specified pixel
926  std::vector<double> alphas = get_alphas(ipix);
927 
928  // Exclude all bins from the background estimate for which the
929  // number of events is below "minoff" or for which alpha is zero
930  for (int i = 0; i < m_gti.size(); ++i) {
931  if (m_counts(ipix, i) < m_minoff || alphas[i] == 0.0) {
932  accepted_bin_bckg_vector[i] = false;
933  }
934  }
935 
936  // Loop over pixels until all background pixels are removed
937  bool background_validated = false;
938  while (background_validated == false) {
939 
940  // Signal that background was validated
941  background_validated = true;
942 
943  // Loop over all the GTIs of the pixel
944  for (int i = 0; i < m_gti.size(); ++i) {
945 
946  // The GTI is discarded from background calculation and not
947  // checked again
948  if (!accepted_bin_bckg_vector[i]) {
949  continue;
950  }
951 
952  // ... otherwise GTI is selected, and we loop over all the
953  // others
954  double noff = 0.0;
955  double alpha = 0.0;
956  for (int j = 0; j < m_gti.size(); ++j) {
957  if (j != i && accepted_bin_bckg_vector[j]) {
958  noff += m_counts(ipix, j);
959  alpha += alphas[j];
960  }
961  }
962 
963  // The background is averaged on the number of bins -1
964  double non = m_counts(ipix, i);
965  alpha = alphas[i]/alpha;
966 
967  // Compute sensitivity in Gaussian sigma using Li & Ma
968  double alpha1 = alpha + 1.0;
969  double ntotal = non+noff;
970  double arg1 = non/ntotal;
971  double arg2 = noff/ntotal;
972  double sig = 0.0;
973  if (alpha != 0.0) {
974  if (noff == 0.0) {
975  sig = 0.0;
976  }
977  else if (non > 0.0) {
978  double term1 = non * std::log((alpha1/alpha)*arg1);
979  double term2 = noff * std::log(alpha1*arg2);
980  sig = std::sqrt(2.0 * (term1 + term2));
981  }
982  else {
983  sig = std::sqrt(2.0 * noff * std::log(alpha1));
984  }
985  sig *= (non < alpha*noff) ? -1.0 : 1.0;
986  }
987 
988  // Update the significance
989  sig_histogram(i) = sig;
990 
991  // If bin is significant, it's removed from the background and
992  // we loop again
993  if (sig > m_sig_threshold) {
994  accepted_bin_bckg_vector[i] = false;
995  background_validated = false;
996  }
997 
998  } // endfor: looped over all GTIs
999 
1000  } // endwhile: iterate until converged
1001 
1002  // Return significance histogram
1003  return sig_histogram;
1004 }
1005 
1006 
1007 /***********************************************************************//**
1008  * @brief Get alpha vector
1009  *
1010  * @param[in] ipix Sky map pixel index
1011  * @return Vector containing effective exposure
1012  ***************************************************************************/
1013 std::vector<double> ctfindvar::get_alphas(const int& ipix) const
1014 {
1015  // Initialise alpha vector
1016  std::vector<double> alphas(m_gti.size(), 0.0);
1017 
1018  // Convert pixel index into sky direction
1019  GSkyDir pix_dir = m_counts.inx2dir(ipix);
1020 
1021  // Loop over all observations
1022  for (int i = 0; i < m_obs.size(); ++i) {
1023 
1024  // Get CTA observation
1025  const GCTAObservation* obs = dynamic_cast<const GCTAObservation*>(m_obs[i]);
1026 
1027  // Fall through if observation is not valid
1028  if (obs == NULL) {
1029  continue;
1030  }
1031 
1032  // Get RoI. Fall through if observation does not overlap with this
1033  // pixel position
1034  GCTARoi roi = obs->roi();
1035  GSkyDir roi_centre = roi.centre().dir();
1036  if (roi_centre.dist_deg(pix_dir) > roi.radius()) {
1037  continue;
1038  }
1039 
1040  // Convert sky direction to instrument direction
1041  GCTAInstDir instdir = obs->pointing().instdir(pix_dir);
1042 
1043  // Extract the good time intervals of the observation
1044  const GGti& obs_gti(obs->gti());
1045 
1046  // Loop over all time bins
1047  for (int j = 0; j < m_gti.size(); ++j) {
1048 
1049  // Make sure observation overlaps with this time interval
1050  double exposure = gti_overlap(m_gti[j], obs_gti);
1051  if (exposure > 0.0) {
1052 
1053  // Get IRF response
1054  const GCTAResponseIrf* rsp =
1055  dynamic_cast<const GCTAResponseIrf*>(obs->response());
1056 
1057  // Throw an exception if observation has no instrument response function
1058  if (rsp == NULL) {
1059  std::string msg =
1060  "No response information available for "+
1061  get_obs_header(obs)+" to compute IRF background. "
1062  "Please specify response information or use "
1063  "another background subtraction method.";
1064  throw GException::invalid_value(G_FILL_ALPHA_VECTOR, msg);
1065  }
1066 
1067  // Get IRF background template
1068  const GCTABackground* bkg = rsp->background();
1069 
1070  // Throw an exception if observation has no IRF background template
1071  if (bkg == NULL) {
1072  std::string msg =
1073  "No IRF background template found in instrument "
1074  "response function for "+
1075  get_obs_header(obs)+". Please specify an instrument "
1076  "response function containing a background template.";
1077  throw GException::invalid_value(G_FILL_ALPHA_VECTOR, msg);
1078  }
1079 
1080  // Add up background rate
1081  #pragma omp critical(ctfindvar_get_alphas)
1082  alphas[j] += exposure * bkg->rate_ebin(instdir, m_emin, m_emax);
1083 
1084  } // endif: exposure was positive
1085 
1086  } // endfor: looped over all time bins
1087 
1088  } // endfor: looped over all observations
1089 
1090  // Return alphas
1091  return alphas;
1092 }
1093 
1094 
1095 /***********************************************************************//**
1096  * @brief Returns number of seconds that two GTIs overlap
1097  *
1098  * @param[in] gti1 First GTI
1099  * @param[in] gti2 Second GTI
1100  * @return Number of seconds that @p gti1 and @p gti2 overlap
1101  ***************************************************************************/
1102 double ctfindvar::gti_overlap(const GGti& gti1, const GGti& gti2) const
1103 {
1104  // Initialise no overlap
1105  double overlap = 0.0;
1106 
1107  // Set first and second GTI so that gti_1st starts before gti_2nd
1108  const GGti& gti_1st = (gti1.tstart() <= gti2.tstart()) ? gti1 : gti2;
1109  const GGti& gti_2nd = (gti1.tstart() > gti2.tstart()) ? gti1 : gti2;
1110 
1111  // Compute overlap
1112  if (gti_1st.tstop() > gti_2nd.tstart()) {
1113  if (gti_1st.tstop() <= gti_2nd.tstop()) {
1114  overlap = gti_1st.tstop() - gti_2nd.tstart();
1115  }
1116  else if (gti_1st.tstop() > gti_2nd.tstop()) {
1117  overlap = gti_2nd.tstop() - gti_2nd.tstart();
1118  }
1119  }
1120 
1121  // Return overlap
1122  return overlap;
1123 }
1124 
1125 
1126 /***********************************************************************//**
1127  * @brief Get start time
1128  *
1129  * @return Start time
1130  ***************************************************************************/
1132 {
1133  // Initialise start time with large unreasonable Julian date
1134  GTime tstart("JD 999999999");
1135 
1136  // If tmin parameter is valid then set the start time
1137  if ((*this)["tmin"].is_valid()) {
1138 
1139  // Query tmin parameter
1140  tstart = (*this)["tmin"].time();
1141 
1142  // Log action
1143  log_string(NORMAL, "Setting start time \"tmin\" parameter");
1144 
1145  } // endif: tmin parameter was valid
1146 
1147  // ... otherwise set start time from observations
1148  else {
1149 
1150  // Loop over all observations
1151  for (int k = 0; k < m_obs.size(); ++k) {
1152 
1153  // Get CTA observation
1154  GCTAObservation* obs = dynamic_cast<GCTAObservation*>(m_obs[k]);
1155 
1156  // Continue only if observation is valid
1157  if (obs != NULL) {
1158 
1159  // Update start time if the start time of the observation is
1160  // smaller than the current start time
1161  if (obs->gti().tstart() < tstart) {
1162  tstart = obs->gti().tstart();
1163  }
1164 
1165  } // endif: observation was valid
1166 
1167  } // endfor: looped over observations
1168 
1169  // Log action
1170  log_string(NORMAL, "Setting start time from observations");
1171 
1172  } // endelse: no valid time specified
1173 
1174  // Return start time
1175  return tstart;
1176 }
1177 
1178 
1179 /***********************************************************************//**
1180  * @brief Get stop time
1181  *
1182  * @return Stop time
1183  ***************************************************************************/
1185 {
1186  // Initialise stop time with small unreasonable Julian date
1187  GTime tstop("JD 0");
1188 
1189  // If tmax parameter is valid then set the stop time
1190  if ((*this)["tmax"].is_valid()) {
1191 
1192  // Query tmax parameter
1193  tstop = (*this)["tmax"].time();
1194 
1195  // Log action
1196  log_string(NORMAL, "Setting stop time \"tmax\" parameter");
1197 
1198  } // endif: tmax parameter was valid
1199 
1200  // ... otherwise set stop time from observations
1201  else {
1202 
1203  // Loop over all observations
1204  for (int k = 0; k < m_obs.size(); ++k) {
1205 
1206  // Get CTA observation
1207  GCTAObservation* obs = dynamic_cast<GCTAObservation*>(m_obs[k]);
1208 
1209  // Continue only if observation is valid
1210  if (obs != NULL) {
1211 
1212  // Update stop time if the stop time of the observation is
1213  // larger than the current stop time
1214  if (obs->gti().tstop() > tstop) {
1215  tstop = obs->gti().tstop();
1216  }
1217 
1218  } // endif: observation was valid
1219 
1220  } // endfor: looped over observations
1221 
1222  // Log action
1223  log_string(NORMAL, "Setting stop time from observations");
1224 
1225  } // endelse: no valid time specified
1226 
1227  // Return stop time
1228  return tstop;
1229 }
1230 
1231 
1232 /***********************************************************************//**
1233  * @brief Write source histograms in FITS format
1234  *
1235  * @param[in,out] fits FITS file
1236  *
1237  * This method creates two FITS binary table extensions and appends them to
1238  * the FITS file.
1239  *
1240  * The first extension is named "SOURCE SIGNIFICANCE" and contains the
1241  * significance of the variability as function of time bin for the pixel
1242  * with the maximum significance and for each source.
1243  *
1244  * The second extension is named "SOURCE POSITION" and contains the Right
1245  * Ascension and Declination for the maximum significance pixel and for each
1246  * source.
1247  ***************************************************************************/
1249 {
1250  // Setup data storage for time and source significances
1251  double tinterval = (*this)["tinterval"].real();
1252  int nbins = (m_tstop-m_tstart) / tinterval + 0.5;
1253  int nsrc = m_pixsigsrc.shape()[0];
1254  GNdarray time_info(2, nbins);
1255  GNdarray max_pixel_info(nbins);
1256  GNdarray src_info(nsrc, nbins);
1257 
1258  // Loop over time bins
1259  for (int i = 0; i < nbins; ++i) {
1260 
1261  // Set time bin mid point
1262  GTime tstart_bin(m_tstart + i * tinterval);
1263  GTime tstop_bin(m_tstart + (i+1) * tinterval);
1264  GTime midpoint((tstop_bin.secs()+tstart_bin.secs())*0.5);
1265 
1266  // Store the time of this bin
1267  time_info(0,i) = tstart_bin.mjd();
1268  time_info(1,i) = tstop_bin.mjd();
1269 
1270  // Get the index associated with this time
1271  int index = time2inx(midpoint);
1272 
1273  // Loop over every source
1274  for (int k = 0; k < nsrc; ++k) {
1275  src_info(k,i) = (index >= 0) ? m_pixsigsrc(k,index) : 0.0;
1276  }
1277 
1278  // Fill maximum pixel information
1279  max_pixel_info(i) = (index >= 0) ? m_pixsigmax(index) : 0.0;
1280 
1281  } // endfor: looped over time bins
1282 
1283  // Create binary tables
1284  GFitsBinTable table_signif(nbins);
1285  GFitsBinTable table_pos(nsrc+1);
1286  table_signif.extname("SOURCE SIGNIFICANCE");
1287  table_pos.extname("SOURCE POSITION");
1288 
1289  // Create columns for time bins
1290  GFitsTableDoubleCol time_start("TSTART", nbins);
1291  GFitsTableDoubleCol time_stop("TSTOP", nbins);
1292  GFitsTableDoubleCol time_sigma("MAXSIGPIXEL", nbins);
1293  time_start.unit("MJD");
1294  time_stop.unit("MJD");
1295  time_sigma.unit("sigma");
1296 
1297  // Create columns for source position
1298  GFitsTableStringCol pos_name("SOURCE", nsrc+1, 20);
1299  GFitsTableDoubleCol pos_ra("RA", nsrc+1);
1300  GFitsTableDoubleCol pos_dec("DEC", nsrc+1);
1301  pos_ra.unit("deg");
1302  pos_dec.unit("deg");
1303 
1304  // Fill time bin columns
1305  for (int i = 0; i < nbins; ++i) {
1306  time_start(i) = time_info(0,i);
1307  time_stop(i) = time_info(1,i);
1308  time_sigma(i) = max_pixel_info(i);
1309  }
1310 
1311  // Fill source columns
1312  pos_name(0) = "MAXSIGPIXEL";
1313  pos_ra(0) = m_max_sig_dir.ra_deg();
1314  pos_dec(0) = m_max_sig_dir.dec_deg();
1315 
1316  // Append columns to tables
1317  table_signif.append(time_start);
1318  table_signif.append(time_stop);
1319  table_signif.append(time_sigma);
1320 
1321  // Append the distribution for each source
1322  for (int k = 0; k < nsrc; ++k) {
1323 
1324  // Initialise source name and direction
1325  std::string src_name("SOURCE");
1326  GSkyDir src_dir;
1327 
1328  // Get the name from the input model file
1329  if (m_inmodel.size() > k) {
1330 
1331  // Extract the model
1332  GModelSky* model = dynamic_cast<GModelSky*>(m_inmodel[k]);
1333 
1334  // If model is a sky model then extract the model centre. Note
1335  // that this only works for a model that results a GSkyRegionCircle
1336  // object as region.
1337  if (model != NULL) {
1338 
1339  // Get the source position
1340  const GModelSpatial* spatial = model->spatial();
1341  const GSkyRegion* region = spatial->region();
1342  const GSkyRegionCircle* circle = dynamic_cast<const GSkyRegionCircle*>(region);
1343 
1344  // If region circle is valid then extract source name and
1345  // sky direction
1346  if (circle != NULL) {
1347  src_name = m_inmodel[k]->name();
1348  src_dir = circle->centre();
1349  }
1350 
1351  } // endif: model was a sky model
1352 
1353  } // endif: there were enough models in the input model
1354 
1355  // ... otherwise set the position from the xsrc and ysrc parameters
1356  else {
1357  if ((*this)["coordsys"].string() == "CEL") {
1358  src_dir.radec_deg((*this)["xsrc"].real(), (*this)["ysrc"].real());
1359  }
1360  else {
1361  src_dir.lb_deg((*this)["xsrc"].real(), (*this)["ysrc"].real());
1362  }
1363  }
1364 
1365  // Create new time column for source
1366  GFitsTableDoubleCol column(src_name, nbins);
1367  column.unit("sigma");
1368 
1369  // Fill column
1370  for (int i = 0; i < nbins; ++i) {
1371  column(i) = src_info(k, i);
1372  }
1373 
1374  // Append column to time table
1375  table_signif.append(column);
1376 
1377  // Store source infirmation
1378  pos_name(k+1) = src_name;
1379  pos_ra(k+1) = src_dir.ra_deg();
1380  pos_dec(k+1) = src_dir.dec_deg();
1381 
1382  } // endfor: looped over sources
1383 
1384  // Append columns to tables
1385  table_pos.append(pos_name);
1386  table_pos.append(pos_ra);
1387  table_pos.append(pos_dec);
1388 
1389  // Append tables to FITS file
1390  fits.append(table_signif);
1391  fits.append(table_pos);
1392 
1393  // Return
1394  return;
1395 }
1396 
1397 
1398 /***********************************************************************//**
1399  * @brief Get the map index associated with a given time
1400  *
1401  * @param[in] time Time
1402  * @return Index of map in counts cube
1403  ***************************************************************************/
1404 int ctfindvar::time2inx(const GTime& time) const
1405 {
1406  // Set default return value
1407  int map_index = -1;
1408 
1409  // Loop over all GTIs
1410  for (int i = 0; i < m_gti.size(); ++i) {
1411 
1412  // If interval contains the time then break
1413  if (m_gti[i].contains(time)) {
1414  map_index = i;
1415  break;
1416  }
1417 
1418  }
1419 
1420  // Return map index
1421  return map_index;
1422 }
Time variability search tool.
Definition: ctfindvar.hpp:47
std::vector< double > get_alphas(const int &ipix) const
Get alpha vector.
Definition: ctfindvar.cpp:1013
GTime m_tstart
Start time for variability study.
Definition: ctfindvar.hpp:94
void setup_observations(GObservations &obs, const bool &response=true, const bool &list=true, const bool &cube=true)
Setup observation container.
Definition: ctool.cpp:431
GNdarray m_pixsigsrc
Store distributions of the source significances.
Definition: ctfindvar.hpp:92
int time2inx(const GTime &time) const
Get the map index associated with a given time.
Definition: ctfindvar.cpp:1404
#define CTFINDVAR_NAME
Definition: ctfindvar.hpp:37
ctfindvar(void)
Void constructor.
Definition: ctfindvar.cpp:67
#define G_FILL_ALPHA_VECTOR
Definition: ctfindvar.cpp:44
void save(void)
Save peak significance map and significance distributions.
Definition: ctfindvar.cpp:255
GTime get_tstart(void)
Get start time.
Definition: ctfindvar.cpp:1131
GSkyMap create_map(const GObservations &obs)
Create a skymap from user parameters.
Definition: ctool.cpp:937
void init_members(void)
Initialise class members.
Definition: ctool.cpp:321
const GObservations & obs(void) const
Return observation container.
void process(void)
Process time variability search tool.
Definition: ctfindvar.cpp:226
void analyse_cube(void)
Analyse all pixels of counts cube.
Definition: ctfindvar.cpp:718
void clear(void)
Clear time variability search tool.
Definition: ctfindvar.cpp:200
ctfindvar & operator=(const ctfindvar &app)
Assignment operator.
Definition: ctfindvar.cpp:165
GNdarray m_pixsigmax
Store distribution for pixel with max significance.
Definition: ctfindvar.hpp:93
GEnergy m_emin
Minimum energy for events.
Definition: ctfindvar.hpp:96
GModelSky sky_model(const GSkyDir &dir) const
Return sky model for a given sky direction.
Definition: ctfindvar.cpp:875
void copy_members(const ctfindvar &app)
Copy class members.
Definition: ctfindvar.cpp:370
GTime get_tstop(void)
Get stop time.
Definition: ctfindvar.cpp:1184
GSkyMap m_peaksigmap
Skymap holding the maximum significance.
Definition: ctfindvar.hpp:91
void log_parameters(const GChatter &chatter)
Log application parameters.
Definition: ctool.cpp:1208
void init_gtis(void)
Initialize Good Time Intervals.
Definition: ctfindvar.cpp:477
Base class for observation tools.
GModels m_model_above_thr
Model storing position with significance above thr.
Definition: ctfindvar.hpp:98
void free_members(void)
Delete class members.
virtual ~ctfindvar(void)
Destructor.
Definition: ctfindvar.cpp:141
std::string get_obs_header(const GObservation *obs) const
Return observation header string.
Definition: ctool.cpp:2090
void init_members(void)
Initialise class members.
Definition: ctfindvar.cpp:343
double m_sig_threshold
Minimum significance required to set source as variable.
Definition: ctfindvar.hpp:90
void create_cube(void)
Create counts cube.
Definition: ctfindvar.cpp:556
void free_members(void)
Delete class members.
Definition: ctool.cpp:357
void free_members(void)
Delete class members.
Definition: ctfindvar.cpp:395
void init_members(void)
Initialise class members.
std::vector< int > get_pixels(void)
Return pixel vector.
Definition: ctfindvar.cpp:799
#define G_FILL_CUBE
Definition: ctfindvar.cpp:43
std::vector< GGti > m_gti
List of time intervals.
Definition: ctfindvar.hpp:86
ctobservation & operator=(const ctobservation &app)
Assignment operator.
GTime m_tstop
Stop time for variability study.
Definition: ctfindvar.hpp:95
GSkyMap m_counts
Counts for each time interval.
Definition: ctfindvar.hpp:85
const bool & read_ahead(void) const
Signal whether parameters should be read ahead.
Definition: ctool.hpp:177
GNdarray get_variability_sig(const int &ipix)
Get the significance of variability for a given skymap pixel.
Definition: ctfindvar.cpp:917
double m_minoff
Minimum counts for use in significance calculation.
Definition: ctfindvar.hpp:89
#define G_INIT_GTIS
Definition: ctfindvar.cpp:47
double gti_overlap(const GGti &gti1, const GGti &gti2) const
Returns number of seconds that two GTIs overlap.
Definition: ctfindvar.cpp:1102
void log_observations(const GChatter &chatter, const GObservations &obs, const std::string &what="Observation")
Log observation container.
Definition: ctool.cpp:1251
GObservations m_obs
Observation container.
Time variability search tool definition.
void get_parameters(void)
Get application parameters.
Definition: ctfindvar.cpp:405
void fill_cube(GCTAObservation *obs)
Fill the cube from the events in an observation.
Definition: ctfindvar.cpp:603
void write_source_histograms(GFits &fits)
Write source histograms in FITS format.
Definition: ctfindvar.cpp:1248
GSkyDir m_max_sig_dir
Sky direction associated with maximum significance.
Definition: ctfindvar.hpp:88
GModels m_inmodel
List of models for source positions.
Definition: ctfindvar.hpp:87
GEnergy m_emax
Maximum energy for events.
Definition: ctfindvar.hpp:97