File indexing completed on 2024-04-06 12:23:26
0001 import operator
0002 from PhysicsTools.HeppyCore.framework.analyzer import Analyzer
0003 from PhysicsTools.HeppyCore.statistics.counter import Counter, Counters
0004 from PhysicsTools.Heppy.analyzers.AutoHandle import AutoHandle
0005 from PhysicsTools.Heppy.physicsobjects.DiObject import DiObject
0006 from PhysicsTools.Heppy.physicsobjects.PhysicsObjects import Lepton
0007 from PhysicsTools.HeppyCore.utils.TriggerMatching import triggerMatched
0008 from PhysicsTools.HeppyCore.utils.DeltaR import deltaR
0009
0010 class DiLeptonAnalyzer( Analyzer ):
0011
0012 """Generic analyzer for Di-Leptons.
0013 See ZMuMuAnalyzer for a concrete case.
0014
0015 Example configuration, and list of parameters:
0016 #O means optional
0017
0018 ana = cfg.Analyzer(
0019 'DiLeptonAnalyzer',
0020 scaleShift1 = eScaleShift, #O shift factor for leg 1 energy scale
0021 scaleShift2 = tauScaleShift,#O same for leg 2
0022 pt1 = 20, # pt, eta, iso cuts for leg 1
0023 eta1 = 2.3,
0024 iso1 = None,
0025 pt2 = 20, # same for leg 2
0026 eta2 = 2.1,
0027 iso2 = 0.1,
0028 m_min = 10, # mass range
0029 m_max = 99999,
0030 dR_min = 0.5, #O min delta R between the two legs
0031 triggerMap = pathsAndFilters, #O, necessary for trigger matching
0032 verbose = False #from base Analyzer class
0033 )
0034
0035 COLIN: need to specify what is needed in the event.
0036 COLIN: need to make delta R non optional.
0037 COLIN: make the dR_min parameter non optional
0038 """
0039
0040
0041
0042
0043
0044
0045 DiObjectClass = DiObject
0046 LeptonClass = Lepton
0047 OtherLeptonClass = Lepton
0048
0049 def beginLoop(self, setup):
0050 super(DiLeptonAnalyzer,self).beginLoop(setup)
0051 self.counters.addCounter('DiLepton')
0052 count = self.counters.counter('DiLepton')
0053 count.register('all events')
0054 count.register('> 0 di-lepton')
0055
0056 count.register('lepton accept')
0057 count.register('third lepton veto')
0058 count.register('leg1 offline cuts passed')
0059 count.register('leg1 trig matched')
0060 count.register('leg2 offline cuts passed')
0061 count.register('leg2 trig matched')
0062 count.register('{min:3.1f} < m < {max:3.1f}'.format( min = self.cfg_ana.m_min,
0063 max = self.cfg_ana.m_max ))
0064 if hasattr(self.cfg_ana, 'dR_min'):
0065 count.register('dR > {min:3.1f}'.format( min = self.cfg_ana.dR_min))
0066
0067 count.register('exactly 1 di-lepton')
0068
0069
0070 def buildDiLeptons(self, cmgDiLeptons, event):
0071 '''Creates python DiLeptons from the di-leptons read from the disk.
0072 to be overloaded if needed.'''
0073 return map( self.__class__.DiObjectClass, cmgDiLeptons )
0074
0075
0076 def buildLeptons(self, cmgLeptons, event):
0077 '''Creates python Leptons from the leptons read from the disk.
0078 to be overloaded if needed.'''
0079 return map( self.__class__.LeptonClass, cmgLeptons )
0080
0081
0082 def buildOtherLeptons(self, cmgLeptons, event):
0083 '''Creates python Leptons from the leptons read from the disk.
0084 to be overloaded if needed.'''
0085 return map( self.__class__.LeptonClass, cmgLeptons )
0086
0087
0088
0089 def process(self, iEvent, event):
0090 self.readCollections( iEvent )
0091 event.diLeptons = self.buildDiLeptons( self.handles['diLeptons'].product(), event )
0092 event.leptons = self.buildLeptons( self.handles['leptons'].product(), event )
0093 event.otherLeptons = self.buildOtherLeptons( self.handles['otherLeptons'].product(), event )
0094 self.shiftEnergyScale(event)
0095 return self.selectionSequence(event, fillCounter=True,
0096 leg1IsoCut=self.cfg_ana.iso1,
0097 leg2IsoCut=self.cfg_ana.iso2)
0098
0099
0100 def shiftEnergyScale(self, event):
0101 scaleShift1 = None
0102 scaleShift2 = None
0103 if hasattr( self.cfg_ana, 'scaleShift1'):
0104 scaleShift1 = self.cfg_ana.scaleShift1
0105 if hasattr( self.cfg_ana, 'scaleShift2'):
0106 scaleShift2 = self.cfg_ana.scaleShift2
0107 if scaleShift1:
0108
0109 map( lambda x: x.leg1().scaleEnergy(scaleShift1), event.diLeptons )
0110 if scaleShift2:
0111 map( lambda x: x.leg2().scaleEnergy(scaleShift2), event.diLeptons )
0112 map( lambda x: x.scaleEnergy(scaleShift2), event.leptons )
0113
0114
0115 def selectionSequence(self, event, fillCounter, leg1IsoCut=None, leg2IsoCut=None):
0116
0117 if fillCounter: self.counters.counter('DiLepton').inc('all events')
0118
0119
0120
0121
0122
0123
0124
0125
0126 if len(event.diLeptons) == 0:
0127 return False
0128 if fillCounter: self.counters.counter('DiLepton').inc('> 0 di-lepton')
0129
0130
0131
0132 selDiLeptons = event.diLeptons
0133
0134
0135 event.leptonAccept = False
0136 if self.leptonAccept( event.leptons ):
0137 if fillCounter: self.counters.counter('DiLepton').inc('lepton accept')
0138 event.leptonAccept = True
0139
0140 event.thirdLeptonVeto = False
0141 if self.thirdLeptonVeto(event.leptons, event.otherLeptons):
0142 if fillCounter: self.counters.counter('DiLepton').inc('third lepton veto')
0143 event.thirdLeptonVeto = True
0144
0145
0146 selDiLeptons = [ diL for diL in selDiLeptons if \
0147 self.testLeg1( diL.leg1(), leg1IsoCut ) ]
0148 if len(selDiLeptons) == 0:
0149 return False
0150 else:
0151 if fillCounter: self.counters.counter('DiLepton').inc('leg1 offline cuts passed')
0152
0153 if len(self.cfg_comp.triggers)>0:
0154
0155 selDiLeptons = [diL for diL in selDiLeptons if \
0156 self.trigMatched(event, diL.leg1(), 'leg1')]
0157 if len(selDiLeptons) == 0:
0158 return False
0159 else:
0160 if fillCounter: self.counters.counter('DiLepton').inc('leg1 trig matched')
0161
0162
0163 selDiLeptons = [ diL for diL in selDiLeptons if \
0164 self.testLeg2( diL.leg2(), leg2IsoCut ) ]
0165 if len(selDiLeptons) == 0:
0166 return False
0167 else:
0168 if fillCounter: self.counters.counter('DiLepton').inc('leg2 offline cuts passed')
0169
0170 if len(self.cfg_comp.triggers)>0:
0171
0172 selDiLeptons = [diL for diL in selDiLeptons if \
0173 self.trigMatched(event, diL.leg2(), 'leg2')]
0174 if len(selDiLeptons) == 0:
0175 return False
0176 else:
0177 if fillCounter: self.counters.counter('DiLepton').inc('leg2 trig matched')
0178
0179
0180 selDiLeptons = [ diL for diL in selDiLeptons if \
0181 self.testMass(diL) ]
0182 if len(selDiLeptons)==0:
0183 return False
0184 else:
0185 if fillCounter: self.counters.counter('DiLepton').inc(
0186 '{min:3.1f} < m < {max:3.1f}'.format( min = self.cfg_ana.m_min,
0187 max = self.cfg_ana.m_max )
0188 )
0189
0190
0191 if hasattr(self.cfg_ana, 'dR_min'):
0192 selDiLeptons = [ diL for diL in selDiLeptons if \
0193 self.testDeltaR(diL) ]
0194 if len(selDiLeptons)==0:
0195 return False
0196 else:
0197 if fillCounter: self.counters.counter('DiLepton').inc(
0198 'dR > {min:3.1f}'.format( min = self.cfg_ana.dR_min )
0199 )
0200
0201
0202 if len(selDiLeptons)==0:
0203 return False
0204 elif len(selDiLeptons)==1:
0205 if fillCounter: self.counters.counter('DiLepton').inc('exactly 1 di-lepton')
0206
0207 event.diLepton = self.bestDiLepton( selDiLeptons )
0208 event.leg1 = event.diLepton.leg1()
0209 event.leg2 = event.diLepton.leg2()
0210 event.selectedLeptons = [event.leg1, event.leg2]
0211
0212 return True
0213
0214
0215 def declareHandles(self):
0216 super(DiLeptonAnalyzer, self).declareHandles()
0217
0218
0219
0220
0221
0222 def leptonAccept(self, leptons):
0223 '''Should implement a default version running on event.leptons.'''
0224 return True
0225
0226
0227 def thirdLeptonVeto(self, leptons, otherLeptons, isoCut = 0.3) :
0228 '''Should implement a default version running on event.leptons.'''
0229 return True
0230
0231
0232 def testLeg1(self, leg, isocut=None):
0233 '''returns testLeg1ID && testLeg1Iso && testLegKine for leg1'''
0234 return self.testLeg1ID(leg) and \
0235 self.testLeg1Iso(leg, isocut) and \
0236 self.testLegKine(leg, self.cfg_ana.pt1, self.cfg_ana.eta1)
0237
0238
0239 def testLeg2(self, leg, isocut=None):
0240 '''returns testLeg2ID && testLeg2Iso && testLegKine for leg2'''
0241 return self.testLeg2ID(leg) and \
0242 self.testLeg2Iso(leg, isocut) and \
0243 self.testLegKine(leg, self.cfg_ana.pt2, self.cfg_ana.eta2)
0244
0245
0246 def testLegKine(self, leg, ptcut, etacut ):
0247 '''Tests pt and eta.'''
0248 return leg.pt() > ptcut and \
0249 abs(leg.eta()) < etacut
0250
0251
0252 def testLeg1ID(self, leg):
0253 '''Always return true by default, overload in your subclass'''
0254 return True
0255
0256
0257 def testLeg1Iso(self, leg, isocut):
0258 '''If isocut is None, the iso value is taken from the iso1 parameter.
0259 Checks the standard dbeta corrected isolation.
0260 '''
0261 if isocut is None:
0262 isocut = self.cfg_ana.iso1
0263 return leg.relIso(0.5) < isocut
0264
0265
0266 def testLeg2ID(self, leg):
0267 '''Always return true by default, overload in your subclass'''
0268 return True
0269
0270
0271 def testLeg2Iso(self, leg, isocut):
0272 '''If isocut is None, the iso value is taken from the iso2 parameter.
0273 Checks the standard dbeta corrected isolation.
0274 '''
0275 if isocut is None:
0276 isocut = self.cfg_ana.iso2
0277 return leg.relIso(0.5) < isocut
0278
0279
0280 def testMass(self, diLepton):
0281 '''returns True if the mass of the dilepton is between the m_min and m_max parameters'''
0282 mass = diLepton.mass()
0283 return self.cfg_ana.m_min < mass and mass < self.cfg_ana.m_max
0284
0285
0286 def testDeltaR(self, diLepton):
0287 '''returns True if the two diLepton.leg1() and .leg2() have a delta R larger than the dR_min parameter.'''
0288 dR = deltaR( diLepton.leg1().eta(), diLepton.leg1().phi(),
0289 diLepton.leg2().eta(), diLepton.leg2().phi())
0290 return dR > self.cfg_ana.dR_min
0291
0292
0293 def bestDiLepton(self, diLeptons):
0294 '''Returns the best diLepton (the one with highest pt1 + pt2).'''
0295 return max( diLeptons, key=operator.methodcaller( 'sumPt' ) )
0296
0297
0298 def trigMatched(self, event, leg, legName):
0299 '''Returns true if the leg is matched to a trigger object as defined in the
0300 triggerMap parameter'''
0301 if not hasattr( self.cfg_ana, 'triggerMap'):
0302 return True
0303 path = event.hltPath
0304 triggerObjects = event.triggerObjects
0305 filters = self.cfg_ana.triggerMap[ path ]
0306 filter = None
0307 if legName == 'leg1':
0308 filter = filters[0]
0309 elif legName == 'leg2':
0310 filter = filters[1]
0311 else:
0312 raise ValueError( 'legName should be leg1 or leg2, not {leg}'.format(
0313 leg=legName ) )
0314
0315
0316 if filter == '':
0317 return True
0318
0319
0320 pdgIds = None
0321 if len(filter) == 2:
0322 filter, pdgIds = filter[0], filter[1]
0323 return triggerMatched(leg, triggerObjects, path, filter,
0324
0325 dR2Max=0.25,
0326 pdgIds=pdgIds )