From e6ed4f27adf48fc429c65beb5641be3c9f85808c Mon Sep 17 00:00:00 2001 From: Aliya Nigamova Date: Fri, 24 Mar 2023 10:29:26 +0100 Subject: [PATCH 01/12] first try, dummy global observable --- python/DatacardParser.py | 13 ++++++++++++- python/ModelTools.py | 11 +++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/python/DatacardParser.py b/python/DatacardParser.py index 338997788af..3ab9584691a 100644 --- a/python/DatacardParser.py +++ b/python/DatacardParser.py @@ -280,6 +280,14 @@ def addDatacardParserOptions(parser): default=None, help="Simplify MH dependent objects: 'fixed', 'pol' with N=0..4", ) + parser.add_option( + "--X-assignflatParamPrior", + dest="flatParamPrior", + default=False, + action="store_true", + help="Assign RooUniform pdf for flatParam NPs", + ) + def strip(l): @@ -522,6 +530,9 @@ def parseCard(file, options): continue elif pdf == "flatParam": ret.flatParamNuisances[lsyst] = True + if options.flatParamPrior: + ret.systs.append([lsyst, nofloat, pdf, args, []]) + ret.add_syst_id(lsyst) # for flat parametric uncertainties, code already does the right thing as long as they are non-constant RooRealVars linked to the model continue elif pdf == "extArg": @@ -669,7 +680,7 @@ def parseCard(file, options): syst2 = [] for lsyst, nofloat, pdf, args, errline in ret.systs: nonNullEntries = 0 - if pdf == "param" or pdf == "constr" or pdf == "discrete" or pdf == "rateParam": # this doesn't have an errline + if pdf == "param" or pdf == "constr" or pdf == "discrete" or pdf == "rateParam" or pdf=="flatParam": # this doesn't have an errline syst2.append((lsyst, nofloat, pdf, args, errline)) continue for b, p, s in ret.keyline: diff --git a/python/ModelTools.py b/python/ModelTools.py index 5a427d3664a..7de5cf19d26 100644 --- a/python/ModelTools.py +++ b/python/ModelTools.py @@ -137,7 +137,6 @@ def doObj(self, name, type, X, ignoreExisting=False): return self.factory_("%s::%s(%s)" % (type, name, X)) else: self.out.write("%s = %s(%s);\n" % (name, type, X)) - def addDiscrete(self, var): if self.options.removeMultiPdf: return @@ -522,6 +521,14 @@ def doNuisances(self): self.doObj("%s_Pdf" % n, "Uniform", "%s[-1,1]" % n) elif pdf == "unif": self.doObj("%s_Pdf" % n, "Uniform", "%s[%f,%f]" % (n, args[0], args[1])) + elif pdf == "flatParam" and self.options.flatParamPrior: + self.doObj( + "%s_Pdf" % n, + "Uniform", + "%s[-1,1]" % (n)) + self.doVar("%s_In[0,-1,1]" % (n)) + self.out.var("%s_In" % n).setConstant(True) + globalobs.append("%s_In" % n) elif pdf == "dFD" or pdf == "dFD2": dFD_min = -(1 + 8 / args[0]) dFD_max = +(1 + 8 / args[0]) @@ -878,7 +885,7 @@ def doExpectedEvents(self): continue if pdf == "constr": continue - if pdf == "rateParam": + if pdf == "rateParam" or pdf=="flatParam": continue if p not in errline[b]: continue From 151c33e07e35fa3844570faa110ef7c6a369f5f1 Mon Sep 17 00:00:00 2001 From: Aliya Nigamova Date: Fri, 24 Mar 2023 16:52:53 +0100 Subject: [PATCH 02/12] add RooFormulaVar with const=0 global observable --- python/DatacardParser.py | 2 +- python/ModelTools.py | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/python/DatacardParser.py b/python/DatacardParser.py index 3ab9584691a..746b6149164 100644 --- a/python/DatacardParser.py +++ b/python/DatacardParser.py @@ -281,7 +281,7 @@ def addDatacardParserOptions(parser): help="Simplify MH dependent objects: 'fixed', 'pol' with N=0..4", ) parser.add_option( - "--X-assignflatParamPrior", + "--X-assign-flatParam-prior", dest="flatParamPrior", default=False, action="store_true", diff --git a/python/ModelTools.py b/python/ModelTools.py index 7de5cf19d26..e87234e1947 100644 --- a/python/ModelTools.py +++ b/python/ModelTools.py @@ -522,11 +522,14 @@ def doNuisances(self): elif pdf == "unif": self.doObj("%s_Pdf" % n, "Uniform", "%s[%f,%f]" % (n, args[0], args[1])) elif pdf == "flatParam" and self.options.flatParamPrior: + self.doExp( + "%s_expr" % n, + "%s-%s_In" % (n,n), + "%s[-1,1],%s_In[0,-1,1]" % (n,n)) self.doObj( - "%s_Pdf" % n, - "Uniform", - "%s[-1,1]" % (n)) - self.doVar("%s_In[0,-1,1]" % (n)) + "%s_Pdf" % n, + "Uniform", + "%s_expr" % n) self.out.var("%s_In" % n).setConstant(True) globalobs.append("%s_In" % n) elif pdf == "dFD" or pdf == "dFD2": From bc025d3d559b8c74e532b20fa4890a9a7d70a752 Mon Sep 17 00:00:00 2001 From: Aliya Nigamova Date: Fri, 24 Mar 2023 17:18:32 +0100 Subject: [PATCH 03/12] fix ind, other small issues --- python/DatacardParser.py | 9 ++++----- python/ModelTools.py | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/python/DatacardParser.py b/python/DatacardParser.py index 746b6149164..e2f19cb13cf 100644 --- a/python/DatacardParser.py +++ b/python/DatacardParser.py @@ -289,7 +289,6 @@ def addDatacardParserOptions(parser): ) - def strip(l): """Strip comments and whitespace from end of line""" idx = l.find("#") @@ -530,10 +529,10 @@ def parseCard(file, options): continue elif pdf == "flatParam": ret.flatParamNuisances[lsyst] = True - if options.flatParamPrior: - ret.systs.append([lsyst, nofloat, pdf, args, []]) - ret.add_syst_id(lsyst) # for flat parametric uncertainties, code already does the right thing as long as they are non-constant RooRealVars linked to the model + if options.flatParamPrior: + ret.systs.append([lsyst, nofloat, pdf, args, []]) + ret.add_syst_id(lsyst) continue elif pdf == "extArg": # look for additional parameters in workspaces @@ -680,7 +679,7 @@ def parseCard(file, options): syst2 = [] for lsyst, nofloat, pdf, args, errline in ret.systs: nonNullEntries = 0 - if pdf == "param" or pdf == "constr" or pdf == "discrete" or pdf == "rateParam" or pdf=="flatParam": # this doesn't have an errline + if pdf == "param" or pdf == "constr" or pdf == "discrete" or pdf == "rateParam" or pdf == "flatParam": # this doesn't have an errline syst2.append((lsyst, nofloat, pdf, args, errline)) continue for b, p, s in ret.keyline: diff --git a/python/ModelTools.py b/python/ModelTools.py index e87234e1947..23378e0aefe 100644 --- a/python/ModelTools.py +++ b/python/ModelTools.py @@ -888,7 +888,7 @@ def doExpectedEvents(self): continue if pdf == "constr": continue - if pdf == "rateParam" or pdf=="flatParam": + if pdf == "rateParam": continue if p not in errline[b]: continue @@ -909,7 +909,7 @@ def doExpectedEvents(self): logNorms.append((errline[b][p], n)) elif pdf == "gmM": factors.append(n) - elif pdf == "trG" or pdf == "unif" or pdf == "dFD" or pdf == "dFD2": + elif pdf == "trG" or pdf == "unif" or pdf == "flatParam" or pdf == "dFD" or pdf == "dFD2": myname = "n_exp_shift_bin%s_proc_%s_%s" % (b, p, n) self.doObj(myname, ROOFIT_EXPR, "'1+%f*@0', %s" % (errline[b][p], n)) factors.append(myname) From 9d9b2b970dcfe39d3e8e514f81c7a6745693041a Mon Sep 17 00:00:00 2001 From: Nick Wardle Date: Thu, 6 Apr 2023 12:49:38 +0200 Subject: [PATCH 04/12] Allow for _norm cases This allows the user to use parameters like `bkg_norm` and the option to create the flatParamPrior will pick it up --- python/Datacard.py | 4 ++++ python/ModelTools.py | 40 +++++++++++++++++++++++++--------------- python/ShapeTools.py | 2 ++ 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/python/Datacard.py b/python/Datacard.py index a3d885dce8f..77d1ec47d3b 100644 --- a/python/Datacard.py +++ b/python/Datacard.py @@ -63,6 +63,9 @@ def __init__(self): self.groups = {} self.discretes = [] + # list of parameters called _norm in user input workspace + self.pdfnorms = {} + def print_structure(self): """ Print the contents of the -> should allow for direct text2workspace on python config @@ -140,6 +143,7 @@ def print_structure(self): print("DC.binParFlags = ", self.binParFlags, "#", type(self.binParFlags)) print("DC.groups = ", self.groups, "#", type(self.groups)) print("DC.discretes = ", self.discretes, "#", type(self.discretes)) + print("DC.pdfnorms = ", self.pdfnorms, "#", type(self.pdfnorms)) print( """ diff --git a/python/ModelTools.py b/python/ModelTools.py index 23378e0aefe..e37f3fa80fb 100644 --- a/python/ModelTools.py +++ b/python/ModelTools.py @@ -137,6 +137,7 @@ def doObj(self, name, type, X, ignoreExisting=False): return self.factory_("%s::%s(%s)" % (type, name, X)) else: self.out.write("%s = %s(%s);\n" % (name, type, X)) + def addDiscrete(self, var): if self.options.removeMultiPdf: return @@ -154,6 +155,13 @@ def __init__(self, datacard, options): self.extraNuisances = [] self.extraGlobalObservables = [] + def getSafeNormName(self, n): + # need to be careful in case user has _norm name and wants to auto-create flatPrior + if self.options.flatParamPrior: + if n in self.DC.pdfnorms.keys(): + return self.DC.pdfnorms[n] + return n + def setPhysics(self, physicsModel): self.physics = physicsModel self.physics.setModelBuilder(self) @@ -522,16 +530,16 @@ def doNuisances(self): elif pdf == "unif": self.doObj("%s_Pdf" % n, "Uniform", "%s[%f,%f]" % (n, args[0], args[1])) elif pdf == "flatParam" and self.options.flatParamPrior: + c_param_name = self.getSafeNormName(n) + v, x1, x2 = self.out.var(c_param_name).getVal(), self.out.var(c_param_name).getMin(), self.out.var(c_param_name).getMax() + if self.options.verbose > 2: + print("will create flat prior for parameter ", c_param_name, " in range [", x1, x2, "]") self.doExp( - "%s_expr" % n, - "%s-%s_In" % (n,n), - "%s[-1,1],%s_In[0,-1,1]" % (n,n)) - self.doObj( - "%s_Pdf" % n, - "Uniform", - "%s_expr" % n) - self.out.var("%s_In" % n).setConstant(True) - globalobs.append("%s_In" % n) + "%s_diff_expr" % c_param_name, "%s-%s_In" % (c_param_name, c_param_name), "%s,%s_In[%g,%g,%g]" % (c_param_name, c_param_name, v, x1, x2) + ) + self.doObj("%s_Pdf" % c_param_name, "Uniform", "%s_diff_expr" % c_param_name) + self.out.var("%s_In" % c_param_name).setConstant(True) + globalobs.append("%s_In" % c_param_name) elif pdf == "dFD" or pdf == "dFD2": dFD_min = -(1 + 8 / args[0]) dFD_max = +(1 + 8 / args[0]) @@ -788,9 +796,10 @@ def doNuisances(self): nuisPdfs = ROOT.RooArgList() nuisVars = ROOT.RooArgSet() for n, nf, p, a, e in self.DC.systs: + c_param_name = self.getSafeNormName(n) if p != "constr": - nuisVars.add(self.out.var(n)) - setNuisPdf.append(n) + nuisVars.add(self.out.var(c_param_name)) + setNuisPdf.append(c_param_name) setNuisPdf = set(setNuisPdf) for n in setNuisPdf: nuisPdfs.add(self.out.pdf(n + "_Pdf")) @@ -804,8 +813,8 @@ def doNuisances(self): self.out.defineSet("globalObservables", gobsVars) else: # doesn't work for too many nuisances :-( # avoid duplicating _Pdf in list - setNuisPdf = set([n for (n, nf, p, a, e) in self.DC.systs]) - self.doSet("nuisances", ",".join(["%s" % n for (n, nf, p, a, e) in self.DC.systs])) + setNuisPdf = set([self.getSafeNormName(n) for (n, nf, p, a, e) in self.DC.systs]) + self.doSet("nuisances", ",".join(["%s" % self.getSafeNormName(n) for (n, nf, p, a, e) in self.DC.systs])) self.doObj("nuisancePdf", "PROD", ",".join(["%s_Pdf" % n for n in setNuisPdf])) self.doSet("globalObservables", ",".join(globalobs)) @@ -888,7 +897,7 @@ def doExpectedEvents(self): continue if pdf == "constr": continue - if pdf == "rateParam": + if pdf == "rateParam" or pdf == "flatParam": continue if p not in errline[b]: continue @@ -909,7 +918,8 @@ def doExpectedEvents(self): logNorms.append((errline[b][p], n)) elif pdf == "gmM": factors.append(n) - elif pdf == "trG" or pdf == "unif" or pdf == "flatParam" or pdf == "dFD" or pdf == "dFD2": + # elif pdf == "trG" or pdf == "unif" or pdf == "flatParam" or pdf == "dFD" or pdf == "dFD2": + elif pdf == "trG" or pdf == "unif" or pdf == "dFD" or pdf == "dFD2": myname = "n_exp_shift_bin%s_proc_%s_%s" % (b, p, n) self.doObj(myname, ROOFIT_EXPR, "'1+%f*@0', %s" % (errline[b][p], n)) factors.append(myname) diff --git a/python/ShapeTools.py b/python/ShapeTools.py index 184112d14ab..a4ee6922c79 100644 --- a/python/ShapeTools.py +++ b/python/ShapeTools.py @@ -615,6 +615,8 @@ def prepareAllShapes(self): raise RuntimeError("There's more than once choice of observables: %s\n" % str(list(shapeObs.keys()))) self.out.binVars = list(shapeObs.values())[0] self.out.safe_import(self.out.binVars) + # keep hold of pdf_norm renaming + self.DC.pdfnorms = self.norm_rename_map.copy() def doCombinedDataset(self): if len(self.DC.bins) == 1 and self.options.forceNonSimPdf: From 9c8376ed21eec1d111d6ed7aab31788020c1b0ef Mon Sep 17 00:00:00 2001 From: Nick Wardle Date: Thu, 6 Apr 2023 12:54:32 +0200 Subject: [PATCH 05/12] Add info to docs As part of PR, add info in docs for running limits --- docs/part3/commonstatsmethods.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/part3/commonstatsmethods.md b/docs/part3/commonstatsmethods.md index 715c0f7927f..9fb651cb2f4 100644 --- a/docs/part3/commonstatsmethods.md +++ b/docs/part3/commonstatsmethods.md @@ -346,6 +346,9 @@ The choice of test-statistic can be made via the option `--testStat` and differe While the above shortcuts are the common variants, you can also try others. The treatment of the nuisances can be changed to the so-called "Hybrid-Bayesian" method which effectively integrates over the nuisance parameters. This can be achieved (with any test-statistic which is not profiled over the nuisances) by setting `--generateNuisances=1 --generateExternalMeasurements=0 --fitNuisances=0`. +!!! warning + If you have unconstrained parameters in your model (`rateParam` or if using a `_norm` variable for a pdf) and you want to use the "Hybrid-Bayesian" method, you **must** declare these as `flatParam` in your datacard and when running text2workspace you must add the option `--X-assign-flatParam-prior` in the command line. This will create uniform priors for these parameters, which is needed for this method and which otherwise would not get created. + !!! info Note that (observed and toy) values of the test statistic stored in the instances of `RooStats::HypoTestResult` when the option `--saveHybridResult` has been specified, are defined without the factor 2 and therefore are twice as small as the values given by the formulas above. This factor is however included automatically by all plotting script supplied within the Combine package. From c5c5d0a23c743baf5716e9dd90e35412d3d1b275 Mon Sep 17 00:00:00 2001 From: Nick Wardle Date: Thu, 6 Apr 2023 12:59:03 +0200 Subject: [PATCH 06/12] Also add for generic toy generation section --- docs/part3/runningthetool.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/part3/runningthetool.md b/docs/part3/runningthetool.md index 4a320fd41bf..7bd396c820c 100644 --- a/docs/part3/runningthetool.md +++ b/docs/part3/runningthetool.md @@ -184,7 +184,7 @@ You can turn off the internal logic by setting `--X-rtd TMCSO_AdaptivePseudoAsim #### Nuisance parameter generation -The default method of dealing with systematics is to generate random values (around their nominal values, see above) for the nuisance parameters, according to their prior pdfs centred around their default values, *before* generating the data. The *unconstrained* nuisance parameters (eg `flatParam` or `rateParam`) or those with *flat* priors are **not** randomised before the data generation. +The default method of dealing with systematics is to generate random values (around their nominal values, see above) for the nuisance parameters, according to their prior pdfs centred around their default values, *before* generating the data. The *unconstrained* nuisance parameters (eg `flatParam` or `rateParam`) or those with *flat* priors are **not** randomised before the data generation. If you wish to also randomise these parameters, you **must** declare these as `flatParam` in your datacard and when running text2workspace you must add the option `--X-assign-flatParam-prior` in the command line. The following are options which define how the toys will be generated, From bad7ed2472745f0510e5bd0a8e3e1baa232b0264 Mon Sep 17 00:00:00 2001 From: Nick Wardle Date: Fri, 14 Apr 2023 14:17:00 +0200 Subject: [PATCH 07/12] Add default option to DC parser Fix issue in combineCards where DC parser misses new option, now set to default to avoid fault but nothing will happen to the output datacard --- python/DatacardParser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/DatacardParser.py b/python/DatacardParser.py index e2f19cb13cf..650e8656b98 100644 --- a/python/DatacardParser.py +++ b/python/DatacardParser.py @@ -367,6 +367,9 @@ def parseCard(file, options): if not hasattr(options, "evaluateEdits"): setattr(options, "evaluateEdits", True) + if not hasattr(options, "flatParamPrior"): + setattr(options, "flatParamPrior", False) + try: for lineNumber, l in enumerate(file): f = l.split() From 6fd381bd84a54616c376e38ee43af8c08fa40851 Mon Sep 17 00:00:00 2001 From: Nick Wardle Date: Fri, 14 Apr 2023 14:19:15 +0200 Subject: [PATCH 08/12] fix linter --- python/DatacardParser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/DatacardParser.py b/python/DatacardParser.py index 650e8656b98..615eb169df3 100644 --- a/python/DatacardParser.py +++ b/python/DatacardParser.py @@ -369,7 +369,7 @@ def parseCard(file, options): if not hasattr(options, "flatParamPrior"): setattr(options, "flatParamPrior", False) - + try: for lineNumber, l in enumerate(file): f = l.split() From 2c29e9fb14c2bdf7eec8a360c85a6f3470569e73 Mon Sep 17 00:00:00 2001 From: Nick Wardle Date: Mon, 17 Apr 2023 12:07:52 +0200 Subject: [PATCH 09/12] Update to move addition of flat prior after all nuisances created --- python/Datacard.py | 3 +++ python/ModelTools.py | 53 ++++++++++++++++++++++++++++++-------------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/python/Datacard.py b/python/Datacard.py index 77d1ec47d3b..2d13048bcdd 100644 --- a/python/Datacard.py +++ b/python/Datacard.py @@ -66,6 +66,9 @@ def __init__(self): # list of parameters called _norm in user input workspace self.pdfnorms = {} + # collection of nuisances to auto-produce flat priors for + self.toCreateFlatParam = {} + def print_structure(self): """ Print the contents of the -> should allow for direct text2workspace on python config diff --git a/python/ModelTools.py b/python/ModelTools.py index e37f3fa80fb..0c81973f8dd 100644 --- a/python/ModelTools.py +++ b/python/ModelTools.py @@ -154,6 +154,7 @@ def __init__(self, datacard, options): self.selfNormBins = [] self.extraNuisances = [] self.extraGlobalObservables = [] + self.globalobs = [] def getSafeNormName(self, n): # need to be careful in case user has _norm name and wants to auto-create flatPrior @@ -181,6 +182,8 @@ def doModel(self, justCheckPhysicsModel=False): self.doNuisances() self.doExtArgs() self.doRateParams() + self.doAutoFlatNuisancePriors() + self.doFillNuisPdfsAndSets() self.doExpectedEvents() if justCheckPhysicsModel: self.physics.done() @@ -323,6 +326,7 @@ def doRateParams(self): v = float(argv) removeRange = len(param_range) == 0 if param_range == "": + if self.options.flatParamPrior: raise ValueError("Cannot create flat Prior for rateParam nuisance parameter '" + argu + "' without specifying a range [a,b]. Please fix in the datacard") ## check range. The parameter needs to be created in range. Then we will remove it param_range = "%g,%g" % (-2.0 * abs(v), 2.0 * abs(v)) # additional check for range requested @@ -389,7 +393,7 @@ def doNuisances(self): if len(self.DC.systs) == 0: return self.doComment(" ----- nuisances -----") - globalobs = [] + #globalobs = [] for n, nofloat, pdf, args, errline in self.DC.systs: is_func_scaled = False @@ -473,7 +477,7 @@ def doNuisances(self): theta, ), ) - globalobs.append("%s_In" % n) + self.globalobs.append("%s_In" % n) if self.options.bin: self.out.var("%s_In" % n).setConstant(True) elif pdf == "gmN": @@ -508,7 +512,7 @@ def doNuisances(self): "Poisson", "%s_In[%d,%f,%f], %s[%f,%f,%f], 1" % (n, args[0], minObs, maxObs, n, args[0] + 1, minExp, maxExp), ) - globalobs.append("%s_In" % n) + self.globalobs.append("%s_In" % n) if self.options.bin: self.out.var("%s_In" % n).setConstant(True) elif pdf == "trG": @@ -522,7 +526,7 @@ def doNuisances(self): trG_max = -1.0 / v r = "%f,%f" % (trG_min, trG_max) self.doObj("%s_Pdf" % n, "Gaussian", "%s[0,%s], %s_In[0,%s], 1" % (n, r, n, r)) - globalobs.append("%s_In" % n) + self.globalobs.append("%s_In" % n) if self.options.bin: self.out.var("%s_In" % n).setConstant(True) elif pdf == "lnU" or pdf == "shapeU": @@ -531,15 +535,11 @@ def doNuisances(self): self.doObj("%s_Pdf" % n, "Uniform", "%s[%f,%f]" % (n, args[0], args[1])) elif pdf == "flatParam" and self.options.flatParamPrior: c_param_name = self.getSafeNormName(n) - v, x1, x2 = self.out.var(c_param_name).getVal(), self.out.var(c_param_name).getMin(), self.out.var(c_param_name).getMax() - if self.options.verbose > 2: - print("will create flat prior for parameter ", c_param_name, " in range [", x1, x2, "]") - self.doExp( - "%s_diff_expr" % c_param_name, "%s-%s_In" % (c_param_name, c_param_name), "%s,%s_In[%g,%g,%g]" % (c_param_name, c_param_name, v, x1, x2) - ) - self.doObj("%s_Pdf" % c_param_name, "Uniform", "%s_diff_expr" % c_param_name) - self.out.var("%s_In" % c_param_name).setConstant(True) - globalobs.append("%s_In" % c_param_name) + if self.out.var(c_param_name): + v, x1, x2 = self.out.var(c_param_name).getVal(), self.out.var(c_param_name).getMin(), self.out.var(c_param_name).getMax() + self.DC.toCreateFlatParam[c_param_name]=[v,x1,x2] + else: self.DC.toCreateFlatParam[c_param_name]=[] + elif pdf == "dFD" or pdf == "dFD2": dFD_min = -(1 + 8 / args[0]) dFD_max = +(1 + 8 / args[0]) @@ -564,7 +564,7 @@ def doNuisances(self): ROOFIT_EXPR_PDF, "'1/(2*(1+exp(%f*(@0-1)))*(1+exp(-%f*(@0+1))))', %s[0,%s], %s_In[0,%s]" % (args[0], args[0], n, r, n, r), ) - globalobs.append("%s_In" % n) + self.globalobs.append("%s_In" % n) if self.options.bin: self.out.var("%s_In" % n).setConstant(True) elif pdf == "constr": @@ -778,7 +778,7 @@ def doNuisances(self): self.out.function("%s_BoundLo" % n), self.out.function("%s_BoundHi" % n), ) - globalobs.append("%s_In" % n) + self.globalobs.append("%s_In" % n) # if self.options.optimizeBoundNuisances: self.out.var(n).setAttribute("optimizeBounds") elif pdf == "extArg": continue @@ -790,6 +790,8 @@ def doNuisances(self): # self.out.var(n).Print('V') if n in self.DC.frozenNuisances: self.out.var(n).setConstant(True) + + def doFillNuisPdfsAndSets(self): if self.options.bin: # avoid duplicating _Pdf in list setNuisPdf = [] @@ -808,7 +810,7 @@ def doNuisances(self): self.out.safe_import(self.out.nuisPdf) self.out.nuisPdfs = nuisPdfs gobsVars = ROOT.RooArgSet() - for g in globalobs: + for g in self.globalobs: gobsVars.add(self.out.var(g)) self.out.defineSet("globalObservables", gobsVars) else: # doesn't work for too many nuisances :-( @@ -816,7 +818,24 @@ def doNuisances(self): setNuisPdf = set([self.getSafeNormName(n) for (n, nf, p, a, e) in self.DC.systs]) self.doSet("nuisances", ",".join(["%s" % self.getSafeNormName(n) for (n, nf, p, a, e) in self.DC.systs])) self.doObj("nuisancePdf", "PROD", ",".join(["%s_Pdf" % n for n in setNuisPdf])) - self.doSet("globalObservables", ",".join(globalobs)) + self.doSet("globalObservables", ",".join(self.globalobs)) + + def doAutoFlatNuisancePriors(self): + if len(self.DC.toCreateFlatParam.keys())>0: + for flatNP in self.DC.toCreateFlatParam.items() : + c_param_name = flatNP[0] + c_param_details = flatNP[1] + print(c_param_name,c_param_details) + if len(c_param_details) : v, x1, x2 = c_param_details + else : v, x1, x2 = self.out.var(c_param_name).getVal(), self.out.var(c_param_name).getMin(), self.out.var(c_param_name).getMax() + if self.options.verbose > 2: + print("Will create flat prior for parameter ", c_param_name, " with range [", x1, x2, "]") + self.doExp( + "%s_diff_expr" % c_param_name, "%s-%s_In" % (c_param_name, c_param_name), "%s,%s_In[%g,%g,%g]" % (c_param_name, c_param_name, v, x1, x2) + ) + self.doObj("%s_Pdf" % c_param_name, "Uniform", "%s_diff_expr" % c_param_name) + self.out.var("%s_In" % c_param_name).setConstant(True) + self.globalobs.append("%s_In" % c_param_name) def doNuisancesGroups(self): # Prepare a dictionary of which group a certain nuisance belongs to From b799ebead0d623a305d02f41a3b19ba0e0f77ace Mon Sep 17 00:00:00 2001 From: Nick Wardle Date: Mon, 17 Apr 2023 12:24:01 +0200 Subject: [PATCH 10/12] Fix missing 'self' --- python/ModelTools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ModelTools.py b/python/ModelTools.py index 0c81973f8dd..4444697b696 100644 --- a/python/ModelTools.py +++ b/python/ModelTools.py @@ -444,7 +444,7 @@ def doNuisances(self): # Use existing constraint since it could be a param self.out.var(n).setVal(0) self.out.var(n).setError(1) - globalobs.append("%s_In" % n) + self.globalobs.append("%s_In" % n) if self.options.bin: self.out.var("%s_In" % n).setConstant(True) if self.options.optimizeBoundNuisances and not is_func_scaled: From 86abab48b73a3a4afce73019b817728f43b862ea Mon Sep 17 00:00:00 2001 From: Nick Wardle Date: Mon, 17 Apr 2023 13:31:06 +0200 Subject: [PATCH 11/12] fix formatting --- python/ModelTools.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/python/ModelTools.py b/python/ModelTools.py index 4444697b696..62a4f79a6e6 100644 --- a/python/ModelTools.py +++ b/python/ModelTools.py @@ -326,7 +326,12 @@ def doRateParams(self): v = float(argv) removeRange = len(param_range) == 0 if param_range == "": - if self.options.flatParamPrior: raise ValueError("Cannot create flat Prior for rateParam nuisance parameter '" + argu + "' without specifying a range [a,b]. Please fix in the datacard") + if self.options.flatParamPrior: + raise ValueError( + "Cannot create flat Prior for rateParam nuisance parameter '" + + argu + + "' without specifying a range [a,b]. Please fix in the datacard" + ) ## check range. The parameter needs to be created in range. Then we will remove it param_range = "%g,%g" % (-2.0 * abs(v), 2.0 * abs(v)) # additional check for range requested @@ -393,7 +398,7 @@ def doNuisances(self): if len(self.DC.systs) == 0: return self.doComment(" ----- nuisances -----") - #globalobs = [] + # globalobs = [] for n, nofloat, pdf, args, errline in self.DC.systs: is_func_scaled = False @@ -537,8 +542,9 @@ def doNuisances(self): c_param_name = self.getSafeNormName(n) if self.out.var(c_param_name): v, x1, x2 = self.out.var(c_param_name).getVal(), self.out.var(c_param_name).getMin(), self.out.var(c_param_name).getMax() - self.DC.toCreateFlatParam[c_param_name]=[v,x1,x2] - else: self.DC.toCreateFlatParam[c_param_name]=[] + self.DC.toCreateFlatParam[c_param_name] = [v, x1, x2] + else: + self.DC.toCreateFlatParam[c_param_name] = [] elif pdf == "dFD" or pdf == "dFD2": dFD_min = -(1 + 8 / args[0]) @@ -821,13 +827,15 @@ def doFillNuisPdfsAndSets(self): self.doSet("globalObservables", ",".join(self.globalobs)) def doAutoFlatNuisancePriors(self): - if len(self.DC.toCreateFlatParam.keys())>0: - for flatNP in self.DC.toCreateFlatParam.items() : + if len(self.DC.toCreateFlatParam.keys()) > 0: + for flatNP in self.DC.toCreateFlatParam.items(): c_param_name = flatNP[0] - c_param_details = flatNP[1] - print(c_param_name,c_param_details) - if len(c_param_details) : v, x1, x2 = c_param_details - else : v, x1, x2 = self.out.var(c_param_name).getVal(), self.out.var(c_param_name).getMin(), self.out.var(c_param_name).getMax() + c_param_details = flatNP[1] + print(c_param_name, c_param_details) + if len(c_param_details): + v, x1, x2 = c_param_details + else: + v, x1, x2 = self.out.var(c_param_name).getVal(), self.out.var(c_param_name).getMin(), self.out.var(c_param_name).getMax() if self.options.verbose > 2: print("Will create flat prior for parameter ", c_param_name, " with range [", x1, x2, "]") self.doExp( From 3976c43da2562451c2bcb8f4de8455e8c5028895 Mon Sep 17 00:00:00 2001 From: Aliya Nigamova Date: Fri, 21 Apr 2023 14:52:01 +0200 Subject: [PATCH 12/12] remove printout --- python/ModelTools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/ModelTools.py b/python/ModelTools.py index 62a4f79a6e6..22afb015e7b 100644 --- a/python/ModelTools.py +++ b/python/ModelTools.py @@ -831,7 +831,6 @@ def doAutoFlatNuisancePriors(self): for flatNP in self.DC.toCreateFlatParam.items(): c_param_name = flatNP[0] c_param_details = flatNP[1] - print(c_param_name, c_param_details) if len(c_param_details): v, x1, x2 = c_param_details else: