CMS 3D CMS Logo

DiLeptonAnalyzer.py
Go to the documentation of this file.
1 import operator
2 from PhysicsTools.HeppyCore.framework.analyzer import Analyzer
3 from PhysicsTools.HeppyCore.statistics.counter import Counter, Counters
4 from PhysicsTools.Heppy.analyzers.AutoHandle import AutoHandle
5 from PhysicsTools.Heppy.physicsobjects.DiObject import DiObject
6 from PhysicsTools.Heppy.physicsobjects.PhysicsObjects import Lepton
7 from PhysicsTools.HeppyCore.utils.TriggerMatching import triggerMatched
8 from PhysicsTools.HeppyCore.utils.DeltaR import deltaR
9 
11 
12  """Generic analyzer for Di-Leptons.
13  See ZMuMuAnalyzer for a concrete case.
14 
15  Example configuration, and list of parameters:
16  #O means optional
17 
18  ana = cfg.Analyzer(
19  'DiLeptonAnalyzer',
20  scaleShift1 = eScaleShift, #O shift factor for leg 1 energy scale
21  scaleShift2 = tauScaleShift,#O same for leg 2
22  pt1 = 20, # pt, eta, iso cuts for leg 1
23  eta1 = 2.3,
24  iso1 = None,
25  pt2 = 20, # same for leg 2
26  eta2 = 2.1,
27  iso2 = 0.1,
28  m_min = 10, # mass range
29  m_max = 99999,
30  dR_min = 0.5, #O min delta R between the two legs
31  triggerMap = pathsAndFilters, #O, necessary for trigger matching
32  verbose = False #from base Analyzer class
33  )
34 
35  COLIN: need to specify what is needed in the event.
36  COLIN: need to make delta R non optional.
37  COLIN: make the dR_min parameter non optional
38  """
39 
40  # The DiObject class will be used as the di-object class
41  # and the Lepton class as the lepton class
42  # Child classes override this choice, and can e.g. decide to use
43  # the TauMuon class as a di-object class
44  # ... not sure other people can understand this comment ;-)
45  DiObjectClass = DiObject
46  LeptonClass = Lepton
47  OtherLeptonClass = Lepton
48 
49  def beginLoop(self, setup):
50  super(DiLeptonAnalyzer,self).beginLoop(setup)
51  self.counters.addCounter('DiLepton')
52  count = self.counters.counter('DiLepton')
53  count.register('all events')
54  count.register('> 0 di-lepton')
55  # count.register('di-lepton cut string ok')
56  count.register('lepton accept')
57  count.register('third lepton veto')
58  count.register('leg1 offline cuts passed')
59  count.register('leg1 trig matched')
60  count.register('leg2 offline cuts passed')
61  count.register('leg2 trig matched')
62  count.register('{min:3.1f} < m < {max:3.1f}'.format( min = self.cfg_ana.m_min,
63  max = self.cfg_ana.m_max ))
64  if hasattr(self.cfg_ana, 'dR_min'):
65  count.register('dR > {min:3.1f}'.format( min = self.cfg_ana.dR_min))
66 
67  count.register('exactly 1 di-lepton')
68 
69 
70  def buildDiLeptons(self, cmgDiLeptons, event):
71  '''Creates python DiLeptons from the di-leptons read from the disk.
72  to be overloaded if needed.'''
73  return map( self.__class__.DiObjectClass, cmgDiLeptons )
74 
75 
76  def buildLeptons(self, cmgLeptons, event):
77  '''Creates python Leptons from the leptons read from the disk.
78  to be overloaded if needed.'''
79  return map( self.__class__.LeptonClass, cmgLeptons )
80 
81 
82  def buildOtherLeptons(self, cmgLeptons, event):
83  '''Creates python Leptons from the leptons read from the disk.
84  to be overloaded if needed.'''
85  return map( self.__class__.LeptonClass, cmgLeptons )
86 
87 
88 
89  def process(self, iEvent, event):
90  self.readCollections( iEvent )
91  event.diLeptons = self.buildDiLeptons( self.handles['diLeptons'].product(), event )
92  event.leptons = self.buildLeptons( self.handles['leptons'].product(), event )
93  event.otherLeptons = self.buildOtherLeptons( self.handles['otherLeptons'].product(), event )
94  self.shiftEnergyScale(event)
95  return self.selectionSequence(event, fillCounter=True,
96  leg1IsoCut=self.cfg_ana.iso1,
97  leg2IsoCut=self.cfg_ana.iso2)
98 
99 
100  def shiftEnergyScale(self, event):
101  scaleShift1 = None
102  scaleShift2 = None
103  if hasattr( self.cfg_ana, 'scaleShift1'):
104  scaleShift1 = self.cfg_ana.scaleShift1
105  if hasattr( self.cfg_ana, 'scaleShift2'):
106  scaleShift2 = self.cfg_ana.scaleShift2
107  if scaleShift1:
108  # import pdb; pdb.set_trace()
109  map( lambda x: x.leg1().scaleEnergy(scaleShift1), event.diLeptons )
110  if scaleShift2:
111  map( lambda x: x.leg2().scaleEnergy(scaleShift2), event.diLeptons )
112  map( lambda x: x.scaleEnergy(scaleShift2), event.leptons )
113 
114 
115  def selectionSequence(self, event, fillCounter, leg1IsoCut=None, leg2IsoCut=None):
116 
117  if fillCounter: self.counters.counter('DiLepton').inc('all events')
118  # if not self.triggerList.triggerPassed(event.triggerObject):
119  # return False
120  # self.counters.counter('DiLepton').inc('trigger passed ')
121 
122  # if event.eventId == 155035:
123  # import pdb; pdb.set_trace()
124 
125  # import pdb; pdb.set_trace()
126  if len(event.diLeptons) == 0:
127  return False
128  if fillCounter: self.counters.counter('DiLepton').inc('> 0 di-lepton')
129 
130  # import pdb; pdb.set_trace()
131  # testing di-lepton itself
132  selDiLeptons = event.diLeptons
133  # selDiLeptons = self.selectDiLeptons( selDiLeptons )
134 
135  event.leptonAccept = False
136  if self.leptonAccept( event.leptons ):
137  if fillCounter: self.counters.counter('DiLepton').inc('lepton accept')
138  event.leptonAccept = True
139 
140  event.thirdLeptonVeto = False
141  if self.thirdLeptonVeto(event.leptons, event.otherLeptons):
142  if fillCounter: self.counters.counter('DiLepton').inc('third lepton veto')
143  event.thirdLeptonVeto = True
144 
145  # testing leg1
146  selDiLeptons = [ diL for diL in selDiLeptons if \
147  self.testLeg1( diL.leg1(), leg1IsoCut ) ]
148  if len(selDiLeptons) == 0:
149  return False
150  else:
151  if fillCounter: self.counters.counter('DiLepton').inc('leg1 offline cuts passed')
152 
153  if len(self.cfg_comp.triggers)>0:
154  # trigger matching leg1
155  selDiLeptons = [diL for diL in selDiLeptons if \
156  self.trigMatched(event, diL.leg1(), 'leg1')]
157  if len(selDiLeptons) == 0:
158  return False
159  else:
160  if fillCounter: self.counters.counter('DiLepton').inc('leg1 trig matched')
161 
162  # testing leg2
163  selDiLeptons = [ diL for diL in selDiLeptons if \
164  self.testLeg2( diL.leg2(), leg2IsoCut ) ]
165  if len(selDiLeptons) == 0:
166  return False
167  else:
168  if fillCounter: self.counters.counter('DiLepton').inc('leg2 offline cuts passed')
169 
170  if len(self.cfg_comp.triggers)>0:
171  # trigger matching leg2
172  selDiLeptons = [diL for diL in selDiLeptons if \
173  self.trigMatched(event, diL.leg2(), 'leg2')]
174  if len(selDiLeptons) == 0:
175  return False
176  else:
177  if fillCounter: self.counters.counter('DiLepton').inc('leg2 trig matched')
178 
179  # mass cut
180  selDiLeptons = [ diL for diL in selDiLeptons if \
181  self.testMass(diL) ]
182  if len(selDiLeptons)==0:
183  return False
184  else:
185  if fillCounter: self.counters.counter('DiLepton').inc(
186  '{min:3.1f} < m < {max:3.1f}'.format( min = self.cfg_ana.m_min,
187  max = self.cfg_ana.m_max )
188  )
189 
190  # delta R cut
191  if hasattr(self.cfg_ana, 'dR_min'):
192  selDiLeptons = [ diL for diL in selDiLeptons if \
193  self.testDeltaR(diL) ]
194  if len(selDiLeptons)==0:
195  return False
196  else:
197  if fillCounter: self.counters.counter('DiLepton').inc(
198  'dR > {min:3.1f}'.format( min = self.cfg_ana.dR_min )
199  )
200 
201  # exactly one?
202  if len(selDiLeptons)==0:
203  return False
204  elif len(selDiLeptons)==1:
205  if fillCounter: self.counters.counter('DiLepton').inc('exactly 1 di-lepton')
206 
207  event.diLepton = self.bestDiLepton( selDiLeptons )
208  event.leg1 = event.diLepton.leg1()
209  event.leg2 = event.diLepton.leg2()
210  event.selectedLeptons = [event.leg1, event.leg2]
211 
212  return True
213 
214 
215  def declareHandles(self):
216  super(DiLeptonAnalyzer, self).declareHandles()
217  # self.handles['cmgTriggerObjectSel'] = AutoHandle(
218  # 'cmgTriggerObjectSel',
219  # 'std::vector<cmg::TriggerObject>'
220  # )
221 
222  def leptonAccept(self, leptons):
223  '''Should implement a default version running on event.leptons.'''
224  return True
225 
226 
227  def thirdLeptonVeto(self, leptons, otherLeptons, isoCut = 0.3) :
228  '''Should implement a default version running on event.leptons.'''
229  return True
230 
231 
232  def testLeg1(self, leg, isocut=None):
233  '''returns testLeg1ID && testLeg1Iso && testLegKine for leg1'''
234  return self.testLeg1ID(leg) and \
235  self.testLeg1Iso(leg, isocut) and \
236  self.testLegKine(leg, self.cfg_ana.pt1, self.cfg_ana.eta1)
237 
238 
239  def testLeg2(self, leg, isocut=None):
240  '''returns testLeg2ID && testLeg2Iso && testLegKine for leg2'''
241  return self.testLeg2ID(leg) and \
242  self.testLeg2Iso(leg, isocut) and \
243  self.testLegKine(leg, self.cfg_ana.pt2, self.cfg_ana.eta2)
244 
245 
246  def testLegKine(self, leg, ptcut, etacut ):
247  '''Tests pt and eta.'''
248  return leg.pt() > ptcut and \
249  abs(leg.eta()) < etacut
250 
251 
252  def testLeg1ID(self, leg):
253  '''Always return true by default, overload in your subclass'''
254  return True
255 
256 
257  def testLeg1Iso(self, leg, isocut):
258  '''If isocut is None, the iso value is taken from the iso1 parameter.
259  Checks the standard dbeta corrected isolation.
260  '''
261  if isocut is None:
262  isocut = self.cfg_ana.iso1
263  return leg.relIso(0.5) < isocut
264 
265 
266  def testLeg2ID(self, leg):
267  '''Always return true by default, overload in your subclass'''
268  return True
269 
270 
271  def testLeg2Iso(self, leg, isocut):
272  '''If isocut is None, the iso value is taken from the iso2 parameter.
273  Checks the standard dbeta corrected isolation.
274  '''
275  if isocut is None:
276  isocut = self.cfg_ana.iso2
277  return leg.relIso(0.5) < isocut
278 
279 
280  def testMass(self, diLepton):
281  '''returns True if the mass of the dilepton is between the m_min and m_max parameters'''
282  mass = diLepton.mass()
283  return self.cfg_ana.m_min < mass and mass < self.cfg_ana.m_max
284 
285 
286  def testDeltaR(self, diLepton):
287  '''returns True if the two diLepton.leg1() and .leg2() have a delta R larger than the dR_min parameter.'''
288  dR = deltaR( diLepton.leg1().eta(), diLepton.leg1().phi(),
289  diLepton.leg2().eta(), diLepton.leg2().phi())
290  return dR > self.cfg_ana.dR_min
291 
292 
293  def bestDiLepton(self, diLeptons):
294  '''Returns the best diLepton (the one with highest pt1 + pt2).'''
295  return max( diLeptons, key=operator.methodcaller( 'sumPt' ) )
296 
297 
298  def trigMatched(self, event, leg, legName):
299  '''Returns true if the leg is matched to a trigger object as defined in the
300  triggerMap parameter'''
301  if not hasattr( self.cfg_ana, 'triggerMap'):
302  return True
303  path = event.hltPath
304  triggerObjects = event.triggerObjects
305  filters = self.cfg_ana.triggerMap[ path ]
306  filter = None
307  if legName == 'leg1':
308  filter = filters[0]
309  elif legName == 'leg2':
310  filter = filters[1]
311  else:
312  raise ValueError( 'legName should be leg1 or leg2, not {leg}'.format(
313  leg=legName ) )
314 
315  # JAN: Need a hack for the embedded samples: No trigger matching in that case
316  if filter == '':
317  return True
318 
319  # the dR2Max value is 0.3^2
320  pdgIds = None
321  if len(filter) == 2:
322  filter, pdgIds = filter[0], filter[1]
323  return triggerMatched(leg, triggerObjects, path, filter,
324  # dR2Max=0.089999,
325  dR2Max=0.25,
326  pdgIds=pdgIds )
def testLeg1(self, leg, isocut=None)
def buildLeptons(self, cmgLeptons, event)
def trigMatched(self, event, leg, legName)
def buildOtherLeptons(self, cmgLeptons, event)
def process(self, iEvent, event)
def testLegKine(self, leg, ptcut, etacut)
def buildDiLeptons(self, cmgDiLeptons, event)
def thirdLeptonVeto(self, leptons, otherLeptons, isoCut=0.3)
def testLeg2(self, leg, isocut=None)
Abs< T >::type abs(const T &t)
Definition: Abs.h:22
def testLeg2Iso(self, leg, isocut)
def selectionSequence(self, event, fillCounter, leg1IsoCut=None, leg2IsoCut=None)
def testLeg1Iso(self, leg, isocut)