Commit 6205af2a12232cf79fc6ef3b314ab4b4c0a0b384

Authored by Jürgen Knödlseder
1 parent e1129812

Add Sphinx tutorials test script (#3510)

The current script covers all 1DC tutorials.
.gitignore
... ... @@ -99,6 +99,7 @@ test/butterfly.txt
99 99 test/.coverage
100 100 test/test_python_ctools.sh
101 101 test/test_python_cscripts.sh
  102 +test/tutorials
102 103 examples/*.fits
103 104 examples/*.xml
104 105 examples/*.dat
... ...
ChangeLog
1   -2020-12-03
  1 +2020-02-03
2 2  
3 3 * Version 2.0.0 released
4 4 ========================
5 5  
  6 + Add Sphinx tutorials test script (#3510)
6 7 Add "refit_if_failed" parameter to ctlike
7 8 Add models() method to ctobssim (#3441)
8 9 Add ctlike results to XML file (#3431)
... ...
Makefile.am
1 1 #############################################################################
2 2 # Makefile for ctools package #
3 3 # ------------------------------------------------------------------------- #
4   -# Copyright (C) 2016-2017 Juergen Knoedlseder #
  4 +# Copyright (C) 2016-2021 Juergen Knoedlseder #
5 5 # ------------------------------------------------------------------------- #
6 6 # #
7 7 # This program is free software: you can redistribute it and/or modify #
... ... @@ -211,13 +211,23 @@ valgrind:
211 211 # Add science verification rule
212 212 science-verification:
213 213 @if type test/science_verification.py >/dev/null 2>&1; then \
214   - cd test; \
215   - ./science_verification.py; \
216   - cd ..; \
  214 + cd test; \
  215 + ./science_verification.py; \
  216 + cd ..; \
217 217 else \
218 218 echo "science_verification.py not found in test."; \
219 219 fi
220 220  
  221 +# Add tutorials verification rule
  222 +check-tutorials:
  223 + @if type test/tutorials.py >/dev/null 2>&1; then \
  224 + cd test; \
  225 + ./tutorials.py; \
  226 + cd ..; \
  227 + else \
  228 + echo "tutorials.py not found in test."; \
  229 + fi
  230 +
221 231 # Add Sphinx rule
222 232 sphinx:
223 233 @if type sphinx-build >/dev/null 2>&1; then \
... ...
1 1 New Features and Important Changes in ctools 2.0.0
2 2  
3   -3 December 2020
  3 +3 February 2021
4 4  
5 5  
6 6 Introduction
... ... @@ -16,7 +16,8 @@ None
16 16  
17 17 Documentation
18 18 -------------
19   -None
  19 +Add test/tutorials.py script and check-tutorial target to test the code in the
  20 +Sphinx tutorials (#3510).
20 21  
21 22  
22 23 Python interface
... ...
README.md
1 1 ctools information
2 2 ==================
3   -* Version: 2.0.0.dev (3 December 2020)
  3 +* Version: 2.0.0.dev (3 February 2021)
4 4 * GammaLib dependency: 2.0.0.dev
5 5  
6 6 [![Build Status](https://cta-jenkins.irap.omp.eu/buildStatus/icon?job=ctools-integrate-os)](https://cta-jenkins.irap.omp.eu/job/ctools-integrate-os/)
... ...
cscripts/csspec.py
... ... @@ -2,7 +2,7 @@
2 2 # ==========================================================================
3 3 # Generates a spectrum.
4 4 #
5   -# Copyright (C) 2014-2020 Michael Mayer
  5 +# Copyright (C) 2014-2021 Michael Mayer
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
... ... @@ -1071,7 +1071,7 @@ class csspec(ctools.csobservation):
1071 1071 energy_high = gammalib.GFitsTableDoubleCol('e_max', nrows)
1072 1072 norm = gammalib.GFitsTableDoubleCol('norm', nrows)
1073 1073 norm_err = gammalib.GFitsTableDoubleCol('norm_err', nrows)
1074   - ulim_values = gammalib.GFitsTableDoubleCol('norm_ul', nrows)
  1074 + norm_ul = gammalib.GFitsTableDoubleCol('norm_ul', nrows)
1075 1075 e2dnde = gammalib.GFitsTableDoubleCol('ref_e2dnde', nrows)
1076 1076 dnde = gammalib.GFitsTableDoubleCol('ref_dnde', nrows)
1077 1077 Npred_values = gammalib.GFitsTableDoubleCol('ref_npred', nrows)
... ... @@ -1084,7 +1084,7 @@ class csspec(ctools.csobservation):
1084 1084 energy_high.unit('TeV')
1085 1085 norm.unit('')
1086 1086 norm_err.unit('')
1087   - ulim_values.unit('')
  1087 + norm_ul.unit('')
1088 1088 e2dnde.unit('erg/cm2/s')
1089 1089 dnde.unit('counts/MeV/cm2/s')
1090 1090 Npred_values.unit('counts')
... ... @@ -1098,8 +1098,12 @@ class csspec(ctools.csobservation):
1098 1098 energy_low[i] = result['energy_low']
1099 1099 energy_high[i] = result['energy_high']
1100 1100 norm[i] = 1.0
1101   - norm_err[i] = result['flux_err'] / result['flux']
1102   - ulim_values[i] = result['ulimit'] / result['flux']
  1101 + if result['flux'] != 0.0:
  1102 + norm_err[i] = result['flux_err'] / result['flux']
  1103 + norm_ul[i] = result['ulimit'] / result['flux']
  1104 + else:
  1105 + norm_err[i] = 0.0
  1106 + norm_ul[i] = 0.0
1103 1107 e2dnde[i] = result['e2dnde']
1104 1108 dnde[i] = result['flux']
1105 1109 Npred_values[i] = result['Npred']
... ... @@ -1109,7 +1113,10 @@ class csspec(ctools.csobservation):
1109 1113 # Add likelihood scan values
1110 1114 for fbin in range(ncols):
1111 1115 dloglike_scan[i,fbin] = result['dloglike'][fbin]
1112   - norm_scan[i,fbin] = result['norm_scan'][fbin] / result['flux']
  1116 + if result['flux'] != 0.0:
  1117 + norm_scan[i,fbin] = result['norm_scan'][fbin] / result['flux']
  1118 + else:
  1119 + norm_scan[i,fbin] = 0.0
1113 1120  
1114 1121 # Initialise FITS Table with extension "SPECTRUM"
1115 1122 table = gammalib.GFitsBinTable(nrows)
... ... @@ -1125,7 +1132,7 @@ class csspec(ctools.csobservation):
1125 1132 table.append(energy_high)
1126 1133 table.append(norm)
1127 1134 table.append(norm_err)
1128   - table.append(ulim_values)
  1135 + table.append(norm_ul)
1129 1136 table.append(e2dnde)
1130 1137 table.append(dnde)
1131 1138 table.append(TSvalues)
... ...
doc/source/users/tutorials/1dc/CasA_model.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  2 +<source_library title="source library">
  3 + <source name="Cassiopeia A" type="PointSource">
  4 + <spectrum type="PowerLaw">
  5 + <parameter name="Prefactor" value="1.45" scale="1e-18" min="0" free="1"/>
  6 + <parameter name="Index" value="2.75" scale="-1" min="-10" max="10" free="1"/>
  7 + <parameter name="PivotEnergy" value="1" scale="1e6" free="0"/>
  8 + </spectrum>
  9 + <spatialModel type="PointSource">
  10 + <parameter name="RA" value="350.8500" scale="1" free="0"/>
  11 + <parameter name="DEC" value="58.8150" scale="1" free="0"/>
  12 + </spatialModel>
  13 + </source>
  14 + <source name="Background model" type="CTAIrfBackground" instrument="CTA">
  15 + <spectrum type="PowerLaw">
  16 + <parameter name="Prefactor" value="1" scale="1" min="0.001" max="1000" free="1"/>
  17 + <parameter name="Index" value="0" scale="1" min="-5" max="5" free="1"/>
  18 + <parameter name="Scale" value="1" scale="1e6" min="0.01" max="1000" free="0"/>
  19 + </spectrum>
  20 + </source>
  21 +</source_library>
... ...
doc/source/users/tutorials/1dc/first_fitting.rst
... ... @@ -39,7 +39,7 @@ You run the :ref:`ctlike` tool as follows:
39 39 Input model definition XML file [$CTOOLS/share/models/crab.xml] bkgcube.xml
40 40 Output model definition XML file [crab_results.xml] results_stacked.xml
41 41  
42   -The tool will take a few minutes (on Mac OS X) to perform the model fitting,
  42 +The tool will take a few minutes (on Mac OS) to perform the model fitting,
43 43 and will write the results into an updated
44 44 :ref:`model definition file <glossary_moddef>`
45 45 containing the fitted model parameters and their statistical uncertainties.
... ... @@ -124,7 +124,7 @@ You create the butterfly diagram for ``Src001`` using
124 124 Stop value for last energy bin in TeV [100.0]
125 125 Output ASCII file [butterfly.txt] butterfly_src001.txt
126 126  
127   -and for `Src003`` using
  127 +and for ``Src003`` using
128 128  
129 129 .. code-block:: bash
130 130  
... ... @@ -192,28 +192,14 @@ a maximum likelihood model fit.
192 192  
193 193 Obviously, ``Src001`` has a spectral cut-off (red flux points) and hence is not
194 194 adequately described by a power law model. You should therefore replace the
195   -power law in the
196   -:ref:`model definition file <glossary_moddef>`
197   -by an exponentially cutoff power law, as shown below:
198   -
199   -.. code-block:: xml
200   -
201   - <?xml version="1.0" encoding="UTF-8" standalone="no"?>
202   - <source_library title="source library">
203   - <source name="Src001" type="PointSource">
204   - <spectrum type="ExponentialCutoffPowerLaw">
205   - <parameter name="Prefactor" scale="1e-18" value="5.7" min="1e-07" max="1000.0" free="1"/>
206   - <parameter name="Index" scale="-1" value="2.48" min="0.0" max="+5.0" free="1"/>
207   - <parameter name="CutoffEnergy" scale="1e7" value="1.0" min="0.01" max="1000.0" free="1"/>
208   - <parameter name="PivotEnergy" scale="1e6" value="0.3" min="0.01" max="1000.0" free="0"/>
209   - </spectrum>
210   - <spatialModel type="PointSource">
211   - <parameter name="RA" value="266.424004498437" scale="1" free="1" />
212   - <parameter name="DEC" value="-29.0049010253548" scale="1" free="1" />
213   - </spatialModel>
214   - </source>
215   - ...
216   - </source_library>
  195 +power law spectrum in the :ref:`model definition file <glossary_moddef>`
  196 +by an exponentially cutoff power law spectrum, as shown below (only the first
  197 +lines of the :ref:`model definition file <glossary_moddef>` are shown for
  198 +illustration):
  199 +
  200 +.. literalinclude:: results_stacked_eplaw.xml
  201 + :language: xml
  202 + :lines: 1-16
217 203  
218 204 Fitting this model to the data improves the fit and the resulting butterfly
219 205 diagram follows now reasonably well the spectral points:
... ...
doc/source/users/tutorials/1dc/first_onoff.rst
... ... @@ -208,6 +208,7 @@ follows:
208 208 Output observation definition XML file [onoff_obs.xml]
209 209 Output model definition XML file [onoff_model.xml]
210 210 Method for background estimation (REFLECTED|CUSTOM) [REFLECTED]
  211 + Shape of the source region (CIRCLE|RECT) [CIRCLE]
211 212 Coordinate system (CEL - celestial, GAL - galactic) (CEL|GAL) [CEL]
212 213 Right Ascension of source region centre (deg) (0-360) [83.63] 350.85
213 214 Declination of source region centre (deg) (-90-90) [22.01] 58.815
... ... @@ -355,29 +356,8 @@ The fit result can be inspected by peeking the log file:
355 356 provided as input model to :ref:`csphagen`. Here an example for an
356 357 input model:
357 358  
358   - .. code-block:: xml
359   -
360   - <?xml version="1.0" encoding="UTF-8" standalone="no"?>
361   - <source_library title="source library">
362   - <source name="Cassiopeia A" type="PointSource">
363   - <spectrum type="PowerLaw">
364   - <parameter name="Prefactor" value="1.45" scale="1e-18" min="0" free="1"/>
365   - <parameter name="Index" value="2.75" scale="-1" min="-10" max="10" free="1"/>
366   - <parameter name="PivotEnergy" value="1" scale="1e6" free="0"/>
367   - </spectrum>
368   - <spatialModel type="PointSource">
369   - <parameter name="RA" value="350.8500" scale="1" free="0"/>
370   - <parameter name="DEC" value="58.8150" scale="1" free="0"/>
371   - </spatialModel>
372   - </source>
373   - <source name="Background model" type="CTAIrfBackground" instrument="CTA">
374   - <spectrum type="PowerLaw">
375   - <parameter name="Prefactor" value="1" scale="1" min="0.001" max="1000" free="1"/>
376   - <parameter name="Index" value="0" scale="1" min="-5" max="5" free="1"/>
377   - <parameter name="Scale" value="1" scale="1e6" min="0.01" max="1000" free="0"/>
378   - </spectrum>
379   - </source>
380   - </source_library>
  359 + .. literalinclude:: CasA_model.xml
  360 + :language: xml
381 361  
382 362 Now rerun :ref:`csphagen` as follows:
383 363  
... ... @@ -395,6 +375,7 @@ The fit result can be inspected by peeking the log file:
395 375 Output observation definition XML file [onoff_obs.xml] onoff_obs_cstat.xml
396 376 Output model definition XML file [onoff_model.xml] onoff_model_cstat.xml
397 377 Method for background estimation (REFLECTED|CUSTOM) [REFLECTED]
  378 + Shape of the source region (CIRCLE|RECT) [CIRCLE]
398 379 Coordinate system (CEL - celestial, GAL - galactic) (CEL|GAL) [CEL]
399 380 Right Ascension of source region centre (deg) (0-360) [350.85]
400 381 Declination of source region centre (deg) (-90-90) [58.815]
... ... @@ -452,7 +433,7 @@ Like for a binned or unbinned analysis, you can create a butterfly diagram by ty
452 433  
453 434 $ ctbutterfly
454 435 Input event list, counts cube or observation definition XML file [events.fits] onoff_obs.xml
455   - Source of interest [Crab] Cassiopeia A
  436 + Source of interest [Crab] Dummy
456 437 Input model definition XML file [$CTOOLS/share/models/crab.xml] CasA_results.xml
457 438 Lower energy limit (TeV) [0.1]
458 439 Upper energy limit (TeV) [100.0] 50.0
... ... @@ -465,7 +446,7 @@ and the SED by typing
465 446 $ csspec
466 447 Input event list, counts cube, or observation definition XML file [events.fits] onoff_obs.xml
467 448 Input model definition XML file [$CTOOLS/share/models/crab.xml] CasA_results.xml
468   - Source name [Crab] Cassiopeia A
  449 + Source name [Crab] Dummy
469 450 Spectrum generation method (SLICE|NODES|AUTO) [AUTO]
470 451 Binning algorithm (FILE|LIN|LOG|POW) [LOG]
471 452 Lower energy limit (TeV) [0.1]
... ...
doc/source/users/tutorials/1dc/results_stacked_eplaw.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
  2 +<source_library title="source library">
  3 + <source name="Src001" type="PointSource">
  4 + <spectrum type="ExponentialCutoffPowerLaw">
  5 + <parameter name="Prefactor" scale="1e-18" value="5.7" min="1e-07" max="1000.0" free="1"/>
  6 + <parameter name="Index" scale="-1" value="2.48" min="0.0" max="+5.0" free="1"/>
  7 + <parameter name="CutoffEnergy" scale="1e7" value="1.0" min="0.01" max="1000.0" free="1"/>
  8 + <parameter name="PivotEnergy" scale="1e6" value="0.3" min="0.01" max="1000.0" free="0"/>
  9 + </spectrum>
  10 + <spatialModel type="PointSource">
  11 + <parameter name="RA" value="266.424004498437" scale="1" free="1"/>
  12 + <parameter name="DEC" value="-29.0049010253548" scale="1" free="1"/>
  13 + </spatialModel>
  14 + </source>
  15 + <source name="Src002" type="PointSource">
  16 + <spectrum type="PowerLaw">
  17 + <parameter name="Prefactor" value="1" scale="5.7e-18" min="0" free="1" />
  18 + <parameter name="Index" value="1" scale="-2.48" min="-4.03225806451613" max="4.03225806451613" free="1" />
  19 + <parameter name="PivotEnergy" value="1" scale="300000" free="0" />
  20 + </spectrum>
  21 + <spatialModel type="PointSource">
  22 + <parameter name="RA" value="264.802012181975" error="0" scale="1" free="1" />
  23 + <parameter name="DEC" value="-31.0050720432975" error="0" scale="1" free="1" />
  24 + </spatialModel>
  25 + </source>
  26 + <source name="Src003" type="PointSource">
  27 + <spectrum type="PowerLaw">
  28 + <parameter name="Prefactor" value="1" error="0" scale="5.7e-18" min="0" free="1" />
  29 + <parameter name="Index" value="1" error="-0" scale="-2.48" min="-4.03225806451613" max="4.03225806451613" free="1" />
  30 + <parameter name="PivotEnergy" value="1" scale="300000" free="0" />
  31 + </spectrum>
  32 + <spatialModel type="PointSource">
  33 + <parameter name="RA" value="266.831945177213" error="0" scale="1" free="1" />
  34 + <parameter name="DEC" value="-28.1460284439951" error="0" scale="1" free="1" />
  35 + </spatialModel>
  36 + </source>
  37 + <source name="Src004" type="PointSource">
  38 + <spectrum type="PowerLaw">
  39 + <parameter name="Prefactor" value="1" error="0" scale="5.7e-18" min="0" free="1" />
  40 + <parameter name="Index" value="1" error="-0" scale="-2.48" min="-4.03225806451613" max="4.03225806451613" free="1" />
  41 + <parameter name="PivotEnergy" value="1" scale="300000" free="0" />
  42 + </spectrum>
  43 + <spatialModel type="PointSource">
  44 + <parameter name="RA" value="268.244867737546" error="0" scale="1" free="1" />
  45 + <parameter name="DEC" value="-30.3728286753247" error="0" scale="1" free="1" />
  46 + </spatialModel>
  47 + </source>
  48 + <source name="Src005" type="PointSource">
  49 + <spectrum type="PowerLaw">
  50 + <parameter name="Prefactor" value="1" error="0" scale="5.7e-18" min="0" free="1" />
  51 + <parameter name="Index" value="1" error="-0" scale="-2.48" min="-4.03225806451613" max="4.03225806451613" free="1" />
  52 + <parameter name="PivotEnergy" value="1" scale="300000" free="0" />
  53 + </spectrum>
  54 + <spatialModel type="PointSource">
  55 + <parameter name="RA" value="270.424492131939" error="0" scale="1" free="1" />
  56 + <parameter name="DEC" value="-29.6624142779832" error="0" scale="1" free="1" />
  57 + </spatialModel>
  58 + </source>
  59 + <source name="Src006" type="PointSource">
  60 + <spectrum type="PowerLaw">
  61 + <parameter name="Prefactor" value="1" error="0" scale="5.7e-18" min="0" free="1" />
  62 + <parameter name="Index" value="1" error="-0" scale="-2.48" min="-4.03225806451613" max="4.03225806451613" free="1" />
  63 + <parameter name="PivotEnergy" value="1" scale="300000" free="0" />
  64 + </spectrum>
  65 + <spatialModel type="PointSource">
  66 + <parameter name="RA" value="263.809469183313" error="0" scale="1" free="1" />
  67 + <parameter name="DEC" value="-31.2230130593475" error="0" scale="1" free="1" />
  68 + </spatialModel>
  69 + </source>
  70 + <source name="BackgroundModel" type="CTACubeBackground" instrument="CTA,HESS,MAGIC,VERITAS">
  71 + <spectrum type="PowerLaw">
  72 + <parameter name="Prefactor" value="1" error="0" scale="1" min="0.01" max="100" free="1" />
  73 + <parameter name="Index" value="0" error="0" scale="1" min="-5" max="5" free="1" />
  74 + <parameter name="PivotEnergy" value="1" scale="1000000" free="0" />
  75 + </spectrum>
  76 + </source>
  77 +</source_library>
... ...
doc/source/users/tutorials/1dc/show_onoff_spectrum.py
... ... @@ -2,7 +2,7 @@
2 2 # ==========================================================================
3 3 # Display butterfly generated by ctbutterfly and spectrum generated by csspec
4 4 #
5   -# Copyright (C) 2018 Juergen Knoedlseder
  5 +# Copyright (C) 2018-2021 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
... ... @@ -51,13 +51,14 @@ def plot_spectrum_butterfly(specfile, butterfile, fmt=&#39;o&#39;, color=&#39;red&#39;):
51 51 # Read spectrum file
52 52 fits = gammalib.GFits(specfile)
53 53 table = fits.table(1)
54   - c_energy = table['Energy']
55   - c_ed = table['ed_Energy']
56   - c_eu = table['eu_Energy']
57   - c_flux = table['Flux']
58   - c_eflux = table['e_Flux']
59   - c_ts = table['TS']
60   - c_upper = table['UpperLimit']
  54 + c_energy = table['e_ref']
  55 + c_ed = table['e_min']
  56 + c_eu = table['e_max']
  57 + c_flux = table['ref_e2dnde']
  58 + c_norm = table['norm']
  59 + c_eflux = table['norm_err']
  60 + c_upper = table['norm_ul']
  61 + c_ts = table['ts']
61 62  
62 63 # Read butterfly file
63 64 csv = gammalib.GCsv(butterfile)
... ... @@ -83,22 +84,23 @@ def plot_spectrum_butterfly(specfile, butterfile, fmt=&#39;o&#39;, color=&#39;red&#39;):
83 84  
84 85 # Get Test Statistic, flux and flux error
85 86 ts = c_ts.real(row)
86   - flx = c_flux.real(row)
87   - e_flx = c_eflux.real(row)
  87 + norm = c_norm.real(row)
  88 + flx = norm * c_flux.real(row)
  89 + e_flx = flx * c_eflux.real(row)
88 90  
89 91 # If Test Statistic is larger than 9 and flux error is smaller than
90 92 # flux then append flux plots ...
91 93 if ts > 9.0 and e_flx < flx:
92 94 energies.append(c_energy.real(row))
93   - flux.append(c_flux.real(row))
  95 + flux.append(flx)
94 96 ed_engs.append(c_ed.real(row))
95 97 eu_engs.append(c_eu.real(row))
96   - e_flux.append(c_eflux.real(row))
  98 + e_flux.append(e_flx)
97 99  
98 100 # ... otherwise append upper limit
99 101 else:
100 102 ul_energies.append(c_energy.real(row))
101   - ul_flux.append(c_upper.real(row))
  103 + ul_flux.append(flx*c_upper.real(row))
102 104 ul_ed_engs.append(c_ed.real(row))
103 105 ul_eu_engs.append(c_eu.real(row))
104 106  
... ...
test/tutorials.py
... ... @@ -46,47 +46,128 @@ class tutorials(gammalib.GPythonTestSuite):
46 46 # Call base class constructor
47 47 gammalib.GPythonTestSuite.__init__(self)
48 48  
49   - # Initialise results
50   - self.results = None
  49 + # Initialise private members
  50 + self._rst_step = 0 # Step counter
  51 + self._rst_path = '' # Path where the tested RST file resides
51 52  
52 53 # Return
53 54 return
54 55  
55   - # Set test functions
56   - def set(self):
  56 + # Clean pfiles directory
  57 + def _clean_pfiles(self):
57 58 """
58   - Set all test functions
  59 + Clean pfiles directory
59 60 """
60   - # Set test name
61   - self.name('Tutorials Verification')
62   -
63   - # Append tutorials
64   - self.append(self.tutorials_1dc, 'Test 1DC tutorial')
  61 + # Remove all files from pfiles directory
  62 + os.system('rm -rf %s/*.par' % os.environ['PFILES'])
65 63  
66 64 # Return
67 65 return
68 66  
69   - # Test line execution
70   - def _execute_cmd(self, cmd, args):
  67 + # Set result directory
  68 + def _set_result_dir(self, path):
71 69 """
72   - Execute command
  70 + Set result directory
  71 +
  72 + Parameters
  73 + ----------
  74 + path : str
  75 + Result directory path
73 76 """
74   - # Create subprocess for tool
75   - p = subprocess.Popen(cmd.split(' '), stdout=subprocess.PIPE,
76   - stdin=subprocess.PIPE,
77   - stderr=subprocess.PIPE)
  77 + # Create result directory
  78 + try:
  79 + os.makedirs(path)
  80 + except:
  81 + pass
78 82  
79   - # Write command arguments parameters
80   - if len(args) > 0:
81   - p.stdin.write(b'%s' % args)
82   - p.stdin.close()
  83 + # Clear result directory
  84 + os.system('rm -rf %s/*' % path)
83 85  
84   - # Get results
85   - res = p.communicate()
  86 + # Step in result directory
  87 + os.chdir(path)
86 88  
87   - # Set error flag and text
88   - name = 'Test execution of command %s.' % cmd
89   - self.test_assert((len(res[1]) == 0), name, res[1])
  89 + # Return
  90 + return
  91 +
  92 + # Test command execution
  93 + def _test_execute_cmd(self, cmd, args):
  94 + """
  95 + Test command execution
  96 +
  97 + Tests the execution of a command. Currently supported commands are:
  98 + - cd
  99 + - mkdir
  100 + - nano
  101 + - ./xxxxxx (any command xxxxxx that resides in the directory of the RST file)
  102 +
  103 + Parameters
  104 + ----------
  105 + cmd : str
  106 + Command line
  107 + args : list of str
  108 + Additional lines following the command line
  109 + """
  110 + # Split command line
  111 + cmdline = cmd.split(' ')
  112 +
  113 + # Check command line
  114 + self.test_assert((len(cmdline) > 0), 'Test command.', cmd)
  115 +
  116 + # Continue only if valid
  117 + if len(cmdline) > 0:
  118 +
  119 + # Extract command
  120 + command = gammalib.strip_whitespace(cmdline[0])
  121 +
  122 + # Handle commands
  123 + if command == 'mkdir':
  124 + self.test_value(len(cmdline), 2, 'Test command "mkdir".')
  125 + self.test_value(len(args), 0, 'Test that no lines follow "mkdir".')
  126 + if len(cmdline) > 1:
  127 + dirname = gammalib.strip_whitespace(cmdline[1])
  128 + os.system('mkdir -p %s' % dirname)
  129 +
  130 + elif command == 'cd':
  131 + self.test_value(len(cmdline), 2, 'Test command "cd".')
  132 + self.test_value(len(args), 0, 'Test that no lines follow "cd".')
  133 + if len(cmdline) > 1:
  134 + dirname = gammalib.strip_whitespace(cmdline[1])
  135 + os.chdir(dirname)
  136 +
  137 + elif command == 'nano':
  138 + self.test_value(len(cmdline), 2, 'Test command "nano".')
  139 + self.test_assert((len(args) > 0), 'Test that lines follow "nano".')
  140 + if len(cmdline) > 1:
  141 + fname = gammalib.strip_whitespace(cmdline[1])
  142 + f = open(fname, 'wb')
  143 + for arg in args:
  144 + f.write(arg)
  145 + f.close()
  146 +
  147 + elif command[0:2] == './':
  148 + scriptname = command[2:]
  149 + self.test_assert((len(args) == 0), 'Test script "%s".' % scriptname)
  150 + tool = '%s/%s' % (self._rst_path, scriptname)
  151 +
  152 + # Debug: show tool
  153 + #print tool.split(' '),
  154 +
  155 + # Create subprocess for tool
  156 + p = subprocess.Popen(tool.split(' '), stdout=subprocess.PIPE,
  157 + stdin=subprocess.PIPE,
  158 + stderr=subprocess.PIPE)
  159 +
  160 + # Get results
  161 + res = p.communicate()
  162 +
  163 + # Set error flag and text
  164 + name = 'Test execution of %s.' % tool
  165 + self.test_assert((len(res[1]) == 0), name, res[1])
  166 +
  167 + else:
  168 + name = 'Test command "%s".' % command
  169 + msg = 'Unknown command "%s"' % command
  170 + self.test_assert(False, name, msg)
90 171  
91 172 # Return
92 173 return
... ... @@ -113,8 +194,7 @@ class tutorials(gammalib.GPythonTestSuite):
113 194  
114 195 # If we have a command then execute it
115 196 if cmd != '':
116   - print(cmd, args)
117   - self._execute_cmd(cmd, args)
  197 + self._test_execute_cmd(cmd, args)
118 198  
119 199 # Set new command
120 200 cmd = line[2:]
... ... @@ -126,8 +206,7 @@ class tutorials(gammalib.GPythonTestSuite):
126 206  
127 207 # If there is a pending command then execute it
128 208 if cmd != '':
129   - print(cmd, args)
130   - self._execute_cmd(cmd, args)
  209 + self._test_execute_cmd(cmd, args)
131 210  
132 211 # Return
133 212 return
... ... @@ -149,23 +228,32 @@ class tutorials(gammalib.GPythonTestSuite):
149 228 lines : list of str
150 229 Command lines
151 230 """
152   - # Initialise command
153   - cmd = ''
  231 + # Increment step counter
  232 + self._rst_step += 1
  233 +
  234 + # Initialise arguments
  235 + args = ''
  236 +
  237 + # Set command
  238 + cmd = tool.split(' ')
154 239  
155 240 # Append lines
156 241 for line in lines:
157 242 pos = line.find(']')
158 243 if pos != -1:
159 244 arg = gammalib.strip_whitespace(line[pos+1:])
160   - cmd += '%s\n' % arg
  245 + args += '%s\n' % arg
  246 +
  247 + # Debug: show tool
  248 + #print cmd,
161 249  
162 250 # Create subprocess for tool
163   - p = subprocess.Popen([tool], stdout=subprocess.PIPE,
164   - stdin=subprocess.PIPE,
165   - stderr=subprocess.PIPE)
  251 + p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
  252 + stdin=subprocess.PIPE,
  253 + stderr=subprocess.PIPE)
166 254  
167 255 # Write command line parameters
168   - p.stdin.write(b'%s' % cmd)
  256 + p.stdin.write(b'%s' % args)
169 257  
170 258 # Get results
171 259 res = p.communicate()
... ... @@ -177,13 +265,20 @@ class tutorials(gammalib.GPythonTestSuite):
177 265 name = 'Test execution of %s.' % tool
178 266 self.test_assert((len(res[1]) == 0), name, res[1])
179 267  
  268 + # Preprend step counter to logfile
  269 + logfile = gammalib.GFilename('%s.log' % (cmd[0]))
  270 + if logfile.exists():
  271 + src = logfile.url()
  272 + dst = '%2.2d_%s' % (self._rst_step, src)
  273 + os.system('mv %s %s' % (src, dst))
  274 +
180 275 # Return
181 276 return
182 277  
183   - # Add workflow step
184   - def _add_workflow_step(self, tool, lines):
  278 + # Test workflow step
  279 + def _test_step(self, tool, lines):
185 280 """
186   - Add workflow step
  281 + Test workflow step
187 282  
188 283 Parameters
189 284 ----------
... ... @@ -191,18 +286,12 @@ class tutorials(gammalib.GPythonTestSuite):
191 286 Tool name
192 287 lines : list of str
193 288 Command lines
194   -
195   - Returns
196   - -------
197   - cmd : str
198   - Workflow command
199 289 """
200   - # If tool is a ctool or cscript then add a ctool or cscript workflow
201   - # step
  290 + # If tool is a ctool or cscript then test ctool or cscript execution
202 291 if tool[0:2] == 'ct' or tool[0:2] == 'cs':
203 292 self._test_ctool_execution(tool, lines)
204 293  
205   - # ... otherwise execute lines
  294 + # ... otherwise test line execution
206 295 else:
207 296 all_lines = ['$ %s' % tool]
208 297 all_lines.extend(lines)
... ... @@ -211,21 +300,33 @@ class tutorials(gammalib.GPythonTestSuite):
211 300 # Return
212 301 return
213 302  
214   - # Extract workflow
215   - def _get_workflow(self, filename):
  303 + # Copy file
  304 + def _copy_file(self, filename):
216 305 """
217   - Extract workflow from Sphinx rst file
  306 + Copy file from rst directory into working directory
  307 + """
  308 + # Set source filename
  309 + src = self._rst_path + '/' + filename
  310 +
  311 + # Copy file
  312 + os.system('cp %s %s' % (src, filename))
  313 +
  314 + # Return
  315 + return
  316 +
  317 + # Test Sphinx rst file
  318 + def _test_rst_file(self, filename):
  319 + """
  320 + Test Sphinx rst file
218 321  
219 322 Parameters
220 323 ----------
221 324 filename : str
222   - RST filename
223   -
224   - Returns
225   - -------
226   - workflow : `~gammalib.GXml`
227   - Workflow XML file
  325 + Sphinx rst filename
228 326 """
  327 + # Set RST path
  328 + self._rst_path = os.path.abspath(os.path.dirname(filename))
  329 +
229 330 # Open RST file
230 331 f = open(filename, 'r')
231 332  
... ... @@ -238,6 +339,8 @@ class tutorials(gammalib.GPythonTestSuite):
238 339 # If we are not in a code block then check whether a code block
239 340 # starts
240 341 if not code:
  342 +
  343 + # Check for start of a code block
241 344 pos = line.find('code-block:: bash')
242 345 if pos != -1:
243 346 code = True
... ... @@ -246,6 +349,14 @@ class tutorials(gammalib.GPythonTestSuite):
246 349 lines = []
247 350 continue
248 351  
  352 + # ... otherwise check for literalinclude, and if found, copy
  353 + # relevant file
  354 + pos = line.find('literalinclude::')
  355 + if pos != -1:
  356 + filename = gammalib.strip_whitespace(line[pos+16:-1])
  357 + self._copy_file(filename)
  358 + continue
  359 +
249 360 # ... otherwise
250 361 else:
251 362  
... ... @@ -257,7 +368,7 @@ class tutorials(gammalib.GPythonTestSuite):
257 368 tool = line[pos+3:-1]
258 369 continue
259 370  
260   - # ... otherwise append lines or add workflow step
  371 + # ... otherwise append lines or test step
261 372 else:
262 373 blank = gammalib.strip_whitespace(line[0:start])
263 374 cmd = line[start:-1]
... ... @@ -265,13 +376,27 @@ class tutorials(gammalib.GPythonTestSuite):
265 376 lines.append(cmd)
266 377 continue
267 378 else:
268   - self._add_workflow_step(tool, lines)
  379 + self._test_step(tool, lines)
269 380 code = False
270 381 continue
271 382  
272   - # Add step if some code is pending
  383 + # Test step if some code is pending
273 384 if code:
274   - self._add_workflow_step(tool, lines)
  385 + self._test_step(tool, lines)
  386 +
  387 + # Return
  388 + return
  389 +
  390 + # Set test functions
  391 + def set(self):
  392 + """
  393 + Set all test functions
  394 + """
  395 + # Set test name
  396 + self.name('Tutorials Verification')
  397 +
  398 + # Append tutorials
  399 + self.append(self.tutorials_1dc, 'Test 1DC tutorial')
275 400  
276 401 # Return
277 402 return
... ... @@ -281,15 +406,49 @@ class tutorials(gammalib.GPythonTestSuite):
281 406 """
282 407 Test 1DC tutorials
283 408 """
284   - # Set environment variables
285   - os.environ['CTADATA'] = '/project-data/cta/data/1dc'
286   - os.environ['CALDB'] = '/project-data/cta/data/1dc/caldb'
  409 + # Continue only if CTADATA1DC environment variable is set
  410 + if 'CTADATA1DC' in os.environ:
  411 +
  412 + # Set environment variables
  413 + os.environ['CTADATA'] = os.environ['CTADATA1DC']
  414 + os.environ['CALDB'] = os.environ['CTADATA1DC']+'/caldb'
  415 +
  416 + # Set Sphinx rst file path
  417 + path = os.path.abspath('../doc/source/users/tutorials/1dc')
  418 +
  419 + # Set result directory
  420 + self._set_result_dir('tutorials/1dc')
287 421  
288   - # Extract workflow
289   - #workflow = self._get_workflow('../doc/source/users/tutorials/1dc/first_onoff.rst')
290   - workflow = self._get_workflow('first_onoff.rst')
  422 + # Get current working directory
  423 + cwd = os.getcwd()
291 424  
292   - # Run workflow
  425 + # Clean pfiles
  426 + self._clean_pfiles()
  427 +
  428 + # Initialise step counter
  429 + self._rst_step = 0
  430 +
  431 + # Test Sphinx rst files
  432 + self._test_rst_file('%s/first_select_obs.rst' % path)
  433 + self._test_rst_file('%s/first_select_events.rst' % path)
  434 + self._test_rst_file('%s/first_skymap.rst' % path)
  435 + self._test_rst_file('%s/first_detect.rst' % path)
  436 + self._test_rst_file('%s/first_stacked.rst' % path)
  437 + self._test_rst_file('%s/first_fitting.rst' % path)
  438 + self._test_rst_file('%s/first_improving.rst' % path)
  439 + self._test_rst_file('%s/first_unbinned.rst' % path)
  440 +
  441 + # Reset working directory
  442 + os.chdir(cwd)
  443 +
  444 + # Clean pfiles
  445 + self._clean_pfiles()
  446 +
  447 + # Initialise step counter
  448 + self._rst_step = 0
  449 +
  450 + # Test Sphinx rst file for On/Off analysis
  451 + self._test_rst_file('%s/first_onoff.rst' % path)
293 452  
294 453 # Return
295 454 return
... ... @@ -318,15 +477,18 @@ if __name__ == &#39;__main__&#39;:
318 477 except:
319 478 pass
320 479  
321   - # Copy ctools parameter files into pfiles directory
322   - os.system('cp -r ../src/*/*.par pfiles/')
323   -
324 480 # Set PFILES environment variable
325   - os.environ['PFILES'] = 'pfiles'
  481 + os.environ['PFILES'] = os.path.abspath('pfiles')
  482 +
  483 + # Get current working directory
  484 + cwd = os.getcwd()
326 485  
327 486 # Run test suite
328 487 success = suites.run()
329 488  
  489 + # Reset working directory
  490 + os.chdir(cwd)
  491 +
330 492 # Save test results
331 493 suites.save('reports/tutorials.xml')
332 494  
... ...