From d33ba1c6dedeb7fbee1846fb6572c2c35087c3f3 Mon Sep 17 00:00:00 2001 From: Philip Daniel Keicher Date: Fri, 28 Sep 2018 10:43:13 +0200 Subject: [PATCH 01/73] added base classes, processObject working --- README.md | 16 ++++ base/helperClass.py | 15 ++++ categoryObject.py | 68 +++++++-------- datacardMaker.py | 38 ++++---- processObject.py | 206 +++++++++++++++++++++++++++++++++++++++----- systematicObject.py | 57 ++++++++---- 6 files changed, 305 insertions(+), 95 deletions(-) create mode 100644 base/helperClass.py diff --git a/README.md b/README.md index 283cb40..0e2083f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,18 @@ # datacardMaker repo for object-oriented datacard maker + +will use scripts in [Philip's base repository](https://gitlab.cern.ch/pkeicher/base) (better make it stand-alone?) + +Current ideas for structure + +- datacardMaker.py: Wrapper to automatically create data cards from category/process/systematic objects + - should be able to know how many categories are to be written (-> j parameter) + - should be able to know how many systematics are involved (-> k parameter) +- processObject.py: object for process (e.g. ttH_hbb) + - process should know what systematics it has + - process should know the correlation to a given systematic +- systematicObject.py: object for nuisance parameters (e.g. bgnorm_ttbarPlusBBbar) + - systematic should know which processes it affects + - systematic should know the correlation to a given process +- categoryObject.py: object for categories + - a category has multiple processes with a number of systematic uncertainties \ No newline at end of file diff --git a/base/helperClass.py b/base/helperClass.py new file mode 100644 index 0000000..f52d51f --- /dev/null +++ b/base/helperClass.py @@ -0,0 +1,15 @@ +from sys import path as spath +baserepodir = "/nfs/dust/cms/user/pkeicher/projects/base/classes" +if not baserepodir in spath: + spath.append(baserepodir) +from helperClass_base import helperClass_base +class helperClass(helperClass_base): + def __init__(self): + super(helperClass, self).__init__() + + def isfloat(value): + try: + float(value) + return True + except ValueError: + return False \ No newline at end of file diff --git a/categoryObject.py b/categoryObject.py index da1cf83..d12ad83 100644 --- a/categoryObject.py +++ b/categoryObject.py @@ -34,30 +34,30 @@ def __init__( self, categoryName, defaultRootFile, systIdentifier -- string that is to be replaced with the nuisance parameter name in the keys """ - self._name_ = categoryName - self._nomkey_ = defaultnominalkey - self._systkey_ = systkey - self._procIden_ = processIdentifier - self._chIden_ = channelIdentifier - self._systIden_ = systIdentifier - self._data_obs_ = None - self._signalprocs_ = [] - self._bkgprocs_ = [] + self._name = categoryName + self._nomkey = defaultnominalkey + self._systkey = systkey + self._procIden = processIdentifier + self._chIden = channelIdentifier + self._systIden = systIdentifier + self._data_obs = None + self._signalprocs = [] + self._bkgprocs = [] #check if process/channel identifiers are in nominal histo key - if not self._procIden_ in self._nomkey_: + if self._procIden in self._nomkey: print "WARNING:\tProcess identifier is still part of nominal histo key!" - if self._chIden_ in self._nomkey_: + if self._chIden in self._nomkey: print "WARNING:\tChannel identifier is still part of nominal histo key! Will replace it" - self._nomkey_ = self._nomkey_.replace(self._chIden_, self._name_) + self._nomkey = self._nomkey.replace(self._chIden, self._name) #check if systematics/process/channel identifiers are in systematics histo key - if not self._systIden_ in self._systkey_: + if not self._systIden in self._systkey: print "WARNING:\tSystematic identifier still not part of nominal histo key!" - if self._chIden_ in self._systkey__: + if self._chIden in self._systkey: print "WARNING:\tChannel identifier is still part of systematic histo key! Will replace it" - self._systkey__ = self._systkey__.replace(self._chIden_, self._name_) - if not self._systIden_ in self._systkey_: + self._systkey = self._systkey.replace(self._chIden, self._name) + if not self._systIden in self._systkey: print "WARNING:\tSystematic identifier still not part of nominal histo key!" #if a list of signal processes is given, add them with default vals @@ -74,48 +74,48 @@ def __init__( self, categoryName, defaultRootFile, def add_signal_process( self, name, rootfile, - histoname = self._nomkey_, - systkey = self._systkey_): + histoname = self._nomkey, + systkey = self._systkey): """ add a signal process. Calls function add_process with list of signal processes """ - self.add_process( dic = self._signalprocs_, name = name, + self.add_process( dic = self._signalprocs, name = name, rootfile = rootfile, histoname = histoname, systkey = systkey) def add_background_process( self, name, rootfile, - histoname = self._nomkey_, - systkey = self._systkey_): + histoname = self._nomkey, + systkey = self._systkey): """ add a background process. Calls function add_process with list of background processes """ - self.add_process( dic = self._bkgprocs_, name = name, + self.add_process( dic = self._bkgprocs, name = name, rootfile = rootfile, histoname = histoname, systkey = systkey) def add_process(self, dic, name, rootfile, - histoname = self._nomkey_, systkey = self._systkey_ + histoname = self._nomkey, systkey = self._systkey ): changedKey = False - if self._procIden_ in histoname: + if self._procIden in histoname: print "WARNING:\tProcess identifier is still part of nominal histo key! Will replace it" - histoname = histoname.replace(self._procIden_, name) - if self._chIden_ in histoname: + histoname = histoname.replace(self._procIden, name) + if self._chIden in histoname: print "WARNING:\tChannel identifier is still part of nominal histo key! Will replace it" - histoname = histoname.replace(self._chIden_, name) - if self._procIden_ in systkey: + histoname = histoname.replace(self._chIden, name) + if self._procIden in systkey: print "WARNING:\tProcess identifier is still part of nominal histo key! Will replace it" - systkey = systkey.replace(self._procIden_, name) - if self._chIden_ in systkey: + systkey = systkey.replace(self._procIden, name) + if self._chIden in systkey: print "WARNING:\tChannel identifier is still part of nominal histo key! Will replace it" - systkey = systkey.replace(self._chIden_, name) + systkey = systkey.replace(self._chIden, name) - controlNomKey = self._nomkey_.replace(self._procIden_, name) - controlSysKey = self._systkey_.replace(self._procIden_, name) - if not (histoname = self._nomkey_ and systkey = self._systkey_): + controlNomKey = self._nomkey.replace(self._procIden, name) + controlSysKey = self._systkey.replace(self._procIden, name) + if not (histoname = self._nomkey and systkey = self._systkey): changedKey = True if name in dic: diff --git a/datacardMaker.py b/datacardMaker.py index a572c43..bf7eaf2 100644 --- a/datacardMaker.py +++ b/datacardMaker.py @@ -1,35 +1,31 @@ import sys from os import path -directory = path.abspath(path.join(path.dirname("./"), ".")) +directory = path.abspath(path.realpath(path.dirname(__file__))) if not directory in sys.path: sys.path.append(directory) -from processObject import * -from systematicObject import * -from categoryObject import * +from categoryObject import categoryObject class datacardMaker: - - def __init__(self): - self._header_ = [] - self._bins_ = "" - self._observation_ = "" - self._processes_ = [] - - - def __init__( self, pathToDatacard, + + def __init__( self, pathToDatacard = None, processIdentifier = "$PROCESS", channelIdentifier = "$CHANNEL", systIdentifier = "$SYSTEMATIC"): - if os.path.exists(pathToDatacard): - - - + self._header = [] + self._bins = "" + self._observation = "" + self._categories = [] + if pathToDatacard: + self.load_from_file(pathToDatacard) + + + def load_from_file(self, pathToDatacard): + if path.exists(pathToDatacard): print "loading datacard from", pathToDatacard with open(pathToDatacard) as datacard: lines = datacard.read().splitlines() - self._header_ = [] self._shapelines_ = [] for n, line in enumerate(lines): if line.startswith("-"): @@ -38,12 +34,12 @@ def __init__( self, pathToDatacard, line.startswith("imax") or line.startswith("kmax") or line.startswith("jmax"): - self._header_.append(line) + self._header.append(line) elif line.startswith("bin") and n != len(lines) and lines[n+1].startswith("observation"): - self._bins_ = line - self._observation_ = lines[n+1] + self._bins = line + self._observation = lines[n+1] elif line.startswith("shapes"): self._shapelines_.append(line) elif line.startswith("process") and diff --git a/processObject.py b/processObject.py index ad22690..41c4a60 100644 --- a/processObject.py +++ b/processObject.py @@ -1,18 +1,32 @@ -from ROOT import TFile, TH1D +from ROOT import TFile, TH1 from os import path +from sys import path as spath +thisdir = path.realpath(path.dirname(__file__)) +basedir = path.join(thisdir, "base") +if not basedir in spath: + spath.append(basedir) +from helperClass import helperClass -class processObject: - - def __init__( self, processName, pathToRootfile, nominalname, - systname, sharesDefault = False): +# if not path.abspath(".") in spath: +# spath.append(path.abspath(".")) +# from systematicObject import systematicObject + +class processObject(object): + helper = helperClass() + _debug = False + def __init__( self, processName = "", pathToRootfile = "", nominalname = "", + categoryname = ""): - self._name_ = processName - self._rootfile_ = pathToRootfile - self._histname_ = nominalname - self._systname_ = systname - self._sharesDefault_ = sharesDefault + self._name = processName + self._rootfile = pathToRootfile + self._nominalhistname = nominalname + self._categoryname = categoryname - self._yield_ = self.calculate_yield() + self._eventcount = self.calculate_yield() + self._uncertainties = {} + + if self._debug: + print "initialized process with name '%s' in category '%s'" % (self._name, self._categoryname) def calculate_yield(self): """ @@ -20,47 +34,193 @@ def calculate_yield(self): """ #open rootfile if it exsists y = -1 - if path.exists(self._rootfile_): - infile = TFile(self._rootfile_) + if path.exists(self._rootfile): + infile = TFile(self._rootfile) #check if root file is intact if infile.IsOpen() and not infile.IsZombie() and not infile.TestBit(TFile.kRecovered): #if yes, try to load histogram - hist = infile.Get(self._histname_) - if isinstance(hist, TH1D): + hist = infile.Get(self._nominalhistname) + if isinstance(hist, TH1): #if successful, save yield y = hist.Integral() else: print "ERROR:\tunable to load histogram! I will let combine calculate it on the fly, but it could crash" infile.Close() else: - print "ERROR:\tunable to open root file for process {0}, cannot set yield".format(self._name_) + print "ERROR:\tunable to open root file for process {0}, cannot set yield".format(self._name) else: - print "ERROR:\troot file does not exist! Cannot set yield for process {0}".format(self._name_) + print "ERROR:\troot file does not exist! Cannot set yield for process {0}".format(self._name) return y - + #getter/setter for yields + @property + def eventcount(self): + return self.get_yield() + @eventcount.setter + def eventcount(self, val): + self.set_yield(val) + def set_yield(self, val): """ set yield for processes to value val """ - self._yield_ = val + self._eventcount = val def get_yield(self): """ get yield for process """ - y = self._yield_ + y = self._eventcount return y - + + #logic for process name + @property + def name(self): + return self.get_name() + @name.setter + def name(self, s): + if self._debug: print "entered setter for name" + self.set_name(s) + + def set_name(self, name): """ set process name """ - self._name_ = name + if self._debug: print "setting name to", name + self._name = name def get_name(self): """ create copy of process name """ - s = self._name_ + s = self._name return s + + @property + def category(self): + return self.get_category() + + @category.setter + def category(self, catname): + if self._debug: print "entered setter for category" + self.set_category(catname) + # self._categoryname = catname + + def get_category(self): + """ + get name for category to which this process belongs to + """ + return self._categoryname + + def set_category(self, catname): + """ + set name for category to which this process belongs to + """ + if self._debug: print "setting category to", catname + self._categoryname = catname + + @property + def rootfile(self): + return self._rootfile + + @rootfile.setter + def rootfile(self, rootpath): + if path.exists(rootpath): + self._rootfile = rootpath + else: + print "file '%s' does not exist!" % rootpath + + @property + def nominalhistname(self): + return self._nominalhistname + + @nominalhistname.setter + def nominalhistname(self, hname): + if path.exists(self._rootfile): + f = TFile(self._rootfile) + h = f.Get(hname) + if isinstance(h, TH1): + self._nominalhistname = hname + else: + print "'%s' does not exist in '%s'" % (hname, self._rootfile) + else: + print "'%s' does not exist! You should check it again" % self._rootfile + + def __str__(self): + """ + current setup: print delivers: + - process name + - process yield + - list of nuisance parameters + """ + s = [] + s.append("Process infos:") + s.append("\tname:\t%s" % self.get_name()) + s.append("\tcategory:\t%s" % self.get_category()) + s.append("\trootfile:\t%s" % self._rootfile) + s.append("\tnominal histname:\t%s" % self._nominalhistname) + s.append("\tyield:\t{0}".format(self._eventcount)) + if len(self._uncertainties) != 0: s.append("\t\tuncertainty\tcorrelation") + for syst in self._uncertainties: + s.append("\t\t%s\t%s" % (syst, self._uncertainties[syst])) + return "\n".join(s) + + def is_good_systval(self, value): + """ + check if 'value' is a format allowed for systematic uncertainties + """ + is_good = False + if value == "-": is_good = True + elif isinstance(value,float) or isinstance(value,int): is_good = True + elif isinstance(value,str): + totest = value.split("/") + if len(totest) in [1,2]: + is_good = all(helper.isfloat(v) for v in totest) + if not is_good: print "The given value is not suitable for an uncertainty in a datacard!" + return is_good + + def add_uncertainty(self, systname, typ, value): + """ + add an uncertainty to this process. This function checks + - whether there already is an entry for 'systname' + - the given value is suitable for a datacard (see 'is_good_systval') + and only adds the systematics if it's new and has a good value + """ + if not systname in self._uncertainties and self.is_good_systval(value): + self._uncertainties[systname] = {"value" : str(value), "type" : typ} + else: + print "There is already an entry for uncertainty %s in process %s" % (systname, self.get_name()) + + def set_uncertainty(self, systname, typ, value): + """ + set the uncertainty 'systname' for this process to type 'typ' and value 'value'. This function checks + - whether there is an entry for 'systname' in the first place + - the given value is suitable for a datacard (see 'is_good_systval') + and only adds the systematics if there is an entry and the value is good + """ + if systname in self._uncertainties and self.is_good_systval(value): + self._uncertainties[systname]["value"] = str(value) + self._uncertainties[systname]["type"] = typ + else: + print "There is no entry for uncertainty %s in process %s" % (systname, self.get_name()) + + def get_uncertainty_value(self, systname): + """ + return correlation of uncertainty 'systname' with this process. + If there is no entry for 'systname' in this process, the function returns '-' + """ + if systname in self._uncertainties: + return self._uncertainties[systname] + else: + return "-" + + def get_uncertainty_type(self, systname): + """ + return type of uncertainty 'systname' in this process. + If there is no entry for 'systname' in this process, the function returns '' + """ + if systname in self._uncertainties: + return self._uncertainties[systname] + else: + return "" \ No newline at end of file diff --git a/systematicObject.py b/systematicObject.py index 630f508..6dcde16 100644 --- a/systematicObject.py +++ b/systematicObject.py @@ -1,26 +1,49 @@ -class systematicObject: +class systematicObject(object): def __init__(self, name, nature, dic = None): - self._name_ = name - self._nature_ = nature - self._dic_ = {} + self._name = name + self._type = nature + self._dic = {} if dic and isinstance(dic, dict): - self._dic_ = dic - - def add_process(self, name, correlation): - self._dic_[name] = correlation + self._dic = dic + + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = str(value) + + @property + def type(self): + return self._type + + @type.setter + def type(self, typ): + self._type = typ + + def add_process(self, category, procname, correlation): + if category in self._dic: + self._dic[category][procname] = correlation + else: + print "ERROR: category is not known to uncertainty '%s'" % self._name - def add_process(self, dic): - if dic and isinstance(dic, dict): - self._dic_ += dic + def add_process(self, dic, category): + if category in self._dic: + if dic and isinstance(dic, dict): + self._dic[category] += dic + else: + print "Could not add process: input must be dictionary!" else: - print "Could not add process: input must be dictionary!" + print "ERROR: category is not known to uncertainty '%s'" % self._name - def get_correlation(self, procName): - if procName in self._dic_: - return str(self._dic_[procName]) - else: - return "-" + def get_correlation(self, category, procName): + if category in self._dic: + if procName in self._dic[category]: + return str(self._dic[category][procName]) + else: + return "-" From 302d8d0cadff18da002241f4f3ef9de99fa617be Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Fri, 28 Sep 2018 13:26:22 +0200 Subject: [PATCH 02/73] merge --- categoryObject.py | 68 +++++++-------- datacardMaker.py | 39 ++++----- processObject.py | 206 +++++++++++++++++++++++++++++++++++++++----- systematicObject.py | 59 +++++++++---- 4 files changed, 274 insertions(+), 98 deletions(-) diff --git a/categoryObject.py b/categoryObject.py index da1cf83..d12ad83 100644 --- a/categoryObject.py +++ b/categoryObject.py @@ -34,30 +34,30 @@ def __init__( self, categoryName, defaultRootFile, systIdentifier -- string that is to be replaced with the nuisance parameter name in the keys """ - self._name_ = categoryName - self._nomkey_ = defaultnominalkey - self._systkey_ = systkey - self._procIden_ = processIdentifier - self._chIden_ = channelIdentifier - self._systIden_ = systIdentifier - self._data_obs_ = None - self._signalprocs_ = [] - self._bkgprocs_ = [] + self._name = categoryName + self._nomkey = defaultnominalkey + self._systkey = systkey + self._procIden = processIdentifier + self._chIden = channelIdentifier + self._systIden = systIdentifier + self._data_obs = None + self._signalprocs = [] + self._bkgprocs = [] #check if process/channel identifiers are in nominal histo key - if not self._procIden_ in self._nomkey_: + if self._procIden in self._nomkey: print "WARNING:\tProcess identifier is still part of nominal histo key!" - if self._chIden_ in self._nomkey_: + if self._chIden in self._nomkey: print "WARNING:\tChannel identifier is still part of nominal histo key! Will replace it" - self._nomkey_ = self._nomkey_.replace(self._chIden_, self._name_) + self._nomkey = self._nomkey.replace(self._chIden, self._name) #check if systematics/process/channel identifiers are in systematics histo key - if not self._systIden_ in self._systkey_: + if not self._systIden in self._systkey: print "WARNING:\tSystematic identifier still not part of nominal histo key!" - if self._chIden_ in self._systkey__: + if self._chIden in self._systkey: print "WARNING:\tChannel identifier is still part of systematic histo key! Will replace it" - self._systkey__ = self._systkey__.replace(self._chIden_, self._name_) - if not self._systIden_ in self._systkey_: + self._systkey = self._systkey.replace(self._chIden, self._name) + if not self._systIden in self._systkey: print "WARNING:\tSystematic identifier still not part of nominal histo key!" #if a list of signal processes is given, add them with default vals @@ -74,48 +74,48 @@ def __init__( self, categoryName, defaultRootFile, def add_signal_process( self, name, rootfile, - histoname = self._nomkey_, - systkey = self._systkey_): + histoname = self._nomkey, + systkey = self._systkey): """ add a signal process. Calls function add_process with list of signal processes """ - self.add_process( dic = self._signalprocs_, name = name, + self.add_process( dic = self._signalprocs, name = name, rootfile = rootfile, histoname = histoname, systkey = systkey) def add_background_process( self, name, rootfile, - histoname = self._nomkey_, - systkey = self._systkey_): + histoname = self._nomkey, + systkey = self._systkey): """ add a background process. Calls function add_process with list of background processes """ - self.add_process( dic = self._bkgprocs_, name = name, + self.add_process( dic = self._bkgprocs, name = name, rootfile = rootfile, histoname = histoname, systkey = systkey) def add_process(self, dic, name, rootfile, - histoname = self._nomkey_, systkey = self._systkey_ + histoname = self._nomkey, systkey = self._systkey ): changedKey = False - if self._procIden_ in histoname: + if self._procIden in histoname: print "WARNING:\tProcess identifier is still part of nominal histo key! Will replace it" - histoname = histoname.replace(self._procIden_, name) - if self._chIden_ in histoname: + histoname = histoname.replace(self._procIden, name) + if self._chIden in histoname: print "WARNING:\tChannel identifier is still part of nominal histo key! Will replace it" - histoname = histoname.replace(self._chIden_, name) - if self._procIden_ in systkey: + histoname = histoname.replace(self._chIden, name) + if self._procIden in systkey: print "WARNING:\tProcess identifier is still part of nominal histo key! Will replace it" - systkey = systkey.replace(self._procIden_, name) - if self._chIden_ in systkey: + systkey = systkey.replace(self._procIden, name) + if self._chIden in systkey: print "WARNING:\tChannel identifier is still part of nominal histo key! Will replace it" - systkey = systkey.replace(self._chIden_, name) + systkey = systkey.replace(self._chIden, name) - controlNomKey = self._nomkey_.replace(self._procIden_, name) - controlSysKey = self._systkey_.replace(self._procIden_, name) - if not (histoname = self._nomkey_ and systkey = self._systkey_): + controlNomKey = self._nomkey.replace(self._procIden, name) + controlSysKey = self._systkey.replace(self._procIden, name) + if not (histoname = self._nomkey and systkey = self._systkey): changedKey = True if name in dic: diff --git a/datacardMaker.py b/datacardMaker.py index a572c43..ad4c0c8 100644 --- a/datacardMaker.py +++ b/datacardMaker.py @@ -1,35 +1,31 @@ import sys from os import path -directory = path.abspath(path.join(path.dirname("./"), ".")) +directory = path.abspath(path.realpath(path.dirname(__file__))) if not directory in sys.path: sys.path.append(directory) -from processObject import * -from systematicObject import * -from categoryObject import * +from categoryObject import categoryObject class datacardMaker: - - def __init__(self): - self._header_ = [] - self._bins_ = "" - self._observation_ = "" - self._processes_ = [] - - - def __init__( self, pathToDatacard, + + def __init__( self, pathToDatacard = None, processIdentifier = "$PROCESS", channelIdentifier = "$CHANNEL", systIdentifier = "$SYSTEMATIC"): - if os.path.exists(pathToDatacard): - - - + self._header = [] + self._bins = "" + self._observation = "" + self._categories = [] + if pathToDatacard: + self.load_from_file(pathToDatacard) + + + def load_from_file(self, pathToDatacard): + if path.exists(pathToDatacard): print "loading datacard from", pathToDatacard with open(pathToDatacard) as datacard: lines = datacard.read().splitlines() - self._header_ = [] self._shapelines_ = [] for n, line in enumerate(lines): if line.startswith("-"): @@ -38,12 +34,12 @@ def __init__( self, pathToDatacard, line.startswith("imax") or line.startswith("kmax") or line.startswith("jmax"): - self._header_.append(line) + self._header.append(line) elif line.startswith("bin") and n != len(lines) and lines[n+1].startswith("observation"): - self._bins_ = line - self._observation_ = lines[n+1] + self._bins = line + self._observation = lines[n+1] elif line.startswith("shapes"): self._shapelines_.append(line) elif line.startswith("process") and @@ -55,4 +51,3 @@ def __init__( self, pathToDatacard, else: print "could not load %s: no such file" % pathToDatacard - diff --git a/processObject.py b/processObject.py index ad22690..035de77 100644 --- a/processObject.py +++ b/processObject.py @@ -1,18 +1,32 @@ -from ROOT import TFile, TH1D +from ROOT import TFile, TH1 from os import path +from sys import path as spath +thisdir = path.realpath(path.dirname(__file__)) +basedir = path.join(thisdir, "base") +if not basedir in spath: + spath.append(basedir) +from helperClass import helperClass -class processObject: - - def __init__( self, processName, pathToRootfile, nominalname, - systname, sharesDefault = False): +# if not path.abspath(".") in spath: +# spath.append(path.abspath(".")) +# from systematicObject import systematicObject + +class processObject(object): + helper = helperClass() + _debug = False + def __init__( self, processName = "", pathToRootfile = "", nominalname = "", + categoryname = ""): - self._name_ = processName - self._rootfile_ = pathToRootfile - self._histname_ = nominalname - self._systname_ = systname - self._sharesDefault_ = sharesDefault + self._name = processName + self._rootfile = pathToRootfile + self._nominalhistname = nominalname + self._categoryname = categoryname - self._yield_ = self.calculate_yield() + self._eventcount = self.calculate_yield() + self._uncertainties = {} + + if self._debug: + print "initialized process with name '%s' in category '%s'" % (self._name, self._categoryname) def calculate_yield(self): """ @@ -20,47 +34,193 @@ def calculate_yield(self): """ #open rootfile if it exsists y = -1 - if path.exists(self._rootfile_): - infile = TFile(self._rootfile_) + if path.exists(self._rootfile): + infile = TFile(self._rootfile) #check if root file is intact if infile.IsOpen() and not infile.IsZombie() and not infile.TestBit(TFile.kRecovered): #if yes, try to load histogram - hist = infile.Get(self._histname_) - if isinstance(hist, TH1D): + hist = infile.Get(self._nominalhistname) + if isinstance(hist, TH1): #if successful, save yield y = hist.Integral() else: print "ERROR:\tunable to load histogram! I will let combine calculate it on the fly, but it could crash" infile.Close() else: - print "ERROR:\tunable to open root file for process {0}, cannot set yield".format(self._name_) + print "ERROR:\tunable to open root file for process {0}, cannot set yield".format(self._name) else: - print "ERROR:\troot file does not exist! Cannot set yield for process {0}".format(self._name_) + print "ERROR:\troot file does not exist! Cannot set yield for process {0}".format(self._name) return y - + #getter/setter for yields + @property + def eventcount(self): + return self.get_yield() + @eventcount.setter + def eventcount(self, val): + self.set_yield(val) + def set_yield(self, val): """ set yield for processes to value val """ - self._yield_ = val + self._eventcount = val def get_yield(self): """ get yield for process """ - y = self._yield_ + y = self._eventcount return y - + + #logic for process name + @property + def name(self): + return self.get_name() + @name.setter + def name(self, s): + if self._debug: print "entered setter for name" + self.set_name(s) + + def set_name(self, name): """ set process name """ - self._name_ = name + if self._debug: print "setting name to", name + self._name = name def get_name(self): """ create copy of process name """ - s = self._name_ + s = self._name return s + + @property + def category(self): + return self.get_category() + + @category.setter + def category(self, catname): + if self._debug: print "entered setter for category" + self.set_category(catname) + # self._categoryname = catname + + def get_category(self): + """ + get name for category to which this process belongs to + """ + return self._categoryname + + def set_category(self, catname): + """ + set name for category to which this process belongs to + """ + if self._debug: print "setting category to", catname + self._categoryname = catname + + @property + def rootfile(self): + return self._rootfile + + @rootfile.setter + def rootfile(self, rootpath): + if path.exists(rootpath): + self._rootfile = rootpath + else: + print "file '%s' does not exist!" % rootpath + + @property + def nominalhistname(self): + return self._nominalhistname + + @nominalhistname.setter + def nominalhistname(self, hname): + if path.exists(self._rootfile): + f = TFile(self._rootfile) + h = f.Get(hname) + if isinstance(h, TH1): + self._nominalhistname = hname + else: + print "'%s' does not exist in '%s'" % (hname, self._rootfile) + else: + print "'%s' does not exist! You should check it again" % self._rootfile + + def __str__(self): + """ + current setup: print delivers: + - process name + - process yield + - list of nuisance parameters + """ + s = [] + s.append("Process infos:") + s.append("\tname:\t%s" % self.get_name()) + s.append("\tcategory:\t%s" % self.get_category()) + s.append("\trootfile:\t%s" % self._rootfile) + s.append("\tnominal histname:\t%s" % self._nominalhistname) + s.append("\tyield:\t{0}".format(self._eventcount)) + if len(self._uncertainties) != 0: s.append("\t\tuncertainty\tcorrelation") + for syst in self._uncertainties: + s.append("\t\t%s\t%s" % (syst, self._uncertainties[syst])) + return "\n".join(s) + + def is_good_systval(self, value): + """ + check if 'value' is a format allowed for systematic uncertainties + """ + is_good = False + if value == "-": is_good = True + elif isinstance(value,float) or isinstance(value,int): is_good = True + elif isinstance(value,str): + totest = value.split("/") + if len(totest) in [1,2]: + is_good = all(helper.isfloat(v) for v in totest) + if not is_good: print "The given value is not suitable for an uncertainty in a datacard!" + return is_good + + def add_uncertainty(self, systname, typ, value): + """ + add an uncertainty to this process. This function checks + - whether there already is an entry for 'systname' + - the given value is suitable for a datacard (see 'is_good_systval') + and only adds the systematics if it's new and has a good value + """ + if not systname in self._uncertainties and self.is_good_systval(value): + self._uncertainties[systname] = {"value" : str(value), "type" : typ} + else: + print "There is already an entry for uncertainty %s in process %s" % (systname, self.get_name()) + + def set_uncertainty(self, systname, typ, value): + """ + set the uncertainty 'systname' for this process to type 'typ' and value 'value'. This function checks + - whether there is an entry for 'systname' in the first place + - the given value is suitable for a datacard (see 'is_good_systval') + and only adds the systematics if there is an entry and the value is good + """ + if systname in self._uncertainties and self.is_good_systval(value): + self._uncertainties[systname]["value"] = str(value) + self._uncertainties[systname]["type"] = typ + else: + print "There is no entry for uncertainty %s in process %s" % (systname, self.get_name()) + + def get_uncertainty_value(self, systname): + """ + return correlation of uncertainty 'systname' with this process. + If there is no entry for 'systname' in this process, the function returns '-' + """ + if systname in self._uncertainties: + return self._uncertainties[systname] + else: + return "-" + + def get_uncertainty_type(self, systname): + """ + return type of uncertainty 'systname' in this process. + If there is no entry for 'systname' in this process, the function returns '' + """ + if systname in self._uncertainties: + return self._uncertainties[systname] + else: + return "" diff --git a/systematicObject.py b/systematicObject.py index 630f508..1baa703 100644 --- a/systematicObject.py +++ b/systematicObject.py @@ -1,26 +1,47 @@ -class systematicObject: +class systematicObject(object): def __init__(self, name, nature, dic = None): - self._name_ = name - self._nature_ = nature - self._dic_ = {} + self._name = name + self._type = nature + self._dic = {} if dic and isinstance(dic, dict): - self._dic_ = dic - - def add_process(self, name, correlation): - self._dic_[name] = correlation - - def add_process(self, dic): - if dic and isinstance(dic, dict): - self._dic_ += dic - else: - print "Could not add process: input must be dictionary!" + self._dic = dic - def get_correlation(self, procName): - if procName in self._dic_: - return str(self._dic_[procName]) + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = str(value) + + @property + def type(self): + return self._type + + @type.setter + def type(self, typ): + self._type = typ + + def add_process(self, category, procname, correlation): + if category in self._dic: + self._dic[category][procname] = correlation + else: + print "ERROR: category is not known to uncertainty '%s'" % self._name + + def add_process(self, dic, category): + if category in self._dic: + if dic and isinstance(dic, dict): + self._dic[category] += dic + else: + print "Could not add process: input must be dictionary!" else: - return "-" + print "ERROR: category is not known to uncertainty '%s'" % self._name - + def get_correlation(self, category, procName): + if category in self._dic: + if procName in self._dic[category]: + return str(self._dic[category][procName]) + else: + return "-" From 897fb3212677f67a1019d1c4611df48dd0018388 Mon Sep 17 00:00:00 2001 From: Philip Daniel Keicher Date: Thu, 4 Oct 2018 17:23:30 +0200 Subject: [PATCH 03/73] started work on creation of datacards --- categoryObject.py | 97 ++++++++++++++++--- datacardMaker.py | 230 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 295 insertions(+), 32 deletions(-) diff --git a/categoryObject.py b/categoryObject.py index d12ad83..ef82fca 100644 --- a/categoryObject.py +++ b/categoryObject.py @@ -4,9 +4,20 @@ if not directory in sys.path: sys.path.append(directory) -from processObject import * +from processObject import processObject -class categoryObject: +class categoryObject(object): + + # def __init__(self): + # self._name = "categoryName" + # self._nomkey = "defaultnominalkey" + # self._systkey = "systkey" + # self._procIden = "processIdentifier" + # self._chIden = "channelIdentifier" + # self._systIden = "systIdentifier" + # self._data_obs = None + # self._signalprocs = {} + # self._bkgprocs = {} def __init__( self, categoryName, defaultRootFile, defaultnominalkey, @@ -33,17 +44,15 @@ def __init__( self, categoryName, defaultRootFile, channelIdentifier -- string that is to be replaced with the channel name in the keys systIdentifier -- string that is to be replaced with the nuisance parameter name in the keys """ - + # self.__init__() self._name = categoryName self._nomkey = defaultnominalkey self._systkey = systkey self._procIden = processIdentifier self._chIden = channelIdentifier self._systIden = systIdentifier - self._data_obs = None - self._signalprocs = [] - self._bkgprocs = [] + #check if process/channel identifiers are in nominal histo key if self._procIden in self._nomkey: print "WARNING:\tProcess identifier is still part of nominal histo key!" @@ -71,35 +80,62 @@ def __init__( self, categoryName, defaultRootFile, for proc in listOfBkg: self.add_background_process(name = proc, rootfile = defaultRootFile) + + @property + def n_signal_procs(self): + return len(self._signalprocs) + + @property + def n_background_procs(self): + return len(self._bkgprocs) + + @property + def name(self): + return self._name + @name.setter + def name(self, val): + self._name = val + - + def add_signal_process( self, name, rootfile, - histoname = self._nomkey, - systkey = self._systkey): + histoname = None, + systkey = None): """ add a signal process. Calls function add_process with list of signal processes """ + if histoname is None: + histoname = self._nomkey + if systkey is None: + systkey = self._systkey self.add_process( dic = self._signalprocs, name = name, rootfile = rootfile, histoname = histoname, systkey = systkey) def add_background_process( self, name, rootfile, - histoname = self._nomkey, - systkey = self._systkey): + histoname = None, + systkey = None): """ add a background process. Calls function add_process with list of background processes """ - + if histoname is None: + histoname = self._nomkey + if systkey is None: + systkey = self._systkey self.add_process( dic = self._bkgprocs, name = name, rootfile = rootfile, histoname = histoname, systkey = systkey) def add_process(self, dic, name, rootfile, - histoname = self._nomkey, systkey = self._systkey + histoname = None, systkey = None ): changedKey = False + if histoname is None: + histoname = self._nomkey + if systkey is None: + systkey = self._systkey if self._procIden in histoname: print "WARNING:\tProcess identifier is still part of nominal histo key! Will replace it" histoname = histoname.replace(self._procIden, name) @@ -115,8 +151,41 @@ def add_process(self, dic, name, rootfile, controlNomKey = self._nomkey.replace(self._procIden, name) controlSysKey = self._systkey.replace(self._procIden, name) - if not (histoname = self._nomkey and systkey = self._systkey): + if not (histoname == self._nomkey and systkey == self._systkey): changedKey = True if name in dic: print "" + + #overloaded functions if input variable is a process + def add_signal_process( self, process): + """ + add a signal process. Calls function add_process with + list of signal processes + """ + self.add_process( dic = self._signalprocs, process = process) + + def add_background_process( self, process): + """ + add a background process. Calls function add_process with + list of background processes + """ + + self.add_process( dic = self._bkgprocs, process = process) + + def add_process(self, dic, process): + if isinstance(process, processObject): + dic[process.name] = process + else: + print "ERROR: Category can only contain processes!" + + def __str__(self): + s = [] + s.append("Category Name:\t%s" % self._name) + s.append("List of signal processes:") + for sig in self._signalprocs: + s.append("\t%s" % self._signalprocs[sig]) + + s.append("List of background processes:") + for bkg in self._bkgprocs: + s.append("\t%s" % self._bkgprocs[bkg]) diff --git a/datacardMaker.py b/datacardMaker.py index bf7eaf2..4b67ebf 100644 --- a/datacardMaker.py +++ b/datacardMaker.py @@ -6,21 +6,84 @@ sys.path.append(directory) from categoryObject import categoryObject +from systematicObject import systematicObject -class datacardMaker: - - def __init__( self, pathToDatacard = None, +class datacardMaker(object): + + def __init__(self): + self._header = [] + self._bins = "" + self._observation = "" + self._categories = {} + self._systematics = {} + self._hardcode_numbers = False + self._replace_files = False + self._outputpath = "/path/for/datacard.txt" + self._block_separator = "-"*130 + + def __init__( self, pathToDatacard, processIdentifier = "$PROCESS", channelIdentifier = "$CHANNEL", systIdentifier = "$SYSTEMATIC"): - self._header = [] - self._bins = "" - self._observation = "" - self._categories = [] + self.__init__() if pathToDatacard: self.load_from_file(pathToDatacard) + @property + def hardcode_numbers(self): + return self._hardcode_numbers + @hardcode_numbers.setter + def hardcode_numbers(self, val): + if type(val) == bool: + self._hardcode_numbers = val + else: + print "Value given is not boolean! Did not set 'hardcode_numbers'" + + @property + def replace_files(self): + return self._replace_files + @replace_files.setter + def replace_files(self, val): + if type(val) == bool: + self._replace_files = val + else: + print "Value given is not boolean! Did not set 'replace_files'" + + @property + def outputpath(self): + return self._outputpath + @outputpath.setter + def outputpath(self, outpath): + if outpath is None: + print "'outpath' cannot be None!" + elif path.exists(outpath): + if self._replace_files: + outpath = path.abspath(outpath) + self._outputpath = outpath + print "will replace", self._outputpath + else: + s = "File %s already exists" % outpath + s += " and I'm not allowed to overwrite - Skipping" + print s + else: + outpath = path.abspath(outpath) + dirname = path.dirname(outpath) + if path.exists(dirname): + self._outputpath = outpath + print "Will create new datacard here:", self._outputpath + else: + s = "Directory %s does not exist" % dirname + s += ", cannot use path", outpath + print s + @property + def block_separator(self): + return self._block_separator + @block_separator.setter + def block_separator(self, sep): + self._block_separator = sep + + def load_from_file(self, pathToDatacard): if path.exists(pathToDatacard): print "loading datacard from", pathToDatacard @@ -30,25 +93,156 @@ def load_from_file(self, pathToDatacard): for n, line in enumerate(lines): if line.startswith("-"): continue - elif line.startswith("Combination") or - line.startswith("imax") or - line.startswith("kmax") or - line.startswith("jmax"): + elif line.startswith("Combination") or line.startswith("imax") or line.startswith("kmax") or line.startswith("jmax"): self._header.append(line) - elif line.startswith("bin") and - n != len(lines) and - lines[n+1].startswith("observation"): + elif line.startswith("bin") and n != len(lines) and lines[n+1].startswith("observation"): self._bins = line self._observation = lines[n+1] elif line.startswith("shapes"): self._shapelines_.append(line) - elif line.startswith("process") and - n != 0 and - lines[n-1].startswith("bin") + elif line.startswith("process") and n != 0 and lines[n-1].startswith("bin"): + pass else: - + pass else: print "could not load %s: no such file" % pathToDatacard + + def get_number_of_procs(self): + """ + Get number of processes. Returns 0 if some categories have different + amount of processes! + """ + num = 0 + for cat in self._categories: + currentnum = self._categories[cat].n_signal_procs + currentnum += self._categories[cat].n_background_procs + if num == 0: num = currentnum + if not num == currentnum: + print "Mismatch! Categories have different number of process!" + num = 0 + break + return num + + def create_header(self): + """ + Create header for the datacard. The header has the following form: + imax -> number of bins + jmax -> number of processes minus 1 + kmax -> number of nuisance parameters + + If the flag 'hardcode_numbers' is set, the appropriate numbers are + directly written to the datacard (recommended for debugging purposes). + If not or a number cannot be obtained, '*' will be used. This forces + text2workspace.py to calculate these numbers on the fly. + """ + header = [] + ncats = "*" + nprocs = "*" + nsysts = "*" + if self._hardcode_numbers: + #get number of categories + if len(self._categories) != 0: + ncats = len(self._categories) + else: + print "Could not find categories! Cannot hard code 'imax'" + + #get number of processes + nprocs = self.get_number_of_procs() - 1 + if nprocs == -1: nprocs = "*" + + #get number of systematics + if len(self._systematics) != 0: + nsysts = len(self._systematics) + + header.append("imax {0} number of bins".format(ncats)) + header.append("jmax {0} number of processes minus 1".format(nprocs)) + header.append("kmax {0} number of nuisance parameters".format(nsysts)) + return "\n".join(header) + + def create_keyword_block(self): + """ + Create block with keywords with which to find the systematic variations + for different processes. This block has the following form: + shape $PROCESS $CHANNEL /path/to/rootfile $NOMINAL_KEY $SYST_VAR_KEY + + with + $PROCESS - process name (can be '*' for all processes) + $CHANNEL - category name (can be '*' for all categories) + /path/to/rootfile - path to .root file with templates + $NOMINAL_KEY - key for nominal templates. If '*' was used before, + this should contain '$CHANNEL' and/or '$PROCESS' + $SYST_VAR_KEY - key for templates for systematic variations. + The key has to contain '$SYSTEMATIC'. If '*' was + used before, this should contain '$CHANNEL' + and/or '$PROCESS' + """ + pass + + def create_observation_block(self): + """ + Create with observation. The block has the following format: + + bin $CHANNEL_1 $CHANNEL_2 (...) + observation $OBS_1_YIELD $OBS_2_YIELD (...) + + where + $CHANNEL_X - different categories + $OBS_X_YIELD - yield of template for data + (...) - place holder for more categories + THIS IS NOT PART OF THE DATACARD! + """ + pass + + def create_process_block(self): + """ + Create the process block of the datacard. It has the following format: + bin $CHANNEL_1 $CHANNEL_1 $CHANNEL_2 (...) + process $PROCESS_1 $PROCESS_2 $PROCESS_1 (...) + process $PROCESS_1_INDEX $PROCESS_2_INDEX $PROCESS_1_INDEX (...) + rate $PROCESS_1_YIELD $PROCESS_2_YIELD $PROCESS_1_YIELD (...) + + where + $CHANNEL_X - different categories + $PROCESS_X - name of process in respective channel + $PROCESS_X_INDEX - index of process (signal: <= 0, bkg: > 0) + $PROCESS_X_YIELD - yield of template for respective process + (...) - place holder for more categories + THIS IS NOT PART OF THE DATACARD! + """ + pass + + def create_systematics_block(self): + """ + Create block for nuisance parameters. Format is as follows: + $SYST_1_NAME $SYST_1_TYPE CORRELATION_PROC_1 CORRELATION_PROC_2 (...) + $SYST_2_NAME $SYST_2_TYPE CORRELATION_PROC_1 CORRELATION_PROC_2 (...) + (...) (...) (...) (...) (...) + + where + $SYST_X_NAME - name of respective nuisance parameter + $SYST_X_TYPE - type of respective nuisance parameter + $CORRELATION_PROC_X - correlation to respective processes + + IMPORTANT: The order of process has to be the same as in the + process block + """ + pass + + def write_datacard(self): + if path.exists(self._outputpath): + #create datacard header + content = [] + content.append(self.create_header()) + #create block with keywords for systematic variations + + #create observation block + + + with open(self._outputpath, "w") as f: + f.write(self._block_separator.join(content)) + else: + print "ERROR: Could not write datacard here:", self._outputpath + From d4d357af2f34e845f8d84ae16f66ca2e62e6f917 Mon Sep 17 00:00:00 2001 From: Philip Daniel Keicher Date: Fri, 5 Oct 2018 18:47:58 +0200 Subject: [PATCH 04/73] changed structure, started to make smaller modules --- base/helperClass.py | 24 ++- base/identificationLogic.py | 12 ++ base/valueConventions.py | 23 +++ categoryObject.py => src/categoryObject.py | 170 ++++++++++++--------- datacardMaker.py => src/datacardMaker.py | 6 +- processObject.py => src/processObject.py | 162 ++++++++++++++------ src/systematicObject.py | 137 +++++++++++++++++ systematicObject.py | 49 ------ 8 files changed, 405 insertions(+), 178 deletions(-) create mode 100644 base/identificationLogic.py create mode 100644 base/valueConventions.py rename categoryObject.py => src/categoryObject.py (50%) rename datacardMaker.py => src/datacardMaker.py (98%) rename processObject.py => src/processObject.py (53%) create mode 100644 src/systematicObject.py delete mode 100644 systematicObject.py diff --git a/base/helperClass.py b/base/helperClass.py index f52d51f..a7d6d80 100644 --- a/base/helperClass.py +++ b/base/helperClass.py @@ -1,3 +1,5 @@ +from ROOT import TFile, TH1 +from os import path as opath from sys import path as spath baserepodir = "/nfs/dust/cms/user/pkeicher/projects/base/classes" if not baserepodir in spath: @@ -7,9 +9,27 @@ class helperClass(helperClass_base): def __init__(self): super(helperClass, self).__init__() - def isfloat(value): + def isfloat(self, value): try: float(value) return True except ValueError: - return False \ No newline at end of file + return False + + def histogram_exists(self, file, histname): + if opath.exists(file): + f = TFile(file) + print f + if self.intact_root_file(f): + h = f.Get(histname) + if isinstance(h, TH1): + f.Close() + return True + else: + print ("WARNING: histogram '%s' does not exist in '%s'" + % (histname, file)) + else: + print "ERROR: File '%s' is broken!" % file + else: + print "ERROR: File '%s' does not exist!" % file + diff --git a/base/identificationLogic.py b/base/identificationLogic.py new file mode 100644 index 0000000..b3ee0e0 --- /dev/null +++ b/base/identificationLogic.py @@ -0,0 +1,12 @@ +class identificationLogic(object): + """docstring for identificationLogic""" + def init_variables(self): + self._nomkey = "defaultnominalkey" + self._systkey = "systkey" + self._procIden = "$PROCESS" #Identifier keyword for processes + self._chIden = "$CHANNEL" #Identifier keyword for categories/channels + self._systIden = "$SYSTEMATIC" #Identifier keyword for systematics + + def __init__(self): + self.init_variables() + \ No newline at end of file diff --git a/base/valueConventions.py b/base/valueConventions.py new file mode 100644 index 0000000..76f982c --- /dev/null +++ b/base/valueConventions.py @@ -0,0 +1,23 @@ +class valueConventions(object): + """docstring for valueConventions""" + def __init__(self, arg): + super(valueConventions, self).__init__() + self.arg = arg + + def is_good_systval(self, value): + """ + check if 'value' is a format allowed for systematic uncertainties + """ + is_good = False + if value is None: return is_good + if value == "-": + is_good = True + elif isinstance(value,float) or isinstance(value,int): + is_good = True + elif isinstance(value,str): + totest = value.split("/") + if len(totest) in [1,2]: + is_good = all(self.isfloat(v) for v in totest) + if not is_good: + print "Given value not suitable for an uncertainty in a datacard!" + return is_good \ No newline at end of file diff --git a/categoryObject.py b/src/categoryObject.py similarity index 50% rename from categoryObject.py rename to src/categoryObject.py index ef82fca..08d0280 100644 --- a/categoryObject.py +++ b/src/categoryObject.py @@ -5,28 +5,26 @@ sys.path.append(directory) from processObject import processObject +from identificationLogic import identificationLogic class categoryObject(object): - # def __init__(self): - # self._name = "categoryName" - # self._nomkey = "defaultnominalkey" - # self._systkey = "systkey" - # self._procIden = "processIdentifier" - # self._chIden = "channelIdentifier" - # self._systIden = "systIdentifier" - # self._data_obs = None - # self._signalprocs = {} - # self._bkgprocs = {} + def init_variables(self): + self._name = "categoryName" + self._data_obs = None + self._signalprocs = {} + self._bkgprocs = {} + self._debug = True + self._key_creator = identificationLogic() - def __init__( self, categoryName, defaultRootFile, - defaultnominalkey, + def __init__( self, categoryName=None, defaultRootFile=None, + defaultnominalkey=None, systkey = None, - listOfSignals = None, - listOfBkg = None, - processIdentifier = "$PROCESS", - channelIdentifier = "$CHANNEL", - systIdentifier = "$SYSTEMATIC" ): + dict_of_signals = None, + dict_of_bkgs = None, + processIdentifier = None, + channelIdentifier = None, + systIdentifier = None ): """ init category. A category has a name, a root file containing the process histograms and a key to find the nominal histograms @@ -37,50 +35,73 @@ def __init__( self, categoryName, defaultRootFile, Variables: categoryName -- name for this category - defaultRootFile -- use this root file path as default for all processes + defaultRootFile -- use this root file path as default for + all processes defaultnominalkey -- key to find the nominal histograms - systkey -- key to find the histograms corresponding to a nuisance parameter shape variation - processIdentifier -- string that is to be replaced with the process name in the keys - channelIdentifier -- string that is to be replaced with the channel name in the keys - systIdentifier -- string that is to be replaced with the nuisance parameter name in the keys + systkey -- key to find the histograms corresponding to + a nuisance parameter shape variation + processIdentifier -- string that is to be replaced with the + process name in the keys + channelIdentifier -- string that is to be replaced with the + channel name in the keys + systIdentifier -- string that is to be replaced with the + nuisance parameter name in the keys """ - # self.__init__() - self._name = categoryName - self._nomkey = defaultnominalkey - self._systkey = systkey - self._procIden = processIdentifier - self._chIden = channelIdentifier - self._systIden = systIdentifier + self.init_variables() + if not categoryName is None: + self._name = categoryName + if not defaultRootFile is None: + self._nomkey = defaultnominalkey + if not systkey is None: + self._systkey = systkey + if not processIdentifier is None: + self._procIden = processIdentifier + if not channelIdentifier is None: + self._chIden = channelIdentifier + if not systIdentifier is None: + self._systIden = systIdentifier + if not defaultnominalkey is None: + self._nomkey = defaultnominalkey - #check if process/channel identifiers are in nominal histo key - if self._procIden in self._nomkey: - print "WARNING:\tProcess identifier is still part of nominal histo key!" - if self._chIden in self._nomkey: - print "WARNING:\tChannel identifier is still part of nominal histo key! Will replace it" - self._nomkey = self._nomkey.replace(self._chIden, self._name) + # #check if process/channel identifiers are in nominal histo key + # self.is_part_of(self._procIden, self._nomkey) + # if self.is_part_of(self._chIden, self._nomkey): + # self._nomkey = self._nomkey.replace(self._chIden, self._name) - #check if systematics/process/channel identifiers are in systematics histo key - if not self._systIden in self._systkey: - print "WARNING:\tSystematic identifier still not part of nominal histo key!" - if self._chIden in self._systkey: - print "WARNING:\tChannel identifier is still part of systematic histo key! Will replace it" - self._systkey = self._systkey.replace(self._chIden, self._name) - if not self._systIden in self._systkey: - print "WARNING:\tSystematic identifier still not part of nominal histo key!" + # #check if systematics/process/channel identifiers + # #are in systematics histo key + # if not self.is_part_of(self._systIden, self._systkey): + # print "WARNING: Identifier for systematics not part of SystKey!" + # if self.is_part_of(self._chIden, self._systkey): + # self._systkey = self._systkey.replace(self._chIden, self._name) + #if a list of signal processes is given, add them with default vals - if listOfSignals: - for proc in listOfSignals: + if dict_of_signals: + for proc in dict_of_signals: self.add_signal_process(name = proc, rootfile = defaultRootFile) #if a list of bkg processes is given, add them with default vals - if listOfBkg: - for proc in listOfBkg: + if dict_of_bkgs: + for proc in dict_of_bkgs: self.add_background_process(name = proc, rootfile = defaultRootFile) - + def is_part_of(self, identifier, key): + if identifier in key: + if self._debug: + s = "Identifier '%s' is part of " % identifier + s += "keyword '%s'" % key + print s + return True + else: + if self._debug: + s = "Identifier '%s' is not part of " % identifier + s += "keyword '%s'" % key + print s + return False + @property def n_signal_procs(self): return len(self._signalprocs) @@ -128,34 +149,34 @@ def add_background_process( self, name, rootfile, rootfile = rootfile, histoname = histoname, systkey = systkey) - def add_process(self, dic, name, rootfile, - histoname = None, systkey = None - ): - changedKey = False - if histoname is None: - histoname = self._nomkey - if systkey is None: - systkey = self._systkey - if self._procIden in histoname: - print "WARNING:\tProcess identifier is still part of nominal histo key! Will replace it" - histoname = histoname.replace(self._procIden, name) - if self._chIden in histoname: - print "WARNING:\tChannel identifier is still part of nominal histo key! Will replace it" - histoname = histoname.replace(self._chIden, name) - if self._procIden in systkey: - print "WARNING:\tProcess identifier is still part of nominal histo key! Will replace it" - systkey = systkey.replace(self._procIden, name) - if self._chIden in systkey: - print "WARNING:\tChannel identifier is still part of nominal histo key! Will replace it" - systkey = systkey.replace(self._chIden, name) + # def add_process(self, dic, name, rootfile, + # histoname = None, systkey = None + # ): + # changedKey = False + # if histoname is None: + # histoname = self._nomkey + # if systkey is None: + # systkey = self._systkey + # if self._procIden in histoname: + # print "WARNING:\tProcess identifier is still part of nominal histo key! Will replace it" + # histoname = histoname.replace(self._procIden, name) + # if self._chIden in histoname: + # print "WARNING:\tChannel identifier is still part of nominal histo key! Will replace it" + # histoname = histoname.replace(self._chIden, name) + # if self._procIden in systkey: + # print "WARNING:\tProcess identifier is still part of nominal histo key! Will replace it" + # systkey = systkey.replace(self._procIden, name) + # if self._chIden in systkey: + # print "WARNING:\tChannel identifier is still part of nominal histo key! Will replace it" + # systkey = systkey.replace(self._chIden, name) - controlNomKey = self._nomkey.replace(self._procIden, name) - controlSysKey = self._systkey.replace(self._procIden, name) - if not (histoname == self._nomkey and systkey == self._systkey): - changedKey = True + # controlNomKey = self._nomkey.replace(self._procIden, name) + # controlSysKey = self._systkey.replace(self._procIden, name) + # if not (histoname == self._nomkey and systkey == self._systkey): + # changedKey = True - if name in dic: - print "" + # if name in dic: + # print "" #overloaded functions if input variable is a process def add_signal_process( self, process): @@ -189,3 +210,4 @@ def __str__(self): s.append("List of background processes:") for bkg in self._bkgprocs: s.append("\t%s" % self._bkgprocs[bkg]) + return "\n".join(s) diff --git a/datacardMaker.py b/src/datacardMaker.py similarity index 98% rename from datacardMaker.py rename to src/datacardMaker.py index 4b67ebf..a13b435 100644 --- a/datacardMaker.py +++ b/src/datacardMaker.py @@ -10,7 +10,7 @@ class datacardMaker(object): - def __init__(self): + def init_variables(self): self._header = [] self._bins = "" self._observation = "" @@ -21,11 +21,11 @@ def __init__(self): self._outputpath = "/path/for/datacard.txt" self._block_separator = "-"*130 - def __init__( self, pathToDatacard, + def __init__( self, pathToDatacard = "", processIdentifier = "$PROCESS", channelIdentifier = "$CHANNEL", systIdentifier = "$SYSTEMATIC"): - self.__init__() + self.init_variables() if pathToDatacard: self.load_from_file(pathToDatacard) diff --git a/processObject.py b/src/processObject.py similarity index 53% rename from processObject.py rename to src/processObject.py index 41c4a60..042a5b3 100644 --- a/processObject.py +++ b/src/processObject.py @@ -1,30 +1,48 @@ -from ROOT import TFile, TH1 +# from ROOT import TFile, TH1 from os import path from sys import path as spath thisdir = path.realpath(path.dirname(__file__)) -basedir = path.join(thisdir, "base") +basedir = path.join(thisdir, "../base") if not basedir in spath: spath.append(basedir) from helperClass import helperClass -# if not path.abspath(".") in spath: -# spath.append(path.abspath(".")) -# from systematicObject import systematicObject + + class processObject(object): helper = helperClass() - _debug = False - def __init__( self, processName = "", pathToRootfile = "", nominalname = "", - categoryname = ""): - - self._name = processName - self._rootfile = pathToRootfile - self._nominalhistname = nominalname - self._categoryname = categoryname - - self._eventcount = self.calculate_yield() - self._uncertainties = {} + helper._debug = 99 + + def init_variables(self): + self._name = "" + self._rootfile = "" + self._categoryname = "" + self._nominalhistname = "" + self._systkey = "" + self._eventcount = -1 + self._uncertainties = {} + self._debug = True + self._calculate_yield = False + + def __init__( self, processName = None, pathToRootfile = None, + nominal_hist_key = None, systematic_hist_key = None, + categoryname = None): + self.init_variables() + if not processName is None: + self._name = processName + if not pathToRootfile is None: + self._rootfile = pathToRootfile + if not nominal_hist_key is None: + self._nominalhistname = nominal_hist_key + if not categoryname is None: + self._categoryname = categoryname + + if self._calculate_yield: + self._eventcount = self.calculate_yield() + if not systematic_hist_key is None: + self._systkey = systematic_hist_key if self._debug: print "initialized process with name '%s' in category '%s'" % (self._name, self._categoryname) @@ -37,7 +55,7 @@ def calculate_yield(self): if path.exists(self._rootfile): infile = TFile(self._rootfile) #check if root file is intact - if infile.IsOpen() and not infile.IsZombie() and not infile.TestBit(TFile.kRecovered): + if self.helper.intact_root_file(infile): #if yes, try to load histogram hist = infile.Get(self._nominalhistname) @@ -137,15 +155,12 @@ def nominalhistname(self): @nominalhistname.setter def nominalhistname(self, hname): - if path.exists(self._rootfile): - f = TFile(self._rootfile) - h = f.Get(hname) - if isinstance(h, TH1): - self._nominalhistname = hname - else: - print "'%s' does not exist in '%s'" % (hname, self._rootfile) + + if self.helper.histogram_exists(file = self._rootfile, + histname = hname): + self._nominalhistname = hname else: - print "'%s' does not exist! You should check it again" % self._rootfile + print "'%s' does not exist in '%s'" % (hname, self._rootfile) def __str__(self): """ @@ -161,40 +176,78 @@ def __str__(self): s.append("\trootfile:\t%s" % self._rootfile) s.append("\tnominal histname:\t%s" % self._nominalhistname) s.append("\tyield:\t{0}".format(self._eventcount)) - if len(self._uncertainties) != 0: s.append("\t\tuncertainty\tcorrelation") - for syst in self._uncertainties: - s.append("\t\t%s\t%s" % (syst, self._uncertainties[syst])) - return "\n".join(s) + if len(self._uncertainties) != 0: + s.append("\tlist of uncertainties:") - def is_good_systval(self, value): - """ - check if 'value' is a format allowed for systematic uncertainties - """ - is_good = False - if value == "-": is_good = True - elif isinstance(value,float) or isinstance(value,int): is_good = True - elif isinstance(value,str): - totest = value.split("/") - if len(totest) in [1,2]: - is_good = all(helper.isfloat(v) for v in totest) - if not is_good: print "The given value is not suitable for an uncertainty in a datacard!" - return is_good + temp = "\t\t%s" % "uncertainty".ljust(15) + temp += "\t%s" % "type".ljust(10) + temp += "\t%s" % "correlation".ljust(15) + s.append(temp) + s.append("\t\t"+"_"*len(temp.expandtabs())) + for syst in self._uncertainties: + temp = "\t\t%s" % syst.ljust(15) + temp += "\t%s" % self._uncertainties[syst]["type"].ljust(10) + temp += "\t%s" % str(self._uncertainties[syst]["value"]).ljust(15) + s.append(temp) + return "\n".join(s) - def add_uncertainty(self, systname, typ, value): + def add_uncertainty(self, syst, typ, value): """ add an uncertainty to this process. This function checks - whether there already is an entry for 'systname' - the given value is suitable for a datacard (see 'is_good_systval') and only adds the systematics if it's new and has a good value """ - if not systname in self._uncertainties and self.is_good_systval(value): - self._uncertainties[systname] = {"value" : str(value), "type" : typ} + + if isinstance(syst, str) and isinstance(typ, str): + if not syst in self._uncertainties: + if typ == "shape": + print "Looking for varied histograms for systematic" + # if self.helper.histogram_exists(self._rootfile, + # self._systkey.replace(self._sy)) + if self.helper.is_good_systval(value): + self._uncertainties[syst] = {} + self._uncertainties[syst]["type"] = typ + self._uncertainties[syst]["value"] = value + return True + else: + temp = "There is already an entry for uncertainty " + temp += "%s in process %s" % (systname, self.get_name()) + print temp else: - print "There is already an entry for uncertainty %s in process %s" % (systname, self.get_name()) + print "ERROR: Could not add uncertainty - input arguments invalid!" + return False + + # def add_uncertainty_from_systematicObject(self, systematic, value = None): + # """ + # add an uncertainty to this process. This function checks + # - whether there already is an entry for 'systname' + # - the given value is suitable for a datacard (see 'is_good_systval') + # and only adds the systematics if it's new and has a good value + # """ + # if isinstance(systematic, systematicObject): + # if not sysname in self._uncertainties: + # if systematic.is_good_systval(value): + # cor = systematic.set_correlation(process = self) + # if cor == "-": + # systematic.add_process( process = self, + # correlation = value) + # else: + # systematic.set_correlation( process = self, + # value = value) + # self._uncertainties[sysname] = systematic + + # else: + # temp = "There is already an entry for uncertainty " + # temp += "%s in process %s" % (systname, self.get_name()) + # print temp + # else: + # print "ERROR: systematic needs to be a systematicObject!" def set_uncertainty(self, systname, typ, value): """ - set the uncertainty 'systname' for this process to type 'typ' and value 'value'. This function checks + set the uncertainty 'systname' for this process to type 'typ' + and value 'value'. This function checks - whether there is an entry for 'systname' in the first place - the given value is suitable for a datacard (see 'is_good_systval') and only adds the systematics if there is an entry and the value is good @@ -211,7 +264,7 @@ def get_uncertainty_value(self, systname): If there is no entry for 'systname' in this process, the function returns '-' """ if systname in self._uncertainties: - return self._uncertainties[systname] + return self._uncertainties[systname]["value"] else: return "-" @@ -221,6 +274,15 @@ def get_uncertainty_type(self, systname): If there is no entry for 'systname' in this process, the function returns '' """ if systname in self._uncertainties: - return self._uncertainties[systname] + return self._uncertainties[systname]["type"] else: - return "" \ No newline at end of file + return "" + + # def get_uncertainty(self, systname): + # """ + # return systematicObject if there is one + # """ + # if systname in self._uncertainties: + # return self._uncertainties[systname] + # else: + # return None \ No newline at end of file diff --git a/src/systematicObject.py b/src/systematicObject.py new file mode 100644 index 0000000..673bbd3 --- /dev/null +++ b/src/systematicObject.py @@ -0,0 +1,137 @@ +from os import path +from sys import path as spath +thisdir = path.realpath(path.dirname(__file__)) +basedir = path.join(thisdir, "base") +if not basedir in spath: + spath.append(basedir) +from helperClass import helperClass + +if not thisdir in spath: + spath.append(thisdir) + +from processObject import processObject + +class systematicObject(object): + helper = helperClass() + def init_variables(self): + self._name = "" + self._type = "" + self._dic = {} + def __init__(self, name=None, nature=None, dic = None): + + if not name is None: + try: + self._name = str(name) + except ValueError: + print "Could not cast name into a string! Name not set" + + if not nature is None: + try: + self._type = str(nature) + except ValueError: + print "Could not cast type into a string! Type not set" + + + if dic and isinstance(dic, dict): + self._dic = dic + + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + try: + self._name = str(name) + except ValueError: + print "Could not cast 'name' into a string! 'name' not set" + + @property + def type(self): + return self._type + + @type.setter + def type(self, typ): + try: + self._type = str(typ) + except ValueError: + print "Could not cast 'type' into a string! 'type' not set" + + def add_process(self, category, procname, value): + if not category in self._dic: + self._dic[category] = {} + if not procname in self._dic[category]: + if self.helper.is_good_systval(value): + self._dic[category][procname] = value + else: + temp = "ERROR: process '%s'" % procname + temp += " in category '%s'" % category + temp += " is already known to systematic '%s'!" % self._name + temp += " Use 'processObject_base.set_correlation()' instead" + print temp + + def add_process(self, dic, category): + if not category in self._dic: + if dic and isinstance(dic, dict): + if all(self.helper.is_good_systval(dic[key]) for key in dic): + self._dic[category] += dic + else: + print "ERROR: input dictionary contains bad values!" + else: + print "Could not add process: input must be dictionary!" + else: + temp = "ERROR: category '%s'" % category + temp += " is unknowns to systematic '%s'" % self._name + print temp + + def add_process(self, process, correlation): + if isinstance(process, processObject_base): + cor = self.get_correlation(process.category, process.name) + if cor == "-": + self.add_process( category = process.category, + procname = process.name, + value = correlation) + else: + temp = "process '%s' is already known to " % process.name + temp += "systematic '%s'! " % self._name + temp += "Use the 'set_correlation' function" + print temp + else: + print "ERROR: Could not add process - input must be processObject_base" + + def get_correlation(self, category, procname): + if category in self._dic: + if procname in self._dic[category]: + return str(self._dic[category][procname]) + else: + return "-" + + def get_correlation(self, process): + category = process.category + procname = process.name + return self.get_correlation(category = category, procname = procname) + + def set_correlation(self, category, procname, value): + if category in self._dic: + if procname in self._dic[category]: + if self.helper.is_good_systval(value): + self._dic[category][procname] = value + else: + print "Could not add process: input must be dictionary!" + else: + temp = "ERROR: category '%s'" % category + temp += " is unknown to systematic '%s'" % self._name + print temp + + def set_correlation(self, process, value): + if isinstance(process, processObject_base): + procname = process.name + category = process.category + if procname in self._dic[category]: + if self.helper.is_good_systval(value): + self._dic[category][procname] = value + else: + print "Could not add process: input must be dictionary!" + else: + print "ERROR: Could not set process - input must be processObject_base" + diff --git a/systematicObject.py b/systematicObject.py deleted file mode 100644 index 6dcde16..0000000 --- a/systematicObject.py +++ /dev/null @@ -1,49 +0,0 @@ -class systematicObject(object): - def __init__(self, name, nature, dic = None): - - self._name = name - self._type = nature - self._dic = {} - - if dic and isinstance(dic, dict): - self._dic = dic - - @property - def name(self): - return self._name - - @name.setter - def name(self, value): - self._name = str(value) - - @property - def type(self): - return self._type - - @type.setter - def type(self, typ): - self._type = typ - - def add_process(self, category, procname, correlation): - if category in self._dic: - self._dic[category][procname] = correlation - else: - print "ERROR: category is not known to uncertainty '%s'" % self._name - - def add_process(self, dic, category): - if category in self._dic: - if dic and isinstance(dic, dict): - self._dic[category] += dic - else: - print "Could not add process: input must be dictionary!" - else: - print "ERROR: category is not known to uncertainty '%s'" % self._name - - def get_correlation(self, category, procName): - if category in self._dic: - if procName in self._dic[category]: - return str(self._dic[category][procName]) - else: - return "-" - - From e5e22e287011353555159f9983597b18cbf6125d Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Fri, 12 Oct 2018 14:30:04 +0200 Subject: [PATCH 05/73] test file added --- test.py | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ testdatacard.txt | 31 +++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 test.py create mode 100644 testdatacard.txt diff --git a/test.py b/test.py new file mode 100644 index 0000000..a7c3e53 --- /dev/null +++ b/test.py @@ -0,0 +1,91 @@ +categories = [] +with open("testdatacard.txt") as datacard: + lines = datacard.read().splitlines() + for n, line in enumerate(lines): + if line.startswith("process") and lines[n+1].startswith("process"): + categories.append(line) + +#for entry in categories: +category = categories[0].split() + +class test: + def __init__( self, pathToDatacard): + self._categories = [] + self._header = [] + self._process = "" + self._categoryprocess = "" + self._category = [] + self._bin = "" + + def load_from_file(self, pathToDatacard): + print "loading datacard from", pathToDatacard + with open(pathToDatacard) as datacard: + lines = datacard.read().splitlines() + self._shapelines_ = [] + self._processes_= "" + self._binprocesses_= "" + for n, line in enumerate(lines): + if line.startswith("-"): + continue + elif line.startswith("Combination") or line.startswith("imax") or line.startswith("kmax") or line.startswith("jmax"): + self._header.append(line) + elif line.startswith("bin") and n != len(lines) and lines[n+1].startswith("observation"): + self._bins = line + self._observation = lines[n+1] + elif line.startswith("shapes"): + self._shapelines_.append(line) + elif line.startswith("process") and n != 0 and lines[n-1].startswith("bin"): + self._processes_=line + self._binprocesses_=lines[n-1] + else: + pass + + self._categories_ = {} + self._category_ = [] + bins = self._bins.split() + bins.pop(0) + for category in bins: + self._categories_[category] = {} + self._category_.append(category) + + self._process_ = {} + processes = self._processes_.split() + processes.pop(0) + binprocesses = self._binprocesses_.split() + binprocesses.pop(0) + if len(processes)==len(binprocesses): + for process,binprocess in zip(processes, binprocesses): + self._categories_[binprocess][process]={} + self._process_[binprocess]=[] + self._process_[binprocess].append(process) + print self._process_ + print self._category_ + for shapelines in self._shapelines_: + shape = shapelines.split() + for category in self._category_: + self._categories_[category]["shapes"]={} + if shape[2] == category or shape[2]=="*": + for process in self._process_[category]: + if shape[1] == "*": + print shape[3] + self._categories_[category]["shapes"]["default"]={} + self._categories_[category]["shapes"]["default"]["rootfile"]=shape[3] + self._categories_[category]["shapes"]["default"]["hist"]=shape[4] + self._categories_[category]["shapes"]["default"]["systhist"]=shape[5] + #elif shape[1] == process + #self._categories_[category][shapes][process]={} + #self._categories_[category][shapes][process][rootfile]=shape[3] + #self._categories_[category][shapes][process][hist]=shape[4] + #self._categories_[category][shapes][process][systhist]=shape[5] + + + + + +s = test("testdatacard.txt") +s.load_from_file("testdatacard.txt") +print s._categories_ +#print s._categories_["ljets_j5_tge4_DeepCSV"]["shapes"]["rootfile"] + + + diff --git a/testdatacard.txt b/testdatacard.txt new file mode 100644 index 0000000..69993b7 --- /dev/null +++ b/testdatacard.txt @@ -0,0 +1,31 @@ +imax * # number of channels +jmax * # number of backgrounds +kmax * # number of nuisance parameters +--------------- +bin ljets_j5_tge4_DeepCSV +observation 1170.778059 +--------------- +shapes * * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root $PROCESS_finaldiscr_$CHANNEL $PROCESS_finaldiscr_$CHANNEL_$SYSTEMATIC +shapes ttH_hbb * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hbb_finaldiscr_$CHANNEL ttH_hbb_finaldiscr_$CHANNEL_$SYSTEMATIC +shapes ttH_hcc * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hcc_finaldiscr_$CHANNEL ttH_hcc_finaldiscr_$CHANNEL_$SYSTEMATIC +shapes ttH_hww * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hww_finaldiscr_$CHANNEL ttH_hww_finaldiscr_$CHANNEL_$SYSTEMATIC +shapes ttH_hzz * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hzz_finaldiscr_$CHANNEL ttH_hzz_finaldiscr_$CHANNEL_$SYSTEMATIC +shapes ttH_htt * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_htt_finaldiscr_$CHANNEL ttH_htt_finaldiscr_$CHANNEL_$SYSTEMATIC +shapes ttH_hgg * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hgg_finaldiscr_$CHANNEL ttH_hgg_finaldiscr_$CHANNEL_$SYSTEMATIC +shapes ttH_hgluglu * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hgluglu_finaldiscr_$CHANNEL ttH_hgluglu_finaldiscr_$CHANNEL_$SYSTEMATIC +shapes ttH_hzg * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hzg_finaldiscr_$CHANNEL ttH_hzg_finaldiscr_$CHANNEL_$SYSTEMATIC +--------------- +bin ljets_j5_tge4_DeepCSV ljets_j5_tge4_DeepCSV ljets_j5_tge4_DeepCSV ljets_j5_tge4_DeepCSV ljets_j5_tge4_DeepCSV ljets_j5_tge4_DeepCSV +process ttH_hbb ttbarOther ttbarPlusB ttbarPlus2B ttbarPlusBBbar ttbarPlusCCbar +process 0 1 2 3 4 5 +rate -1 276.773 168.479 81.369 386.849 197.186 +--------------- +lumi_13TeV_2016 lnN 1.025 1.025 1.025 1.025 1.025 1.025 +QCDscale_ttH lnN 0.908/1.058 - - - - - +QCDscale_ttbar lnN - 0.96/1.02 0.96/1.02 0.96/1.02 0.96/1.02 0.96/1.02 +pdf_gg_ttH lnN 1.036 - - - - - +pdf_gg lnN - 1.04 1.04 1.04 1.04 1.04 +bgnorm_ttbarPlusB lnN - - 1.5 - - - +bgnorm_ttbarPlus2B lnN - - - 1.5 - - +bgnorm_ttbarPlusBBbar lnN - - - - 1.5 - +bgnorm_ttbarPlusCCbar lnN - - - - - 1.5 From 25ebbab6e3686577ea5fb469d1b0590923274237 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Fri, 12 Oct 2018 15:03:58 +0200 Subject: [PATCH 06/73] changed test file --- test.py | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/test.py b/test.py index a7c3e53..5f2b497 100644 --- a/test.py +++ b/test.py @@ -17,11 +17,13 @@ def __init__( self, pathToDatacard): self._category = [] self._bin = "" + def load_from_file(self, pathToDatacard): print "loading datacard from", pathToDatacard with open(pathToDatacard) as datacard: lines = datacard.read().splitlines() self._shapelines_ = [] + self._systematics_ = [] self._processes_= "" self._binprocesses_= "" for n, line in enumerate(lines): @@ -36,19 +38,29 @@ def load_from_file(self, pathToDatacard): self._shapelines_.append(line) elif line.startswith("process") and n != 0 and lines[n-1].startswith("bin"): self._processes_=line - self._binprocesses_=lines[n-1] - else: + self._binprocesses_=lines[n-1] + elif line.startswith("bin") and lines[n+1].startswith("process"): + pass + elif line.startswith("process") and lines[n+1].startswith("rate"): pass + elif line.startswith("observation") or line.startswith("rate"): + pass + else: + self._systematics_.append(line) self._categories_ = {} self._category_ = [] + self._process_ = {} bins = self._bins.split() bins.pop(0) for category in bins: self._categories_[category] = {} + self._process_[category]=[] + self._categories_[category]["shapes"]={} self._category_.append(category) - - self._process_ = {} + + print self._systematics_ + processes = self._processes_.split() processes.pop(0) binprocesses = self._binprocesses_.split() @@ -56,30 +68,29 @@ def load_from_file(self, pathToDatacard): if len(processes)==len(binprocesses): for process,binprocess in zip(processes, binprocesses): self._categories_[binprocess][process]={} - self._process_[binprocess]=[] + self._categories_[binprocess][process]["shapes"]={} self._process_[binprocess].append(process) - print self._process_ - print self._category_ + + for shapelines in self._shapelines_: shape = shapelines.split() for category in self._category_: - self._categories_[category]["shapes"]={} if shape[2] == category or shape[2]=="*": - for process in self._process_[category]: - if shape[1] == "*": - print shape[3] + if shape[1] == "*": self._categories_[category]["shapes"]["default"]={} self._categories_[category]["shapes"]["default"]["rootfile"]=shape[3] self._categories_[category]["shapes"]["default"]["hist"]=shape[4] self._categories_[category]["shapes"]["default"]["systhist"]=shape[5] - #elif shape[1] == process - #self._categories_[category][shapes][process]={} - #self._categories_[category][shapes][process][rootfile]=shape[3] - #self._categories_[category][shapes][process][hist]=shape[4] - #self._categories_[category][shapes][process][systhist]=shape[5] + + for process in self._process_[category]: + if shape[1] == process: + self._categories_[category][process]["shapes"]={} + self._categories_[category][process]["shapes"]["rootfile"]=shape[3] + self._categories_[category][process]["shapes"]["hist"]=shape[4] + self._categories_[category][process]["shapes"]["systhist"]=shape[5] - + s = test("testdatacard.txt") From 45e3081e8dde27ed4249afc2075e471634ce55e1 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Fri, 12 Oct 2018 15:43:51 +0200 Subject: [PATCH 07/73] test file working with dicts --- src/datacardMaker.py | 8 +++++++- test.py | 18 ++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index a13b435..bf161e8 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -90,6 +90,8 @@ def load_from_file(self, pathToDatacard): with open(pathToDatacard) as datacard: lines = datacard.read().splitlines() self._shapelines_ = [] + self._processes_= [] + self._binprocesses_= [] for n, line in enumerate(lines): if line.startswith("-"): continue @@ -101,9 +103,13 @@ def load_from_file(self, pathToDatacard): elif line.startswith("shapes"): self._shapelines_.append(line) elif line.startswith("process") and n != 0 and lines[n-1].startswith("bin"): - pass + self._processes_.append(line) + self._binprocesses_.append(line) + else: pass + + else: diff --git a/test.py b/test.py index 5f2b497..46a2cd0 100644 --- a/test.py +++ b/test.py @@ -59,7 +59,7 @@ def load_from_file(self, pathToDatacard): self._categories_[category]["shapes"]={} self._category_.append(category) - print self._systematics_ + processes = self._processes_.split() processes.pop(0) @@ -69,6 +69,7 @@ def load_from_file(self, pathToDatacard): for process,binprocess in zip(processes, binprocesses): self._categories_[binprocess][process]={} self._categories_[binprocess][process]["shapes"]={} + self._categories_[binprocess][process]["systematics"]={} self._process_[binprocess].append(process) @@ -91,12 +92,25 @@ def load_from_file(self, pathToDatacard): + for systematics in self._systematics_: + systematic = systematics.split() + sys=systematic[0] + typ=systematic[1] + print sys + print typ + systematic.pop(1) + systematic.pop(0) + for value,process,category in zip(systematic,processes,binprocesses): + if value!="-": + self._categories_[category][process]["systematics"][sys]={} + self._categories_[category][process]["systematics"][sys]["type"]=typ + self._categories_[category][process]["systematics"][sys]["value"]=value s = test("testdatacard.txt") s.load_from_file("testdatacard.txt") print s._categories_ -#print s._categories_["ljets_j5_tge4_DeepCSV"]["shapes"]["rootfile"] +print s._categories_["ljets_j5_tge4_DeepCSV"]["ttbarPlus2B"] From 554f395e9d0208aef3eb5291751d9f09c68bcfdf Mon Sep 17 00:00:00 2001 From: Philip Daniel Keicher Date: Mon, 15 Oct 2018 18:05:14 +0200 Subject: [PATCH 08/73] successfully out-sourced value conventions and identification logic --- base/identificationLogic.py | 215 +++++++++++++++++++++++++++++++++++- base/valueConventions.py | 18 ++- src/categoryObject.py | 3 +- src/processObject.py | 45 +++++--- 4 files changed, 255 insertions(+), 26 deletions(-) diff --git a/base/identificationLogic.py b/base/identificationLogic.py index b3ee0e0..50a647f 100644 --- a/base/identificationLogic.py +++ b/base/identificationLogic.py @@ -1,12 +1,215 @@ class identificationLogic(object): - """docstring for identificationLogic""" + """ + The identificationLogic class is meant to handle all logic concerning + the key handling for the different categories required in datacards. + + The class currently has the following properties: + + generic_nominal_key -- generic key for nominal histograms + , e.g. $PROCESS_finaldiscr_$CHANNEL + (member variable: _generic_nom_key) + generic_systematics_key -- generic key for histograms for + systematic variations , e.g. + $PROCESS_finaldiscr_$CHANNEL_$SYSTEMATIC + (member variable: _generic_syst_key) + process_identificator -- Identifier keyword for processes + , e.g. $PROCESS + (member variable: _procIden) + channel_identificator -- Identifier keyword for categories/channels + , e.g. $CHANNEL + (member variable: _chIden) + systematics_identificator -- Identifier keyword for systematics + , e.g. $SYSTEMATIC + (member variable: _systIden) + """ def init_variables(self): - self._nomkey = "defaultnominalkey" - self._systkey = "systkey" + """ + define member variables here + """ + self._generic_nom_key = "$PROCESS_finaldiscr_$CHANNEL" + self._generic_syst_key = "$PROCESS_finaldiscr_$CHANNEL_$SYSTEMATIC" self._procIden = "$PROCESS" #Identifier keyword for processes self._chIden = "$CHANNEL" #Identifier keyword for categories/channels self._systIden = "$SYSTEMATIC" #Identifier keyword for systematics - def __init__(self): - self.init_variables() - \ No newline at end of file + def __init__( self, generic_nominal_key = "", generic_systematics_key = "", + process_id_key = "", channel_id_key = "", + systematics_id_key = ""): + """ + initialize identificationLogic. + """ + self.init_variables() + if not generic_nominal_key == "": + self._generic_nom_key = generic_nominal_key + if not generic_systematics_key == "": + self._generic_syst_key = generic_systematics_key + + def __str__(self): + """ + print out all member variables + """ + s = [] + s.append("Identification logic:") + s.append("\tChannel Identification Key: %s" % self._chIden) + s.append("\tProcess Identification Key: %s" % self._procIden) + s.append("\tSystematics Identification Key: %s" % self._systIden) + s.append("\tGeneric Key for nominal histograms: %s" % self._generic_nom_key) + s.append("\tGeneric Key for systematic histograms: %s" % self._generic_syst_key) + print "\n".join(s) + + #define properties for member variables + @property + def generic_nominal_key(self): + return self._generic_nom_key + @generic_nominal_key.setter + def generic_nominal_key(self, key): + if not self._procIden in key: + s = "WARNING: process identification keyword '%s'" % self._procIden + s+= " is not part of new nominal key '%s'!" % key + print s + if not self._chIden in key: + s = "WARNING: channel identification keyword '%s'" % self._procIden + s+= " is not part of new nominal key '%s'!" % key + print s + self._generic_nom_key = key + + @property + def generic_systematics_key(self): + return self._generic_syst_key + @generic_systematics_key.setter + def generic_systematics_key(self, key): + if not self._procIden in key: + s = "WARNING: process identification keyword '%s'" % self._procIden + s+= " is not part of new systematics key '%s'!" % key + print s + if not self._chIden in key: + s = "WARNING: channel identification keyword '%s'" % self._procIden + s+= " is not part of new systematics key '%s'!" % key + print s + if not self._systIden in key: + s = "WARNING: systematics ID keyword '%s'" % self._procIden + s+= " is not part of new systematics key '%s'!" % key + print s + self._generic_syst_key = key + + @property + def process_identificator(self): + return self._procIden + @process_identificator.setter + def process_identificator(self, key): + self._procIden = key + + @property + def channel_identificator(self): + return self._chIden + @channel_identificator.setter + def channel_identificator(self, key): + self._chIden = key + + @property + def systematics_identificator(self): + return self._systIden + @systematics_identificator.setter + def systematics_identificator(self, key): + self._systIden = key + + def insert_channel(self, channel_name, base_key): + """ + build a key from 'base_key' for a specific channel 'channel_name'. + """ + if not base_key is None or base_key == "": + print "unsuitable base_key!" + return "" + if self._chIden in base_key: + return base_key.replace(self._chIden, channel_name) + + def insert_process(self, process_name, base_key): + """ + build a key from 'base_key' for a specific process 'process_name'. + """ + if not base_key is None or base_key == "": + print "unsuitable base_key!" + return "" + if self._procIden in base_key: + return base_key.replace(self._procIden, process_name) + + def insert_systematic(self, systematic_name, base_key): + """ + build a key from 'base_key' for a specific systematic 'systematic_name'. + """ + if not base_key is None or base_key == "": + print "unsuitable base_key!" + return "" + if self._systIden in base_key: + return base_key.replace(self._systIden, systematic_name) + + def build_nominal_histo_name( self, process_name, channel_name, + base_key = ""): + """ + build nominal histogram name for process 'process_name' in + category 'channel_name'. The histogram is built based on the + generic nominal histogram key by default. + """ + if base_key == "" or not isinstance(base_key, str): + base_key = self._generic_nom_key + key = self.insert_process( process_name=process_name, + base_key = base_key) + key = self.insert_channel( channel_name = channel_name, + base_key = key) + return key + + def build_systematics_histo_name_down( self, process_name, channel_name, + systematic_name, base_key = ""): + """ + build systematic histogram name for process 'process_name' in + category 'channel_name' and 'down' variation for + systematic 'systematic_name'. The histogram is built based on the + generic systematic histogram key by default. + """ + if base_key == "" or not isinstance(base_key, str): + base_key = self._generic_syst_key + key = self.insert_process( process_name=process_name, + base_key = base_key) + key = self.insert_channel( channel_name = channel_name, + base_key = key) + key = self.insert_systematic( systematic_name = systematic_name, + base_key = key) + return key+"Down" + + def build_systematics_histo_name_up( self, process_name, channel_name, + systematic_name, base_key = ""): + """ + build systematic histogram name for process 'process_name' in + category 'channel_name' and 'up' variation for + systematic 'systematic_name'. The histogram is built based on the + generic systematic histogram key by default. + """ + if base_key == "" or not isinstance(base_key, str): + base_key = self._generic_syst_key + key = self.insert_process( process_name=process_name, + base_key = base_key) + key = self.insert_channel( channel_name = channel_name, + base_key = key) + key = self.insert_systematic( systematic_name = systematic_name, + base_key = key) + return key+"Up" + + def build_systematics_histo_names( self, process_name, channel_name, + systematic_name, base_key = ""): + """ + build systematic histogram name for process 'process_name' in + category 'channel_name' and systematic 'systematic_name'. + The histogram is built based on the generic systematic histogram key + by default. Return list of strings with two histogram names: + first one is for 'up' variation + second one is for 'down' variation + """ + up = self.build_systematics_histo_name_up(process_name = process_name, + channel_name = channel_name, + systematic_name = systematic_name, + base_key = base_key) + down = self.build_systematics_histo_name_down(process_name=process_name, + channel_name = channel_name, + systematic_name = systematic_name, + base_key = base_key) + return [up, down] \ No newline at end of file diff --git a/base/valueConventions.py b/base/valueConventions.py index 76f982c..c3e5aea 100644 --- a/base/valueConventions.py +++ b/base/valueConventions.py @@ -1,8 +1,18 @@ +from os import path +from sys import path as spath +thisdir = path.realpath(path.dirname(__file__)) +basedir = path.join(thisdir, "../base") +if not basedir in spath: + spath.append(basedir) +from helperClass import helperClass + class valueConventions(object): """docstring for valueConventions""" - def __init__(self, arg): - super(valueConventions, self).__init__() - self.arg = arg + _helper = helperClass() + def __init__(self): + + print "Initializing valueConventions" + def is_good_systval(self, value): """ @@ -17,7 +27,7 @@ def is_good_systval(self, value): elif isinstance(value,str): totest = value.split("/") if len(totest) in [1,2]: - is_good = all(self.isfloat(v) for v in totest) + is_good = all(self._helper.isfloat(v) for v in totest) if not is_good: print "Given value not suitable for an uncertainty in a datacard!" return is_good \ No newline at end of file diff --git a/src/categoryObject.py b/src/categoryObject.py index 08d0280..21c67a1 100644 --- a/src/categoryObject.py +++ b/src/categoryObject.py @@ -8,13 +8,12 @@ from identificationLogic import identificationLogic class categoryObject(object): - + _debug = True def init_variables(self): self._name = "categoryName" self._data_obs = None self._signalprocs = {} self._bkgprocs = {} - self._debug = True self._key_creator = identificationLogic() def __init__( self, categoryName=None, defaultRootFile=None, diff --git a/src/processObject.py b/src/processObject.py index 042a5b3..0fab72e 100644 --- a/src/processObject.py +++ b/src/processObject.py @@ -6,12 +6,13 @@ if not basedir in spath: spath.append(basedir) from helperClass import helperClass - - - +from identificationLogic import identificationLogic +from valueConventions import valueConventions class processObject(object): helper = helperClass() + id_logic = identificationLogic() + value_rules = valueConventions() helper._debug = 99 def init_variables(self): @@ -44,7 +45,9 @@ def __init__( self, processName = None, pathToRootfile = None, if not systematic_hist_key is None: self._systkey = systematic_hist_key if self._debug: - print "initialized process with name '%s' in category '%s'" % (self._name, self._categoryname) + s = "initialized process with name '%s'" % self._name + s += "in category '%s'" % self._categoryname + print s def calculate_yield(self): """ @@ -52,6 +55,8 @@ def calculate_yield(self): """ #open rootfile if it exsists y = -1 + + #TODO: export file logic to dedicated class if path.exists(self._rootfile): infile = TFile(self._rootfile) #check if root file is intact @@ -63,10 +68,14 @@ def calculate_yield(self): #if successful, save yield y = hist.Integral() else: - print "ERROR:\tunable to load histogram! I will let combine calculate it on the fly, but it could crash" + s = "ERROR:\tunable to load histogram!" + s += " I will let combine calculate it on the fly," + s += " but it could crash" infile.Close() else: - print "ERROR:\tunable to open root file for process {0}, cannot set yield".format(self._name) + s = "ERROR:\tunable to open root file for" + s += " process %s" % self._name + print s else: print "ERROR:\troot file does not exist! Cannot set yield for process {0}".format(self._name) return y @@ -195,7 +204,8 @@ def add_uncertainty(self, syst, typ, value): """ add an uncertainty to this process. This function checks - whether there already is an entry for 'systname' - - the given value is suitable for a datacard (see 'is_good_systval') + - the given value is suitable for a datacard + (see valueConventions.is_good_systval) and only adds the systematics if it's new and has a good value """ @@ -205,11 +215,13 @@ def add_uncertainty(self, syst, typ, value): print "Looking for varied histograms for systematic" # if self.helper.histogram_exists(self._rootfile, # self._systkey.replace(self._sy)) - if self.helper.is_good_systval(value): + if self.value_rules.is_good_systval(value): self._uncertainties[syst] = {} self._uncertainties[syst]["type"] = typ self._uncertainties[syst]["value"] = value return True + else: + print "Value {0} is not a good value!".format(value) else: temp = "There is already an entry for uncertainty " temp += "%s in process %s" % (systname, self.get_name()) @@ -252,16 +264,20 @@ def set_uncertainty(self, systname, typ, value): - the given value is suitable for a datacard (see 'is_good_systval') and only adds the systematics if there is an entry and the value is good """ - if systname in self._uncertainties and self.is_good_systval(value): - self._uncertainties[systname]["value"] = str(value) - self._uncertainties[systname]["type"] = typ + if systname in self._uncertainties: + if self.value_rules.is_good_systval(value): + self._uncertainties[systname]["value"] = str(value) + self._uncertainties[systname]["type"] = typ else: - print "There is no entry for uncertainty %s in process %s" % (systname, self.get_name()) + s = "There is no entry for uncertainty %s" % systname + s += " in process %s! Please add it first" % self.get_name() + print s def get_uncertainty_value(self, systname): """ return correlation of uncertainty 'systname' with this process. - If there is no entry for 'systname' in this process, the function returns '-' + If there is no entry for 'systname' in this process, the function + returns '-' """ if systname in self._uncertainties: return self._uncertainties[systname]["value"] @@ -271,7 +287,8 @@ def get_uncertainty_value(self, systname): def get_uncertainty_type(self, systname): """ return type of uncertainty 'systname' in this process. - If there is no entry for 'systname' in this process, the function returns '' + If there is no entry for 'systname' in this process, the function + returns '' """ if systname in self._uncertainties: return self._uncertainties[systname]["type"] From 378966729df4e399999c970916d5c10b6a5725f1 Mon Sep 17 00:00:00 2001 From: Philip Daniel Keicher Date: Mon, 15 Oct 2018 19:00:48 +0200 Subject: [PATCH 09/73] implemented file handler --- base/fileHandler.py | 99 ++++++++++++++++++++++++++++++++++++++++++++ base/helperClass.py | 20 +-------- src/processObject.py | 31 ++++---------- 3 files changed, 108 insertions(+), 42 deletions(-) create mode 100644 base/fileHandler.py diff --git a/base/fileHandler.py b/base/fileHandler.py new file mode 100644 index 0000000..eacc5e5 --- /dev/null +++ b/base/fileHandler.py @@ -0,0 +1,99 @@ +from ROOT import TFile, TH1 +from os import path as opath + +class fileHandler(object): + """ + The fileHandler class manages all access to files required for the analysis + TODO: Improve memory handling by cleverly closing files + """ + _debug = 200 + + def init_variables(self): + self._filepath = "/path/to/file" + self._file = None + + def __init__(self): + if self._debug >= 10: + print "Initializing fileHandler" + self.init_variables() + if opath.exists(self._filepath): + self._file = self.load_file(filepath) + + def __del__(self): + self.close_file() + + def is_file_open(self): + if isinstance(self._file, TFile): + return self._file.IsOpen() + return False + + def close_file(self): + if self.is_file_open: + self._file.Close() + + def open_file(self, path): + if self._debug >= 99: + print "opening file '%s'" % filepath + if opath.exists(filepath): + f = TFile.Open(filepath) + if self.intact_root_file(f): + self._file = f + else: + if self._debug >= 3: + print "Could not open file '%s'" % filepath + else: + if self._debug >= 3: + print "File '%s' does not exist!" % filepath + return None + + @property + def filepath(self): + return self._filepath + @filepath.setter + def filepath(self, path): + if opath.exists(path): + self.close_file() + self.open_file(path) + + def intact_root_file(self, f): + + if f and isinstance(f, TFile): + if f.IsOpen(): + if not f.IsZombie(): + if not f.TestBit(TFile.kRecovered): + return True + else: + if self._debug >= 99: + print "ERROR: file '%s' is recovered!" % f.GetName() + else: + if self._debug >= 99: + print "ERROR: file '%s' is zombie!" % f.GetName() + else: + if self._debug >= 99: + print "ERROR: file '%s' is not open" % f.GetName() + return False + + def load_histogram(self, file, histname): + if self._file: + if self._debug >= 10: + print "DEBUG: loading histogram '%s' from '%s'" % (histname, file) + h = self._file.Get(histname) + if isinstance(h, TH1): + return h + else: + print ("WARNING: histogram '%s' does not exist in '%s'" + % (histname, file)) + return None + + def histogram_exists(self, file, histname): + h = self.load_histogram(file = file, histname = histname) + if h: + return True + return False + + def get_integral(self, file, histname): + h = self.load_histogram(file = file, histname = histname) + if h: + return h.Integral() + else: + return None \ No newline at end of file diff --git a/base/helperClass.py b/base/helperClass.py index a7d6d80..1ee3111 100644 --- a/base/helperClass.py +++ b/base/helperClass.py @@ -14,22 +14,4 @@ def isfloat(self, value): float(value) return True except ValueError: - return False - - def histogram_exists(self, file, histname): - if opath.exists(file): - f = TFile(file) - print f - if self.intact_root_file(f): - h = f.Get(histname) - if isinstance(h, TH1): - f.Close() - return True - else: - print ("WARNING: histogram '%s' does not exist in '%s'" - % (histname, file)) - else: - print "ERROR: File '%s' is broken!" % file - else: - print "ERROR: File '%s' does not exist!" % file - + return False \ No newline at end of file diff --git a/src/processObject.py b/src/processObject.py index 0fab72e..c8838ab 100644 --- a/src/processObject.py +++ b/src/processObject.py @@ -8,11 +8,13 @@ from helperClass import helperClass from identificationLogic import identificationLogic from valueConventions import valueConventions +from fileHandler import fileHandler class processObject(object): helper = helperClass() id_logic = identificationLogic() value_rules = valueConventions() + file_handler = fileHandler() helper._debug = 99 def init_variables(self): @@ -57,27 +59,10 @@ def calculate_yield(self): y = -1 #TODO: export file logic to dedicated class - if path.exists(self._rootfile): - infile = TFile(self._rootfile) - #check if root file is intact - if self.helper.intact_root_file(infile): - #if yes, try to load histogram - - hist = infile.Get(self._nominalhistname) - if isinstance(hist, TH1): - #if successful, save yield - y = hist.Integral() - else: - s = "ERROR:\tunable to load histogram!" - s += " I will let combine calculate it on the fly," - s += " but it could crash" - infile.Close() - else: - s = "ERROR:\tunable to open root file for" - s += " process %s" % self._name - print s - else: - print "ERROR:\troot file does not exist! Cannot set yield for process {0}".format(self._name) + temp = file_handler.get_integral(file = self._rootfile, + histname = self._nominalhistname) + if temp: + y = temp return y #getter/setter for yields @property @@ -165,7 +150,7 @@ def nominalhistname(self): @nominalhistname.setter def nominalhistname(self, hname): - if self.helper.histogram_exists(file = self._rootfile, + if self.file_handler.histogram_exists(file = self._rootfile, histname = hname): self._nominalhistname = hname else: @@ -213,7 +198,7 @@ def add_uncertainty(self, syst, typ, value): if not syst in self._uncertainties: if typ == "shape": print "Looking for varied histograms for systematic" - # if self.helper.histogram_exists(self._rootfile, + # if self.file_handler.histogram_exists(self._rootfile, # self._systkey.replace(self._sy)) if self.value_rules.is_good_systval(value): self._uncertainties[syst] = {} From 8c35f29f5674e489c3fadf0074a9e0217f3c7e35 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 16 Oct 2018 10:04:42 +0200 Subject: [PATCH 10/73] update to new version --- datacardMaker.py | 53 ------------------------ src/combinedtestdatacard.txt | 50 ++++++++++++++++++++++ src/processObject.py | 5 --- test.py => src/test.py | 37 +++++++++++------ testdatacard.txt => src/testdatacard.txt | 0 systematicObject.py | 48 --------------------- 6 files changed, 74 insertions(+), 119 deletions(-) delete mode 100644 datacardMaker.py create mode 100644 src/combinedtestdatacard.txt rename test.py => src/test.py (77%) rename testdatacard.txt => src/testdatacard.txt (100%) delete mode 100644 systematicObject.py diff --git a/datacardMaker.py b/datacardMaker.py deleted file mode 100644 index ad4c0c8..0000000 --- a/datacardMaker.py +++ /dev/null @@ -1,53 +0,0 @@ -import sys -from os import path - -directory = path.abspath(path.realpath(path.dirname(__file__))) -if not directory in sys.path: - sys.path.append(directory) - -from categoryObject import categoryObject - -class datacardMaker: - - def __init__( self, pathToDatacard = None, - processIdentifier = "$PROCESS", - channelIdentifier = "$CHANNEL", - systIdentifier = "$SYSTEMATIC"): - self._header = [] - self._bins = "" - self._observation = "" - self._categories = [] - if pathToDatacard: - self.load_from_file(pathToDatacard) - - - def load_from_file(self, pathToDatacard): - if path.exists(pathToDatacard): - print "loading datacard from", pathToDatacard - with open(pathToDatacard) as datacard: - lines = datacard.read().splitlines() - self._shapelines_ = [] - for n, line in enumerate(lines): - if line.startswith("-"): - continue - elif line.startswith("Combination") or - line.startswith("imax") or - line.startswith("kmax") or - line.startswith("jmax"): - self._header.append(line) - elif line.startswith("bin") and - n != len(lines) and - lines[n+1].startswith("observation"): - self._bins = line - self._observation = lines[n+1] - elif line.startswith("shapes"): - self._shapelines_.append(line) - elif line.startswith("process") and - n != 0 and - lines[n-1].startswith("bin") - else: - - - - else: - print "could not load %s: no such file" % pathToDatacard diff --git a/src/combinedtestdatacard.txt b/src/combinedtestdatacard.txt new file mode 100644 index 0000000..26096f9 --- /dev/null +++ b/src/combinedtestdatacard.txt @@ -0,0 +1,50 @@ +Combination of limits_BDT_datacard_ljets_j5_tge4_DeepCSV_hdecay.txt limits_BDT_datacard_ljets_jge6_t3_DeepCSV_hdecay.txt limits_BDT_datacard_ljets_jge6_tge4_DeepCSV_hdecay.txt +imax 3 number of bins +jmax 5 number of processes minus 1 +kmax 9 number of nuisance parameters +---------------------------------------------------------------------------------------------------------------------------------- +shapes * ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root $PROCESS_finaldiscr_ljets_j5_tge4_DeepCSV $PROCESS_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC +shapes ttH_hbb ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hbb_finaldiscr_ljets_j5_tge4_DeepCSV ttH_hbb_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC +shapes ttH_hcc ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hcc_finaldiscr_ljets_j5_tge4_DeepCSV ttH_hcc_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC +shapes ttH_hgg ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hgg_finaldiscr_ljets_j5_tge4_DeepCSV ttH_hgg_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC +shapes ttH_hgluglu ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hgluglu_finaldiscr_ljets_j5_tge4_DeepCSV ttH_hgluglu_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC +shapes ttH_htt ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_htt_finaldiscr_ljets_j5_tge4_DeepCSV ttH_htt_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC +shapes ttH_hww ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hww_finaldiscr_ljets_j5_tge4_DeepCSV ttH_hww_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC +shapes ttH_hzg ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hzg_finaldiscr_ljets_j5_tge4_DeepCSV ttH_hzg_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC +shapes ttH_hzz ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hzz_finaldiscr_ljets_j5_tge4_DeepCSV ttH_hzz_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC +shapes * ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root $PROCESS_finaldiscr_ljets_jge6_t3_DeepCSV $PROCESS_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC +shapes ttH_hbb ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hbb_finaldiscr_ljets_jge6_t3_DeepCSV ttH_hbb_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC +shapes ttH_hcc ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hcc_finaldiscr_ljets_jge6_t3_DeepCSV ttH_hcc_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC +shapes ttH_hgg ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hgg_finaldiscr_ljets_jge6_t3_DeepCSV ttH_hgg_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC +shapes ttH_hgluglu ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hgluglu_finaldiscr_ljets_jge6_t3_DeepCSV ttH_hgluglu_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC +shapes ttH_htt ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_htt_finaldiscr_ljets_jge6_t3_DeepCSV ttH_htt_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC +shapes ttH_hww ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hww_finaldiscr_ljets_jge6_t3_DeepCSV ttH_hww_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC +shapes ttH_hzg ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hzg_finaldiscr_ljets_jge6_t3_DeepCSV ttH_hzg_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC +shapes ttH_hzz ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hzz_finaldiscr_ljets_jge6_t3_DeepCSV ttH_hzz_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC +shapes * ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root $PROCESS_finaldiscr_ljets_jge6_tge4_DeepCSV $PROCESS_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC +shapes ttH_hbb ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hbb_finaldiscr_ljets_jge6_tge4_DeepCSV ttH_hbb_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC +shapes ttH_hcc ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hcc_finaldiscr_ljets_jge6_tge4_DeepCSV ttH_hcc_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC +shapes ttH_hgg ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hgg_finaldiscr_ljets_jge6_tge4_DeepCSV ttH_hgg_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC +shapes ttH_hgluglu ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hgluglu_finaldiscr_ljets_jge6_tge4_DeepCSV ttH_hgluglu_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC +shapes ttH_htt ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_htt_finaldiscr_ljets_jge6_tge4_DeepCSV ttH_htt_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC +shapes ttH_hww ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hww_finaldiscr_ljets_jge6_tge4_DeepCSV ttH_hww_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC +shapes ttH_hzg ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hzg_finaldiscr_ljets_jge6_tge4_DeepCSV ttH_hzg_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC +shapes ttH_hzz ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hzz_finaldiscr_ljets_jge6_tge4_DeepCSV ttH_hzz_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC +---------------------------------------------------------------------------------------------------------------------------------- +bin ch1 ch2 ch3 +observation 1170.778059 21239.611394 2306.661628 +---------------------------------------------------------------------------------------------------------------------------------- +bin ch1 ch1 ch1 ch1 ch1 ch1 ch2 ch2 ch2 ch2 ch2 ch2 ch3 ch3 ch3 ch3 ch3 ch3 +process ttH_hbb ttbarPlusB ttbarPlusCCbar ttbarPlus2B ttbarOther ttbarPlusBBbar ttH_hbb ttbarPlusB ttbarPlusCCbar ttbarPlus2B ttbarOther ttbarPlusBBbar ttH_hbb ttbarPlusB ttbarPlusCCbar ttbarPlus2B ttbarOther ttbarPlusBBbar +process 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 +rate -1.0e+00 168.479 197.186 81.369 276.773 386.849 -1.0e+00 2620.04 4777.01 1515.92 9638.36 2414.54 -1.0e+00 250.838 351.411 157.59 289.246 1123.92 +---------------------------------------------------------------------------------------------------------------------------------- +QCDscale_ttH lnN 0.908/1.058 - - - - - 0.908/1.058 - - - - - 0.908/1.058 - - - - - +QCDscale_ttbar lnN - 0.96/1.02 0.96/1.02 0.96/1.02 0.96/1.02 0.96/1.02 - 0.96/1.02 0.96/1.02 0.96/1.02 0.96/1.02 0.96/1.02 - 0.96/1.02 0.96/1.02 0.96/1.02 0.96/1.02 0.96/1.02 +bgnorm_ttbarPlus2B lnN - - - 1.5 - - - - - 1.5 - - - - - 1.5 - - +bgnorm_ttbarPlusB lnN - 1.5 - - - - - 1.5 - - - - - 1.5 - - - - +bgnorm_ttbarPlusBBbar lnN - - - - - 1.5 - - - - - 1.5 - - - - - 1.5 +bgnorm_ttbarPlusCCbar lnN - - 1.5 - - - - - 1.5 - - - - - 1.5 - - - +lumi_13TeV_2016 lnN 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 +pdf_gg lnN - 1.04 1.04 1.04 1.04 1.04 - 1.04 1.04 1.04 1.04 1.04 - 1.04 1.04 1.04 1.04 1.04 +pdf_gg_ttH lnN 1.036 - - - - - 1.036 - - - - - 1.036 - - - - - diff --git a/src/processObject.py b/src/processObject.py index 4b8f689..508cfb4 100644 --- a/src/processObject.py +++ b/src/processObject.py @@ -276,13 +276,9 @@ def get_uncertainty_type(self, systname): if systname in self._uncertainties: return self._uncertainties[systname]["type"] else: -<<<<<<< HEAD:processObject.py return "" -======= - return "" - # def get_uncertainty(self, systname): # """ # return systematicObject if there is one @@ -291,4 +287,3 @@ def get_uncertainty_type(self, systname): # return self._uncertainties[systname] # else: # return None ->>>>>>> d4d357af2f34e845f8d84ae16f66ca2e62e6f917:src/processObject.py diff --git a/test.py b/src/test.py similarity index 77% rename from test.py rename to src/test.py index 46a2cd0..719200b 100644 --- a/test.py +++ b/src/test.py @@ -1,18 +1,13 @@ -categories = [] -with open("testdatacard.txt") as datacard: - lines = datacard.read().splitlines() - for n, line in enumerate(lines): - if line.startswith("process") and lines[n+1].startswith("process"): - categories.append(line) - -#for entry in categories: -category = categories[0].split() +from processObject import processObject +from categoryObject import categoryObject +from systematicObject import systematicObject + class test: def __init__( self, pathToDatacard): self._categories = [] self._header = [] - self._process = "" + self._process = [] self._categoryprocess = "" self._category = [] self._bin = "" @@ -96,8 +91,6 @@ def load_from_file(self, pathToDatacard): systematic = systematics.split() sys=systematic[0] typ=systematic[1] - print sys - print typ systematic.pop(1) systematic.pop(0) for value,process,category in zip(systematic,processes,binprocesses): @@ -105,12 +98,30 @@ def load_from_file(self, pathToDatacard): self._categories_[category][process]["systematics"][sys]={} self._categories_[category][process]["systematics"][sys]["type"]=typ self._categories_[category][process]["systematics"][sys]["value"]=value - + + def file_to_object(self,pathToDatacard): + for category in self._category_: + self._category.append(categoryObject(category,self._categories_[category]["shapes"]["default"]["rootfile"],self._categories_[category]["shapes"]["default"]["hist"],self._categories_[category]["shapes"]["default"]["systhist"],None,None,None,None,None)) + for process in self._process_[category]: + if not self._categories_[category][process]["shapes"]: + self._process.append(processObject(process,self._categories_[category]["shapes"]["default"]["rootfile"],self._categories_[category]["shapes"]["default"]["hist"],self._categories_[category]["shapes"]["default"]["systhist"],category)) + else: + self._process.append(processObject(process,self._categories_[category][process]["shapes"]["rootfile"],self._categories_[category][process]["shapes"]["hist"],self._categories_[category][process]["shapes"]["systhist"],category)) s = test("testdatacard.txt") s.load_from_file("testdatacard.txt") print s._categories_ print s._categories_["ljets_j5_tge4_DeepCSV"]["ttbarPlus2B"] +s.file_to_object("testdatacard.txt") +print s._category +print s._process +for process in s._process: + print process.get_name() + print process.get_category() +#d = test("combinedtestdatacard.txt") +#d.load_from_file("combinedtestdatacard.txt") +#print d._categories_ +#print d._categories_["ch1"]["ttbarPlus2B"] diff --git a/testdatacard.txt b/src/testdatacard.txt similarity index 100% rename from testdatacard.txt rename to src/testdatacard.txt diff --git a/systematicObject.py b/systematicObject.py deleted file mode 100644 index 9dd205c..0000000 --- a/systematicObject.py +++ /dev/null @@ -1,48 +0,0 @@ -class systematicObject(object): - def __init__(self, name, nature, dic = None): - - self._name = name - self._type = nature - self._dic = {} - - if dic and isinstance(dic, dict): - self._dic = dic - - - @property - def name(self): - return self._name - - @name.setter - def name(self, value): - self._name = str(value) - - @property - def type(self): - return self._type - - @type.setter - def type(self, typ): - self._type = typ - - def add_process(self, category, procname, correlation): - if category in self._dic: - self._dic[category][procname] = correlation - else: - print "ERROR: category is not known to uncertainty '%s'" % self._name - - def add_process(self, dic, category): - if category in self._dic: - if dic and isinstance(dic, dict): - self._dic[category] += dic - else: - print "Could not add process: input must be dictionary!" - else: - print "ERROR: category is not known to uncertainty '%s'" % self._name - - def get_correlation(self, category, procName): - if category in self._dic: - if procName in self._dic[category]: - return str(self._dic[category][procName]) - else: - return "-" From f63ddd55e13b24587bdd0a52949d366a4a456f32 Mon Sep 17 00:00:00 2001 From: Philip Daniel Keicher Date: Tue, 16 Oct 2018 10:27:55 +0200 Subject: [PATCH 11/73] added tests for process and category objects --- test.py | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ test.root | Bin 0 -> 4233 bytes 2 files changed, 81 insertions(+) create mode 100644 test.py create mode 100644 test.root diff --git a/test.py b/test.py new file mode 100644 index 0000000..838ed2f --- /dev/null +++ b/test.py @@ -0,0 +1,81 @@ +import sys +from os import path + +directory = path.abspath(path.realpath(path.dirname(__file__))) +srcdir = path.join(directory, "src") +if not srcdir in sys.path: + sys.path.append(srcdir) + +basedir = path.join(directory, "base") +if not basedir in sys.path: + sys.path.append(basedir) +from helperClass import helperClass + +from processObject import processObject +from categoryObject import categoryObject +from datacardMaker import datacardMaker +from test_cls import testClass + +def datacard_tests(): + testproc = processObject() + + testproc.name = "nominal" + testproc.category = "jge6_tge4" + testproc.rootfile = "test.root" + testproc.nominalhistname = "nominal" + testproc.systname = "nominal_$SYSTEMATIC" + testproc.add_uncertainty("lumi", "lnN", "1.025") + testproc.add_uncertainty("pdf_gg", "lnN", "0.98/1.02") + testproc.add_uncertainty(syst = "JES", typ = "shape", value = 1.0) + #not working yet + # testproc.add_uncertainty(syst = 5, typ = "shape", value = 1.0) + # testproc.add_uncertainty(syst = "JES", typ = "shape", value = "five") + print testproc + + category = categoryObject() + category.name = "jge6_tge4" + category.add_signal_process(testproc) + print category + +def init_tests(): + a = testClass() + + b = testClass() + c = testClass() + + a.x = 7 + print a.x + print b.x + print c.x + + a.helper = helperClass() + + print a.helper + print b.helper + print c.helper + + testClass.helper = helperClass() + + print a.helper + print b.helper + print c.helper + + print "deleting helper of a" + del a.helper + + print a.helper + print b.helper + print c.helper + + a.helper = b.helper + + print a.helper + print b.helper + print c.helper + +def main(): + datacard_tests() + # init_tests() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/test.root b/test.root new file mode 100644 index 0000000000000000000000000000000000000000..1cea7f38a2260f1a5d4263b1ddc68fc7bd83c88f GIT binary patch literal 4233 zcmb7|cT`i$_Qy{WI-!OVItG*$Ab>%-AXNn}AV^VqlNula6r=W5kRCdS2!y6m z!j-1Q`+oPm_5S(2ch=hL%sOZG%wC__d(C_&2!p`^z(_j)0B!)l zo85Y?1v{%X)L0|0Xd0BX3tSk;%ov69Jqp`tRjeUtk4zj}gz|6tmAlhs46^npdXS%(1qtb|y5Yiy3UE|m(3UkQHz6$q(o>QlFjuo1L?q`b zHN-v&6Vyf&Fpxeo-^mq2oM&RA7vvA9qz5A7RDMv`kLLt|7ZrgcAa{%aH1#(EmVY8} zyrh4{D&QEz8~}Lz6T~I=6A*(ZAO`;ik@FV_*ycxmK|2bNv2=n%3RFJ@&x(HK0JgoZ z&0{vujt9})6okVS`Bd;FsB~v&HFpB{!){SsOSlaj4nM-5Zmi~YT(19kceLRktU|;M zAhKkPx&Zt>M}QF+AFCMGW1!~LT%B&&PsyjVbvgp&pxG7Z*M7Ge?NW4_Zx3LPwnKtS z9~7QFTP-wpiiy=Y8FZO)gD5RNhR5sN!*j!HwSi1|jQb`9;$5g<_S^QUGu>BhqJxr+oF_%RE@cBP3e7;aV(OS^BkrhxN?sl5`mS|*QRs;mXu%&`RxhV#KFFK1a!fj+YWfYTi;wwUU%bwZ!K7d_7vbY;K`{odIj|NQd&XAIVqxjpNuKXR}#!^o? zged!ny0E^LeN>d-PN7765Cxjy1JSBOI^5D5p}>F&N`a0L--k$}CT9$^ni5S5cKXu= zENpy=$bjS=`RHug$sQFg2mSy7m}fPEPAZ&QG-9bIMW=nt=D6+wH(E<$FDwpv1sCM* z?B^bI(cc5ZsfP)6@x3ML>*ep`?nZrKaa2~9nSoR>YB9aGv-cIW=GIalnojUrPzxMW zF2$`pSr<=P&$wVQohsB@b;_jh{fM~V_`t@{+xr7UDoW%!AxV1zebrY2LOM=z0naPU zA#ZshMpW+>!07M;%&8%Isste{V|Fm~G_+S~#ysR*kM)IB;lKR ziI$>gW7%#zmz73F60h1_LiEqEfZqpeH5Ryg2pS>;mi!P#Ll#6D^wan-~V^us0G@`$V z#AZhbIrp0%M_VGu_-pn(dsuzJCC{#`!!fUwYBgKCKpQm&-)GKl(;k%2!P?tXmEub( zjzWqs`RzQ$RvcJ!@^JtDaD;NW*vH;a7lPC;lP?CnV$S8z$_h!>b9kEHkU85-XDZjE zs~C+dC1pKkATJZ9ULwgc4f6+wa1mpHug_R!?`)bxl3}y%EY>_NSn#DemNnL?AQi?%DHBt;+EG|l0WyD?u_j3PE1U=N%F$#ul9$yUA?*{ znP5_Vea@EE_D=kUN+SkxS;o1fVc*?E+c;joB)1D@3l32L4TryW`=~?c=;^@l{!EPT z5Z`+B;Cwn^ZZ#h|56iu|i}UVHeWt6Y9^6|QF{3JUZllb{GsA`vShTH#v7?T<$YM;?PN&{uNgTj%_%7qk3H4HU(_-?3J&&x5+en1wea~FJ83T^8CEVNx^uuD^iAYQrE z#kcVsu0B1dXKbl z`hIf~3mJ*S$4TwVPQ80Qt8_*oNVK2h&OFbk&ol(xM|mA=ZYWp#etqaxr-h4zL@~qf zfgOr;jjrK%zC}iwPgb@4D&|}Ubr%~V{zNnFF82QB;%zLj(^Z@BM@qaKl<+xSA-Qx% zTeM;^e2gOuRD2npQbG!-LVL}(+%@s#gZMA}RNVVeQPk4$E)e=u! z8o{m$e4EwCZA&v@tzzL1n?Qu6K?OU+U2daF3y6tw%M!(E9NSf9CCgzpOl`7Nm-&|k zspW``arFqS-Nc3{RpZ59uhPc8MvamDVw^` zd*Sxg-f!jOG4(o`umKoJ#2{9yJe&J4fgTyap*G}qO<~Dt3h{~4RcdNWh`{mXH{-Aw z1#w|(IA?0;-OjO&C!~@nPx@7CjIr7eG_vTMLVn7g{Npj+ryuaerAM56EnyBC4ZG{AH6fJ5L8r^z-T$}>-UAGLJ-WibSk%iP(o`wj29NCH@So29ft-2U6!~aQ zbFh^3E{9Lq(M7IlR@Oeu4!YC8jT?BJwDB~XcTM-fOI%fdYFCLv+&q_=_Nr8~fAhVd z?sIhH$#0X^*5>=(e~@; zK7kBa%GXfDNDRG*xvQw*FQ}Nex41M>CpNBRm@1@H?wKkQv9`}uE>B*0SC)aQB#D4E zeJt4Ayx`GA{YJx~*!14k??H?Vt&PEDXKliIQmw^RyXF`0@jIj8YF*rGy++~EDrG`Z z-8WlSg`&KJ$8`aZT>-nC$(Ix>#7@reSSr})_4B3f+=#M1H9+Ph;K%Xu66mj2rySHCby^3M`65=n!yIBoZj>sX~=Qe>7M|qd6P*fIU6dCs6GdutWxmw9)X#Hle{0g4^kX+dC=XFAQ z#BIwsYzMt{utbH5a(fl_Q_W@$87qPMF1B892;20)DBE&W{;DC_95mMMY;*Bg_~A>J zG~TSwdsQ^N9Fb6t$_`OAMPz(#K42p(AN|Z}p4pzD-{Wd8zbV~%TH{^bM=9pZVVp*7 z(S*TE((^Fo_~PxXj{%^iDIah&f&(Y^8#Oa8B z)$B`97`J_hhv1i0w&m2#3A5XO5>agZMV}Bl3$(Z@QnkOs*$E5Li8*-1y{+>YIL~Ms zOJm&EM(;Z%zXU|0`lxc8Dt)PEav<^KOnxGrsp9wKO#UCznfehzl{f#D?xptoAH?DR gDw?Q~pihEk`d82=w+d7t@t;9cH3oY^C^`f92Xwkc%K!iX literal 0 HcmV?d00001 From dc6854c310238cc414503a88f839ea879990a2e1 Mon Sep 17 00:00:00 2001 From: Philip Daniel Keicher Date: Tue, 16 Oct 2018 10:35:34 +0200 Subject: [PATCH 12/73] bug fix: fixed checks for systematic uncertainties --- src/processObject.py | 5 +++-- test.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/processObject.py b/src/processObject.py index c8838ab..cb5330a 100644 --- a/src/processObject.py +++ b/src/processObject.py @@ -206,10 +206,11 @@ def add_uncertainty(self, syst, typ, value): self._uncertainties[syst]["value"] = value return True else: - print "Value {0} is not a good value!".format(value) + print "Value '{0}' is not a good value!".format(value) else: temp = "There is already an entry for uncertainty " - temp += "%s in process %s" % (systname, self.get_name()) + temp += "'%s' in process '%s'! " % (syst, self.get_name()) + temp += "Please use 'set_uncertainty' instead." print temp else: print "ERROR: Could not add uncertainty - input arguments invalid!" diff --git a/test.py b/test.py index 838ed2f..7ae5093 100644 --- a/test.py +++ b/test.py @@ -28,8 +28,9 @@ def datacard_tests(): testproc.add_uncertainty("pdf_gg", "lnN", "0.98/1.02") testproc.add_uncertainty(syst = "JES", typ = "shape", value = 1.0) #not working yet - # testproc.add_uncertainty(syst = 5, typ = "shape", value = 1.0) - # testproc.add_uncertainty(syst = "JES", typ = "shape", value = "five") + testproc.add_uncertainty(syst = 5, typ = "shape", value = 1.0) + testproc.add_uncertainty(syst = "JES", typ = "shape", value = "five") + testproc.add_uncertainty(syst = "JES2", typ = "shape", value = "five") print testproc category = categoryObject() From 3243acfdf1ee8f180695fdc8963d6f765e1ae470 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 16 Oct 2018 12:04:50 +0200 Subject: [PATCH 13/73] updated loadfromfile --- src/categoryObject.py | 44 +++++++++++++++++++++++- src/datacardMaker.py | 79 ++++++++++++++++++++++++++++++++++++++----- src/test.py | 9 ++--- 3 files changed, 118 insertions(+), 14 deletions(-) diff --git a/src/categoryObject.py b/src/categoryObject.py index 21c67a1..6e9b12a 100644 --- a/src/categoryObject.py +++ b/src/categoryObject.py @@ -116,6 +116,43 @@ def name(self): def name(self, val): self._name = val + @property + def rootfile(self): + return self._rootfile + + @rootfile.setter + def rootfile(self, rootpath): + if path.exists(rootpath): + self._rootfile = rootpath + else: + print "file '%s' does not exist!" % rootpath + + @property + def nominalhistname(self): + return self._nomkey + + @nominalhistname.setter + def nominalhistname(self, hname): + + if self.file_handler.histogram_exists(file = self._rootfile, + histname = hname): + self._nomkey = hname + else: + print "'%s' does not exist in '%s'" % (hname, self._rootfile) + + @property + def systname(self): + return self._systkey + + @shistname.setter + def systname(self, hname): + + if self.file_handler.histogram_exists(file = self._rootfile, + histname = hname): + self._systkey = hname + else: + print "'%s' does not exist in '%s'" % (hname, self._rootfile) + def add_signal_process( self, name, rootfile, @@ -198,7 +235,12 @@ def add_process(self, dic, process): dic[process.name] = process else: print "ERROR: Category can only contain processes!" - + + def __getitem__(self, process): + for bkgprocs in self._bkpgprocs: + if bkgprocs=process: + return bkgprocs + def __str__(self): s = [] s.append("Category Name:\t%s" % self._name) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index bf161e8..34e76e1 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -6,6 +6,7 @@ sys.path.append(directory) from categoryObject import categoryObject +from processObject import processObject from systematicObject import systematicObject class datacardMaker(object): @@ -90,8 +91,10 @@ def load_from_file(self, pathToDatacard): with open(pathToDatacard) as datacard: lines = datacard.read().splitlines() self._shapelines_ = [] - self._processes_= [] - self._binprocesses_= [] + self._systematics_ = [] + self._processes_= "" + self._binprocesses_= "" + self._processtype_ = "" for n, line in enumerate(lines): if line.startswith("-"): continue @@ -103,15 +106,73 @@ def load_from_file(self, pathToDatacard): elif line.startswith("shapes"): self._shapelines_.append(line) elif line.startswith("process") and n != 0 and lines[n-1].startswith("bin"): - self._processes_.append(line) - self._binprocesses_.append(line) - - else: + self._processes_= line + self._binprocesses_= lines[n-1] + self._processtype_ = lines[n+1] + elif line.startswith("bin") and lines[n+1].startswith("process"): pass - - - + elif line.startswith("process") and lines[n+1].startswith("rate"): + pass + elif line.startswith("observation") or line.startswith("rate"): + pass + else: + self._systematics_.append(line) + + #create categoryObject for each category + bins=self._bins.split() + bins.pop(0) + for category in bins: + self._categories[category] = categoryObject() + self._categories[category].name = category + + + #create processObjects for each process in a category + processes = self._processes_.split() + processes.pop(0) + binprocesses = self._binprocesses_.split() + binprocesses.pop(0) + processtypes = self._processtype_.split() + processtypes.pop(0) + if len(processes)==len(binprocesses) and len(processes)==len(processtypes): + for process,category,processtype in zip(processes,binprocesses,processtypes): + proc=processObject() + proc.name = process + proc.category = category + for shapelines in self._shapelines_: + shape = shapelines.split() + if shape[2] == category or shape[2]=="*": + if shape[1] == "*": + proc.rootfile = shape[3] + proc.nominalhistname=shape[4] + proc.systname=shape[5] + #self._categories[category].rootfile = shape[3] + #self._categories[category].nominalhistname = shape[4] + #self._categories[category].systname = shape[5] + for process in self._process_[category]: + if shape[1] == process: + proc.rootfile=shape[3] + proc.nominalhistname=shape[4] + proc.systname=shape[5] + + + if processtype > 0: + self._categories[category].add_background_process(proc) + else: + self._categories[category].add_signal_process(proc) + + #adds systematics to processes + for systematics in self._systematics_: + systematic = systematics.split() + sys=systematic[0] + typ=systematic[1] + systematic.pop(1) + systematic.pop(0) + for value,process,category in zip(systematic,processes,binprocesses): + if value!="-": + #self._categories[category] .add_uncertainty(sys,typ,value) + + else: print "could not load %s: no such file" % pathToDatacard diff --git a/src/test.py b/src/test.py index 719200b..5f565f8 100644 --- a/src/test.py +++ b/src/test.py @@ -9,7 +9,7 @@ def __init__( self, pathToDatacard): self._header = [] self._process = [] self._categoryprocess = "" - self._category = [] + self._category = {} self._bin = "" @@ -101,7 +101,7 @@ def load_from_file(self, pathToDatacard): def file_to_object(self,pathToDatacard): for category in self._category_: - self._category.append(categoryObject(category,self._categories_[category]["shapes"]["default"]["rootfile"],self._categories_[category]["shapes"]["default"]["hist"],self._categories_[category]["shapes"]["default"]["systhist"],None,None,None,None,None)) + self._category[category]=(categoryObject(category,self._categories_[category]["shapes"]["default"]["rootfile"],self._categories_[category]["shapes"]["default"]["hist"],self._categories_[category]["shapes"]["default"]["systhist"],None,None,None,None,None)) for process in self._process_[category]: if not self._categories_[category][process]["shapes"]: self._process.append(processObject(process,self._categories_[category]["shapes"]["default"]["rootfile"],self._categories_[category]["shapes"]["default"]["hist"],self._categories_[category]["shapes"]["default"]["systhist"],category)) @@ -112,13 +112,14 @@ def file_to_object(self,pathToDatacard): s.load_from_file("testdatacard.txt") print s._categories_ print s._categories_["ljets_j5_tge4_DeepCSV"]["ttbarPlus2B"] - s.file_to_object("testdatacard.txt") -print s._category print s._process for process in s._process: + s._category["ljets_j5_tge4_DeepCSV"].add_signal_process(process) print process.get_name() print process.get_category() +for category in s._category: + print category #d = test("combinedtestdatacard.txt") #d.load_from_file("combinedtestdatacard.txt") #print d._categories_ From 041b6467466b9f39bcb64471d052e9c938cc9289 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 16 Oct 2018 12:42:07 +0200 Subject: [PATCH 14/73] added file to test overloading getitem --- src/test2.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/test2.py diff --git a/src/test2.py b/src/test2.py new file mode 100644 index 0000000..4ce7340 --- /dev/null +++ b/src/test2.py @@ -0,0 +1,40 @@ +class cO: + def __init__(self,category): + self.__category = category + self.__dic ={} + + def add_process(self,process): + if isinstance(process, pO): + self.__dic[process.getName]= process + + def __getitem__(self, process): + for proc in self.__dic: + if proc.getName==process: + return proc + + def __str__(self): + s = [] + s.append("Category Name:\t%s" % self.__category) + return "\n".join(s) + +class pO: + def __init__(self,process): + self.__process = process + self.__number = None + + + def getName(self): + return self.__process + def setNumber(self,num): + self.__number=num + def getNumber(self): + return self.__number + + +c1=cO("ch1") +p1=pO("process1") +p1.setNumber(1) +print p1.getNumber() +c1.add_process(p1) +print c1 +c1["process1"].getNumber \ No newline at end of file From b6da79d604f5b694c5e4ee962e71194680c9f017 Mon Sep 17 00:00:00 2001 From: Philip Daniel Keicher Date: Tue, 16 Oct 2018 15:29:02 +0200 Subject: [PATCH 15/73] bug fix: histogram handling now working --- base/fileHandler.py | 47 +++++++++------ base/identificationLogic.py | 47 +++++++++++++-- src/categoryObject.py | 111 ++++++++++++++++++++++-------------- src/processObject.py | 64 ++++++++++++--------- test.py | 3 +- 5 files changed, 179 insertions(+), 93 deletions(-) diff --git a/base/fileHandler.py b/base/fileHandler.py index eacc5e5..6cd324e 100644 --- a/base/fileHandler.py +++ b/base/fileHandler.py @@ -23,27 +23,34 @@ def __del__(self): self.close_file() def is_file_open(self): - if isinstance(self._file, TFile): + if self._debug >= 99: + print "DEBUG: checking for open file" + if not self._file is None and isinstance(self._file, TFile): return self._file.IsOpen() + if self._debug >= 99: + print "DEBUG: file is not a TFile!" return False def close_file(self): - if self.is_file_open: + if self._debug >= 99: + print "DEBUG: Closing file if it's open" + if self.is_file_open(): self._file.Close() def open_file(self, path): if self._debug >= 99: - print "opening file '%s'" % filepath - if opath.exists(filepath): - f = TFile.Open(filepath) - if self.intact_root_file(f): + print "opening file '%s'" % path + if opath.exists(path): + f = TFile.Open(path) + if self.is_intact_file(f): self._file = f + self._filepath = path else: if self._debug >= 3: - print "Could not open file '%s'" % filepath + print "Could not open file '%s'" % path else: if self._debug >= 3: - print "File '%s' does not exist!" % filepath + print "File '%s' does not exist!" % path return None @property @@ -51,11 +58,13 @@ def filepath(self): return self._filepath @filepath.setter def filepath(self, path): - if opath.exists(path): - self.close_file() - self.open_file(path) + if self._debug >= 20: + print "trying to set file path to", path - def intact_root_file(self, f): + self.close_file() + self.open_file(path) + + def is_intact_file(self, f): if f and isinstance(f, TFile): if f.IsOpen(): @@ -73,7 +82,9 @@ def intact_root_file(self, f): print "ERROR: file '%s' is not open" % f.GetName() return False - def load_histogram(self, file, histname): + def load_histogram(self, histname): + if self._debug >= 99: + print "DEBUG: entering 'fileHandler.load_histogram'" if self._file: if self._debug >= 10: print "DEBUG: loading histogram '%s' from '%s'" % (histname, file) @@ -85,14 +96,16 @@ def load_histogram(self, file, histname): % (histname, file)) return None - def histogram_exists(self, file, histname): - h = self.load_histogram(file = file, histname = histname) + def histogram_exists(self, histname): + h = self.load_histogram(histname = histname) + if self._debug >= 99: + print "found histogram at", h if h: return True return False - def get_integral(self, file, histname): - h = self.load_histogram(file = file, histname = histname) + def get_integral(self, histname): + h = self.load_histogram(histname = histname) if h: return h.Integral() else: diff --git a/base/identificationLogic.py b/base/identificationLogic.py index 50a647f..92d0db0 100644 --- a/base/identificationLogic.py +++ b/base/identificationLogic.py @@ -22,6 +22,7 @@ class identificationLogic(object): , e.g. $SYSTEMATIC (member variable: _systIden) """ + _debug = 99 def init_variables(self): """ define member variables here @@ -63,12 +64,12 @@ def generic_nominal_key(self): return self._generic_nom_key @generic_nominal_key.setter def generic_nominal_key(self, key): - if not self._procIden in key: + if not self._procIden in key and self._debug >= 3: s = "WARNING: process identification keyword '%s'" % self._procIden s+= " is not part of new nominal key '%s'!" % key print s - if not self._chIden in key: - s = "WARNING: channel identification keyword '%s'" % self._procIden + if not self._chIden in key and self._debug >= 3: + s = "WARNING: channel identification keyword '%s'" % self._chIden s+= " is not part of new nominal key '%s'!" % key print s self._generic_nom_key = key @@ -212,4 +213,42 @@ def build_systematics_histo_names( self, process_name, channel_name, channel_name = channel_name, systematic_name = systematic_name, base_key = base_key) - return [up, down] \ No newline at end of file + return [up, down] + + def matches_generic_nominal_key(self, tocheck, process_name, + channel_name = None): + """ + Check whether given nominal histogram name 'tocheck' is compatible + with the generic nominal key + """ + temp = tocheck + if not channel_name is None: + if channel_name in temp: + temp = temp.replace(channel_name, self._chIden) + if process_name in temp: + temp = temp.replace(process_name, self._procIden) + if temp == self._generic_nom_key: + return True + return False + + def matches_generic_systematic_key( self, tocheck, process_name, + channel_name = None, + systematic_name = None): + """ + Check whether given systematic histogram name 'tocheck' is compatible + with the generic systematic key + """ + temp = tocheck + if not channel_name is None and not channel_name == "": + if channel_name in temp and not self._chIden in temp: + temp = temp.replace(channel_name, self._chIden) + if process_name in temp and not process_name == "": + if not self._procIden in temp: + temp = temp.replace(process_name, self._procIden) + if systematic_name in temp and not systematic_name == "": + if not self._systIden in temp: + temp = temp.replace(systematic_name, self._systIden) + + if temp == self._generic_syst_key: + return True + return False \ No newline at end of file diff --git a/src/categoryObject.py b/src/categoryObject.py index 21c67a1..041c1e3 100644 --- a/src/categoryObject.py +++ b/src/categoryObject.py @@ -15,15 +15,14 @@ def init_variables(self): self._signalprocs = {} self._bkgprocs = {} self._key_creator = identificationLogic() + self._default_root_file = None def __init__( self, categoryName=None, defaultRootFile=None, defaultnominalkey=None, systkey = None, dict_of_signals = None, dict_of_bkgs = None, - processIdentifier = None, - channelIdentifier = None, - systIdentifier = None ): + ): """ init category. A category has a name, a root file containing the process histograms and a key to find the nominal histograms @@ -39,28 +38,18 @@ def __init__( self, categoryName=None, defaultRootFile=None, defaultnominalkey -- key to find the nominal histograms systkey -- key to find the histograms corresponding to a nuisance parameter shape variation - processIdentifier -- string that is to be replaced with the - process name in the keys - channelIdentifier -- string that is to be replaced with the - channel name in the keys - systIdentifier -- string that is to be replaced with the - nuisance parameter name in the keys """ self.init_variables() if not categoryName is None: self._name = categoryName - if not defaultRootFile is None: - self._nomkey = defaultnominalkey - if not systkey is None: - self._systkey = systkey - if not processIdentifier is None: - self._procIden = processIdentifier - if not channelIdentifier is None: - self._chIden = channelIdentifier - if not systIdentifier is None: - self._systIden = systIdentifier if not defaultnominalkey is None: - self._nomkey = defaultnominalkey + self._key_creator.generic_nominal_key = defaultnominalkey + if not systkey is None: + self._key_creator.generic_systematics_key = systkey + if not defaultRootFile is None: + if path.exists(defaultRootFile): + self._default_root_file = defaultRootFile + # #check if process/channel identifiers are in nominal histo key @@ -87,19 +76,19 @@ def __init__( self, categoryName=None, defaultRootFile=None, for proc in dict_of_bkgs: self.add_background_process(name = proc, rootfile = defaultRootFile) - def is_part_of(self, identifier, key): - if identifier in key: - if self._debug: - s = "Identifier '%s' is part of " % identifier - s += "keyword '%s'" % key - print s - return True - else: - if self._debug: - s = "Identifier '%s' is not part of " % identifier - s += "keyword '%s'" % key - print s - return False + # def is_part_of(self, identifier, key): + # if identifier in key: + # if self._debug: + # s = "Identifier '%s' is part of " % identifier + # s += "keyword '%s'" % key + # print s + # return True + # else: + # if self._debug: + # s = "Identifier '%s' is not part of " % identifier + # s += "keyword '%s'" % key + # print s + # return False @property def n_signal_procs(self): @@ -115,27 +104,57 @@ def name(self): @name.setter def name(self, val): self._name = val + + @property + def signal_processes(self): + return self._signalprocs + + @property + def background_processes(self): + return self._bkgprocs - + @property + def generic_key_nominal_hist(self): + return self._key_creator.generic_nominal_key + @generic_key_nominal_hist.setter + def generic_key_nominal_hist(self, key): + channelkey = self._key_creator.insert_channel(channel_name = self._name, + base_key = key) + self._key_creator.generic_nominal_key = channelkey + + @property + def generic_key_systematic_hist(self): + return self._key_creator.generic_systematics_key + @generic_key_systematic_hist.setter + def generic_key_systematic_hist(self, key): + channelkey = self._key_creator.insert_channel(channel_name = self._name, + base_key = key) + self._key_creator.generic_systematics_key = channelkey + + + - def add_signal_process( self, name, rootfile, - histoname = None, + + def add_signal_process( self, name, rootfile = None, histoname = None, systkey = None): """ add a signal process. Calls function add_process with list of signal processes """ if histoname is None: - histoname = self._nomkey + histoname = self._key_creator.insert_process(process_name = name, + base_key = self._key_creator.generic_nominal_key) if systkey is None: - systkey = self._systkey - self.add_process( dic = self._signalprocs, name = name, + systkey = self._key_creator.insert_process(process_name = name, + base_key = self._key_creator.generic_systematics_key) + if rootfile is None: + rootfile = self._default_root_file + self.add_process_raw( dic = self._signalprocs, name = name, rootfile = rootfile, histoname = histoname, systkey = systkey) - def add_background_process( self, name, rootfile, - histoname = None, - systkey = None): + def add_background_process( self, name, rootfile = None, + histoname = None, systkey = None): """ add a background process. Calls function add_process with list of background processes @@ -144,7 +163,7 @@ def add_background_process( self, name, rootfile, histoname = self._nomkey if systkey is None: systkey = self._systkey - self.add_process( dic = self._bkgprocs, name = name, + self.add_process_raw( dic = self._bkgprocs, name = name, rootfile = rootfile, histoname = histoname, systkey = systkey) @@ -199,6 +218,12 @@ def add_process(self, dic, process): else: print "ERROR: Category can only contain processes!" + def add_process_raw(self, dic, name, rootfile, histoname, systkey): + temp = processObject(processName = name, pathToRootfile = rootfile, + nominal_hist_key = histoname, systematic_hist_key = systkey, + categoryname = self._name) + self.add_process(dic = dic, process = temp) + def __str__(self): s = [] s.append("Category Name:\t%s" % self._name) diff --git a/src/processObject.py b/src/processObject.py index cb5330a..e764ad6 100644 --- a/src/processObject.py +++ b/src/processObject.py @@ -1,4 +1,3 @@ -# from ROOT import TFile, TH1 from os import path from sys import path as spath thisdir = path.realpath(path.dirname(__file__)) @@ -6,16 +5,16 @@ if not basedir in spath: spath.append(basedir) from helperClass import helperClass -from identificationLogic import identificationLogic +# from identificationLogic import identificationLogic from valueConventions import valueConventions from fileHandler import fileHandler class processObject(object): - helper = helperClass() - id_logic = identificationLogic() - value_rules = valueConventions() - file_handler = fileHandler() - helper._debug = 99 + _helper = helperClass() + # _id_logic = identificationLogic() + _value_rules = valueConventions() + _file_handler = fileHandler() + _helper._debug = 99 def init_variables(self): self._name = "" @@ -48,22 +47,20 @@ def __init__( self, processName = None, pathToRootfile = None, self._systkey = systematic_hist_key if self._debug: s = "initialized process with name '%s'" % self._name - s += "in category '%s'" % self._categoryname + s += " in category '%s'" % self._categoryname print s def calculate_yield(self): """ returns yield (TH1D::Integral() ) for this process """ - #open rootfile if it exsists y = -1 - #TODO: export file logic to dedicated class - temp = file_handler.get_integral(file = self._rootfile, - histname = self._nominalhistname) + temp = _file_handler.get_integral(histname = self._nominalhistname) if temp: y = temp return y + #getter/setter for yields @property def eventcount(self): @@ -83,6 +80,8 @@ def get_yield(self): get yield for process """ y = self._eventcount + if self._debug >= 99: + print "returning yield of", y return y #logic for process name @@ -91,7 +90,8 @@ def name(self): return self.get_name() @name.setter def name(self, s): - if self._debug: print "entered setter for name" + if self._debug >= 99: + print "entered setter for name" self.set_name(s) @@ -99,7 +99,8 @@ def set_name(self, name): """ set process name """ - if self._debug: print "setting name to", name + if self._debug >= 20: + print "setting name to", name self._name = name def get_name(self): @@ -115,7 +116,8 @@ def category(self): @category.setter def category(self, catname): - if self._debug: print "entered setter for category" + if self._debug >= 99: + print "entered setter for category" self.set_category(catname) # self._categoryname = catname @@ -129,17 +131,20 @@ def set_category(self, catname): """ set name for category to which this process belongs to """ - if self._debug: print "setting category to", catname + if self._debug >= 20: + print "setting category to", catname self._categoryname = catname @property - def rootfile(self): - return self._rootfile + def file(self): + return self._file_handler.filepath - @rootfile.setter - def rootfile(self, rootpath): + @file.setter + def file(self, rootpath): + if self._debug >= 20: + print "setting filepath to", rootpath if path.exists(rootpath): - self._rootfile = rootpath + self._file_handler.filepath = rootpath else: print "file '%s' does not exist!" % rootpath @@ -150,11 +155,12 @@ def nominalhistname(self): @nominalhistname.setter def nominalhistname(self, hname): - if self.file_handler.histogram_exists(file = self._rootfile, - histname = hname): + if self._file_handler.histogram_exists(histname = hname): self._nominalhistname = hname + if self. else: - print "'%s' does not exist in '%s'" % (hname, self._rootfile) + s = "'%s' does not exist " % hname + s += "in '%s'" % self._file_handler.filepath def __str__(self): """ @@ -198,9 +204,9 @@ def add_uncertainty(self, syst, typ, value): if not syst in self._uncertainties: if typ == "shape": print "Looking for varied histograms for systematic" - # if self.file_handler.histogram_exists(self._rootfile, + # if self._file_handler.histogram_exists(self._rootfile, # self._systkey.replace(self._sy)) - if self.value_rules.is_good_systval(value): + if self._value_rules.is_good_systval(value): self._uncertainties[syst] = {} self._uncertainties[syst]["type"] = typ self._uncertainties[syst]["value"] = value @@ -213,7 +219,9 @@ def add_uncertainty(self, syst, typ, value): temp += "Please use 'set_uncertainty' instead." print temp else: - print "ERROR: Could not add uncertainty - input arguments invalid!" + s = "ERROR: Could not add uncertainty - " + s += "both name and type of systematic are required to be strings!" + print s return False # def add_uncertainty_from_systematicObject(self, systematic, value = None): @@ -251,7 +259,7 @@ def set_uncertainty(self, systname, typ, value): and only adds the systematics if there is an entry and the value is good """ if systname in self._uncertainties: - if self.value_rules.is_good_systval(value): + if self._value_rules.is_good_systval(value): self._uncertainties[systname]["value"] = str(value) self._uncertainties[systname]["type"] = typ else: diff --git a/test.py b/test.py index 7ae5093..7b7f401 100644 --- a/test.py +++ b/test.py @@ -21,7 +21,8 @@ def datacard_tests(): testproc.name = "nominal" testproc.category = "jge6_tge4" - testproc.rootfile = "test.root" + print "setting rootfile" + testproc.file = "test.root" testproc.nominalhistname = "nominal" testproc.systname = "nominal_$SYSTEMATIC" testproc.add_uncertainty("lumi", "lnN", "1.025") From 1bc47090ca5be47d3380acad5b37c3ec4630d6c1 Mon Sep 17 00:00:00 2001 From: Philip Daniel Keicher Date: Tue, 16 Oct 2018 17:21:57 +0200 Subject: [PATCH 16/73] check for variated templates for shape uncertainties working --- base/identificationLogic.py | 75 +++++++++++++++++++++++++++---------- src/categoryObject.py | 16 ++++++-- src/processObject.py | 34 ++++++++++++----- test.py | 4 +- 4 files changed, 95 insertions(+), 34 deletions(-) diff --git a/base/identificationLogic.py b/base/identificationLogic.py index 92d0db0..17f379e 100644 --- a/base/identificationLogic.py +++ b/base/identificationLogic.py @@ -23,6 +23,7 @@ class identificationLogic(object): (member variable: _systIden) """ _debug = 99 + _allowed_dependencies = ["process", "channel"] def init_variables(self): """ define member variables here @@ -32,6 +33,7 @@ def init_variables(self): self._procIden = "$PROCESS" #Identifier keyword for processes self._chIden = "$CHANNEL" #Identifier keyword for categories/channels self._systIden = "$SYSTEMATIC" #Identifier keyword for systematics + self._belongs_to = None def __init__( self, generic_nominal_key = "", generic_systematics_key = "", process_id_key = "", channel_id_key = "", @@ -113,38 +115,56 @@ def systematics_identificator(self): @systematics_identificator.setter def systematics_identificator(self, key): self._systIden = key + + @property + def belongs_to(self): + return self._belongs_to + @belongs_to.setter + def belongs_to(self, value): + if value in self._allowed_dependencies: + self._belongs_to = value + else: + print "ERROR: Dependency to '%s' is not allowed!" % value + + def insert_channel(self, channel_name, base_key): """ build a key from 'base_key' for a specific channel 'channel_name'. """ - if not base_key is None or base_key == "": + if base_key is None or base_key == "": print "unsuitable base_key!" return "" - if self._chIden in base_key: - return base_key.replace(self._chIden, channel_name) + if not channel_name == "" and not channel_name is None: + if self._chIden in base_key: + return base_key.replace(self._chIden, channel_name) + return base_key def insert_process(self, process_name, base_key): """ build a key from 'base_key' for a specific process 'process_name'. """ - if not base_key is None or base_key == "": + if base_key is None or base_key == "": print "unsuitable base_key!" return "" - if self._procIden in base_key: - return base_key.replace(self._procIden, process_name) + if not process_name == "" and not process_name is None: + if self._procIden in base_key: + return base_key.replace(self._procIden, process_name) + return base_key def insert_systematic(self, systematic_name, base_key): """ build a key from 'base_key' for a specific systematic 'systematic_name'. """ - if not base_key is None or base_key == "": + if base_key is None or base_key == "": print "unsuitable base_key!" return "" - if self._systIden in base_key: - return base_key.replace(self._systIden, systematic_name) + if not systematic_name == "" and not systematic_name is None: + if self._systIden in base_key: + return base_key.replace(self._systIden, systematic_name) + return base_key - def build_nominal_histo_name( self, process_name, channel_name, + def build_nominal_histo_name( self, process_name, channel_name = "", base_key = ""): """ build nominal histogram name for process 'process_name' in @@ -159,8 +179,10 @@ def build_nominal_histo_name( self, process_name, channel_name, base_key = key) return key - def build_systematics_histo_name_down( self, process_name, channel_name, - systematic_name, base_key = ""): + def build_systematic_histo_name_down( self, process_name = "", + channel_name = "", + systematic_name = "", + base_key = ""): """ build systematic histogram name for process 'process_name' in category 'channel_name' and 'down' variation for @@ -177,15 +199,21 @@ def build_systematics_histo_name_down( self, process_name, channel_name, base_key = key) return key+"Down" - def build_systematics_histo_name_up( self, process_name, channel_name, - systematic_name, base_key = ""): + def build_systematic_histo_name_up( self, process_name = "", + channel_name = "", systematic_name = "", + base_key = ""): """ build systematic histogram name for process 'process_name' in category 'channel_name' and 'up' variation for systematic 'systematic_name'. The histogram is built based on the generic systematic histogram key by default. """ + if self._debug >= 99: + print "DEBUG: Building name for systematic up variation from", base_key if base_key == "" or not isinstance(base_key, str): + if self._debug >= 99: + s = "DEBUG: Bad base key detected!" + s += " Will replace it with", self._generic_syst_key base_key = self._generic_syst_key key = self.insert_process( process_name=process_name, base_key = base_key) @@ -195,8 +223,9 @@ def build_systematics_histo_name_up( self, process_name, channel_name, base_key = key) return key+"Up" - def build_systematics_histo_names( self, process_name, channel_name, - systematic_name, base_key = ""): + def build_systematic_histo_names( self, process_name = "", + channel_name = "", systematic_name = "", + base_key = ""): """ build systematic histogram name for process 'process_name' in category 'channel_name' and systematic 'systematic_name'. @@ -205,11 +234,11 @@ def build_systematics_histo_names( self, process_name, channel_name, first one is for 'up' variation second one is for 'down' variation """ - up = self.build_systematics_histo_name_up(process_name = process_name, + up = self.build_systematic_histo_name_up(process_name = process_name, channel_name = channel_name, systematic_name = systematic_name, base_key = base_key) - down = self.build_systematics_histo_name_down(process_name=process_name, + down = self.build_systematic_histo_name_down(process_name=process_name, channel_name = channel_name, systematic_name = systematic_name, base_key = base_key) @@ -251,4 +280,12 @@ def matches_generic_systematic_key( self, tocheck, process_name, if temp == self._generic_syst_key: return True - return False \ No newline at end of file + return False + + def is_allowed_key(self, key): + return True + # if + # if not key is None and not key == "": + # if not self._procIden in key: + # return True + # return False \ No newline at end of file diff --git a/src/categoryObject.py b/src/categoryObject.py index 041c1e3..8ea020e 100644 --- a/src/categoryObject.py +++ b/src/categoryObject.py @@ -15,7 +15,7 @@ def init_variables(self): self._signalprocs = {} self._bkgprocs = {} self._key_creator = identificationLogic() - self._default_root_file = None + self._default_file = None def __init__( self, categoryName=None, defaultRootFile=None, defaultnominalkey=None, @@ -48,7 +48,7 @@ def __init__( self, categoryName=None, defaultRootFile=None, self._key_creator.generic_systematics_key = systkey if not defaultRootFile is None: if path.exists(defaultRootFile): - self._default_root_file = defaultRootFile + self._default_file = defaultRootFile @@ -131,6 +131,16 @@ def generic_key_systematic_hist(self, key): base_key = key) self._key_creator.generic_systematics_key = channelkey + @property + def default_file(self): + return self._default_file + @default_file.setter + def default_file(self, path): + if path.exists(path): + self._default_file = path + else: + print "ERROR: File '%s' does not exist!" % path + @@ -148,7 +158,7 @@ def add_signal_process( self, name, rootfile = None, histoname = None, systkey = self._key_creator.insert_process(process_name = name, base_key = self._key_creator.generic_systematics_key) if rootfile is None: - rootfile = self._default_root_file + rootfile = self._default_file self.add_process_raw( dic = self._signalprocs, name = name, rootfile = rootfile, histoname = histoname, systkey = systkey) diff --git a/src/processObject.py b/src/processObject.py index e764ad6..29fd4de 100644 --- a/src/processObject.py +++ b/src/processObject.py @@ -5,13 +5,14 @@ if not basedir in spath: spath.append(basedir) from helperClass import helperClass -# from identificationLogic import identificationLogic +from identificationLogic import identificationLogic from valueConventions import valueConventions from fileHandler import fileHandler class processObject(object): _helper = helperClass() - # _id_logic = identificationLogic() + _id_logic = identificationLogic() + identificationLogic.belongs_to = "process" _value_rules = valueConventions() _file_handler = fileHandler() _helper._debug = 99 @@ -149,19 +150,30 @@ def file(self, rootpath): print "file '%s' does not exist!" % rootpath @property - def nominalhistname(self): + def nominal_hist_name(self): return self._nominalhistname - @nominalhistname.setter - def nominalhistname(self, hname): + @nominal_hist_name.setter + def nominal_hist_name(self, hname): if self._file_handler.histogram_exists(histname = hname): - self._nominalhistname = hname - if self. + #following if statement should be redundand + if self._id_logic.is_allowed_key(hname): + self._id_logic.generic_nominal_key = hname + self._eventcount = self._file_handler.get_integral(hname) else: s = "'%s' does not exist " % hname s += "in '%s'" % self._file_handler.filepath + @property + def systematic_hist_name(self): + return self._systkey + @systematic_hist_name.setter + def systematic_hist_name(self, key): + if self._id_logic.is_allowed_key(key): + self._systkey = key + + def __str__(self): """ current setup: print delivers: @@ -203,9 +215,11 @@ def add_uncertainty(self, syst, typ, value): if isinstance(syst, str) and isinstance(typ, str): if not syst in self._uncertainties: if typ == "shape": - print "Looking for varied histograms for systematic" - # if self._file_handler.histogram_exists(self._rootfile, - # self._systkey.replace(self._sy)) + print "Looking for varied histograms for systematic", syst + keys = self._id_logic.build_systematic_histo_names( + systematic_name = syst, base_key = self._systkey) + if not all(self._file_handler.histogram_exists(k) for k in keys): + return False if self._value_rules.is_good_systval(value): self._uncertainties[syst] = {} self._uncertainties[syst]["type"] = typ diff --git a/test.py b/test.py index 7b7f401..e39a803 100644 --- a/test.py +++ b/test.py @@ -23,8 +23,8 @@ def datacard_tests(): testproc.category = "jge6_tge4" print "setting rootfile" testproc.file = "test.root" - testproc.nominalhistname = "nominal" - testproc.systname = "nominal_$SYSTEMATIC" + testproc.nominal_hist_name = "nominal" + testproc.systematic_hist_name = "nominal_$SYSTEMATIC" testproc.add_uncertainty("lumi", "lnN", "1.025") testproc.add_uncertainty("pdf_gg", "lnN", "0.98/1.02") testproc.add_uncertainty(syst = "JES", typ = "shape", value = 1.0) From f8e2e4e84e091fd63c7dec19f8895428317682c0 Mon Sep 17 00:00:00 2001 From: Philip Daniel Keicher Date: Tue, 16 Oct 2018 17:49:23 +0200 Subject: [PATCH 17/73] started working on writing a datacard --- src/datacardMaker.py | 52 +++++++++++++++++++++++++++++++------------- src/processObject.py | 8 +++---- test.py | 7 ++++++ 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index a13b435..8cf3f0b 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -9,7 +9,7 @@ from systematicObject import systematicObject class datacardMaker(object): - + _debug = 200 def init_variables(self): self._header = [] self._bins = "" @@ -18,7 +18,7 @@ def init_variables(self): self._systematics = {} self._hardcode_numbers = False self._replace_files = False - self._outputpath = "/path/for/datacard.txt" + self._outputpath = "" self._block_separator = "-"*130 def __init__( self, pathToDatacard = "", @@ -46,6 +46,8 @@ def replace_files(self): def replace_files(self, val): if type(val) == bool: self._replace_files = val + if self._debug >= 99: + print "DEBUG: setting 'replace_files' to", val else: print "Value given is not boolean! Did not set 'replace_files'" @@ -57,13 +59,13 @@ def outputpath(self, outpath): if outpath is None: print "'outpath' cannot be None!" elif path.exists(outpath): + outpath = path.abspath(outpath) + self._outputpath = outpath if self._replace_files: - outpath = path.abspath(outpath) - self._outputpath = outpath print "will replace", self._outputpath else: - s = "File %s already exists" % outpath - s += " and I'm not allowed to overwrite - Skipping" + s = "WARNING: File %s already exists" % outpath + s += " and I'm not allowed to overwrite" print s else: outpath = path.abspath(outpath) @@ -84,6 +86,17 @@ def block_separator(self, sep): self._block_separator = sep + def add_category(self, category): + if isinstance(category, categoryObject): + catname = category.name + if not catname in self._categories: + self._categories[catname] = category + else: + print "ERROR: Category %s is known to this datacard!" % catname + else: + print "ERROR: Input required to be instance of categoryObject!" + + def load_from_file(self, pathToDatacard): if path.exists(pathToDatacard): print "loading datacard from", pathToDatacard @@ -120,7 +133,7 @@ def get_number_of_procs(self): currentnum += self._categories[cat].n_background_procs if num == 0: num = currentnum if not num == currentnum: - print "Mismatch! Categories have different number of process!" + print "Mismatch! Categories have different number of processes!" num = 0 break return num @@ -155,6 +168,8 @@ def create_header(self): #get number of systematics if len(self._systematics) != 0: nsysts = len(self._systematics) + else: + print "WARNING: Did not find any systematics!" header.append("imax {0} number of bins".format(ncats)) @@ -231,18 +246,25 @@ def create_systematics_block(self): """ pass - def write_datacard(self): - if path.exists(self._outputpath): - #create datacard header - content = [] - content.append(self.create_header()) - #create block with keywords for systematic variations + def create_datacard_text(self): + #create datacard header + content = [] + content.append(self.create_header()) + #create block with keywords for systematic variations - #create observation block + #create observation block + return self._block_separator.join(content) + def write_datacard(self): + text = "" + if self._outputpath and not path.exists(self._outputpath): + text = self.create_datacard_text() + elif path.exists(self._outputpath) and self._replace_files: + text = self.create_datacard_text() + if not text == "": with open(self._outputpath, "w") as f: - f.write(self._block_separator.join(content)) + f.write(text) else: print "ERROR: Could not write datacard here:", self._outputpath diff --git a/src/processObject.py b/src/processObject.py index 29fd4de..89cef5c 100644 --- a/src/processObject.py +++ b/src/processObject.py @@ -19,7 +19,7 @@ class processObject(object): def init_variables(self): self._name = "" - self._rootfile = "" + # self._rootfile = "" self._categoryname = "" self._nominalhistname = "" self._systkey = "" @@ -36,7 +36,7 @@ def __init__( self, processName = None, pathToRootfile = None, if not processName is None: self._name = processName if not pathToRootfile is None: - self._rootfile = pathToRootfile + self._file_handler.filepath = pathToRootfile if not nominal_hist_key is None: self._nominalhistname = nominal_hist_key if not categoryname is None: @@ -159,7 +159,7 @@ def nominal_hist_name(self, hname): if self._file_handler.histogram_exists(histname = hname): #following if statement should be redundand if self._id_logic.is_allowed_key(hname): - self._id_logic.generic_nominal_key = hname + self._nominalhistname = hname self._eventcount = self._file_handler.get_integral(hname) else: s = "'%s' does not exist " % hname @@ -185,7 +185,7 @@ def __str__(self): s.append("Process infos:") s.append("\tname:\t%s" % self.get_name()) s.append("\tcategory:\t%s" % self.get_category()) - s.append("\trootfile:\t%s" % self._rootfile) + s.append("\trootfile:\t%s" % self._file_handler.filepath) s.append("\tnominal histname:\t%s" % self._nominalhistname) s.append("\tyield:\t{0}".format(self._eventcount)) if len(self._uncertainties) != 0: diff --git a/test.py b/test.py index e39a803..c9d0f36 100644 --- a/test.py +++ b/test.py @@ -39,6 +39,13 @@ def datacard_tests(): category.add_signal_process(testproc) print category + dm = datacardMaker() + dm.add_category(category) + dm.outputpath = "test.txt" + dm.hardcode_numbers = True + dm.replace_files = True + dm.write_datacard() + def init_tests(): a = testClass() From f89e26d9a73762b0bef7837f4d31f52b41dfcf81 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 16 Oct 2018 19:30:01 +0200 Subject: [PATCH 18/73] changes to categoryObject --- src/categoryObject.py | 9 ++++++--- src/datacardMaker.py | 3 ++- src/test2.py | 31 +++++++++++++++++++++++-------- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/categoryObject.py b/src/categoryObject.py index 6e9b12a..a6c33e4 100644 --- a/src/categoryObject.py +++ b/src/categoryObject.py @@ -237,9 +237,12 @@ def add_process(self, dic, process): print "ERROR: Category can only contain processes!" def __getitem__(self, process): - for bkgprocs in self._bkpgprocs: - if bkgprocs=process: - return bkgprocs + for name,procobj in self._bkpgprocs.items(): + if name==process: + return procobj + for name,procobj in self._signalprocs.items(): + if name==process: + return procobj def __str__(self): s = [] diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 34e76e1..a894482 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -139,6 +139,7 @@ def load_from_file(self, pathToDatacard): proc=processObject() proc.name = process proc.category = category + for shapelines in self._shapelines_: shape = shapelines.split() if shape[2] == category or shape[2]=="*": @@ -170,7 +171,7 @@ def load_from_file(self, pathToDatacard): systematic.pop(0) for value,process,category in zip(systematic,processes,binprocesses): if value!="-": - #self._categories[category] .add_uncertainty(sys,typ,value) + self._categories[category][process].add_uncertainty(sys,typ,value) else: diff --git a/src/test2.py b/src/test2.py index 4ce7340..a0fc49c 100644 --- a/src/test2.py +++ b/src/test2.py @@ -1,20 +1,30 @@ class cO: - def __init__(self,category): - self.__category = category - self.__dic ={} + + def __init__(self,category): + self.__name= category + self.dic = {} + def add_process(self,process): if isinstance(process, pO): - self.__dic[process.getName]= process + name=process.getName() + print name + self.dic[name]= process def __getitem__(self, process): - for proc in self.__dic: - if proc.getName==process: + for name,proc in self.dic.items(): + if name == process: return proc + def printdict(self): + for name,process in self.dic.items(): + print name, "corresponds to", process.getName() + def __str__(self): s = [] s.append("Category Name:\t%s" % self.__category) + #for name,process in self.__dic.items(): + #s.append("Process Name:\t%s" % name) return "\n".join(s) class pO: @@ -34,7 +44,12 @@ def getNumber(self): c1=cO("ch1") p1=pO("process1") p1.setNumber(1) +p2=pO("process2") +p2.setNumber(2) print p1.getNumber() +print p1.getName() +print p2.getName() c1.add_process(p1) -print c1 -c1["process1"].getNumber \ No newline at end of file +c1.add_process(p2) +c1.printdict() +print c1["process1"].getNumber() From ea5f208714f9c5938477af6cfbfc136ac4d2d83e Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 16 Oct 2018 19:36:16 +0200 Subject: [PATCH 19/73] fixes minor spelling mistake --- src/categoryObject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/categoryObject.py b/src/categoryObject.py index a6c33e4..f147d87 100644 --- a/src/categoryObject.py +++ b/src/categoryObject.py @@ -144,7 +144,7 @@ def nominalhistname(self, hname): def systname(self): return self._systkey - @shistname.setter + @systname.setter def systname(self, hname): if self.file_handler.histogram_exists(file = self._rootfile, From fbfd3aa982d2ec1a5d974ea5601b87e90c3d87e7 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 16 Oct 2018 20:31:59 +0200 Subject: [PATCH 20/73] changed str to int to work in loa_from_file --- src/datacardMaker.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index ccb4884..1047b56 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -148,11 +148,11 @@ def load_from_file(self, pathToDatacard): processtypes.pop(0) if len(processes)==len(binprocesses) and len(processes)==len(processtypes): - for process,category,processtype in zip(processes,binprocesses,processtypes): + for process,category,pt in zip(processes,binprocesses,processtypes): proc=processObject() proc.name = process proc.category = category - + processtype = int(pt) for shapelines in self._shapelines_: shape = shapelines.split() if shape[2] == category or shape[2]=="*": @@ -170,7 +170,7 @@ def load_from_file(self, pathToDatacard): proc.systname=shape[5] - if processtype > 0: + if processtype >= 1: self._categories[category].add_background_process(proc) else: self._categories[category].add_signal_process(proc) From 07113607cf63fcac9e5d51956dd407a9bf63e3bb Mon Sep 17 00:00:00 2001 From: Philip Daniel Keicher Date: Fri, 19 Oct 2018 09:20:10 +0200 Subject: [PATCH 21/73] added uncertainties property --- src/processObject.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/processObject.py b/src/processObject.py index 89cef5c..008d2ec 100644 --- a/src/processObject.py +++ b/src/processObject.py @@ -173,6 +173,9 @@ def systematic_hist_name(self, key): if self._id_logic.is_allowed_key(key): self._systkey = key + @property + def uncertainties(self): + return list(self._uncertainties.keys()) def __str__(self): """ From c72ae687cb1c56f64ecde149eea31c9b168ebed3 Mon Sep 17 00:00:00 2001 From: Philip Daniel Keicher Date: Fri, 19 Oct 2018 09:27:46 +0200 Subject: [PATCH 22/73] systematic can now read out processObjects --- src/systematicObject.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/systematicObject.py b/src/systematicObject.py index 673bbd3..2c0350b 100644 --- a/src/systematicObject.py +++ b/src/systematicObject.py @@ -67,7 +67,7 @@ def add_process(self, category, procname, value): temp = "ERROR: process '%s'" % procname temp += " in category '%s'" % category temp += " is already known to systematic '%s'!" % self._name - temp += " Use 'processObject_base.set_correlation()' instead" + temp += " Use 'processObject.set_correlation()' instead" print temp def add_process(self, dic, category): @@ -84,10 +84,12 @@ def add_process(self, dic, category): temp += " is unknowns to systematic '%s'" % self._name print temp - def add_process(self, process, correlation): - if isinstance(process, processObject_base): + def add_process(self, process, correlation = "-"): + if isinstance(process, processObject): cor = self.get_correlation(process.category, process.name) if cor == "-": + if correlation == "-": + correlation = process.get_uncertainty_value(systname = self._name) self.add_process( category = process.category, procname = process.name, value = correlation) @@ -97,7 +99,7 @@ def add_process(self, process, correlation): temp += "Use the 'set_correlation' function" print temp else: - print "ERROR: Could not add process - input must be processObject_base" + print "ERROR: Could not add process - input must be processObject" def get_correlation(self, category, procname): if category in self._dic: @@ -123,15 +125,20 @@ def set_correlation(self, category, procname, value): temp += " is unknown to systematic '%s'" % self._name print temp - def set_correlation(self, process, value): - if isinstance(process, processObject_base): + def set_correlation(self, process, value = "-"): + if isinstance(process, processObject): procname = process.name category = process.category + if value == "-": + value = process.get_uncertainty_value(systname = self._name) if procname in self._dic[category]: if self.helper.is_good_systval(value): self._dic[category][procname] = value else: - print "Could not add process: input must be dictionary!" + s = "ERROR: Process '%s' is not known yet to" % procname + s += " systematic '%s'!" % self._name + s += " Please use 'systematicObject.add_process' instead" + print s else: - print "ERROR: Could not set process - input must be processObject_base" + s = "ERROR: Could not set process! Input must be processObject" From 9955c29d914bd6588ec1905fb6567adc4f20c74e Mon Sep 17 00:00:00 2001 From: Philip Daniel Keicher Date: Fri, 19 Oct 2018 09:33:40 +0200 Subject: [PATCH 23/73] add logic to check compatibility with default settings in categories --- src/categoryObject.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/categoryObject.py b/src/categoryObject.py index 8ea020e..682e661 100644 --- a/src/categoryObject.py +++ b/src/categoryObject.py @@ -15,6 +15,7 @@ def init_variables(self): self._signalprocs = {} self._bkgprocs = {} self._key_creator = identificationLogic() + self._key_creator.belongs_to = "channel" self._default_file = None def __init__( self, categoryName=None, defaultRootFile=None, @@ -224,6 +225,8 @@ def add_background_process( self, process): def add_process(self, dic, process): if isinstance(process, processObject): + if self._default_file is None: + self.default_file = process.file dic[process.name] = process else: print "ERROR: Category can only contain processes!" @@ -234,6 +237,21 @@ def add_process_raw(self, dic, name, rootfile, histoname, systkey): categoryname = self._name) self.add_process(dic = dic, process = temp) + def is_compatible_with_default(self, process): + """ + check whether information for 'process' is compatible with default + information for this category + """ + nominal_is_compatible = self._key_creator.matches_generic_nominal_key( + tocheck = process.nominal_hist_name, + process_name = process.name, + category_name = self._name) + systematic_is_compatible = self._key_creator.matches_generic_systematic_key( + tocheck = process.systematic_hist_name, + process_name = process.name, + category_name = self._name) + return (nominal_is_compatible and systematic_is_compatible) + def __str__(self): s = [] s.append("Category Name:\t%s" % self._name) From e6d4bc5f182ab447a2c1321046bf9d1341397293 Mon Sep 17 00:00:00 2001 From: Philip Daniel Keicher Date: Fri, 19 Oct 2018 09:57:13 +0200 Subject: [PATCH 24/73] started working on shape keyword block --- src/datacardMaker.py | 53 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 8cf3f0b..f3a551e 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -91,6 +91,7 @@ def add_category(self, category): catname = category.name if not catname in self._categories: self._categories[catname] = category + # self.update_systematics(category = category) else: print "ERROR: Category %s is known to this datacard!" % catname else: @@ -138,6 +139,29 @@ def get_number_of_procs(self): break return num + def collect_uncertainties(self, process_dict): + """ + Loop over all process in the dictionary 'process_dict' and save + the respective systematic uncertainties and correlations + """ + for process in process_dict: + for syst in process.uncertainties: + #first, check if uncertainty is already known + #if not, create new systematicsObject + if not syst in self._systematics: + self._systematics[syst] = systematicObject(name = syst, + nature = process.get_uncertainty_type()) + self._systematics[syst].add_process(process = process) + + + def update_systematics(self, category): + + self.collect_uncertainties(process_dict = category.signal_processes) + self.collect_uncertainties(process_dict = category.background_processes) + + + + def create_header(self): """ Create header for the datacard. The header has the following form: @@ -177,6 +201,27 @@ def create_header(self): header.append("kmax {0} number of nuisance parameters".format(nsysts)) return "\n".join(header) + def write_keyword_block_line(self, process_name, category_name, file, + nominal_key, syst_key): + s = ["shapes"] + s.append(process_name) + s.append(category_name) + s.append(file) + s.append(nominal_key) + s.append(syst_key) + + return s + + def write_keyword_block_lines(self, category): + lines = [] + line = self.write_keyword_block_line(process_name = "*", + category_name = category.name, file = category.default_file, + nominal_key = category.generic_key_nominal_hist, + syst_key = category.generic_key_systematic_hist) + + + + def create_keyword_block(self): """ Create block with keywords with which to find the systematic variations @@ -194,7 +239,11 @@ def create_keyword_block(self): used before, this should contain '$CHANNEL' and/or '$PROCESS' """ - pass + lines = [] + for cat in self._categories: + if any(cat in syst._dict and syst.type == "shape" for syst in self._systematics): + lines += self.write_keyword_block_lines(category = self._categories[cat]) + return "\n".join(lines) def create_observation_block(self): """ @@ -249,6 +298,8 @@ def create_systematics_block(self): def create_datacard_text(self): #create datacard header content = [] + for cat in self._categories: + self.update_systematics(self._categories[cat]) content.append(self.create_header()) #create block with keywords for systematic variations From 9011c861f47524a20054205c74fc427ddfb85263 Mon Sep 17 00:00:00 2001 From: Philip Daniel Keicher Date: Fri, 19 Oct 2018 10:25:53 +0200 Subject: [PATCH 25/73] various bug fixes in systematicObjects --- src/categoryObject.py | 13 +++------ src/datacardMaker.py | 41 +++++++++++++++------------- src/systematicObject.py | 59 +++++++++++++++++++++-------------------- 3 files changed, 56 insertions(+), 57 deletions(-) diff --git a/src/categoryObject.py b/src/categoryObject.py index 9062039..b577a5e 100644 --- a/src/categoryObject.py +++ b/src/categoryObject.py @@ -136,16 +136,11 @@ def generic_key_systematic_hist(self, key): def default_file(self): return self._default_file @default_file.setter - def default_file(self, path): - if path.exists(path): - self._default_file = path + def default_file(self, filepath): + if path.exists(filepath): + self._default_file = filepath else: - print "ERROR: File '%s' does not exist!" % path - - - - - + print "ERROR: File '%s' does not exist!" % filepath def add_signal_process( self, name, rootfile = None, histoname = None, systkey = None): diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 5179995..1ca3c47 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -153,22 +153,24 @@ def load_from_file(self, pathToDatacard): proc=processObject() proc.name = process proc.category = category - processtype = int(pt) + processtype = int(pt) for shapelines in self._shapelines_: shape = shapelines.split() if shape[2] == category or shape[2]=="*": if shape[1] == "*": - proc.rootfile = shape[3] - proc.nominalhistname=shape[4] - proc.systname=shape[5] - #self._categories[category].default_file = shape[3] - #self._categories[category].generic_key_nominal_hist = shape[4] - #self._categories[category].generic_key_systematic_hist = shape[5] + pass + # proc.rootfile = shape[3] + # proc.nominalhistname=shape[4] + # proc.systname=shape[5] + #self._categories[category].default_file = shape[3] + #self._categories[category].generic_key_nominal_hist = shape[4] + #self._categories[category].generic_key_systematic_hist = shape[5] if shape[1] == process: - proc.rootfile=shape[3] - proc.nominalhistname=shape[4] - proc.systname=shape[5] + pass + # proc.rootfile=shape[3] + # proc.nominalhistname=shape[4] + # proc.systname=shape[5] if processtype >= 1: @@ -178,13 +180,13 @@ def load_from_file(self, pathToDatacard): #adds systematics to processes for systematics in self._systematics_: - systematic = systematics.split() - sys=systematic[0] - typ=systematic[1] - systematic.pop(1) - systematic.pop(0) - for value,process,category in zip(systematic,processes,binprocesses): - if value!="-": + systematic = systematics.split() + sys=systematic[0] + typ=systematic[1] + systematic.pop(1) + systematic.pop(0) + for value,process,category in zip(systematic,processes,binprocesses): + if value!="-": self._categories[category][process].add_uncertainty(sys,typ,value) @@ -212,13 +214,14 @@ def collect_uncertainties(self, process_dict): Loop over all process in the dictionary 'process_dict' and save the respective systematic uncertainties and correlations """ - for process in process_dict: + for process_name in process_dict: + process = process_dict[process_name] for syst in process.uncertainties: #first, check if uncertainty is already known #if not, create new systematicsObject if not syst in self._systematics: self._systematics[syst] = systematicObject(name = syst, - nature = process.get_uncertainty_type()) + nature = process.get_uncertainty_type(syst)) self._systematics[syst].add_process(process = process) diff --git a/src/systematicObject.py b/src/systematicObject.py index 2c0350b..1556930 100644 --- a/src/systematicObject.py +++ b/src/systematicObject.py @@ -18,7 +18,7 @@ def init_variables(self): self._type = "" self._dic = {} def __init__(self, name=None, nature=None, dic = None): - + self.init_variables() if not name is None: try: self._name = str(name) @@ -57,41 +57,41 @@ def type(self, typ): except ValueError: print "Could not cast 'type' into a string! 'type' not set" - def add_process(self, category, procname, value): - if not category in self._dic: - self._dic[category] = {} - if not procname in self._dic[category]: + def add_process_raw(self, category_name, process_name, value): + if not category_name in self._dic: + self._dic[category_name] = {} + if not process_name in self._dic[category_name]: if self.helper.is_good_systval(value): - self._dic[category][procname] = value + self._dic[category_name][process_name] = value else: - temp = "ERROR: process '%s'" % procname + temp = "ERROR: process '%s'" % process_name temp += " in category '%s'" % category temp += " is already known to systematic '%s'!" % self._name temp += " Use 'processObject.set_correlation()' instead" print temp - def add_process(self, dic, category): - if not category in self._dic: + def add_process_by_dictionary(self, dic, category): + if not category_name in self._dic: if dic and isinstance(dic, dict): if all(self.helper.is_good_systval(dic[key]) for key in dic): - self._dic[category] += dic + self._dic[category_name] += dic else: print "ERROR: input dictionary contains bad values!" else: print "Could not add process: input must be dictionary!" else: - temp = "ERROR: category '%s'" % category + temp = "ERROR: category '%s'" % category_name temp += " is unknowns to systematic '%s'" % self._name print temp def add_process(self, process, correlation = "-"): if isinstance(process, processObject): - cor = self.get_correlation(process.category, process.name) + cor = self.get_correlation(process = process) if cor == "-": if correlation == "-": correlation = process.get_uncertainty_value(systname = self._name) - self.add_process( category = process.category, - procname = process.name, + self.add_process_raw( category_name = process.category, + process_name = process.name, value = correlation) else: temp = "process '%s' is already known to " % process.name @@ -101,41 +101,42 @@ def add_process(self, process, correlation = "-"): else: print "ERROR: Could not add process - input must be processObject" - def get_correlation(self, category, procname): - if category in self._dic: - if procname in self._dic[category]: - return str(self._dic[category][procname]) + def get_correlation_raw(self, category_name, process_name): + if category_name in self._dic: + if process_name in self._dic[category_name]: + return str(self._dic[category_name][process_name]) else: return "-" def get_correlation(self, process): category = process.category - procname = process.name - return self.get_correlation(category = category, procname = procname) + process_name = process.name + return self.get_correlation_raw( category_name = category, + process_name = process_name) - def set_correlation(self, category, procname, value): - if category in self._dic: - if procname in self._dic[category]: + def set_correlation_raw(self, category_name, process_name, value): + if category_name in self._dic: + if process_name in self._dic[category_name]: if self.helper.is_good_systval(value): - self._dic[category][procname] = value + self._dic[category_name][process_name] = value else: print "Could not add process: input must be dictionary!" else: - temp = "ERROR: category '%s'" % category + temp = "ERROR: category '%s'" % category_name temp += " is unknown to systematic '%s'" % self._name print temp def set_correlation(self, process, value = "-"): if isinstance(process, processObject): - procname = process.name + process_name = process.name category = process.category if value == "-": value = process.get_uncertainty_value(systname = self._name) - if procname in self._dic[category]: + if process_name in self._dic[category]: if self.helper.is_good_systval(value): - self._dic[category][procname] = value + self._dic[category][process_name] = value else: - s = "ERROR: Process '%s' is not known yet to" % procname + s = "ERROR: Process '%s' is not known yet to" % process_name s += " systematic '%s'!" % self._name s += " Please use 'systematicObject.add_process' instead" print s From 28bdadb468c8f7d476d44699377be0fef8a2215a Mon Sep 17 00:00:00 2001 From: Philip Daniel Keicher Date: Fri, 19 Oct 2018 10:51:09 +0200 Subject: [PATCH 26/73] nothing --- src/datacardMaker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 1ca3c47..726ab1a 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -147,7 +147,7 @@ def load_from_file(self, pathToDatacard): binprocesses.pop(0) processtypes = self._processtype_.split() processtypes.pop(0) - + if len(processes)==len(binprocesses) and len(processes)==len(processtypes): for process,category,pt in zip(processes,binprocesses,processtypes): proc=processObject() From 0fc5b28964c970033f3208b72383dcb6495c2dab Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Fri, 19 Oct 2018 11:03:40 +0200 Subject: [PATCH 27/73] minor performance changes --- src/categoryObject.py | 20 +++++++++++++------- src/datacardMaker.py | 10 ++++++---- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/categoryObject.py b/src/categoryObject.py index efb75e8..9a88f27 100644 --- a/src/categoryObject.py +++ b/src/categoryObject.py @@ -231,13 +231,19 @@ def add_process(self, dic, process): def __getitem__(self, process): - for name,procobj in self._bkgprocs.items(): - if name==process: - return procobj - for name,procobj in self._signalprocs.items(): - if name==process: - return procobj - + #for name,procobj in self._bkgprocs.items(): + #if name==process: + #return procobj + #for name,procobj in self._signalprocs.items(): + #if name==process: + #return procobj + if process in self._bkgprocs: + return self._bkgprocs[process] + elif process in self._signalprocs: + return self._signalprocs[process] + else: + print "ERROR: Process not in Category" + def add_process_raw(self, dic, name, rootfile, histoname, systkey): diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 1047b56..735e0cd 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -158,16 +158,18 @@ def load_from_file(self, pathToDatacard): if shape[2] == category or shape[2]=="*": if shape[1] == "*": proc.rootfile = shape[3] - proc.nominalhistname=shape[4] - proc.systname=shape[5] + proc.nominal_hist_name=shape[4] + proc.systematic_hist_name=shape[5] + print shape [3] + print shape [4] #self._categories[category].default_file = shape[3] #self._categories[category].generic_key_nominal_hist = shape[4] #self._categories[category].generic_key_systematic_hist = shape[5] if shape[1] == process: proc.rootfile=shape[3] - proc.nominalhistname=shape[4] - proc.systname=shape[5] + proc.nominal_hist_name=shape[4] + proc.systematic_hist_name=shape[5] if processtype >= 1: From 800a1ad3fbb7f57c8c68c13e648b9551bf9ee56e Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Fri, 26 Oct 2018 14:51:32 +0200 Subject: [PATCH 28/73] added printout, fixed issue of adding process as processObject --- src/systematicObject.py | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/systematicObject.py b/src/systematicObject.py index 1556930..f3ea411 100644 --- a/src/systematicObject.py +++ b/src/systematicObject.py @@ -4,7 +4,6 @@ basedir = path.join(thisdir, "base") if not basedir in spath: spath.append(basedir) -from helperClass import helperClass if not thisdir in spath: spath.append(thisdir) @@ -12,7 +11,6 @@ from processObject import processObject class systematicObject(object): - helper = helperClass() def init_variables(self): self._name = "" self._type = "" @@ -61,8 +59,11 @@ def add_process_raw(self, category_name, process_name, value): if not category_name in self._dic: self._dic[category_name] = {} if not process_name in self._dic[category_name]: - if self.helper.is_good_systval(value): + if True: + #if self.helper.is_good_systval(value): + self._dic[category_name][process_name] = {} self._dic[category_name][process_name] = value + else: temp = "ERROR: process '%s'" % process_name temp += " in category '%s'" % category @@ -86,7 +87,8 @@ def add_process_by_dictionary(self, dic, category): def add_process(self, process, correlation = "-"): if isinstance(process, processObject): - cor = self.get_correlation(process = process) + cor = correlation + #self.get_correlation(process = process) why?? if cor == "-": if correlation == "-": correlation = process.get_uncertainty_value(systname = self._name) @@ -117,7 +119,8 @@ def get_correlation(self, process): def set_correlation_raw(self, category_name, process_name, value): if category_name in self._dic: if process_name in self._dic[category_name]: - if self.helper.is_good_systval(value): + if True: + #if self.helper.is_good_systval(value): self._dic[category_name][process_name] = value else: print "Could not add process: input must be dictionary!" @@ -133,7 +136,8 @@ def set_correlation(self, process, value = "-"): if value == "-": value = process.get_uncertainty_value(systname = self._name) if process_name in self._dic[category]: - if self.helper.is_good_systval(value): + if True: + #if self.helper.is_good_systval(value): self._dic[category][process_name] = value else: s = "ERROR: Process '%s' is not known yet to" % process_name @@ -143,3 +147,22 @@ def set_correlation(self, process, value = "-"): else: s = "ERROR: Could not set process! Input must be processObject" + def __str__(self): + s = [] + s.append("Systematic Name:\t%s" % self._name) + s.append("Systematic Type:\t%s" % self._type) + if len(self._dic) != 0: + s.append("\tlist of processes:") + temp = "\t\t%s" % "category".ljust(15) + temp += "\t\t%s" % "process".ljust(15) + temp += "\t%s" % "value".ljust(15) + s.append(temp) + s.append("\t\t"+"_"*len(temp.expandtabs())) + for category in self._dic: + for process in self._dic[category]: + temp = "\t\t%s" % category.ljust(15) + temp += "\t\t%s" % process.ljust(15) + temp += "\t%s" % str(self._dic[category][process]).ljust(10) + s.append(temp) + + return "\n".join(s) From 07b1b4ad5f8a3d511ac34503c0df9927fa572512 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Fri, 26 Oct 2018 14:52:54 +0200 Subject: [PATCH 29/73] added function to collect all processes (useful for create_process_block and create_systematics_block) --- src/datacardMaker.py | 43 +++++++++++++++++++++++++++++++++++++++++-- src/test.py | 5 +++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 9f0d02e..30c4c7a 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -16,6 +16,7 @@ def init_variables(self): self._bins = "" self._observation = "" self._categories = {} + self._processes = {} self._systematics = {} self._hardcode_numbers = False self._replace_files = False @@ -209,6 +210,7 @@ def load_from_file(self, pathToDatacard): def add_generic_keys(self,category_name,list_of_shapelines): + #adds generic key for categories self._categories[category_name].default_file = list_of_shapelines[3] self._categories[category_name].generic_key_nominal_hist = list_of_shapelines[4] self._categories[category_name].generic_key_systematic_hist = list_of_shapelines[5] @@ -216,7 +218,7 @@ def add_generic_keys(self,category_name,list_of_shapelines): def manage_processes(self, category_name, process_name, list_of_shapelines): """ - Search process corresponding to shapeline and add the key names + Search process corresponding to shapeline and add the explicit key names """ if process_name == "*": #for process in self._categories[category_name].processes(): @@ -375,6 +377,40 @@ def create_observation_block(self): """ pass + def collect_processes(self): + """ + Updates the list of all processes avaliable. Adds list of categories to process. + Finally updates process index (has to be the same for the same process independet of category) + """ + sigprc=[] + bkgprc=[] + for category in self._categories: + for process in self._categories[category]._bkgprocs: + if not process in self._processes: + self._processes[process]={} + bkgprc.append(process) + self._processes[process]["category"]=[] + self._processes[process]["category"].append(category) + + for process in self._categories[category]._signalprocs: + if not process in self._processes: + self._processes[process]={} + sigprc.append(process) + self._processes[process]["category"]=[] + self._processes[process]["category"].append(category) + + #Calculates number for process (signal: <= 0, bkg: > 0) + for n,sig in enumerate(sigprc): + self._processes[sig]["process_index"]=1+n-len(sigprc) + print n + + for n,bkg in enumerate(bkgprc): + self._processes[bkg]["process_index"]=1+n + print n + + + print self._processes + def create_process_block(self): """ Create the process block of the datacard. It has the following format: @@ -391,7 +427,10 @@ def create_process_block(self): (...) - place holder for more categories THIS IS NOT PART OF THE DATACARD! """ - pass + self.collect_processes() + lines = [] + + def create_systematics_block(self): """ diff --git a/src/test.py b/src/test.py index 00df21c..6f9874a 100644 --- a/src/test.py +++ b/src/test.py @@ -5,3 +5,8 @@ for name,category in d._categories.items(): print name print category +for name,category in d._categories.items(): + d.update_systematics(category) +for name,syst in d._systematics.items(): + print syst +d.collect_processes() \ No newline at end of file From a37eaf11320c9344de9f2efcb4b2cba728d20829 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Fri, 26 Oct 2018 16:31:33 +0200 Subject: [PATCH 30/73] changing sequence of channel/processes --- src/datacardMaker.py | 51 +++++++++++++++++++++++++++++++++-------- src/systematicObject.py | 2 +- src/test.py | 3 ++- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 30c4c7a..0321a04 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -1,4 +1,5 @@ import sys +import operator from os import path directory = path.abspath(path.realpath(path.dirname(__file__))) @@ -384,31 +385,33 @@ def collect_processes(self): """ sigprc=[] bkgprc=[] + self._process_index_= {} for category in self._categories: for process in self._categories[category]._bkgprocs: if not process in self._processes: - self._processes[process]={} + self._processes[process]=[] bkgprc.append(process) - self._processes[process]["category"]=[] - self._processes[process]["category"].append(category) + if not category in self._processes[process]: + self._processes[process].append(category) for process in self._categories[category]._signalprocs: if not process in self._processes: - self._processes[process]={} + self._processes[process]=[] sigprc.append(process) - self._processes[process]["category"]=[] - self._processes[process]["category"].append(category) + if not category in self._processes[process]: + self._processes[process].append(category) #Calculates number for process (signal: <= 0, bkg: > 0) for n,sig in enumerate(sigprc): - self._processes[sig]["process_index"]=1+n-len(sigprc) + self._process_index_[sig]=(1+n-len(sigprc)) print n for n,bkg in enumerate(bkgprc): - self._processes[bkg]["process_index"]=1+n + self._process_index_[bkg]=(1+n) print n - + + print self._process_index_ print self._processes def create_process_block(self): @@ -428,7 +431,37 @@ def create_process_block(self): THIS IS NOT PART OF THE DATACARD! """ self.collect_processes() + + indices=[] + for process in self._process_index_: + indices.append(self._process_index_[process]) + indices.sort() + + lines = [] + if len(self._processes) != 0: + bins = "bin".ljust(15) + process = "process".ljust(15) + process_index = "process".ljust(15) + rate = "rate".ljust(15) + for category in self._categories: + for index in indices: + for proc,ind in self._process_index_.items(): + if index==ind and category in self._processes[proc]: + bins += "\t\t%s" % category.ljust(15) + process += "\t\t%s" % proc.ljust(15 +) process_index += "\t\t%s" % index.ljust(15) + #rate += "\t\t%s" % self._categories[category][proc]._eventcount() + lines.append(bins) + lines.append(process) + lines.append(process_index) + lines.append(rate) + + else: + print "WARNING: Did not find any processes!" + + return "\n".join(lines) + diff --git a/src/systematicObject.py b/src/systematicObject.py index f3ea411..7207b49 100644 --- a/src/systematicObject.py +++ b/src/systematicObject.py @@ -152,7 +152,7 @@ def __str__(self): s.append("Systematic Name:\t%s" % self._name) s.append("Systematic Type:\t%s" % self._type) if len(self._dic) != 0: - s.append("\tlist of processes:") + s.append("list of processes:") temp = "\t\t%s" % "category".ljust(15) temp += "\t\t%s" % "process".ljust(15) temp += "\t%s" % "value".ljust(15) diff --git a/src/test.py b/src/test.py index 6f9874a..03da382 100644 --- a/src/test.py +++ b/src/test.py @@ -1,4 +1,5 @@ from datacardMaker import datacardMaker +import operator d=datacardMaker() d.load_from_file("combinedtestdatacard.txt") @@ -9,4 +10,4 @@ d.update_systematics(category) for name,syst in d._systematics.items(): print syst -d.collect_processes() \ No newline at end of file +print d.create_process_block() From 2032e3bca87ac49da87d1feb3be20ddecb56d3e2 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Mon, 29 Oct 2018 17:23:38 +0100 Subject: [PATCH 31/73] datacard writes process and systematics block --- src/categoryObject.py | 10 +-- src/datacardMaker.py | 142 +++++++++++++++++++++------------------- src/systematicObject.py | 17 +++-- src/test.py | 4 ++ 4 files changed, 87 insertions(+), 86 deletions(-) diff --git a/src/categoryObject.py b/src/categoryObject.py index f2d9d2d..8fb3984 100644 --- a/src/categoryObject.py +++ b/src/categoryObject.py @@ -253,12 +253,7 @@ def is_compatible_with_default(self, process): return (nominal_is_compatible and systematic_is_compatible) def __getitem__(self, process): - #for name,procobj in self._bkgprocs.items(): - #if name==process: - #return procobj - #for name,procobj in self._signalprocs.items(): - #if name==process: - #return procobj + if process in self._bkgprocs: return self._bkgprocs[process] elif process in self._signalprocs: @@ -280,9 +275,6 @@ def __contains__(self, processName): else: return False - - - def __str__(self): s = [] diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 0321a04..122fe6a 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -17,12 +17,11 @@ def init_variables(self): self._bins = "" self._observation = "" self._categories = {} - self._processes = {} self._systematics = {} self._hardcode_numbers = False self._replace_files = False self._outputpath = "" - self._block_separator = "-"*130 + self._block_separator = "\n" + "-"*130 + "\n" def __init__( self, pathToDatacard = "", processIdentifier = "$PROCESS", @@ -378,41 +377,20 @@ def create_observation_block(self): """ pass - def collect_processes(self): - """ - Updates the list of all processes avaliable. Adds list of categories to process. - Finally updates process index (has to be the same for the same process independet of category) - """ - sigprc=[] - bkgprc=[] - self._process_index_= {} - for category in self._categories: - for process in self._categories[category]._bkgprocs: - if not process in self._processes: - self._processes[process]=[] - bkgprc.append(process) - if not category in self._processes[process]: - self._processes[process].append(category) - - for process in self._categories[category]._signalprocs: - if not process in self._processes: - self._processes[process]=[] - sigprc.append(process) - if not category in self._processes[process]: - self._processes[process].append(category) - - #Calculates number for process (signal: <= 0, bkg: > 0) - for n,sig in enumerate(sigprc): - self._process_index_[sig]=(1+n-len(sigprc)) - print n - - for n,bkg in enumerate(bkgprc): - self._process_index_[bkg]=(1+n) - print n - - print self._process_index_ - print self._processes + + def get_signal_processes(self): + #Overwriting for more than 1 category, only working when same processes for all categories + for category in self._categories: + sigprc=sorted(self._categories[category]._signalprocs) + return sigprc + + def get_bkg_processes(self): + #Overwriting for more than 1 category, only working when same processes for all categories + for category in self._categories: + bkgprc=sorted(self._categories[category]._bkgprocs) + return bkgprc + def create_process_block(self): """ @@ -430,40 +408,53 @@ def create_process_block(self): (...) - place holder for more categories THIS IS NOT PART OF THE DATACARD! """ - self.collect_processes() - indices=[] - for process in self._process_index_: - indices.append(self._process_index_[process]) - indices.sort() + signalprocs=self.get_signal_processes() + bkgprocs=self.get_bkg_processes() - lines = [] - if len(self._processes) != 0: - bins = "bin".ljust(15) - process = "process".ljust(15) - process_index = "process".ljust(15) - rate = "rate".ljust(15) - for category in self._categories: - for index in indices: - for proc,ind in self._process_index_.items(): - if index==ind and category in self._processes[proc]: - bins += "\t\t%s" % category.ljust(15) - process += "\t\t%s" % proc.ljust(15 -) process_index += "\t\t%s" % index.ljust(15) - #rate += "\t\t%s" % self._categories[category][proc]._eventcount() - lines.append(bins) - lines.append(process) - lines.append(process_index) - lines.append(rate) - - else: - print "WARNING: Did not find any processes!" - - return "\n".join(lines) + #Leaves one bin empty, necessary for systematics block + bins = "bin".ljust(50) + process = "process".ljust(50) + process_index = "process".ljust(50) + rate = "rate".ljust(50) + + for category in self._categories: + #Signal processes first + for number,signal_process in enumerate(signalprocs): + + bins += "%s" % category.ljust(25) + process += "%s" % signal_process.ljust(25) + + index=1+number-len(self._categories[category]._signalprocs) + process_index += "%s" % str(index).ljust(25) + + rate += "%s" % str(self._categories[category]._signalprocs[signal_process].eventcount).ljust(25) + #Same with background processes + for number,bkg_process in enumerate(bkgprocs): + bins += "%s" % category.ljust(25) + process += "%s" % bkg_process.ljust(25) + + index=1+number + process_index += "%s" % str(index).ljust(25) + rate += "%s" % str(self._categories[category]._bkgprocs[bkg_process].eventcount).ljust(25) + + lines.append(bins) + lines.append(process) + lines.append(process_index) + lines.append(rate) + return "\n".join(lines) + - + def systematic_value(self,systematic,process,category): + if category in self._systematics[systematic]._dic: + if process in self._systematics[systematic]._dic[category]: + return self._systematics[systematic]._dic[category][process] + else: + return "-" + else: + return "-" def create_systematics_block(self): """ @@ -480,7 +471,21 @@ def create_systematics_block(self): IMPORTANT: The order of process has to be the same as in the process block """ - pass + signalprocs=self.get_signal_processes() + bkgprocs=self.get_bkg_processes() + + lines = [] + for systematic in self._systematics: + temp="%s" % systematic.ljust(25) + temp+="%s" % str(self._systematics[systematic].type).ljust(25) + for category in self._categories: + #Signal processes first + for number,signal_process in enumerate(signalprocs): + temp += "%s" % str(self.systematic_value(systematic=systematic, process=signal_process, category=category)).ljust(25) + for number,bkg_process in enumerate(bkgprocs): + temp += "%s" % str(self.systematic_value(systematic=systematic, process=bkg_process, category=category)).ljust(25) + lines.append(temp) + return "\n".join(lines) def create_datacard_text(self): #create datacard header @@ -489,8 +494,9 @@ def create_datacard_text(self): self.update_systematics(self._categories[cat]) content.append(self.create_header()) #create block with keywords for systematic variations - - #create observation block + content.append(self.create_process_block()) + content.append(self.create_systematics_block()) + #create observation blockr return self._block_separator.join(content) def write_datacard(self): diff --git a/src/systematicObject.py b/src/systematicObject.py index 7207b49..c0ac88b 100644 --- a/src/systematicObject.py +++ b/src/systematicObject.py @@ -9,8 +9,10 @@ spath.append(thisdir) from processObject import processObject +from valueConventions import valueConventions class systematicObject(object): + _value_rules = valueConventions() def init_variables(self): self._name = "" self._type = "" @@ -59,8 +61,7 @@ def add_process_raw(self, category_name, process_name, value): if not category_name in self._dic: self._dic[category_name] = {} if not process_name in self._dic[category_name]: - if True: - #if self.helper.is_good_systval(value): + if self._value_rules.is_good_systval(value): self._dic[category_name][process_name] = {} self._dic[category_name][process_name] = value @@ -87,8 +88,7 @@ def add_process_by_dictionary(self, dic, category): def add_process(self, process, correlation = "-"): if isinstance(process, processObject): - cor = correlation - #self.get_correlation(process = process) why?? + cor = self.get_correlation(process = process) if cor == "-": if correlation == "-": correlation = process.get_uncertainty_value(systname = self._name) @@ -109,7 +109,8 @@ def get_correlation_raw(self, category_name, process_name): return str(self._dic[category_name][process_name]) else: return "-" - + else: + return "-" def get_correlation(self, process): category = process.category process_name = process.name @@ -119,8 +120,7 @@ def get_correlation(self, process): def set_correlation_raw(self, category_name, process_name, value): if category_name in self._dic: if process_name in self._dic[category_name]: - if True: - #if self.helper.is_good_systval(value): + if self._value_rules.is_good_systval(value): self._dic[category_name][process_name] = value else: print "Could not add process: input must be dictionary!" @@ -136,8 +136,7 @@ def set_correlation(self, process, value = "-"): if value == "-": value = process.get_uncertainty_value(systname = self._name) if process_name in self._dic[category]: - if True: - #if self.helper.is_good_systval(value): + if self._value_rules.is_good_systval(value): self._dic[category][process_name] = value else: s = "ERROR: Process '%s' is not known yet to" % process_name diff --git a/src/test.py b/src/test.py index 03da382..83e3a8d 100644 --- a/src/test.py +++ b/src/test.py @@ -11,3 +11,7 @@ for name,syst in d._systematics.items(): print syst print d.create_process_block() +print d.create_systematics_block() +d.replace_files = True +d.outputpath="test.txt" +d.write_datacard() \ No newline at end of file From 5fad275f0f1c9f40d401088fff7c51803e948778 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 19:52:35 +0100 Subject: [PATCH 32/73] added observation block --- src/datacardMaker.py | 28 +++++++++++++++++++++++++++- src/test.py | 3 ++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 122fe6a..23d5bfc 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -375,7 +375,33 @@ def create_observation_block(self): (...) - place holder for more categories THIS IS NOT PART OF THE DATACARD! """ - pass + lines = [] + bins = "bin".ljust(25) + observation = "observation".ljust(25) + for category in self._categories: + obs=0 + value=True + + bins += "%s" % category.ljust(25) + for process in self._categories[category]: + eventcount=self._categories[category][process].eventcount + if not eventcount==-1 and isinstance(eventcount, float): + obs+=eventcount + else: + value=False + if value: + observation += "%s" % obs.ljust(25) + else: + observation += "-1.0e+00".ljust(25) + + lines.append(bins) + lines.append(observation) + + return "\n".join(lines) + + + + diff --git a/src/test.py b/src/test.py index 83e3a8d..d9d80ba 100644 --- a/src/test.py +++ b/src/test.py @@ -14,4 +14,5 @@ print d.create_systematics_block() d.replace_files = True d.outputpath="test.txt" -d.write_datacard() \ No newline at end of file +d.write_datacard() +print d.create_observation_block() \ No newline at end of file From d45792516559df02ddd608b6426bfa13f2b45312 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 19:53:39 +0100 Subject: [PATCH 33/73] added observation block --- src/datacardMaker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 23d5bfc..aa80842 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -390,13 +390,13 @@ def create_observation_block(self): else: value=False if value: - observation += "%s" % obs.ljust(25) + observation += "%s" % str(obs).ljust(25) else: observation += "-1.0e+00".ljust(25) lines.append(bins) lines.append(observation) - + return "\n".join(lines) From b1bf4d5624d31fe356e613c473cf90abb9ba6cc3 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:01:35 +0100 Subject: [PATCH 34/73] working on keyword block --- src/datacardMaker.py | 16 ++++++++-------- src/test.py | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index aa80842..20b486c 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -278,7 +278,6 @@ def update_systematics(self, category): - def create_header(self): """ Create header for the datacard. The header has the following form: @@ -357,9 +356,11 @@ def create_keyword_block(self): and/or '$PROCESS' """ lines = [] - for cat in self._categories: - if any(cat in syst._dict and syst.type == "shape" for syst in self._systematics): - lines += self.write_keyword_block_lines(category = self._categories[cat]) + for category in self._categories: + lines.append(self.write_keyword_block_lines(category=self._categories[category])) + # for category in self._categories: + # if any(category in syst._dict and syst.type == "shape" for syst in self._systematics): + # lines += self.write_keyword_block_lines(category = self._categories[cat]) return "\n".join(lines) def create_observation_block(self): @@ -400,10 +401,6 @@ def create_observation_block(self): return "\n".join(lines) - - - - def get_signal_processes(self): #Overwriting for more than 1 category, only working when same processes for all categories @@ -519,10 +516,13 @@ def create_datacard_text(self): for cat in self._categories: self.update_systematics(self._categories[cat]) content.append(self.create_header()) + #create keyword block + #create block with keywords for systematic variations content.append(self.create_process_block()) content.append(self.create_systematics_block()) #create observation blockr + content.append(self.create_observation_block()) return self._block_separator.join(content) def write_datacard(self): diff --git a/src/test.py b/src/test.py index d9d80ba..7f3cb11 100644 --- a/src/test.py +++ b/src/test.py @@ -15,4 +15,5 @@ d.replace_files = True d.outputpath="test.txt" d.write_datacard() -print d.create_observation_block() \ No newline at end of file +print d.create_observation_block() +print d.create_keyword_block() \ No newline at end of file From 0fa361303cce5114e65226fd420d7061da2959e3 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:04:45 +0100 Subject: [PATCH 35/73] working on keyword block --- src/datacardMaker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 20b486c..c49630a 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -334,6 +334,7 @@ def write_keyword_block_lines(self, category): category_name = category.name, file = category.default_file, nominal_key = category.generic_key_nominal_hist, syst_key = category.generic_key_systematic_hist) + return line @@ -357,7 +358,7 @@ def create_keyword_block(self): """ lines = [] for category in self._categories: - lines.append(self.write_keyword_block_lines(category=self._categories[category])) + lines+=self.write_keyword_block_lines(category=self._categories[category])) # for category in self._categories: # if any(category in syst._dict and syst.type == "shape" for syst in self._systematics): # lines += self.write_keyword_block_lines(category = self._categories[cat]) From ffd350a4bc34f68a2e63be4eec0fd10fafa5c072 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:07:01 +0100 Subject: [PATCH 36/73] working on keyword block --- src/datacardMaker.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index c49630a..0478e3b 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -335,6 +335,7 @@ def write_keyword_block_lines(self, category): nominal_key = category.generic_key_nominal_hist, syst_key = category.generic_key_systematic_hist) return line + print line @@ -358,11 +359,12 @@ def create_keyword_block(self): """ lines = [] for category in self._categories: - lines+=self.write_keyword_block_lines(category=self._categories[category])) + lines+=self.write_keyword_block_lines(category=self._categories[category]) + print lines # for category in self._categories: # if any(category in syst._dict and syst.type == "shape" for syst in self._systematics): # lines += self.write_keyword_block_lines(category = self._categories[cat]) - return "\n".join(lines) + #return "\n".join(lines) def create_observation_block(self): """ From 0c27a4d123d5418b94daa677ddf6993bbc2cd841 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:07:22 +0100 Subject: [PATCH 37/73] merge --- src/datacardMaker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index c49630a..3dedb75 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -358,7 +358,7 @@ def create_keyword_block(self): """ lines = [] for category in self._categories: - lines+=self.write_keyword_block_lines(category=self._categories[category])) + lines+=self.write_keyword_block_lines(category=self._categories[category]) # for category in self._categories: # if any(category in syst._dict and syst.type == "shape" for syst in self._systematics): # lines += self.write_keyword_block_lines(category = self._categories[cat]) From c8303727d55c9c9089ff52d1a3af70f39bfa1ad8 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:16:25 +0100 Subject: [PATCH 38/73] working on keyword block --- src/datacardMaker.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 0478e3b..560c181 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -319,13 +319,14 @@ def create_header(self): def write_keyword_block_line(self, process_name, category_name, file, nominal_key, syst_key): - s = ["shapes"] - s.append(process_name) - s.append(category_name) - s.append(file) - s.append(nominal_key) - s.append(syst_key) - + s = "shapes".ljust(25) + s.+= "%s" % (process_name).ljust(25) + s.+= "%s" %(category_name).ljust(25) + s.+= "%s" %(file).ljust(25) + s.+= "%s" %(nominal_key).ljust(25) + s.+= "%s" %(syst_key).ljust(25) + + print s return s def write_keyword_block_lines(self, category): @@ -337,7 +338,12 @@ def write_keyword_block_lines(self, category): return line print line - + def write_keyword(self,category): + lines=[] + for process in self._categories[category]: + lines.append(self.write_keyword_block_line(process_name=process,category_name=category,file=self._categories[category][process].file, + nominal_key=self._categories[category][process].nominal_hist_name,syst_key=self._categories[category][process].systematic_hist_name)) + def create_keyword_block(self): @@ -361,10 +367,7 @@ def create_keyword_block(self): for category in self._categories: lines+=self.write_keyword_block_lines(category=self._categories[category]) print lines - # for category in self._categories: - # if any(category in syst._dict and syst.type == "shape" for syst in self._systematics): - # lines += self.write_keyword_block_lines(category = self._categories[cat]) - #return "\n".join(lines) + def create_observation_block(self): """ From 41c2b9d0049e8aa2d062a3547444f453d913083e Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:17:00 +0100 Subject: [PATCH 39/73] working on keyword block --- src/datacardMaker.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 560c181..7aa0de2 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -320,11 +320,11 @@ def create_header(self): def write_keyword_block_line(self, process_name, category_name, file, nominal_key, syst_key): s = "shapes".ljust(25) - s.+= "%s" % (process_name).ljust(25) - s.+= "%s" %(category_name).ljust(25) - s.+= "%s" %(file).ljust(25) - s.+= "%s" %(nominal_key).ljust(25) - s.+= "%s" %(syst_key).ljust(25) + s+= "%s" % (process_name).ljust(25) + s+= "%s" %(category_name).ljust(25) + s+= "%s" %(file).ljust(25) + s+= "%s" %(nominal_key).ljust(25) + s+= "%s" %(syst_key).ljust(25) print s return s From 256d2b4b1362bd36735abf327db31f975d59407a Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:20:04 +0100 Subject: [PATCH 40/73] working on keyword block --- src/datacardMaker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 7aa0de2..0d745bf 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -365,7 +365,7 @@ def create_keyword_block(self): """ lines = [] for category in self._categories: - lines+=self.write_keyword_block_lines(category=self._categories[category]) + lines+=self.write_keyword(category=self._categories[category]) print lines From 0980f00ab0a8ec3aa35b9742572706a0697ea18c Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:21:57 +0100 Subject: [PATCH 41/73] working on keyword block --- src/datacardMaker.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 0d745bf..71cc0a8 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -340,9 +340,9 @@ def write_keyword_block_lines(self, category): def write_keyword(self,category): lines=[] - for process in self._categories[category]: - lines.append(self.write_keyword_block_line(process_name=process,category_name=category,file=self._categories[category][process].file, - nominal_key=self._categories[category][process].nominal_hist_name,syst_key=self._categories[category][process].systematic_hist_name)) + for process in category: + lines.append(self.write_keyword_block_line(process_name=process,category_name=category.name,file=category[process].file, + nominal_key=category[process].nominal_hist_name,syst_key=category[process].systematic_hist_name)) From 76f3fd6000f473adf258bb99f8bb84bf8b9da92e Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:25:16 +0100 Subject: [PATCH 42/73] working on keyword block --- src/datacardMaker.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 71cc0a8..1eea048 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -343,6 +343,7 @@ def write_keyword(self,category): for process in category: lines.append(self.write_keyword_block_line(process_name=process,category_name=category.name,file=category[process].file, nominal_key=category[process].nominal_hist_name,syst_key=category[process].systematic_hist_name)) + return lines @@ -365,8 +366,8 @@ def create_keyword_block(self): """ lines = [] for category in self._categories: - lines+=self.write_keyword(category=self._categories[category]) - print lines + lines.append(self.write_keyword(category=self._categories[category])) + print lines def create_observation_block(self): From 03b89092a8aad39d5c216b0d7a688436577ef004 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:30:05 +0100 Subject: [PATCH 43/73] working on keyword block --- src/datacardMaker.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 1eea048..6741a67 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -322,9 +322,9 @@ def write_keyword_block_line(self, process_name, category_name, file, s = "shapes".ljust(25) s+= "%s" % (process_name).ljust(25) s+= "%s" %(category_name).ljust(25) - s+= "%s" %(file).ljust(25) - s+= "%s" %(nominal_key).ljust(25) - s+= "%s" %(syst_key).ljust(25) + s+= "%s" %(file).ljust(100) + s+= "%s" %(nominal_key).ljust(100) + s+= "%s" %(syst_key).ljust(100) print s return s @@ -366,7 +366,7 @@ def create_keyword_block(self): """ lines = [] for category in self._categories: - lines.append(self.write_keyword(category=self._categories[category])) + lines.extend(self.write_keyword(category=self._categories[category])) print lines From 76c6e0e434bd7018fc9790ac5c31f5671ddd433b Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:31:11 +0100 Subject: [PATCH 44/73] working on keyword block --- src/datacardMaker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 6741a67..c9dc5f3 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -366,7 +366,7 @@ def create_keyword_block(self): """ lines = [] for category in self._categories: - lines.extend(self.write_keyword(category=self._categories[category])) + lines=(self.write_keyword(category=self._categories[category])) print lines From 45de5731ce86b87b72dc361364e70c03b475cda1 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:32:10 +0100 Subject: [PATCH 45/73] working on keyword block --- src/datacardMaker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index c9dc5f3..e77c80a 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -367,7 +367,7 @@ def create_keyword_block(self): lines = [] for category in self._categories: lines=(self.write_keyword(category=self._categories[category])) - print lines + return "\n".join(lines) def create_observation_block(self): From ed415b3a312ae19c40f12b1ec79d321fc7d9fc3b Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:34:31 +0100 Subject: [PATCH 46/73] working on keyword block --- src/datacardMaker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index e77c80a..df8d814 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -366,7 +366,8 @@ def create_keyword_block(self): """ lines = [] for category in self._categories: - lines=(self.write_keyword(category=self._categories[category])) + lines=write_keyword_block_lines(category=category) + lines.extend(self.write_keyword(category=self._categories[category])) return "\n".join(lines) From 14edabe2bb34d7b33a51a520214af8e44cb164c4 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:35:59 +0100 Subject: [PATCH 47/73] working on keyword block --- src/datacardMaker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index df8d814..242e7b4 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -335,8 +335,8 @@ def write_keyword_block_lines(self, category): category_name = category.name, file = category.default_file, nominal_key = category.generic_key_nominal_hist, syst_key = category.generic_key_systematic_hist) - return line print line + return line def write_keyword(self,category): lines=[] @@ -366,7 +366,7 @@ def create_keyword_block(self): """ lines = [] for category in self._categories: - lines=write_keyword_block_lines(category=category) + lines=self.write_keyword_block_lines(category=category) lines.extend(self.write_keyword(category=self._categories[category])) return "\n".join(lines) From 752bb01e69923ebdc62c4bb56480710df08305d5 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:36:40 +0100 Subject: [PATCH 48/73] working on keyword block --- src/datacardMaker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 242e7b4..19250aa 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -366,7 +366,7 @@ def create_keyword_block(self): """ lines = [] for category in self._categories: - lines=self.write_keyword_block_lines(category=category) + lines=self.write_keyword_block_lines(category=self._categories[category]) lines.extend(self.write_keyword(category=self._categories[category])) return "\n".join(lines) From 034007964649c90b12ef037733969659ab33c7a6 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:38:38 +0100 Subject: [PATCH 49/73] working on keyword block --- src/datacardMaker.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 19250aa..491f496 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -331,11 +331,12 @@ def write_keyword_block_line(self, process_name, category_name, file, def write_keyword_block_lines(self, category): lines = [] - line = self.write_keyword_block_line(process_name = "*", - category_name = category.name, file = category.default_file, - nominal_key = category.generic_key_nominal_hist, - syst_key = category.generic_key_systematic_hist) - print line + #line = self.write_keyword_block_line(process_name = "*", + # category_name = category.name, file = category.default_file, + # nominal_key = category.generic_key_nominal_hist, + # syst_key = category.generic_key_systematic_hist) + + print category.name return line def write_keyword(self,category): From fef80fa243829cd604d636f0accd6a093b4ab90e Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:39:19 +0100 Subject: [PATCH 50/73] working on keyword block --- src/datacardMaker.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 491f496..72baca2 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -337,7 +337,6 @@ def write_keyword_block_lines(self, category): # syst_key = category.generic_key_systematic_hist) print category.name - return line def write_keyword(self,category): lines=[] From 0434f8024e79647b2682961b4893d0dc52fbaf11 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:40:40 +0100 Subject: [PATCH 51/73] working on keyword block --- src/datacardMaker.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 72baca2..c95df9d 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -339,11 +339,11 @@ def write_keyword_block_lines(self, category): print category.name def write_keyword(self,category): - lines=[] + line=[] for process in category: - lines.append(self.write_keyword_block_line(process_name=process,category_name=category.name,file=category[process].file, + line.append(self.write_keyword_block_line(process_name=process,category_name=category.name,file=category[process].file, nominal_key=category[process].nominal_hist_name,syst_key=category[process].systematic_hist_name)) - return lines + return line @@ -366,8 +366,8 @@ def create_keyword_block(self): """ lines = [] for category in self._categories: - lines=self.write_keyword_block_lines(category=self._categories[category]) - lines.extend(self.write_keyword(category=self._categories[category])) + #lines=self.write_keyword_block_lines(category=self._categories[category]) + lines=(self.write_keyword(category=self._categories[category])) return "\n".join(lines) From 3d1c630c94a90709bc6f3f4cfd16604316f172bf Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:41:29 +0100 Subject: [PATCH 52/73] working on keyword block --- src/datacardMaker.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index c95df9d..b7b0b4b 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -366,6 +366,7 @@ def create_keyword_block(self): """ lines = [] for category in self._categories: + self.write_keyword_block_lines(category=self._categories[category]) #lines=self.write_keyword_block_lines(category=self._categories[category]) lines=(self.write_keyword(category=self._categories[category])) return "\n".join(lines) From bf01ea14859e90f5aedd7478ec8bed328124a703 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:43:07 +0100 Subject: [PATCH 53/73] working on keyword block --- src/datacardMaker.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index b7b0b4b..a8aa5f9 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -337,6 +337,9 @@ def write_keyword_block_lines(self, category): # syst_key = category.generic_key_systematic_hist) print category.name + print category.default_file + print category.generic_key_nominal_hist + print category.generic_key_systematic_hist def write_keyword(self,category): line=[] From cbe0844354aa6982c1d9d73b5f352f6519e9bc24 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:50:45 +0100 Subject: [PATCH 54/73] working on keyword block --- src/datacardMaker.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index a8aa5f9..e2b16c1 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -181,13 +181,15 @@ def load_from_file(self, pathToDatacard): process_name = shape[1] if category_name=="*": for category in self._categories: - self.add_generic_keys(category_name=category, - list_of_shapelines=shape) + if process_name=="*": + self.add_generic_keys(category_name=category, + list_of_shapelines=shape) self.manage_processes(category_name=category, process_name=process_name,list_of_shapelines=shape) elif category_name in self._categories: - self.add_generic_keys(category_name=category, - list_of_shapelines=shape) + if process_name=="*": + self.add_generic_keys(category_name=category, + list_of_shapelines=shape) self.manage_processes(category_name = category_name, process_name=process_name,list_of_shapelines=shape) @@ -371,7 +373,7 @@ def create_keyword_block(self): for category in self._categories: self.write_keyword_block_lines(category=self._categories[category]) #lines=self.write_keyword_block_lines(category=self._categories[category]) - lines=(self.write_keyword(category=self._categories[category])) + lines+=(self.write_keyword(category=self._categories[category])) return "\n".join(lines) From abefaefb4193f821e26887209041aac7d471cb90 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:52:54 +0100 Subject: [PATCH 55/73] working on keyword block --- src/datacardMaker.py | 3 +++ src/test.py | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index e2b16c1..357e32b 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -214,6 +214,9 @@ def load_from_file(self, pathToDatacard): def add_generic_keys(self,category_name,list_of_shapelines): #adds generic key for categories self._categories[category_name].default_file = list_of_shapelines[3] + print list_of_shapelines[3] + print list_of_shapelines[4] + print list_of_shapelines[5] self._categories[category_name].generic_key_nominal_hist = list_of_shapelines[4] self._categories[category_name].generic_key_systematic_hist = list_of_shapelines[5] diff --git a/src/test.py b/src/test.py index 7f3cb11..5761c67 100644 --- a/src/test.py +++ b/src/test.py @@ -10,10 +10,10 @@ d.update_systematics(category) for name,syst in d._systematics.items(): print syst -print d.create_process_block() -print d.create_systematics_block() +#print d.create_process_block() +#print d.create_systematics_block() d.replace_files = True d.outputpath="test.txt" -d.write_datacard() +#d.write_datacard() print d.create_observation_block() print d.create_keyword_block() \ No newline at end of file From 30fafc3a7f2894c7d79075b762e0b20733c2d07d Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 20:57:38 +0100 Subject: [PATCH 56/73] working on keyword block --- src/datacardMaker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 357e32b..62d33f3 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -187,8 +187,8 @@ def load_from_file(self, pathToDatacard): self.manage_processes(category_name=category, process_name=process_name,list_of_shapelines=shape) elif category_name in self._categories: - if process_name=="*": - self.add_generic_keys(category_name=category, + if process_name == "*": + self.add_generic_keys(category_name=category_name, list_of_shapelines=shape) self.manage_processes(category_name = category_name, process_name=process_name,list_of_shapelines=shape) From 62305c61dce3e4044b8de7b4b465de6aab70f92a Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 21:02:28 +0100 Subject: [PATCH 57/73] working on keyword block --- src/datacardMaker.py | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 62d33f3..797b038 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -214,9 +214,6 @@ def load_from_file(self, pathToDatacard): def add_generic_keys(self,category_name,list_of_shapelines): #adds generic key for categories self._categories[category_name].default_file = list_of_shapelines[3] - print list_of_shapelines[3] - print list_of_shapelines[4] - print list_of_shapelines[5] self._categories[category_name].generic_key_nominal_hist = list_of_shapelines[4] self._categories[category_name].generic_key_systematic_hist = list_of_shapelines[5] @@ -334,19 +331,16 @@ def write_keyword_block_line(self, process_name, category_name, file, print s return s - def write_keyword_block_lines(self, category): - lines = [] - #line = self.write_keyword_block_line(process_name = "*", - # category_name = category.name, file = category.default_file, - # nominal_key = category.generic_key_nominal_hist, - # syst_key = category.generic_key_systematic_hist) + def write_keyword_generic_lines(self, category): + + line = self.write_keyword_block_line(process_name = "*", + category_name = category.name, file = category.default_file, + nominal_key = category.generic_key_nominal_hist, + syst_key = category.generic_key_systematic_hist) - print category.name - print category.default_file - print category.generic_key_nominal_hist - print category.generic_key_systematic_hist + return line - def write_keyword(self,category): + def write_keyword_process_lines(self,category): line=[] for process in category: line.append(self.write_keyword_block_line(process_name=process,category_name=category.name,file=category[process].file, @@ -374,9 +368,8 @@ def create_keyword_block(self): """ lines = [] for category in self._categories: - self.write_keyword_block_lines(category=self._categories[category]) - #lines=self.write_keyword_block_lines(category=self._categories[category]) - lines+=(self.write_keyword(category=self._categories[category])) + lines+=self.write_keyword_generic_lines(category=self._categories[category]) + lines+=(self.write_keyword_process_lines(category=self._categories[category])) return "\n".join(lines) From da334b314f6cc3770292cdf3e8d201ab0879d089 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 21:04:08 +0100 Subject: [PATCH 58/73] working on keyword block --- src/datacardMaker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 797b038..b3178ff 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -369,7 +369,7 @@ def create_keyword_block(self): lines = [] for category in self._categories: lines+=self.write_keyword_generic_lines(category=self._categories[category]) - lines+=(self.write_keyword_process_lines(category=self._categories[category])) + lines.extend(self.write_keyword_process_lines(category=self._categories[category])) return "\n".join(lines) From 0e5964eb1d4ba2469a8e2df2805931bf079d67fc Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 21:05:12 +0100 Subject: [PATCH 59/73] working on keyword block --- src/datacardMaker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index b3178ff..c845c45 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -368,8 +368,8 @@ def create_keyword_block(self): """ lines = [] for category in self._categories: - lines+=self.write_keyword_generic_lines(category=self._categories[category]) - lines.extend(self.write_keyword_process_lines(category=self._categories[category])) + print self.write_keyword_generic_lines(category=self._categories[category]) + lines +=(self.write_keyword_process_lines(category=self._categories[category])) return "\n".join(lines) From 1842ff525be8158a80200224c100208a2564d708 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 21:10:43 +0100 Subject: [PATCH 60/73] working on keyword block --- src/datacardMaker.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index c845c45..29da075 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -342,9 +342,14 @@ def write_keyword_generic_lines(self, category): def write_keyword_process_lines(self,category): line=[] + line.append(self.write_keyword_generic_lines(category=category)) for process in category: - line.append(self.write_keyword_block_line(process_name=process,category_name=category.name,file=category[process].file, - nominal_key=category[process].nominal_hist_name,syst_key=category[process].systematic_hist_name)) + file=category[process].file + nominal_hist_name=category[process].nominal_hist_name + systematic_hist_name=ategory[process].systematic_hist_name + if not file=="" and not nominal_hist_name=="" and not systematic_hist_name=="": + line.append(self.write_keyword_block_line(process_name=process,category_name=category.name,file=file, + nominal_key=nominal_hist_name,syst_key=systematic_hist_name)) return line @@ -368,7 +373,6 @@ def create_keyword_block(self): """ lines = [] for category in self._categories: - print self.write_keyword_generic_lines(category=self._categories[category]) lines +=(self.write_keyword_process_lines(category=self._categories[category])) return "\n".join(lines) From b6363fe7484b85ef1fd056aad03cbc955e4651d6 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 21:11:41 +0100 Subject: [PATCH 61/73] working on keyword block --- src/datacardMaker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 29da075..2570743 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -346,7 +346,7 @@ def write_keyword_process_lines(self,category): for process in category: file=category[process].file nominal_hist_name=category[process].nominal_hist_name - systematic_hist_name=ategory[process].systematic_hist_name + systematic_hist_name=category[process].systematic_hist_name if not file=="" and not nominal_hist_name=="" and not systematic_hist_name=="": line.append(self.write_keyword_block_line(process_name=process,category_name=category.name,file=file, nominal_key=nominal_hist_name,syst_key=systematic_hist_name)) From c1a302bf64e6d13605b274846ca177e7b8487eba Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 21:15:14 +0100 Subject: [PATCH 62/73] working on keyword block --- src/datacardMaker.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 2570743..82bd325 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -332,6 +332,9 @@ def write_keyword_block_line(self, process_name, category_name, file, return s def write_keyword_generic_lines(self, category): + """ + Adds the generic key + """ line = self.write_keyword_block_line(process_name = "*", category_name = category.name, file = category.default_file, @@ -342,7 +345,9 @@ def write_keyword_generic_lines(self, category): def write_keyword_process_lines(self,category): line=[] + #adds the generic key line.append(self.write_keyword_generic_lines(category=category)) + #adds the process keys (need to add: only add process key if it doesnt match the generic key) for process in category: file=category[process].file nominal_hist_name=category[process].nominal_hist_name @@ -531,12 +536,13 @@ def create_datacard_text(self): self.update_systematics(self._categories[cat]) content.append(self.create_header()) #create keyword block - + content.append(self.create_keyword_block()) + #create observation block + content.append(self.create_observation_block()) #create block with keywords for systematic variations content.append(self.create_process_block()) content.append(self.create_systematics_block()) - #create observation blockr - content.append(self.create_observation_block()) + return self._block_separator.join(content) def write_datacard(self): From 30c675d67069b6752631d46752210d2d438f7860 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 21:15:49 +0100 Subject: [PATCH 63/73] working on keyword block --- src/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test.py b/src/test.py index 5761c67..b10394d 100644 --- a/src/test.py +++ b/src/test.py @@ -14,6 +14,6 @@ #print d.create_systematics_block() d.replace_files = True d.outputpath="test.txt" -#d.write_datacard() +d.write_datacard() print d.create_observation_block() print d.create_keyword_block() \ No newline at end of file From db00775dac387d9a7d98905fb6ae037dd1a01018 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 21:19:06 +0100 Subject: [PATCH 64/73] working on keyword block --- src/test.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/test.py b/src/test.py index b10394d..a7a7380 100644 --- a/src/test.py +++ b/src/test.py @@ -16,4 +16,10 @@ d.outputpath="test.txt" d.write_datacard() print d.create_observation_block() -print d.create_keyword_block() \ No newline at end of file +print d.create_keyword_block() + +c=datacardMaker() +c.load_from_file("testdatacard.txt") +c.replace_files=True +c.outputpath="text2.txt" +c.write_datacard() \ No newline at end of file From 98283c5f9139b0dac7147c95d2f331c97983f01f Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 21:25:32 +0100 Subject: [PATCH 65/73] added keyword block and observation block for datacard --- src/datacardMaker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 82bd325..55898e3 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -405,7 +405,7 @@ def create_observation_block(self): bins += "%s" % category.ljust(25) for process in self._categories[category]: eventcount=self._categories[category][process].eventcount - if not eventcount==-1 and isinstance(eventcount, float): + if not eventcount==-1 and not eventcount =="-1" and isinstance(eventcount, float): obs+=eventcount else: value=False From fe364d80b36d007541ab80f5545244262fad460b Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 21:31:14 +0100 Subject: [PATCH 66/73] trying to fix header --- src/datacardMaker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 55898e3..e5fa66d 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -296,7 +296,8 @@ def create_header(self): ncats = "*" nprocs = "*" nsysts = "*" - if self._hardcode_numbers: + if True: + #if self._hardcode_numbers: #get number of categories if len(self._categories) != 0: ncats = len(self._categories) From 898ded15c76e87fb4c5ed496e5f6937a14beca0b Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 21:44:53 +0100 Subject: [PATCH 67/73] fixing shapeline error --- src/datacardMaker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index e5fa66d..65f140d 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -185,7 +185,7 @@ def load_from_file(self, pathToDatacard): self.add_generic_keys(category_name=category, list_of_shapelines=shape) self.manage_processes(category_name=category, - process_name=process_name,list_of_shapelines=shape) + process_name=process_name,list_of_shapelines=shape) elif category_name in self._categories: if process_name == "*": self.add_generic_keys(category_name=category_name, From d76ac103a8959fa46eef9c28d7355e109e2a9093 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Tue, 30 Oct 2018 21:56:16 +0100 Subject: [PATCH 68/73] shapelines not working as they should right now --- src/datacardMaker.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 65f140d..a5ca68c 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -223,11 +223,9 @@ def manage_processes(self, category_name, process_name, list_of_shapelines): Search process corresponding to shapeline and add the explicit key names """ if process_name == "*": - #for process in self._categories[category_name].processes(): for process in self._categories[category_name]: self.add_keys(category_name=category_name, process_name=process,list_of_shapelines=list_of_shapelines) - #elif process_name in self._categories[category_name].processes(): elif process_name in self._categories[category_name]: self.add_keys(category_name=category_name, process_name=process_name,list_of_shapelines=list_of_shapelines) From a5622f63bfcdc9017cbe58baeccf429e0b11cb37 Mon Sep 17 00:00:00 2001 From: Philip Daniel Keicher Date: Tue, 6 Nov 2018 17:15:44 +0100 Subject: [PATCH 69/73] small bug fixes, datacard maker is able to produce datacards now --- base/fileHandler.py | 2 ++ base/identificationLogic.py | 2 +- src/categoryObject.py | 6 +++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/base/fileHandler.py b/base/fileHandler.py index 3c53197..0c56960 100644 --- a/base/fileHandler.py +++ b/base/fileHandler.py @@ -94,6 +94,8 @@ def load_histogram(self, histname): else: print ("WARNING: histogram '%s' does not exist in '%s'" % (histname, file)) + else: + print "ERROR: In fileHandler - file is not set!" return None def histogram_exists(self, histname): diff --git a/base/identificationLogic.py b/base/identificationLogic.py index be01e49..874a75e 100644 --- a/base/identificationLogic.py +++ b/base/identificationLogic.py @@ -86,7 +86,7 @@ def generic_systematics_key(self, key): s+= " is not part of new systematics key '%s'!" % key print s if not self._chIden in key: - s = "WARNING: channel identification keyword '%s'" % self._procIden + s = "WARNING: channel identification keyword '%s'" % self._chIden s+= " is not part of new systematics key '%s'!" % key print s if not self._systIden in key: diff --git a/src/categoryObject.py b/src/categoryObject.py index 8fb3984..517a2d6 100644 --- a/src/categoryObject.py +++ b/src/categoryObject.py @@ -142,7 +142,7 @@ def default_file(self, filepath): else: print "ERROR: File '%s' does not exist!" % filepath - def add_signal_process( self, name, rootfile = None, histoname = None, + def add_signal_process_raw( self, name, rootfile = None, histoname = None, systkey = None): """ add a signal process. Calls function add_process with @@ -160,7 +160,7 @@ def add_signal_process( self, name, rootfile = None, histoname = None, rootfile = rootfile, histoname = histoname, systkey = systkey) - def add_background_process( self, name, rootfile = None, + def add_background_process_raw( self, name, rootfile = None, histoname = None, systkey = None): """ add a background process. Calls function add_process with @@ -234,7 +234,7 @@ def add_process(self, dic, process): def add_process_raw(self, dic, name, rootfile, histoname, systkey): temp = processObject(processName = name, pathToRootfile = rootfile, nominal_hist_key = histoname, systematic_hist_key = systkey, - categoryname = self._name) + categoryName = self._name) self.add_process(dic = dic, process = temp) def is_compatible_with_default(self, process): From 4a1b01bb2288e452801466f331fcb8d035cfcae2 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Fri, 9 Nov 2018 14:40:33 +0100 Subject: [PATCH 70/73] fixing minor bugs --- README.md | 1 - base/identificationLogic.py | 10 +++++-- src/categoryObject.py | 19 ++++++++----- src/datacardMaker.py | 25 ++++++++++++----- src/processObject.py | 14 +++++----- src/test2.py | 55 ------------------------------------- 6 files changed, 44 insertions(+), 80 deletions(-) delete mode 100644 src/test2.py diff --git a/README.md b/README.md index 0e2083f..8f58e4f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # datacardMaker repo for object-oriented datacard maker -will use scripts in [Philip's base repository](https://gitlab.cern.ch/pkeicher/base) (better make it stand-alone?) Current ideas for structure diff --git a/base/identificationLogic.py b/base/identificationLogic.py index 874a75e..f82513f 100644 --- a/base/identificationLogic.py +++ b/base/identificationLogic.py @@ -22,7 +22,7 @@ class identificationLogic(object): , e.g. $SYSTEMATIC (member variable: _systIden) """ - _debug = 99 + _debug = 1 _allowed_dependencies = ["process", "channel"] def init_variables(self): """ @@ -137,7 +137,11 @@ def insert_channel(self, channel_name, base_key): return "" if not channel_name == "" and not channel_name is None: if self._chIden in base_key: - return base_key.replace(self._chIden, channel_name) + s = base_key.replace(self._chIden, channel_name) + if self._debug >= 30: + print "-"*130, "\nDEBUG: key after channel insertion:", s + print "-"*130 + return s return base_key def insert_process(self, process_name, base_key): @@ -292,6 +296,6 @@ def is_allowed_key(self, key): def is_nongeneric_key(self,key): if not key is None and not key == "": - if not self._procIden in key: + if not (self._procIden in key or self._chIden in key): return True return False \ No newline at end of file diff --git a/src/categoryObject.py b/src/categoryObject.py index 517a2d6..bf528ae 100644 --- a/src/categoryObject.py +++ b/src/categoryObject.py @@ -8,7 +8,7 @@ from identificationLogic import identificationLogic class categoryObject(object): - _debug = True + _debug = 200 def init_variables(self): self._name = "categoryName" self._data_obs = None @@ -113,6 +113,7 @@ def signal_processes(self): @property def background_processes(self): return self._bkgprocs + @property def generic_key_nominal_hist(self): @@ -221,6 +222,9 @@ def add_background_process( self, process): def add_process(self, dic, process): if isinstance(process, processObject): + if self._debug >= 99: + print "DEBUG: adding process", process.name + print process if self._default_file is None: self.default_file = process.file dic[process.name] = process @@ -254,12 +258,12 @@ def is_compatible_with_default(self, process): def __getitem__(self, process): - if process in self._bkgprocs: - return self._bkgprocs[process] - elif process in self._signalprocs: - return self._signalprocs[process] - else: - print "ERROR: Process not in Category!" + if process in self._bkgprocs: + return self._bkgprocs[process] + elif process in self._signalprocs: + return self._signalprocs[process] + else: + print "ERROR: Process not in Category!" def __iter__(self): all_processes={} @@ -279,6 +283,7 @@ def __contains__(self, processName): def __str__(self): s = [] s.append("Category Name:\t%s" % self._name) + s.append("Default source file:\t%s" % self._default_file) s.append("List of signal processes:") for sig in self._signalprocs: s.append("\t%s" % self._signalprocs[sig]) diff --git a/src/datacardMaker.py b/src/datacardMaker.py index a5ca68c..4890626 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -147,6 +147,9 @@ def load_from_file(self, pathToDatacard): for category in bins: self._categories[category] = categoryObject() self._categories[category].name = category + if self._debug >= 50: + print "initialized category", category + print self._categories[category] #Create processObjects for each process in a category @@ -217,6 +220,18 @@ def add_generic_keys(self,category_name,list_of_shapelines): self._categories[category_name].generic_key_nominal_hist = list_of_shapelines[4] self._categories[category_name].generic_key_systematic_hist = list_of_shapelines[5] + def add_keys(self, category_name,process_name,list_of_shapelines): + """ + add filename, nominal key and systematic key to processObject + """ + process = self._categories[category_name][process_name] + # self._categories[category_name][process_name].file = list_of_shapelines[3] + # self._categories[category_name][process_name].nominal_hist_name = list_of_shapelines[4] + # self._categories[category_name][process_name].systematic_hist_name = list_of_shapelines[5] + process.file = list_of_shapelines[3] + process.nominal_hist_name = list_of_shapelines[4] + process.systematic_hist_name = list_of_shapelines[5] + def manage_processes(self, category_name, process_name, list_of_shapelines): """ @@ -229,15 +244,11 @@ def manage_processes(self, category_name, process_name, list_of_shapelines): elif process_name in self._categories[category_name]: self.add_keys(category_name=category_name, process_name=process_name,list_of_shapelines=list_of_shapelines) + else: + print "could not find process %s in category %s" % (process_name, category_name) - def add_keys(self, category_name,process_name,list_of_shapelines): - """ - add filename, nominal key and systematic key to processObject - """ - self._categories[category_name][process_name].file = list_of_shapelines[3] - self._categories[category_name][process_name].nominal_hist_name = list_of_shapelines[4] - self._categories[category_name][process_name].systematic_hist_name = list_of_shapelines[5] + def get_number_of_procs(self): """ diff --git a/src/processObject.py b/src/processObject.py index cbc79a8..d578071 100644 --- a/src/processObject.py +++ b/src/processObject.py @@ -10,22 +10,20 @@ from fileHandler import fileHandler class processObject(object): - _helper = helperClass() _id_logic = identificationLogic() identificationLogic.belongs_to = "process" _value_rules = valueConventions() _file_handler = fileHandler() - _helper._debug = 99 def init_variables(self): self._name = "" - # self._rootfile = "" + self._file = "" self._categoryname = "" self._nominalhistname = "" self._systkey = "" self._eventcount = -1 self._uncertainties = {} - self._debug = True + self._debug = 100 self._calculate_yield = False @@ -46,7 +44,7 @@ def __init__( self, processName = None, pathToRootfile = None, # self._eventcount = self.calculate_yield() if not systematic_hist_key is None: self._systkey = systematic_hist_key - if self._debug: + if self._debug >= 3: s = "initialized process with name '%s'" % self._name s += " in category '%s'" % self._categoryname print s @@ -160,7 +158,8 @@ def nominal_hist_name(self, hname): self._nominalhistname = hname self._eventcount = self._file_handler.get_integral(hname) else: - hist = self._id_logic.build_nominal_histo_name(process_name = self._name, base_key = hname) + hist = self._id_logic.build_nominal_histo_name(process_name = self._name, base_key = hname, + channel_name = self._categoryname) if self._file_handler.histogram_exists(histname = hist): self._nominalhistname = hist self._eventcount = self._file_handler.get_integral(hist) @@ -176,7 +175,8 @@ def systematic_hist_name(self, key): if self._id_logic.is_nongeneric_key(key): self._systkey = key else: - systkey = self._id_logic.build_nominal_histo_name(process_name = self._name, base_key = key) + systkey = self._id_logic.build_nominal_histo_name(process_name = self._name, base_key = key, + channel_name = self._categoryname) self._systkey = systkey @property diff --git a/src/test2.py b/src/test2.py deleted file mode 100644 index a0fc49c..0000000 --- a/src/test2.py +++ /dev/null @@ -1,55 +0,0 @@ -class cO: - - - def __init__(self,category): - self.__name= category - self.dic = {} - - def add_process(self,process): - if isinstance(process, pO): - name=process.getName() - print name - self.dic[name]= process - - def __getitem__(self, process): - for name,proc in self.dic.items(): - if name == process: - return proc - - def printdict(self): - for name,process in self.dic.items(): - print name, "corresponds to", process.getName() - - def __str__(self): - s = [] - s.append("Category Name:\t%s" % self.__category) - #for name,process in self.__dic.items(): - #s.append("Process Name:\t%s" % name) - return "\n".join(s) - -class pO: - def __init__(self,process): - self.__process = process - self.__number = None - - - def getName(self): - return self.__process - def setNumber(self,num): - self.__number=num - def getNumber(self): - return self.__number - - -c1=cO("ch1") -p1=pO("process1") -p1.setNumber(1) -p2=pO("process2") -p2.setNumber(2) -print p1.getNumber() -print p1.getName() -print p2.getName() -c1.add_process(p1) -c1.add_process(p2) -c1.printdict() -print c1["process1"].getNumber() From d9e821dd334b7d6cd73613e6066c23a674cb421e Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Fri, 9 Nov 2018 15:23:09 +0100 Subject: [PATCH 71/73] changed readmes --- README.md | 23 ++++++++++------------- base/helperClass.py | 17 ----------------- base/valueConventions.py | 10 +++++++--- src/datacardMaker.py | 28 +++++++++++++--------------- src/processObject.py | 1 - 5 files changed, 30 insertions(+), 49 deletions(-) delete mode 100644 base/helperClass.py diff --git a/README.md b/README.md index 8f58e4f..7a99549 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,14 @@ # datacardMaker repo for object-oriented datacard maker +Writes Datacards -Current ideas for structure - -- datacardMaker.py: Wrapper to automatically create data cards from category/process/systematic objects - - should be able to know how many categories are to be written (-> j parameter) - - should be able to know how many systematics are involved (-> k parameter) -- processObject.py: object for process (e.g. ttH_hbb) - - process should know what systematics it has - - process should know the correlation to a given systematic -- systematicObject.py: object for nuisance parameters (e.g. bgnorm_ttbarPlusBBbar) - - systematic should know which processes it affects - - systematic should know the correlation to a given process -- categoryObject.py: object for categories - - a category has multiple processes with a number of systematic uncertainties \ No newline at end of file +Current structure: +- src/datacardMaker.py: Wrapper to automatically create data cards from category/process/systematic objects +- src/categoryObject.py: Object for categories that contains processes +- src/processObject.py: Object for process (e.g. ttH_hbb), contains uncertainties corresponding to the process +- src/systematicObject.py: object for nuisance parameters (e.g. bgnorm_ttbarPlusBBbar) +- base/fileHandler.py: fileHandler class manages all access to files required for the analysis +- base/identificationLogic: identificationLogic class is meant to handle all logic concerning + the key handling for the different categories required in datacards +- base/valueConventions.py: checks if value is allowed for systematic \ No newline at end of file diff --git a/base/helperClass.py b/base/helperClass.py deleted file mode 100644 index 1ee3111..0000000 --- a/base/helperClass.py +++ /dev/null @@ -1,17 +0,0 @@ -from ROOT import TFile, TH1 -from os import path as opath -from sys import path as spath -baserepodir = "/nfs/dust/cms/user/pkeicher/projects/base/classes" -if not baserepodir in spath: - spath.append(baserepodir) -from helperClass_base import helperClass_base -class helperClass(helperClass_base): - def __init__(self): - super(helperClass, self).__init__() - - def isfloat(self, value): - try: - float(value) - return True - except ValueError: - return False \ No newline at end of file diff --git a/base/valueConventions.py b/base/valueConventions.py index c3e5aea..a9dff79 100644 --- a/base/valueConventions.py +++ b/base/valueConventions.py @@ -4,15 +4,19 @@ basedir = path.join(thisdir, "../base") if not basedir in spath: spath.append(basedir) -from helperClass import helperClass class valueConventions(object): """docstring for valueConventions""" - _helper = helperClass() def __init__(self): print "Initializing valueConventions" + def isfloat(self, value): + try: + float(value) + return True + except ValueError: + return False def is_good_systval(self, value): """ @@ -27,7 +31,7 @@ def is_good_systval(self, value): elif isinstance(value,str): totest = value.split("/") if len(totest) in [1,2]: - is_good = all(self._helper.isfloat(v) for v in totest) + is_good = all(self.isfloat(v) for v in totest) if not is_good: print "Given value not suitable for an uncertainty in a datacard!" return is_good \ No newline at end of file diff --git a/src/datacardMaker.py b/src/datacardMaker.py index 4890626..9a65fcb 100644 --- a/src/datacardMaker.py +++ b/src/datacardMaker.py @@ -185,15 +185,15 @@ def load_from_file(self, pathToDatacard): if category_name=="*": for category in self._categories: if process_name=="*": - self.add_generic_keys(category_name=category, + self.load_from_file_add_generic_keys(category_name=category, list_of_shapelines=shape) - self.manage_processes(category_name=category, + self.load_from_file_manage_processes(category_name=category, process_name=process_name,list_of_shapelines=shape) elif category_name in self._categories: if process_name == "*": - self.add_generic_keys(category_name=category_name, + self.load_from_file_add_generic_keys(category_name=category_name, list_of_shapelines=shape) - self.manage_processes(category_name = category_name, + self.load_from_file_manage_processes(category_name = category_name, process_name=process_name,list_of_shapelines=shape) @@ -214,35 +214,33 @@ def load_from_file(self, pathToDatacard): print "could not load %s: no such file" % pathToDatacard - def add_generic_keys(self,category_name,list_of_shapelines): + def load_from_file_add_generic_keys(self,category_name,list_of_shapelines): #adds generic key for categories - self._categories[category_name].default_file = list_of_shapelines[3] - self._categories[category_name].generic_key_nominal_hist = list_of_shapelines[4] - self._categories[category_name].generic_key_systematic_hist = list_of_shapelines[5] + category=self._categories[category_name] + category.default_file = list_of_shapelines[3] + category.generic_key_nominal_hist = list_of_shapelines[4] + category.generic_key_systematic_hist = list_of_shapelines[5] - def add_keys(self, category_name,process_name,list_of_shapelines): + def load_from_file_add_keys(self, category_name,process_name,list_of_shapelines): """ add filename, nominal key and systematic key to processObject """ process = self._categories[category_name][process_name] - # self._categories[category_name][process_name].file = list_of_shapelines[3] - # self._categories[category_name][process_name].nominal_hist_name = list_of_shapelines[4] - # self._categories[category_name][process_name].systematic_hist_name = list_of_shapelines[5] process.file = list_of_shapelines[3] process.nominal_hist_name = list_of_shapelines[4] process.systematic_hist_name = list_of_shapelines[5] - def manage_processes(self, category_name, process_name, list_of_shapelines): + def load_from_file_manage_processes(self, category_name, process_name, list_of_shapelines): """ Search process corresponding to shapeline and add the explicit key names """ if process_name == "*": for process in self._categories[category_name]: - self.add_keys(category_name=category_name, + self.load_from_file_add_keys(category_name=category_name, process_name=process,list_of_shapelines=list_of_shapelines) elif process_name in self._categories[category_name]: - self.add_keys(category_name=category_name, + self.load_from_file_add_keys(category_name=category_name, process_name=process_name,list_of_shapelines=list_of_shapelines) else: print "could not find process %s in category %s" % (process_name, category_name) diff --git a/src/processObject.py b/src/processObject.py index d578071..b596580 100644 --- a/src/processObject.py +++ b/src/processObject.py @@ -4,7 +4,6 @@ basedir = path.join(thisdir, "../base") if not basedir in spath: spath.append(basedir) -from helperClass import helperClass from identificationLogic import identificationLogic from valueConventions import valueConventions from fileHandler import fileHandler From 81acb189dbb94fd368f2d76b55337669fa8a7475 Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Fri, 9 Nov 2018 15:44:38 +0100 Subject: [PATCH 72/73] removed files --- src/combinedtestdatacard.txt | 50 ------------------- src/test.py | 25 ---------- src/testdatacard.txt | 31 ------------ test.py | 90 ----------------------------------- test.root | Bin 4233 -> 0 bytes 5 files changed, 196 deletions(-) delete mode 100644 src/combinedtestdatacard.txt delete mode 100644 src/test.py delete mode 100644 src/testdatacard.txt delete mode 100644 test.py delete mode 100644 test.root diff --git a/src/combinedtestdatacard.txt b/src/combinedtestdatacard.txt deleted file mode 100644 index 26096f9..0000000 --- a/src/combinedtestdatacard.txt +++ /dev/null @@ -1,50 +0,0 @@ -Combination of limits_BDT_datacard_ljets_j5_tge4_DeepCSV_hdecay.txt limits_BDT_datacard_ljets_jge6_t3_DeepCSV_hdecay.txt limits_BDT_datacard_ljets_jge6_tge4_DeepCSV_hdecay.txt -imax 3 number of bins -jmax 5 number of processes minus 1 -kmax 9 number of nuisance parameters ----------------------------------------------------------------------------------------------------------------------------------- -shapes * ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root $PROCESS_finaldiscr_ljets_j5_tge4_DeepCSV $PROCESS_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC -shapes ttH_hbb ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hbb_finaldiscr_ljets_j5_tge4_DeepCSV ttH_hbb_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC -shapes ttH_hcc ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hcc_finaldiscr_ljets_j5_tge4_DeepCSV ttH_hcc_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC -shapes ttH_hgg ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hgg_finaldiscr_ljets_j5_tge4_DeepCSV ttH_hgg_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC -shapes ttH_hgluglu ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hgluglu_finaldiscr_ljets_j5_tge4_DeepCSV ttH_hgluglu_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC -shapes ttH_htt ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_htt_finaldiscr_ljets_j5_tge4_DeepCSV ttH_htt_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC -shapes ttH_hww ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hww_finaldiscr_ljets_j5_tge4_DeepCSV ttH_hww_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC -shapes ttH_hzg ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hzg_finaldiscr_ljets_j5_tge4_DeepCSV ttH_hzg_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC -shapes ttH_hzz ch1 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hzz_finaldiscr_ljets_j5_tge4_DeepCSV ttH_hzz_finaldiscr_ljets_j5_tge4_DeepCSV_$SYSTEMATIC -shapes * ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root $PROCESS_finaldiscr_ljets_jge6_t3_DeepCSV $PROCESS_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC -shapes ttH_hbb ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hbb_finaldiscr_ljets_jge6_t3_DeepCSV ttH_hbb_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC -shapes ttH_hcc ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hcc_finaldiscr_ljets_jge6_t3_DeepCSV ttH_hcc_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC -shapes ttH_hgg ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hgg_finaldiscr_ljets_jge6_t3_DeepCSV ttH_hgg_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC -shapes ttH_hgluglu ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hgluglu_finaldiscr_ljets_jge6_t3_DeepCSV ttH_hgluglu_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC -shapes ttH_htt ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_htt_finaldiscr_ljets_jge6_t3_DeepCSV ttH_htt_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC -shapes ttH_hww ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hww_finaldiscr_ljets_jge6_t3_DeepCSV ttH_hww_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC -shapes ttH_hzg ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hzg_finaldiscr_ljets_jge6_t3_DeepCSV ttH_hzg_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC -shapes ttH_hzz ch2 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hzz_finaldiscr_ljets_jge6_t3_DeepCSV ttH_hzz_finaldiscr_ljets_jge6_t3_DeepCSV_$SYSTEMATIC -shapes * ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root $PROCESS_finaldiscr_ljets_jge6_tge4_DeepCSV $PROCESS_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC -shapes ttH_hbb ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hbb_finaldiscr_ljets_jge6_tge4_DeepCSV ttH_hbb_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC -shapes ttH_hcc ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hcc_finaldiscr_ljets_jge6_tge4_DeepCSV ttH_hcc_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC -shapes ttH_hgg ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hgg_finaldiscr_ljets_jge6_tge4_DeepCSV ttH_hgg_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC -shapes ttH_hgluglu ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hgluglu_finaldiscr_ljets_jge6_tge4_DeepCSV ttH_hgluglu_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC -shapes ttH_htt ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_htt_finaldiscr_ljets_jge6_tge4_DeepCSV ttH_htt_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC -shapes ttH_hww ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hww_finaldiscr_ljets_jge6_tge4_DeepCSV ttH_hww_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC -shapes ttH_hzg ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hzg_finaldiscr_ljets_jge6_tge4_DeepCSV ttH_hzg_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC -shapes ttH_hzz ch3 /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hzz_finaldiscr_ljets_jge6_tge4_DeepCSV ttH_hzz_finaldiscr_ljets_jge6_tge4_DeepCSV_$SYSTEMATIC ----------------------------------------------------------------------------------------------------------------------------------- -bin ch1 ch2 ch3 -observation 1170.778059 21239.611394 2306.661628 ----------------------------------------------------------------------------------------------------------------------------------- -bin ch1 ch1 ch1 ch1 ch1 ch1 ch2 ch2 ch2 ch2 ch2 ch2 ch3 ch3 ch3 ch3 ch3 ch3 -process ttH_hbb ttbarPlusB ttbarPlusCCbar ttbarPlus2B ttbarOther ttbarPlusBBbar ttH_hbb ttbarPlusB ttbarPlusCCbar ttbarPlus2B ttbarOther ttbarPlusBBbar ttH_hbb ttbarPlusB ttbarPlusCCbar ttbarPlus2B ttbarOther ttbarPlusBBbar -process 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 -rate -1.0e+00 168.479 197.186 81.369 276.773 386.849 -1.0e+00 2620.04 4777.01 1515.92 9638.36 2414.54 -1.0e+00 250.838 351.411 157.59 289.246 1123.92 ----------------------------------------------------------------------------------------------------------------------------------- -QCDscale_ttH lnN 0.908/1.058 - - - - - 0.908/1.058 - - - - - 0.908/1.058 - - - - - -QCDscale_ttbar lnN - 0.96/1.02 0.96/1.02 0.96/1.02 0.96/1.02 0.96/1.02 - 0.96/1.02 0.96/1.02 0.96/1.02 0.96/1.02 0.96/1.02 - 0.96/1.02 0.96/1.02 0.96/1.02 0.96/1.02 0.96/1.02 -bgnorm_ttbarPlus2B lnN - - - 1.5 - - - - - 1.5 - - - - - 1.5 - - -bgnorm_ttbarPlusB lnN - 1.5 - - - - - 1.5 - - - - - 1.5 - - - - -bgnorm_ttbarPlusBBbar lnN - - - - - 1.5 - - - - - 1.5 - - - - - 1.5 -bgnorm_ttbarPlusCCbar lnN - - 1.5 - - - - - 1.5 - - - - - 1.5 - - - -lumi_13TeV_2016 lnN 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 1.025 -pdf_gg lnN - 1.04 1.04 1.04 1.04 1.04 - 1.04 1.04 1.04 1.04 1.04 - 1.04 1.04 1.04 1.04 1.04 -pdf_gg_ttH lnN 1.036 - - - - - 1.036 - - - - - 1.036 - - - - - diff --git a/src/test.py b/src/test.py deleted file mode 100644 index a7a7380..0000000 --- a/src/test.py +++ /dev/null @@ -1,25 +0,0 @@ -from datacardMaker import datacardMaker -import operator - -d=datacardMaker() -d.load_from_file("combinedtestdatacard.txt") -for name,category in d._categories.items(): - print name - print category -for name,category in d._categories.items(): - d.update_systematics(category) -for name,syst in d._systematics.items(): - print syst -#print d.create_process_block() -#print d.create_systematics_block() -d.replace_files = True -d.outputpath="test.txt" -d.write_datacard() -print d.create_observation_block() -print d.create_keyword_block() - -c=datacardMaker() -c.load_from_file("testdatacard.txt") -c.replace_files=True -c.outputpath="text2.txt" -c.write_datacard() \ No newline at end of file diff --git a/src/testdatacard.txt b/src/testdatacard.txt deleted file mode 100644 index 69993b7..0000000 --- a/src/testdatacard.txt +++ /dev/null @@ -1,31 +0,0 @@ -imax * # number of channels -jmax * # number of backgrounds -kmax * # number of nuisance parameters ---------------- -bin ljets_j5_tge4_DeepCSV -observation 1170.778059 ---------------- -shapes * * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root $PROCESS_finaldiscr_$CHANNEL $PROCESS_finaldiscr_$CHANNEL_$SYSTEMATIC -shapes ttH_hbb * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hbb_finaldiscr_$CHANNEL ttH_hbb_finaldiscr_$CHANNEL_$SYSTEMATIC -shapes ttH_hcc * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hcc_finaldiscr_$CHANNEL ttH_hcc_finaldiscr_$CHANNEL_$SYSTEMATIC -shapes ttH_hww * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hww_finaldiscr_$CHANNEL ttH_hww_finaldiscr_$CHANNEL_$SYSTEMATIC -shapes ttH_hzz * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hzz_finaldiscr_$CHANNEL ttH_hzz_finaldiscr_$CHANNEL_$SYSTEMATIC -shapes ttH_htt * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_htt_finaldiscr_$CHANNEL ttH_htt_finaldiscr_$CHANNEL_$SYSTEMATIC -shapes ttH_hgg * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hgg_finaldiscr_$CHANNEL ttH_hgg_finaldiscr_$CHANNEL_$SYSTEMATIC -shapes ttH_hgluglu * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hgluglu_finaldiscr_$CHANNEL ttH_hgluglu_finaldiscr_$CHANNEL_$SYSTEMATIC -shapes ttH_hzg * /nfs/dust/cms/user/lreuter/limits/common/output_limitInput.root ttH_hzg_finaldiscr_$CHANNEL ttH_hzg_finaldiscr_$CHANNEL_$SYSTEMATIC ---------------- -bin ljets_j5_tge4_DeepCSV ljets_j5_tge4_DeepCSV ljets_j5_tge4_DeepCSV ljets_j5_tge4_DeepCSV ljets_j5_tge4_DeepCSV ljets_j5_tge4_DeepCSV -process ttH_hbb ttbarOther ttbarPlusB ttbarPlus2B ttbarPlusBBbar ttbarPlusCCbar -process 0 1 2 3 4 5 -rate -1 276.773 168.479 81.369 386.849 197.186 ---------------- -lumi_13TeV_2016 lnN 1.025 1.025 1.025 1.025 1.025 1.025 -QCDscale_ttH lnN 0.908/1.058 - - - - - -QCDscale_ttbar lnN - 0.96/1.02 0.96/1.02 0.96/1.02 0.96/1.02 0.96/1.02 -pdf_gg_ttH lnN 1.036 - - - - - -pdf_gg lnN - 1.04 1.04 1.04 1.04 1.04 -bgnorm_ttbarPlusB lnN - - 1.5 - - - -bgnorm_ttbarPlus2B lnN - - - 1.5 - - -bgnorm_ttbarPlusBBbar lnN - - - - 1.5 - -bgnorm_ttbarPlusCCbar lnN - - - - - 1.5 diff --git a/test.py b/test.py deleted file mode 100644 index c9d0f36..0000000 --- a/test.py +++ /dev/null @@ -1,90 +0,0 @@ -import sys -from os import path - -directory = path.abspath(path.realpath(path.dirname(__file__))) -srcdir = path.join(directory, "src") -if not srcdir in sys.path: - sys.path.append(srcdir) - -basedir = path.join(directory, "base") -if not basedir in sys.path: - sys.path.append(basedir) -from helperClass import helperClass - -from processObject import processObject -from categoryObject import categoryObject -from datacardMaker import datacardMaker -from test_cls import testClass - -def datacard_tests(): - testproc = processObject() - - testproc.name = "nominal" - testproc.category = "jge6_tge4" - print "setting rootfile" - testproc.file = "test.root" - testproc.nominal_hist_name = "nominal" - testproc.systematic_hist_name = "nominal_$SYSTEMATIC" - testproc.add_uncertainty("lumi", "lnN", "1.025") - testproc.add_uncertainty("pdf_gg", "lnN", "0.98/1.02") - testproc.add_uncertainty(syst = "JES", typ = "shape", value = 1.0) - #not working yet - testproc.add_uncertainty(syst = 5, typ = "shape", value = 1.0) - testproc.add_uncertainty(syst = "JES", typ = "shape", value = "five") - testproc.add_uncertainty(syst = "JES2", typ = "shape", value = "five") - print testproc - - category = categoryObject() - category.name = "jge6_tge4" - category.add_signal_process(testproc) - print category - - dm = datacardMaker() - dm.add_category(category) - dm.outputpath = "test.txt" - dm.hardcode_numbers = True - dm.replace_files = True - dm.write_datacard() - -def init_tests(): - a = testClass() - - b = testClass() - c = testClass() - - a.x = 7 - print a.x - print b.x - print c.x - - a.helper = helperClass() - - print a.helper - print b.helper - print c.helper - - testClass.helper = helperClass() - - print a.helper - print b.helper - print c.helper - - print "deleting helper of a" - del a.helper - - print a.helper - print b.helper - print c.helper - - a.helper = b.helper - - print a.helper - print b.helper - print c.helper - -def main(): - datacard_tests() - # init_tests() - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/test.root b/test.root deleted file mode 100644 index 1cea7f38a2260f1a5d4263b1ddc68fc7bd83c88f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4233 zcmb7|cT`i$_Qy{WI-!OVItG*$Ab>%-AXNn}AV^VqlNula6r=W5kRCdS2!y6m z!j-1Q`+oPm_5S(2ch=hL%sOZG%wC__d(C_&2!p`^z(_j)0B!)l zo85Y?1v{%X)L0|0Xd0BX3tSk;%ov69Jqp`tRjeUtk4zj}gz|6tmAlhs46^npdXS%(1qtb|y5Yiy3UE|m(3UkQHz6$q(o>QlFjuo1L?q`b zHN-v&6Vyf&Fpxeo-^mq2oM&RA7vvA9qz5A7RDMv`kLLt|7ZrgcAa{%aH1#(EmVY8} zyrh4{D&QEz8~}Lz6T~I=6A*(ZAO`;ik@FV_*ycxmK|2bNv2=n%3RFJ@&x(HK0JgoZ z&0{vujt9})6okVS`Bd;FsB~v&HFpB{!){SsOSlaj4nM-5Zmi~YT(19kceLRktU|;M zAhKkPx&Zt>M}QF+AFCMGW1!~LT%B&&PsyjVbvgp&pxG7Z*M7Ge?NW4_Zx3LPwnKtS z9~7QFTP-wpiiy=Y8FZO)gD5RNhR5sN!*j!HwSi1|jQb`9;$5g<_S^QUGu>BhqJxr+oF_%RE@cBP3e7;aV(OS^BkrhxN?sl5`mS|*QRs;mXu%&`RxhV#KFFK1a!fj+YWfYTi;wwUU%bwZ!K7d_7vbY;K`{odIj|NQd&XAIVqxjpNuKXR}#!^o? zged!ny0E^LeN>d-PN7765Cxjy1JSBOI^5D5p}>F&N`a0L--k$}CT9$^ni5S5cKXu= zENpy=$bjS=`RHug$sQFg2mSy7m}fPEPAZ&QG-9bIMW=nt=D6+wH(E<$FDwpv1sCM* z?B^bI(cc5ZsfP)6@x3ML>*ep`?nZrKaa2~9nSoR>YB9aGv-cIW=GIalnojUrPzxMW zF2$`pSr<=P&$wVQohsB@b;_jh{fM~V_`t@{+xr7UDoW%!AxV1zebrY2LOM=z0naPU zA#ZshMpW+>!07M;%&8%Isste{V|Fm~G_+S~#ysR*kM)IB;lKR ziI$>gW7%#zmz73F60h1_LiEqEfZqpeH5Ryg2pS>;mi!P#Ll#6D^wan-~V^us0G@`$V z#AZhbIrp0%M_VGu_-pn(dsuzJCC{#`!!fUwYBgKCKpQm&-)GKl(;k%2!P?tXmEub( zjzWqs`RzQ$RvcJ!@^JtDaD;NW*vH;a7lPC;lP?CnV$S8z$_h!>b9kEHkU85-XDZjE zs~C+dC1pKkATJZ9ULwgc4f6+wa1mpHug_R!?`)bxl3}y%EY>_NSn#DemNnL?AQi?%DHBt;+EG|l0WyD?u_j3PE1U=N%F$#ul9$yUA?*{ znP5_Vea@EE_D=kUN+SkxS;o1fVc*?E+c;joB)1D@3l32L4TryW`=~?c=;^@l{!EPT z5Z`+B;Cwn^ZZ#h|56iu|i}UVHeWt6Y9^6|QF{3JUZllb{GsA`vShTH#v7?T<$YM;?PN&{uNgTj%_%7qk3H4HU(_-?3J&&x5+en1wea~FJ83T^8CEVNx^uuD^iAYQrE z#kcVsu0B1dXKbl z`hIf~3mJ*S$4TwVPQ80Qt8_*oNVK2h&OFbk&ol(xM|mA=ZYWp#etqaxr-h4zL@~qf zfgOr;jjrK%zC}iwPgb@4D&|}Ubr%~V{zNnFF82QB;%zLj(^Z@BM@qaKl<+xSA-Qx% zTeM;^e2gOuRD2npQbG!-LVL}(+%@s#gZMA}RNVVeQPk4$E)e=u! z8o{m$e4EwCZA&v@tzzL1n?Qu6K?OU+U2daF3y6tw%M!(E9NSf9CCgzpOl`7Nm-&|k zspW``arFqS-Nc3{RpZ59uhPc8MvamDVw^` zd*Sxg-f!jOG4(o`umKoJ#2{9yJe&J4fgTyap*G}qO<~Dt3h{~4RcdNWh`{mXH{-Aw z1#w|(IA?0;-OjO&C!~@nPx@7CjIr7eG_vTMLVn7g{Npj+ryuaerAM56EnyBC4ZG{AH6fJ5L8r^z-T$}>-UAGLJ-WibSk%iP(o`wj29NCH@So29ft-2U6!~aQ zbFh^3E{9Lq(M7IlR@Oeu4!YC8jT?BJwDB~XcTM-fOI%fdYFCLv+&q_=_Nr8~fAhVd z?sIhH$#0X^*5>=(e~@; zK7kBa%GXfDNDRG*xvQw*FQ}Nex41M>CpNBRm@1@H?wKkQv9`}uE>B*0SC)aQB#D4E zeJt4Ayx`GA{YJx~*!14k??H?Vt&PEDXKliIQmw^RyXF`0@jIj8YF*rGy++~EDrG`Z z-8WlSg`&KJ$8`aZT>-nC$(Ix>#7@reSSr})_4B3f+=#M1H9+Ph;K%Xu66mj2rySHCby^3M`65=n!yIBoZj>sX~=Qe>7M|qd6P*fIU6dCs6GdutWxmw9)X#Hle{0g4^kX+dC=XFAQ z#BIwsYzMt{utbH5a(fl_Q_W@$87qPMF1B892;20)DBE&W{;DC_95mMMY;*Bg_~A>J zG~TSwdsQ^N9Fb6t$_`OAMPz(#K42p(AN|Z}p4pzD-{Wd8zbV~%TH{^bM=9pZVVp*7 z(S*TE((^Fo_~PxXj{%^iDIah&f&(Y^8#Oa8B z)$B`97`J_hhv1i0w&m2#3A5XO5>agZMV}Bl3$(Z@QnkOs*$E5Li8*-1y{+>YIL~Ms zOJm&EM(;Z%zXU|0`lxc8Dt)PEav<^KOnxGrsp9wKO#UCznfehzl{f#D?xptoAH?DR gDw?Q~pihEk`d82=w+d7t@t;9cH3oY^C^`f92Xwkc%K!iX From 5b9dbef24d70a8ed424c340eb8a93b04624af13c Mon Sep 17 00:00:00 2001 From: Lea Reuter Date: Fri, 9 Nov 2018 15:45:00 +0100 Subject: [PATCH 73/73] added readmes --- base/README.md | 7 +++++++ src/README.md | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 base/README.md create mode 100644 src/README.md diff --git a/base/README.md b/base/README.md new file mode 100644 index 0000000..ce754e7 --- /dev/null +++ b/base/README.md @@ -0,0 +1,7 @@ +# datacardMaker +repo for object-oriented datacard maker + +- base/fileHandler.py: fileHandler class manages all access to files required for the analysis +- base/identificationLogic: identificationLogic class is meant to handle all logic concerning + the key handling for the different categories required in datacards +- base/valueConventions.py: checks if value is allowed for systematic \ No newline at end of file diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..375c015 --- /dev/null +++ b/src/README.md @@ -0,0 +1,31 @@ +# datacardMaker src +classes for object-oriented datacard maker + +- datacardMaker.py: Wrapper to automatically create data cards from category/process/systematic objects + - a datacard maker object is able to have multiple categories with multiple processes with corresponding systematics + - uses category, process and systematic objects + - add category with add_category() + - write Datacard with write_datacard() + - read Datacard with load_from_file() +- categoryObject.py: object for categories + - a category has multiple processes with a number of systematic uncertainties + - categoryObject knows generic keys for files and histogram names for data and uncertainties + - categories have a list of signal and background processes + - uses processObject + - add process with add_signal_process() or add_background_process() + - set generic keys with + - default_file + - generic_key_nominal_hist + - generic_key_systematic_hist +- processObject.py: object for process (e.g. ttH_hbb) + - process knows its category: category_name + - process knows yield: get_yield + - process knows corresponding keys: + - file + - nominal_hist_name + - systematic_hist_name + - process knows what systematics it has + - process knows the correlation to a given systematic +- systematicObject.py: object for nuisance parameters (e.g. bgnorm_ttbarPlusBBbar) + - systematic knows which processes it affects + - systematic knows the correlation to a given process