Commit 9c0e2e0a44b1a54918de62becc21e337b4dee59f

Authored by Jürgen Knödlseder
2 parents b1bd4caa 2e1aefc0

Merge branch '1996-ctselect-add-phase-selection' into integration

# Conflicts:
#	src/ctselect/ctselect.par
ChangeLog
1   -2017-04-11
  1 +2017-04-13
2 2  
3 3 * Version 1.3.0 released
4 4 ========================
5 5  
  6 + Add "phase" parameter to ctselect (#1996)
6 7 Add ctphase tool (#1982)
7 8 Add ctprob tool (#1991)
8 9 ctlike stores covariance matrix in either CSV or FITS format (#1863)
... ...
1 1 New Features and Important Changes in ctools 1.3.0.dev1
2 2  
3   -11 April 2017
  3 +13 April 2017
4 4  
5 5  
6 6 Introduction
... ... @@ -47,7 +47,12 @@ Now, for map cubes outside the RoI the flux is set to zero.
47 47  
48 48 ctselect - CTA event selection
49 49 ------------------------------
50   -None
  50 +A "phase" parameter has been added to ctselect to allow event selection
  51 +according to their phase information. The phase parameter accepts a
  52 +string that provides a comma separated list of phase intervals in the
  53 +format "0.1:0.4,0.6:0.8". Wrap around is supported (e.g. "0.8:0.1"
  54 +selects events with phases in the intervals [0.8,1.0] and [0.0,0.1]
  55 +(#1996).
51 56  
52 57  
53 58 ctbin - CTA event binning
... ...
cscripts/Makefile.am
... ... @@ -127,6 +127,7 @@ testscripts = $(srcdir)/tests/__init__.py \
127 127 $(top_srcdir)/test/test_csobsdef.py \
128 128 $(top_srcdir)/test/test_csobsinfo.py \
129 129 $(top_srcdir)/test/test_csobsselect.py \
  130 + $(top_srcdir)/test/test_csphasecrv.py \
130 131 $(top_srcdir)/test/test_cspull.py \
131 132 $(top_srcdir)/test/test_csresmap.py \
132 133 $(top_srcdir)/test/test_csroot2caldb.py \
... ...
doc/source/users/reference_manual/ctselect.rst
... ... @@ -52,39 +52,48 @@ General parameters
52 52  
53 53 ``ra [real]``
54 54 Right Ascension of acceptance cone (or ROI) centre (J2000, in degrees).
55   - If INDEF, NONE, UNDEF or UNDEFINED is passed as value, not ROI
  55 + If INDEF, NONE, UNDEF or UNDEFINED is passed as value, no ROI
56 56 selection will be performed.
57 57  
58 58 ``dec [real]``
59 59 Declination of acceptance cone (or ROI) centre (J2000, in degrees).
60   - If INDEF, NONE, UNDEF or UNDEFINED is passed as value, not ROI
  60 + If INDEF, NONE, UNDEF or UNDEFINED is passed as value, no ROI
61 61 selection will be performed.
62 62  
63 63 ``rad [real]``
64 64 Radius of acceptance cone (or ROI) centre (in degrees).
65   - If INDEF, NONE, UNDEF or UNDEFINED is passed as value, not ROI
  65 + If INDEF, NONE, UNDEF or UNDEFINED is passed as value, no ROI
66 66 selection will be performed.
67 67  
68 68 ``tmin [real]``
69 69 CTA mission elapsed start time defining the earliest event (in seconds).
70   - If INDEF, NONE, UNDEF or UNDEFINED is passed as value, not time
  70 + If INDEF, NONE, UNDEF or UNDEFINED is passed as value, no time
71 71 selection will be performed.
72 72  
73 73 ``tmax [real]``
74 74 CTA mission elapsed stop time defining the latest event (in seconds).
75   - If INDEF, NONE, UNDEF or UNDEFINED is passed as value, not time
  75 + If INDEF, NONE, UNDEF or UNDEFINED is passed as value, no time
76 76 selection will be performed.
77 77  
78 78 ``emin [real]``
79 79 Lower energy limit of events (in TeV).
80   - If INDEF, NONE, UNDEF or UNDEFINED is passed as value, not energy
  80 + If INDEF, NONE, UNDEF or UNDEFINED is passed as value, no energy
81 81 selection will be performed.
82 82  
83 83 ``emax [real]``
84 84 Upper energy limit of events (in TeV).
85   - If INDEF, NONE, UNDEF or UNDEFINED is passed as value, not energy
  85 + If INDEF, NONE, UNDEF or UNDEFINED is passed as value, no energy
86 86 selection will be performed.
87 87  
  88 +``(phase = NONE) [string]``
  89 + String to apply a phase selection. The string must contain the boundaries
  90 + of the phase interval to be selected separated be a colon. More than one
  91 + interval can be specified at the same time. In this case intervals must be
  92 + separated by a comma. Examples of valid strings are: phase = "0.3:0.6",
  93 + phase = "0.3:0.6,0.8:0.9" or phase = "0.8:0.2". In the last case, events
  94 + with phases in the intervals [0.8,1.0] and [0.0,0.2] are selected. If NONE
  95 + is passed as value, no phase selection will be performed.
  96 +
88 97 ``(expr = "") [string]``
89 98 Additional event selection expression (cfitsio syntax).
90 99  
... ...
src/ctselect/ctselect.cpp
... ... @@ -34,6 +34,7 @@
34 34  
35 35 /* __ Method name definitions ____________________________________________ */
36 36 #define G_RUN "ctselect::run()"
  37 +#define G_GET_PARAMETERS "ctselect::get_parameters()"
37 38 #define G_SELECT_EVENTS "ctselect::select_events(GCTAObservation*, "\
38 39 "std::string&, std::string&, std::string&)"
39 40 #define G_SET_EBOUNDS "ctselect::set_ebounds(GCTAObservation*, GEbounds&)"
... ... @@ -481,9 +482,12 @@ void ctselect::init_members(void)
481 482 m_gtiname.clear();
482 483 m_timemin.clear();
483 484 m_timemax.clear();
484   - m_select_energy = true;
485   - m_select_roi = true;
486   - m_select_time = true;
  485 + m_phasemin.clear();
  486 + m_phasemax.clear();
  487 + m_select_energy = false;
  488 + m_select_roi = false;
  489 + m_select_time = false;
  490 + m_select_phase = false;
487 491  
488 492 // Return
489 493 return;
... ... @@ -516,9 +520,12 @@ void ctselect::copy_members(const ctselect& app)
516 520 m_gtiname = app.m_gtiname;
517 521 m_timemin = app.m_timemin;
518 522 m_timemax = app.m_timemax;
  523 + m_phasemin = app.m_phasemin;
  524 + m_phasemax = app.m_phasemax;
519 525 m_select_energy = app.m_select_energy;
520 526 m_select_roi = app.m_select_roi;
521 527 m_select_time = app.m_select_time;
  528 + m_select_phase = app.m_select_phase;
522 529  
523 530 // Return
524 531 return;
... ... @@ -548,9 +555,10 @@ void ctselect::free_members(void)
548 555 void ctselect::get_parameters(void)
549 556 {
550 557 // Initialise selection flags
551   - m_select_energy = true;
552   - m_select_roi = true;
553   - m_select_time = true;
  558 + m_select_energy = false;
  559 + m_select_roi = false;
  560 + m_select_time = false;
  561 + m_select_phase = false;
554 562  
555 563 // Setup observations from "inobs" parameter. Do not request response
556 564 // information and do not accept counts cubes.
... ... @@ -563,7 +571,7 @@ void ctselect::get_parameters(void)
563 571 m_roi = get_roi();
564 572 m_select_roi = m_roi.is_valid();
565 573  
566   - // Check for sanity of time selection parameters
  574 + // Get time selection parameters
567 575 if ((*this)["tmin"].is_valid() && (*this)["tmax"].is_valid()) {
568 576  
569 577 // Get User parameters
... ... @@ -578,19 +586,109 @@ void ctselect::get_parameters(void)
578 586 m_select_time = true;
579 587 }
580 588 }
581   - else {
582   - m_select_time = false;
583   - }
584 589  
585   - // Check for sanity of energy selection parameters
  590 + // Get energy selection parameters
586 591 if ((*this)["emin"].is_valid() && (*this)["emax"].is_valid()) {
587 592 m_emin = (*this)["emin"].real();
588 593 m_emax = (*this)["emax"].real();
589 594 m_select_energy = true;
590 595 }
591   - else {
592   - m_select_energy = false;
593   - }
  596 +
  597 + // Get phase selection string
  598 + std::string phase_expr = gammalib::strip_whitespace(gammalib::tolower(
  599 + (*this)["phase"].string()));
  600 +
  601 + // Get phase selection parameters
  602 + if ((phase_expr.length() > 0) && (phase_expr != "none")) {
  603 +
  604 + // Get phase selection string
  605 + std::string phase_expr = (*this)["phase"].string();
  606 +
  607 + // Extract phase bins from selection string
  608 + std::vector<std::string> phase_splits = gammalib::split(phase_expr, ",");
  609 + for (int i = 0; i < phase_splits.size(); ++i) {
  610 +
  611 + // Get minimum and maximum of phase bin
  612 + std::vector<std::string> phase_toks =
  613 + gammalib::split(phase_splits[i], ":");
  614 +
  615 + // Throw an exception if there are not two elements
  616 + if (phase_toks.size() != 2) {
  617 + std::string msg = "Invalid phase selection string \""+
  618 + phase_splits[i]+"\" encountered. The phase "
  619 + "selection string requires a minimum and "
  620 + "maximum value separated by a colon, e.g. "
  621 + "\"0.3:0.5\".";
  622 + throw GException::invalid_value(G_GET_PARAMETERS, msg);
  623 + }
  624 +
  625 + // Throw an exception if one of the elements is empty
  626 + if (gammalib::strip_whitespace(phase_toks[0]).length() == 0) {
  627 + std::string msg = "Invalid phase selection string \""+
  628 + phase_splits[i]+"\" encountered. The phase "
  629 + "selection string requires a minimum and "
  630 + "maximum value separated by a colon, e.g. "
  631 + "\"0.3:0.5\".";
  632 + throw GException::invalid_value(G_GET_PARAMETERS, msg);
  633 + }
  634 + if (gammalib::strip_whitespace(phase_toks[1]).length() == 0) {
  635 + std::string msg = "Invalid phase selection string \""+
  636 + phase_splits[i]+"\" encountered. The phase "
  637 + "selection string requires a minimum and "
  638 + "maximum value separated by a colon, e.g. "
  639 + "\"0.3:0.5\".";
  640 + throw GException::invalid_value(G_GET_PARAMETERS, msg);
  641 + }
  642 +
  643 + // Get phase bin boundaries
  644 + double phase_min = gammalib::todouble(phase_toks[0]);
  645 + double phase_max = gammalib::todouble(phase_toks[1]);
  646 +
  647 + // Throw an exception if phase minimum or maximum is outside the
  648 + // range [0,1]
  649 + if (phase_min < 0.0 || phase_min > 1.0) {
  650 + std::string msg = "Phase minimum "+gammalib::str(phase_min)+
  651 + " outside the valid range [0,1]. Please "
  652 + "specify phase interval boundaries comprised "
  653 + "within 0 and 1.";
  654 + throw GException::invalid_value(G_GET_PARAMETERS, msg);
  655 + }
  656 + if (phase_max < 0.0 || phase_max > 1.0) {
  657 + std::string msg = "Phase maximum "+gammalib::str(phase_max)+
  658 + " outside the valid range [0,1]. Please "
  659 + "specify phase interval boundaries comprised "
  660 + "within 0 and 1.";
  661 + throw GException::invalid_value(G_GET_PARAMETERS, msg);
  662 + }
  663 +
  664 + // If phase minimum is smaller than phase maximum then append a
  665 + // single interval
  666 + if (phase_min < phase_max) {
  667 + m_phasemin.push_back(phase_min);
  668 + m_phasemax.push_back(phase_max);
  669 + }
  670 +
  671 + // Otherwise if the phase minimum is larger than the maximum then
  672 + // consider this a wrap around interval and append one interval
  673 + // from phase minimum to 1 and another interval from 0 to phase
  674 + // maximum
  675 + else if (phase_min > phase_max) {
  676 + if (phase_min < 1.0) {
  677 + m_phasemin.push_back(phase_min);
  678 + m_phasemax.push_back(1.0);
  679 + }
  680 + if (phase_max > 0.0) {
  681 + m_phasemin.push_back(0.0);
  682 + m_phasemax.push_back(phase_max);
  683 + }
  684 + }
  685 +
  686 + } // endfor: looped over phase selection string
  687 +
  688 + // Signal that a phase selection information is available
  689 + m_select_phase = (m_phasemin.size() > 0);
  690 +
  691 + } // endif: phase selection parameters were valid
594 692  
595 693 // Get other User parameters
596 694 m_expr = (*this)["expr"].string();
... ... @@ -722,6 +820,54 @@ void ctselect::select_events(GCTAObservation* obs,
722 820  
723 821 } // endif: made time selection
724 822  
  823 + // Make phase selection
  824 + if (m_select_phase) {
  825 +
  826 + // Check if event_list has phase
  827 + if (list->has_phase()) {
  828 +
  829 + // Loop over phase selections
  830 + for (int i = 0; i < m_phasemin.size(); ++i) {
  831 +
  832 + // Check if phasemax is larger than phasemin
  833 + if (i == 0) {
  834 + selection += add + "( ";
  835 + }
  836 + else {
  837 + selection += add;
  838 + }
  839 +
  840 + // Format phase with sufficient accuracy and add to selection string
  841 + char cmin[80];
  842 + char cmax[80];
  843 + sprintf(cmin, "%.8f", m_phasemin[i]);
  844 + sprintf(cmax, "%.8f", m_phasemax[i]);
  845 + selection += "(PHASE >= "+std::string(cmin)+" && PHASE <= "+
  846 + std::string(cmax)+")";
  847 + add = " || ";
  848 + log_value(NORMAL, "Phase range "+gammalib::str(i+1),
  849 + gammalib::str(m_phasemin[i])+" - "+
  850 + gammalib::str(m_phasemax[i]));
  851 +
  852 + } // end for loop
  853 +
  854 + // If phase intervals have been appended then close the selection
  855 + // string and take provision for appending another selection
  856 + if (m_phasemin.size() > 0) {
  857 + selection += " )";
  858 + add = " && ";
  859 + }
  860 +
  861 + } // endif: there was PHASE information in the event list
  862 +
  863 + // Otherwise signal that no PHASE information is available
  864 + else {
  865 + log_value(NORMAL, "Phase range", "Event list has no \"PHASE\" "
  866 + "column. Phase selection skipped.");
  867 + }
  868 +
  869 + } // endif: made phase selection
  870 +
725 871 // Make energy selection
726 872 if (m_select_energy) {
727 873  
... ...
src/ctselect/ctselect.hpp
... ... @@ -99,10 +99,12 @@ protected:
99 99 std::vector<std::string> m_gtiname; //!< GTI extension names
100 100 GTime m_timemin; //!< Earliest time
101 101 GTime m_timemax; //!< Latest time
  102 + std::vector<double> m_phasemin; //!< Phase bin minima
  103 + std::vector<double> m_phasemax; //!< Phase bin maxima
102 104 bool m_select_energy; //!< Perform energy selection
103 105 bool m_select_roi; //!< Perform RoI selection
104 106 bool m_select_time; //!< Perform time selection
  107 + bool m_select_phase; //!< Perform phase selection
105 108 };
106 109  
107   -
108 110 #endif /* CTSELECT_HPP */
... ...
src/ctselect/ctselect.par
... ... @@ -31,6 +31,7 @@ tmin, r, a, 0.0,,, &quot;Start time (CTA MET in seconds)&quot;
31 31 tmax, r, a, 0.0,,, "End time (CTA MET in seconds)"
32 32 emin, r, a, 0.1,,, "Lower energy limit (TeV)"
33 33 emax, r, a, 100.0,,, "Upper energy limit (TeV)"
  34 +phase, s, h, NONE,,, "Phase expression in the format phasemin0:phasemax0,phasemin1:phasemax1,..."
34 35 expr, s, h, ,,, "Additional selection expression"
35 36 usethres,s, h, NONE,NONE|DEFAULT|USER,, "Energy threshold type"
36 37  
... ...
test/test_ctselect.py
... ... @@ -2,7 +2,7 @@
2 2 # ==========================================================================
3 3 # This scripts performs unit tests for the ctselect tool.
4 4 #
5   -# Copyright (C) 2014-2016 Juergen Knoedlseder
  5 +# Copyright (C) 2014-2017 Juergen Knoedlseder
6 6 #
7 7 # This program is free software: you can redistribute it and/or modify
8 8 # it under the terms of the GNU General Public License as published by
... ... @@ -40,6 +40,7 @@ class Test(test):
40 40 test.__init__(self)
41 41  
42 42 # Set test data
  43 + self._phased_events = self._datadir + '/phased_events.fits'
43 44 self._invalid_events = self._datadir + '/invalid_event_list.fits'
44 45  
45 46 # Return
... ... @@ -355,7 +356,136 @@ class Test(test):
355 356 select.execute()
356 357  
357 358 # Check result file
358   - self._check_result_file('ctselect_py10.fits', nevents=0, dec=-22.01, rad=5.0)
  359 + self._check_result_file('ctselect_py10.fits', nevents=0, dec=-22.01,
  360 + rad=5.0)
  361 +
  362 + # Now test phase cut
  363 + select = ctools.ctselect()
  364 + select['inobs'] = self._phased_events
  365 + select['ra'] = 83.63
  366 + select['dec'] = 22.01
  367 + select['rad'] = 5.0
  368 + select['tmin'] = 'INDEF'
  369 + select['tmax'] = 'INDEF'
  370 + select['emin'] = 0.1
  371 + select['emax'] = 100.0
  372 + select['phase'] = '0.1:0.4,0.6:0.8'
  373 + select['outobs'] = 'ctselect_py11.fits'
  374 + select['logfile'] = 'ctselect_py11.log'
  375 + select['chatter'] = 2
  376 +
  377 + # Run ctselect tool
  378 + select.logFileOpen() # Make sure we get a log file
  379 + select.execute()
  380 +
  381 + # Check result file
  382 + self._check_result_file('ctselect_py11.fits', nevents=1909, rad=5.0)
  383 +
  384 + # Now test anoother phase cut
  385 + select = ctools.ctselect()
  386 + select['inobs'] = self._phased_events
  387 + select['ra'] = 83.63
  388 + select['dec'] = 22.01
  389 + select['rad'] = 5.0
  390 + select['tmin'] = 'INDEF'
  391 + select['tmax'] = 'INDEF'
  392 + select['emin'] = 0.1
  393 + select['emax'] = 100.0
  394 + select['phase'] = '0.8:0.3'
  395 + select['outobs'] = 'ctselect_py12.fits'
  396 + select['logfile'] = 'ctselect_py12.log'
  397 + select['chatter'] = 2
  398 +
  399 + # Run ctselect tool
  400 + select.logFileOpen() # Make sure we get a log file
  401 + select.execute()
  402 +
  403 + # Check result file
  404 + self._check_result_file('ctselect_py12.fits', nevents=1995, rad=5.0)
  405 +
  406 + # Test invalid minimum phase value
  407 + self.test_try('Test invalid minimum phase value')
  408 + try:
  409 + select['phase'] = '-0.1:0.3'
  410 + select['outobs'] = 'ctselect_py13.fits'
  411 + select['logfile'] = 'ctselect_py13.log'
  412 + select.logFileOpen()
  413 + select.execute()
  414 + self.test_try_failure('Exception not thrown for "-0.1:0.3"')
  415 + except ValueError:
  416 + self.test_try_success()
  417 +
  418 + # Test invalid minimum phase value
  419 + self.test_try('Test invalid minimum phase value')
  420 + try:
  421 + select['phase'] = '1.1:0.3'
  422 + select['outobs'] = 'ctselect_py14.fits'
  423 + select['logfile'] = 'ctselect_py14.log'
  424 + select.logFileOpen()
  425 + select.execute()
  426 + self.test_try_failure('Exception not thrown for "1.1:0.3"')
  427 + except ValueError:
  428 + self.test_try_success()
  429 +
  430 + # Test invalid maximum phase value
  431 + self.test_try('Test invalid maximum phase value')
  432 + try:
  433 + select['phase'] = '0.1:-0.3'
  434 + select['outobs'] = 'ctselect_py15.fits'
  435 + select['logfile'] = 'ctselect_py15.log'
  436 + select.logFileOpen()
  437 + select.execute()
  438 + self.test_try_failure('Exception not thrown for "0.1:-0.3"')
  439 + except ValueError:
  440 + self.test_try_success()
  441 +
  442 + # Test invalid maximum phase value
  443 + self.test_try('Test invalid maximum phase value')
  444 + try:
  445 + select['phase'] = '0.1:1.1'
  446 + select['outobs'] = 'ctselect_py16.fits'
  447 + select['logfile'] = 'ctselect_py16.log'
  448 + select.logFileOpen()
  449 + select.execute()
  450 + self.test_try_failure('Exception not thrown for "0.1:1.1"')
  451 + except ValueError:
  452 + self.test_try_success()
  453 +
  454 + # Test invalid phase string
  455 + self.test_try('Test invalid phase string')
  456 + try:
  457 + select['phase'] = '0.1:'
  458 + select['outobs'] = 'ctselect_py17.fits'
  459 + select['logfile'] = 'ctselect_py17.log'
  460 + select.logFileOpen()
  461 + select.execute()
  462 + self.test_try_failure('Exception not thrown for "0.1:"')
  463 + except ValueError:
  464 + self.test_try_success()
  465 +
  466 + # Test invalid phase string
  467 + self.test_try('Test invalid phase string')
  468 + try:
  469 + select['phase'] = ':1.0'
  470 + select['outobs'] = 'ctselect_py18.fits'
  471 + select['logfile'] = 'ctselect_py18.log'
  472 + select.logFileOpen()
  473 + select.execute()
  474 + self.test_try_failure('Exception not thrown for ":1.0"')
  475 + except ValueError:
  476 + self.test_try_success()
  477 +
  478 + # Test invalid phase string
  479 + self.test_try('Test invalid phase string')
  480 + try:
  481 + select['phase'] = '0.1-1.0'
  482 + select['outobs'] = 'ctselect_py19.fits'
  483 + select['logfile'] = 'ctselect_py19.log'
  484 + select.logFileOpen()
  485 + select.execute()
  486 + self.test_try_failure('Exception not thrown for "0.1-1.0"')
  487 + except ValueError:
  488 + self.test_try_success()
359 489  
360 490 # Return
361 491 return
... ...