CMS 3D CMS Logo

 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
GeneratorAnalyzer.py
Go to the documentation of this file.
1 from PhysicsTools.Heppy.analyzers.core.Analyzer import Analyzer
2 from PhysicsTools.Heppy.analyzers.core.AutoHandle import AutoHandle
3 from PhysicsTools.Heppy.physicsutils.genutils import isNotFromHadronicShower, realGenMothers, realGenDaughters
4 
5 def interestingPdgId(id,includeLeptons=False):
6  id = abs(id)
7  return id in [6,7,8,17,18] or (includeLeptons and 11 <= id and id < 16) or (22 <= id and id < 40) or id > 1000000
8 
9 class GeneratorAnalyzer( Analyzer ):
10  """Save the hard-scattering final state of the event: top quarks, gauge & higgs bosons and BSM
11  particles, plus their immediate decay products, and their siblings (in order to get the jets
12  from matched X+jets generation.
13  Incoming partons are not included, by design.
14 
15  In the default configuration, leptons, light quarks and gluons are saved before FSR (a la status 3).
16  Everything else is saved after any radiation, i.e. immediately before the decay.
17 
18  Particles are saved in a list event.generatorSummary, with the index of their mothers ('motherIndex')
19  if the mother is also in the list, and with the pdgId of the mother ('motherId') and grand-mother
20  ('grandmotherId'). Particles also carry their index in the miniAOD genparticles collection ('rawIndex')
21  In addition, a 'sourceId' is set to the pdgId of the heaviest ancestor (or of the particle itself)
22  i.e. in top -> W -> lepton: the lepton sourceId will be 6
23  in tt+W with W -> lepton, the sourceId of the lepton will be 24.
24  sourceId will be 99 for paricles from hard scattering whose mother is light
25 
26  If requested, the full list of genParticles is also produced in event.genParticles (with indices
27  aligned to the miniAOD one). For particles that are in the generatorSummary, the same object is used.
28  An extra index 'genSummaryIndex' will be added to all particles, with the index in the generatorSummary
29  or -1 if the particle is not in the generatorSummary.
30 
31  Also, if requested it creates the splitted collections:
32  event.genHiggsBosons = []
33  event.genVBosons = []
34  event.gennus = [] # prompt neutrinos
35  event.gennusFromTop = [] # Neutrinos from t->W decay
36  event.genleps = [] # leptons from direct decays
37  event.gentauleps = [] # leptons from prompt taus
38  event.gentaus = [] # hadronically-decaying taus (if allGenTaus is False) or all taus (if allGenTaus is True)
39  event.gentopquarks = []
40  event.genbquarks = [] # b quarks from hard event (e.g. from top decays)
41  event.genwzquarks = [] # quarks from W,Z decays
42  event.genbquarksFromTop = []
43  event.genbquarksFromH = []
44  event.genlepsFromTop = [] #mu/ele that have a t->W chain as ancestor, also contained in event.genleps
45  event.genwzquarks and event.genbquarks, might have overlaps
46  event.genbquarksFromTop and event.genbquarksFromH are all contained in event.genbquarks
47 
48  In addition to genParticles, if makeLHEweights is set to True, the list WeightsInfo objects of the LHE branch
49  is stored in event.LHE_weights
50 
51  """
52 
53  def __init__(self, cfg_ana, cfg_comp, looperName ):
54  super(GeneratorAnalyzer,self).__init__(cfg_ana,cfg_comp,looperName)
55  self.stableBSMParticleIds = set(cfg_ana.stableBSMParticleIds) # neutralinos and such
56  self.savePreFSRParticleIds = set(cfg_ana.savePreFSRParticleIds)
57  self.makeAllGenParticles = cfg_ana.makeAllGenParticles
58  self.makeSplittedGenLists = cfg_ana.makeSplittedGenLists
59  self.allGenTaus = cfg_ana.allGenTaus if self.makeSplittedGenLists else False
60  self.makeLHEweights = cfg_ana.makeLHEweights
61 
62  def declareHandles(self):
63  super(GeneratorAnalyzer, self).declareHandles()
64  self.mchandles['genParticles'] = AutoHandle( 'prunedGenParticles', 'std::vector<reco::GenParticle>' )
65  if self.makeLHEweights:
66  self.mchandles['LHEweights'] = AutoHandle( 'source', 'LHEEventProduct', mayFail = True, lazy = False )
67 
68  def beginLoop(self,setup):
69  super(GeneratorAnalyzer,self).beginLoop(setup)
70 
71  def makeMCInfo(self, event):
72  verbose = getattr(self.cfg_ana, 'verbose', False)
73  rawGenParticles = self.mchandles['genParticles'].product()
74  good = []; keymap = {};
75  allGenParticles = []
76  for rawIndex,p in enumerate(rawGenParticles):
77  if self.makeAllGenParticles: allGenParticles.append(p)
78  id = abs(p.pdgId())
79  status = p.status()
80  # particles must be status > 2, except for prompt leptons, photons, neutralinos
81  if status <= 2:
82  if ((id not in self.stableBSMParticleIds) and
83  (id not in [11,12,13,14,15,16,22] or not isNotFromHadronicShower(p))):
84  continue
85  # a particle must not be decaying into itself
86  #print " test %6d : %+8d %3d : %8.2f %+5.2f %+5.2f : %d %d : %+8d {%6d}: %s" % ( rawIndex,
87  # p.pdgId(), p.status(), p.pt(), p.eta(), p.phi(), p.numberOfMothers(), p.numberOfDaughters(),
88  # p.motherRef(0).pdgId() if p.numberOfMothers() > 0 else -999, p.motherRef(0).key() if p.numberOfMothers() > 0 else -999,
89  # " ".join("%d[%d]" % (p.daughter(i).pdgId(), p.daughter(i).status()) for i in xrange(p.numberOfDaughters())))
90  if id in self.savePreFSRParticleIds:
91  # for light objects, we want them pre-radiation
92  if any((p.mother(j).pdgId() == p.pdgId()) for j in xrange(p.numberOfMothers())):
93  #print " fail auto-decay"
94  continue
95  else:
96  # everything else, we want it after radiation, i.e. just before decay
97  if any((p.daughter(j).pdgId() == p.pdgId() and p.daughter(j).status() > 2) for j in xrange(p.numberOfDaughters())):
98  #print " fail auto-decay"
99  continue
100  # FIXME find a better criterion to discard there
101  if status == 71:
102  #drop QCD radiation with unclear parentage
103  continue
104  # is it an interesting particle?
105  ok = False
106  if interestingPdgId(id):
107  #print " pass pdgId"
108  ok = True
109  ### no: we don't select by decay, so that we keep the particle summary free of incoming partons and such
110  # if not ok and any(interestingPdgId(d.pdgId()) for d in realGenDaughters(p)):
111  # #print " pass dau"
112  # ok = True
113  if not ok:
114  for mom in realGenMothers(p):
115  if interestingPdgId(mom.pdgId()) or (getattr(mom,'rawIndex',-1) in keymap):
116  #print " interesting mom"
117  # exclude extra x from p -> p + x
118  if not any(mom.daughter(j2).pdgId() == mom.pdgId() for j2 in xrange(mom.numberOfDaughters())):
119  #print " pass no-self-decay"
120  ok = True
121  # Account for generator feature with Higgs decaying to itself with same four-vector but no daughters
122  elif mom.pdgId() == 25 and any(mom.daughter(j2).pdgId() == 25 and mom.daughter(j2).numberOfDaughters()==0 for j2 in range(mom.numberOfDaughters())):
123  ok = True
124  if abs(mom.pdgId()) == 15:
125  # if we're a tau daughter we're status 2
126  # if we passed all the previous steps, then we're a prompt lepton
127  # so we'd like to be included
128  ok = True
129  if not ok and p.pt() > 10 and id in [1,2,3,4,5,21,22] and any(interestingPdgId(d.pdgId()) for d in realGenDaughters(mom)):
130  # interesting for being a parton brother of an interesting particle (to get the extra jets in ME+PS)
131  ok = True
132  if ok:
133  gp = p
134  gp.rawIndex = rawIndex # remember its index, so that we can set the mother index later
135  keymap[rawIndex] = len(good)
136  good.append(gp)
137  # connect mother links
138  for igp,gp in enumerate(good):
139  gp.motherIndex = -1
140  gp.sourceId = 99
141  gp.genSummaryIndex = igp
142  ancestor = None if gp.numberOfMothers() == 0 else gp.motherRef(0)
143  while ancestor != None and ancestor.isNonnull():
144  if ancestor.key() in keymap:
145  gp.motherIndex = keymap[ancestor.key()]
146  if ancestor.pdgId() != good[gp.motherIndex].pdgId():
147  print "Error keying %d: motherIndex %d, ancestor.pdgId %d, good[gp.motherIndex].pdgId() %d " % (igp, gp.motherIndex, ancestor.pdgId(), good[gp.motherIndex].pdgId())
148  break
149  ancestor = None if ancestor.numberOfMothers() == 0 else ancestor.motherRef(0)
150  if abs(gp.pdgId()) not in [1,2,3,4,5,11,12,13,14,15,16,21]:
151  gp.sourceId = gp.pdgId()
152  if gp.motherIndex != -1:
153  ancestor = good[gp.motherIndex]
154  if ancestor.sourceId != 99 and (ancestor.mass() > gp.mass() or gp.sourceId == 99):
155  gp.sourceId = ancestor.sourceId
156  event.generatorSummary = good
157  # add the ID of the mother to be able to recreate last decay chains
158  for ip,p in enumerate(good):
159  moms = realGenMothers(p)
160  if len(moms)==0:
161  p.motherId = 0
162  p.grandmotherId = 0
163  elif len(moms)==1:
164  p.motherId = moms[0].pdgId()
165  gmoms = realGenMothers(moms[0])
166  p.grandmotherId = (gmoms[0].pdgId() if len(gmoms)==1 else (0 if len(gmoms)==0 else -9999))
167  else:
168  #print " unclear what mothers to give to this particle, among "," ".join("%d[%d]" % (m.pdgId(),m.status()) for m in moms)
169  p.motherId = -9999
170  p.grandmotherId = -9999
171  if verbose:
172  print "%3d {%6d}: %+8d %3d : %8.2f %+5.2f %+5.2f : %d %2d : %+8d {%3d}: %s" % ( ip,p.rawIndex,
173  p.pdgId(), p.status(), p.pt(), p.eta(), p.phi(), len(moms), p.numberOfDaughters(),
174  p.motherId, p.motherIndex,
175  " ".join("%d[%d]" % (p.daughter(i).pdgId(), p.daughter(i).status()) for i in xrange(p.numberOfDaughters())))
176  if verbose:
177  print "\n\n"
178 
179  if self.makeAllGenParticles:
180  event.genParticles = allGenParticles
181 
182  if self.makeSplittedGenLists:
183  event.genHiggsBosons = []
184  event.genVBosons = []
185  event.gennus = []
186  event.gennusFromTop = []
187  event.genleps = []
188  event.gentauleps = []
189  event.gentaus = []
190  event.gentopquarks = []
191  event.genbquarks = []
192  event.genwzquarks = []
193  event.genbquarksFromTop = []
194  event.genbquarksFromH = []
195  event.genlepsFromTop = []
196  for p in event.generatorSummary:
197  id = abs(p.pdgId())
198  if id == 25:
199  event.genHiggsBosons.append(p)
200  elif id in {23,24}:
201  event.genVBosons.append(p)
202  elif id in {12,14,16}:
203  event.gennus.append(p)
204 
205  momids = [(m, abs(m.pdgId())) for m in realGenMothers(p)]
206 
207  #have a look at the lepton mothers
208  for mom, momid in momids:
209  #lepton from W
210  if momid == 24:
211  wmomids = [abs(m.pdgId()) for m in realGenMothers(mom)]
212  #W from t
213  if 6 in wmomids:
214  #save mu,e from t->W->mu/e
215  event.gennusFromTop.append(p)
216 
217  elif id in {11,13}:
218  #taus to separate vector
219  if abs(p.motherId) == 15:
220  event.gentauleps.append(p)
221  #all muons and electrons
222  else:
223  event.genleps.append(p)
224  momids = [(m, abs(m.pdgId())) for m in realGenMothers(p)]
225 
226  #have a look at the lepton mothers
227  for mom, momid in momids:
228  #lepton from W
229  if momid == 24:
230  wmomids = [abs(m.pdgId()) for m in realGenMothers(mom)]
231  #W from t
232  if 6 in wmomids:
233  #save mu,e from t->W->mu/e
234  event.genlepsFromTop.append(p)
235  elif id == 15:
236  if self.allGenTaus or not any([abs(d.pdgId()) in {11,13} for d in realGenDaughters(p)]):
237  event.gentaus.append(p)
238  elif id == 6:
239  event.gentopquarks.append(p)
240  elif id == 5:
241  event.genbquarks.append(p)
242  momids = [abs(m.pdgId()) for m in realGenMothers(p)]
243  if 6 in momids: event.genbquarksFromTop.append(p)
244  if 25 in momids: event.genbquarksFromH.append(p)
245  if id <= 5 and any([abs(m.pdgId()) in {23,24} for m in realGenMothers(p)]):
246  event.genwzquarks.append(p)
247 
248  #Add LHE weight info
249  event.LHE_weights = []
250  if self.makeLHEweights:
251  if self.mchandles['LHEweights'].isValid():
252  for w in self.mchandles['LHEweights'].product().weights():
253  event.LHE_weights.append(w)
254 
255  def process(self, event):
256  self.readCollections( event.input )
257 
258  # if not MC, nothing to do
259  if not self.cfg_comp.isMC:
260  return True
261  # do MC level analysis
262  self.makeMCInfo(event)
263  return True
264 
265 import PhysicsTools.HeppyCore.framework.config as cfg
266 setattr(GeneratorAnalyzer,"defaultConfig",
267  cfg.Analyzer(GeneratorAnalyzer,
268  # BSM particles that can appear with status <= 2 and should be kept
269  stableBSMParticleIds = [ 1000022 ],
270  # Particles of which we want to save the pre-FSR momentum (a la status 3).
271  # Note that for quarks and gluons the post-FSR doesn't make sense,
272  # so those should always be in the list
273  savePreFSRParticleIds = [ 1,2,3,4,5, 11,12,13,14,15,16, 21 ],
274  # Make also the list of all genParticles, for other analyzers to handle
275  makeAllGenParticles = True,
276  # Make also the splitted lists
277  makeSplittedGenLists = True,
278  allGenTaus = False,
279  # Save LHE weights in LHEEventProduct
280  makeLHEweights = True,
281  # Print out debug information
282  verbose = False,
283  )
284 )
def realGenMothers
Definition: genutils.py:96
def isNotFromHadronicShower
Definition: genutils.py:65
bool any(const std::vector< T > &v, const T &what)
Definition: ECalSD.cc:34
def realGenDaughters
Definition: genutils.py:77
Abs< T >::type abs(const T &t)
Definition: Abs.h:22
static std::string join(char **cmd)
Definition: RemoteFile.cc:18
tuple status
Definition: ntuplemaker.py:245