ctools 2.1.0.dev
Loading...
Searching...
No Matches
csbkgmodel.py
Go to the documentation of this file.
1#! /usr/bin/env python
2# ==========================================================================
3# Generates background model
4#
5# Copyright (C) 2018-2022 Juergen Knoedlseder
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20# ==========================================================================
21import sys
22import gammalib
23import ctools
24
25
26# ================ #
27# csbkgmodel class #
28# ================ #
29class csbkgmodel(ctools.csobservation):
30 """
31 Generates background model
32 """
33 # Constructor
34 def __init__(self, *argv):
35 """
36 Constructor
37 """
38 # Initialise application by calling the appropriate class constructor
39 self._init_csobservation(self.__class__.__name__, ctools.__version__, argv)
40
41 # Set members
42 self._models = gammalib.GModels()
43 self._instrument = ''
44
45 # Return
46 return
47
48 # __getstate__ method for pickling
49 def __getstate__(self):
50 """
51 Extend ctools.csobservation getstate method to include some members
52
53 Returns
54 -------
55 state : dict
56 Pickled instance
57 """
58 # Set pickled dictionary
59 state = {'base' : ctools.csobservation.__getstate__(self),
60 'models' : self._models,
61 'instrument' : self._instrument}
62
63 # Return pickled dictionary
64 return state
65
66 # __setstate__ method for pickling
67 def __setstate__(self, state):
68 """
69 Extend ctools.csobservation setstate method to include some members
70
71 Parameters
72 ----------
73 state : dict
74 Pickled instance
75 """
76 ctools.csobservation.__setstate__(self, state['base'])
77 self._models = state['models']
78 self._instrument = state['instrument']
79
80 # Return
81 return
82
83
84 # Private methods
85 def _get_parameters(self):
86 """
87 Get parameters from parfile
88 """
89 # Setup observations (require response, allow event list and counts
90 # cube)
91 self._setup_observations(self.obs(), True, True, True)
92
93 # Set instrument name
94 self._instrument = self._get_instrument()
95
96 # Query input parameters
97 spatial = self['spatial'].string()
98 if spatial == 'GAUSS(E)':
99 snumbins = self['snumbins'].integer()
100 if snumbins > 1:
101 self['smin'].real()
102 self['smax'].real()
103 if spatial == 'LOOKUP':
104 self['slufile'].filename()
105 if spatial == 'LOOKUP' or spatial == 'GAUSS' or \
106 spatial == 'GAUSS(E)' or spatial == 'PROFILE' or \
107 spatial == 'POLYNOM':
108 self['gradient'].boolean()
109 spectral = self['spectral'].string()
110 if spectral == 'NODES':
111 self._create_energies()
112 self['runwise'].boolean()
113 self['emin'].real()
114 self['emax'].real()
115 self['rad'].real()
116
117 # Query ahead output model filename
118 if self._read_ahead():
119 self['outmodel'].filename()
120
121 # Write input parameters into logger
122 self._log_parameters(gammalib.TERSE)
123
124 # Return
125 return
126
128 """
129 Get instrument name
130
131 Extracts the instrument name from the observation container. If there
132 are multiple instruments in the container then use the "instrument"
133 parameter to set the instrument name.
134
135 Returns
136 -------
137 instrument : str
138 Instrument name
139 """
140 # Initialise instrument
141 instrument = ''
142
143 # Loop over all observations
144 for obs in self.obs():
145 if instrument == '':
146 instrument = obs.instrument()
147 elif instrument != obs.instrument():
148 instrument = self['instrument'].string()
149 break
150
151 # Return instrument
152 return instrument
153
154 def _generate_initial_model(self, sigma_min=2.0, sigma_max=1000.0):
155 """
156 Generate initial background model
157
158 Parameters
159 ----------
160 sigma_min : float, optional
161 Minimum sigma
162 sigma_max : float, optional
163 Maximum sigma
164
165 Returns
166 -------
167 model : `~gammalib.GModelData()`
168 Background model
169 """
170 # Handle IRF model
171 if self['spatial'].string() == 'IRF':
172 epivot = gammalib.GEnergy(1.0, 'TeV')
173 spectral = gammalib.GModelSpectralPlaw(1.0, 0.0, epivot)
174 model = gammalib.GCTAModelIrfBackground(spectral)
175
176 # Handle AEFF model
177 elif self['spatial'].string() == 'AEFF':
178 epivot = gammalib.GEnergy(1.0, 'TeV')
179 spectral = gammalib.GModelSpectralPlaw(1.0e-13, -2.5, epivot)
180 model = gammalib.GCTAModelAeffBackground(spectral)
181
182 # Handle other models
183 else:
184
185 # Handle LOOKUP model
186 if self['spatial'].string() == 'LOOKUP':
187
188 # Set spatial model
189 factor1 = gammalib.GCTAModelSpatialLookup(self['slufile'].filename())
190
191 # Handle GAUSS model
192 elif self['spatial'].string() == 'GAUSS':
193
194 # Set spatial model
195 factor1 = gammalib.GCTAModelRadialGauss(3.0)
196 factor1['Sigma'].min(sigma_min)
197 factor1['Sigma'].max(sigma_max)
198
199 # Handle GAUSS(E) model
200 elif self['spatial'].string() == 'GAUSS(E)':
201
202 # Set spatial model. If a single energy bin is specified then
203 # use the GAUSS model. Otherwise allocate a node spectrum for
204 # the energy nodes of GAUSS(E).
205 if self['snumbins'].integer() == 1:
206 factor1 = gammalib.GCTAModelRadialGauss(3.0)
207 factor1['Sigma'].min(sigma_min)
208 factor1['Sigma'].max(sigma_max)
209 else:
210 emin = gammalib.GEnergy(self['smin'].real(), 'TeV')
211 emax = gammalib.GEnergy(self['smax'].real(), 'TeV')
212 energies = gammalib.GEnergies(self['snumbins'].integer(), emin, emax)
213 spectrum = gammalib.GModelSpectralConst(3.0)
214 nodes = gammalib.GModelSpectralNodes(spectrum, energies)
215 for i in range(nodes.nodes()):
216 nodes[i*2+1].min(sigma_min)
217 nodes[i*2+1].max(sigma_max)
218 nodes.autoscale()
219 factor1 = gammalib.GCTAModelSpatialGaussSpectrum(nodes)
220
221 # Handle PROFILE model
222 elif self['spatial'].string() == 'PROFILE':
223
224 # Set spatial model
225 factor1 = gammalib.GCTAModelRadialProfile(2.0, 4.0, 5.0)
226 factor1['Width'].min(1.0)
227 factor1['Width'].max(10.0)
228 factor1['Core'].min(1.0)
229 factor1['Core'].max(10.0)
230 factor1['Tail'].min(1.0)
231 factor1['Tail'].max(10.0)
232 factor1['Tail'].free()
233
234 # Handle POLYNOM model
235 elif self['spatial'].string() == 'POLYNOM':
236
237 # Set spatial model
238 factor1 = gammalib.GCTAModelRadialPolynom([1.0, -0.1, +0.1])
239 factor1['Coeff0'].min(0.1)
240 factor1['Coeff0'].max(10.0)
241 factor1['Coeff0'].fix()
242 factor1['Coeff1'].min(-10.0)
243 factor1['Coeff1'].max(10.0)
244 factor1['Coeff2'].min(-10.0)
245 factor1['Coeff2'].max(10.0)
246
247 # Any other strings (should never occur)
248 else:
249 model = None
250
251 # Optionally add gradient
252 if self['gradient'].boolean():
253 spatial = gammalib.GCTAModelSpatialMultiplicative()
254 factor2 = gammalib.GCTAModelSpatialGradient()
255 spatial.append(factor1)
256 spatial.append(factor2)
257 else:
258 spatial = factor1
259
260 # Set spectral model
261 epivot = gammalib.GEnergy(1.0, 'TeV')
262 spectral = gammalib.GModelSpectralPlaw(3.0e-4, -1.5, epivot)
263 spectral['Prefactor'].min(1.0e-8)
264
265 # Set background model
266 model = gammalib.GCTAModelBackground(spatial, spectral)
267
268 # Set background name and instrument
269 if model is not None:
270 model.name('Background')
271 model.instruments(self._instrument)
272
273 # Return model
274 return model
275
276 def _select_events(self, obs):
277 """
278 Select events within a given RoI radius
279
280 Parameters
281 ----------
282 obs : `~gammalib.GObservations()`
283 Observation container
284
285 Returns
286 -------
287 obs : `~gammalib.GObservations()`
288 Observation container
289 """
290 # Setup task parameters
291 select = ctools.ctselect(obs)
292 select['ra'] = 'UNDEF'
293 select['dec'] = 'UNDEF'
294 select['rad'] = self['rad'].real()
295 select['tmin'] = 'UNDEF'
296 select['tmax'] = 'UNDEF'
297 select['emin'] = self['emin'].real()
298 select['emax'] = self['emax'].real()
299 select['usethres'] = 'DEFAULT'
300
301 # Select events
302 select.run()
303
304 # Extract observation
305 obs = select.obs().copy()
306
307 # Return observation
308 return obs
309
310 def _create_nodes(self, model):
311 """
312 Replace spectral model by node function
313
314 Parameters
315 ----------
316 model : `~gammalib.GModelData()`
317 Input background model
318
319 Returns
320 -------
321 model : `~gammalib.GModelData()`
322 Background model with spectral node function
323 """
324 # Extract spectral model of background component
325 spectrum = model.spectral()
326
327 # Define node energies
328 energies = self._create_energies()
329
330 # Create node spectrum
331 nodes = gammalib.GModelSpectralNodes(spectrum, energies)
332 nodes.autoscale()
333
334 # Set minimum node intensities (this avoids NaNs during model fitting)
335 for i in range(nodes.size()):
336 nodes[i].min(1.0e-10*nodes[i].value())
337
338 # Set node spectrum
339 model.spectral(nodes)
340
341 # Return
342 return model
343
344 def _generate_bkg(self, obs):
345 """
346 Generate background models
347
348 Parameters
349 ----------
350 obs : `~gammalib.GObservations()`
351 Observations container
352
353 Returns
354 -------
355 model : `~gammalib.GModelData()`
356 Background model component
357 """
358 # Write header for event selection
359 self._log_header3(gammalib.EXPLICIT, 'Select events from observation')
360
361 # Select events
362 obs = self._select_events(obs)
363
364 # Write header for initial background model generation
365 self._log_header3(gammalib.EXPLICIT, 'Generate initial background model')
366
367 # Generate initial background model
368 model = self._generate_initial_model()
369
370 # Attach initial background model
371 models = gammalib.GModels()
372 models.append(model)
373 obs.models(models)
374
375 # Write header for initial model fitting
376 self._log_header3(gammalib.EXPLICIT, 'Fit initial background model')
377
378 # Perform maximum likelihood fitting with initial model
379 like = ctools.ctlike(obs)
380 like.run()
381
382 # Extract optimiser
383 opt = like.opt()
384
385 # Extract fitted model
386 model = like.obs().models()[0].copy()
387
388 # If a NODES model is requested then refit a node spectrum
389 if self['spectral'].string() == 'NODES':
390
391 # Create nodes spectrum from fitted initial model
392 model = self._create_nodes(model)
393
394 # Attach node spectrum
395 models = gammalib.GModels()
396 models.append(model)
397 obs.models(models)
398
399 # Write header for node model fitting
400 self._log_header3(gammalib.EXPLICIT, 'Fit nodes background model')
401
402 # Perform maximum likelihood fitting with node model
403 like = ctools.ctlike(obs)
404 like.run()
405
406 # Extract optimiser
407 opt = like.opt()
408
409 # Extract fitted model
410 model = like.obs().models()[0].copy()
411
412 # Remove nodes with zero errors as they are not constrained
413 # by the data and may lead to fitting problems later
414 spectral = model.spectral()
415 nodes = spectral.nodes()
416 for i in range(nodes):
417 iint = 2*(nodes - i) - 1
418 if spectral[iint].error() == 0.0:
419 spectral.remove(nodes - i - 1)
420
421 # Write optimizer
422 self._log_string(gammalib.EXPLICIT, str(opt))
423
424 # Return model
425 return model
426
428 """
429 Generate background models
430 """
431 # Loop over observations
432 for run in self.obs():
433
434 # Write header
435 self._log_header2(gammalib.TERSE, self._get_obs_header(run))
436
437 # Build observation container with single run
438 obs = gammalib.GObservations()
439 obs.append(run)
440
441 # Generate background model for
442 model = self._generate_bkg(obs)
443
444 # Set model attributes
445 model.name(('Background_%s' % run.id()))
446 model.ids(run.id())
447
448 # Write model
449 self._log_string(gammalib.NORMAL, str(model))
450
451 # Append background model
452 self._models.append(model)
453
454 # Return
455 return
456
457
458 # Public methods
459 def process(self):
460 """
461 Process the script
462 """
463 # Get parameters
464 self._get_parameters()
465
466 # Write observation into logger
467 self._log_observations(gammalib.NORMAL, self.obs(), 'Input observation')
468
469 # Remove observations from wrong instruments
470 obs = self.obs()
471 for i in range(obs.size()-1, -1, -1):
472 if obs[i].instrument() != self._instrument:
473 obs.remove(i)
474
475 # Log observations to be used
476 self._log_value(gammalib.NORMAL, 'Obs matching instrument', self.obs().size())
477
478 # Write header
479 self._log_header1(gammalib.TERSE, 'Generate background models')
480
481 # Clear models
482 self._models.clear()
483
484 # Generate background models
485 if self['runwise'].boolean():
487 else:
488 model = self._generate_bkg(self.obs())
489 self._models.append(model)
490
491 # Write models
492 self._log_models(gammalib.NORMAL, self._models, 'Output model')
493
494 # Return
495 return
496
497 def save(self):
498 """
499 Save models
500 """
501 # Write header
502 self._log_header1(gammalib.TERSE, 'Save models')
503
504 # Get models filename
505 outmodel = self['outmodel'].filename()
506
507 # Log file name
508 self._log_value(gammalib.NORMAL, 'Models file', outmodel.url())
509
510 # Save models
511 self._models.save(outmodel)
512
513 # Return
514 return
515
516 def models(self):
517 """
518 Return background models
519
520 Returns
521 -------
522 ebounds : `~gammalib.GModels()`
523 Background models
524 """
525 # Return
526 return self._models
527
528
529# ======================== #
530# Main routine entry point #
531# ======================== #
532if __name__ == '__main__':
533
534 # Create instance of application
535 app = csbkgmodel(sys.argv)
536
537 # Execute application
538 app.execute()
_generate_initial_model(self, sigma_min=2.0, sigma_max=1000.0)