ctools 2.1.0.dev
Loading...
Searching...
No Matches
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
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 ***************************************************************************/
84ctfindvar::ctfindvar(const GObservations& obs) :
85 ctobservation(CTFINDVAR_NAME, VERSION, obs)
86{
87 // Initialise 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 ***************************************************************************/
104ctfindvar::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
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 ***************************************************************************/
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;
376 m_max_sig_dir = app.m_max_sig_dir;
377 m_minoff = app.m_minoff;
378 m_sig_threshold = app.m_sig_threshold;
379 m_peaksigmap = app.m_peaksigmap;
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;
385 m_model_above_thr = app.m_model_above_thr;
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
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
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 ***************************************************************************/
603void 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 ***************************************************************************/
799std::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 ***************************************************************************/
875GModelSky 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 ***************************************************************************/
917GNdarray 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 ***************************************************************************/
1013std::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 ***************************************************************************/
1102double 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 ***************************************************************************/
1404int 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
double gti_overlap(const GGti &gti1, const GGti &gti2) const
Returns number of seconds that two GTIs overlap.
int time2inx(const GTime &time) const
Get the map index associated with a given time.
GNdarray get_variability_sig(const int &ipix)
Get the significance of variability for a given skymap pixel.
std::vector< int > get_pixels(void)
Return pixel vector.
void process(void)
Process time variability search tool.
void clear(void)
Clear time variability search tool.
std::vector< GGti > m_gti
List of time intervals.
Definition ctfindvar.hpp:86
double m_minoff
Minimum counts for use in significance calculation.
Definition ctfindvar.hpp:89
double m_sig_threshold
Minimum significance required to set source as variable.
Definition ctfindvar.hpp:90
GNdarray m_pixsigmax
Store distribution for pixel with max significance.
Definition ctfindvar.hpp:93
void init_gtis(void)
Initialize Good Time Intervals.
GSkyMap m_peaksigmap
Skymap holding the maximum significance.
Definition ctfindvar.hpp:91
void free_members(void)
Delete class members.
void copy_members(const ctfindvar &app)
Copy class members.
void get_parameters(void)
Get application parameters.
virtual ~ctfindvar(void)
Destructor.
ctfindvar & operator=(const ctfindvar &app)
Assignment operator.
void analyse_cube(void)
Analyse all pixels of counts cube.
GTime get_tstop(void)
Get stop time.
GTime m_tstart
Start time for variability study.
Definition ctfindvar.hpp:94
std::vector< double > get_alphas(const int &ipix) const
Get alpha vector.
GSkyDir m_max_sig_dir
Sky direction associated with maximum significance.
Definition ctfindvar.hpp:88
GModels m_model_above_thr
Model storing position with significance above thr.
Definition ctfindvar.hpp:98
void save(void)
Save peak significance map and significance distributions.
GEnergy m_emin
Minimum energy for events.
Definition ctfindvar.hpp:96
GEnergy m_emax
Maximum energy for events.
Definition ctfindvar.hpp:97
GModels m_inmodel
List of models for source positions.
Definition ctfindvar.hpp:87
ctfindvar(void)
Void constructor.
Definition ctfindvar.cpp:67
GNdarray m_pixsigsrc
Store distributions of the source significances.
Definition ctfindvar.hpp:92
GTime get_tstart(void)
Get start time.
GSkyMap m_counts
Counts for each time interval.
Definition ctfindvar.hpp:85
void write_source_histograms(GFits &fits)
Write source histograms in FITS format.
void create_cube(void)
Create counts cube.
GTime m_tstop
Stop time for variability study.
Definition ctfindvar.hpp:95
void fill_cube(GCTAObservation *obs)
Fill the cube from the events in an observation.
GModelSky sky_model(const GSkyDir &dir) const
Return sky model for a given sky direction.
void init_members(void)
Initialise class members.
Base class for observation tools.
void free_members(void)
Delete class members.
ctobservation & operator=(const ctobservation &app)
Assignment operator.
GObservations m_obs
Observation container.
const GObservations & obs(void) const
Return observation container.
void init_members(void)
Initialise class members.
void free_members(void)
Delete class members.
Definition ctool.cpp:357
GSkyMap create_map(const GObservations &obs)
Create a skymap from user parameters.
Definition ctool.cpp:937
void log_observations(const GChatter &chatter, const GObservations &obs, const std::string &what="Observation")
Log observation container.
Definition ctool.cpp:1251
const bool & read_ahead(void) const
Signal whether parameters should be read ahead.
Definition ctool.hpp:177
void log_parameters(const GChatter &chatter)
Log application parameters.
Definition ctool.cpp:1208
void init_members(void)
Initialise class members.
Definition ctool.cpp:321
void setup_observations(GObservations &obs, const bool &response=true, const bool &list=true, const bool &cube=true)
Setup observation container.
Definition ctool.cpp:431
std::string get_obs_header(const GObservation *obs) const
Return observation header string.
Definition ctool.cpp:2090
#define G_FILL_CUBE
Definition ctbin.cpp:43
#define G_FILL_ALPHA_VECTOR
Definition ctfindvar.cpp:44
#define G_INIT_GTIS
Definition ctfindvar.cpp:47
Time variability search tool definition.
#define CTFINDVAR_NAME
Definition ctfindvar.hpp:37