analysisutils.py

Sanchez David, 11/05/2014 09:45 AM

Download (19.8 KB)

 
1
# ==========================================================================
2
# This script provides a number of functions that are useful for handling
3
# CTA observations.
4
#
5
# Copyright (C) 2011-2014 David Sanchez Michael Mayer Rolf Buehler 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 ctools as ct
22
import gammalib as gl
23
from math import sqrt,log10,floor
24
import sys
25

    
26
class base(object):
27
    def __init__(self):
28
        self.classname = self.__class__.__name__
29
        self.errorcolor = "\033[31m"#red
30
        self.infocolor = "\033[34m"#blue
31
        self.warningcolor = "\033[33m"#yellow
32
        self.successcolor = "\033[32m"#green
33
        self.endcolor = "\033[0m"#reset
34
        self.prependfunction = True
35
        self.basemembers = ["classname","errorcolor","infocolor","warningcolor","successcolor","endcolor","prependfunction"]
36
        
37
    def error(self,message, functionname = ""):
38
        printstring = ""
39
        if functionname == "":
40
            printstring = "\n"+self.errorcolor+"*** Error ["+self.classname+"]: "+message+" ***\n"+self.endcolor
41
        else:
42
            printstring = "\n"+self.errorcolor+"*** Error ["+self.classname+"::"+functionname+"]: "+message+" ***\n"+self.endcolor
43
        sys.exit(printstring)
44
    
45
    def info(self,message,newline=True):
46
        printstring = self.infocolor+"["+self.classname+"]: "+message+self.endcolor     
47
        if newline:
48
            print printstring
49
        else:
50
            print self.infocolor+message+self.endcolor,
51
            sys.stdout.flush()  
52
        
53
    def warning(self,message,functionname = ""):
54
        printstring = ""
55
        if functionname == "":
56
            printstring = self.warningcolor+"["+self.classname+"] Warning: "+message+self.endcolor
57
        else:
58
            printstring = self.warningcolor+"["+self.classname+"::"+functionname+"] Warning: "+message+self.endcolor
59
        print printstring
60
        
61
    def success(self,message):
62
        printstring = self.successcolor+"["+self.classname+"]: "+message+self.endcolor
63
        print printstring
64
        
65
    def progress(self,message="."):
66
        string = self.infocolor+message+self.endcolor
67
        print string
68
        
69
        
70
class ResultsSorage(dict,base):
71
    """class to store the results in the good format"""
72
    def __init__(self,*arg,**kw):
73
        super(ResultsSorage, self).__init__(*arg, **kw)
74
        base.__init__(self)
75
        self["dnde"]      = {}
76
        self["ulim_dnde"] = []
77
        self["time"]      = {}
78
        self["ener"]      = {}
79
        self["TS"]        = []
80
        self["Iflux"]     = {}
81
        
82
        self["time"]["tmin_value"]   = []
83
        self["time"]["tmax_value"]   = []
84
        self["time"]["unit"]       = "sec"
85
        
86
        self["ener"]["value"]      = []
87
        self["ener"]["ed_value"]   = []
88
        self["ener"]["eu_value"]   = []
89
        self["ener"]["unit"]       = "MeV"
90
        
91
        self["dnde"]["value"]      = []
92
        self["dnde"]["ed_value"]   = []
93
        self["dnde"]["eu_value"]   = []
94
        self["dnde"]["unit"]       = "ph/cm2/s/MeV"
95
        
96
        self["Iflux"]["value"]     = []
97
        self["Iflux"]["ed_value"]  = []
98
        self["Iflux"]["eu_value"]  = []
99
        self["Iflux"]["unit"]      = "ph/cm2/s"
100
        
101
    def Print(self):
102
        self.success("Results are :")
103
        for k in self.iterkeys():
104
            self.info("Key name : "+k)
105
            try :
106
                for ki in self[k].iterkeys():
107
                    print "\t",ki,"\t",self[k][ki]
108
            except:
109
                print self[k]
110
        print 
111
        
112
class Analyser(base):
113
    def __init__(self):
114
        super(Analyser,self).__init__()
115
        self.info("Creating "+self.classname+" object")
116
        self.m_obs = None
117
        self.like  = None
118
        
119
        self.m_ebinalg  = "LOG"
120
        self.m_eunit    = "TeV"
121
        self.m_enumbins = 10#nbins
122
        self.m_usepnt   = True # Use pointing for map centre
123
        self.m_coordsys = "CEL"#coord
124
        self.m_proj     = "TAN"#proj
125
        self.m_binned   = False
126
        self.m_edisp    = False
127
        self.m_stat     = "POISSON"#stat
128

    
129
        self.m_source_set = False
130
        self.m_files_set  = False
131
        self.m_time_set   = False
132
        self.m_energy_set = False
133
        self.m_irfs_set   = False
134
        self.m_roi_set    = False
135
        self.m_xml_set    = False
136
        
137
    def set_xml(self,xml):
138
        self.m_xml     = xml
139
        ind = xml.find(".xml")
140
        if not ind==-1:
141
            self.m_xml_out = xml[:xml.find(".xml")]+"_results"+xml[xml.find(".xml"):]
142
        self.m_xml_set = True
143
        
144
    def set_roi(self,roi):
145
        self.m_roi      = float(roi)
146
        self.m_nxpix    = int(self.m_roi*100)#npix
147
        self.m_nypix    = int(self.m_roi*100)#npix
148
        self.m_binsz    = 0.05#binsz
149
        self.m_roi_set  = True
150

    
151
    def set_irfs(self,caldb,irf):
152
        self.m_caldb    = caldb
153
        self.m_irf      = irf
154
        self.m_irfs_set = True
155

    
156
    def set_Fitsfiles(self,evtfile,tag="CTA_analysis"):
157
        self.m_evtfile   = "event_selected_"+tag+".fits"    
158
        self.m_raw_evt   = evtfile
159
        self.m_cntfile   = "countmap_"+tag+".fits"
160
        self.m_files_set = True
161

    
162
    def set_source(self,name,ra,dec):
163
        self.m_ra         = ra
164
        self.m_dec        = dec
165
        self.m_name       = name
166
        self.m_source_set = True
167

    
168
    def validate(self):
169
        if not self.m_time_set :
170
            self.error("Time range of the observations not set")
171

    
172
        if not self.m_files_set :
173
            self.error("FITS files of the observations not set")
174

    
175
        if not self.m_source_set :
176
            self.error("Source not set")
177

    
178
        if not self.m_energy_set :
179
            self.error("Energy range of the observations not set")
180
            
181
        if not self.m_irfs_set :
182
            self.error("IRFs to use not set")
183

    
184
        if not self.m_roi_set :
185
            self.error("ROI to use not set")
186

    
187
        if not self.m_xml_set :
188
            self.error("XML files for the sky model not set")
189

    
190
    def copy(self,analyse,tag="CTA_analysis_copy"):
191
        self.set_xml(analyse.m_xml)
192
        self.set_irfs(analyse.m_caldb,analyse.m_irf)
193
        self.set_roi(analyse.m_roi)
194
        self.set_time_boundary(analyse.m_tmin,analyse.m_tmax)
195
        self.set_energy_boundary(analyse.m_emin,analyse.m_emax)
196
        self.set_source(analyse.m_name,analyse.m_ra,analyse.m_dec)
197
        self.set_Fitsfiles(analyse.m_evtfile,tag)
198

    
199

    
200
    def set_obs(self,obs):
201
        self.m_obs = obs #set the GObservation container
202

    
203
    def set_time_boundary(self,tmin,tmax):
204
        """Set the start and stop time of the observation"""
205
        self.m_tmin        = float(tmin)
206
        self.m_tmax        = float(tmax)
207
        self.m_time_set    = True
208

    
209
    def set_energy_boundary(self,emin,emax):
210
        """Set the energy bounds of the observation"""
211
        self.m_emin = emin
212
        self.m_emax = emax
213
        ebounds = gl.GEbounds(gl.GEnergy(emin, self.m_eunit), \
214
                                gl.GEnergy(emax, self.m_eunit))
215

    
216
        if self.m_obs:
217
            for obs in self.m_obs:
218
                obs.events().ebounds(ebounds)
219
        self.m_energy_set = True
220
        
221
    def ctselect(self):
222
        # ctselect application and set parameters
223
        self.info("Running ctselect to cut on events")
224
        self.validate()
225
        if self.m_obs:
226
            filter = ct.ctselect(self.m_obs)
227
        else:
228
            filter = ct.ctselect()
229
        
230
        filter["infile"] = self.m_raw_evt               
231
        filter["outfile"] = self.m_evtfile  
232
        filter["usepnt"].boolean(self.m_usepnt) # Use pointing for map centre
233
        filter["ra"]  = self.m_ra
234
        filter["dec"]  = self.m_dec
235
        filter["rad"]  = self.m_roi
236
        filter["tmin"] = self.m_tmin
237
        filter["tmax"] = self.m_tmax
238
        filter["emin"].real(self.m_emin)
239
        filter["emax"].real(self.m_emax)
240
        filter["expr"] = ""
241
        filter.logFileOpen()
242

    
243
        filter.run()
244
        if not(self.m_obs):
245
            filter.save()
246

    
247
        if self.m_obs:
248
            # Make a deep copy of the observation that will be returned
249
            # (the ctbin object will go out of scope one the function is
250
            # left)
251
            self.m_obs = filter.obs().copy()
252
        
253
    def ctbin(self,log=False,debug=False):
254
        # ctbin application and set parameters
255
        self.validate()
256
        self.info("Running ctbin to create count map")
257
        if self.m_obs:
258
            bin = ct.ctbin(self.m_obs)
259
        else:
260
            bin = ct.ctbin()
261
            bin["evfile"] = self.m_evtfile
262
        bin["outfile"] = self.m_cntfile
263
        bin["ebinalg"].string(self.m_ebinalg)
264
        bin["emin"].real(self.m_emin)
265
        bin["emax"].real(self.m_emax)
266
        Nbdecade = log10(self.m_emax)-log10(self.m_emin)#Compute the number of decade
267
        bin["enumbins"].integer(int(self.m_enumbins*Nbdecade))
268
        bin["usepnt"].boolean(self.m_usepnt) # Use pointing for map centre
269
        bin["nxpix"].integer(self.m_nxpix)
270
        bin["nypix"].integer(self.m_nypix)
271
        bin["binsz"].real(self.m_binsz)
272
        bin["coordsys"].string(self.m_coordsys)
273
        bin["proj"].string(self.m_proj)
274
        
275
        # Optionally open the log file
276
        if log:
277
            bin.logFileOpen()
278

    
279
        # Optionally switch-on debugging model
280
        if debug:
281
            bin["debug"].boolean(True)
282

    
283
        # Run ctbin application. This will loop over all observations in
284
        # the container and bin the events in counts maps
285
        bin.execute()
286
        if self.m_obs:
287
            # Make a deep copy of the observation that will be returned
288
            # (the ctbin object will go out of scope one the function is
289
            # left)
290
            self.m_obs = bin.obs().copy()
291
            
292
    def create_fit(self,log=False,debug=False):
293
        self.validate()
294
        # create ctlike instance with given parameters
295
        self.info("Fitting Data using ctlike")
296
        if self.m_obs:
297
            self.like = ct.ctlike(self.m_obs)
298
        else:
299
            self.like = ct.ctlike()
300
            if self.m_binned:
301
                self.like["infile"] = self.m_cntfile
302
            else:
303
                self.like["infile"] = self.m_evtfile
304
                
305
            self.like["stat"].string(self.m_stat)
306
            self.like["caldb"].string(self.m_caldb)
307
            self.like["irf"].string(self.m_irf)
308
        self.like["srcmdl"] = self.m_xml 
309
        self.like["edisp"].boolean(self.m_edisp)
310
        self.like["outmdl"] = self.m_xml_out
311

    
312
        # Optionally open the log file
313
        if log:
314
            self.like.logFileOpen()
315
        # Optionally switch-on debugging model
316
        if debug:
317
            self.like["debug"].boolean(True)
318
            
319
    def fit(self,log=False,debug=False):
320
        self.validate()
321
        if not(self.like):
322
            self.warning("ctlike object not created, creating now")
323
            self.create_fit(log,debug)
324

    
325
        # Run ctlike application.
326
        self.like.run()
327
        # Save the results in XML
328
        self.like.save()
329
        self.success("Fit performed")
330
        
331
    def PrintResults(self,srcname = ""):
332
        self.info("Results of the Fit")
333
        for m in self.like.obs().models():
334
            if srcname == m.name() or srcname=="":
335
                print "Model : "+m.name()
336
                print m
337
                
338
    def GetSrcResuls(self,Res,srcname,E0=None,factor = 1e6,tmin=None,tmax=None):
339
        """function to get and store the results of a fit"""
340
        results = self.GetSrcParams(srcname)
341

    
342
        if 1:
343
        # #Time
344
        # if (tmin is not None) or (tmax is not None) :
345
            # Res["time"]["tmin_value"].append(tmin)
346
            # Res["time"]["tmax_value"].append(tmax)
347
            # Res["time"]["unit"] = "sec"
348
        # else:
349
            Res["time"]["tmin_value"].append(self.m_tmin)
350
            Res["time"]["tmax_value"].append(self.m_tmax)
351
            Res["time"]["unit"] = "sec"
352

    
353
        #Energy
354
        if (E0 is not None):
355
            Res["ener"]["value"].append(E0*factor)
356
            Res["ener"]["ed_value"].append((E0-self.m_emin)*factor)
357
            Res["ener"]["eu_value"].append((self.m_emax-E0)*factor)
358
            Res["ener"]["unit"] = "MeV"
359
        else:
360
            Res["ener"]["value"].append(sqrt(self.m_emin*self.m_emax))
361
            Res["ener"]["ed_value"].append(-self.m_emin+sqrt(self.m_emin*self.m_emax))
362
            Res["ener"]["eu_value"].append(self.m_emax-sqrt(self.m_emin*self.m_emax))
363
            Res["ener"]["unit"] = self.m_eunit
364

    
365
        Res["Iflux"]["value"].append(results["flux"])
366
        Res["Iflux"]["eu_value"].append(results["eflux"])
367
        Res["Iflux"]["ed_value"].append(results["eflux"])
368
        # Res["Iflux"]["unit"] = "unit"
369
        
370
        m = self.GetModel(srcname)
371
        Res["dnde"]["value"].append(m.spectral()[0].value())
372
        Res["dnde"]["eu_value"].append(m.spectral()[0].error())
373
        Res["dnde"]["ed_value"].append(m.spectral()[0].error())
374

    
375
        Res["TS"].append(100)
376

    
377
        UL = UpperLimitComputer(self,srcname) 
378
        UL.run()
379
        Res["ulim_dnde"].append(0)
380
        # del UL
381
        return Res
382

    
383
    def GetSrcParams(self,srcname):
384
        results = {}
385
        found = False
386
        m = self.GetModel(srcname)
387
        results["flux"] = m.spectral().flux(gl.GEnergy(self.m_emin,self.m_eunit),gl.GEnergy(self.m_emax,self.m_eunit))
388
        results["eflux"] = m.spectral().eflux(gl.GEnergy(self.m_emin,self.m_eunit),gl.GEnergy(self.m_emax,self.m_eunit))
389
        for par in m.spectral():
390
            if par.is_free():
391
                results[par.name()] = par.value()
392
                results["e"+par.name()] = par.error()
393
            
394
        return results
395
        
396
    def GetModel(self,srcname):
397
        found = False
398
        for m in self.like.obs().models():
399
            if m.name() == srcname:
400
                return m
401
        self.error("source "+srcname+" not in the source list")
402
            
403
    
404
def MakeEbin(analyse,nbins,srcname,binned = False):
405
    #Store the results
406
    Res = ResultsSorage()
407
    
408
    #check the source name, exit is the srcname is false
409
    analyse.GetModel(srcname)
410
    
411
    #compute the conversion factor to have energy in MeV
412
    Energy_list = {"MeV":1,"GeV":1e3,"TeV":1e6} #TODO
413
    factor = Energy_list[analyse.m_eunit]
414
    
415
    for i in xrange(nbins):
416
        obs_copy = analyse.like.obs().copy()
417
        emin = pow(10,log10(analyse.m_emin) + (log10(analyse.m_emax)-log10(analyse.m_emin))/(nbins)*i)
418
        emax = pow(10,log10(analyse.m_emin) + (log10(analyse.m_emax)-log10(analyse.m_emin))/(nbins)*(i+1))
419
        E0 = pow(10, (log10(emin) + log10(emax)) / 2)
420
        print "Making energy bin between %2.1e"%(emin)+analyse.m_eunit+" and %2.1e"%(emax)+analyse.m_eunit
421

    
422
        # change the PivotEnergy to E0
423
        m = obs_copy.models()[srcname]
424
        for par in m.spectral():
425
            if par.name() == "PivotEnergy":
426
                par.value(E0*factor)
427
                            
428
        ana_bin = Analyser()
429

    
430
        #copy the obs object
431
        ana_bin.set_obs(obs_copy)
432

    
433
        #copy the analyser parameters
434
        ana_bin.copy(analyse)
435

    
436
        # Set energy boundaries
437
        ana_bin.set_energy_boundary(emin,emax)
438
        ana_bin.ctselect()
439

    
440
        # if binned:
441
            # ana_bin.ctbin()
442

    
443
        # Create the fit object
444
        ana_bin.create_fit(log=False,debug=False)
445
        
446
        # Run ctlike application.
447
        ana_bin.like.run()
448

    
449
        #Retrive the results in the good format
450
        Res = ana_bin.GetSrcResuls(Res,srcname,E0,factor)
451
        
452
        del ana_bin
453
    return Res
454
    
455
def MakeLC(analyse,nbins,srcname,binned = False):
456
    #Store the results
457
    Res = ResultsSorage()
458

    
459
    #check the source name, exit is the srcname is false
460
    analyse.GetModel(srcname)
461
    
462
    for i in xrange(nbins):
463
        obs_copy = analyse.like.obs().copy()
464
        tmin = analyse.m_tmin + (analyse.m_tmax-analyse.m_tmin)/(nbins)*i
465
        tmax = analyse.m_tmin + (analyse.m_tmax-analyse.m_tmin)/(nbins)*(i+1)
466
        print "Making time bin between %2.1e"%(tmin)+" and %2.1e"%(tmax)
467
                            
468
        ana_bin = Analyser()
469

    
470
        #copy the obs object
471
        ana_bin.set_obs(obs_copy)
472
        
473
        #copy the analyser parameters
474
        ana_bin.copy(analyse)
475

    
476
        # Set energy boundaries
477
        ana_bin.set_time_boundary(tmin,tmax)
478
        ana_bin.ctselect()
479

    
480
        # if binned:
481
            # ana_bin.ctbin()
482

    
483
        # Create the fit object
484
        ana_bin.create_fit(log=False,debug=False)
485
        
486
        # Run ctlike application.
487
        ana_bin.like.run()
488

    
489
        #Retrive the results in the good format
490
        Res = ana_bin.GetSrcResuls(Res,srcname,tmin=tmin,tmax=tmax)
491
        
492
        del ana_bin
493
    return Res
494
        
495
class UpperLimitComputer(base):
496
    def __init__(self,analyser,srcname,parname = "Prefactor"):
497
        super(UpperLimitComputer,self).__init__()
498
        self.info("Creating "+self.classname+" object")
499
        # self.like = analyser.like.copy()
500
        obs = analyser.like.obs().copy()
501
        self.like = ct.ctlike(obs)
502
        self.succes = False
503

    
504
        # get spectral model
505
        self.model = self.like.obs().models()[srcname]
506
        self.spec = self.model.spectral()
507
        self.parname = parname
508
        self.bestloglike = analyser.like.opt().value()
509
        self.dloglike = 3.84/2.0 #95% CL now
510

    
511
    def _bisec(self,a,b,tol = 1e-12):
512
        """iterative function to find the roots"""
513
        if (self._func(a)==0.):
514
            return a
515
        elif (self._func(b)==0):
516
            return b
517
        elif (self._func(b)*self._func(a)>0.):
518
            self.warning("Finding root for upper limit calculation failed")
519
            return 0
520
        mid = (a+b)/2
521
        err = abs(a-b)/2
522
        fm = self._func(mid)
523
        if (err<tol or self._func(mid)==0.):
524
            self.succes = True
525
            return mid
526
        elif (fm*self._func(a)<0):
527
            b = mid
528
        else :
529
            a = mid
530
        return self._bisec(a,b)
531
           
532
    def _func(self,value): 
533
        """function to be minimized"""
534
        # get parmeter value
535
        val = value*self.spec[self.parname].scale()
536
        
537
        # ensure value to be above minimum boundary
538
        if val <= self.spec[self.parname].min():
539
            return 0.0-self.self.bestloglike-self.dloglike
540
        
541
        # set value
542
        self.spec[self.parname].value(val)
543
        
544
        # Recalculate likelihood
545
        self.like.run()
546
        logl = self.like.opt().value()   
547
        
548
        # Return likelihood difference
549
        return (logl-self.bestloglike-self.dloglike)
550
    
551
    def run(self):
552
        
553
        # get spectral parameters and set start value
554
        value = self.spec[self.parname].value()
555
        error = self.spec[self.parname].error()
556
        scale = self.spec[self.parname].scale()
557

    
558
        #Fix all parameters
559
        for model in self.like.obs().models():
560
            for par in model:
561
                par.fix()
562
                
563
        
564
        # set start value to 2 sigma above fit value
565
        self.startpar = value / scale + 2* error/scale
566

    
567
        # set start (a) and (b) depend on the CL asked
568
        a= value / scale +  (self.dloglike/4.)*error/scale
569
        b=value / scale + (self.dloglike*2)* error/scale
570
        ul = self._bisec(a,b)
571
        
572
        self.ulimit = ul*self.spec[self.parname].scale()
573
        # Test for convergence
574
        if self.succes:
575
            self.success("Upper limit of parameter \""+self.parname +"\"  determined to "+str(self.ulimit))