def searchHNL(self, event): self.readCollections(event.input) self.counters.counter('HNL').inc('all events') # make PF candidates try: pfs = map(PhysicsObject, self.handles['pfcand'].product()) event.pfs = pfs except: print(event.eventId, event.run, event.lumi) return False #; set_trace() self.counters.counter('HNL').inc('good pf collections') # event.rho = self.handles['rho'].product()[0] ##################################################################################### # primary vertex ##################################################################################### if not len(event.goodVertices): return False self.counters.counter('HNL').inc('>0 good vtx') ##################################################################################### # produce collections and map our objects to convenient Heppy objects ##################################################################################### # make muon collections event.muons = map(Muon, self.handles['muons'].product()) # event.dsamuons = self.buildDisplacedMuons(self.handles['dsamuons'].product()) # event.dgmuons = self.buildDisplacedMuons(self.handles['dgmuons' ].product()) for imu in event.muons: imu.type = 13 imu.rho = event.rho # for imu in event.dsamuons: imu.type = 26 # for imu in event.dgmuons : imu.type = 39 # save a flag to know whether the muons is likely OOT # FIXME! for displaced too? #TODO filter rather saving the flag for imu in event.muons: imu.isoot = self.isOotMuon(imu) event.electrons = map(Electron, self.handles['electrons'].product()) # prepare the electrons for ID mangling for ele in event.electrons: ele.event = event.input.object( ) # RM: this is needed god knows why to get the eleID ele.rho = event.rho event.photons = map(Photon, self.handles['photons'].product()) event.taus = map(Tau, self.handles['taus'].product()) # make vertex objects event.pvs = self.handles['pvs'].product() event.svs = self.handles['svs'].product() event.beamspot = self.handles['beamspot'].product() # make met object event.pfmet = self.handles['pfmet'].product().at(0) event.puppimet = self.handles['puppimet'].product().at(0) # make jet object jets = map(Jet, self.handles['jets'].product()) # assign to the leptons the primary vertex, will be needed to compute a few quantities pv = event.goodVertices[0] self.assignVtx(event.muons, pv) self.assignVtx(event.electrons, pv) ##################################################################################### # filter for events with at least 3 leptons in proper flavor combination ##################################################################################### #check there are enough leptons if len(event.muons + event.electrons) < 3: return False self.counters.counter('HNL').inc('>= 3 leptons') #check there are enough leptons in the resp. flavor combination if not self.checkLeptonFlavors(event.electrons, event.muons): return False #save the key numbers event.nElectrons = len(event.electrons) event.nMuons = len(event.muons) event.nLeptons = len(event.electrons) + len(event.muons) self.counters.counter('HNL').inc( '>= 3 leptons with correct flavor combo') ##################################################################################### # Preselect electrons ##################################################################################### #FIXME: make min electron pt configurable and set the default at 5 GeV event.electrons = [ iele for iele in event.electrons if iele.pt() > 5. and abs(iele.eta()) < 2.5 ] #check there are enough leptons in the resp. flavor combination if not self.checkLeptonFlavors(event.electrons, event.muons): return False self.counters.counter('HNL').inc( 'enough electrons passing preselection') ##################################################################################### # Preselect muons ##################################################################################### event.muons = [ imu for imu in event.muons if imu.pt() > 3. and abs(imu.eta()) < 2.4 ] # event.dsamuons = [imu for imu in event.dsamuons if imu.pt()>3. and abs(imu.eta())>2.4] # event.dgmuons = [imu for imu in event.dgmuons if imu.pt()>3. and abs(imu.eta())>2.4] #check there are enough leptons in the resp. flavor combination if not self.checkLeptonFlavors(event.electrons, event.muons): return False self.counters.counter('HNL').inc('enough muons passing preselection') ##################################################################################### # Preselect the prompt leptons ##################################################################################### prompt_mu_cands = sorted( [mu for mu in event.muons if self.preselectPromptMuons(mu)], key=lambda x: x.pt(), reverse=True) prompt_ele_cands = sorted([ ele for ele in event.electrons if self.preselectPromptElectrons(ele) ], key=lambda x: x.pt(), reverse=True) if self.cfg_ana.promptLepton == 'mu': prompt_leps = prompt_mu_cands elif self.cfg_ana.promptLepton == 'ele': prompt_leps = prompt_ele_cands else: print 'ERROR: HNLAnalyzer not supported lepton flavour', self.cfg_ana.promptLepton exit(0) if not len(prompt_leps): return False self.counters.counter('HNL').inc('>0 prompt lep') ##################################################################################### # HLT matching ##################################################################################### # match only if the trigger fired and if it is among those we care about fired_triggers = [ info for info in getattr(event, 'trigger_infos', []) if info.fired and '_'.join(info.name.split('_')[:-1]) in self.cfg_ana.triggersAndFilters.keys() ] drmax = 0.15 # loop over the selected prompt leptons for ilep in prompt_leps: # prepare the HLT obj container, empty ilep.matched_hlt_obj = [] # loop over the final HLT objects of each path for info in fired_triggers: # get the HLT name w/o version hltname = '_'.join(info.name.split('_')[:-1]) # get the filter name you want to match the offline lepton to lastfilter = self.cfg_ana.triggersAndFilters[hltname] # get the corresponding HLT objects lastobjects = [ iobj for iobj in info.objects if lastfilter in [ilab for ilab in iobj.filterLabels()] ] # match HLT objects and leptons matchedobjs = [ iobj for iobj in lastobjects if deltaR(iobj, ilep) < drmax ] # extend the list of matched objects ilep.matched_hlt_obj.extend(matchedobjs) # remove duplicates through 'set' ilep.matched_hlt_obj = [iobj for iobj in set(ilep.matched_hlt_obj)] # now filter out non matched leptons prompt_leps = [ ilep for ilep in prompt_leps if len(ilep.matched_hlt_obj) > 0 ] if len(prompt_leps) == 0: return False self.counters.counter('HNL').inc('>0 trig match prompt lep') ##################################################################################### # Select the prompt lepton candidate and remove it from the collection of leptons ##################################################################################### # the collection is already sorted by pt, just take the first prompt_lep = prompt_leps[0] # remove the prompt lepton from the corresponding lepton collection that will be later used to find the displaced di-lepton event.filtered_muons = [ mu for mu in event.muons if mu.physObj != prompt_lep.physObj ] event.filtered_electrons = [ ele for ele in event.electrons if ele.physObj != prompt_lep.physObj ] ######################################################################################## # Preselection for the reco leptons and then create pairs ######################################################################################## # some simple preselection event.muons = [ imu for imu in event.filtered_muons if imu.pt() > 3. and abs(imu.eta()) < 2.4 ] # event.dsamuons = [imu for imu in event.dsamuons if imu.pt()>3. and abs(imu.eta())>2.4] # event.dgmuons = [imu for imu in event.dgmuons if imu.pt()>3. and abs(imu.eta())>2.4] event.electrons = [ iele for iele in event.filtered_electrons if iele.pt() > 5. and abs(iele.eta()) < 2.5 ] # create all the possible di-lepton pairs out of the different collections dileptons = self.makeLeptonPairs(event, event.electrons, event.muons) if not len(dileptons): return False self.counters.counter('HNL').inc('> 0 di-lepton') ######################################################################################## # Vertex Fit: Select only dilepton pairs with mutual vertices ######################################################################################## dileptonsvtx = [] for index, pair in enumerate(dileptons): if pair[0] == pair[1]: continue sv = fitVertex(pair, self.cfg_ana.L1L2LeptonType) if not sv: continue dileptonsvtx.append(DiLepton(pair, sv, pv, event.beamspot)) event.dileptonsvtx = dileptonsvtx if not len(event.dileptonsvtx): return False self.counters.counter('HNL').inc('> 0 di-lepton + vtx') ######################################################################################## # Select the most promising dilepton candidate ######################################################################################## self.selectDiLepton(event, dileptonsvtx) ######################################################################################## # Create a reco HNL3L object and "harvest" all the relevant eventinfos ######################################################################################## event.the_3lep_cand = HN3L(prompt_lep, event.displaced_dilepton_reco_cand.lep1(), event.displaced_dilepton_reco_cand.lep2(), event.pfmet) # save reco secondary vertex event.recoSv = event.displaced_dilepton_reco_cand.vtx() event.recoSv.disp3DFromBS = ROOT.VertexDistance3D().distance( event.recoSv, pv) event.recoSv.disp3DFromBS_sig = event.recoSv.disp3DFromBS.significance( ) # create an 'ideal' vertex out of the BS point = ROOT.reco.Vertex.Point( event.beamspot.position().x(), event.beamspot.position().y(), event.beamspot.position().z(), ) error = event.beamspot.covariance3D() chi2 = 0. ndof = 0. bsvtx = ROOT.reco.Vertex(point, error, chi2, ndof, 2) # size? say 3? does it matter? event.recoSv.disp2DFromBS = ROOT.VertexDistanceXY().distance( event.recoSv, bsvtx) event.recoSv.disp2DFromBS_sig = event.recoSv.disp2DFromBS.significance( ) event.recoSv.prob = ROOT.TMath.Prob(event.recoSv.chi2(), int(event.recoSv.ndof())) dilep_p4 = event.displaced_dilepton_reco_cand.lep1().p4( ) + event.displaced_dilepton_reco_cand.lep2().p4() perp = ROOT.math.XYZVector(dilep_p4.px(), dilep_p4.py(), 0.) dxybs = ROOT.GlobalPoint( -1 * ((event.beamspot.x0() - event.recoSv.x()) + (event.recoSv.z() - event.beamspot.z0()) * event.beamspot.dxdz()), -1 * ((event.beamspot.y0() - event.recoSv.y()) + (event.recoSv.z() - event.beamspot.z0()) * event.beamspot.dydz()), 0) vperp = ROOT.math.XYZVector(dxybs.x(), dxybs.y(), 0.) cos = vperp.Dot(perp) / (vperp.R() * perp.R()) event.recoSv.disp2DFromBS_cos = cos ######################################################################################## # Define event.selectedLeptons, will be used by JetAnalyzer.py ######################################################################################## # the selected 3 leptons must be leptons and not jets event.selectedLeptons = [ event.the_3lep_cand.l0(), event.the_3lep_cand.l1(), event.the_3lep_cand.l2() ] # plus any isolated electron or muon is also a good lepton rather than a jet event.selMuons = [ mu for mu in event.muons if self.preselectPromptMuons(mu, pt=10) and mu.relIsoR(R=0.3, dBetaFactor=0.5, allCharged=0) < 0.15 ] event.selElectrons = [ ele for ele in event.electrons if self.preselectPromptElectrons(ele, pt=10) and ele.relIsoR(R=0.3, dBetaFactor=0.5, allCharged=0) < 0.15 ] # RM: what about taus? event.selectedLeptons += event.selMuons event.selectedLeptons += event.selElectrons ######################################################################################## # Extra prompt and isolated lepton veto ######################################################################################## event.veto_mus = [ mu for mu in event.selMuons if mu.physObj not in [ event.the_3lep_cand.l0().physObj, event.the_3lep_cand.l1().physObj, event.the_3lep_cand.l2().physObj ] ] event.veto_eles = [ ele for ele in event.selElectrons if ele.physObj not in [ event.the_3lep_cand.l0().physObj, event.the_3lep_cand.l1().physObj, event.the_3lep_cand.l2().physObj ] ] #FIXME: Is this step really needed? if len(event.veto_eles): event.veto_save_ele = sorted([ele for ele in event.veto_eles], key=lambda x: x.pt, reverse=True)[0] if len(event.veto_mus): event.veto_save_mu = sorted([mu for mu in event.veto_mus], key=lambda x: x.pt, reverse=True)[0] ######################################################################################## # charged PF isolation ######################################################################################## event.the_3lep_cand.abs_tot_iso03_rhoArea = totIso( event, 'rhoArea', 0.3) event.the_3lep_cand.abs_tot_iso04_rhoArea = totIso( event, 'rhoArea', 0.4) event.the_3lep_cand.abs_tot_iso05_rhoArea = totIso( event, 'rhoArea', 0.5) event.the_3lep_cand.rel_tot_iso03_rhoArea = event.the_3lep_cand.abs_tot_iso03_rhoArea / event.the_3lep_cand.hnVisP4( ).pt() event.the_3lep_cand.rel_tot_iso04_rhoArea = event.the_3lep_cand.abs_tot_iso04_rhoArea / event.the_3lep_cand.hnVisP4( ).pt() event.the_3lep_cand.rel_tot_iso05_rhoArea = event.the_3lep_cand.abs_tot_iso05_rhoArea / event.the_3lep_cand.hnVisP4( ).pt() event.the_3lep_cand.abs_tot_iso03_deltaBeta = totIso( event, 'dBeta', 0.3) event.the_3lep_cand.abs_tot_iso04_deltaBeta = totIso( event, 'dBeta', 0.4) event.the_3lep_cand.abs_tot_iso05_deltaBeta = totIso( event, 'dBeta', 0.5) event.the_3lep_cand.rel_tot_iso03_deltaBeta = event.the_3lep_cand.abs_tot_iso03_deltaBeta / event.the_3lep_cand.hnVisP4( ).pt() event.the_3lep_cand.rel_tot_iso04_deltaBeta = event.the_3lep_cand.abs_tot_iso04_deltaBeta / event.the_3lep_cand.hnVisP4( ).pt() event.the_3lep_cand.rel_tot_iso05_deltaBeta = event.the_3lep_cand.abs_tot_iso05_deltaBeta / event.the_3lep_cand.hnVisP4( ).pt() ##################################################################################### # After passing all selections and we have an HNL candidate, pass a "true" boolean! ##################################################################################### return True
def process(self, event): self.readCollections(event.input) self.counters.counter('HNLGenTree').inc('all events') # produce collections event.genp_pruned = self.mchandles['genp_pruned'].product() event.genp_packed = self.mchandles['genp_packed'].product() # no point to run this if it's not a HNL signal if 'HN3L' not in self.cfg_comp.name: return True # for pp in event.genp_packed: # printer = lambda : 'pat::PackedGenParticle: %d, pt %.2f, eta %.2f, phi %.2f, mass %.2f, status %d' %(pp.pdgId(), pp.pt(), pp.eta(), pp.phi(), pp.mass(), pp.status()) # import pdb ; pdb.set_trace() # pp.__str__ = printer # import pdb ; pdb.set_trace() # # import pdb ; pdb.set_trace() # all gen particles event.genp = [ip for ip in event.genp_pruned ] + [ip for ip in event.genp_packed] # get the heavy neutrino the_hns = [ ip for ip in event.genp_pruned if abs(ip.pdgId()) == 9900012 and ip.isLastCopy() ] event.the_hn = the_hns[0] # one per event # prompt lepton event.the_pl = map(GenParticle, [ ip for ip in event.genp_pruned if abs(ip.pdgId()) in [11, 13] and ip.isPromptFinalState() and not isAncestor(event.the_hn, ip) ])[0] # get the immediate daughters of the heavy neutrino decay event.the_hn.initialdaus = [ event.the_hn.daughter(jj) for jj in range(event.the_hn.numberOfDaughters()) ] event.the_hn.lep1 = max([ ii for ii in event.the_hn.initialdaus if abs(ii.pdgId()) in [11, 13] ], key=lambda x: x.pt()) event.the_hn.lep2 = min([ ii for ii in event.the_hn.initialdaus if abs(ii.pdgId()) in [11, 13] ], key=lambda x: x.pt()) try: event.the_hn.neu = [ ii for ii in event.the_hn.initialdaus if abs(ii.pdgId()) in [12, 14] ][0] # there can be only one except: print( '\t<< event.the_hn.neu = [ii for ii in event.the_hn.initialdaus if abs(ii.pdgId()) in [12, 14]][0] >> crashes\n\tevent:', event.eventId, event.run, event.lumi) self.counters.counter('HNLGenTree').inc('bad events') # set_trace() return False # FIXME there is some problem with the line above in the new signal samples # identify the secondary vertex event.the_hn.the_sv = event.the_hn.lep1.vertex() # need to analyse the lepton after they radiated / converted for ip in [event.the_hn.lep1, event.the_hn.lep2, event.the_pl]: finaldaus = [] for ipp in event.genp_packed: # for ipp in event.genp: # try with all particles, see if it makes sense mother = ipp.mother(0) if mother and isAncestor(ip, mother): finaldaus.append(ipp) ip.finaldaughters = sorted(finaldaus, key=lambda x: x.pt(), reverse=True) ip.hasConvOrRad = (len(ip.finaldaughters) > 1) if len(ip.finaldaughters) > 1: try: ip.finallep = max([ ii for ii in ip.finaldaughters if ii.pdgId() == ip.pdgId() ], key=lambda x: x.pt()) except: try: ip.finallep = max([ ii for ii in ip.finaldaughters if abs(ii.pdgId()) in [11, 13] ], key=lambda x: x.pt()) except: return False # import pdb ; pdb.set_trace() else: ip.finallep = ip # 4-momentum of the visible part of the HN event.the_hn.vishn = event.the_hn.lep1.finallep.p4( ) + event.the_hn.lep2.finallep.p4() # make it into a handy class event.the_hnl = HN3L(event.the_pl.finallep, event.the_hn.lep1.finallep, event.the_hn.lep2.finallep, event.the_hn.neu) return True
def process(self, event): # decide whether filter the event or pass through # useful to run multiple final states at the same time return_statement = getattr(self.cfg_ana, 'pass_through', False) # initiate a flag setattr( event, 'pass_%s' % (self.cfg_ana.promptLepton + self.cfg_ana.L1L2LeptonType), False) self.readCollections(event.input) self.counters.counter('HNL').inc('all events') ##################################################################################### # primary vertex ##################################################################################### if not len(event.goodVertices): return return_statement self.counters.counter('HNL').inc('>0 good vtx') ##################################################################################### # produce collections and map our objects to convenient Heppy objects ##################################################################################### # make muon collections event.muons = map(Muon, self.handles['muons'].product()) # event.dsamuons = self.buildDisplacedMuons(self.handles['dsamuons'].product()) # event.dgmuons = self.buildDisplacedMuons(self.handles['dgmuons' ].product()) for imu in event.muons: imu.type = 13 imu.rho = event.rho # for imu in event.dsamuons: imu.type = 26 # for imu in event.dgmuons : imu.type = 39 # save a flag to know whether the muons is likely OOT # FIXME! for displaced too? for imu in event.muons: imu.isoot = self.isOotMuon(imu) event.electrons = map(Electron, self.handles['electrons'].product()) # prepare the electrons for ID mangling for ele in event.electrons: ele.event = event.input.object( ) # RM: this is needed god knows why to get the eleID ele.rho = event.rho # assign to the leptons the primary vertex, will be needed to compute a few quantities pv = event.goodVertices[0] self.assignVtx(event.muons, pv) self.assignVtx(event.electrons, pv) ##################################################################################### # filter for events with at least 3 leptons in proper flavor combination ##################################################################################### event.muons = [ imu for imu in event.muons if self.cfg_ana.muon_preselection(imu) ] event.electrons = [ iele for iele in event.electrons if self.cfg_ana.ele_preselection(iele) ] if not self.checkLeptonFlavors(event.electrons, event.muons): return return_statement self.counters.counter('HNL').inc('>= 3L w/ correct flavours') ##################################################################################### # finish reading collections ##################################################################################### # make vertex objects event.beamspot = self.handles['beamspot'].product() # make met object event.pfmet = self.handles['pfmet'].product().at(0) event.puppimet = self.handles['puppimet'].product().at(0) ##################################################################################### # Preselect the prompt leptons ##################################################################################### prompt_mu_cands = sorted([ mu for mu in event.muons if self.preselectPromptMuons(mu, isoAE03cut=0.2) ], key=lambda x: x.pt(), reverse=True) prompt_ele_cands = sorted([ ele for ele in event.electrons if self.preselectPromptElectrons(ele, isoAE03cut=0.2) ], key=lambda x: x.pt(), reverse=True) if self.cfg_ana.promptLepton == 'm': prompt_leps = prompt_mu_cands elif self.cfg_ana.promptLepton == 'e': prompt_leps = prompt_ele_cands else: print 'ERROR: HNLAnalyzer not supported lepton flavour', self.cfg_ana.promptLepton sys.exit(0) if not len(prompt_leps): return return_statement self.counters.counter('HNL').inc('>0 prompt lep') ##################################################################################### # HLT matching ##################################################################################### # match only if the trigger fired and if it is among those we care about # set_trace() fired_triggers = [ info for info in getattr(event, 'trigger_infos', []) if info.fired and '_'.join(info.name.split('_')[:-1]) in self.cfg_ana.triggersAndFilters.keys() ] drmax = getattr(self.cfg_ana, 'dr_max', 0.15) # loop over the selected prompt leptons for ilep in prompt_leps: # prepare the HLT obj container, empty ilep.matched_hlt_obj = [] # loop over the final HLT objects of each path for info in fired_triggers: # get the HLT name w/o version hltname = '_'.join(info.name.split('_')[:-1]) # get the filter name you want to match the offline lepton to lastfilter = self.cfg_ana.triggersAndFilters[hltname] # get the corresponding HLT objects lastobjects = [ iobj for iobj in info.objects if lastfilter in [ilab for ilab in iobj.filterLabels()] ] # match HLT objects and leptons matchedobjs = [ iobj for iobj in lastobjects if deltaR(iobj, ilep) < drmax ] # extend the list of matched objects ilep.matched_hlt_obj.extend(matchedobjs) # set_trace() # remove duplicates through 'set' ilep.matched_hlt_obj = [iobj for iobj in set(ilep.matched_hlt_obj)] # now filter out non matched leptons prompt_leps = [ ilep for ilep in prompt_leps if len(ilep.matched_hlt_obj) > 0 ] if len(prompt_leps) == 0: return return_statement self.counters.counter('HNL').inc('>0 trig match prompt lep') ##################################################################################### # Select the prompt lepton candidate and remove it from the collection of leptons ##################################################################################### # the collection is already sorted by pt, just take the first prompt_lep = prompt_leps[0] # remove the prompt lepton from the corresponding lepton collection that will be later used to find the displaced di-lepton event.filtered_muons = [ mu for mu in event.muons if mu.physObj != prompt_lep.physObj ] event.filtered_electrons = [ ele for ele in event.electrons if ele.physObj != prompt_lep.physObj ] ######################################################################################## # Create pairs ######################################################################################## # create all the possible di-lepton pairs out of the different collections # AFTER having removed the prompt lepton dileptons = self.makeLeptonPairs(event, event.filtered_electrons, event.filtered_muons) if not len(dileptons): return return_statement self.counters.counter('HNL').inc('> 0 di-lepton') ######################################################################################## # Vertex Fit: Select only dilepton pairs with mutual vertices ######################################################################################## dileptonsvtx = [] for index, pair in enumerate(dileptons): if pair[0] == pair[1]: continue sv = fitVertex(pair, self.cfg_ana.L1L2LeptonType) if not sv: continue dileptonsvtx.append(DiLepton(pair, sv, pv, event.beamspot)) ######################################################################################## # skim the dilepton candidates ######################################################################################## dilep_selector = getattr(self.cfg_ana, 'dilepton_selector', True) dileptonsvtx = [ idilep for idilep in dileptonsvtx if dilep_selector(idilep) ] if not len(dileptonsvtx): return return_statement event.dileptonsvtx = dileptonsvtx final_state = self.cfg_ana.promptLepton + self.cfg_ana.L1L2LeptonType if not getattr(event, 'dileptonsvtx_dict', False): event.dileptonsvtx_dict = OrderedDict() event.dileptonsvtx_dict[final_state] = event.dileptonsvtx if not len(event.dileptonsvtx): return return_statement self.counters.counter('HNL').inc('> 0 di-lepton + vtx') ######################################################################################## # Select the most promising dilepton candidate ######################################################################################## event.displaced_dilepton_reco_cand = self.selectDiLepton( event, dileptonsvtx) ######################################################################################## # Create a reco HNL3L object and "harvest" all the relevant eventinfos ######################################################################################## event.the_3lep_cand = HN3L(prompt_lep, event.displaced_dilepton_reco_cand.lep1(), event.displaced_dilepton_reco_cand.lep2(), event.pfmet) if not getattr(event, 'the_3lep_cand_dict', False): event.the_3lep_cand_dict = OrderedDict() event.the_3lep_cand_dict[final_state] = event.the_3lep_cand # save reco secondary vertex event.recoSv = event.displaced_dilepton_reco_cand.vtx() event.recoSv.disp3DFromBS = ROOT.VertexDistance3D().distance( event.recoSv, pv) event.recoSv.disp3DFromBS_sig = event.recoSv.disp3DFromBS.significance( ) # create an 'ideal' vertex out of the BS point = ROOT.reco.Vertex.Point( event.beamspot.position().x(), event.beamspot.position().y(), event.beamspot.position().z(), ) error = event.beamspot.covariance3D() chi2 = 0. ndof = 0. bsvtx = ROOT.reco.Vertex(point, error, chi2, ndof, 2) # size? say 3? does it matter? event.recoSv.disp2DFromBS = ROOT.VertexDistanceXY().distance( event.recoSv, bsvtx) event.recoSv.disp2DFromBS_sig = event.recoSv.disp2DFromBS.significance( ) event.recoSv.prob = ROOT.TMath.Prob(event.recoSv.chi2(), int(event.recoSv.ndof())) dilep_p4 = event.displaced_dilepton_reco_cand.lep1().p4( ) + event.displaced_dilepton_reco_cand.lep2().p4() perp = ROOT.math.XYZVector(dilep_p4.px(), dilep_p4.py(), 0.) dxybs = ROOT.GlobalPoint( -1 * ((event.beamspot.x0() - event.recoSv.x()) + (event.recoSv.z() - event.beamspot.z0()) * event.beamspot.dxdz()), -1 * ((event.beamspot.y0() - event.recoSv.y()) + (event.recoSv.z() - event.beamspot.z0()) * event.beamspot.dydz()), 0) vperp = ROOT.math.XYZVector(dxybs.x(), dxybs.y(), 0.) cos = vperp.Dot(perp) / (vperp.R() * perp.R()) event.recoSv.disp2DFromBS_cos = cos if not getattr(event, 'recoSv_dict', False): event.recoSv_dict = OrderedDict() event.recoSv_dict[final_state] = event.recoSv ######################################################################################## # Define event.selectedLeptons, will be used by JetAnalyzer.py ######################################################################################## # different selected leptons for different final states if not hasattr(event, 'selectedLeptons'): event.selectedLeptons = OrderedDict() # final state, as configured final_state = self.cfg_ana.promptLepton + self.cfg_ana.L1L2LeptonType # the selected 3 leptons must be leptons and not jets event.selectedLeptons[final_state] = [ event.the_3lep_cand.l0(), event.the_3lep_cand.l1(), event.the_3lep_cand.l2() ] # plus any isolated electron or muon is also a good lepton rather than a jet event.selMuons = [ mu for mu in event.muons if self.preselectPromptMuons(mu, pt=10) and mu.relIso(cone_size=0.3, iso_type='dbeta', dbeta_factor=0.5, all_charged=0) < 0.15 and mu not in event.selectedLeptons[final_state] ] event.selElectrons = [ ele for ele in event.electrons if self.preselectPromptElectrons(ele, pt=10) and ele.relIso(cone_size=0.3, iso_type='dbeta', dbeta_factor=0.5, all_charged=0) < 0.15 and ele not in event.selectedLeptons[final_state] ] # RM: what about taus? event.selectedLeptons[final_state] += event.selMuons event.selectedLeptons[final_state] += event.selElectrons ######################################################################################## # Extra prompt and isolated lepton veto ######################################################################################## event.veto_mus = [ mu for mu in event.selMuons if mu.physObj not in [ event.the_3lep_cand.l0().physObj, event.the_3lep_cand.l1().physObj, event.the_3lep_cand.l2().physObj ] ] event.veto_eles = [ ele for ele in event.selElectrons if ele.physObj not in [ event.the_3lep_cand.l0().physObj, event.the_3lep_cand.l1().physObj, event.the_3lep_cand.l2().physObj ] ] # FIXME!: Is this step really needed? if len(event.veto_eles): event.veto_save_ele = sorted([ele for ele in event.veto_eles], key=lambda x: x.pt, reverse=True)[0] if len(event.veto_mus): event.veto_save_mu = sorted([mu for mu in event.veto_mus], key=lambda x: x.pt, reverse=True)[0] ##################################################################################### # After passing all selections and we have an HNL candidate, pass a "true" boolean! ##################################################################################### # save a flag setattr( event, 'pass_%s' % (self.cfg_ana.promptLepton + self.cfg_ana.L1L2LeptonType), True) # print '\n\n' + '='*50 # print 'run %d \t lumi %d events %d' %(event.run, event.lumi, event.eventId) # print '\tmmm candidate charge12', event.the_3lep_cand_dict['mmm'].charge12() # print '\tmmm candidate mass12 ', event.the_3lep_cand_dict['mmm'].mass12() # print '\tmmm candidate cos ', event.recoSv_dict['mmm'].disp2DFromBS_cos # print '\t\t',event.the_3lep_cand_dict['mmm'].l0() # print '\t\t',event.the_3lep_cand_dict['mmm'].l1() # print '\t\t',event.the_3lep_cand_dict['mmm'].l2() # for ii in map(Muon, self.handles['muons'].product()): print ii # import pdb ; pdb.set_trace() return True
def process(self, event): self.readCollections(event.input) self.counters.counter('HNLGenTree').inc('all events') # produce collections event.genp_pruned = self.mchandles['genp_pruned'].product() event.genp_packed = self.mchandles['genp_packed'].product() # all gen particles event.genp = [ip for ip in event.genp_pruned ] + [ip for ip in event.genp_packed] # get the heavy neutrino the_hns = [ ip for ip in event.genp_pruned if abs(ip.pdgId()) == 9900012 and ip.isLastCopy() ] event.the_hn = the_hns[0] # one per event # prompt lepton event.the_pl = map(GenParticle, [ ip for ip in event.genp_pruned if abs(ip.pdgId()) in [11, 13] and ip.isPromptFinalState() and not isAncestor(event.the_hn, ip) ])[0] # get the immediate daughters of the heavy neutrino decay event.the_hn.initialdaus = [ event.the_hn.daughter(jj) for jj in range(event.the_hn.numberOfDaughters()) ] event.the_hn.lep1 = max([ ii for ii in event.the_hn.initialdaus if abs(ii.pdgId()) in [11, 13] ], key=lambda x: x.pt()) event.the_hn.lep2 = min([ ii for ii in event.the_hn.initialdaus if abs(ii.pdgId()) in [11, 13] ], key=lambda x: x.pt()) event.the_hn.neu = [ ii for ii in event.the_hn.initialdaus if abs(ii.pdgId()) in [12, 14] ][0] # there can be only one # identify the secondary vertex event.the_hn.the_sv = event.the_hn.lep1.vertex() # need to analyse the lepton after they radiated / converted for ip in [event.the_hn.lep1, event.the_hn.lep2, event.the_pl]: finaldaus = [] for ipp in event.genp_packed: # for ipp in event.genp: # try with all particles, see if it makes sense mother = ipp.mother(0) if mother and isAncestor(ip, mother): finaldaus.append(ipp) ip.finaldaughters = sorted(finaldaus, key=lambda x: x.pt(), reverse=True) ip.hasConvOrRad = (len(ip.finaldaughters) > 1) if len(ip.finaldaughters) > 1: try: ip.finallep = max([ ii for ii in ip.finaldaughters if ii.pdgId() == ip.pdgId() ], key=lambda x: x.pt()) except: try: ip.finallep = max([ ii for ii in ip.finaldaughters if abs(ii.pdgId()) in [11, 13] ], key=lambda x: x.pt()) except: return False # import pdb ; pdb.set_trace() else: ip.finallep = ip # 4-momentum of the visible part of the HN event.the_hn.vishn = event.the_hn.lep1.finallep.p4( ) + event.the_hn.lep2.finallep.p4() # make it into a handy class event.the_hnl = HN3L(event.the_pl.finallep, event.the_hn.lep1.finallep, event.the_hn.lep2.finallep, event.the_hn.neu) return True
def process(self, event): self.readCollections(event.input) self.counters.counter('HNL').inc('all events') # make PF candidates try: pfs = map(PhysicsObject, self.handles['pfcand'].product()) # set_trace() # return False except: print(event.eventId, event.run, event.lumi); return False#; set_trace() self.counters.counter('HNL').inc('good collections') ##################################################################################### # primary vertex ##################################################################################### if not len(event.goodVertices): return False self.counters.counter('HNL').inc('>0 good vtx') ##################################################################################### # produce collections and map our objects to convenient Heppy objects ##################################################################################### # make muon collections event.muons = map(Muon, self.handles['muons'].product()) # event.dsamuons = self.buildDisplacedMuons(self.handles['dsamuons'].product()) # event.dgmuons = self.buildDisplacedMuons(self.handles['dgmuons' ].product()) for imu in event.muons : imu.type = 13 # for imu in event.dsamuons: imu.type = 26 # for imu in event.dgmuons : imu.type = 39 # save a flag to know whether the muons is likely OOT # FIXME! for displaced too? for imu in event.muons: imu.isoot = self.isOotMuon(imu) event.electrons = map(Electron, self.handles['electrons'].product()) # prepare the electrons for ID mangling for ele in event.electrons: ele.event = event.input.object() # RM: this is needed god knows why to get the eleID ele.rho = event.rho event.photons = map(Photon , self.handles['photons' ].product()) event.taus = map(Tau , self.handles['taus' ].product()) # make vertex objects event.pvs = self.handles['pvs' ].product() event.svs = self.handles['svs' ].product() event.beamspot = self.handles['beamspot'].product() # make met object event.pfmet = self.handles['pfmet' ].product().at(0) event.puppimet = self.handles['puppimet'].product().at(0) # make jet object jets = map(Jet, self.handles['jets'].product()) # assign to the leptons the primary vertex, will be needed to compute a few quantities pv = event.goodVertices[0] self.assignVtx(event.muons , pv) self.assignVtx(event.electrons, pv) ##################################################################################### # Preselect the prompt leptons ##################################################################################### prompt_mu_cands = sorted([mu for mu in event.muons if self.preselectPromptMuons (mu) ], key = lambda x : x.pt(), reverse = True) prompt_ele_cands = sorted([ele for ele in event.electrons if self.preselectPromptElectrons(ele)], key = lambda x : x.pt(), reverse = True) if self.cfg_ana.promptLepton=='mu': prompt_leps = prompt_mu_cands elif self.cfg_ana.promptLepton=='ele': prompt_leps = prompt_ele_cands else: print 'ERROR: HNLAnalyzer not supported lepton flavour', self.cfg_ana.promptLepton exit(0) if not len(prompt_leps): return False self.counters.counter('HNL').inc('>0 prompt lep') ##################################################################################### # HLT matching ##################################################################################### # match only if the trigger fired and if it is among those we care about fired_triggers = [info for info in getattr(event, 'trigger_infos', []) if info.fired and '_'.join(info.name.split('_')[:-1]) in self.cfg_ana.triggersAndFilters.keys()] drmax=0.15 # loop over the selected prompt leptons for ilep in prompt_leps: # prepare the HLT obj container, empty ilep.matched_hlt_obj = [] # loop over the final HLT objects of each path for info in fired_triggers: # get the HLT name w/o version hltname = '_'.join(info.name.split('_')[:-1]) # get the filter name you want to match the offline lepton to lastfilter = self.cfg_ana.triggersAndFilters[hltname] # get the corresponding HLT objects lastobjects = [iobj for iobj in info.objects if lastfilter in [ilab for ilab in iobj.filterLabels()]] # match HLT objects and leptons matchedobjs = [iobj for iobj in lastobjects if deltaR(iobj, ilep)<drmax] # extend the list of matched objects ilep.matched_hlt_obj.extend(matchedobjs) # remove duplicates through 'set' ilep.matched_hlt_obj = [iobj for iobj in set(ilep.matched_hlt_obj)] # now filter out non matched leptons prompt_leps = [ilep for ilep in prompt_leps if len(ilep.matched_hlt_obj)>0] if len(prompt_leps)==0: return False self.counters.counter('HNL').inc('>0 trig match prompt lep') ##################################################################################### # Select the prompt lepton candidate and remove it from the collection of leptons ##################################################################################### # the collection is already sorted by pt, just take the first prompt_lep = prompt_leps[0] # remove the prompt lepton from the corresponding lepton collection that will be later used to find the displaced di-lepton event.filtered_muons = [mu for mu in event.muons if mu.physObj != prompt_lep.physObj] event.filtered_electrons = [ele for ele in event.electrons if ele.physObj != prompt_lep.physObj] ######################################################################################## # Preselection for the reco muons before pairing them ######################################################################################## # some simple preselection event.muons = [imu for imu in event.filtered_muons if imu.pt()>3. and abs(imu.eta())<2.4] # event.dsamuons = [imu for imu in event.dsamuons if imu.pt()>3. and abs(imu.eta())>2.4] # event.dgmuons = [imu for imu in event.dgmuons if imu.pt()>3. and abs(imu.eta())>2.4] # create all the possible di-muon pairs out of the three different collections # FIXME! configure which collections to use # dimuons = combinations(event.muons + event.dsamuons + event.dgmuons, 2) dimuons = combinations(event.muons, 2) dimuons = [(mu1, mu2) for mu1, mu2 in dimuons if deltaR(mu1, mu2)>0.01] if not len(dimuons): return False self.counters.counter('HNL').inc('> 0 di-muon') ######################################################################################## # Vertex Fit: Select only dimuon pairs with mutual vertices ######################################################################################## dimuonsvtx = [] for index, pair in enumerate(dimuons): if pair[0]==pair[1]: continue sv = fitVertex(pair) if not sv: continue dimuonsvtx.append(DiLepton(pair, sv, pv, event.beamspot)) event.dimuonsvtx = dimuonsvtx if not len(event.dimuonsvtx): return False self.counters.counter('HNL').inc('> 0 di-muon + vtx') ######################################################################################## # candidate choice by different criteria ######################################################################################## which_candidate = getattr(self.cfg_ana, 'candidate_selection', 'maxpt') if which_candidate == 'minmass' : event.displaced_dilepton_reco_cand = None if not len(dimuonsvtx) else sorted(dimuonsvtx, key = lambda x : (x.isSS(), x.mass() ), reverse=False)[0] if which_candidate == 'minchi2' : event.displaced_dilepton_reco_cand = None if not len(dimuonsvtx) else sorted(dimuonsvtx, key = lambda x : (x.isSS(), x.chi2() ), reverse=False)[0] if which_candidate == 'mindr' : event.displaced_dilepton_reco_cand = None if not len(dimuonsvtx) else sorted(dimuonsvtx, key = lambda x : (x.isSS(), x.dr() ), reverse=False)[0] if which_candidate == 'maxdphi' : event.displaced_dilepton_reco_cand = None if not len(dimuonsvtx) else sorted(dimuonsvtx, key = lambda x : (x.isSS(), -x.dphi() ), reverse=False)[0] if which_candidate == 'mindeta' : event.displaced_dilepton_reco_cand = None if not len(dimuonsvtx) else sorted(dimuonsvtx, key = lambda x : (x.isSS(), x.deta() ), reverse=False)[0] if which_candidate == 'maxdisp2dbs': event.displaced_dilepton_reco_cand = None if not len(dimuonsvtx) else sorted(dimuonsvtx, key = lambda x : (x.isSS(), -x.disp2DFromBS() ), reverse=False)[0] if which_candidate == 'maxdisp2dpv': event.displaced_dilepton_reco_cand = None if not len(dimuonsvtx) else sorted(dimuonsvtx, key = lambda x : (x.isSS(), -x.disp2DFromPV() ), reverse=False)[0] if which_candidate == 'maxdisp3dpv': event.displaced_dilepton_reco_cand = None if not len(dimuonsvtx) else sorted(dimuonsvtx, key = lambda x : (x.isSS(), -x.disp3DFromPV() ), reverse=False)[0] if which_candidate == 'maxdls2dbs' : event.displaced_dilepton_reco_cand = None if not len(dimuonsvtx) else sorted(dimuonsvtx, key = lambda x : (x.isSS(), -x.disp2DFromBSSignificance() ), reverse=False)[0] if which_candidate == 'maxdls2dpv' : event.displaced_dilepton_reco_cand = None if not len(dimuonsvtx) else sorted(dimuonsvtx, key = lambda x : (x.isSS(), -x.disp2DFromPVSignificance() ), reverse=False)[0] if which_candidate == 'maxdls3dpv' : event.displaced_dilepton_reco_cand = None if not len(dimuonsvtx) else sorted(dimuonsvtx, key = lambda x : (x.isSS(), -x.disp3DFromPVSignificance() ), reverse=False)[0] if which_candidate == 'maxcos' : event.displaced_dilepton_reco_cand = None if not len(dimuonsvtx) else sorted(dimuonsvtx, key = lambda x : (x.isSS(), -x.cosTransversePointingAngleBS()), reverse=False)[0] if which_candidate == 'maxpt' : event.displaced_dilepton_reco_cand = None if not len(dimuonsvtx) else sorted(dimuonsvtx, key = lambda x : (x.isSS(), -x.pt() ), reverse=False)[0] ######################################################################################## # Create a reco HNL3L object ######################################################################################## event.the_3lep_cand = HN3L(prompt_lep, event.displaced_dilepton_reco_cand.lep1(), event.displaced_dilepton_reco_cand.lep2(), event.pfmet) # save reco secondary vertex event.recoSv = event.displaced_dilepton_reco_cand.vtx() event.recoSv.disp3DFromBS = ROOT.VertexDistance3D().distance(event.recoSv, pv) event.recoSv.disp3DFromBS_sig = event.recoSv.disp3DFromBS.significance() # create an 'ideal' vertex out of the BS point = ROOT.reco.Vertex.Point( event.beamspot.position().x(), event.beamspot.position().y(), event.beamspot.position().z(), ) error = event.beamspot.covariance3D() chi2 = 0. ndof = 0. bsvtx = ROOT.reco.Vertex(point, error, chi2, ndof, 2) # size? say 3? does it matter? event.recoSv.disp2DFromBS = ROOT.VertexDistanceXY().distance(event.recoSv, bsvtx) event.recoSv.disp2DFromBS_sig = event.recoSv.disp2DFromBS.significance() event.recoSv.prob = ROOT.TMath.Prob(event.recoSv.chi2(), int(event.recoSv.ndof())) dilep_p4 = event.displaced_dilepton_reco_cand.lep1().p4() + event.displaced_dilepton_reco_cand.lep2().p4() perp = ROOT.math.XYZVector(dilep_p4.px(), dilep_p4.py(), 0.) dxybs = ROOT.GlobalPoint(-1*((event.beamspot.x0() - event.recoSv.x()) + (event.recoSv.z() - event.beamspot.z0()) * event.beamspot.dxdz()), -1*((event.beamspot.y0() - event.recoSv.y()) + (event.recoSv.z() - event.beamspot.z0()) * event.beamspot.dydz()), 0) vperp = ROOT.math.XYZVector(dxybs.x(), dxybs.y(), 0.) cos = vperp.Dot(perp)/(vperp.R()*perp.R()) event.recoSv.disp2DFromBS_cos = cos ######################################################################################## # Define event.selectedLeptons, will be used by JetAnalyzer.py ######################################################################################## # the selected 3 leptons must be leptons and not jets event.selectedLeptons = [event.the_3lep_cand.l0(), event.the_3lep_cand.l1(), event.the_3lep_cand.l2()] # plus any isolated electron or muon is also a good lepton rather than a jet event.selMuons = [mu for mu in event.muons if self.preselectPromptMuons (mu , pt=10) and mu .relIsoR(R=0.3, dBetaFactor=0.5, allCharged=0)<0.15] event.selElectrons = [ele for ele in event.electrons if self.preselectPromptElectrons(ele, pt=10) and ele.relIsoR(R=0.3, dBetaFactor=0.5, allCharged=0)<0.15] # RM: what about taus? event.selectedLeptons += event.selMuons event.selectedLeptons += event.selElectrons ######################################################################################## # Extra prompt and isolated lepton veto ######################################################################################## event.veto_mus = [ele for ele in event.selMuons if ele.physObj not in [event.the_3lep_cand.l0().physObj, event.the_3lep_cand.l1().physObj, event.the_3lep_cand.l2().physObj] ] event.veto_eles = [mu for mu in event.selElectrons if mu .physObj not in [event.the_3lep_cand.l0().physObj, event.the_3lep_cand.l1().physObj, event.the_3lep_cand.l2().physObj] ] if len(event.veto_eles): event.veto_save_ele = sorted([ele for ele in event.veto_eles], key = lambda x : x.pt, reverse = True)[0] if len(event.veto_mus ): event.veto_save_mu = sorted([mu for mu in event.veto_mus ], key = lambda x : x.pt, reverse = True)[0] ######################################################################################## # charged PF isolation ######################################################################################## chargedpfs = [ipf for ipf in pfs if ipf.charge()!=0 and abs(ipf.pdgId())!=11 and abs(ipf.pdgId())!=13] chargedpfs = [ipf for ipf in chargedpfs if ipf.pt()>0.6 and abs(ipf.eta())<2.5] chargedpfs = [ipf for ipf in chargedpfs if abs(ipf.dxy(event.recoSv.position()))<0.1 and abs(ipf.dz(event.recoSv.position()))<0.5] chisopfs = [ipf for ipf in chargedpfs if deltaR(ipf, event.the_3lep_cand.hnP4())<0.5] event.the_3lep_cand.abs_ch_iso = sum([ipf.pt() for ipf in chisopfs]) #FIXME MAYBE WE WANNA CHANGE HERE TO E INSTEAD OF pT event.the_3lep_cand.rel_ch_iso = event.the_3lep_cand.abs_ch_iso/event.the_3lep_cand.hnP4().pt() for mu in event.muons: print event.eventId, mu, mu.simType() # if (abs(event.the_3lep_cand.l1().simType()) > 100 or abs(event.the_3lep_cand.l2().simType()) > 100): set_trace() return True
def process(self, event): self.readCollections(event.input) self.counters.counter('HNLGenTree').inc('all events') # produce collections event.genp_pruned = self.mchandles['genp_pruned'].product() event.genp_packed = self.mchandles['genp_packed'].product() # no point to run this if it's not a HNL signal if 'HN3L' not in self.cfg_comp.name: return True # for pp in event.genp_packed: # printer = lambda : 'pat::PackedGenParticle: %d, pt %.2f, eta %.2f, phi %.2f, mass %.2f, status %d' %(pp.pdgId(), pp.pt(), pp.eta(), pp.phi(), pp.mass(), pp.status()) # import pdb ; pdb.set_trace() # pp.__str__ = printer # import pdb ; pdb.set_trace() # # import pdb ; pdb.set_trace() # all gen particles event.genp = [ip for ip in event.genp_pruned ] + [ip for ip in event.genp_packed] # get the heavy neutrino the_hns = [ ip for ip in event.genp_pruned if abs(ip.pdgId()) in [9900012, 9990012] and ip.isLastCopy() ] # 9900012 is Majorana, 9990012 is Dirac. Dirac comes in two species, particle and anti-particle! event.the_hn = the_hns[0] # one per event # RM FIXME! for the tau case, we might want to save the tau daughters as well... # prompt lepton # import pdb ; pdb.set_trace() # treat light leptons and taus differently, as the former are stable. the latter decay pl_candidates = [ ip for ip in event.genp_pruned if abs(ip.pdgId()) in [11, 13] and ip.isPromptFinalState() and not isAncestor(event.the_hn, ip) ] pl_candidates += [ ip for ip in event.genp_pruned if abs(ip.pdgId()) in [15] and ip.isPromptDecayed() and not isAncestor(event.the_hn, ip) ] event.the_pl = map(GenParticle, pl_candidates)[0] # import pdb ; pdb.set_trace() # event.the_pl = map(GenParticle, [ip for ip in event.genp_pruned if abs(ip.pdgId()) in [11,13,15] and ip.isPromptFinalState() and not isAncestor(event.the_hn, ip)])[0] # get the immediate daughters of the heavy neutrino decay event.the_hn.initialdaus = [ event.the_hn.daughter(jj) for jj in range(event.the_hn.numberOfDaughters()) ] event.the_hn.lep1 = max([ ii for ii in event.the_hn.initialdaus if abs(ii.pdgId()) in [11, 13, 15] ], key=lambda x: x.pt()) event.the_hn.lep2 = min([ ii for ii in event.the_hn.initialdaus if abs(ii.pdgId()) in [11, 13, 15] ], key=lambda x: x.pt()) event.the_hn.neu = [ ii for ii in event.the_hn.initialdaus if abs(ii.pdgId()) in [12, 14, 16] ][0] # there can be only one # identify the secondary vertex event.the_hn.the_sv = event.the_hn.lep1.vertex() # need to analyse the lepton after they radiated / converted for ip in [event.the_hn.lep1, event.the_hn.lep2, event.the_pl]: finaldaus = [] for ipp in event.genp_packed: # for ipp in event.genp: # try with all particles, see if it makes sense mother = ipp.mother(0) if mother and isAncestor(ip, mother): finaldaus.append(ipp) ip.finaldaughters = sorted(finaldaus, key=lambda x: x.pt(), reverse=True) ip.hasConvOrRad = (len(ip.finaldaughters) > 1) if len(ip.finaldaughters) > 1: try: ip.finallep = max([ ii for ii in ip.finaldaughters if ii.pdgId() == ip.pdgId() ], key=lambda x: x.pt()) except: try: ip.finallep = max([ ii for ii in ip.finaldaughters if abs(ii.pdgId()) in [11, 13] ], key=lambda x: x.pt()) except: return False # import pdb ; pdb.set_trace() else: ip.finallep = ip # 4-momentum of the visible part of the HN event.the_hn.vishn = event.the_hn.lep1.finallep.p4( ) + event.the_hn.lep2.finallep.p4() # make it into a handy class event.the_hnl = HN3L(event.the_pl.finallep, event.the_hn.lep1.finallep, event.the_hn.lep2.finallep, event.the_hn.neu) return True