Commit 4819414f598f98c03cabdc3be3bac865f845a7c1

Authored by Jürgen Knödlseder
2 parents e3a0f9eb 7c45f419

Merge branch '1364-write-csspec' into integration

Conflicts:
	scripts/Makefile.am
scripts/Makefile.am
... ... @@ -6,13 +6,13 @@ binscripts = cta_kb_bkgrates.py cta_root2irf.py cta_make_nodes.py \
6 6  
7 7 # Scripts to be distributed in pythondir
8 8 pythonscripts = cspull.py cssens.py cstsdist.py cscaldb.py csresmap.py \
9   - csobsdef.py \
  9 + csobsdef.py csspec.py \
10 10 lsmodel.py \
11 11 obsutils.py
12 12  
13 13 # Scripts for which symbolic links will be created in bindir
14 14 linkscripts = cspull cssens cstsdist cscaldb csresmap \
15   - csobsdef \
  15 + csobsdef csspec \
16 16 lsmodel
17 17  
18 18 # Distribute scripts
... ...
scripts/csspec.py 0 → 100755
  1 +#! /usr/bin/env python
  2 +# ==========================================================================
  3 +# Spectral points generation script.
  4 +#
  5 +# Copyright (C) 2014-2015 Michael Mayer
  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 gammalib
  22 +import ctools
  23 +import sys
  24 +
  25 +# ============== #
  26 +# cstsdist class #
  27 +# ============== #
  28 +class csspec(ctools.cscript):
  29 + """
  30 + This class implements the creation of spectral points. It derives from
  31 + the ctools.cscript class which provides support for parameter files,
  32 + command line arguments, and logging. In that way the Python script
  33 + behaves just as a regular ctool.
  34 + """
  35 + def __init__(self, *argv):
  36 + """
  37 + Constructor.
  38 + """
  39 +
  40 + # Set name
  41 + self.name = "csspec"
  42 + self.version = "1.0.0"
  43 +
  44 + # Initialise some members
  45 + self.obs = None
  46 +
  47 + # Initialise some members
  48 + if isinstance(argv[0],gammalib.GObservations):
  49 + self.obs = argv[0]
  50 + argv = argv[1:]
  51 + else:
  52 + self.obs = gammalib.GObservations()
  53 + self.obs.clear()
  54 + self.m_outfile = ""
  55 +
  56 + # Make sure that parfile exists
  57 + file = self.parfile()
  58 +
  59 + # Initialise application
  60 + if len(argv) == 0:
  61 + ctools.cscript.__init__(self, self.name, self.version)
  62 + elif len(argv) ==1:
  63 + ctools.cscript.__init__(self, self.name, self.version, *argv)
  64 + else:
  65 + raise TypeError("Invalid number of arguments given.")
  66 +
  67 + # Set logger properties
  68 + self.log_header()
  69 + self.log.date(True)
  70 +
  71 + # Return
  72 + return
  73 +
  74 + def __del__(self):
  75 + """
  76 + Destructor.
  77 + """
  78 + # Write separator into logger
  79 + if self.logTerse():
  80 + self.log("\n")
  81 +
  82 + # Return
  83 + return
  84 +
  85 + def parfile(self):
  86 + """
  87 + Check if parfile exists. If parfile does not exist then create a
  88 + default parfile. This kluge avoids shipping the cscript with a parfile.
  89 + """
  90 + # Set parfile name
  91 + parfile = self.name+".par"
  92 +
  93 + try:
  94 + pars = gammalib.GApplicationPars(parfile)
  95 + except:
  96 + # Signal if parfile was not found
  97 + sys.stdout.write("Parfile "+parfile+" not found. Create default parfile.\n")
  98 +
  99 + # Create default parfile
  100 + pars = gammalib.GApplicationPars()
  101 + pars.append(gammalib.GApplicationPar("inobs","f","a","events.fits","","","Event list, counts cube, or observation definition file"))
  102 + pars.append(gammalib.GApplicationPar("inmodel","f","a","$CTOOLS/share/models/crab.xml","","","Source model"))
  103 + pars.append(gammalib.GApplicationPar("srcname","s","a","Crab","","","Source name"))
  104 + pars.append(gammalib.GApplicationPar("outfile","f","a","spectrum.fits","","","Output file name"))
  105 + pars.append(gammalib.GApplicationPar("expcube","f","a","NONE","","","Exposure cube file (only needed for stacked analysis)"))
  106 + pars.append(gammalib.GApplicationPar("psfcube","f","a","NONE","","","PSF cube file (only needed for stacked analysis)"))
  107 + pars.append(gammalib.GApplicationPar("caldb","s","a","dummy","","","Calibration database"))
  108 + pars.append(gammalib.GApplicationPar("irf","s","a","cta_dummy_irf","","","Instrument response function"))
  109 + pars.append(gammalib.GApplicationPar("emin","r","h","0.1","","","Lower energy limit (TeV)"))
  110 + pars.append(gammalib.GApplicationPar("emax","r","h","100.0","","","Upper energy limit (TeV)"))
  111 + pars.append(gammalib.GApplicationPar("enumbins","i","a","20","","","Number of energy bins"))
  112 + pars.append(gammalib.GApplicationPar("ebinalg","s","h","LOG","LIN|LOG|FILE","","Binning algorithm"))
  113 + pars.append(gammalib.GApplicationPar("calc_ts","b","h","yes","yes|no","","Compute TS value in each bin"))
  114 + pars.append(gammalib.GApplicationPar("calc_ulim","b","h","yes","yes|no","","Compute upper limit in each bin"))
  115 + pars.append(gammalib.GApplicationPar("fix_srcs","b","h","yes","yes|no","","Fix other skymodel parameters"))
  116 + pars.append(gammalib.GApplicationPar("fix_bkg","b","h","no","yes|no","","Fix background parameters"))
  117 + pars.append_standard()
  118 + pars.append(gammalib.GApplicationPar("logfile","f","h","csspec.log","","","Log filename"))
  119 + pars.save(parfile)
  120 +
  121 + # Return
  122 + return
  123 +
  124 + def get_parameters(self):
  125 + """
  126 + Get parameters from parfile and setup the observation.
  127 + """
  128 + # Get parameters
  129 + # Set observation if not done before
  130 + if self.obs == None or self.obs.size() == 0:
  131 + self.require_inobs("csspec::get_parameters()")
  132 + self.obs = self.get_observations()
  133 +
  134 + # Set models if we have none
  135 + if self.obs.models().size() == 0:
  136 + self.obs.models(self["inmodel"].filename())
  137 +
  138 + # Get source name
  139 + self.m_srcname = self["srcname"].string()
  140 +
  141 + # Get ebounds
  142 + self.m_ebounds = self.create_ebounds()
  143 +
  144 + # Read other parameters
  145 + self.m_outfile = self["outfile"].filename()
  146 +
  147 + # Get other parameeters
  148 + self.m_calc_ulimit = self["calc_ulim"].boolean()
  149 + self.m_calc_ts = self["calc_ts"].boolean()
  150 + self.m_fix_bkg = self["fix_bkg"].boolean()
  151 + self.m_fix_srcs = self["fix_srcs"].boolean()
  152 +
  153 + # Set some fixed parameters
  154 + self.m_log = False # Logging in client tools
  155 + self.m_chatter = self["chatter"].integer()
  156 + self.m_clobber = self["clobber"].boolean()
  157 + self.m_debug = self["debug"].boolean()
  158 +
  159 + # Return
  160 + return
  161 +
  162 + def models(self, models):
  163 + """
  164 + Set model.
  165 + """
  166 + # Copy models
  167 + self.obs.models(models.clone())
  168 +
  169 + # Return
  170 + return
  171 +
  172 + def execute(self):
  173 + """
  174 + Execute the script.
  175 + """
  176 + # Run the script
  177 + self.run()
  178 +
  179 + # Save residual map
  180 + self.fits.saveto(self.m_outfile, self.m_clobber)
  181 +
  182 + # Return
  183 + return
  184 +
  185 + def run(self):
  186 + """
  187 + Run the script.
  188 + """
  189 + # Switch screen logging on in debug mode
  190 + if self.logDebug():
  191 + self.log.cout(True)
  192 +
  193 + # Get parameters
  194 + self.get_parameters()
  195 +
  196 + # Write input parameters into logger
  197 + if self.logTerse():
  198 + self.log_parameters()
  199 + self.log("\n")
  200 +
  201 + # Write observation into logger
  202 + if self.logTerse():
  203 + self.log("\n")
  204 + self.log.header1("Observation")
  205 + self.log(str(self.obs))
  206 + self.log("\n")
  207 +
  208 + # Write header
  209 + if self.logTerse():
  210 + self.log("\n")
  211 + self.log.header1("Adjust model parameters")
  212 +
  213 + for model in self.obs.models():
  214 + model.tscalc(False)
  215 + if model.name() == self.m_srcname:
  216 + for par in model:
  217 + par.fix()
  218 + model.spectral()[0].free()
  219 + if self.m_calc_ts:
  220 + model.tscalc(True)
  221 +
  222 + elif self.m_fix_bkg and not model.classname() == "GModelData":
  223 + for par in model:
  224 + par.fix()
  225 +
  226 + elif self.m_fix_srcs and model.classname() == "GModelSky":
  227 + for par in model:
  228 + par.fix()
  229 +
  230 + # Write header
  231 + if self.logTerse():
  232 + self.log("\n")
  233 + self.log.header1("Generate spectrum")
  234 +
  235 + # Initialise FITS Table with extension "SPECTRUM"
  236 + table = gammalib.GFitsBinTable(self.m_ebounds.size())
  237 + table.extname("SPECTRUM")
  238 +
  239 + # Add Header for compatibility with gammalib.GMWLSpectrum
  240 + table.card("Instrument", "CTA", "Name of Instrument")
  241 + table.card("Telescope", "CTA", "Name of Telescope")
  242 +
  243 + # Create FITS table columns
  244 + energy = gammalib.GFitsTableDoubleCol("Energy", self.m_ebounds.size())
  245 + energy.unit("TeV")
  246 + energy_err = gammalib.GFitsTableDoubleCol("e_Energy", self.m_ebounds.size())
  247 + energy_err.unit("TeV")
  248 + flux = gammalib.GFitsTableDoubleCol("Flux", self.m_ebounds.size())
  249 + flux.unit("erg/cm2/s")
  250 + flux_err = gammalib.GFitsTableDoubleCol("e_Flux", self.m_ebounds.size())
  251 + flux_err.unit("erg/cm2/s")
  252 + TSvalues = gammalib.GFitsTableDoubleCol("TS", self.m_ebounds.size())
  253 + ulim_values = gammalib.GFitsTableDoubleCol("UpperLimit", self.m_ebounds.size())
  254 + ulim_values.unit("erg/cm2/s")
  255 +
  256 + # Loop over energy bins
  257 + for i in range(self.m_ebounds.size()):
  258 +
  259 + # Get energy boundaries
  260 + emin = self.m_ebounds.emin(i)
  261 + emax = self.m_ebounds.emax(i)
  262 + emean = self.m_ebounds.emean(i)
  263 + elogmean = self.m_ebounds.elogmean(i)
  264 + elogmean2 = elogmean.MeV() * elogmean.MeV()
  265 +
  266 + # Select events
  267 + select = ctools.ctselect(self.obs)
  268 + select["emin"].real(emin.TeV())
  269 + select["emax"].real(emax.TeV())
  270 + select["tmin"].value("UNDEFINED")
  271 + select["tmax"].value("UNDEFINED")
  272 + select["rad"].value("UNDEFINED")
  273 + select["ra"].value("UNDEFINED")
  274 + select["dec"].value("UNDEFINED")
  275 + select.run()
  276 +
  277 + # likelihood
  278 + like = ctools.ctlike(select.obs())
  279 + like.run()
  280 +
  281 + # Get results
  282 + fitted_models = like.obs().models()
  283 + source = fitted_models[self.m_srcname]
  284 +
  285 + # Calculate Upper Limit
  286 + ulimit_value = -1.0
  287 + #if self.m_calc_ulimit:
  288 + # ulimit = ctools.ctulimit(like.obs())
  289 + # ulimit["eref"].real(elogmean.TeV())
  290 + # ulimit.run()
  291 + # ulimit_value.ulimit.diff_ulimit()
  292 +
  293 + # Get TS value
  294 + TS = -1.0
  295 + if self.m_calc_ts:
  296 + TS = source.ts()
  297 +
  298 + # Get differential flux
  299 + fitted_flux = source.spectral().eval(elogmean,gammalib.GTime())
  300 +
  301 + # Compute flux error
  302 + parvalue = source.spectral()[0].value()
  303 + rel_error = source.spectral()[0].error()/parvalue
  304 + e_flux = fitted_flux*rel_error
  305 +
  306 + # Set values for storage
  307 + TSvalues[i] = TS
  308 +
  309 + # Store energy as TeV
  310 + energy[i] = elogmean.TeV()
  311 +
  312 + # Store energy error
  313 + energy_err[i] = (emax - emean).TeV()
  314 +
  315 + # Convert fluxes to nuFnu
  316 + flux[i] = fitted_flux * elogmean2 * gammalib.MeV2erg
  317 + flux_err[i] = e_flux * elogmean2 * gammalib.MeV2erg
  318 + if ulimit_value > 0.0:
  319 + ulim_values[i] = ulimit_value * elogmean2 * gammalib.MeV2erg
  320 +
  321 +
  322 + # Log information
  323 + if self.logExplicit():
  324 + self.log("Bin "+str(i)+" ["+str(emin.TeV())+"-"+str(emax.TeV())+"] TeV: ")
  325 + self.log("Flux = "+str(flux[i]))
  326 + self.log(" +- "+str(flux_err[i])+" [erg/cm2/s]")
  327 + if self.m_calc_ts and TSvalues[i] > 0.0:
  328 + self.log(", TS = "+str(TS))
  329 + if self.m_calc_ulimit and ulim_values[i] > 0.0:
  330 + self.log(", UL = "+str(ulim_values[i])+" [erg/cm2/s]")
  331 + self.log("\n")
  332 +
  333 + # Append filles columns to fits table
  334 + table.append(energy)
  335 + table.append(energy_err)
  336 + table.append(flux)
  337 + table.append(flux_err)
  338 + table.append(TSvalues)
  339 + table.append(ulim_values)
  340 +
  341 + # Create the FITS file now
  342 + self.fits = gammalib.GFits()
  343 + self.fits.append(table)
  344 +
  345 + # Return
  346 + return
  347 +
  348 +
  349 +# ======================== #
  350 +# Main routine entry point #
  351 +# ======================== #
  352 +if __name__ == '__main__':
  353 + """
  354 + Generates spectral points.
  355 + """
  356 + # Create instance of application
  357 + app = csspec(sys.argv)
  358 +
  359 + # Open logfile
  360 + app.logFileOpen()
  361 +
  362 + # Execute application
  363 + app.execute()
  364 +
0 365 \ No newline at end of file
... ...
test/test_cscripts.sh.in
... ... @@ -188,3 +188,30 @@ else
188 188 exit 1
189 189 fi
190 190 $ECHO " ok"
  191 +
  192 +#
  193 +# Test csspec
  194 +# =============
  195 +$ECHO -n "Test csspec: "
  196 +chmod +x @abs_top_srcdir@/scripts/csspec.py
  197 +@abs_top_srcdir@/scripts/csspec.py \
  198 + inobs="data/crab_events.fits" \
  199 + inmodel="data/crab.xml" \
  200 + caldb="irf" \
  201 + irf="cta_dummy_irf" \
  202 + enumbins=5 \
  203 + srcname="Crab" \
  204 + emin=0.1 \
  205 + emax=100.0 \
  206 + outfile="spectrum.fits" > csspec.log
  207 +$ECHO -n "."
  208 +if [ -s "spectrum.fits" ]
  209 +then
  210 + $ECHO -n "."
  211 +else
  212 + $ECHO " spectrum.fits file is not valid"
  213 + exit 1
  214 +fi
  215 +$ECHO " ok"
  216 +
  217 +
... ...