csupperlimit.py

Mayer Michael, 07/09/2014 02:43 PM

Download (9.17 KB)

 
1
#! /usr/bin/env python
2
# ==========================================================================
3
# This script generates the pull distribution for all model parameters.
4
#
5
# Copyright (C) 2011-2014 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

    
22
import gammalib as gl
23
import ctools as ct
24
from scipy import optimize
25
import sys
26
import math
27

    
28

    
29
# ============ #
30
# csupperlimit class #
31
# ============ #
32
class csupperlimit(gl.GApplication):
33
    """
34
    This class implements an the computation of an upper limit value
35
    using a standard bayesian approach. It derives from the 
36
    GammaLib::GApplication class which provides support for parameter
37
    files, command line arguments, and logging. In that way the Python
38
    script behaves just as a regular ctool.
39
    """
40
    def __init__(self, *argv):
41

    
42
        """
43
        Constructor.
44
        """
45

    
46
        # Set name
47
        self.name    = "csupperlimit"
48
        self.version = "0.0.1"
49
         
50
        # Initialise some members
51
        if isinstance(argv[0],gl.GObservations):
52
            self.obs = argv[0]
53
        else:      
54
            self.obs      = gl.GObservations()
55
        
56
        self.source = "CrabNebula"
57
        self.parname = "Prefactor"
58
        self.confidence = 0.95
59
        self.bestloglike = 0.0
60
        
61

    
62
        
63
        # Make sure that parfile exists
64
        file = self.parfile()
65

    
66
        # Initialise application
67
        if len(argv) == 1:
68
            gl.GApplication.__init__(self, self.name, self.version)
69
        elif len(argv) ==2:
70
            gl.GApplication.__init__(self, self.name, self.version, argv[1:])
71
        else:
72
            raise TypeError("Invalid number of arguments given.")
73
        
74
        # Set logger properties
75
        self.log_header()
76
        self.log.date(True)
77
 
78
        # Return
79
        return
80
    
81
    def __del__(self):
82
        """
83
        Destructor.
84
        """
85
        #  Write separator into logger
86
        if self.logTerse():
87
            self.log("\n")
88
        
89
        # Return
90
        return
91

    
92
    def parfile(self):
93
        """
94
        Check if parfile exists. If parfile does not exist then create a
95
        default parfile. This kluge avoids shipping the cscript with a parfile.
96
        """
97
        # Set parfile name
98
        parfile = self.name+".par"
99
        
100
        try:
101
            pars = gl.GApplicationPars(parfile)
102
        except:
103
            # Signal if parfile was not found
104
            sys.stdout.write("Parfile "+parfile+" not found. Create default parfile.\n")
105
            
106
            # Create default parfile
107
            pars = gl.GApplicationPars()
108
            pars.append(gl.GApplicationPar("infile", "f", "a", "events.fits","","","observation definition file"))
109
            pars.append(gl.GApplicationPar("stat","s","h","POISSON","","","Optimization statistics"))
110
            pars.append(gl.GApplicationPar("refit",  "b", "h", "no","","", "Do refitting?"))
111
            pars.append(gl.GApplicationPar("srcmdl","f","a","$CTOOLS/share/models/crab.xml","","","Source model"))
112
            pars.append(gl.GApplicationPar("caldb","s","h","","","","Calibration database"))
113
            pars.append(gl.GApplicationPar("irf","s","a","cta_dummy_irf","","","Instrument response function"))
114
            pars.append(gl.GApplicationPar("edisp","b","h","no","","","Apply energy dispersion?"))
115
            pars.append(gl.GApplicationPar("logfile", "f", "h", "csupperlimit.log","","", "Log filename"))
116
            pars.append_standard()
117
            pars.save(parfile)
118
        
119
        # Return
120
        return
121

    
122

    
123
    def _func(self,value): 
124
        # get spectral model
125
        spec = self.obs.models()[self.source].spectral()
126
        
127
        # get parmeter value
128
        val = value[0]*spec[self.parname].scale()
129
        
130
        # ensure value to be above minimum boundary
131
        if val <= spec[self.parname].min():
132
            return 0.0-self.bestloglike-self.dloglike
133
        
134
        # set value
135
        spec[self.parname].value(val)
136
        
137
        # Recalculate likelihood
138
        like = ct.ctlike(self.obs)
139
        like.run()
140
        logl = like.opt().value()   
141
        
142
        # Return likelihood difference
143
        return (logl-self.bestloglike-self.dloglike)
144

    
145
        
146
    def get_parameters(self):
147
        """
148
        Get parameters from parfile and setup the observation.
149
        """
150
         
151
        # Setup obs if it isn't already set
152
        if self.obs.size() == 0:
153
            
154
            infile = self["infile"].filename()
155
            try:      
156
                self.obs = gl.GObservations(infile)
157
            except:
158
                obs0 = gl.GCTAObservation(infile)
159
                self.obs.append(obs0)
160
            mdlfile = self["srcmdl"].filename()
161
            self.obs.models(mdlfile)
162
            self.m_caldb    = self["caldb"].string()
163
            self.m_irf      = self["irf"].string()
164
            
165
        else:
166
            self.m_caldb = ""
167
            self.m_irf = ""
168
            
169
        # get hidden parameters
170
        self.m_stat = self["stat"].string()
171
        self.m_refit = self["refit"].boolean()
172
        self.m_edisp = self["edisp"].boolean()
173
        self.m_chatter = self["chatter"].integer()
174
        self.m_debug = self["debug"].boolean()
175
        self.m_clobber = self["clobber"].boolean()
176
        self.m_mode = self["mode"].string()
177
        self.m_logfile = self["logfile"].filename()
178
            
179
        # get delta log-like
180
        # this has to be improved and made more flexible    
181
        if self.confidence == 0.95:
182
            self.dloglike = 3.84/2.0
183
       
184
    def execute(self):
185
        """
186
        Execute the script.
187
        """
188
        # Run the script
189
        self.run()
190
         
191
        # Return
192
        return
193

    
194

    
195
    def obs(self):
196
        """
197
        Return observations
198
        """
199
        return self.obs.clone()    
200
    
201

    
202
    def run(self):
203
        """
204
        Run the fit.
205
        """
206
 
207
        # Switch screen logging on in debug mode
208
        if self.logDebug():
209
            self.log.cout(True)
210

    
211
        
212
        # Get parameters
213
        self.get_parameters()
214
 
215
        #  Write input parameters into logger
216
        if self.logTerse():
217
            self.log_parameters()
218
            self.log("\n")
219
        
220
        # Write observation into logger
221
        if self.logTerse():
222
            self.log("\n")
223
            self.log.header1("Observation")
224
            self.log(str(self.obs))
225
            self.log("\n")
226

    
227
        # Write header
228
        if self.logTerse():
229
            self.log("\n")
230
            self.log.header1("Generate pull distribution")
231

    
232
        # Check if observation container is already optimised
233
        # might add a flag in the future for that
234
        if self.obs.function().value() == 0.0:
235
            print "Optimising observation container"
236
            like = ct.ctlike(self.obs)
237
            like.run()
238
            self.bestloglike = like.opt().value()
239
            self.obs.models(like.obs().models().clone())
240
        
241
 
242
        # Fix all parameters
243
        for model in self.obs.models():
244
            for par in model:
245
                par.fix()
246
        
247
        # get spectral parameters and set start value
248
        spectral = self.obs.models()[self.source].spectral()
249
        value = spectral[self.parname].value()
250
        error = spectral[self.parname].error()
251
        scale = spectral[self.parname].scale()
252
        
253
        # set start value to 2 sigma above fit value
254
        self.startpar = value / scale + 2* error/scale
255

    
256
        # calculate best loglike value if not done above
257
        if not self.bestloglike == 0.0: 
258
            like = ct.ctlike(self.obs)
259
            like.run()
260
            self.bestloglike = like.opt().value()
261
        
262
        # use scipy to find upper limit solution
263
        # The method used here is to determine the parameter value
264
        # where the likelihood function has changed by dloglike
265
        sol = optimize.root(self._func, self.startpar, method='hybr')
266
        
267
        # Test for convergence
268
        if sol.success:
269
            self.ulimit = sol.x[0]*spectral[self.parname].scale()
270
            if self.m_chatter>0:
271
                print "Upper limit of parameter \""+self.parname +"\" of source \""+self.source+"\" determined to "+str(self.ulimit)
272
        else:
273
            self.ulimit = 0.0 
274
            if self.m_chatter >0:
275
                print "Upper limit could not be determined"
276

    
277

    
278
       
279
        
280

    
281

    
282
# ======================== #
283
# Main routine entry point #
284
# ======================== #
285
if __name__ == '__main__':
286
    """
287
    Generates upperlimit for a source model.
288
    """
289
    # Create instance of application
290
    app = csupperlimit(sys.argv)
291
     
292
    # Open logfile
293
    app.logFileOpen()
294
     
295
    # Execute application
296
    app.execute()
297