test_OnOff_8th.py
1 |
#! /Applications/Anaconda/bin/python
|
---|---|
2 |
# ====================================================================
|
3 |
# This script tests the ON-OFF analysis functionality of the gammalib
|
4 |
# ====================================================================
|
5 |
|
6 |
from ctools import * |
7 |
from gammalib import * |
8 |
from math import * |
9 |
from cscripts import obsutils |
10 |
from copy import deepcopy |
11 |
|
12 |
import os |
13 |
import glob |
14 |
import sys |
15 |
|
16 |
import numpy |
17 |
|
18 |
# ======================== #
|
19 |
# Setup single observation #
|
20 |
# ======================== #
|
21 |
def single_obs(pntdir, tstart=0.0, duration=1800.0, deadc=0.95, \ |
22 |
emin=0.1, emax=100.0, rad=5.0, \ |
23 |
irf="South_50h", caldb="prod2", id="000000", name="Obs", instrument="CTA"): |
24 |
"""
|
25 |
Returns a single CTA observation.
|
26 |
|
27 |
Parameters:
|
28 |
pntdir - Pointing direction [GSkyDir]
|
29 |
Keywords:
|
30 |
tstart - Start time [seconds] (default: 0.0)
|
31 |
duration - Duration of observation [seconds] (default: 1800.0)
|
32 |
deadc - Deadtime correction factor (default: 0.95)
|
33 |
emin - Minimum event energy [TeV] (default: 0.1)
|
34 |
emax - Maximum event energy [TeV] (default: 100.0)
|
35 |
rad - ROI radius used for analysis [deg] (default: 5.0)
|
36 |
irf - Instrument response function (default: cta_dummy_irf)
|
37 |
caldb - Calibration database path (default: "dummy")
|
38 |
id - Run identifier (default: "000000")
|
39 |
instrument - Intrument (default: "CTA")
|
40 |
"""
|
41 |
# Allocate CTA observation
|
42 |
obs_cta = GCTAObservation() |
43 |
|
44 |
# Set calibration database
|
45 |
db = GCaldb() |
46 |
if (os.path.isdir(caldb)):
|
47 |
db.rootdir(caldb) |
48 |
else:
|
49 |
db.open("cta", caldb)
|
50 |
|
51 |
# Set pointing direction
|
52 |
pnt = GCTAPointing() |
53 |
pnt.dir(pntdir) |
54 |
obs_cta.pointing(pnt) |
55 |
|
56 |
# Set ROI
|
57 |
roi = GCTARoi() |
58 |
instdir = GCTAInstDir() |
59 |
instdir.dir(pntdir) |
60 |
roi.centre(instdir) |
61 |
roi.radius(rad) |
62 |
|
63 |
# Set GTI
|
64 |
gti = GGti() |
65 |
gti.append(GTime(tstart), GTime(tstart+duration)) |
66 |
|
67 |
# Set energy boundaries
|
68 |
ebounds = GEbounds(GEnergy(emin, "TeV"),GEnergy(emax, "TeV")) |
69 |
|
70 |
# Allocate event list
|
71 |
events = GCTAEventList() |
72 |
events.roi(roi) |
73 |
events.gti(gti) |
74 |
events.ebounds(ebounds) |
75 |
obs_cta.events(events) |
76 |
|
77 |
# Set instrument response
|
78 |
obs_cta.response(irf, db) |
79 |
|
80 |
# Set ontime, livetime, and deadtime correction factor
|
81 |
obs_cta.ontime(duration) |
82 |
obs_cta.livetime(duration*deadc) |
83 |
obs_cta.deadc(deadc) |
84 |
obs_cta.id(id)
|
85 |
obs_cta.name(name) |
86 |
|
87 |
# Return CTA observation
|
88 |
return obs_cta
|
89 |
|
90 |
|
91 |
# ======================================== #
|
92 |
# Add CTA background model to observations #
|
93 |
# ======================================== #
|
94 |
def add_background_model(models,name="Background",instru="CTA",id="",bgd_file="",bgd_sigma=0.0): |
95 |
|
96 |
"""
|
97 |
Add CTA background model to a model container.
|
98 |
|
99 |
Parameters:
|
100 |
models - a model container
|
101 |
bgd_file - file function for background spectral dependence
|
102 |
bgd_sigma - sigma parameter for the gaussian offset angle dependence of the background rate
|
103 |
"""
|
104 |
|
105 |
# Gaussian radial dependence model
|
106 |
if bgd_sigma > 0.0: |
107 |
|
108 |
# Spatial part
|
109 |
bgd_radial = GCTAModelRadialGauss(bgd_sigma) |
110 |
|
111 |
# Spectral part
|
112 |
if len(bgd_file) > 0: |
113 |
bgd_spectrum = GModelSpectralFunc(bgd_file,1.0)
|
114 |
else:
|
115 |
pivot_nrj=GEnergy(1.0e6, "MeV") |
116 |
bgd_spectrum = GModelSpectralPlaw(1.0e-6, -2.0, pivot_nrj) |
117 |
bgd_spectrum["Prefactor"].value(61.8e-6) |
118 |
bgd_spectrum["Prefactor"].scale(1.0e-6) |
119 |
bgd_spectrum["PivotEnergy"].value(1.0) |
120 |
bgd_spectrum["PivotEnergy"].scale(1.0e6) |
121 |
bgd_spectrum["Index"].value(-1.85) |
122 |
bgd_spectrum["Index"].scale(1.0) |
123 |
|
124 |
# Full model
|
125 |
bgd_model = GCTAModelRadialAcceptance(bgd_radial, bgd_spectrum) |
126 |
|
127 |
# IRF-defined model
|
128 |
else:
|
129 |
|
130 |
# Spectral model
|
131 |
pivot_nrj=GEnergy(1.0e6, "MeV") |
132 |
bgd_spectrum = GModelSpectralPlaw(1.0e-6, -2.0, pivot_nrj) |
133 |
bgd_spectrum["Prefactor"].value(1.0) |
134 |
bgd_spectrum["Prefactor"].scale(1.0) |
135 |
bgd_spectrum["PivotEnergy"].value(1.0) |
136 |
bgd_spectrum["PivotEnergy"].scale(1.0e6) |
137 |
bgd_spectrum["Index"].value(0.0) |
138 |
bgd_spectrum["Index"].scale(1.0) |
139 |
|
140 |
# Full model
|
141 |
bgd_model = GCTAModelIrfBackground(bgd_spectrum) |
142 |
|
143 |
# Set other background properties
|
144 |
if (len(name) > 0): |
145 |
bgd_model.name(name) |
146 |
else:
|
147 |
bgd_model.name("Background")
|
148 |
if (len(instru) > 0): |
149 |
bgd_model.instruments(instru) |
150 |
else:
|
151 |
bgd_model.instruments("CTA")
|
152 |
if (len(id) > 0): |
153 |
bgd_model.ids(id)
|
154 |
|
155 |
# By default, set model to be fitted
|
156 |
if (len(bgd_file) > 0): |
157 |
bgd_model['Normalization'].free()
|
158 |
else:
|
159 |
bgd_model['Prefactor'].free()
|
160 |
bgd_model['Index'].free()
|
161 |
|
162 |
# Add background model to container
|
163 |
models.append(bgd_model) |
164 |
|
165 |
# Return model container
|
166 |
return models
|
167 |
|
168 |
|
169 |
# =========================== #
|
170 |
# Set any general type source #
|
171 |
# =========================== #
|
172 |
def add_source_model(models, name, ra, dec, coord='CEL', \ |
173 |
skymap='', specfile='', \ |
174 |
flux=5.7e-16, index=-2.5, pivot=3.0e5, \ |
175 |
type="point", sigma=1.0, radius=1.0, width=0.1, \ |
176 |
nnode=0, emin=1.0, emax=10.0, \ |
177 |
pivot_scale=1e6, flux_scale=1.0e-16): |
178 |
"""
|
179 |
Adds a single point-source with power-law spectrum to a model container.
|
180 |
The default is crab-like.
|
181 |
|
182 |
Parameters:
|
183 |
models - a model container
|
184 |
name - Unique source name
|
185 |
ra - RA of source location [deg]
|
186 |
dec - Declination of source location [deg]
|
187 |
Keywords:
|
188 |
flux - Source flux density in 1.0e-16 ph/cm2/s/MeV
|
189 |
index - Source spectral index
|
190 |
pivot - Pivot energy in TeV
|
191 |
type - Source spatial model
|
192 |
sigma/radius/width - Spatial model parameters
|
193 |
nnode - Number of nodes (if > 0, spectral model is a node function)
|
194 |
"""
|
195 |
|
196 |
# Set source spectral model
|
197 |
if (nnode > 0) and (emin != emax): |
198 |
spectrum = GModelSpectralNodes() |
199 |
dloge=log10(emax/emin)/nnode |
200 |
logemin=log10(emin) |
201 |
spectrum.reserve(nnode) |
202 |
for i in range(nnode): |
203 |
enode = GEnergy() |
204 |
enode.TeV(10.0**(logemin+(i+0.5)*dloge)) |
205 |
inode=flux*(enode.MeV()/pivot)**(index) |
206 |
spectrum.append(enode, inode) |
207 |
spectrum[i*2].scale(pivot_scale)
|
208 |
spectrum[i*2].fix()
|
209 |
spectrum[i*2+1].scale(flux_scale) |
210 |
spectrum[i*2+1].free() |
211 |
# Set source spectral model
|
212 |
elif (len(specfile) > 0): |
213 |
spectrum = GModelSpectralFunc(specfile) |
214 |
spectrum["Normalization"].value(1.0) |
215 |
spectrum["Normalization"].free()
|
216 |
else:
|
217 |
pivot_nrj=GEnergy() |
218 |
pivot_nrj.TeV(pivot*pivot_scale/1e6)
|
219 |
spectrum = GModelSpectralPlaw(flux, index,pivot_nrj) |
220 |
spectrum["Prefactor"].scale(flux_scale)
|
221 |
spectrum["Prefactor"].value(flux)
|
222 |
spectrum["Prefactor"].free()
|
223 |
spectrum["PivotEnergy"].scale(pivot_scale)
|
224 |
spectrum["PivotEnergy"].value(pivot)
|
225 |
spectrum["Index"].value(index)
|
226 |
spectrum["Index"].scale(1.0) |
227 |
spectrum["Index"].free()
|
228 |
|
229 |
# Set source spatial model
|
230 |
location = GSkyDir() |
231 |
if (coord == 'CEL'): |
232 |
location.radec_deg(ra, dec) |
233 |
else:
|
234 |
location.lb_deg(ra, dec) |
235 |
if (len(skymap) > 0): |
236 |
spatial = GModelSpatialDiffuseMap(skymap,1.0)
|
237 |
source = GModelSky(spatial, spectrum) |
238 |
spatial[0].free()
|
239 |
elif type == "point": |
240 |
spatial = GModelSpatialPointSource(location) |
241 |
spatial[0].free()
|
242 |
spatial[1].free()
|
243 |
source = GModelSky(spatial, spectrum) |
244 |
elif type == "gauss": |
245 |
radial = GModelSpatialRadialGauss(location, sigma) |
246 |
radial[0].free()
|
247 |
radial[1].free()
|
248 |
radial[2].free()
|
249 |
source = GModelSky(radial, spectrum) |
250 |
elif type == "disk": |
251 |
radial = GModelSpatialRadialDisk(location, radius) |
252 |
radial[0].free()
|
253 |
radial[1].free()
|
254 |
radial[2].free()
|
255 |
source = GModelSky(radial, spectrum) |
256 |
elif type == "shell": |
257 |
radial = GModelSpatialRadialShell(location, radius, width) |
258 |
radial[0].free()
|
259 |
radial[1].free()
|
260 |
radial[2].free()
|
261 |
radial[3].free()
|
262 |
source = GModelSky(radial, spectrum) |
263 |
else:
|
264 |
print "ERROR: Unknown source type '"+type+"'." |
265 |
return None |
266 |
|
267 |
# Set source name
|
268 |
source.name(name) |
269 |
|
270 |
# Append source model to model container
|
271 |
models.append(source) |
272 |
|
273 |
# Return source
|
274 |
return models
|
275 |
|
276 |
# ======================================== #
|
277 |
# Set the fit parameters for a given model #
|
278 |
# ======================================== #
|
279 |
def set_fit_param(model, parname=[], parfit=[], parval=[]): |
280 |
"""
|
281 |
Set the fit parameters for a given model
|
282 |
|
283 |
Arguments
|
284 |
- model: a model container
|
285 |
- parname: array of parameters names
|
286 |
- parfit: array of fit/fix flags (True=fit,False=fix)
|
287 |
- parval: array of initial/fix parameters values
|
288 |
|
289 |
Notes
|
290 |
- Arrays should have same numbers of elements
|
291 |
- A way to not set a value but just the fit/fix flag is to pass/leave parval empty
|
292 |
- If among several parameters some values must be set and others not, separate these and call the function two times
|
293 |
"""
|
294 |
|
295 |
# Count/check number of parameters to be set
|
296 |
if (len(parname) != len(parfit)): |
297 |
print "Incorrect input. Fit parameters not set or modified." |
298 |
return 0 |
299 |
else:
|
300 |
num_toset=len(parname)
|
301 |
|
302 |
# Loop over parameters
|
303 |
for i in range(model.size()): |
304 |
name=model[i].name() |
305 |
if (name in parname): |
306 |
# Get index of matching name
|
307 |
i_par=parname.index(name) |
308 |
# Set the parameter to be fix/free
|
309 |
if (parfit[i_par]):
|
310 |
model[name].free() |
311 |
else:
|
312 |
model[name].fix() |
313 |
# Optionally set the value
|
314 |
if (len(parval) > 0): |
315 |
model[name].value(parval[i_par]) |
316 |
|
317 |
# Exit point
|
318 |
return model
|
319 |
|
320 |
|
321 |
# ===================== #
|
322 |
# Simulate observations #
|
323 |
# ===================== #
|
324 |
def sim(obs, log=False, debug=False, chatter=2, edisp=False, seed=0, |
325 |
emin=None, emax=None, nbins=0, addbounds=False, |
326 |
binsz=0.05, npix=200, proj='TAN', coord='GAL'): |
327 |
"""
|
328 |
Simulate events for all observations in the container
|
329 |
|
330 |
Simulate events for all observations using ctobssim. If the number of
|
331 |
energy bins is positive, the events are binned into a counts cube using
|
332 |
ctbin. If multiple observations are simulated, the counts cube is a
|
333 |
stacked cube and the corresponding response cubes are computed using
|
334 |
ctexpcube, ctpsfcube, ctbkgcube and optionally ctedispcube. The response
|
335 |
cubes are attached to the first observation in the container, which
|
336 |
normally is the observation with the counts cube.
|
337 |
|
338 |
Parameters
|
339 |
----------
|
340 |
obs : `~gammalib.GObservations`
|
341 |
Observation container without events
|
342 |
log : bool, optional
|
343 |
Create log file(s)
|
344 |
debug : bool, optional
|
345 |
Create console dump?
|
346 |
chatter : int, optional
|
347 |
Chatter level
|
348 |
edisp : bool, optional
|
349 |
Apply energy dispersion?
|
350 |
seed : int, optional
|
351 |
Seed value for simulations
|
352 |
emin : float, optional
|
353 |
Minimum energy of counts cube for binned (TeV)
|
354 |
emax : float, optional
|
355 |
Maximum energy of counts cube for binned (TeV)
|
356 |
nbins : int, optional
|
357 |
Number of energy bins (0=unbinned)
|
358 |
addbounds : bool, optional
|
359 |
Add boundaries at observation energies
|
360 |
binsz : float, optional
|
361 |
Pixel size for binned simulation (deg/pixel)
|
362 |
npix : int, optional
|
363 |
Number of pixels in X and Y for binned simulation
|
364 |
proj : str, optional
|
365 |
Projection for binned simulation
|
366 |
coord : str, optional
|
367 |
Coordinate system for binned simulation
|
368 |
|
369 |
Returns
|
370 |
-------
|
371 |
obs : `~gammalib.GObservations`
|
372 |
Observation container filled with simulated events
|
373 |
"""
|
374 |
# Allocate ctobssim application and set parameters
|
375 |
sim = ctobssim(obs) |
376 |
sim['seed'] = seed
|
377 |
sim['edisp'] = edisp
|
378 |
sim['chatter'] = chatter
|
379 |
sim['debug'] = debug
|
380 |
|
381 |
# Optionally open the log file
|
382 |
if log:
|
383 |
sim.logFileOpen() |
384 |
|
385 |
# Run ctobssim application. This will loop over all observations in the
|
386 |
# container and simulation the events for each observation. Note that
|
387 |
# events are not added together, they still apply to each observation
|
388 |
# separately.
|
389 |
sim.run() |
390 |
|
391 |
# Binned option?
|
392 |
if nbins > 0: |
393 |
|
394 |
# If energy boundaries are not given then determine the minimum and
|
395 |
# the maximum energies from all observations and use these values
|
396 |
# as energy boundaries. The energy boundaries are given in TeV.
|
397 |
if emin == None or emax == None: |
398 |
emin = 1.0e30
|
399 |
emax = 0.0
|
400 |
for run in sim.obs(): |
401 |
emin = min(run.events().ebounds().emin().TeV(), emin)
|
402 |
emax = max(run.events().ebounds().emax().TeV(), emax)
|
403 |
|
404 |
# Allocate ctbin application and set parameters
|
405 |
bin = ctbin(sim.obs()) |
406 |
bin['ebinalg'] = 'LOG' |
407 |
bin['emin'] = emin |
408 |
bin['emax'] = emax |
409 |
bin['enumbins'] = nbins |
410 |
bin['usepnt'] = True # Use pointing for map centre |
411 |
bin['nxpix'] = npix |
412 |
bin['nypix'] = npix |
413 |
bin['binsz'] = binsz |
414 |
bin['coordsys'] = coord |
415 |
bin['proj'] = proj |
416 |
bin['chatter'] = chatter |
417 |
bin['debug'] = debug |
418 |
|
419 |
# Optionally open the log file
|
420 |
if log:
|
421 |
bin.logFileOpen()
|
422 |
|
423 |
# Run ctbin application. This will loop over all observations in
|
424 |
# the container and bin the events in counts maps
|
425 |
bin.run()
|
426 |
|
427 |
# If we have multiple input observations then create stacked response
|
428 |
# cubes and append them to the observation
|
429 |
if len(sim.obs()) > 1: |
430 |
|
431 |
# Get stacked response (use pointing for map centre)
|
432 |
response = get_stacked_response(sim.obs(), None, None, |
433 |
binsz=binsz, nxpix=npix, nypix=npix, |
434 |
emin=emin, emax=emax, enumbins=nbins, |
435 |
edisp=edisp, |
436 |
coordsys=coord, proj=proj, |
437 |
addbounds=addbounds, |
438 |
log=log, debug=debug, |
439 |
chatter=chatter) |
440 |
|
441 |
# Set stacked response
|
442 |
if edisp:
|
443 |
bin.obs()[0].response(response['expcube'], |
444 |
response['psfcube'],
|
445 |
response['edispcube'],
|
446 |
response['bkgcube'])
|
447 |
else:
|
448 |
bin.obs()[0].response(response['expcube'], |
449 |
response['psfcube'],
|
450 |
response['bkgcube'])
|
451 |
|
452 |
# Set new models
|
453 |
bin.obs().models(response['models']) |
454 |
|
455 |
# Make a deep copy of the observation that will be returned
|
456 |
# (the ctbin object will go out of scope one the function is
|
457 |
# left)
|
458 |
obs = bin.obs().copy()
|
459 |
|
460 |
else:
|
461 |
|
462 |
# Make a deep copy of the observation that will be returned
|
463 |
# (the ctobssim object will go out of scope one the function is
|
464 |
# left)
|
465 |
obs = sim.obs().copy() |
466 |
|
467 |
# Delete the simulation
|
468 |
del sim
|
469 |
|
470 |
# Return observation container
|
471 |
return obs
|
472 |
|
473 |
# ==================== #
|
474 |
# Get stacked response #
|
475 |
# ==================== #
|
476 |
def get_stacked_response(obs, xref, yref, binsz=0.05, nxpix=200, nypix=200, |
477 |
emin=0.1, emax=100.0, enumbins=20, edisp=False, |
478 |
coordsys='GAL', proj='TAN', addbounds=False, |
479 |
log=False, debug=False, chatter=2): |
480 |
"""
|
481 |
Get stacked response cubes
|
482 |
|
483 |
The number of energies bins are set to at least 30 bins per decade, and
|
484 |
the "enumbins" parameter is only used if the number of bins is larger
|
485 |
than 30 bins per decade.
|
486 |
|
487 |
If the xref or yref arguments are "None" the response cube centre will be
|
488 |
determined from the pointing information in the observation container.
|
489 |
|
490 |
Parameters
|
491 |
----------
|
492 |
obs : `~gammalib.GObservations`
|
493 |
Observation container
|
494 |
xref : float
|
495 |
Right Ascension or Galactic longitude of response centre (deg)
|
496 |
yref : float
|
497 |
Declination or Galactic latitude of response centre (deg)
|
498 |
binsz : float, optional
|
499 |
Pixel size (deg/pixel)
|
500 |
nxpix : int, optional
|
501 |
Number of pixels in X direction
|
502 |
nypix : int, optional
|
503 |
Number of pixels in Y direction
|
504 |
emin : float, optional
|
505 |
Minimum energy (TeV)
|
506 |
emax : float, optional
|
507 |
Maximum energy (TeV)
|
508 |
enumbins : int, optional
|
509 |
Number of energy bins
|
510 |
edisp : bool, optional
|
511 |
Apply energy dispersion?
|
512 |
coordsys : str, optional
|
513 |
Coordinate system
|
514 |
proj : str, optional
|
515 |
Projection
|
516 |
addbounds : bool, optional
|
517 |
Add boundaries at observation energies
|
518 |
log : bool, optional
|
519 |
Create log file(s)
|
520 |
debug : bool, optional
|
521 |
Create console dump?
|
522 |
chatter : int, optional
|
523 |
Chatter level
|
524 |
|
525 |
Returns
|
526 |
-------
|
527 |
result : dict
|
528 |
Dictionary of response cubes
|
529 |
"""
|
530 |
# If no xref and yref arguments have been specified then use the pointing
|
531 |
# information
|
532 |
if xref == None or yref == None: |
533 |
usepnt = True
|
534 |
else:
|
535 |
usepnt = False
|
536 |
|
537 |
# Set number of energy bins to at least 30 per energy decade
|
538 |
_enumbins = int((log10(emax) - log10(emin)) * 30.0) |
539 |
if enumbins > _enumbins:
|
540 |
_enumbins = enumbins |
541 |
|
542 |
# Compute spatial binning for point spread function and energy dispersion
|
543 |
# cubes. The spatial binning is 10 times coarser than the spatial binning
|
544 |
# of the exposure and background cubes. At least 2 spatial are required.
|
545 |
psf_binsz = 10.0 * binsz
|
546 |
psf_nxpix = max(nxpix // 10, 2) # Make sure result is int |
547 |
psf_nypix = max(nypix // 10, 2) # Make sure result is int |
548 |
|
549 |
# Create exposure cube
|
550 |
expcube = ctexpcube(obs) |
551 |
expcube['incube'] = 'NONE' |
552 |
expcube['usepnt'] = usepnt
|
553 |
expcube['ebinalg'] = 'LOG' |
554 |
expcube['binsz'] = binsz
|
555 |
expcube['nxpix'] = nxpix
|
556 |
expcube['nypix'] = nypix
|
557 |
expcube['enumbins'] = _enumbins
|
558 |
expcube['emin'] = emin
|
559 |
expcube['emax'] = emax
|
560 |
expcube['coordsys'] = coordsys
|
561 |
expcube['proj'] = proj
|
562 |
expcube['addbounds'] = addbounds
|
563 |
expcube['debug'] = debug
|
564 |
expcube['chatter'] = chatter
|
565 |
if not usepnt: |
566 |
expcube['xref'] = xref
|
567 |
expcube['yref'] = yref
|
568 |
if log:
|
569 |
expcube.logFileOpen() |
570 |
expcube.run() |
571 |
|
572 |
# Create point spread function cube
|
573 |
psfcube = ctpsfcube(obs) |
574 |
psfcube['incube'] = 'NONE' |
575 |
psfcube['usepnt'] = usepnt
|
576 |
psfcube['ebinalg'] = 'LOG' |
577 |
psfcube['binsz'] = psf_binsz
|
578 |
psfcube['nxpix'] = psf_nxpix
|
579 |
psfcube['nypix'] = psf_nypix
|
580 |
psfcube['enumbins'] = _enumbins
|
581 |
psfcube['emin'] = emin
|
582 |
psfcube['emax'] = emax
|
583 |
psfcube['coordsys'] = coordsys
|
584 |
psfcube['proj'] = proj
|
585 |
psfcube['addbounds'] = addbounds
|
586 |
psfcube['debug'] = debug
|
587 |
psfcube['chatter'] = chatter
|
588 |
if not usepnt: |
589 |
psfcube['xref'] = xref
|
590 |
psfcube['yref'] = yref
|
591 |
if log:
|
592 |
psfcube.logFileOpen() |
593 |
psfcube.run() |
594 |
|
595 |
# Create background cube
|
596 |
bkgcube = ctbkgcube(obs) |
597 |
bkgcube['incube'] = 'NONE' |
598 |
bkgcube['usepnt'] = usepnt
|
599 |
bkgcube['ebinalg'] = 'LOG' |
600 |
bkgcube['binsz'] = binsz
|
601 |
bkgcube['nxpix'] = nxpix
|
602 |
bkgcube['nypix'] = nypix
|
603 |
bkgcube['enumbins'] = _enumbins
|
604 |
bkgcube['emin'] = emin
|
605 |
bkgcube['emax'] = emax
|
606 |
bkgcube['coordsys'] = coordsys
|
607 |
bkgcube['proj'] = proj
|
608 |
bkgcube['addbounds'] = addbounds
|
609 |
bkgcube['debug'] = debug
|
610 |
bkgcube['chatter'] = chatter
|
611 |
if not usepnt: |
612 |
bkgcube['xref'] = xref
|
613 |
bkgcube['yref'] = yref
|
614 |
if log:
|
615 |
bkgcube.logFileOpen() |
616 |
bkgcube.run() |
617 |
|
618 |
# If energy dispersion is requested then create energy dispersion cube
|
619 |
if edisp:
|
620 |
edispcube = ctedispcube(obs) |
621 |
edispcube['incube'] = 'NONE' |
622 |
edispcube['usepnt'] = usepnt
|
623 |
edispcube['ebinalg'] = 'LOG' |
624 |
edispcube['binsz'] = psf_binsz
|
625 |
edispcube['nxpix'] = psf_nxpix
|
626 |
edispcube['nypix'] = psf_nypix
|
627 |
edispcube['enumbins'] = _enumbins
|
628 |
edispcube['emin'] = emin
|
629 |
edispcube['emax'] = emax
|
630 |
edispcube['coordsys'] = coordsys
|
631 |
edispcube['proj'] = proj
|
632 |
edispcube['addbounds'] = addbounds
|
633 |
edispcube['debug'] = debug
|
634 |
edispcube['chatter'] = chatter
|
635 |
if not usepnt: |
636 |
edispcube['xref'] = xref
|
637 |
edispcube['yref'] = yref
|
638 |
if log:
|
639 |
edispcube.logFileOpen() |
640 |
edispcube.run() |
641 |
|
642 |
# Build response dictionary
|
643 |
response = {} |
644 |
response['expcube'] = expcube.expcube().copy()
|
645 |
response['psfcube'] = psfcube.psfcube().copy()
|
646 |
response['bkgcube'] = bkgcube.bkgcube().copy()
|
647 |
response['models'] = bkgcube.models().copy()
|
648 |
if edisp:
|
649 |
response['edispcube'] = edispcube.expcube().copy()
|
650 |
|
651 |
# Return response cubes
|
652 |
return response
|
653 |
|
654 |
# ================================= #
|
655 |
# Get stacked observation container #
|
656 |
# ================================= #
|
657 |
def get_stacked_obs(cls, obs): |
658 |
"""
|
659 |
Bin an observation and return an observation container with a single
|
660 |
binned observation
|
661 |
|
662 |
Parameters
|
663 |
----------
|
664 |
cls : `~cscript`
|
665 |
cscript class
|
666 |
obs : `~gammalib.GObservations`
|
667 |
Observation container
|
668 |
|
669 |
Returns
|
670 |
-------
|
671 |
obs : `~gammalib.GObservations`
|
672 |
Observation container where the first observation is a binned observation
|
673 |
"""
|
674 |
# Write header
|
675 |
if cls._logExplicit():
|
676 |
cls._log.header3('Binning events')
|
677 |
|
678 |
# Bin events
|
679 |
cntcube = ctbin(obs) |
680 |
cntcube['usepnt'] = False |
681 |
cntcube['ebinalg'] = 'LOG' |
682 |
cntcube['xref'] = cls['xref'].real() |
683 |
cntcube['yref'] = cls['yref'].real() |
684 |
cntcube['binsz'] = cls['binsz'].real() |
685 |
cntcube['nxpix'] = cls['nxpix'].integer() |
686 |
cntcube['nypix'] = cls['nypix'].integer() |
687 |
cntcube['enumbins'] = cls['enumbins'].integer() |
688 |
cntcube['emin'] = cls['emin'].real() |
689 |
cntcube['emax'] = cls['emax'].real() |
690 |
cntcube['coordsys'] = cls['coordsys'].string() |
691 |
cntcube['proj'] = cls['proj'].string() |
692 |
cntcube.run() |
693 |
|
694 |
# Write header
|
695 |
if cls._logExplicit():
|
696 |
cls._log.header3('Creating stacked response')
|
697 |
|
698 |
# Get stacked response
|
699 |
response = get_stacked_response(obs, |
700 |
cls['xref'].real(),
|
701 |
cls['yref'].real(),
|
702 |
binsz=cls['binsz'].real(),
|
703 |
nxpix=cls['nxpix'].integer(),
|
704 |
nypix=cls['nypix'].integer(),
|
705 |
emin=cls['emin'].real(),
|
706 |
emax=cls['emax'].real(),
|
707 |
enumbins=cls['enumbins'].integer(),
|
708 |
edisp=cls['edisp'].boolean(),
|
709 |
coordsys=cls['coordsys'].string(),
|
710 |
proj=cls['proj'].string())
|
711 |
|
712 |
# Retrieve a new oberservation container
|
713 |
new_obs = cntcube.obs().copy() |
714 |
|
715 |
# Set stacked response
|
716 |
if cls['edisp'].boolean(): |
717 |
new_obs[0].response(response['expcube'], response['psfcube'], |
718 |
response['edispcube'], response['bkgcube']) |
719 |
else:
|
720 |
new_obs[0].response(response['expcube'], response['psfcube'], |
721 |
response['bkgcube'])
|
722 |
|
723 |
# Get new models
|
724 |
models = response['models']
|
725 |
|
726 |
# Set models for new oberservation container
|
727 |
new_obs.models(models) |
728 |
|
729 |
# Return new oberservation container
|
730 |
return new_obs
|
731 |
|
732 |
# =================== #
|
733 |
# Make a simple model #
|
734 |
# =================== #
|
735 |
def make_simple_model(models,name,ra,dec,flux,idx,num_obs,instru='CTA'): |
736 |
|
737 |
# Add one background model for each observation
|
738 |
for i_obs in range(num_obs): |
739 |
model_name="Background %d" % i_obs
|
740 |
models = add_background_model(models,bgd_sigma=0.0,instru=instru,id="%d" % i_obs,name=model_name) |
741 |
set_fit_param(models[model_name],parname=['Sigma','Normalization','Prefactor','Index'],parfit=[False,True,True,False]) |
742 |
# Add source model
|
743 |
model_name="Test source"
|
744 |
models = add_source_model(models, name, ra, dec, flux=flux, index=idx, pivot=1.0e6, type="point", pivot_scale=1e6, flux_scale=1.0e-16) |
745 |
set_fit_param(models[model_name],parname=['Prefactor','Index','RA','DEC'],parfit=[True,True,False,False]) |
746 |
|
747 |
# Exit
|
748 |
return models
|
749 |
|
750 |
# =================== #
|
751 |
# Make a simple model #
|
752 |
# =================== #
|
753 |
def reset_simple_model(models,ra,dec,flux,idx,num_obs): |
754 |
|
755 |
# Add background model (one for each pointing)
|
756 |
for i_obs in range(num_obs): |
757 |
model_name="Background %d" % i_obs
|
758 |
set_fit_param(models[model_name],parname=['Sigma','Normalization','Prefactor','Index'],parfit=[False,True,True,False],parval=[4.0,1.0,1.0,0.0]) |
759 |
set_fit_param(models["Test source"],parname=['Prefactor','Index','RA','DEC'],parfit=[True,True,False,False],parval=[flux,idx,ra,dec]) |
760 |
|
761 |
# Exit
|
762 |
return models
|
763 |
|
764 |
# ===================== #
|
765 |
# Print out fit results #
|
766 |
# ===================== #
|
767 |
def print_fit_results(obslist): |
768 |
|
769 |
# Print
|
770 |
num_obs=len(obslist)
|
771 |
for i_obs in range(num_obs): |
772 |
model_name="Background %d" % i_obs
|
773 |
print "%s prefactor: %.3e +/- %.3e " % (model_name,obslist.models()[model_name]['Prefactor'].value(),obslist.models()[model_name]['Prefactor'].error()) |
774 |
print "%s index: %.3e +/- %.3e " % (model_name,obslist.models()[model_name]['Index'].value(),obslist.models()[model_name]['Index'].error()) |
775 |
print "Source prefactor: %.3e +/- %.3e " % (obslist.models()["Test source"]['Prefactor'].value(),obslist.models()["Test source"]['Prefactor'].error()) |
776 |
print "Source index: %.3e +/- %.3e " % (obslist.models()["Test source"]['Index'].value(),obslist.models()["Test source"]['Index'].error()) |
777 |
print "Fit ended with value %.3e and status %d after %d iterations." % (opt.value(),opt.status(),opt.iter()) |
778 |
|
779 |
# Exit
|
780 |
return
|
781 |
|
782 |
|
783 |
#=====================#
|
784 |
# Routine entry point #
|
785 |
#=====================#
|
786 |
if __name__ == '__main__': |
787 |
|
788 |
"""
|
789 |
Aims:
|
790 |
This script tests the ON-OFF analysis functionality of the gammalib
|
791 |
|
792 |
Arguments:
|
793 |
- None
|
794 |
|
795 |
Notes:
|
796 |
- Hard-coded source, observations, and regions (more flexible script later...)
|
797 |
- Approximate method to set OFF regions (works only on equator)
|
798 |
- Have to set up path to data base (db and irf)
|
799 |
- Source flux set as flux density at 1TeV
|
800 |
"""
|
801 |
|
802 |
# Job parameters
|
803 |
|
804 |
# CTA
|
805 |
db="prod2"
|
806 |
irf="South_50h"
|
807 |
bgd=""
|
808 |
duration=1800.0
|
809 |
rad=5.0
|
810 |
# Source
|
811 |
name="Test source"
|
812 |
ra=0.0
|
813 |
dec=0.0
|
814 |
flux=5.0e-17
|
815 |
idx=-2.5
|
816 |
dflux=1.5 # Offset factor by which true value is rescaled before fit |
817 |
didx=0.3 # Offset factor by which true value is rescaled before fit |
818 |
# Observations
|
819 |
num_obs=2
|
820 |
emin=0.1
|
821 |
emax=100.0
|
822 |
nbins=9
|
823 |
onshift=1.0
|
824 |
onsize=0.3
|
825 |
noff=3
|
826 |
nx=500
|
827 |
ny=500
|
828 |
dx=0.02
|
829 |
dy=0.02
|
830 |
npix=nx*ny |
831 |
# Others
|
832 |
chatter=4
|
833 |
seed=10
|
834 |
|
835 |
# Debug
|
836 |
print 'Got params...' |
837 |
|
838 |
# Create observations container
|
839 |
obslist = GObservations() |
840 |
dirlist = [] |
841 |
for i_obs in range(num_obs): |
842 |
obsdir = GSkyDir() |
843 |
obsdir.radec_deg(ra+((-1)**i_obs)*onshift, dec)
|
844 |
obs=single_obs(obsdir, tstart=0.0, duration=duration, deadc=0.95, \ |
845 |
emin=emin, emax=emax, rad=rad, \ |
846 |
irf=irf, caldb=db, id="%d" % i_obs, name="Obs%d" % i_obs, instrument="CTA") |
847 |
obslist.append(obs) |
848 |
dirlist.append(obsdir) |
849 |
print 'Created %d observations...' % (num_obs) |
850 |
|
851 |
# Create model container
|
852 |
models = GModels() |
853 |
models_onoff_fit = GModels() |
854 |
# Make a simple model for two observations
|
855 |
models = make_simple_model(models,name,ra,dec,flux,idx,num_obs,instru='CTA')
|
856 |
models_onoff_fit = make_simple_model(models_onoff_fit,name,ra,dec,flux,idx,num_obs,instru='CTAOnOff')
|
857 |
# Add model to the container
|
858 |
obslist.models(models) |
859 |
obslist.models().save("sim_model.xml")
|
860 |
print 'Created source and background model...' |
861 |
|
862 |
# Make events list
|
863 |
obslist = sim(obslist, log=False, debug=False, chatter=chatter, edisp=False, seed=seed, nbins=0) |
864 |
# Save events and observations
|
865 |
for obs in obslist: |
866 |
obs.save("%s_events.fits" % obs.name())
|
867 |
obslist.save("UNBINNED-observations.xml")
|
868 |
print 'Created events list...' |
869 |
# Make counts cube
|
870 |
bin = ctbin(obslist) |
871 |
bin['ebinalg'] = 'LOG' |
872 |
bin['emin'] = emin |
873 |
bin['emax'] = emax |
874 |
bin['enumbins'] = nbins |
875 |
bin['usepnt'] = True |
876 |
bin['nxpix'] = 200 |
877 |
bin['nypix'] = 200 |
878 |
bin['binsz'] = 0.05 |
879 |
bin['coordsys'] = 'CEL' |
880 |
bin['proj'] = 'TAN' |
881 |
bin['outcube'] = "cntcube.fits" |
882 |
bin.run()
|
883 |
bin.save()
|
884 |
print 'Created events cube...' |
885 |
|
886 |
# Make ON map
|
887 |
srcdir = GSkyDir() |
888 |
srcdir.radec_deg(ra,dec) |
889 |
onmap= GSkyMap('TAN','CEL',ra,dec,dx,dy,nx,ny,1) |
890 |
# Loop over all pixels in map and flag those falling into regions
|
891 |
for i in range(npix): |
892 |
pixdir=onmap.inx2dir(i) |
893 |
if pixdir.dist_deg(srcdir) < onsize:
|
894 |
onmap[i,0]= 1.0 |
895 |
# Make ON region
|
896 |
onreg=GSkyRegionMap(onmap) |
897 |
print 'Created ON map' |
898 |
|
899 |
# Make OFF maps
|
900 |
offregs=[] |
901 |
for i_obs in range(num_obs): |
902 |
offmap= GSkyMap('TAN','CEL',ra,dec,dx,dy,nx,ny,1) |
903 |
offdirs=[] |
904 |
# Prepare directions to circular OFF regions
|
905 |
for i in range(noff): |
906 |
phi=(i+1)*360./(noff+1.0) |
907 |
offdir = GSkyDir(dirlist[i_obs]) |
908 |
offdir.rotate_deg(phi-((-1)**i_obs)*90., onshift) |
909 |
offdirs.append(offdir) |
910 |
# Loop over all pixels in map and flag those falling into OFF regions
|
911 |
for i in range(npix): |
912 |
# Direction to pixel in map
|
913 |
pixdir=offmap.inx2dir(i) |
914 |
# Loop over centers of OFF regions and compute distance to this pixel
|
915 |
for cendir in offdirs: |
916 |
if pixdir.dist_deg(cendir) < onsize:
|
917 |
offmap[i,0]= 1.0 |
918 |
break
|
919 |
# Make a sky region out of the map
|
920 |
offregs.append(GSkyRegionMap(offmap)) |
921 |
|
922 |
# Define energy binning for ON-OFF observations
|
923 |
etrue = GEbounds(nbins, GEnergy(emin, "TeV"), GEnergy(emax, "TeV")) |
924 |
ereco = GEbounds(nbins, GEnergy(emin, "TeV"), GEnergy(emax, "TeV")) |
925 |
|
926 |
# Make and save ON-OFF observations
|
927 |
onofflist = GObservations() |
928 |
for obs,offreg,i_obs in zip(obslist,offregs,range(num_obs)): |
929 |
onoffobs=GCTAOnOffObservation(obs, etrue, ereco, onreg, offreg, srcdir) |
930 |
onoffobs.name("Obs%d" % i_obs)
|
931 |
onoffobs.id("%d" % i_obs)
|
932 |
onoffobs.arf().save("ARF_%d.fits" % i_obs)
|
933 |
onoffobs.rmf().save("RMF_%d.fits" % i_obs)
|
934 |
onoffobs.on_spec().save("ON-spectrum_%d.fits" % i_obs)
|
935 |
onoffobs.off_spec().save("OFF-spectrum_%d.fits" % i_obs)
|
936 |
onoffobs.on_regions().map().save("ON-regions_%d.fits" % i_obs)
|
937 |
onoffobs.off_regions().map().save("OFF-regions_%d.fits" % i_obs)
|
938 |
onofflist.append(onoffobs) |
939 |
# Save ON-OFF observations
|
940 |
onofflist.save("ONOFF-observations.xml")
|
941 |
print "Created and saved ON-OFF observations" |
942 |
|
943 |
# Print ON-OFF data
|
944 |
for oneobs in onofflist: |
945 |
print "\nObservation %s with %.1fs ON time" % (oneobs.name(),oneobs.ontime()) |
946 |
for i in range(nbins): |
947 |
print "ONobs %d - OFFobs %d - Aeff %.2e - Bgd %.2e" % (oneobs.on_spec()[i],oneobs.off_spec()[i],oneobs.arf()[i],oneobs.arf()['BACKGROUND'][i]) |
948 |
print '\n' |
949 |
|
950 |
# Reset model container and append it to observation list
|
951 |
models_onoff_fit=reset_simple_model(models_onoff_fit,ra,dec,dflux*flux,idx-didx,num_obs) |
952 |
onofflist.models(models_onoff_fit) |
953 |
|
954 |
# Set optimizer and logger
|
955 |
log=GLog('fit_onoff.log',True) |
956 |
log.chatter(chatter) |
957 |
opt=GOptimizerLM(log) |
958 |
|
959 |
# Optimize for ON-OFF analysis
|
960 |
print '\nON-OFF fitting...' |
961 |
onofflist.optimize(opt) |
962 |
onofflist.errors(opt) |
963 |
print_fit_results(onofflist) |
964 |
|
965 |
# Free memory
|
966 |
del opt
|
967 |
del log
|
968 |
|
969 |
# Reset model container and append it to observation list
|
970 |
models=reset_simple_model(models,ra,dec,dflux*flux,idx-didx,num_obs) |
971 |
obslist.models(models) |
972 |
|
973 |
# Set optimizer and logger
|
974 |
log=GLog('fit_unbinned.log',True) |
975 |
log.chatter(chatter) |
976 |
opt=GOptimizerLM(log) |
977 |
|
978 |
# Optimize for Fermi-style analysis
|
979 |
print '\nUnbinned fitting...' |
980 |
obslist.optimize(opt) |
981 |
obslist.errors(opt) |
982 |
print_fit_results(obslist) |
983 |
|
984 |
# Free memory
|
985 |
del opt
|
986 |
del log
|
987 |
|
988 |
|