ctools  2.0.0
 All Classes Namespaces Files Functions Variables Macros Pages
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 # ==========================================================================
21 import sys
22 import gammalib
23 import ctools
24 
25 
26 # ================ #
27 # csbkgmodel class #
28 # ================ #
29 class 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 
127  def _get_instrument(self):
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():
486  self._generate_runwise_bkg()
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 # ======================== #
532 if __name__ == '__main__':
533 
534  # Create instance of application
535  app = csbkgmodel(sys.argv)
536 
537  # Execute application
538  app.execute()