def fillMETAndDiLeptonBranches(self, event, tau1, tau2, met, met_vars): """Help function to compute variable related to the MET and visible tau candidates, and fill the corresponding branches.""" # PROPAGATE TES/LTF/JTF shift to MET (assume shift is already applied to object) if self.ismc and 't' in self.channel: if hasattr(tau1,'es') and tau1.es!=1: dp = tau1.tlv*(1.-1./tau1.es) # assume shift is already applied correctmet(met,dp) if hasattr(tau2,'es') and tau2.es!=1: dp = tau2.tlv*(1.-1./tau2.es) #print ">>> fillMETAndDiLeptonBranches: Correcting MET for es=%.3f, pt=%.3f, dpt=%.3f, gm=%d"%(tau2.es,tau2.pt,dp.Pt(),tau2.genPartFlav) correctmet(met,tau2.tlv*(1.-1./tau2.es)) tau1 = tau1.tlv # continue with TLorentzVector tau2 = tau2.tlv # continue with TLorentzVector # MET self.out.met[0] = met.Pt() self.out.metphi[0] = met.Phi() self.out.mt_1[0] = sqrt( 2*self.out.pt_1[0]*met.Pt()*(1-cos(deltaPhi(self.out.phi_1[0],met.Phi()))) ) self.out.mt_2[0] = sqrt( 2*self.out.pt_2[0]*met.Pt()*(1-cos(deltaPhi(self.out.phi_2[0],met.Phi()))) ) ###self.out.puppimetpt[0] = event.PuppiMET_pt ###self.out.puppimetphi[0] = event.PuppiMET_phi ###self.out.metsignificance[0] = event.MET_significance ###self.out.metcov00[0] = event.MET_covXX ###self.out.metcov01[0] = event.MET_covXY ###self.out.metcov11[0] = event.MET_covYY ###self.out.fixedGridRhoFastjetAll[0] = event.fixedGridRhoFastjetAll # PZETA leg1 = TVector3(tau1.Px(),tau1.Py(),0.) leg2 = TVector3(tau2.Px(),tau2.Py(),0.) zetaAxis = TVector3(leg1.Unit()+leg2.Unit()).Unit() # bisector of visible tau candidates pzetavis = leg1*zetaAxis + leg2*zetaAxis # bisector of visible ditau momentum onto zeta axis pzetamiss = met.Vect()*zetaAxis # projection of MET onto zeta axis self.out.pzetamiss[0] = pzetamiss self.out.pzetavis[0] = pzetavis self.out.dzeta[0] = pzetamiss - 0.85*pzetavis # MET SYSTEMATICS for unc, met_var in met_vars.iteritems(): getattr(self.out,"met_"+unc)[0] = met_var.Pt() getattr(self.out,"metphi_"+unc)[0] = met_var.Phi() getattr(self.out,"mt_1_"+unc)[0] = sqrt( 2 * self.out.pt_1[0] * met_var.Pt() * ( 1 - cos(deltaPhi(self.out.phi_1[0],met_var.Phi())) )) getattr(self.out,"dzeta_"+unc)[0] = met_var.Vect()*zetaAxis - 0.85*pzeta_vis # DILEPTON self.out.m_vis[0] = (tau1 + tau2).M() self.out.pt_ll[0] = (tau1 + tau2).Pt() self.out.dR_ll[0] = tau1.DeltaR(tau2) self.out.dphi_ll[0] = deltaPhi(self.out.phi_1[0], self.out.phi_2[0]) self.out.deta_ll[0] = abs(self.out.eta_1[0] - self.out.eta_2[0]) self.out.chi[0] = exp(abs(tau1.Rapidity() - tau2.Rapidity()))
def analyze(self, event): """Process event, return True (pass, go to next module) or False (fail, go to next event).""" # NO CUT self.cutflow.Fill(self.cut_none) # TRIGGER if not (event.HLT_IsoMu24 or event.HLT_IsoMu27): return False self.cutflow.Fill(self.cut_trig) ##### MUON ####################################### muons = [ ] veto_muons = [ ] for muon in Collection(event,'Muon'): good_muon = muon.mediumId and muon.pfRelIso04_all < 0.5 and abs(muon.eta) < 2.4 # and muon.dz<0.2 and muon.dxy<0.045 signal_muon = good_muon and muon.pt > 25.0 veto_muon = good_muon and muon.pt > 15.0 if signal_muon: muons.append(muon) if veto_muon: veto_muons.append(muon) if len(muons)==0: return False self.cutflow.Fill(self.cut_muon) if len(veto_muons) > 1: return False self.cutflow.Fill(self.cut_muon_veto) ##### ELECTRON ################################### electrons = [ ] veto_electrons = [ ] for electron in Collection(event,'Electron'): good_electron = electron.mvaFall17V2noIso_WPL and electron.pfRelIso03_all < 0.5 and abs(electron.eta) < 2.3 #and electron.convVeto and electron.lostHits<=1 and electron.dz<0.2 and electron.dxy<0.045 signal_electron = good_electron and electron.pt > 15.0 veto_electron = good_electron and electron.pt > 10.0 if signal_electron: electrons.append(electron) if veto_electron: veto_electrons.append(electron) if len(electrons) == 0: return False self.cutflow.Fill(self.cut_electron) if len(veto_electrons) > 1: return False self.cutflow.Fill(self.cut_electron_veto) ##### eMu PAIR ################################# dileps = [ ] for electron in electrons: for muon in muons: if muon.DeltaR(electron)<0.5: continue ltau = LeptonPair(electron,electron.pfRelIso03_all,muon,muon.pfRelIso04_all) dileps.append(ltau) if len(dileps)==0: return False electron, muon = max(dileps).pair #electron.tlv = electron.p4() #muon.tlv = muon.p4() self.cutflow.Fill(self.cut_pair) # SELECT Jets jets = [] bjets = [] self.jet_leading_pt[0]=-99 self.jet_leading_eta[0]=-99 self.jet_sub_pt[0]=-99 self.jet_sub_eta[0]=-99 self.bjet_leading_pt[0]=-99 self.bjet_leading_eta[0]=-99 self.bjet_sub_pt[0]=-99 self.bjet_sub_eta[0]=-99 for jet in Collection(event,'Jet'): veto_jet = jet.pt > 20.0 and abs(jet.eta) < 4.7 and jet.puId>0 and jet.jetId>1 if veto_jet and jet.DeltaR(muon)>=0.5 and jet.DeltaR(electron)>=0.5: veto_basic_jet = jet.pt > 30.0 if veto_basic_jet: jets.append(jet) veto_bjet = jet.btagDeepFlavB > 0.2770 and abs(jet.eta) < 2.5 if veto_bjet: bjets.append(jet) self.jetnumber[0]=len(jets) self.bjetnumber[0]=len(bjets) if self.jetnumber[0]>0: jet = max(jets,key=lambda p: p.pt) self.jet_leading_pt[0]=jet.pt self.jet_leading_eta[0]=jet.eta if self.jetnumber[0]>1: new_jets = set(jets) new_jets.remove(max(jets,key=lambda p: p.pt)) jet = max(new_jets,key=lambda p: p.pt) self.jet_sub_pt[0]=jet.pt self.jet_sub_eta[0]=jet.eta if self.bjetnumber[0]>0: bjet = max(bjets,key=lambda p: p.pt) self.bjet_leading_pt[0]=bjet.pt self.bjet_leading_eta[0]=bjet.eta if self.bjetnumber[0]>1: new_bjets = set(bjets) new_bjets.remove(max(bjets,key=lambda p: p.pt)) bjet = max(new_bjets,key=lambda p: p.pt) self.bjet_sub_pt[0]=bjet.pt self.bjet_sub_eta[0]=bjet.eta # CHOOSE MET definition # TODO section 4: compare the PuppiMET and (PF-based) MET in terms of mean, resolution and data/expectation agreement of their own distributions and of related quantities # and choose one of them for the refinement of Z to tautau selection. puppimet = Met(event, 'PuppiMET') met = Met(event, 'MET') self.metpt[0] = met.pt self.metphi[0] = met.phi self.metsumEt[0] = met.sumEt self.puppimetpt[0] = puppimet.pt self.puppimetphi[0] = puppimet.phi self.puppimetsumEt[0] = puppimet.sumEt # SAVE VARIABLES self.pt_1[0] = muon.pt self.eta_1[0] = muon.eta self.q_1[0] = muon.charge self.id_1[0] = muon.mediumId self.iso_1[0] = muon.pfRelIso04_all # keep in mind: the SMALLER the value, the more the muon is isolated self.pt_2[0] = electron.pt self.eta_2[0] = electron.eta self.q_2[0] = electron.charge self.iso_2[0] = electron.pfRelIso03_all # keep in mind: the HIGHER the value of the discriminator, the more the tau is isolated self.m_vis[0] = (muon.p4()+electron.p4()).M() ######### add high level variables ######### self.z_vis_pt[0] = (muon.p4()+electron.p4()).Pt() self.z_full_pt[0] = (puppimet.p4()+muon.p4()+electron.p4()).Pt() self.dphi[0] = deltaPhi(muon.phi , puppimet.phi ) self.mt[0] = math.sqrt( 2*muon.pt*puppimet.pt*(1 - math.cos(self.dphi[0])) ) bisec = (muon.phi + electron.phi)/2.0 self.miss_Dzeta[0] = puppimet.pt * math.cos(bisec - puppimet.phi) self.vis_Dzeta[0] = muon.pt*math.cos(bisec - muon.phi) + electron.pt*math.cos(bisec - muon.phi) self.deltaR_mu_e[0] = muon.DeltaR(electron) self.pu_rho[0] = event.fixedGridRhoFastjetAll self.npvs[0] = event.PV_npvs if self.ismc: self.genmatch_1[0] = muon.genPartFlav # in case of muons: 1 == prompt muon, 15 == muon from tau decay, also other values available for muons from jets self.genmatch_2[0] = electron.genPartFlav #Flavour of genParticle for MC matching to status==1 electrons or photons: 1 = prompt electron (including gamma*->mu mu), 15 = electron from prompt tau, 22 = prompt photon (likely conversion), 5 = electron from b, 4 = electron from c, 3 = electron from light or unknown, 0 = unmatched self.genWeight[0] = event.genWeight self.gennpus[0] = event.Pileup_nPU self.tree.Fill() return True
def fillMETAndDiLeptonBranches(self, event, tau1, tau2, met, met_vars): """Help function to compute variable related to the MET and visible tau candidates, (passed as TLorentzVectors) and fill the corresponding branches.""" # PROPAGATE LTF/JTF shift to MET (assume shift is already applied to object) if self.ismc and 'tau' in self.channel: # TODO: TES as well if self.ltf != 1.0: dp = tau2 * (1. - 1. / self.ltf) if self.channel == 'tautau': dp += tau1 * (1. - 1. / self.ltf) correctmet(met, dp) elif self.jtf != 1.0: dp = tau2 * (1. - 1. / self.jtf) if self.channel == 'tautau': dp += tau1 * (1. - 1. / self.jtf) correctmet(met, dp) # MET self.out.met[0] = met.Pt() self.out.metphi[0] = met.Phi() self.out.mt_1[0] = sqrt( 2 * self.out.pt_1[0] * met.Pt() * (1 - cos(deltaPhi(self.out.phi_1[0], met.Phi())))) self.out.mt_2[0] = sqrt( 2 * self.out.pt_2[0] * met.Pt() * (1 - cos(deltaPhi(self.out.phi_2[0], met.Phi())))) ###self.out.puppimetpt[0] = event.PuppiMET_pt ###self.out.puppimetphi[0] = event.PuppiMET_phi ###self.out.metsignificance[0] = event.MET_significance ###self.out.metcov00[0] = event.MET_covXX ###self.out.metcov01[0] = event.MET_covXY ###self.out.metcov11[0] = event.MET_covYY ###self.out.fixedGridRhoFastjetAll[0] = event.fixedGridRhoFastjetAll # PZETA leg1 = TVector3(tau1.Px(), tau1.Py(), 0.) leg2 = TVector3(tau2.Px(), tau2.Py(), 0.) zetaAxis = TVector3(leg1.Unit() + leg2.Unit()).Unit() pzetavis = leg1 * zetaAxis + leg2 * zetaAxis pzetamiss = met.Vect() * zetaAxis self.out.pzetamiss[0] = pzetamiss self.out.pzetavis[0] = pzetavis self.out.dzeta[0] = pzetamiss - 0.85 * pzetavis # MET SYSTEMATICS for unc, met_var in met_vars.iteritems(): getattr(self.out, "met_" + unc)[0] = met_var.Pt() getattr(self.out, "metphi_" + unc)[0] = met_var.Phi() getattr(self.out, "mt_1_" + unc)[0] = sqrt( 2 * self.out.pt_1[0] * met_var.Pt() * (1 - cos(deltaPhi(self.out.phi_1[0], met_var.Phi())))) getattr(self.out, "dzeta_" + unc)[0] = met_var.Vect() * zetaAxis - 0.85 * pzeta_vis # DILEPTON self.out.m_vis[0] = (tau1 + tau2).M() self.out.pt_ll[0] = (tau1 + tau2).Pt() self.out.dR_ll[0] = tau1.DeltaR(tau2) self.out.dphi_ll[0] = deltaPhi(self.out.phi_1[0], self.out.phi_2[0]) self.out.deta_ll[0] = abs(self.out.eta_1[0] - self.out.eta_2[0]) self.out.chi[0] = exp(abs(tau1.Rapidity() - tau2.Rapidity()))
def analyze(self, event): """Process event, return True (pass, go to next module) or False (fail, go to next event).""" # NO CUT self.cutflow.Fill(self.cut_none) # TRIGGER if not event.HLT_IsoMu27: return False self.cutflow.Fill(self.cut_trig) # SELECT MUON muons = [] # TODO section 4: extend with a veto of additional muons. Veto muons should have the same quality selection as signal muons (or even looser), # but with a lower pt cut, e.g. muon.pt > 15.0 veto_muons = [] for muon in Collection(event, 'Muon'): good_muon = muon.mediumId and muon.pfRelIso04_all < 0.5 and abs( muon.eta) < 2.5 signal_muon = good_muon and muon.pt > 28.0 and muon.pfIsoId >= 4 # add tight pfIsoId for signal muons, eff ~ 0.95 according to https://twiki.cern.ch/twiki/bin/view/CMS/SWGuideMuonIdRun2#Muon_Isolation veto_muon = good_muon and muon.pt > 15.0 # TODO section 4: introduce a veto muon selection here if signal_muon: muons.append(muon) if veto_muon: # CAUTION: that's NOT an elif here and intended in that way! veto_muons.append(muon) if len(muons) == 0: return False self.cutflow.Fill(self.cut_muon) # TODO section 4: What should be the requirement to veto events with additional muons? if len(veto_muons) > len(muons): return False self.cutflow.Fill(self.cut_muon_veto) # SELECT TAU # TODO section 6: Which decay modes of a tau should be considered for an analysis? Extend tau selection accordingly taus = [] for tau in Collection(event, 'Tau'): good_tau = tau.pt > 18.0 and tau.idDeepTau2017v2p1VSe >= 1 and tau.idDeepTau2017v2p1VSmu >= 1 and tau.idDeepTau2017v2p1VSjet >= 1 if good_tau: taus.append(tau) if len(taus) < 1: return False self.cutflow.Fill(self.cut_tau) # SELECT ELECTRONS FOR VETO # TODO section 4: extend the selection of veto electrons: pt > 15.0, # with loose WP of the mva based ID (Fall17 training without isolation), # and a custom isolation cut on PF based isolation using all PF candidates. electrons = [] for electron in Collection(event, 'Electron'): veto_electron = electron.mvaFall17V2Iso_WPL and electron.pt > 15.0 and electron.pfRelIso03_all < 0.5 # TODO section 4: introduce a veto electron selection here if veto_electron: electrons.append(electron) if len(electrons) > 0: return False self.cutflow.Fill(self.cut_electron_veto) # PAIR # TODO section 4 (optional): the mutau pair is constructed from a muon with highest pt and a tau with highest pt. # However, there is also the possibility to select the mutau pair according to the isolation. # If you like, you could try to implement mutau pair building algorithm, following the instructions on # https://twiki.cern.ch/twiki/bin/view/CMS/HiggsToTauTauWorking2017#Pair_Selection_Algorithm, but using the latest isolation quantities/discriminators muon = max(muons, key=lambda p: p.pt) tau = max(taus, key=lambda p: p.pt) if muon.DeltaR(tau) < 0.4: return False self.cutflow.Fill(self.cut_pair) # SELECT Jets # TODO section 4: Jets are not used directly in our analysis, but it can be good to have a look at least the number of jets (and b-tagged jets) of your selection. # Therefore, collect at first jets with pt > 20, |eta| < 4.7, passing loose WP of Pileup ID, and tight WP for jetID. # The collected jets are furthermore not allowed to overlap with the signal muon and signal tau in deltaR, so selected them to have deltaR >= 0.5 w.r.t. the signal muon and signal tau. jets = [] bjets = [] for jet in Collection(event, 'Jet'): good_jet = jet.pt > 20.0 and abs( jet.eta ) < 4.7 and jet.puId >= 4 and jet.jetId >= 2 and jet.DeltaR( tau) >= 0.5 and jet.DeltaR(muon) >= 0.5 # Then, select for this collection "usual" jets, which have pt > 30 in addition, count their number, and store pt & eta of the leading and subleading jet. if good_jet and jet.pt > 30.0: jets.append(jet) # For b-tagged jets, require additionally DeepFlavour b+bb+lepb tag with medium WP and |eta| < 2.5, count their number, and store pt & eta of the leading and subleading b-tagged jet. if good_jet and jet.btagDeepFlavB > 0.2770 and abs( jet.eta ) < 2.5: # btag WP from: https://twiki.cern.ch/twiki/bin/viewauth/CMS/BtagRecommendation102X bjets.append(jet) jets.sort(key=lambda p: p.pt, reverse=True) bjets.sort(key=lambda p: p.pt, reverse=True) self.njets[0] = len(jets) self.nbjets[0] = len(bjets) if len(jets) > 0: self.jet_pt_1[0] = jets[0].pt self.jet_eta_1[0] = jets[0].eta else: self.jet_pt_1[0] = -1. self.jet_eta_1[0] = -10. if len(jets) > 1: self.jet_pt_2[0] = jets[1].pt self.jet_eta_2[0] = jets[1].eta else: self.jet_pt_2[0] = -1. self.jet_eta_2[0] = -10. if len(bjets) > 0: self.bjet_pt_1[0] = bjets[0].pt self.bjet_eta_1[0] = bjets[0].eta else: self.bjet_pt_1[0] = -1. self.bjet_eta_1[0] = -10. if len(bjets) > 1: self.bjet_pt_2[0] = bjets[1].pt self.bjet_eta_2[0] = bjets[1].eta else: self.bjet_pt_2[0] = -1. self.bjet_eta_2[0] = -10. # Then, select for this collection "usual" jets, which have pt > 30 in addition, count their number, and store pt & eta of the leading and subleading jet. # For b-tagged jets, require additionally DeepFlavour b+bb+lepb tag with medium WP and |eta| < 2.5, count their number, and store pt & eta of the leading and subleading b-tagged jet. # CHOOSE MET definition # TODO section 4: compare the PuppiMET and (PF-based) MET in terms of mean, resolution and data/expectation agreement of their own distributions and of related quantities # and choose one of them for the refinement of Z to tautau selection. puppimet = Met(event, 'PuppiMET') met = Met(event, 'MET') # SAVE VARIABLES # TODO section 4: extend the variable list with more quantities (also high level ones). Compute at least: # - visible pt of the Z boson candidate # - best-estimate for pt of Z boson candidate (now including contribution from neutrinos) # - transverse mass of the system composed from the muon and MET vectors. Definition can be found in doi:10.1140/epjc/s10052-018-6146-9. # Caution: use ROOT DeltaPhi for difference in phi and check that deltaPhi is between -pi and pi.Have a look at transverse mass with both versions of MET # - Dzeta. Definition can be found in doi:10.1140/epjc/s10052-018-6146-9. Have a look at the variable with both versions of MET # - Separation in DeltaR between muon and tau candidate # - global event quantities like the proper definition of pileup density rho, number of reconstructed vertices, # - in case of MC: number of true (!!!) pileup interactions self.pt_1[0] = muon.pt self.eta_1[0] = muon.eta self.q_1[0] = muon.charge self.id_1[0] = muon.mediumId self.iso_1[ 0] = muon.pfRelIso04_all # keep in mind: the SMALLER the value, the more the muon is isolated self.decayMode_1[0] = self.default_int # not needed for a muon self.pt_2[0] = tau.pt self.eta_2[0] = tau.eta self.q_2[0] = tau.charge self.id_2[0] = tau.idDeepTau2017v2p1VSjet self.anti_e_2[0] = tau.idDeepTau2017v2p1VSe self.anti_mu_2[0] = tau.idDeepTau2017v2p1VSmu self.iso_2[ 0] = tau.rawDeepTau2017v2p1VSjet # keep in mind: the HIGHER the value of the discriminator, the more the tau is isolated self.decayMode_2[0] = tau.decayMode self.m_vis[0] = (muon.p4() + tau.p4()).M() self.pt_vis[0] = (muon.p4() + tau.p4()).Pt() self.met_puppimet[0] = puppimet.pt self.met_PFmet[0] = met.pt self.pt_Z_puppimet[0] = (muon.p4() + tau.p4() + puppimet.p4()).Pt() self.pt_Z_PFmet[0] = (muon.p4() + tau.p4() + met.p4()).Pt() self.mt_1_puppimet[0] = sqrt( 2 * muon.pt * puppimet.pt * (1 - cos(deltaPhi(muon.phi, puppimet.phi)))) self.mt_1_PFmet[0] = sqrt(2 * muon.pt * met.pt * (1 - cos(deltaPhi(muon.phi, met.phi)))) self.mt_2_puppimet[0] = sqrt( 2 * tau.pt * puppimet.pt * (1 - cos(deltaPhi(tau.phi, puppimet.phi)))) self.mt_2_PFmet[0] = sqrt(2 * tau.pt * met.pt * (1 - cos(deltaPhi(tau.phi, met.phi)))) # calculate dZeta leg1 = TVector3(muon.p4().Px(), muon.p4().Py(), 0.) leg2 = TVector3(tau.p4().Px(), tau.p4().Py(), 0.) zetaAxis = TVector3(leg1.Unit() + leg2.Unit()).Unit() pzetavis = leg1 * zetaAxis + leg2 * zetaAxis pzetamiss_puppi = puppimet.p4().Vect() * zetaAxis pzetamiss_PF = met.p4().Vect() * zetaAxis self.dzeta_puppimet[0] = pzetamiss_puppi - 0.85 * pzetavis self.dzeta_PFmet[0] = pzetamiss_PF - 0.85 * pzetavis self.dR_ll[0] = muon.DeltaR(tau) self.rho[0] = event.fixedGridRhoFastjetAll self.npv[0] = event.PV_npvs self.npv_good[0] = event.PV_npvsGood if self.ismc: self.genmatch_1[ 0] = muon.genPartFlav # in case of muons: 1 == prompt muon, 15 == muon from tau decay, also other values available for muons from jets self.genmatch_2[ 0] = tau.genPartFlav # in case of taus: 0 == unmatched (corresponds then to jet), # 1 == prompt electron, 2 == prompt muon, 3 == electron from tau decay, # 4 == muon from tau decay, 5 == hadronic tau decay self.genWeight[0] = event.genWeight self.npu[0] = event.Pileup_nPU self.npu_true[0] = event.Pileup_nTrueInt # get corrections self.mu_isoSF_weight[0] = self.muiso_tool.getSF(muon.pt, muon.eta) self.mu_idSF_weight[0] = self.muid_tool.getSF(muon.pt, muon.eta) self.puweight[0] = self.puWeight_tool.getPUweight( event.Pileup_nTrueInt) if self.dozpt: truthZ = [] for genPart in Collection(event, 'GenPart'): truthZboson = (genPart.pdgId == 23) and (genPart.status == 62) if truthZboson: truthZ.append(genPart) if len(truthZ) == 1: self.zptweight[0] = self.zpt_tool.getSF( truthZ[0].p4().M(), truthZ[0].p4().Pt()) self.tree.Fill() return True