csupperlimit.py
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 |
|