class KappaVKappaT(LHCHCGBaseModel): """ Copy of Kappas model with a combined kappa_V (for kappa_W and kappa_Z), and where hcc is independent of kappa_t. For tHq multilepton analysis (HIG-17-005) NOTE - Do not use this model for a generic analysis, instead use the LHCHCGModels:K3 or K7 models and freeze POIs accordingly """ def __init__(self, resolved=True, BRU=True, addInvisible=False, coupleTopTau=False): LHCHCGBaseModel.__init__( self ) # not using 'super(x,self).__init__' since I don't understand it self.doBRU = BRU self.resolved = resolved self.addInvisible = addInvisible self.coupleTopTau = coupleTopTau def setPhysicsOptions(self, physOptions): self.setPhysicsOptionsBase(physOptions) for po in physOptions: if po.startswith("BRU="): self.doBRU = (po.replace("BRU=", "") in ["yes", "1", "Yes", "True", "true"]) print "BR uncertainties in partial widths: %s " % self.doBRU def doParametersOfInterest(self): """Create POI out of signal strength and MH""" self.modelBuilder.doVar("r[1,0.0,10.0]") self.modelBuilder.doVar("kappa_V[1,0.0,2.0]") self.modelBuilder.doVar("kappa_t[1,-10.0,10.0]") self.modelBuilder.doVar("kappa_mu[1,0.0,5.0]") if not self.coupleTopTau: self.modelBuilder.doVar("kappa_tau[1,0.0,3.0]") self.modelBuilder.factory_( "expr::kappa_mu_expr(\"@0*@1+(1-@0)*@2\", CMS_use_kmu[0], kappa_mu, kappa_tau)" ) else: self.modelBuilder.factory_( "expr::kappa_mu_expr(\"@0*@1+(1-@0)*@2\", CMS_use_kmu[0], kappa_mu, kappa_t)" ) self.modelBuilder.doVar("kappa_b[1,0.0,3.0]") self.modelBuilder.doVar( "kappa_c[1,0.0,3.0]") # treat hcc independently from kappa_t if not self.resolved: self.modelBuilder.doVar("kappa_g[1,0.0,2.0]") self.modelBuilder.doVar("kappa_gam[1,0.0,2.5]") self.modelBuilder.doVar("BRinv[0,0,1]") if not self.addInvisible: self.modelBuilder.out.var("BRinv").setConstant(True) pois = 'kappa_V,kappa_t,kappa_b,kappa_c' if not self.coupleTopTau: pois += ',kappa_tau' if not self.resolved: pois += ',kappa_g,kappa_gam' if self.addInvisible: pois += ",BRinv" self.doMH() self.modelBuilder.doSet("POI", pois) self.SMH = SMHiggsBuilder(self.modelBuilder) self.setup() def setup(self): self.dobbH() # SM BR for d in SM_HIGG_DECAYS + ["hss"]: self.SMH.makeBR(d) # BR uncertainties if self.doBRU: self.SMH.makePartialWidthUncertainties() else: for d in SM_HIGG_DECAYS: self.modelBuilder.factory_( 'HiggsDecayWidth_UncertaintyScaling_%s[1.0]' % d) # get VBF, tHq, tHW, ggZH cross section self.SMH.makeScaling('qqH', CW='kappa_V', CZ='kappa_V') self.SMH.makeScaling("tHq", CW='kappa_V', Ctop="kappa_t") self.SMH.makeScaling("tHW", CW='kappa_V', Ctop="kappa_t") self.SMH.makeScaling("ggZH", CZ='kappa_V', Ctop="kappa_t", Cb="kappa_b") # resolve loops if self.resolved: self.SMH.makeScaling('ggH', Cb='kappa_b', Ctop='kappa_t', Cc="kappa_t") self.SMH.makeScaling('hgluglu', Cb='kappa_b', Ctop='kappa_t') if not self.coupleTopTau: self.SMH.makeScaling('hgg', Cb='kappa_b', Ctop='kappa_t', CW='kappa_V', Ctau='kappa_tau') self.SMH.makeScaling('hzg', Cb='kappa_b', Ctop='kappa_t', CW='kappa_V', Ctau='kappa_tau') else: self.SMH.makeScaling('hgg', Cb='kappa_b', Ctop='kappa_t', CW='kappa_V', Ctau='kappa_t') self.SMH.makeScaling('hzg', Cb='kappa_b', Ctop='kappa_t', CW='kappa_V', Ctau='kappa_t') else: self.modelBuilder.factory_( 'expr::Scaling_hgluglu("@0*@0", kappa_g)') self.modelBuilder.factory_('expr::Scaling_hgg("@0*@0", kappa_gam)') self.modelBuilder.factory_('expr::Scaling_hzg("@0*@0", kappa_gam)') self.modelBuilder.factory_( 'expr::Scaling_ggH_7TeV("@0*@0", kappa_g)') self.modelBuilder.factory_( 'expr::Scaling_ggH_8TeV("@0*@0", kappa_g)') self.modelBuilder.factory_( 'expr::Scaling_ggH_13TeV("@0*@0", kappa_g)') self.modelBuilder.factory_( 'expr::Scaling_ggH_14TeV("@0*@0", kappa_g)') ## partial witdhs, normalized to the SM one self.modelBuilder.factory_( 'expr::c7_Gscal_Z("@0*@0*@1*@2", kappa_V, SM_BR_hzz, HiggsDecayWidth_UncertaintyScaling_hzz)' ) self.modelBuilder.factory_( 'expr::c7_Gscal_W("@0*@0*@1*@2", kappa_V, SM_BR_hww, HiggsDecayWidth_UncertaintyScaling_hww)' ) if not self.coupleTopTau: self.modelBuilder.factory_( 'expr::c7_Gscal_tau("@0*@0*@1*@4+@2*@2*@3*@5", kappa_tau, SM_BR_htt, kappa_mu_expr, SM_BR_hmm, HiggsDecayWidth_UncertaintyScaling_htt, HiggsDecayWidth_UncertaintyScaling_hmm)' ) else: self.modelBuilder.factory_( 'expr::c7_Gscal_tau("@0*@0*@1*@4+@2*@2*@3*@5", kappa_t, SM_BR_htt, kappa_mu_expr, SM_BR_hmm, HiggsDecayWidth_UncertaintyScaling_htt, HiggsDecayWidth_UncertaintyScaling_hmm)' ) self.modelBuilder.factory_( 'expr::c7_Gscal_top("@0*@0 * @1*@2", kappa_c, SM_BR_hcc, HiggsDecayWidth_UncertaintyScaling_hcc)' ) self.modelBuilder.factory_( 'expr::c7_Gscal_bottom("@0*@0 * (@1*@3+@2)", kappa_b, SM_BR_hbb, SM_BR_hss, HiggsDecayWidth_UncertaintyScaling_hbb)' ) self.modelBuilder.factory_( 'expr::c7_Gscal_gluon(" @0 * @1 * @2", Scaling_hgluglu, SM_BR_hgluglu, HiggsDecayWidth_UncertaintyScaling_hgluglu)' ) self.modelBuilder.factory_( 'expr::c7_Gscal_gamma("@0*@1*@4 + @2*@3*@5", Scaling_hgg, SM_BR_hgg, Scaling_hzg, SM_BR_hzg, HiggsDecayWidth_UncertaintyScaling_hgg, HiggsDecayWidth_UncertaintyScaling_hzg)' ) # fix to have all BRs add up to unity self.modelBuilder.factory_("sum::c7_SMBRs(%s)" % (",".join( "SM_BR_" + X for X in "hzz hww htt hmm hcc hbb hss hgluglu hgg hzg".split()))) self.modelBuilder.out.function("c7_SMBRs").Print("") ## total witdh, normalized to the SM one self.modelBuilder.factory_( 'expr::c7_Gscal_tot("(@1+@2+@3+@4+@5+@6+@7)/@8/(1-@0)", BRinv, c7_Gscal_Z, c7_Gscal_W, c7_Gscal_tau, c7_Gscal_top, c7_Gscal_bottom, c7_Gscal_gluon, c7_Gscal_gamma, c7_SMBRs)' ) ## BRs, normalized to the SM ones: they scale as (partial/partial_SM) / (total/total_SM) self.modelBuilder.factory_( 'expr::c7_BRscal_hww("@0*@0*@2/@1", kappa_V, c7_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hww)' ) self.modelBuilder.factory_( 'expr::c7_BRscal_hzz("@0*@0*@2/@1", kappa_V, c7_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hzz)' ) if not self.coupleTopTau: self.modelBuilder.factory_( 'expr::c7_BRscal_htt("@0*@0*@2/@1", kappa_tau, c7_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_htt)' ) else: self.modelBuilder.factory_( 'expr::c7_BRscal_htt("@0*@0*@2/@1", kappa_t, c7_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_htt)' ) self.modelBuilder.factory_( 'expr::c7_BRscal_hmm("@0*@0*@2/@1", kappa_mu_expr, c7_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hmm)' ) self.modelBuilder.factory_( 'expr::c7_BRscal_hbb("@0*@0*@2/@1", kappa_b, c7_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hbb)' ) self.modelBuilder.factory_( 'expr::c7_BRscal_hcc("@0*@0*@2/@1", kappa_c, c7_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hcc)' ) self.modelBuilder.factory_( 'expr::c7_BRscal_hgg("@0*@2/@1", Scaling_hgg, c7_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hgg)' ) self.modelBuilder.factory_( 'expr::c7_BRscal_hzg("@0*@2/@1", Scaling_hzg, c7_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hzg)' ) self.modelBuilder.factory_( 'expr::c7_BRscal_hgluglu("@0*@2/@1", Scaling_hgluglu, c7_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hgluglu)' ) self.modelBuilder.factory_('expr::c7_BRscal_hinv("@0", BRinv)') def getHiggsSignalYieldScale(self, production, decay, energy): name = "c7_XSBRscal_%s_%s_%s" % (production, decay, energy) if self.modelBuilder.out.function(name) == None: if production in ["ggH", "qqH", "ggZH", "tHq", "tHW"]: XSscal = ("@0", "Scaling_%s_%s" % (production, energy)) elif production == "WH": XSscal = ("@0*@0", "kappa_V") elif production == "ZH": XSscal = ("@0*@0", "kappa_V") elif production == "ttH": XSscal = ("@0*@0", "kappa_t") elif production == "bbH": XSscal = ("@0*@0", "kappa_b") else: raise RuntimeError, "Production %s not supported" % production BRscal = decay if not self.modelBuilder.out.function("c7_BRscal_" + BRscal): raise RuntimeError, "Decay mode %s not supported" % decay if decay == "hss": BRscal = "hbb" if production in ['tHq', 'tHW', 'ttH']: self.modelBuilder.factory_( 'expr::%s("%s*@1*@2", %s, c7_BRscal_%s, r)' % (name, XSscal[0], XSscal[1], BRscal)) elif production == "ggH" and (decay in self.add_bbH) and energy in [ "7TeV", "8TeV", "13TeV", "14TeV" ]: b2g = "CMS_R_bbH_ggH_%s_%s[%g]" % (decay, energy, 0.01) b2gs = "CMS_bbH_scaler_%s" % energy self.modelBuilder.factory_( 'expr::%s("(%s + @1*@1*@2*@3)*@4", %s, kappa_b, %s, %s, c7_BRscal_%s)' % (name, XSscal[0], XSscal[1], b2g, b2gs, BRscal)) else: self.modelBuilder.factory_( 'expr::%s("%s*@1*@2", %s, c7_BRscal_%s,r)' % (name, XSscal[0], XSscal[1], BRscal)) print '[LHC-HCG Kappas]', name, production, decay, energy, ": ", self.modelBuilder.out.function(name).Print("") return name
class HHModel(PhysicsModel): """ Models the HH production as linear sum of 6 components (VBF) and 3 components (GGF) """ #scaleBR=True to scale for branching ratio dependence #setBR1 to overwrite the single Higgs decay mode and one of the double Higgs decay modes #setBR2 to overwrite the second decay mode of double Higgs def __init__(self, ggf_sample_list, vbf_sample_list, name, setBR1="", setBR2=""): PhysicsModel.__init__(self) self.name = name self.check_validity_ggf(ggf_sample_list) self.check_validity_vbf(vbf_sample_list) self.ggf_formula = GGFHHFormula(ggf_sample_list) self.vbf_formula = VBFHHFormula(vbf_sample_list) self.setBR1 = setBR1 self.setBR2 = setBR2 self.scaleBR = True # include branching ratio uncertainties in the systematics # if only one single HH channel is considered, it is suggested to # include the specific BR uncertainty(s) directly in the datacard. # This should be re-evaluated for a HH combination self.doBRU = False self.dump_inputs() self.f_r_singleH_names = [] def setPhysicsOptions(self, physOptions): for po in physOptions: if po.startswith("BRU="): self.doBRU = (po.replace("BRU=", "") in ["yes", "1", "Yes", "True", "true"]) if po.startswith("scaleBR="): self.scaleBR = (po.replace("BRU=", "") in ["no", "0", "No", "False", "false"]) print "BR uncertainties in partial widths: %s " % self.doBRU def check_validity_ggf(self, ggf_sample_list): if len(ggf_sample_list) != 3: raise RuntimeError( "%s : malformed GGF input sample list - expect 3 samples" % self.name) if not isinstance(ggf_sample_list, list) and not isinstance( ggf_sample_list, tuple): raise RuntimeError( "%s : malformed GGF input sample list - expect list or tuple" % self.name) for s in ggf_sample_list: if not isinstance(s, GGFHHSample): raise RuntimeError( "%s : malformed GGF input sample list - each element must be a GGFHHSample" % self.name) def check_validity_vbf(self, vbf_sample_list): if len(vbf_sample_list) != 6: raise RuntimeError( "%s : malformed VBF input sample list - expect 6 samples" % self.name) if not isinstance(vbf_sample_list, list) and not isinstance( vbf_sample_list, tuple): raise RuntimeError( "%s : malformed VBF input sample list - expect list or tuple" % self.name) for s in vbf_sample_list: if not isinstance(s, VBFHHSample): raise RuntimeError( "%s : malformed VBF input sample list - each element must be a VBFHHSample" % self.name) def dump_inputs(self): print "[INFO] HH model : ", self.name print "...... GGF configuration" for i, s in enumerate(self.ggf_formula.sample_list): print " {0:<3} ... kl : {1:<3}, kt : {2:<3}, xs : {3:<3.8f} pb, label : {4}".format( i, s.val_kl, s.val_kt, s.val_xs, s.label) print "...... VBF configuration" for i, s in enumerate(self.vbf_formula.sample_list): print " {0:<3} ... CV : {1:<3}, C2V : {2:<3}, kl : {3:<3}, xs : {4:<3.8f} pb, label : {5}".format( i, s.val_CV, s.val_C2V, s.val_kl, s.val_xs, s.label) def doParametersOfInterest(self): ## the model is built with: ## r x [GGF + VBF] ## GGF = r_GGF x [sum samples(kl, kt)] ## VBF = r_VBF x [sum samples(kl, CV, C2V)] POIs = "r,r_gghh,r_qqhh,CV,C2V,kl,kt" self.modelBuilder.doVar("r[1,0,10]") self.modelBuilder.doVar("r_gghh[1,0,10]") self.modelBuilder.doVar("r_qqhh[1,0,10]") self.modelBuilder.doVar("CV[1,-10,10]") self.modelBuilder.doVar("C2V[1,-10,10]") self.modelBuilder.doVar("kl[1,-30,30]") self.modelBuilder.doVar("kt[1,-10,10]") self.modelBuilder.doSet("POI", POIs) self.SMH = SMHiggsBuilder(self.modelBuilder) self.modelBuilder.out.var("r_gghh").setConstant(True) self.modelBuilder.out.var("r_qqhh").setConstant(True) self.modelBuilder.out.var("CV").setConstant(True) self.modelBuilder.out.var("C2V").setConstant(True) self.modelBuilder.out.var("kl").setConstant(True) self.modelBuilder.out.var("kt").setConstant(True) #I need to build MH variables because the BR are tabulated as a function of MH # the mass setting must be provided as input, i.e. '-m 125' if self.modelBuilder.out.var("MH"): self.modelBuilder.out.var("MH").setVal(self.options.mass) self.modelBuilder.out.var("MH").setConstant(True) else: self.modelBuilder.doVar("MH[%g]" % self.options.mass) self.create_scalings() def create_scalings(self): """ create the functions that scale the six components of vbf and the 3 components of ggf """ ###################################################################### #create Higgs BR scalings for d in SM_HIGG_DECAYS + ["hss"]: self.SMH.makeBR(d) # BR uncertainties if self.doBRU: self.SMH.makePartialWidthUncertainties() else: for d in SM_HIGG_DECAYS: self.modelBuilder.factory_( 'HiggsDecayWidth_UncertaintyScaling_%s[1.0]' % d) # fix to have all BRs add up to unity self.modelBuilder.factory_("sum::c7_SMBRs(%s)" % (",".join( "SM_BR_" + X for X in "hzz hww htt hmm hcc hbb hss hgluglu hgg hzg".split()))) self.modelBuilder.out.function("c7_SMBRs").Print("") # get VBF, tHq, tHW, ggZH cross section and resolved loops self.SMH.makeScaling('qqH', CW='CV', CZ='CV') self.SMH.makeScaling("tHq", CW='CV', Ctop="kt") self.SMH.makeScaling("tHW", CW='CV', Ctop="kt") self.SMH.makeScaling("ggZH", CZ='CV', Ctop="kt", Cb="1") self.SMH.makeScaling('ggH', Cb='1', Ctop='kt', Cc="1") self.SMH.makeScaling('hgluglu', Cb='1', Ctop='kt') self.SMH.makeScaling('hgg', Cb='1', Ctop='kt', CW='CV', Ctau='1') self.SMH.makeScaling('hzg', Cb='1', Ctop='kt', CW='CV', Ctau='1') cGammap = { "hgg": 0.49e-2, "hzz": 0.83e-2, "hww": 0.73e-2, "hgluglu": 0.66e-2, "htt": 0, "hbb": 0, "hcc": 0, "hmm": 0 } # First we need to create the terms that account for the self-coupling --> Just scale partial width first - https://arxiv.org/abs/1709.08649 Eq 22. # probably a better way to code this since the partial width expressions are being repeated when we write the BR for dec in cGammap.keys(): valC1 = cGammap[dec] self.modelBuilder.factory_('expr::kl_scalBR_%s("(@0-1)*%g",kl)' % (dec, valC1)) # next make the partial widths, also including the kappas -> we want to include the term from the normal kappas and the one from the self-coupling self.modelBuilder.factory_( 'expr::kVktkl_Gscal_Z("(@0*@0+@3)*@1*@2", CV, SM_BR_hzz, HiggsDecayWidth_UncertaintyScaling_hzz, kl_scalBR_hzz)' ) self.modelBuilder.factory_( 'expr::kVktkl_Gscal_W("(@0*@0+@3)*@1*@2", CV, SM_BR_hww, HiggsDecayWidth_UncertaintyScaling_hww, kl_scalBR_hww)' ) self.modelBuilder.factory_( 'expr::kVktkl_Gscal_tau("(1+@4)*@0*@2 + (1+@5)*@1*@3", SM_BR_htt, SM_BR_hmm, HiggsDecayWidth_UncertaintyScaling_htt, HiggsDecayWidth_UncertaintyScaling_hmm,kl_scalBR_htt, kl_scalBR_hmm)' ) self.modelBuilder.factory_( 'expr::kVktkl_Gscal_top("(1+@2)*@0*@1", SM_BR_hcc, HiggsDecayWidth_UncertaintyScaling_hcc, kl_scalBR_hcc)' ) self.modelBuilder.factory_( 'expr::kVktkl_Gscal_bottom("(1+@3) * (@0*@2+@1)", SM_BR_hbb, SM_BR_hss, HiggsDecayWidth_UncertaintyScaling_hbb, kl_scalBR_hbb)' ) self.modelBuilder.factory_( 'expr::kVktkl_Gscal_gluon(" (@0+@3) * @1 * @2", Scaling_hgluglu, SM_BR_hgluglu, HiggsDecayWidth_UncertaintyScaling_hgluglu, kl_scalBR_hgluglu)' ) self.modelBuilder.factory_( 'expr::kVktkl_Gscal_gamma("(@0+@6)*@1*@4 + @2*@3*@5", Scaling_hgg, SM_BR_hgg, Scaling_hzg, SM_BR_hzg, HiggsDecayWidth_UncertaintyScaling_hgg, HiggsDecayWidth_UncertaintyScaling_hzg, kl_scalBR_hgg)' ) # no kl dependance on H->zg known yet ? # fix to have all BRs add up to unity self.modelBuilder.factory_("sum::kVktkl_SMBRs(%s)" % (",".join( "SM_BR_" + X for X in "hzz hww htt hmm hcc hbb hss hgluglu hgg hzg".split()))) self.modelBuilder.out.function("kVktkl_SMBRs").Print("") ## total witdh, normalized to the SM one (just the sum over the partial widths/SM total BR) self.modelBuilder.factory_( 'expr::kVktkl_Gscal_tot("(@0+@1+@2+@3+@4+@5+@6)/@7", kVktkl_Gscal_Z, kVktkl_Gscal_W, kVktkl_Gscal_tau, kVktkl_Gscal_top, kVktkl_Gscal_bottom, kVktkl_Gscal_gluon, kVktkl_Gscal_gamma, kVktkl_SMBRs)' ) ## BRs, normalized to the SM ones: they scale as (partial/partial_SM) / (total/total_SM) self.modelBuilder.factory_( 'expr::kVktkl_BRscal_hww("(@0*@0+@3)*@2/@1", CV, kVktkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hww, kl_scalBR_hww)' ) self.modelBuilder.factory_( 'expr::kVktkl_BRscal_hzz("(@0*@0+@3)*@2/@1", CV, kVktkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hzz, kl_scalBR_hzz)' ) self.modelBuilder.factory_( 'expr::kVktkl_BRscal_htt("(1+@2)*@1/@0", kVktkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_htt, kl_scalBR_htt)' ) self.modelBuilder.factory_( 'expr::kVktkl_BRscal_hmm("(1+@2)*@1/@0", kVktkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hmm, kl_scalBR_hmm)' ) self.modelBuilder.factory_( 'expr::kVktkl_BRscal_hbb("(1+@2)*@1/@0", kVktkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hbb, kl_scalBR_hbb)' ) self.modelBuilder.factory_( 'expr::kVktkl_BRscal_hcc("(1+@2)*@1/@0", kVktkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hcc, kl_scalBR_hcc)' ) self.modelBuilder.factory_( 'expr::kVktkl_BRscal_hgg("(@0+@3)*@2/@1", Scaling_hgg, kVktkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hgg,kl_scalBR_hgg)' ) self.modelBuilder.factory_( 'expr::kVktkl_BRscal_hzg("@0*@2/@1", Scaling_hzg, kVktkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hzg)' ) self.modelBuilder.factory_( 'expr::kVktkl_BRscal_hgluglu("(@0+@3)*@2/@1", Scaling_hgluglu, kVktkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hgluglu, kl_scalBR_hgluglu)' ) ###################################################################### #create single Higgs production scalings energy = "13TeV" cXSmap_13 = { "ggH": 0.66e-2, "qqH": 0.64e-2, "WH": 1.03e-2, "ZH": 1.19e-2, "ttH": 3.51e-2, "VH": (0.5 * (1.03e-2 + 1.19e-2)) } EWKmap_13 = { "ggH": 1.049, "qqH": 0.932, "WH": 0.93, "ZH": 0.947, "ttH": 1.014, "VH": (0.5 * (0.93 + 0.947)) } dZH = -1.536e-3 for production in SM_HIGG_PROD: if production in ["ggZH", "tHq", "tHW"]: self.f_r_singleH_names.append("Scaling_%s_%s" % (production, energy)) elif production in ["ggH", "qqH"]: EWK = EWKmap_13[production] self.modelBuilder.factory_("expr::kVktkl_XSscal_%s_%s(\"(@1+(@0-1)*%g/%g)/((1-(@0*@0-1)*%g))\",kl,Scaling_%s_%s)"\ %(production,energy,cXSmap_13[production],EWK,dZH,production,energy)) self.f_r_singleH_names.append("kVktkl_XSscal_%s_%s" % (production, energy)) elif production in ["ZH", "WH", "VH"]: EWK = EWKmap_13[production] self.modelBuilder.factory_("expr::kVktkl_XSscal_%s_%s(\"(@1*@1+(@0-1)*%g/%g)/((1-(@0*@0-1)*%g))\",kl,CV)"\ %(production,energy,cXSmap_13[production],EWK,dZH)) self.f_r_singleH_names.append("kVktkl_XSscal_%s_%s" % (production, energy)) elif production == "ttH": EWK = EWKmap_13[production] self.modelBuilder.factory_("expr::kVktkl_XSscal_%s_%s(\"(@1*@1+(@0-1)*%g/%g)/((1-(@0*@0-1)*%g))\",kl,kt)"\ %(production,energy,cXSmap_13[production],EWK,dZH)) self.f_r_singleH_names.append("kVktkl_XSscal_%s_%s" % (production, energy)) self.f_r_vbf_names = [ ] # the RooFormulae that scale the components (VBF) self.f_r_ggf_names = [ ] # the RooFormulae that scale the components (GGF) def pow_to_mul_string(expr): """ Convert integer powers in an expression to Muls, like a**2 => a*a. Returns a string """ pows = list(expr.atoms(Pow)) if any(not e.is_Integer for b, e in (i.as_base_exp() for i in pows)): raise ValueError("A power contains a non-integer exponent") s = str(expr) repl = zip(pows, (Mul(*[b] * e, evaluate=False) for b, e in (i.as_base_exp() for i in pows))) for fr, to in repl: s = s.replace(str(fr), str(to)) return s ### loop on the GGF scalings for i, s in enumerate(self.ggf_formula.sample_list): f_name = 'f_ggfhhscale_sample_{0}'.format(i) f_expr = self.ggf_formula.coeffs[ i] # the function that multiplies each sample # print f_expr # for ROOFit, this will convert expressions as a**2 to a*a s_expr = pow_to_mul_string(f_expr) couplings_in_expr = [] if 'kl' in s_expr: couplings_in_expr.append('kl') if 'kt' in s_expr: couplings_in_expr.append('kt') # no constant expressions are expected if len(couplings_in_expr) == 0: raise RuntimeError( 'GGF HH : scaling expression has no coefficients') for idx, ce in enumerate(couplings_in_expr): # print '..replacing', ce symb = '@{}'.format(idx) s_expr = s_expr.replace(ce, symb) arglist = ','.join(couplings_in_expr) exprname = 'expr::{}(\"{}\" , {})'.format(f_name, s_expr, arglist) # print exprname self.modelBuilder.factory_( exprname) # the function that scales each VBF sample f_prod_name_pmode = f_name + '_r_gghh' prodname_pmode = 'prod::{}(r_gghh,{})'.format( f_prod_name_pmode, f_name) self.modelBuilder.factory_( prodname_pmode ) ## the function that scales this production mode # self.modelBuilder.out.function(f_prod_name).Print("") ## will just print out the values f_prod_name = f_prod_name_pmode + '_r' prodname = 'prod::{}(r,{})'.format(f_prod_name, f_prod_name_pmode) self.modelBuilder.factory_( prodname) ## the function that scales this production mode # self.modelBuilder.out.function(f_prod_name).Print("") ## will just print out the values self.f_r_ggf_names.append( f_prod_name) #bookkeep the scaling that has been created ### loop on the VBF scalings for i, s in enumerate(self.vbf_formula.sample_list): f_name = 'f_vbfhhscale_sample_{0}'.format(i) f_expr = self.vbf_formula.coeffs[ i] # the function that multiplies each sample # print f_expr # for ROOFit, this will convert expressions as a**2 to a*a s_expr = pow_to_mul_string(f_expr) couplings_in_expr = [] if 'CV' in s_expr: couplings_in_expr.append('CV') if 'C2V' in s_expr: couplings_in_expr.append('C2V') if 'kl' in s_expr: couplings_in_expr.append('kl') # no constant expressions are expected if len(couplings_in_expr) == 0: raise RuntimeError( 'VBF HH : scaling expression has no coefficients') for idx, ce in enumerate(couplings_in_expr): # print '..replacing', ce symb = '@{}'.format(idx) s_expr = s_expr.replace(ce, symb) arglist = ','.join(couplings_in_expr) exprname = 'expr::{}(\"{}\" , {})'.format(f_name, s_expr, arglist) # print exprname self.modelBuilder.factory_( exprname) # the function that scales each VBF sample f_prod_name_pmode = f_name + '_r_qqhh' prodname_pmode = 'prod::{}(r_qqhh,{})'.format( f_prod_name_pmode, f_name) self.modelBuilder.factory_( prodname_pmode ) ## the function that scales this production mode # self.modelBuilder.out.function(f_prod_name_pmode).Print("") ## will just print out the values f_prod_name = f_prod_name_pmode + '_r' prodname = 'prod::{}(r,{})'.format(f_prod_name, f_prod_name_pmode) self.modelBuilder.factory_( prodname) ## the function that scales this production mode # self.modelBuilder.out.function(f_prod_name).Print("") ## will just print out the values self.f_r_vbf_names.append( f_prod_name) #bookkeep the scaling that has been created def getYieldScale(self, bin, process): if not self.DC.isSignal[process]: return 1 processSource = process.split("_")[0] # It might happen that I use a different process naming than the default one # In that case I will convert it to the default naming if not processSource in processSource_dict: for defaultname, equivalentnames in processSource_dict.items(): for equivalentname in equivalentnames: if processSource == equivalentname: print "[WARNING]: process name \"%s\" is not the default name but I found it equivalent to \"%s\"" % ( processSource, defaultname) print " --> if this is not the case fix the naming!" processSource = defaultname decaySource = [] if self.setBR1 != "": decaySource.append(self.setBR1) else: decaySource.append(process.split("_")[-1]) #in case of HH I have to consider an additional Higgs decay if processSource == "ggHH" or processSource == "qqHH": if self.setBR2 != "": decaySource.append(self.setBR2) else: decaySource.append(process.split("_")[-2]) if not (processSource == "ggHH" or processSource == "qqHH"): if (processSource == "bbH"): return 1 #neglect bbH dependence on kl for XS_scaling_name in self.f_r_singleH_names: if processSource in XS_scaling_name: if not self.modelBuilder.out.function("kVktkl_BRscal_" + decaySource[0]): raise RuntimeError, "Decay mode %s not supported" % decaySource[ 0] if not self.scaleBR: return XS_scaling_name else: BR_scaling_name = "kVktkl_BRscal_" + decaySource[0] XSBR_scaling_name = "kVktkl_XSBRscal_%s_%s" % ( processSource, decaySource[0]) if not self.modelBuilder.out.function( XSBR_scaling_name): self.modelBuilder.factory_( "expr::%s(\"0.+@0*(@0>0.)*@1\",%s,%s)" % (XSBR_scaling_name, XS_scaling_name, BR_scaling_name) ) #I used a trick to avoid negative XS reweights return XSBR_scaling_name #if I am here I have a double H process or an unsopported process ## my control to verify for a unique association between process <-> scaling function try: self.scalingMap except AttributeError: self.scalingMap = {} # match the process name in the datacard to the input sample of the calculation # this is the only point where the two things must be matched if not process in self.scalingMap: self.scalingMap[process] = [] imatched_ggf = [] imatched_vbf = [] for isample, sample in enumerate(self.ggf_formula.sample_list): if process.startswith(sample.label): # print self.name, ": {:>40} ===> {:>40}".format(process, sample.label) imatched_ggf.append(isample) for isample, sample in enumerate(self.vbf_formula.sample_list): if process.startswith(sample.label): # print self.name, ": {:>40} ===> {:>40}".format(process, sample.label) imatched_vbf.append(isample) ## this checks that a process finds a unique scaling if len(imatched_ggf) + len(imatched_vbf) != 1: print "[ERROR] : in HH model named", self.name, "there are", len( imatched_ggf), "GGF name matches and", len( imatched_vbf), "VBF name matches" raise RuntimeError( 'HHModel : could not uniquely match the process %s to the expected sample list' % process) if len(imatched_ggf) == 1: isample = imatched_ggf[0] self.scalingMap[process].append((isample, 'GGF')) XS_scaling_name = self.f_r_ggf_names[isample] if not self.scaleBR: return XS_scaling_name else: BR1_scaling_name = "kVktkl_BRscal_" + decaySource[0] BR2_scaling_name = "kVktkl_BRscal_" + decaySource[1] if not self.modelBuilder.out.function(BR1_scaling_name): raise RuntimeError, "Decay mode %s not supported" % BR1_scaling_name if not self.modelBuilder.out.function(BR2_scaling_name): raise RuntimeError, "Decay mode %s not supported" % BR2_scaling_name XSBR_scaling_name = "%s_%s_%s" % ( XS_scaling_name, decaySource[0], decaySource[1]) if not self.modelBuilder.out.function(XSBR_scaling_name): self.modelBuilder.factory_( 'expr::%s("@0*@1*@2",%s,%s,%s)' % (XSBR_scaling_name, XS_scaling_name, BR1_scaling_name, BR2_scaling_name)) return XSBR_scaling_name if len(imatched_vbf) == 1: isample = imatched_vbf[0] self.scalingMap[process].append((isample, 'VBF')) XS_scaling_name = self.f_r_vbf_names[isample] if not self.scaleBR: return XS_scaling_name else: BR1_scaling_name = "kVktkl_BRscal_" + decaySource[0] BR2_scaling_name = "kVktkl_BRscal_" + decaySource[1] if not self.modelBuilder.out.function(BR1_scaling_name): raise RuntimeError, "Decay mode %s not supported" % BR1_scaling_name if not self.modelBuilder.out.function(BR2_scaling_name): raise RuntimeError, "Decay mode %s not supported" % BR2_scaling_name XSBR_scaling_name = "%s_%s_%s" % ( XS_scaling_name, decaySource[0], decaySource[1]) if not self.modelBuilder.out.function(XSBR_scaling_name): self.modelBuilder.factory_( 'expr::%s("@0*@1*@2",%s,%s,%s)' % (XSBR_scaling_name, XS_scaling_name, BR1_scaling_name, BR2_scaling_name)) return XSBR_scaling_name raise RuntimeError( 'HHModel : fatal error in getYieldScale - this should never happen' ) def done(self): ## this checks that a scaling has been attached to a unique process scalings = {} for k, i in self.scalingMap.items( ): ## key -> process, item -> [(isample, 'type')] samples = list(set(i)) # remove duplicates for s in samples: if not s in scalings: scalings[s] = [] scalings[s].append(k) for key, val in scalings.items(): if len(val) > 1: print "[WARNING] : in HH model named", self.name, "there is a double assignment of a scaling : ", key, " ==> ", val #raise RuntimeError('HHModel : coudl not uniquely match the scaling to the process') ## now check that, if a VBF/GGF scaling exists, there are actually 6/3 samples in the card n_VBF = 0 n_GGF = 0 for k, i in self.scalingMap.items(): # the step above ensured me that the list contains a single element -> i[0] if i[0][1] == "GGF": n_GGF += 1 elif i[0][1] == "VBF": n_VBF += 1 else: raise RuntimeError( "HHModel : unrecognised type %s - should never happen" % i[0][1]) if n_GGF > 0 and n_GGF < 3: raise RuntimeError( "HHModel : you did not pass all the 3 samples needed to build the GGF HH model" ) if n_VBF > 0 and n_VBF < 6: raise RuntimeError( "HHModel : you did not pass all the 6 samples needed to build the VBF HH model" )
class StageXToEFTModel(STXStoEFTBaseModel): def __init__(self,stage): STXStoEFTBaseModel.__init__(self) self.stage = stage import HiggsAnalysis.CombinedLimit.STXS as STXS self.PROCESSES['stage%s'%self.stage] = [x for v in getattr(STXS,"stage%s_procs"%self.stage).itervalues() for x in v] self.PROCESSES["fixedproc"] = STXS.fixed_procs self.DECAYS = ['hzz','hbb','htt','hww','hgg','hgluglu','hcc','hzg','hmm','tot'] def setPhysicsOptions(self,physOptions): self.setPhysicsOptionsBase(physOptions) def doParametersOfInterest(self): if self.floatMass: print " --> [WARNING] Floating Higgs mass selected. STXStoEFT model assumes MH=125.0 GeV" self.doMH() self.SMH = SMHiggsBuilder(self.modelBuilder) #Read in parameter list from file using textToPOIList function self.textToPOIList( os.path.join(self.SMH.datadir,'eft/HEL/pois.txt') ) POIs = ','.join(self.pois.keys()) for poi, poi_range in self.pois.iteritems(): self.modelBuilder.doVar("%s%s"%(poi,poi_range)) self.modelBuilder.doSet("POI",POIs) #POIs for cWW and cB defined in terms of constraints on cWW+cB and cWW-cB: define expression for individual coefficient self.modelBuilder.factory_("expr::cWW_x02(\"0.5*(@0+@1)\",cWWPluscB_x02,cWWMinuscB_x02)") self.modelBuilder.factory_("expr::cB_x02(\"0.5*(@0-@1)\",cWWPluscB_x02,cWWMinuscB_x02)") self.poi_scaling['cWW'] = "0.01*cWW_x02" self.poi_scaling['cB'] = "0.01*cB_x02" # Freeze cWW+cB if freezeOtherParameters if self.freezeOtherParameters: for poi in self.pois: if 'cWWPluscB' in poi: self.modelBuilder.out.var( poi ).setConstant(True) #set up model self.setup() #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def setup(self): #For additional options e.g. STXS/BR uncertainties: defined in base class if self.doBRU: self.SMH.makePartialWidthUncertainties() if self.doSTXSU: self.makeSTXSBinUncertainties( STXSstage=self.stage ) # Read scaling functions for STXS bins and decays from txt files self.textToSTXSScalingFunctions( os.path.join(self.SMH.datadir, 'eft/HEL/stage%s_xs.txt'%self.stage) ) self.textToDecayScalingFunctions( os.path.join(self.SMH.datadir, 'eft/HEL/decay.txt' ) ) # Make scaling functions for STXS processes for proc in self.PROCESSES["stage%s"%self.stage]: self.makeScalingFunction( proc, STXSstage=self.stage ) # Make dummy scaling (=1) for fixed procs for proc in self.PROCESSES["fixedproc"]: self.modelBuilder.factory_("expr::scaling_%s(\"@0\",1.)"%proc) # Make partial width + total width scaling functions for dec in self.DECAYS: self.makeScalingFunction( dec ) # Make BR scaling functions: partial width/total width for dec in self.DECAYS: if dec != "tot": self.makeBRScalingFunction( dec ) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def getHiggsSignalYieldScale(self,production,decay,energy): name = "stxstoeft_scaling_%s_%s_%s"%(production,decay,energy) if self.modelBuilder.out.function(name) == None: XSscal = None BRscal = None # Extract STXS stage process belongs to: in descreasing order as want most recent th. unc if production in self.PROCESSES['stage%s'%self.stage]: key = "stage%s"%self.stage elif production in self.PROCESSES['fixedproc']: key = "fixedproc" else: raise ValueError("[ERROR] Process %s is not supported in STXStoEFT Model (stage %s)"%(production,self.stage)) # Give production correct scaling XSscal = "scaling_%s"%production # Check decay scaling exists: if decay in self.DECAYS: BRscal = "scaling_BR_%s"%decay else: raise ValueError("[ERROR] Decay %s is not supported in STXStoEFT Model"%decay) # Uncertainty scaling: BR and STXS bin uncertainties if( self.doSTXSU )&( self.doBRU ): THUscaler = "uncertainty_scaling_%s_%s"%(production,decay) self.modelBuilder.factory_('expr::uncertainty_scaling_%s_%s(\"@0*@1\",%s_UncertaintyScaling_%s,HiggsDecayWidth_UncertaintyScaling_%s)'%(production,decay,key,production,decay)) elif( self.doSTXSU ): THUscaler = "uncertainty_scaling_%s"%production self.modelBuilder.factory_('expr::uncertainty_scaling_%s(\"@0\",%s_UncertaintyScaling_%s)'%(production,key,production)) elif( self.doBRU ): THUscaler = "uncertainty_scaling_%s"%decay self.modelBuilder.factory_('expr::uncertainty_scaling_%s(\"@0\",HiggsDecayWidth_UncertaintyScaling_%s)'%(decay,decay)) #Combine XS and BR scaling: incuding theory unc if option selected if( self.doSTXSU )|( self.doBRU ): self.modelBuilder.factory_("prod::%s(%s)"%(name,",".join([XSscal,BRscal,THUscaler]))) else: self.modelBuilder.factory_("prod::%s(%s)"%(name,",".join([XSscal,BRscal]))) return name
class TrilinearHiggsKappaVKappaF(LHCHCGBaseModel): "assume the SM coupling but let the Higgs mass to float" def __init__(self, BRU=True): LHCHCGBaseModel.__init__(self) self.doBRU = BRU def setPhysicsOptions(self, physOptions): self.setPhysicsOptionsBase(physOptions) for po in physOptions: if po.startswith("BRU="): self.doBRU = (po.replace("BRU=", "") in ["yes", "1", "Yes", "True", "true"]) print "BR uncertainties in partial widths: %s " % self.doBRU def doParametersOfInterest(self): """Create POI out of signal strength and MH""" self.modelBuilder.doVar("kappa_V[1,0.0,2.0]") self.modelBuilder.doVar("kappa_F[1,-2.0,2.0]") self.modelBuilder.doVar("kappa_lambda[1,-20,20]") pois = 'kappa_V,kappa_F,kappa_lambda' self.doMH() self.modelBuilder.doSet("POI", pois) self.SMH = SMHiggsBuilder(self.modelBuilder) self.setup() def setup(self): #self.dobbH() # SM BR for d in SM_HIGG_DECAYS + ["hss"]: self.SMH.makeBR(d) # BR uncertainties if self.doBRU: self.SMH.makePartialWidthUncertainties() else: for d in SM_HIGG_DECAYS: self.modelBuilder.factory_( 'HiggsDecayWidth_UncertaintyScaling_%s[1.0]' % d) # fix to have all BRs add up to unity self.modelBuilder.factory_("sum::c7_SMBRs(%s)" % (",".join( "SM_BR_" + X for X in "hzz hww htt hmm hcc hbb hss hgluglu hgg hzg".split()))) self.modelBuilder.out.function("c7_SMBRs").Print("") # get VBF, tHq, tHW, ggZH cross section and resolved loops self.SMH.makeScaling('qqH', CW='kappa_V', CZ='kappa_V') self.SMH.makeScaling("tHq", CW='kappa_V', Ctop="kappa_F") self.SMH.makeScaling("tHW", CW='kappa_V', Ctop="kappa_F") self.SMH.makeScaling("ggZH", CZ='kappa_V', Ctop="kappa_F", Cb="kappa_F") self.SMH.makeScaling('ggH', Cb='kappa_F', Ctop='kappa_F', Cc="kappa_F") self.SMH.makeScaling('hgluglu', Cb='kappa_F', Ctop='kappa_F') self.SMH.makeScaling('hgg', Cb='kappa_F', Ctop='kappa_F', CW='kappa_V', Ctau='kappa_F') self.SMH.makeScaling('hzg', Cb='kappa_F', Ctop='kappa_F', CW='kappa_V', Ctau='kappa_F') cGammap = { "hgg": 0.49e-2, "hzz": 0.83e-2, "hww": 0.73e-2, "hgluglu": 0.66e-2, "htt": 0, "hbb": 0, "hcc": 0, "hmm": 0 } # First we need to create the terms that account for the self-coupling --> Just scale partial width first - https://arxiv.org/abs/1709.08649 Eq 22. # probably a better way to code this since the partial width expressions are being repeated when we write the BR for dec in cGammap.keys(): valC1 = cGammap[dec] self.modelBuilder.factory_( 'expr::kl_scalBR_%s("(@0-1)*%g",kappa_lambda)' % (dec, valC1)) # next make the partial widths, also including the kappas -> we want to include the term from the normal kappas and the one from the self-coupling self.modelBuilder.factory_( 'expr::kVkFkl_Gscal_Z("(@0*@0+@3)*@1*@2", kappa_V, SM_BR_hzz, HiggsDecayWidth_UncertaintyScaling_hzz, kl_scalBR_hzz)' ) self.modelBuilder.factory_( 'expr::kVkFkl_Gscal_W("(@0*@0+@3)*@1*@2", kappa_V, SM_BR_hww, HiggsDecayWidth_UncertaintyScaling_hww, kl_scalBR_hww)' ) self.modelBuilder.factory_( 'expr::kVkFkl_Gscal_tau("(@0*@0+@6)*@1*@4 + (@2*@2+@7)*@3*@5", kappa_F, SM_BR_htt, kappa_F, SM_BR_hmm, HiggsDecayWidth_UncertaintyScaling_htt, HiggsDecayWidth_UncertaintyScaling_hmm,kl_scalBR_htt, kl_scalBR_hmm)' ) self.modelBuilder.factory_( 'expr::kVkFkl_Gscal_top("(@0*@0+@3)*@1*@2", kappa_F, SM_BR_hcc, HiggsDecayWidth_UncertaintyScaling_hcc, kl_scalBR_hcc)' ) self.modelBuilder.factory_( 'expr::kVkFkl_Gscal_bottom("(@0*@0+@4) * (@1*@3+@2)", kappa_F, SM_BR_hbb, SM_BR_hss, HiggsDecayWidth_UncertaintyScaling_hbb, kl_scalBR_hbb)' ) self.modelBuilder.factory_( 'expr::kVkFkl_Gscal_gluon(" (@0+@3) * @1 * @2", Scaling_hgluglu, SM_BR_hgluglu, HiggsDecayWidth_UncertaintyScaling_hgluglu, kl_scalBR_hgluglu)' ) self.modelBuilder.factory_( 'expr::kVkFkl_Gscal_gamma("(@0+@6)*@1*@4 + @2*@3*@5", Scaling_hgg, SM_BR_hgg, Scaling_hzg, SM_BR_hzg, HiggsDecayWidth_UncertaintyScaling_hgg, HiggsDecayWidth_UncertaintyScaling_hzg, kl_scalBR_hgg)' ) # no kappa_lambda dependance on H->zg known yet ? # fix to have all BRs add up to unity self.modelBuilder.factory_("sum::kVkFkl_SMBRs(%s)" % (",".join( "SM_BR_" + X for X in "hzz hww htt hmm hcc hbb hss hgluglu hgg hzg".split()))) self.modelBuilder.out.function("kVkFkl_SMBRs").Print("") ## total witdh, normalized to the SM one (just the sum over the partial widths/SM total BR) self.modelBuilder.factory_( 'expr::kVkFkl_Gscal_tot("(@0+@1+@2+@3+@4+@5+@6)/@7", kVkFkl_Gscal_Z, kVkFkl_Gscal_W, kVkFkl_Gscal_tau, kVkFkl_Gscal_top, kVkFkl_Gscal_bottom, kVkFkl_Gscal_gluon, kVkFkl_Gscal_gamma, kVkFkl_SMBRs)' ) ## BRs, normalized to the SM ones: they scale as (partial/partial_SM) / (total/total_SM) self.modelBuilder.factory_( 'expr::kVkFkl_BRscal_hww("(@0*@0+@3)*@2/@1", kappa_V, kVkFkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hww, kl_scalBR_hww)' ) self.modelBuilder.factory_( 'expr::kVkFkl_BRscal_hzz("(@0*@0+@3)*@2/@1", kappa_V, kVkFkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hzz, kl_scalBR_hzz)' ) self.modelBuilder.factory_( 'expr::kVkFkl_BRscal_htt("(@0*@0+@3)*@2/@1", kappa_F, kVkFkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_htt, kl_scalBR_htt)' ) self.modelBuilder.factory_( 'expr::kVkFkl_BRscal_hmm("(@0*@0+@3)*@2/@1", kappa_F, kVkFkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hmm, kl_scalBR_hmm)' ) self.modelBuilder.factory_( 'expr::kVkFkl_BRscal_hbb("(@0*@0+@3)*@2/@1", kappa_F, kVkFkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hbb, kl_scalBR_hbb)' ) self.modelBuilder.factory_( 'expr::kVkFkl_BRscal_hcc("(@0*@0+@3)*@2/@1", kappa_F, kVkFkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hcc, kl_scalBR_hcc)' ) self.modelBuilder.factory_( 'expr::kVkFkl_BRscal_hgg("(@0+@3)*@2/@1", Scaling_hgg, kVkFkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hgg,kl_scalBR_hgg)' ) self.modelBuilder.factory_( 'expr::kVkFkl_BRscal_hzg("@0*@2/@1", Scaling_hzg, kVkFkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hzg)' ) self.modelBuilder.factory_( 'expr::kVkFkl_BRscal_hgluglu("(@0+@3)*@2/@1", Scaling_hgluglu, kVkFkl_Gscal_tot, HiggsDecayWidth_UncertaintyScaling_hgluglu, kl_scalBR_hgluglu)' ) def getHiggsSignalYieldScale(self, production, decay, energy): name = "kVkFkl_XSBRscal_%s_%s_%s" % (production, decay, energy) if self.modelBuilder.out.function(name) == None: # now make production scaling --> taken from Tab. 2 of https://arxiv.org/pdf/1607.04251v1.pdf, using formula from https://arxiv.org/pdf/1709.08649.pdf (eqn 18) cXSmap_7 = { "ggH": 0.66e-2, "qqH": 0.65e-2, "WH": 1.06e-2, "ZH": 1.23e-2, "ttH": 3.87e-2 } cXSmap_8 = { "ggH": 0.66e-2, "qqH": 0.65e-2, "WH": 1.05e-2, "ZH": 1.22e-2, "ttH": 3.78e-2 } cXSmap_13 = { "ggH": 0.66e-2, "qqH": 0.64e-2, "WH": 1.03e-2, "ZH": 1.19e-2, "ttH": 3.51e-2 } EWKmap_13 = { "ggH": 1.049, "qqH": 0.932, "WH": 0.93, "ZH": 0.947, "ttH": 1.014 } cXSmaps = {"7TeV": cXSmap_7, "8TeV": cXSmap_8, "13TeV": cXSmap_13} dZH = -1.536e-3 if production in ["ggZH", "tHq", "tHW"]: XSscal = ("@0", "Scaling_%s_%s" % (production, energy)) elif production in ["ggH", "qqH"]: C1_map = cXSmaps[energy] EWK = EWKmap_13[production] self.modelBuilder.factory_("expr::kVkFkl_XSscal_%s_%s(\"(@1+(@0-1)*%g/%g)/((1-(@0*@0-1)*%g))\",kappa_lambda,Scaling_%s_%s)"\ %(production,energy,C1_map[production],EWK,dZH,production,energy)) XSscal = ("@0", "kVkFkl_XSscal_%s_%s, " % (production, energy)) elif production in ["ZH", "WH"]: C1_map = cXSmaps[energy] EWK = EWKmap_13[production] self.modelBuilder.factory_("expr::kVkFkl_XSscal_%s_%s(\"(@1*@1+(@0-1)*%g/%g)/((1-(@0*@0-1)*%g))\",kappa_lambda,kappa_V)"\ %(production,energy,C1_map[production],EWK,dZH)) XSscal = ("@0", "kVkFkl_XSscal_%s_%s, " % (production, energy)) elif production == "ttH": C1_map = cXSmaps[energy] EWK = EWKmap_13[production] self.modelBuilder.factory_("expr::kVkFkl_XSscal_%s_%s(\"(@1*@1+(@0-1)*%g/%g)/((1-(@0*@0-1)*%g))\",kappa_lambda,kappa_F)"\ %(production,energy,C1_map[production],EWK,dZH)) XSscal = ("@0", "kVkFkl_XSscal_%s_%s, " % (production, energy)) elif production == "bbH": XSscal = ("@0*@0", "kappa_F") else: raise RuntimeError, "Production %s not supported" % production BRscal = decay if decay == "hss": BRscal = "hbb" if not self.modelBuilder.out.function("kVkFkl_BRscal_" + BRscal): raise RuntimeError, "Decay mode %s not supported" % decay self.modelBuilder.factory_( 'expr::%s("%s*@1", %s, kVkFkl_BRscal_%s)' % (name, XSscal[0], XSscal[1], BRscal)) print '[LHC-HCG Kappas]', name, production, decay, energy, ": ", self.modelBuilder.out.function(name).Print("") return name
class AllStagesToEFTModel(STXStoEFTBaseModel): def __init__(self): STXStoEFTBaseModel.__init__(self) from HiggsAnalysis.CombinedLimit.STXS import fixed_procs, stage0_procs, stage1_procs, stage1_1_procs #self.PROCESSES = {} for s in ['0','1','1_1']: if s=='0': self.PROCESSES["stage%s"%s] = [x for v in stage0_procs.itervalues() for x in v] elif s=='1': self.PROCESSES["stage%s"%s] = [x for v in stage1_procs.itervalues() for x in v] else: self.PROCESSES["stage%s"%s] = [x for v in stage1_1_procs.itervalues() for x in v] self.PROCESSES["fixedproc"] = fixed_procs self.DECAYS = ['hzz','hbb','htt','hww','hgg','hgluglu','hcc','hzg','hmm','tot'] def setPhysicsOptions(self,physOptions): self.setPhysicsOptionsBase(physOptions) def doParametersOfInterest(self): if self.floatMass: print " --> [WARNING] Floating Higgs mass selected. STXStoEFT model assumes MH=125.0 GeV" self.doMH() self.SMH = SMHiggsBuilder(self.modelBuilder) #Read in parameter list from file using textToPOIList function self.textToPOIList( os.path.join(self.SMH.datadir,'eft/HEL/pois.txt') ) POIs = ','.join(self.pois.keys()) for poi, poi_range in self.pois.iteritems(): self.modelBuilder.doVar("%s%s"%(poi,poi_range)) # Remove cWW+cB from POI list if freezing other parameters if self.freezeOtherParameters: POIs = re.sub("cWWPluscB_x02,","",POIs) self.modelBuilder.doSet("POI",POIs) #POIs for cWW and cB defined in terms of constraints on cWW+cB and cWW-cB: define expression for individual coefficient self.modelBuilder.factory_("expr::cWW_x02(\"0.5*(@0+@1)\",cWWPluscB_x02,cWWMinuscB_x02)") self.modelBuilder.factory_("expr::cB_x02(\"0.5*(@0-@1)\",cWWPluscB_x02,cWWMinuscB_x02)") self.poi_scaling['cWW'] = "0.01*cWW_x02" self.poi_scaling['cB'] = "0.01*cB_x02" # Freeze cWW+cB if freezeOtherParameters if self.freezeOtherParameters: for poi in self.pois: if 'cWWPluscB' in poi: self.modelBuilder.out.var( poi ).setConstant(True) #set up model self.setup() #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def setup(self): #For additional options e.g. STXS/BR uncertainties: defined in base class if self.doBRU: self.SMH.makePartialWidthUncertainties() if self.doSTXSU: self.makeSTXSBinUncertainties( STXSstage="all" ) # Make scaling functions for each STXS process stored = [] for s in ['1','0','1_1']: stage = "stage%s"%s # Read scaling functions for STXS bins from txt file if s=="1" and self.useLHCHXSWGNumbers: self.textToSTXSScalingFunctions( os.path.join(self.SMH.datadir, 'eft/HEL/%s_xs_LHCHXSWG-INT-2017-001.txt'%stage) ) else: if self.useExtendedVBFScheme: self.textToSTXSScalingFunctions( os.path.join(self.SMH.datadir, 'eft/HEL/%s_xs_extended_vbf.txt'%stage) ) else: self.textToSTXSScalingFunctions( os.path.join(self.SMH.datadir, 'eft/HEL/%s_xs.txt'%stage) ) for proc in self.PROCESSES[stage]: if proc not in stored: self.makeScalingFunction( proc, STXSstage=s ) stored.append( proc ) # Make dummy scaling (=1) for fixed procs for proc in self.PROCESSES["fixedproc"]: self.modelBuilder.factory_("expr::scaling_%s(\"@0\",1.)"%proc) # Read scaling functions for decays from txt files self.textToDecayScalingFunctions( os.path.join(self.SMH.datadir, 'eft/HEL/decay.txt' ) ) # Make partial width + total width scaling functions for dec in self.DECAYS: self.makeScalingFunction( dec ) # Make BR scaling functions: partial width/total width for dec in self.DECAYS: if dec != "tot": self.makeBRScalingFunction( dec ) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def getHiggsSignalYieldScale(self,production,decay,energy): # Function to convert troublesome procs into viable one for HC combination production = convert_to_STXS(production,decay) name = "stxstoeft_scaling_%s_%s_%s"%(production,decay,energy) if self.modelBuilder.out.function(name) == None: XSscal = None BRscal = None # Extract STXS stage process belongs to: in descreasing order as want most recent th. unc if production in self.PROCESSES['stage1_1']: key = "stage1_1" elif production in self.PROCESSES['stage1']: key = "stage1" elif production in self.PROCESSES['stage0']: key = "stage0" elif production in self.PROCESSES['fixedproc']: key = "fixedproc" # For fwd production: set to SM elif "fwd" in production: self.modelBuilder.factory_("expr::scaling_%s(\"@0\",1.)"%production) key = None else: print "[WARNING] Process %s is not supported in STXStoEFT Model, Setting to 1"%production return 1 #raise ValueError("[ERROR] Process %s is not supported in STXStoEFT Model"%production) # Give production correct scaling XSscal = "scaling_%s"%production # Check decay scaling exists: if decay in self.DECAYS: BRscal = "scaling_BR_%s"%decay else: print "[WARNING] Decay %s is not supported in STXStoEFT Model, setting to 1"%decay return 1 #raise ValueError("[ERROR] Decay %s is not supported in STXStoEFT Model"%decay) # Uncertainty scaling: BR and STXS bin uncertainties if( self.doSTXSU )&( self.doBRU ): if key==None: THUscaler = "uncertainty_scaling_%s"%(decay) self.modelBuilder.factory_('expr::uncertainty_scaling_%s(\"@0\",HiggsDecayWidth_UncertaintyScaling_%s)'%(decay,decay)) else: THUscaler = "uncertainty_scaling_%s_%s"%(production,decay) self.modelBuilder.factory_('expr::uncertainty_scaling_%s_%s(\"@0*@1\",%s_UncertaintyScaling_%s,HiggsDecayWidth_UncertaintyScaling_%s)'%(production,decay,key,production,decay)) elif( self.doSTXSU ): if key==None: THUscaler = "uncertainty_scaling_dummy" self.modelBuilder.factory_('expr::uncertainty_scaling_dummy(\"@0\",1.)') else: THUscaler = "uncertainty_scaling_%s"%production self.modelBuilder.factory_('expr::uncertainty_scaling_%s(\"@0\",%s_UncertaintyScaling_%s)'%(production,key,production)) elif( self.doBRU ): THUscaler = "uncertainty_scaling_%s"%decay self.modelBuilder.factory_('expr::uncertainty_scaling_%s(\"@0\",HiggsDecayWidth_UncertaintyScaling_%s)'%(decay,decay)) #Combine XS and BR scaling: incuding theory unc if option selected if( self.doSTXSU )|( self.doBRU ): self.modelBuilder.factory_("prod::%s(%s)"%(name,",".join([XSscal,BRscal,THUscaler]))) else: self.modelBuilder.factory_("prod::%s(%s)"%(name,",".join([XSscal,BRscal]))) return name