test_OnOff_8th.py

Martin Pierrick, 10/13/2017 09:23 AM

Download (32.6 KB)

 
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