Commit 8085434844f98fec0e64fce3729f5e718ce65901

Authored by Jürgen Knödlseder
1 parent 003f01cc

Refactor cscripts and add some functions to obsutils (#1828)

The obsutils.sim() function does not correctly handle stacked observations. It appends stacked response cubes to a counts cube in case that multiple observations have been simulated and stacked analysis was requested.

The stacked response cube computation is done using the obsutils.get_stacked_response() function.

The function obsutils.set_ts_model() has been added to setup a model for TS determination. This is used by some of the cscripts, and avoids code duplication.

Finally, the cstsdist and cspull scripts have been refactored.
cscripts/cspull.py
@@ -36,25 +36,19 @@ class cspull(ctools.cscript): @@ -36,25 +36,19 @@ class cspull(ctools.cscript):
36 # Constructor 36 # Constructor
37 def __init__(self, *argv): 37 def __init__(self, *argv):
38 """ 38 """
39 - Constructor. 39 + Constructor
40 """ 40 """
41 # Set name 41 # Set name
42 self._name = 'cspull' 42 self._name = 'cspull'
43 - self._version = '1.1.0' 43 + self._version = '1.2.0'
44 44
45 # Initialise some members 45 # Initialise some members
46 self._edisp = False 46 self._edisp = False
47 - self._exposure = None  
48 - self._psfcube = None  
49 - self._bckcube = None  
50 - self._edispcube = None  
51 - self._stackmodels = None  
52 self._coordsys = 'CEL' 47 self._coordsys = 'CEL'
53 self._proj = 'TAN' 48 self._proj = 'TAN'
54 self._log_clients = False # Static parameter 49 self._log_clients = False # Static parameter
55 self._offset = 0.0 50 self._offset = 0.0
56 self._ntrials = 0 51 self._ntrials = 0
57 - self._outfile = gammalib.GFilename()  
58 self._npix = 0 52 self._npix = 0
59 self._binsz = 0.0 53 self._binsz = 0.0
60 self._pattern = 'single' 54 self._pattern = 'single'
@@ -62,11 +56,10 @@ class cspull(ctools.cscript): @@ -62,11 +56,10 @@ class cspull(ctools.cscript):
62 self._seed = 1 56 self._seed = 1
63 self._chatter = 2 57 self._chatter = 2
64 58
65 - # Initialise observation container from constructor arguments. 59 + # Initialise observation container from constructor arguments
66 self._obs, argv = self._set_input_obs(argv) 60 self._obs, argv = self._set_input_obs(argv)
67 61
68 - # Initialise application by calling the appropriate class  
69 - # constructor. 62 + # Initialise application by calling the appropriate class constructor
70 self._init_cscript(argv) 63 self._init_cscript(argv)
71 64
72 # Return 65 # Return
@@ -108,7 +101,6 @@ class cspull(ctools.cscript): @@ -108,7 +101,6 @@ class cspull(ctools.cscript):
108 self._obs.models(self['inmodel'].filename()) 101 self._obs.models(self['inmodel'].filename())
109 102
110 # Read other parameters 103 # Read other parameters
111 - self._outfile = self['outfile'].filename()  
112 self._ntrials = self['ntrials'].integer() 104 self._ntrials = self['ntrials'].integer()
113 self._edisp = self['edisp'].boolean() 105 self._edisp = self['edisp'].boolean()
114 self._offset = self['offset'].real() 106 self._offset = self['offset'].real()
@@ -116,6 +108,7 @@ class cspull(ctools.cscript): @@ -116,6 +108,7 @@ class cspull(ctools.cscript):
116 self._chatter = self['chatter'].integer() 108 self._chatter = self['chatter'].integer()
117 109
118 # Query some parameters 110 # Query some parameters
  111 + self['outfile'].filename()
119 self['profile'].boolean() 112 self['profile'].boolean()
120 113
121 # Write input parameters into logger 114 # Write input parameters into logger
@@ -135,6 +128,9 @@ class cspull(ctools.cscript): @@ -135,6 +128,9 @@ class cspull(ctools.cscript):
135 obs : `~gammalib.GObservations` 128 obs : `~gammalib.GObservations`
136 Observation container. 129 Observation container.
137 """ 130 """
  131 + # Set duration
  132 + duration = self['tmax'].real() - self['tmin'].real()
  133 +
138 # Setup observation definition list 134 # Setup observation definition list
139 obsdeflist = obsutils.set_obs_patterns(self._pattern, 135 obsdeflist = obsutils.set_obs_patterns(self._pattern,
140 ra=self['ra'].real(), 136 ra=self['ra'].real(),
@@ -144,7 +140,7 @@ class cspull(ctools.cscript): @@ -144,7 +140,7 @@ class cspull(ctools.cscript):
144 # Create list of observations 140 # Create list of observations
145 obs = obsutils.set_obs_list(obsdeflist, 141 obs = obsutils.set_obs_list(obsdeflist,
146 tstart=self['tmin'].real(), 142 tstart=self['tmin'].real(),
147 - duration=self['tmax'].real()-self['tmin'].real(), 143 + duration=duration,
148 deadc=self['deadc'].real(), 144 deadc=self['deadc'].real(),
149 emin=self['emin'].real(), 145 emin=self['emin'].real(),
150 emax=self['emax'].real(), 146 emax=self['emax'].real(),
@@ -155,128 +151,6 @@ class cspull(ctools.cscript): @@ -155,128 +151,6 @@ class cspull(ctools.cscript):
155 # Return observation container 151 # Return observation container
156 return obs 152 return obs
157 153
158 - def _set_stacked_irf(self):  
159 - """  
160 - Prepare stacked IRFs for stacked analysis  
161 - """  
162 - # Write header into logger  
163 - if self._logTerse():  
164 - self._log('\n')  
165 - self._log.header1('Compute stacked response')  
166 -  
167 - # Set number of energy bins to at least 30 per energy decade  
168 - enumbins = int((math.log10(self['emax'].real()) -  
169 - math.log10(self['emin'].real())) * 30.0)  
170 - if self._enumbins > enumbins:  
171 - enumbins = self._enumbins  
172 -  
173 - # Compute spatial binning for point spread function and  
174 - # energy dispersion cubes  
175 - binsz = 10.0 * self['binsz'].real()  
176 - nxpix = self['npix'].integer() // 10 # Make sure result is int  
177 - nypix = self['npix'].integer() // 10 # Make sure result is int  
178 - if nxpix < 2:  
179 - nxpix = 2  
180 - if nypix < 2:  
181 - nypix = 2  
182 -  
183 - # Get stacked exposure  
184 - expcube = ctools.ctexpcube(self._obs)  
185 - expcube['incube'] = 'NONE'  
186 - expcube['usepnt'] = True  
187 - expcube['ebinalg'] = 'LOG'  
188 - expcube['binsz'] = self._binsz  
189 - expcube['nxpix'] = self._npix  
190 - expcube['nypix'] = self._npix  
191 - expcube['enumbins'] = enumbins  
192 - expcube['emin'] = self['emin'].real()  
193 - expcube['emax'] = self['emax'].real()  
194 - expcube['coordsys'] = self._coordsys  
195 - expcube['proj'] = self._proj  
196 - expcube['chatter'] = self._chatter  
197 - expcube['debug'] = self._logDebug()  
198 - expcube.run()  
199 -  
200 - # Notify exposure computation  
201 - if self._logTerse():  
202 - self._log('Computed exposure cube\n')  
203 -  
204 - # Get stacked Psf  
205 - psfcube = ctools.ctpsfcube(self._obs)  
206 - psfcube['incube'] = 'NONE'  
207 - psfcube['usepnt'] = True  
208 - psfcube['ebinalg'] = 'LOG'  
209 - psfcube['binsz'] = binsz  
210 - psfcube['nxpix'] = nxpix  
211 - psfcube['nypix'] = nypix  
212 - psfcube['enumbins'] = enumbins  
213 - psfcube['emin'] = self['emin'].real()  
214 - psfcube['emax'] = self['emax'].real()  
215 - psfcube['coordsys'] = self._coordsys  
216 - psfcube['proj'] = self._proj  
217 - psfcube['chatter'] = self._chatter  
218 - psfcube['debug'] = self._logDebug()  
219 - psfcube.run()  
220 -  
221 - # Notify Psf computation  
222 - if self._logTerse():  
223 - self._log('Computed point spread function cube\n')  
224 -  
225 - # Optionally get stacked Edisp  
226 - if self._edisp:  
227 - edispcube = ctools.ctedispcube(self._obs)  
228 - edispcube['incube'] = 'NONE'  
229 - edispcube['usepnt'] = True  
230 - edispcube['ebinalg'] = 'LOG'  
231 - edispcube['binsz'] = binsz  
232 - edispcube['nxpix'] = nxpix  
233 - edispcube['nypix'] = nypix  
234 - edispcube['enumbins'] = enumbins  
235 - edispcube['emin'] = self['emin'].real()  
236 - edispcube['emax'] = self['emax'].real()  
237 - edispcube['coordsys'] = self._coordsys  
238 - edispcube['proj'] = self._proj  
239 - edispcube['chatter'] = self._chatter  
240 - edispcube['debug'] = self._logDebug()  
241 - edispcube.run()  
242 -  
243 - # Store result  
244 - self._edispcube = edispcube.edispcube().copy()  
245 -  
246 - # Logging  
247 - if self._logTerse():  
248 - self._log('Computed energy dispersion cube\n')  
249 -  
250 - # Get stacked background  
251 - bkgcube = ctools.ctbkgcube(self._obs)  
252 - bkgcube['incube'] = 'NONE'  
253 - bkgcube['usepnt'] = True  
254 - bkgcube['ebinalg'] = 'LOG'  
255 - bkgcube['binsz'] = self._binsz  
256 - bkgcube['nxpix'] = self._npix  
257 - bkgcube['nypix'] = self._npix  
258 - bkgcube['enumbins'] = enumbins  
259 - bkgcube['emin'] = self['emin'].real()  
260 - bkgcube['emax'] = self['emax'].real()  
261 - bkgcube['coordsys'] = self._coordsys  
262 - bkgcube['proj'] = self._proj  
263 - bkgcube['chatter'] = self._chatter  
264 - bkgcube['debug'] = self._logDebug()  
265 - bkgcube.run()  
266 -  
267 - # Notify background cube computation  
268 - if self._logTerse():  
269 - self._log('Computed background cube\n')  
270 -  
271 - # Store results  
272 - self._exposure = expcube.expcube().copy()  
273 - self._psfcube = psfcube.psfcube().copy()  
274 - self._bckcube = bkgcube.bkgcube().copy()  
275 - self._stackmodels = bkgcube.models().copy()  
276 -  
277 - # Return  
278 - return  
279 -  
280 def _obs_string(self, obs): 154 def _obs_string(self, obs):
281 """ 155 """
282 Generate summary string for observation 156 Generate summary string for observation
@@ -310,7 +184,7 @@ class cspull(ctools.cscript): @@ -310,7 +184,7 @@ class cspull(ctools.cscript):
310 # Return summary string 184 # Return summary string
311 return text 185 return text
312 186
313 - def _trial(self, seed, stacked): 187 + def _trial(self, seed):
314 """ 188 """
315 Compute the pull for a single trial 189 Compute the pull for a single trial
316 190
@@ -318,8 +192,6 @@ class cspull(ctools.cscript): @@ -318,8 +192,6 @@ class cspull(ctools.cscript):
318 ---------- 192 ----------
319 seed : int 193 seed : int
320 Random number generator seed 194 Random number generator seed
321 - stacked : bool  
322 - Use stacked analysis  
323 195
324 Returns 196 Returns
325 ------- 197 -------
@@ -354,16 +226,6 @@ class cspull(ctools.cscript): @@ -354,16 +226,6 @@ class cspull(ctools.cscript):
354 debug=self._logDebug(), 226 debug=self._logDebug(),
355 chatter=self._chatter) 227 chatter=self._chatter)
356 228
357 - # Set response for a stacked observation  
358 - if stacked:  
359 - if self._edisp:  
360 - obs[0].response(self._exposure, self._psfcube,  
361 - self._edispcube, self._bckcube)  
362 - else:  
363 - obs[0].response(self._exposure, self._psfcube,  
364 - self._bckcube)  
365 - obs.models(self._stackmodels)  
366 -  
367 # Determine number of events in simulation 229 # Determine number of events in simulation
368 nevents = 0.0 230 nevents = 0.0
369 for run in obs: 231 for run in obs:
@@ -427,10 +289,10 @@ class cspull(ctools.cscript): @@ -427,10 +289,10 @@ class cspull(ctools.cscript):
427 # Set parameter name 289 # Set parameter name
428 name = model_name+'_'+par.name() 290 name = model_name+'_'+par.name()
429 291
430 - # Append parameter, Pull_parameter and Unc_parameter 292 + # Append parameter, Pull_parameter and e_parameter
431 colnames.append(name) 293 colnames.append(name)
432 colnames.append('Pull_'+name) 294 colnames.append('Pull_'+name)
433 - colnames.append('Unc_'+name) 295 + colnames.append('e_'+name)
434 296
435 # Compute pull 297 # Compute pull
436 fitted_value = par.value() 298 fitted_value = par.value()
@@ -444,7 +306,7 @@ class cspull(ctools.cscript): @@ -444,7 +306,7 @@ class cspull(ctools.cscript):
444 # Store results 306 # Store results
445 values[name] = fitted_value 307 values[name] = fitted_value
446 values['Pull_'+name] = pull 308 values['Pull_'+name] = pull
447 - values['Unc_'+name] = error 309 + values['e_'+name] = error
448 310
449 # Write result 311 # Write result
450 if self._logNormal(): 312 if self._logNormal():
@@ -481,13 +343,10 @@ class cspull(ctools.cscript): @@ -481,13 +343,10 @@ class cspull(ctools.cscript):
481 self._log.header1(gammalib.number('Observation',len(self._obs))) 343 self._log.header1(gammalib.number('Observation',len(self._obs)))
482 self._log(str(self._obs)) 344 self._log(str(self._obs))
483 self._log('\n') 345 self._log('\n')
484 -  
485 - # Signal if stacked analysis is requested  
486 - stacked = self._obs.size() > 1 and self._enumbins > 0  
487 -  
488 - # If several observations and binned: prepare stacked irfs  
489 - if stacked:  
490 - self._set_stacked_irf() 346 + if self._logExplicit():
  347 + for obs in self._obs:
  348 + self._log(str(obs))
  349 + self._log('\n')
491 350
492 # Write header 351 # Write header
493 if self._logTerse(): 352 if self._logTerse():
@@ -498,18 +357,21 @@ class cspull(ctools.cscript): @@ -498,18 +357,21 @@ class cspull(ctools.cscript):
498 for seed in range(self._ntrials): 357 for seed in range(self._ntrials):
499 358
500 # Make a trial and add initial seed 359 # Make a trial and add initial seed
501 - result = self._trial(seed + self._seed, stacked) 360 + result = self._trial(seed + self._seed)
  361 +
  362 + # Set output filename
  363 + outfile = self['outfile'].filename().url()
502 364
503 # Write out result immediately 365 # Write out result immediately
504 if seed == 0: 366 if seed == 0:
505 - f = open(self._outfile.url(), 'w') 367 + f = open(outfile, 'w')
506 writer = csv.DictWriter(f, result['colnames']) 368 writer = csv.DictWriter(f, result['colnames'])
507 headers = {} 369 headers = {}
508 for n in result['colnames']: 370 for n in result['colnames']:
509 headers[n] = n 371 headers[n] = n
510 writer.writerow(headers) 372 writer.writerow(headers)
511 else: 373 else:
512 - f = open(self._outfile.url(), 'a') 374 + f = open(outfile, 'a')
513 writer = csv.DictWriter(f, result['colnames']) 375 writer = csv.DictWriter(f, result['colnames'])
514 writer.writerow(result['values']) 376 writer.writerow(result['values'])
515 f.close() 377 f.close()
cscripts/cstsdist.par
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 # ------------------------------------------------------------------------- # 5 # ------------------------------------------------------------------------- #
6 # # 6 # #
7 # File: cstsdist.par # 7 # File: cstsdist.par #
8 -# Version: 1.1.0 # 8 +# Version: 1.2.0 #
9 # # 9 # #
10 # Author: Juergen Knoedlseder # 10 # Author: Juergen Knoedlseder #
11 # Jurgen.Knodlseder@irap.omp.eu # 11 # Jurgen.Knodlseder@irap.omp.eu #
@@ -36,6 +36,8 @@ outfile, f, a, ts.dat,,, &quot;Output Test Statistic distribution file&quot; @@ -36,6 +36,8 @@ outfile, f, a, ts.dat,,, &quot;Output Test Statistic distribution file&quot;
36 ntrials, i, a, 10,,, "Number of trials" 36 ntrials, i, a, 10,,, "Number of trials"
37 ra, r, a, 83.63,0,360, "RA of pointing (deg)" 37 ra, r, a, 83.63,0,360, "RA of pointing (deg)"
38 dec, r, a, 22.01,-90,90, "Dec of pointing (deg)" 38 dec, r, a, 22.01,-90,90, "Dec of pointing (deg)"
  39 +coordsys, s, a, CEL,CEL|GAL,, "Coordinate system (CEL - celestial, GAL - galactic)"
  40 +proj, s, a, TAN,AIT|AZP|CAR|MER|MOL|STG|TAN,, "Projection method"
39 emin, r, a, 0.1,,, "Lower energy limit (TeV)" 41 emin, r, a, 0.1,,, "Lower energy limit (TeV)"
40 emax, r, a, 100.0,,, "Upper energy limit (TeV)" 42 emax, r, a, 100.0,,, "Upper energy limit (TeV)"
41 enumbins, i, a, 0,,, "Number of energy bins (0=unbinned)" 43 enumbins, i, a, 0,,, "Number of energy bins (0=unbinned)"
cscripts/cstsdist.py
@@ -30,13 +30,7 @@ from cscripts import obsutils @@ -30,13 +30,7 @@ from cscripts import obsutils
30 # ============== # 30 # ============== #
31 class cstsdist(ctools.cscript): 31 class cstsdist(ctools.cscript):
32 """ 32 """
33 - Generates Test Statistic distribution for a model.  
34 -  
35 -  
36 - This class implements the TS distribution generation script. It derives  
37 - from the ctools.cscript class which provides support for parameter  
38 - files, command line arguments, and logging. In that way the Python  
39 - script behaves just as a regular ctool. 33 + Generates Test Statistic distribution for a model
40 """ 34 """
41 35
42 # Constructor 36 # Constructor
@@ -45,24 +39,17 @@ class cstsdist(ctools.cscript): @@ -45,24 +39,17 @@ class cstsdist(ctools.cscript):
45 Constructor. 39 Constructor.
46 """ 40 """
47 # Set name 41 # Set name
48 - self._name = "cstsdist"  
49 - self._version = "1.1.0" 42 + self._name = 'cstsdist'
  43 + self._version = '1.2.0'
50 44
51 # Initialise some members 45 # Initialise some members
52 - self._obs = gammalib.GObservations()  
53 - self._pattern = "single"  
54 - self._srcname = ""  
55 - self._enumbins = 0  
56 - self._npix = 0  
57 - self._binsz = 0.0  
58 - self._outfile = gammalib.GFilename("ts.dat")  
59 - self._ntrials = 10  
60 - self._edisp = False  
61 - self._debug = False  
62 - self._log_clients = False  
63 -  
64 - # Initialise application by calling the appropriate class  
65 - # constructor. 46 + self._srcname = ''
  47 + self._log_clients = False
  48 +
  49 + # Initialise observation container from constructor arguments
  50 + self._obs, argv = self._set_input_obs(argv)
  51 +
  52 + # Initialise application by calling the appropriate class constructor
66 self._init_cscript(argv) 53 self._init_cscript(argv)
67 54
68 # Return 55 # Return
@@ -74,165 +61,116 @@ class cstsdist(ctools.cscript): @@ -74,165 +61,116 @@ class cstsdist(ctools.cscript):
74 """ 61 """
75 Get parameters from parfile and setup the observation. 62 Get parameters from parfile and setup the observation.
76 """ 63 """
77 -  
78 # Set observation if not done before 64 # Set observation if not done before
79 if self._obs == None or self._obs.size() == 0: 65 if self._obs == None or self._obs.size() == 0:
80 self._obs = self._get_observations() 66 self._obs = self._get_observations()
81 67
82 - # Check for requested pattern and use above  
83 - # observation parameters to set wobble pattern  
84 - self._pattern = self["pattern"].string()  
85 - if self._pattern == "four": 68 + # Check for requested pattern and use above observation parameters
  69 + # to set wobble pattern
  70 + if self['pattern'].string() == 'four':
86 self._obs = self._set_obs() 71 self._obs = self._set_obs()
87 72
88 # Get source name 73 # Get source name
89 - self._srcname = self["srcname"].string()  
90 -  
91 - # Get number of energy bins  
92 - self._enumbins = self["enumbins"].integer() 74 + self._srcname = self['srcname'].string()
93 75
94 - # Read parameters for binned if requested  
95 - if not self._enumbins == 0:  
96 - self._npix = self["npix"].integer()  
97 - self._binsz = self["binsz"].real() 76 + # Query parameters for binned if requested
  77 + if self['enumbins'].integer() != 0:
  78 + self['npix'].integer()
  79 + self['binsz'].real()
  80 + self['coordsys'].string()
  81 + self['proj'].string()
98 82
99 # Set models if we have none 83 # Set models if we have none
100 if self._obs.models().size() == 0: 84 if self._obs.models().size() == 0:
101 - self._obs.models(self["inmodel"].filename()) 85 + self._obs.models(self['inmodel'].filename())
102 86
103 - # Get other parameters  
104 - self._outfile = self["outfile"].filename()  
105 - self._ntrials = self["ntrials"].integer()  
106 - self._edisp = self["edisp"].boolean()  
107 - self._debug = self["debug"].boolean() 87 + # Query parameters
  88 + self['edisp'].boolean()
  89 + self['ntrials'].integer()
  90 + self['outfile'].filename()
  91 + self['debug'].boolean()
108 92
109 # Write input parameters into logger 93 # Write input parameters into logger
110 if self._logTerse(): 94 if self._logTerse():
111 self._log_parameters() 95 self._log_parameters()
112 - self._log("\n") 96 + self._log('\n')
113 97
114 # Return 98 # Return
115 return 99 return
116 100
117 def _set_obs(self): 101 def _set_obs(self):
118 """ 102 """
119 - Set an observation container. 103 + Set an observation container
120 104
121 - Returns:  
122 - Observation container. 105 + Returns
  106 + -------
  107 + obs : `~gammalib.GObservations`
  108 + Observation container
123 """ 109 """
  110 + # Set duration
  111 + duration = self['tmax'].real() - self['tmin'].real()
124 112
125 # Setup observation definition list 113 # Setup observation definition list
126 - obsdeflist = obsutils.set_obs_patterns(self._pattern,  
127 - ra=self["ra"].real(),  
128 - dec=self["dec"].real(),  
129 - offset=self["offset"].real()) 114 + obsdeflist = obsutils.set_obs_patterns(self['pattern'].string(),
  115 + ra = self['ra'].real(),
  116 + dec = self['dec'].real(),
  117 + offset = self['offset'].real())
130 118
131 # Create list of observations 119 # Create list of observations
132 obs = obsutils.set_obs_list(obsdeflist, 120 obs = obsutils.set_obs_list(obsdeflist,
133 - tstart=self["tmin"].real(),  
134 - duration=self["tmax"].real()-self["tmin"].real(),  
135 - deadc=self["deadc"].real(),  
136 - emin=self["emin"].real(),  
137 - emax=self["emax"].real(),  
138 - rad=self["rad"].real(),  
139 - irf=self["irf"].string(),  
140 - caldb=self["caldb"].string()) 121 + tstart = self['tmin'].real(),
  122 + duration = duration,
  123 + deadc = self['deadc'].real(),
  124 + emin = self['emin'].real(),
  125 + emax = self['emax'].real(),
  126 + rad = self['rad'].real(),
  127 + irf = self['irf'].string(),
  128 + caldb = self['caldb'].string())
141 129
142 # Return observation container 130 # Return observation container
143 return obs 131 return obs
144 132
145 - def _set_models(self, fitspat=False, fitspec=False): 133 + def _trial(self, seed):
146 """ 134 """
147 - Set full model and background model. 135 + Create the TS for a single trial
148 136
149 - Kwargs:  
150 - fitspat: Fit spatial parameter (default: False).  
151 - fitspec: Fit spectral parameters (default: False).  
152 -  
153 - Returns:  
154 - Tuple containing full model and background model.  
155 - """  
156 - # Retrieve full model from observation container  
157 - full_model = self._obs.models().copy()  
158 -  
159 - # Get source model  
160 - model = full_model[self._srcname]  
161 -  
162 - # Check that model has a Prefactor  
163 - if not model.has_par("Prefactor"):  
164 - msg = "Model \""+self._srcname+"\" has no parameter "+\  
165 - "\"Prefactor\". Only spectral models with a "+\  
166 - "\"Prefactor\" parameter are supported."  
167 - raise RuntimeError(msg)  
168 -  
169 - # Fit or fix spatial parameters  
170 - if fitspat:  
171 - if model.has_par("RA"):  
172 - model["RA"].free()  
173 - if model.has_par("DEC"):  
174 - model["DEC"].free()  
175 - if model.has_par("Sigma"):  
176 - model["Sigma"].free()  
177 - if model.has_par("Radius"):  
178 - model["Radius"].free()  
179 - if model.has_par("Width"):  
180 - model["Width"].free()  
181 - else:  
182 - if model.has_par("RA"):  
183 - model["RA"].fix()  
184 - if model.has_par("DEC"):  
185 - model["DEC"].fix()  
186 - if model.has_par("Sigma"):  
187 - model["Sigma"].fix()  
188 - if model.has_par("Radius"):  
189 - model["Radius"].fix()  
190 - if model.has_par("Width"):  
191 - model["Width"].fix()  
192 -  
193 - # Fit or fix spectral parameters  
194 - if fitspec:  
195 - if model.has_par("Index"):  
196 - model["Index"].free()  
197 - if model.has_par("Cutoff"):  
198 - model["Cutoff"].free()  
199 - else:  
200 - if model.has_par("Index"):  
201 - model["Index"].fix()  
202 - if model.has_par("Cutoff"):  
203 - model["Cutoff"].fix()  
204 -  
205 - # Create background model  
206 - bkg_model = full_model.copy()  
207 - bkg_model.remove(self._srcname)  
208 -  
209 - # Return models  
210 - return full_model, bkg_model  
211 -  
212 - def _trial(self, seed, full_model, bkg_model):  
213 - """  
214 - Create the TS for a single trial. 137 + Parameters
  138 + ----------
  139 + seed : int
  140 + Random number generator seed
215 141
216 - Args:  
217 - seed: Random number generator seed  
218 - full_model: Full model  
219 - bkg_model: Background model  
220 -  
221 - Returns: 142 + Returns
  143 + -------
  144 + result : dict
222 Result dictionary 145 Result dictionary
223 """ 146 """
224 # Write header 147 # Write header
225 if self._logExplicit(): 148 if self._logExplicit():
226 - self._log.header2("Trial "+str(seed+1)) 149 + self._log.header2('Trial '+str(seed+1))
  150 +
  151 + # Set default binned parameters
  152 + coordsys = 'CEL'
  153 + proj = 'TAN'
  154 + npix = 0
  155 + binsz = 0.0
  156 +
  157 + # If binned analysis is requested the read the binned parameters
  158 + if self['enumbins'].integer() != 0:
  159 + npix = self['npix'].integer()
  160 + binsz = self['binsz'].real()
  161 + coordsys = self['coordsys'].string()
  162 + proj = self['proj'].string()
227 163
228 # Simulate events 164 # Simulate events
229 sim = obsutils.sim(self._obs, 165 sim = obsutils.sim(self._obs,
230 - nbins=self._enumbins,  
231 - seed=seed,  
232 - binsz=self._binsz,  
233 - npix=self._npix,  
234 - log=self._log_clients,  
235 - debug=self._debug) 166 + nbins = self['enumbins'].integer(),
  167 + seed = seed,
  168 + proj = proj,
  169 + coord = coordsys,
  170 + binsz = binsz,
  171 + npix = npix,
  172 + log = self._log_clients,
  173 + debug = self['debug'].boolean())
236 174
237 # Determine number of events in simulation 175 # Determine number of events in simulation
238 nevents = 0.0 176 nevents = 0.0
@@ -241,110 +179,70 @@ class cstsdist(ctools.cscript): @@ -241,110 +179,70 @@ class cstsdist(ctools.cscript):
241 179
242 # Write simulation results 180 # Write simulation results
243 if self._logExplicit(): 181 if self._logExplicit():
244 - self._log.header3("Simulation")  
245 - self._log.parformat("Number of simulated events") 182 + self._log.header3('Simulation')
  183 + self._log.parformat('Number of simulated events')
246 self._log(nevents) 184 self._log(nevents)
247 - self._log("\n")  
248 -  
249 - # Fit background only  
250 - sim.models(bkg_model)  
251 - like_bgm = obsutils.fit(sim,  
252 - log=self._log_clients,  
253 - debug=self._debug)  
254 - result_bgm = like_bgm.obs().models().copy()  
255 - LogL_bgm = like_bgm.opt().value()  
256 - npred_bgm = like_bgm.obs().npred()  
257 -  
258 - # Write background fit results  
259 - if self._logExplicit():  
260 - self._log.header3("Background model fit")  
261 - self._log.parformat("log likelihood")  
262 - self._log(LogL_bgm)  
263 - self._log("\n")  
264 - self._log.parformat("Number of predicted events")  
265 - self._log(npred_bgm)  
266 - self._log("\n")  
267 - for model in result_bgm:  
268 - self._log.parformat("Model")  
269 - self._log(model.name())  
270 - self._log("\n")  
271 - for par in model:  
272 - self._log(str(par)+"\n")  
273 -  
274 - # Fit background and test source  
275 - sim.models(full_model)  
276 - like_all = obsutils.fit(sim,  
277 - log=self._log_clients,  
278 - debug=self._debug)  
279 - result_all = like_all.obs().models().copy()  
280 - LogL_all = like_all.opt().value()  
281 - npred_all = like_all.obs().npred()  
282 - ts = 2.0*(LogL_bgm-LogL_all)  
283 -  
284 - # Write background and test source fit results 185 + self._log('\n')
  186 +
  187 + # Fit model
  188 + fit = obsutils.fit(sim, log = self._log_clients,
  189 + debug = self['debug'].boolean())
  190 +
  191 + # Get model fitting results
  192 + logL = fit.opt().value()
  193 + npred = fit.obs().npred()
  194 + models = fit.obs().models()
  195 + model = models[self._srcname]
  196 + ts = model.ts()
  197 +
  198 + # Write fit results, either explicit or normal
285 if self._logExplicit(): 199 if self._logExplicit():
286 - self._log.header3("Background and test source model fit")  
287 - self._log.parformat("Test statistics") 200 + self._log.header3('Test source model fit')
  201 + self._log.parformat('Test statistics')
288 self._log(ts) 202 self._log(ts)
289 - self._log("\n")  
290 - self._log.parformat("log likelihood")  
291 - self._log(LogL_all)  
292 - self._log("\n")  
293 - self._log.parformat("Number of predicted events")  
294 - self._log(npred_all)  
295 - self._log("\n")  
296 - for model in result_all:  
297 - self._log.parformat("Model") 203 + self._log('\n')
  204 + self._log.parformat('log likelihood')
  205 + self._log(logL)
  206 + self._log('\n')
  207 + self._log.parformat('Number of predicted events')
  208 + self._log(npred)
  209 + self._log('\n')
  210 + for model in models:
  211 + self._log.parformat('Model')
298 self._log(model.name()) 212 self._log(model.name())
299 - self._log("\n") 213 + self._log('\n')
300 for par in model: 214 for par in model:
301 - self._log(str(par)+"\n")  
302 -  
303 - # Write result  
304 - elif self._logTerse():  
305 - self._log.parformat("Trial "+str(seed))  
306 - self._log("TS=") 215 + self._log(str(par)+'\n')
  216 + elif self._logNormal():
  217 + self._log.parformat('Trial '+str(seed))
  218 + self._log('TS=')
307 self._log(ts) 219 self._log(ts)
308 - self._log(" Prefactor=")  
309 - self._log(result_all[self._srcname]["Prefactor"].value())  
310 - self._log("+/-")  
311 - self._log(result_all[self._srcname]["Prefactor"].error())  
312 - self._log("\n") 220 + self._log(' Prefactor=')
  221 + self._log(model['Prefactor'].value())
  222 + self._log('+/-')
  223 + self._log(model['Prefactor'].error())
  224 + self._log('\n')
313 225
314 # Initialise results 226 # Initialise results
315 colnames = [] 227 colnames = []
316 values = {} 228 values = {}
317 229
318 # Set TS value 230 # Set TS value
319 - colnames.append("TS")  
320 - values["TS"] = ts  
321 -  
322 - # Set logL for background fit  
323 - colnames.append("LogL_bgm")  
324 - values["LogL_bgm"] = LogL_bgm  
325 -  
326 - # Set logL for full fit  
327 - colnames.append("LogL_all")  
328 - values["LogL_all"] = LogL_all 231 + colnames.append('TS')
  232 + values['TS'] = ts
329 233
330 # Set Nevents 234 # Set Nevents
331 - colnames.append("Nevents")  
332 - values["Nevents"] = nevents  
333 -  
334 - # Set Npred for background fit  
335 - colnames.append("Npred_bkg")  
336 - values["Npred_bkg"] = npred_bgm 235 + colnames.append('Nevents')
  236 + values['Nevents'] = nevents
337 237
338 - # Set Npred for full fit  
339 - colnames.append("Npred_all")  
340 - values["Npred_all"] = npred_all 238 + # Set Npred
  239 + colnames.append('Npred')
  240 + values['Npred'] = npred
341 241
342 # Gather free full fit parameters 242 # Gather free full fit parameters
343 - for i in range(result_all.size()):  
344 - model = result_all[i] 243 + for model in models:
345 model_name = model.name() 244 model_name = model.name()
346 - for k in range(model.size()):  
347 - par = model[k] 245 + for par in model:
348 if par.is_free(): 246 if par.is_free():
349 247
350 # Set parameter name 248 # Set parameter name
@@ -355,7 +253,7 @@ class cstsdist(ctools.cscript): @@ -355,7 +253,7 @@ class cstsdist(ctools.cscript):
355 values[name] = par.value() 253 values[name] = par.value()
356 254
357 # Append error 255 # Append error
358 - name = "Unc_"+name 256 + name = 'e_'+name
359 colnames.append(name) 257 colnames.append(name)
360 values[name] = par.error() 258 values[name] = par.error()
361 259
@@ -369,7 +267,7 @@ class cstsdist(ctools.cscript): @@ -369,7 +267,7 @@ class cstsdist(ctools.cscript):
369 # Public methods 267 # Public methods
370 def run(self): 268 def run(self):
371 """ 269 """
372 - Run the script. 270 + Run the script
373 """ 271 """
374 # Switch screen logging on in debug mode 272 # Switch screen logging on in debug mode
375 if self._logDebug(): 273 if self._logDebug():
@@ -378,55 +276,51 @@ class cstsdist(ctools.cscript): @@ -378,55 +276,51 @@ class cstsdist(ctools.cscript):
378 # Get parameters 276 # Get parameters
379 self._get_parameters() 277 self._get_parameters()
380 278
381 - # Initialise models  
382 - full_model, bkg_model = self._set_models() 279 + # Set models for this observation
  280 + self.models(obsutils.set_ts_model(self._obs.models(), self._srcname))
383 281
384 # Write models into logger 282 # Write models into logger
385 if self._logTerse(): 283 if self._logTerse():
386 - self._log("\n")  
387 - self._log.header1("Models")  
388 - self._log.header2("Background model")  
389 - self._log(str(bkg_model))  
390 - self._log("\n\n")  
391 - self._log.header2("Full model")  
392 - self._log(str(full_model))  
393 - self._log("\n") 284 + self._log('\n')
  285 + self._log.header1('Models')
  286 + self._log(str(self._obs.models()))
  287 + self._log('\n')
394 288
395 # Write observation into logger 289 # Write observation into logger
396 if self._logTerse(): 290 if self._logTerse():
397 - self._log("\n")  
398 - self._log.header1("Observation") 291 + self._log('\n')
  292 + self._log.header1(gammalib.number('Observation',len(self._obs)))
399 self._log(str(self._obs)) 293 self._log(str(self._obs))
400 - self._log("\n")  
401 -  
402 - # Write models into logger  
403 - if self._logTerse():  
404 - self._log("\n")  
405 - self._log.header1("Test model")  
406 - self._log(str(full_model))  
407 - self._log("\n") 294 + self._log('\n')
  295 + if self._logExplicit():
  296 + for obs in self._obs:
  297 + self._log(str(obs))
  298 + self._log('\n')
408 299
409 # Write header 300 # Write header
410 if self._logTerse(): 301 if self._logTerse():
411 - self._log("\n")  
412 - self._log.header1("Generate TS distribution") 302 + self._log('\n')
  303 + self._log.header1('Generate TS distribution')
413 304
414 # Loop over trials 305 # Loop over trials
415 - for seed in range(self._ntrials): 306 + for seed in range(self['ntrials'].integer()):
416 307
417 # Make a trial 308 # Make a trial
418 - result = self._trial(seed, full_model, bkg_model) 309 + result = self._trial(seed)
  310 +
  311 + # Set output filename
  312 + outfile = self['outfile'].filename().url()
419 313
420 # Write out result immediately 314 # Write out result immediately
421 if seed == 0: 315 if seed == 0:
422 - f = open(self._outfile.url(), 'w') 316 + f = open(outfile, 'w')
423 writer = csv.DictWriter(f, result['colnames']) 317 writer = csv.DictWriter(f, result['colnames'])
424 headers = {} 318 headers = {}
425 for n in result['colnames']: 319 for n in result['colnames']:
426 headers[n] = n 320 headers[n] = n
427 writer.writerow(headers) 321 writer.writerow(headers)
428 else: 322 else:
429 - f = open(self._outfile.url(), 'a') 323 + f = open(outfile, 'a')
430 writer = csv.DictWriter(f, result['colnames']) 324 writer = csv.DictWriter(f, result['colnames'])
431 writer.writerow(result['values']) 325 writer.writerow(result['values'])
432 f.close() 326 f.close()
@@ -436,17 +330,22 @@ class cstsdist(ctools.cscript): @@ -436,17 +330,22 @@ class cstsdist(ctools.cscript):
436 330
437 def models(self, models): 331 def models(self, models):
438 """ 332 """
439 - Set model. 333 + Set model
  334 +
  335 + Parameters
  336 + ----------
  337 + models : `~gammalib.GModels`
  338 + Model container
440 """ 339 """
441 # Copy models 340 # Copy models
442 - self._obs.models(models.copy()) 341 + self._obs.models(models)
443 342
444 # Return 343 # Return
445 return 344 return
446 345
447 def execute(self): 346 def execute(self):
448 """ 347 """
449 - Execute the script. 348 + Execute the script
450 """ 349 """
451 # Open logfile 350 # Open logfile
452 self.logFileOpen() 351 self.logFileOpen()
cscripts/obsutils.py
1 # ========================================================================== 1 # ==========================================================================
2 -# CTA observation handling support functions. 2 +# CTA observation handling support functions
3 # 3 #
4 # Copyright (C) 2011-2016 Juergen Knoedlseder 4 # Copyright (C) 2011-2016 Juergen Knoedlseder
5 # 5 #
@@ -30,6 +30,14 @@ def sim(obs, log=False, debug=False, chatter=2, edisp=False, seed=0, @@ -30,6 +30,14 @@ def sim(obs, log=False, debug=False, chatter=2, edisp=False, seed=0,
30 """ 30 """
31 Simulate events for all observations in the container 31 Simulate events for all observations in the container
32 32
  33 + Simulate events for all observations using ctobssim. If the number of
  34 + energy bins is positive, the events are then binned in a counts cube
  35 + using ctbin. If multiple observations are simulated, the counts cube is
  36 + a stacked cube and the corresponding response cubes are computed using
  37 + ctexpcube, ctpsfcube, ctbkgcube and optionally ctedispcube. The response
  38 + cubes are attached to the first observation in the container, which
  39 + normally is the observation with the counts cube.
  40 +
33 Parameters 41 Parameters
34 ---------- 42 ----------
35 obs : `~gammalib.GObservations` 43 obs : `~gammalib.GObservations`
@@ -42,7 +50,7 @@ def sim(obs, log=False, debug=False, chatter=2, edisp=False, seed=0, @@ -42,7 +50,7 @@ def sim(obs, log=False, debug=False, chatter=2, edisp=False, seed=0,
42 Chatter level 50 Chatter level
43 edisp : bool, optional 51 edisp : bool, optional
44 Apply energy dispersion? 52 Apply energy dispersion?
45 - seed : int, integer 53 + seed : int, optional
46 Seed value for simulations 54 Seed value for simulations
47 emin : float, optional 55 emin : float, optional
48 Minimum energy of counts cube for binned (TeV) 56 Minimum energy of counts cube for binned (TeV)
@@ -64,7 +72,6 @@ def sim(obs, log=False, debug=False, chatter=2, edisp=False, seed=0, @@ -64,7 +72,6 @@ def sim(obs, log=False, debug=False, chatter=2, edisp=False, seed=0,
64 obs : `~gammalib.GObservations` 72 obs : `~gammalib.GObservations`
65 Observation container filled with simulated events 73 Observation container filled with simulated events
66 """ 74 """
67 -  
68 # Allocate ctobssim application and set parameters 75 # Allocate ctobssim application and set parameters
69 sim = ctools.ctobssim(obs) 76 sim = ctools.ctobssim(obs)
70 sim['seed'] = seed 77 sim['seed'] = seed
@@ -125,6 +132,48 @@ def sim(obs, log=False, debug=False, chatter=2, edisp=False, seed=0, @@ -125,6 +132,48 @@ def sim(obs, log=False, debug=False, chatter=2, edisp=False, seed=0,
125 # the container and bin the events in counts maps 132 # the container and bin the events in counts maps
126 bin.run() 133 bin.run()
127 134
  135 + # If we have multiple input observations then create stacked response
  136 + # cubes and append them to the observation
  137 + if len(sim.obs()) > 1:
  138 +
  139 + # First get the cube. We need this because ctbin builds a cube on
  140 + # the fly. We should avoid this and make the cube part of ctbin
  141 + cube = bin.cube()
  142 +
  143 + # Get xref, yref, coordsys and proj from counts cube
  144 + _xref = cube.counts().projection().crval(0)
  145 + _yref = cube.counts().projection().crval(1)
  146 + _coordsys = cube.counts().projection().coordsys()
  147 + _proj = cube.counts().projection().code()
  148 +
  149 + # Kluge: map "EQU" to "CEL" coordinate system (not sure why the
  150 + # GSkyProjection::coordsys returns "EQU" instead of "CEL"
  151 + if _coordsys == 'EQU':
  152 + _coordsys = 'CEL'
  153 +
  154 + # Get stacked response
  155 + response = get_stacked_response(sim.obs(), _xref, _yref,
  156 + binsz=binsz, nxpix=npix, nypix=npix,
  157 + emin=emin, emax=emax, enumbins=nbins,
  158 + edisp=edisp,
  159 + coordsys=_coordsys, proj=_proj,
  160 + log=log, debug=debug,
  161 + chatter=chatter)
  162 +
  163 + # Set stacked response
  164 + if edisp:
  165 + bin.obs()[0].response(response['expcube'],
  166 + response['psfcube'],
  167 + response['edispcube'],
  168 + response['bkgcube'])
  169 + else:
  170 + bin.obs()[0].response(response['expcube'],
  171 + response['psfcube'],
  172 + response['bkgcube'])
  173 +
  174 + # Set new models
  175 + bin.obs().models(response['models'])
  176 +
128 # Make a deep copy of the observation that will be returned 177 # Make a deep copy of the observation that will be returned
129 # (the ctbin object will go out of scope one the function is 178 # (the ctbin object will go out of scope one the function is
130 # left) 179 # left)
@@ -149,33 +198,36 @@ def sim(obs, log=False, debug=False, chatter=2, edisp=False, seed=0, @@ -149,33 +198,36 @@ def sim(obs, log=False, debug=False, chatter=2, edisp=False, seed=0,
149 # ================ # 198 # ================ #
150 def fit(obs, log=False, debug=False, chatter=2, edisp=False): 199 def fit(obs, log=False, debug=False, chatter=2, edisp=False):
151 """ 200 """
152 - Perform maximum likelihood fitting of observations in the container.  
153 -  
154 - Parameters:  
155 - obs - Observation container  
156 - Keywords:  
157 - log - Create log file(s)  
158 - debug - Create screen dump  
159 - chatter - Chatter level  
160 - edisp - Apply energy dispersion? 201 + Perform maximum likelihood fitting of observations in the container
  202 +
  203 + Parameters
  204 + ----------
  205 + obs : `~gammalib.GObservations`
  206 + Observation container
  207 + log : bool, optional
  208 + Create log file(s)
  209 + debug : bool, optional
  210 + Create console dump?
  211 + chatter : int, optional
  212 + Chatter level
  213 + edisp : bool, optional
  214 + Apply energy dispersion?
  215 +
  216 + Returns
  217 + -------
  218 + like : `~ctools.ctlike`
  219 + ctlike application
161 """ 220 """
162 - # Allocate ctlike application 221 + # Allocate ctlike application and set parameters
163 like = ctools.ctlike(obs) 222 like = ctools.ctlike(obs)
  223 + like['debug'] = debug
  224 + like['chatter'] = chatter
  225 + like['edisp'] = edisp
164 226
165 # Optionally open the log file 227 # Optionally open the log file
166 if log: 228 if log:
167 like.logFileOpen() 229 like.logFileOpen()
168 230
169 - # Optionally switch-on debugging model  
170 - if debug:  
171 - like["debug"] = True  
172 -  
173 - # Set chatter level  
174 - like["chatter"] = chatter  
175 -  
176 - # Optionally apply energy dispersion  
177 - like["edisp"] = edisp  
178 -  
179 # Run ctlike application. 231 # Run ctlike application.
180 like.run() 232 like.run()
181 233
@@ -188,34 +240,36 @@ def fit(obs, log=False, debug=False, chatter=2, edisp=False): @@ -188,34 +240,36 @@ def fit(obs, log=False, debug=False, chatter=2, edisp=False):
188 # ============================================================== # 240 # ============================================================== #
189 def cterror(obs, srcname, log=False, debug=False, chatter=2): 241 def cterror(obs, srcname, log=False, debug=False, chatter=2):
190 """ 242 """
191 - Perform maximum likelihood fitting of observations in the container.  
192 -  
193 - Parameters:  
194 - obs - Observation container  
195 - srcname - Source name  
196 - Keywords:  
197 - log - Create log file(s)  
198 - debug - Create screen dump  
199 - chatter - Chatter level  
200 - edisp - Apply energy dispersion? 243 + Fit observations and determine errors using likelihood profile
  244 +
  245 + Parameters
  246 + ----------
  247 + obs : `~gammalib.GObservations`
  248 + Observation container
  249 + srcname : str
  250 + Source name
  251 + log : bool, optional
  252 + Create log file(s)
  253 + debug : bool, optional
  254 + Create console dump?
  255 + chatter : int, optional
  256 + Chatter level
  257 +
  258 + Returns
  259 + -------
  260 + error : `~ctools.cterror`
  261 + cterror application
201 """ 262 """
202 - # Allocate cterror application 263 + # Allocate cterror application and set parameters
203 error = ctools.cterror(obs) 264 error = ctools.cterror(obs)
204 -  
205 - # Set cterror parameters  
206 - error["srcname"] = srcname 265 + error['srcname'] = srcname
  266 + error['debug'] = debug
  267 + error['chatter'] = chatter
207 268
208 # Optionally open the log file 269 # Optionally open the log file
209 if log: 270 if log:
210 error.logFileOpen() 271 error.logFileOpen()
211 272
212 - # Optionally switch-on debugging model  
213 - if debug:  
214 - error["debug"] = True  
215 -  
216 - # Set chatter level  
217 - error["chatter"] = chatter  
218 -  
219 # Run cterror application. 273 # Run cterror application.
220 error.run() 274 error.run()
221 275
@@ -226,24 +280,39 @@ def cterror(obs, srcname, log=False, debug=False, chatter=2): @@ -226,24 +280,39 @@ def cterror(obs, srcname, log=False, debug=False, chatter=2):
226 # ================= # 280 # ================= #
227 # Create counts map # 281 # Create counts map #
228 # ================= # 282 # ================= #
229 -def cntmap(obs, proj="TAN", coord="GAL", xval=0.0, yval=0.0, \ 283 +def cntmap(obs, proj='TAN', coord='GAL', xval=0.0, yval=0.0, \
230 binsz=0.05, nxpix=200, nypix=200, \ 284 binsz=0.05, nxpix=200, nypix=200, \
231 - outname="cntmap.fits"): 285 + outname='cntmap.fits'):
232 """ 286 """
233 - Creates a counts map by combining the events of all observations.  
234 - The counts map will be a summed map over all energies.  
235 -  
236 - Parameters:  
237 - obs - Observation container  
238 - Keywords:  
239 - proj - Projection type (e.g. TAN, CAR, STG, ...) (default: TAN)  
240 - coord - Coordinate type (GAL, CEL) (default: GAL)  
241 - xval - Reference longitude value [deg] (default: 0.0)  
242 - yval - Reference latitude value [deg] (default: 0.0)  
243 - binsz - Pixel size [deg/pixel] (default: 0.05)  
244 - nxpix - Number of pixels in X direction (default: 200)  
245 - nypix - Number of pixels in Y direction (default: 200)  
246 - outname - Counts map FITS filename (default: cntmap.fits) 287 + Creates a counts map by combining the events of all observations
  288 +
  289 + The counts map will be a summed map over all energies
  290 +
  291 + Parameters
  292 + ----------
  293 + obs : `~gammalib.GObservations`
  294 + Observation container without events
  295 + proj : str, optional
  296 + Projection for binned simulation
  297 + coord : str, optional
  298 + Coordinate system for binned simulation
  299 + xval : float, optional
  300 + Counts map centre of X direction (deg)
  301 + yval : float, optional
  302 + Counts map centre of Y direction (deg)
  303 + binsz : float, optional
  304 + Pixel size for binned simulation (deg/pixel)
  305 + nxpix : int, optional
  306 + Number of pixels in X direction
  307 + nypix : int, optional
  308 + Number of pixels in Y direction
  309 + outname : str, optional
  310 + Counts map file name
  311 +
  312 + Returns
  313 + -------
  314 + map : `~gammalib.GSkyMap`
  315 + Counts map
247 """ 316 """
248 # Allocate counts map 317 # Allocate counts map
249 map = gammalib.GSkyMap(proj, coord, xval, yval, -binsz, binsz, nxpix, nypix, 1) 318 map = gammalib.GSkyMap(proj, coord, xval, yval, -binsz, binsz, nxpix, nypix, 1)
@@ -276,26 +345,42 @@ def cntmap(obs, proj=&quot;TAN&quot;, coord=&quot;GAL&quot;, xval=0.0, yval=0.0, \ @@ -276,26 +345,42 @@ def cntmap(obs, proj=&quot;TAN&quot;, coord=&quot;GAL&quot;, xval=0.0, yval=0.0, \
276 # ================ # 345 # ================ #
277 # Create model map # 346 # Create model map #
278 # ================ # 347 # ================ #
279 -def modmap(obs, eref=0.1, proj="TAN", coord="GAL", xval=0.0, yval=0.0, \ 348 +def modmap(obs, eref=0.1, proj='TAN', coord='GAL', xval=0.0, yval=0.0, \
280 binsz=0.05, nxpix=200, nypix=200, \ 349 binsz=0.05, nxpix=200, nypix=200, \
281 - outname="modmap.fits"): 350 + outname='modmap.fits'):
282 """ 351 """
283 - Make model map for a given reference energy by combining all observations. 352 + Make model map for a given reference energy by combining all observations
  353 +
284 The model map will be evaluated for a given reference energy 'eref' and will 354 The model map will be evaluated for a given reference energy 'eref' and will
285 be given in units of [counts/(sr MeV s)]. 355 be given in units of [counts/(sr MeV s)].
286 356
287 - Parameters:  
288 - obs - Observation container  
289 - Keywords:  
290 - eref - Reference energy for which model is created [TeV] (default: 0.1)  
291 - proj - Projection type (e.g. TAN, CAR, STG, ...) (default: TAN)  
292 - coord - Coordinate type (GAL, CEL) (default: GAL)  
293 - xval - Reference longitude value [deg] (default: 0.0)  
294 - yval - Reference latitude value [deg] (default: 0.0)  
295 - binsz - Pixel size [deg/pixel] (default: 0.05)  
296 - nxpix - Number of pixels in X direction (default: 200)  
297 - nypix - Number of pixels in Y direction (default: 200)  
298 - outname - Model map FITS filename (default: modmap.fits) 357 + Parameters
  358 + ----------
  359 + obs : `~gammalib.GObservations`
  360 + Observation container without events
  361 + eref : float, optional
  362 + Reference energy for which model is created (TeV)
  363 + proj : str, optional
  364 + Projection for binned simulation
  365 + coord : str, optional
  366 + Coordinate system for binned simulation
  367 + xval : float, optional
  368 + Counts map centre of X direction (deg)
  369 + yval : float, optional
  370 + Counts map centre of Y direction (deg)
  371 + binsz : float, optional
  372 + Pixel size for binned simulation (deg/pixel)
  373 + nxpix : int, optional
  374 + Number of pixels in X direction
  375 + nypix : int, optional
  376 + Number of pixels in Y direction
  377 + outname : str, optional
  378 + Model map file name
  379 +
  380 + Returns
  381 + -------
  382 + map : `~gammalib.GSkyMap`
  383 + Model map
299 """ 384 """
300 # Allocate model map 385 # Allocate model map
301 map = gammalib.GSkyMap(proj, coord, xval, yval, -binsz, binsz, nxpix, nypix, 1) 386 map = gammalib.GSkyMap(proj, coord, xval, yval, -binsz, binsz, nxpix, nypix, 1)
@@ -338,53 +423,46 @@ def modmap(obs, eref=0.1, proj=&quot;TAN&quot;, coord=&quot;GAL&quot;, xval=0.0, yval=0.0, \ @@ -338,53 +423,46 @@ def modmap(obs, eref=0.1, proj=&quot;TAN&quot;, coord=&quot;GAL&quot;, xval=0.0, yval=0.0, \
338 return map 423 return map
339 424
340 425
341 -# ===================================== #  
342 -# Set one CTA observation (old version) #  
343 -# ===================================== #  
344 -def set(pntdir, tstart=0.0, duration=1800.0, deadc=0.95, \  
345 - emin=0.1, emax=100.0, rad=5.0, \  
346 - irf="South_50h", caldb="prod2"):  
347 - """  
348 - Obsolete function, use set_obs instead.  
349 - """  
350 - # Print warning  
351 - print("Warning: obsutils.set is obsolete, use obsutils.set_obs instead.")  
352 -  
353 - # Call new function  
354 - obs_cta = set_obs(pntdir, tstart=tstart, duration=duration, deadc=deadc, \  
355 - emin=emin, emax=emax, rad=rad, \  
356 - irf=irf, caldb=caldb)  
357 -  
358 - # Return CTA observation  
359 - return obs_cta  
360 -  
361 -  
362 # ======================= # 426 # ======================= #
363 # Set one CTA observation # 427 # Set one CTA observation #
364 # ======================= # 428 # ======================= #
365 def set_obs(pntdir, tstart=0.0, duration=1800.0, deadc=0.95, \ 429 def set_obs(pntdir, tstart=0.0, duration=1800.0, deadc=0.95, \
366 emin=0.1, emax=100.0, rad=5.0, \ 430 emin=0.1, emax=100.0, rad=5.0, \
367 - irf="South_50h", caldb="prod2", id="000000"): 431 + irf='South_50h', caldb='prod2', id='000000'):
368 """ 432 """
369 - Set a single CTA observation. 433 + Set a single CTA observation
370 434
371 The function sets a single CTA observation containing an empty CTA 435 The function sets a single CTA observation containing an empty CTA
372 event list. By looping over this function you can add CTA observations 436 event list. By looping over this function you can add CTA observations
373 to the observation container. 437 to the observation container.
374 438
375 - Args:  
376 - pntdir: Pointing direction [GSkyDir]  
377 -  
378 - Kwargs:  
379 - tstart: Start time (seconds) (default: 0.0)  
380 - duration: Duration of observation (seconds) (default: 1800.0)  
381 - deadc: Deadtime correction factor (default: 0.95)  
382 - emin: Minimum event energy (TeV) (default: 0.1)  
383 - emax: Maximum event energy (TeV) (default: 100.0)  
384 - rad: ROI radius used for analysis (deg) (default: 5.0)  
385 - irf: Instrument response function (default: "South_50h")  
386 - caldb: Calibration database path (default: "prod2")  
387 - id: Run identifier (default: "000000") 439 + Parameters
  440 + ----------
  441 + pntdir : `~gammalib.GSkyDir`
  442 + Pointing direction
  443 + tstart : float, optional
  444 + Start time (seconds)
  445 + duration : float, optional
  446 + Duration of observation (seconds)
  447 + deadc : float, optional
  448 + Deadtime correction factor
  449 + emin : float, optional
  450 + Minimum event energy (TeV)
  451 + emax : float, optional
  452 + Maximum event energy (TeV)
  453 + rad : float, optional
  454 + ROI radius used for analysis (deg)
  455 + irf : str, optional
  456 + Instrument response function
  457 + caldb : str, optional
  458 + Calibration database path
  459 + id : str, optional
  460 + Observation identifier
  461 +
  462 + Returns
  463 + -------
  464 + obs : `~gammalib.GCTAObservation`
  465 + CTA observation
388 """ 466 """
389 # Allocate CTA observation 467 # Allocate CTA observation
390 obs_cta = gammalib.GCTAObservation() 468 obs_cta = gammalib.GCTAObservation()
@@ -394,7 +472,7 @@ def set_obs(pntdir, tstart=0.0, duration=1800.0, deadc=0.95, \ @@ -394,7 +472,7 @@ def set_obs(pntdir, tstart=0.0, duration=1800.0, deadc=0.95, \
394 if (gammalib.dir_exists(caldb)): 472 if (gammalib.dir_exists(caldb)):
395 db.rootdir(caldb) 473 db.rootdir(caldb)
396 else: 474 else:
397 - db.open("cta", caldb) 475 + db.open('cta', caldb)
398 476
399 # Set pointing direction 477 # Set pointing direction
400 pnt = gammalib.GCTAPointing() 478 pnt = gammalib.GCTAPointing()
@@ -413,8 +491,8 @@ def set_obs(pntdir, tstart=0.0, duration=1800.0, deadc=0.95, \ @@ -413,8 +491,8 @@ def set_obs(pntdir, tstart=0.0, duration=1800.0, deadc=0.95, \
413 gti.append(gammalib.GTime(tstart), gammalib.GTime(tstart+duration)) 491 gti.append(gammalib.GTime(tstart), gammalib.GTime(tstart+duration))
414 492
415 # Set energy boundaries 493 # Set energy boundaries
416 - ebounds = gammalib.GEbounds(gammalib.GEnergy(emin, "TeV"),  
417 - gammalib.GEnergy(emax, "TeV")) 494 + ebounds = gammalib.GEbounds(gammalib.GEnergy(emin, 'TeV'),
  495 + gammalib.GEnergy(emax, 'TeV'))
418 496
419 # Allocate event list 497 # Allocate event list
420 events = gammalib.GCTAEventList() 498 events = gammalib.GCTAEventList()
@@ -440,10 +518,11 @@ def set_obs(pntdir, tstart=0.0, duration=1800.0, deadc=0.95, \ @@ -440,10 +518,11 @@ def set_obs(pntdir, tstart=0.0, duration=1800.0, deadc=0.95, \
440 # Set list of CTA observations # 518 # Set list of CTA observations #
441 # ============================ # 519 # ============================ #
442 def set_obs_list(obsdeflist, tstart=0.0, duration=1800.0, deadc=0.95, \ 520 def set_obs_list(obsdeflist, tstart=0.0, duration=1800.0, deadc=0.95, \
443 - emin=0.1, emax=100.0, rad=5.0, \  
444 - irf="South_50h", caldb="prod2"): 521 + emin=0.1, emax=100.0, rad=5.0, \
  522 + irf='South_50h', caldb='prod2'):
445 """ 523 """
446 - Returns an observation container filled with a list of CTA observations. 524 + Returns an observation container filled with a list of CTA observations
  525 +
447 The list is defined by the obsdeflist parameter which is a dictionnary 526 The list is defined by the obsdeflist parameter which is a dictionnary
448 containing the mandatory keywords 'ra' and 'dec' that specify the 527 containing the mandatory keywords 'ra' and 'dec' that specify the
449 pointing direction for a given observation. Optional keyword give control 528 pointing direction for a given observation. Optional keyword give control
@@ -451,27 +530,31 @@ def set_obs_list(obsdeflist, tstart=0.0, duration=1800.0, deadc=0.95, \ @@ -451,27 +530,31 @@ def set_obs_list(obsdeflist, tstart=0.0, duration=1800.0, deadc=0.95, \
451 energy range, etc. If an optional keyword is not specified, the function 530 energy range, etc. If an optional keyword is not specified, the function
452 keyword is used instead. 531 keyword is used instead.
453 532
454 - Parameters:  
455 - obsdeflist - Observation definition list [{'ra': x.xx, 'dec': x.xx}]  
456 - The directory can take the following optional keywords:  
457 - - duration: Duration of CTA observation [seconds]  
458 - - deadc: Deadtime correction factor  
459 - - emin: Minimum event energy [TeV]  
460 - - emax: Maximum event energy [TeV]  
461 - - rad: ROI radius used for analysis [deg]  
462 - - irf: Instrument response function  
463 - - caldb: Calibration database path  
464 - Optional keywords overwrite keywords specified in the  
465 - function call.  
466 - Keywords:  
467 - tstart - Start time [seconds] (default: 0.0)  
468 - duration - Duration of one CTA observation [seconds] (default: 1800.0)  
469 - deadc - Deadtime correction factor (default: 0.95)  
470 - emin - Minimum event energy [TeV] (default: 0.1)  
471 - emax - Maximum event energy [TeV] (default: 100.0)  
472 - rad - ROI radius used for analysis [deg] (default: 5.0)  
473 - irf - Instrument response function (default: cta_dummy_irf)  
474 - caldb - Calibration database path (default: "dummy") 533 + Parameters
  534 + ----------
  535 + obsdeflist : list of dict
  536 + Observation definition list
  537 + tstart : float, optional
  538 + Start time (seconds)
  539 + duration : float, optional
  540 + Duration of observation (seconds)
  541 + deadc : float, optional
  542 + Deadtime correction factor
  543 + emin : float, optional
  544 + Minimum event energy (TeV)
  545 + emax : float, optional
  546 + Maximum event energy (TeV)
  547 + rad : float, optional
  548 + ROI radius used for analysis (deg)
  549 + irf : str, optional
  550 + Instrument response function
  551 + caldb : str, optional
  552 + Calibration database path
  553 +
  554 + Returns
  555 + -------
  556 + obs : `~gammalib.GObservations`
  557 + Observation container filled with CTA observation
475 """ 558 """
476 # Initialise empty observation container 559 # Initialise empty observation container
477 obs = gammalib.GObservations() 560 obs = gammalib.GObservations()
@@ -530,7 +613,7 @@ def set_obs_list(obsdeflist, tstart=0.0, duration=1800.0, deadc=0.95, \ @@ -530,7 +613,7 @@ def set_obs_list(obsdeflist, tstart=0.0, duration=1800.0, deadc=0.95, \
530 obs_irf = irf 613 obs_irf = irf
531 614
532 # Generate identifier string 615 # Generate identifier string
533 - id = "%6.6d" % obs_id 616 + id = '%6.6d' % obs_id
534 617
535 # Set CTA observation 618 # Set CTA observation
536 obs_cta = set_obs(pntdir, tstart=obs_start, duration=obs_duration, \ 619 obs_cta = set_obs(pntdir, tstart=obs_start, duration=obs_duration, \
@@ -553,26 +636,38 @@ def set_obs_list(obsdeflist, tstart=0.0, duration=1800.0, deadc=0.95, \ @@ -553,26 +636,38 @@ def set_obs_list(obsdeflist, tstart=0.0, duration=1800.0, deadc=0.95, \
553 # ============================ # 636 # ============================ #
554 def set_obs_patterns(pattern, ra=83.6331, dec=22.0145, offset=1.5): 637 def set_obs_patterns(pattern, ra=83.6331, dec=22.0145, offset=1.5):
555 """ 638 """
556 - Sets a number of standard patterns.  
557 -  
558 - Parameters:  
559 - pattern - Observation pattern. Possible options are:  
560 - - "single": single pointing  
561 - - "four": four pointings 'offset' around pattern centre 639 + Sets a number of standard patterns
  640 +
  641 + Parameters
  642 + ----------
  643 + pattern : str
  644 + Observation pattern ("single", "four")
  645 + ra : float, optional
  646 + Right Ascension of pattern centre (deg)
  647 + dec : float, optional
  648 + Declination of pattern centre (deg)
  649 + offset : float, optional
  650 + Offset from pattern centre (deg)
562 651
563 - Keywords:  
564 - ra - Right Ascension of pattern centre [deg] (default: 83.6331)  
565 - dec - Declination of pattern centre [deg] (default: 22.0145)  
566 - offset - Offset from pattern centre [deg] (default: 1.5) 652 + Returns
  653 + -------
  654 + obsdeflist : list
  655 + Observation definition list
567 """ 656 """
568 # Initialise observation definition list 657 # Initialise observation definition list
569 obsdeflist = [] 658 obsdeflist = []
570 659
571 - # Add patterns  
572 - if pattern == "single": 660 + # If the pattern is a single observation then append the Right Ascension
  661 + # and Declination to the observation definition list
  662 + if pattern == 'single':
573 obsdef = {'ra': ra, 'dec': dec} 663 obsdef = {'ra': ra, 'dec': dec}
574 obsdeflist.append(obsdef) 664 obsdeflist.append(obsdef)
575 - elif pattern == "four": 665 +
  666 + # ... otherwise, if the pattern is four observations then append four
  667 + # observations offset by a certain amount from the pattern centre to the
  668 + # observation definition list
  669 + elif pattern == 'four':
  670 +
576 # Set pattern centre 671 # Set pattern centre
577 centre = gammalib.GSkyDir() 672 centre = gammalib.GSkyDir()
578 centre.radec_deg(ra, dec) 673 centre.radec_deg(ra, dec)
@@ -582,8 +677,232 @@ def set_obs_patterns(pattern, ra=83.6331, dec=22.0145, offset=1.5): @@ -582,8 +677,232 @@ def set_obs_patterns(pattern, ra=83.6331, dec=22.0145, offset=1.5):
582 pntdir = centre.copy() 677 pntdir = centre.copy()
583 pntdir.rotate_deg(phi, offset) 678 pntdir.rotate_deg(phi, offset)
584 obsdeflist.append({'ra': pntdir.ra_deg(), 'dec': pntdir.dec_deg()}) 679 obsdeflist.append({'ra': pntdir.ra_deg(), 'dec': pntdir.dec_deg()})
  680 +
  681 + # ... otherwise we have an unknown pattern
585 else: 682 else:
586 - print("Warning: Observation pattern '"+str(pattern)+"' not recognized.") 683 + print('Warning: Observation pattern "'+str(pattern)+'" not recognized.')
587 684
588 # Return observation definition list 685 # Return observation definition list
589 return obsdeflist 686 return obsdeflist
  687 +
  688 +
  689 +# ======================== #
  690 +# Set model for TS fitting #
  691 +# ======================== #
  692 +def set_ts_model(models, srcname, ra=None, dec=None, fitspat=False, fitspec=False):
  693 + """
  694 + Set model for TS fitting
  695 +
  696 + Parameters
  697 + ----------
  698 + models : `~gammalib.GModels`
  699 + Input model container
  700 + srcname : str
  701 + Test source name
  702 + ra : float, optional
  703 + Right Ascension of test source (deg)
  704 + dec : float, optional
  705 + Declination of test source (deg)
  706 + fitspat : bool, optional
  707 + Fit spatial parameter?
  708 + fitspec : bool, optional
  709 + Fit spectral parameters?
  710 +
  711 + Returns
  712 + -------
  713 + model : `~gammalib.GModels`
  714 + Model container for TS fitting
  715 + """
  716 + # Create a clone of the input model
  717 + outmodels = models.copy()
  718 +
  719 + # Disable TS computation for all model components (will enable the
  720 + # test source later)
  721 + for model in outmodels:
  722 + model.tscalc(False)
  723 +
  724 + # Get source model and enable TS computation
  725 + model = outmodels[srcname]
  726 + model.tscalc(True)
  727 +
  728 + # If source model has no "Prefactor" parameter then raise an exception
  729 + if not model.has_par('Prefactor'):
  730 + msg = ('Model "%s" has no parameter "Prefactor". Only spectral '
  731 + 'models with a "Prefactor" parameter are supported.' %
  732 + srcname)
  733 + raise RuntimeError(msg)
  734 +
  735 + # Set position of test source
  736 + if ra != None and dec != None:
  737 + if model.has_par('RA') and model.has_par('DEC'):
  738 + model['RA'].value(ra)
  739 + model['DEC'].value(dec)
  740 +
  741 + # Set possible spatial and spectral parameters
  742 + spatial = ['RA', 'DEC', 'Sigma', 'Radius', 'Width', 'PA',
  743 + 'MinorRadius', 'MajorRadius']
  744 + spectral = ['Index', 'Index1', 'Index2', 'BreakEnergy', 'CutoffEnergy',
  745 + 'InverseCutoffEnergy']
  746 +
  747 + # Fit or fix spatial parameters
  748 + for par in spatial:
  749 + if model.has_par(par):
  750 + if fitspat:
  751 + model[par].free()
  752 + else:
  753 + model[par].fix()
  754 +
  755 + # Fit or fix spectral parameters
  756 + for par in spectral:
  757 + if model.has_par(par):
  758 + if fitspec:
  759 + model[par].free()
  760 + else:
  761 + model[par].fix()
  762 +
  763 + # Return model container
  764 + return outmodels
  765 +
  766 +
  767 +# ==================== #
  768 +# Get stacked response #
  769 +# ==================== #
  770 +def get_stacked_response(obs, xref, yref, binsz=0.05, nxpix=200, nypix=200,
  771 + emin=0.1, emax=100.0, enumbins=20, edisp=False,
  772 + coordsys='GAL', proj='TAN',
  773 + log=False, debug=False, chatter=2):
  774 + """
  775 + Get stacked response cubes
  776 +
  777 + Parameters
  778 + ----------
  779 + obs : `~gammalib.GObservations`
  780 + Observation container
  781 + xref : float
  782 + Right Ascension or Galactic longitude of response centre (deg)
  783 + yref : float
  784 + Declination or Galactic latitude of response centre (deg)
  785 + binsz : float, optional
  786 + Pixel size (deg/pixel)
  787 + nxpix : int, optional
  788 + Number of pixels in X direction
  789 + nypix : int, optional
  790 + Number of pixels in Y direction
  791 + emin : float, optional
  792 + Minimum energy (TeV)
  793 + emax : float, optional
  794 + Maximum energy (TeV)
  795 + enumbins : int, optional
  796 + Number of energy bins
  797 + edisp : bool, optional
  798 + Apply energy dispersion?
  799 + coordsys : str, optional
  800 + Coordinate system
  801 + proj : str, optional
  802 + Projection
  803 + log : bool, optional
  804 + Create log file(s)
  805 + debug : bool, optional
  806 + Create console dump?
  807 + chatter : int, optional
  808 + Chatter level
  809 +
  810 + Returns
  811 + -------
  812 + result : dict
  813 + Dictionary of response cubes
  814 + """
  815 + # Compute spatial binning for point spread function and
  816 + # energy dispersion cubes
  817 + psf_binsz = 10.0 * binsz
  818 + psf_nxpix = min(nxpix // 10, 2) # Make sure result is int
  819 + psf_nypix = min(nypix // 10, 2) # Make sure result is int
  820 +
  821 + # Create exposure cube
  822 + expcube = ctools.ctexpcube(obs)
  823 + expcube['incube'] = 'NONE'
  824 + expcube['usepnt'] = False
  825 + expcube['ebinalg'] = 'LOG'
  826 + expcube['xref'] = xref
  827 + expcube['yref'] = yref
  828 + expcube['binsz'] = binsz
  829 + expcube['nxpix'] = nxpix
  830 + expcube['nypix'] = nypix
  831 + expcube['enumbins'] = enumbins
  832 + expcube['emin'] = emin
  833 + expcube['emax'] = emax
  834 + expcube['coordsys'] = coordsys
  835 + expcube['proj'] = proj
  836 + if log:
  837 + expcube.logFileOpen()
  838 + expcube.run()
  839 +
  840 + # Create point spread function cube
  841 + psfcube = ctools.ctpsfcube(obs)
  842 + psfcube['incube'] = 'NONE'
  843 + psfcube['usepnt'] = False
  844 + psfcube['ebinalg'] = 'LOG'
  845 + psfcube['xref'] = xref
  846 + psfcube['yref'] = yref
  847 + psfcube['binsz'] = psf_binsz
  848 + psfcube['nxpix'] = psf_nxpix
  849 + psfcube['nypix'] = psf_nypix
  850 + psfcube['enumbins'] = enumbins
  851 + psfcube['emin'] = emin
  852 + psfcube['emax'] = emax
  853 + psfcube['coordsys'] = coordsys
  854 + psfcube['proj'] = proj
  855 + if log:
  856 + psfcube.logFileOpen()
  857 + psfcube.run()
  858 +
  859 + # Create background cube
  860 + bkgcube = ctools.ctbkgcube(obs)
  861 + bkgcube['incube'] = 'NONE'
  862 + bkgcube['usepnt'] = False
  863 + bkgcube['ebinalg'] = 'LOG'
  864 + bkgcube['xref'] = xref
  865 + bkgcube['yref'] = yref
  866 + bkgcube['binsz'] = binsz
  867 + bkgcube['nxpix'] = nxpix
  868 + bkgcube['nypix'] = nypix
  869 + bkgcube['enumbins'] = enumbins
  870 + bkgcube['emin'] = emin
  871 + bkgcube['emax'] = emax
  872 + bkgcube['coordsys'] = coordsys
  873 + bkgcube['proj'] = proj
  874 + if log:
  875 + bkgcube.logFileOpen()
  876 + bkgcube.run()
  877 +
  878 + # If energy dispersion is requested then create energy dispersion cube
  879 + if edisp:
  880 + edispcube = ctools.ctedispcube(obs)
  881 + edispcube['incube'] = 'NONE'
  882 + edispcube['usepnt'] = False
  883 + edispcube['ebinalg'] = 'LOG'
  884 + edispcube['xref'] = xref
  885 + edispcube['yref'] = yref
  886 + edispcube['binsz'] = psf_binsz
  887 + edispcube['nxpix'] = psf_nxpix
  888 + edispcube['nypix'] = psf_nypix
  889 + edispcube['enumbins'] = enumbins
  890 + edispcube['emin'] = emin
  891 + edispcube['emax'] = emax
  892 + edispcube['coordsys'] = coordsys
  893 + edispcube['proj'] = proj
  894 + if log:
  895 + edispcube.logFileOpen()
  896 + edispcube.run()
  897 +
  898 + # Build response dictionary
  899 + response = {}
  900 + response['expcube'] = expcube.expcube().copy()
  901 + response['psfcube'] = psfcube.psfcube().copy()
  902 + response['bkgcube'] = bkgcube.bkgcube().copy()
  903 + response['models'] = bkgcube.models().copy()
  904 + if edisp:
  905 + response['edispcube'] = edispcube.expcube().copy()
  906 +
  907 + # Return response cubes
  908 + return response
doc/source/users/reference_manual/cstsdist.rst
@@ -75,6 +75,12 @@ General parameters @@ -75,6 +75,12 @@ General parameters
75 ``dec [real]`` 75 ``dec [real]``
76 Declination of CTA pointing (J2000, in degrees). 76 Declination of CTA pointing (J2000, in degrees).
77 77
  78 +``coordsys <CEL|GAL> [string]``
  79 + Coordinate system (CEL - celestial, GAL - galactic).
  80 +
  81 +``proj <AIT|AZP|CAR|MER|MOL|STG|TAN> [string]``
  82 + Projection method.
  83 +
78 ``emin [real]`` 84 ``emin [real]``
79 Lower energy limit (in TeV). 85 Lower energy limit (in TeV).
80 86
test/test_cstsdist.py
@@ -76,7 +76,7 @@ class Test(test): @@ -76,7 +76,7 @@ class Test(test):
76 ' enumbins=0 tmax=1800.0 rad=5.0 npix=200 npix=200'+ \ 76 ' enumbins=0 tmax=1800.0 rad=5.0 npix=200 npix=200'+ \
77 ' binsz=0.05'+ \ 77 ' binsz=0.05'+ \
78 ' outfile="cstsdist_cmd1.dat"'+ \ 78 ' outfile="cstsdist_cmd1.dat"'+ \
79 - ' logfile="cstsdist_cmd1.log" chatter=1' 79 + ' logfile="cstsdist_cmd1.log" chatter=2'
80 80
81 # Check if execution of wrong command fails 81 # Check if execution of wrong command fails
82 self.test_assert(self._execute('command_that_does_not_exist') != 0, 82 self.test_assert(self._execute('command_that_does_not_exist') != 0,
@@ -117,7 +117,6 @@ class Test(test): @@ -117,7 +117,6 @@ class Test(test):
117 tsdist['srcname'] = 'Crab' 117 tsdist['srcname'] = 'Crab'
118 tsdist['caldb'] = self._caldb 118 tsdist['caldb'] = self._caldb
119 tsdist['irf'] = self._irf 119 tsdist['irf'] = self._irf
120 - tsdist['outfile'] = 'cstsdist_py1.dat'  
121 tsdist['ntrials'] = 1 120 tsdist['ntrials'] = 1
122 tsdist['ra'] = 83.63 121 tsdist['ra'] = 83.63
123 tsdist['dec'] = 22.01 122 tsdist['dec'] = 22.01
@@ -126,10 +125,9 @@ class Test(test): @@ -126,10 +125,9 @@ class Test(test):
126 tsdist['enumbins'] = 0 125 tsdist['enumbins'] = 0
127 tsdist['tmax'] = 1800.0 126 tsdist['tmax'] = 1800.0
128 tsdist['rad'] = 5.0 127 tsdist['rad'] = 5.0
129 - tsdist['npix'] = 200  
130 - tsdist['binsz'] = 0.05 128 + tsdist['outfile'] = 'cstsdist_py1.dat'
131 tsdist['logfile'] = 'cstsdist_py1.log' 129 tsdist['logfile'] = 'cstsdist_py1.log'
132 - tsdist['chatter'] = 2 130 + tsdist['chatter'] = 3
133 131
134 # Run cstsdist script 132 # Run cstsdist script
135 tsdist.logFileOpen() # Make sure we get a log file 133 tsdist.logFileOpen() # Make sure we get a log file
@@ -139,20 +137,56 @@ class Test(test): @@ -139,20 +137,56 @@ class Test(test):
139 # Check pull distribution file 137 # Check pull distribution file
140 self._check_result_file('cstsdist_py1.dat') 138 self._check_result_file('cstsdist_py1.dat')
141 139
  140 + # Set-up binned cstsdist in four observation wobble mode
  141 + tsdist = cscripts.cstsdist()
  142 + tsdist['inmodel'] = self._model
  143 + tsdist['srcname'] = 'Crab'
  144 + tsdist['caldb'] = self._caldb
  145 + tsdist['irf'] = self._irf
  146 + tsdist['ntrials'] = 1
  147 + tsdist['ra'] = 83.63
  148 + tsdist['dec'] = 22.01
  149 + tsdist['coordsys'] = 'CEL'
  150 + tsdist['proj'] = 'TAN'
  151 + tsdist['emin'] = 0.1
  152 + tsdist['emax'] = 100.0
  153 + tsdist['enumbins'] = 10
  154 + tsdist['tmax'] = 1800.0
  155 + tsdist['rad'] = 5.0
  156 + tsdist['npix'] = 50
  157 + tsdist['binsz'] = 0.1
  158 + tsdist['pattern'] = 'four'
  159 + tsdist['outfile'] = 'cstsdist_py2.dat'
  160 + tsdist['logfile'] = 'cstsdist_py2.log'
  161 + tsdist['chatter'] = 4
  162 +
  163 + # Execute cstsdist script
  164 + tsdist.execute()
  165 +
  166 + # Check pull distribution file
  167 + self._check_result_file('cstsdist_py2.dat', ncols=9)
  168 +
142 # Return 169 # Return
143 return 170 return
144 171
145 # Check result file 172 # Check result file
146 - def _check_result_file(self, filename): 173 + def _check_result_file(self, filename, ncols=11):
147 """ 174 """
148 Check result file 175 Check result file
  176 +
  177 + Parameters
  178 + ----------
  179 + filename : str
  180 + Name of result file
  181 + ncols : int, optional
  182 + Required number of columns
149 """ 183 """
150 # Open result file as CSV file 184 # Open result file as CSV file
151 results = gammalib.GCsv(filename, ',') 185 results = gammalib.GCsv(filename, ',')
152 186
153 # Check dimensions 187 # Check dimensions
154 - self.test_value(results.nrows(), 2, 'Check for 2 rows in TS file')  
155 - self.test_value(results.ncols(), 14, 'Check for 14 columns in TS file') 188 + self.test_value(results.nrows(), 2, 'Check rows in TS file')
  189 + self.test_value(results.ncols(), ncols, 'Check columns in TS file')
156 190
157 # Return 191 # Return
158 return 192 return