CMS 3D CMS Logo

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  """
49 
50  def __init__(self, cfg_ana, cfg_comp, looperName ):
51  super(GeneratorAnalyzer,self).__init__(cfg_ana,cfg_comp,looperName)
52  self.stableBSMParticleIds = set(cfg_ana.stableBSMParticleIds) # neutralinos and such
53  self.savePreFSRParticleIds = set(cfg_ana.savePreFSRParticleIds)
54  self.makeAllGenParticles = cfg_ana.makeAllGenParticles
55  self.makeSplittedGenLists = cfg_ana.makeSplittedGenLists
56  self.allGenTaus = cfg_ana.allGenTaus if self.makeSplittedGenLists else False
57 
58  def declareHandles(self):
59  super(GeneratorAnalyzer, self).declareHandles()
60  self.mchandles['genParticles'] = AutoHandle( 'prunedGenParticles', 'std::vector<reco::GenParticle>' )
61 
62  def beginLoop(self,setup):
63  super(GeneratorAnalyzer,self).beginLoop(setup)
64 
65  def makeMCInfo(self, event):
66  verbose = getattr(self.cfg_ana, 'verbose', False)
67  rawGenParticles = self.mchandles['genParticles'].product()
68  good = []; keymap = {};
69  allGenParticles = []
70  for rawIndex,p in enumerate(rawGenParticles):
71  if self.makeAllGenParticles: allGenParticles.append(p)
72  id = abs(p.pdgId())
73  status = p.status()
74  # particles must be status > 2, except for prompt leptons, photons, neutralinos
75  if status <= 2:
76  if ((id not in self.stableBSMParticleIds) and
77  (id not in [11,12,13,14,15,16,22] or not isNotFromHadronicShower(p))):
78  continue
79  # a particle must not be decaying into itself
80  #print " test %6d : %+8d %3d : %8.2f %+5.2f %+5.2f : %d %d : %+8d {%6d}: %s" % ( rawIndex,
81  # p.pdgId(), p.status(), p.pt(), p.eta(), p.phi(), p.numberOfMothers(), p.numberOfDaughters(),
82  # p.motherRef(0).pdgId() if p.numberOfMothers() > 0 else -999, p.motherRef(0).key() if p.numberOfMothers() > 0 else -999,
83  # " ".join("%d[%d]" % (p.daughter(i).pdgId(), p.daughter(i).status()) for i in xrange(p.numberOfDaughters())))
84  if id in self.savePreFSRParticleIds:
85  # for light objects, we want them pre-radiation
86  if any((p.mother(j).pdgId() == p.pdgId()) for j in xrange(p.numberOfMothers())):
87  #print " fail auto-decay"
88  continue
89  else:
90  # everything else, we want it after radiation, i.e. just before decay
91  if any((p.daughter(j).pdgId() == p.pdgId() and p.daughter(j).status() > 2) for j in xrange(p.numberOfDaughters())):
92  #print " fail auto-decay"
93  continue
94  # FIXME find a better criterion to discard there
95  if status == 71:
96  #drop QCD radiation with unclear parentage
97  continue
98  # is it an interesting particle?
99  ok = False
100  if interestingPdgId(id):
101  #print " pass pdgId"
102  ok = True
103  ### no: we don't select by decay, so that we keep the particle summary free of incoming partons and such
104  # if not ok and any(interestingPdgId(d.pdgId()) for d in realGenDaughters(p)):
105  # #print " pass dau"
106  # ok = True
107  if not ok:
108  for mom in realGenMothers(p):
109  if interestingPdgId(mom.pdgId()) or (getattr(mom,'rawIndex',-1) in keymap):
110  #print " interesting mom"
111  # exclude extra x from p -> p + x
112  if not any(mom.daughter(j2).pdgId() == mom.pdgId() for j2 in xrange(mom.numberOfDaughters())):
113  #print " pass no-self-decay"
114  ok = True
115  # Account for generator feature with Higgs decaying to itself with same four-vector but no daughters
116  elif mom.pdgId() == 25 and any(mom.daughter(j2).pdgId() == 25 and mom.daughter(j2).numberOfDaughters()==0 for j2 in range(mom.numberOfDaughters())):
117  ok = True
118  if abs(mom.pdgId()) == 15:
119  # if we're a tau daughter we're status 2
120  # if we passed all the previous steps, then we're a prompt lepton
121  # so we'd like to be included
122  ok = True
123  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)):
124  # interesting for being a parton brother of an interesting particle (to get the extra jets in ME+PS)
125  ok = True
126  if not ok and id in [11, 13, 15] and abs(mom.pdgId()) in [1,2,3,4,5,21]:
127  # Lepton e.g. in off-shell DY with the mother being one of the incoming partons
128  ok = True
129  if ok:
130  gp = p
131  gp.rawIndex = rawIndex # remember its index, so that we can set the mother index later
132  keymap[rawIndex] = len(good)
133  good.append(gp)
134  # connect mother links
135  for igp,gp in enumerate(good):
136  gp.motherIndex = -1
137  gp.sourceId = 99
138  gp.genSummaryIndex = igp
139  ancestor = None if gp.numberOfMothers() == 0 else gp.motherRef(0)
140  while ancestor != None and ancestor.isNonnull():
141  if ancestor.key() in keymap:
142  gp.motherIndex = keymap[ancestor.key()]
143  if ancestor.pdgId() != good[gp.motherIndex].pdgId():
144  print "Error keying %d: motherIndex %d, ancestor.pdgId %d, good[gp.motherIndex].pdgId() %d " % (igp, gp.motherIndex, ancestor.pdgId(), good[gp.motherIndex].pdgId())
145  break
146  ancestor = None if ancestor.numberOfMothers() == 0 else ancestor.motherRef(0)
147  if abs(gp.pdgId()) not in [1,2,3,4,5,11,12,13,14,15,16,21]:
148  gp.sourceId = gp.pdgId()
149  if gp.motherIndex != -1:
150  ancestor = good[gp.motherIndex]
151  if ancestor.sourceId != 99 and (ancestor.mass() > gp.mass() or gp.sourceId == 99):
152  gp.sourceId = ancestor.sourceId
153  event.generatorSummary = good
154  # add the ID of the mother to be able to recreate last decay chains
155  for ip,p in enumerate(good):
156  moms = realGenMothers(p)
157  if len(moms)==0:
158  p.motherId = 0
159  p.grandmotherId = 0
160  elif len(moms)==1:
161  p.motherId = moms[0].pdgId()
162  gmoms = realGenMothers(moms[0])
163  p.grandmotherId = (gmoms[0].pdgId() if len(gmoms)==1 else (0 if len(gmoms)==0 else -9999))
164  else:
165  #print " unclear what mothers to give to this particle, among "," ".join("%d[%d]" % (m.pdgId(),m.status()) for m in moms)
166  p.motherId = -9999
167  p.grandmotherId = -9999
168  if verbose:
169  print "%3d {%6d}: %+8d %3d : %8.2f %+5.2f %+5.2f : %d %2d : %+8d {%3d}: %s" % ( ip,p.rawIndex,
170  p.pdgId(), p.status(), p.pt(), p.eta(), p.phi(), len(moms), p.numberOfDaughters(),
171  p.motherId, p.motherIndex,
172  " ".join("%d[%d]" % (p.daughter(i).pdgId(), p.daughter(i).status()) for i in xrange(p.numberOfDaughters())))
173  if verbose:
174  print "\n\n"
175 
176  if self.makeAllGenParticles:
177  event.genParticles = allGenParticles
178 
179  if self.makeSplittedGenLists:
180  event.genHiggsBosons = []
181  event.genVBosons = []
182  event.gennus = []
183  event.gennusFromTop = []
184  event.genleps = []
185  event.gentauleps = []
186  event.gentaus = []
187  event.gentopquarks = []
188  event.genbquarks = []
189  event.genwzquarks = []
190  event.genbquarksFromTop = []
191  event.genbquarksFromH = []
192  event.genlepsFromTop = []
193  for p in event.generatorSummary:
194  id = abs(p.pdgId())
195  if id == 25:
196  event.genHiggsBosons.append(p)
197  elif id in {23,24}:
198  event.genVBosons.append(p)
199  elif id in {12,14,16}:
200  event.gennus.append(p)
201 
202  momids = [(m, abs(m.pdgId())) for m in realGenMothers(p)]
203 
204  #have a look at the lepton mothers
205  for mom, momid in momids:
206  #lepton from W
207  if momid == 24:
208  wmomids = [abs(m.pdgId()) for m in realGenMothers(mom)]
209  #W from t
210  if 6 in wmomids:
211  #save mu,e from t->W->mu/e
212  event.gennusFromTop.append(p)
213 
214  elif id in {11,13}:
215  #taus to separate vector
216  if abs(p.motherId) == 15:
217  event.gentauleps.append(p)
218  #all muons and electrons
219  else:
220  event.genleps.append(p)
221  momids = [(m, abs(m.pdgId())) for m in realGenMothers(p)]
222 
223  #have a look at the lepton mothers
224  for mom, momid in momids:
225  #lepton from W
226  if momid == 24:
227  wmomids = [abs(m.pdgId()) for m in realGenMothers(mom)]
228  #W from t
229  if 6 in wmomids:
230  #save mu,e from t->W->mu/e
231  event.genlepsFromTop.append(p)
232  elif id == 15:
233  if self.allGenTaus or not any([abs(d.pdgId()) in {11,13} for d in realGenDaughters(p)]):
234  event.gentaus.append(p)
235  elif id == 6:
236  event.gentopquarks.append(p)
237  elif id == 5:
238  event.genbquarks.append(p)
239  momids = [abs(m.pdgId()) for m in realGenMothers(p)]
240  if 6 in momids: event.genbquarksFromTop.append(p)
241  if 25 in momids: event.genbquarksFromH.append(p)
242  if id <= 5 and any([abs(m.pdgId()) in {23,24} for m in realGenMothers(p)]):
243  event.genwzquarks.append(p)
244 
245  def process(self, event):
246  self.readCollections( event.input )
247 
248  # if not MC, nothing to do
249  if not self.cfg_comp.isMC:
250  return True
251  # do MC level analysis
252  self.makeMCInfo(event)
253  return True
254 
255 import PhysicsTools.HeppyCore.framework.config as cfg
256 setattr(GeneratorAnalyzer,"defaultConfig",
257  cfg.Analyzer(GeneratorAnalyzer,
258  # BSM particles that can appear with status <= 2 and should be kept
259  stableBSMParticleIds = [ 1000022 ],
260  # Particles of which we want to save the pre-FSR momentum (a la status 3).
261  # Note that for quarks and gluons the post-FSR doesn't make sense,
262  # so those should always be in the list
263  savePreFSRParticleIds = [ 1,2,3,4,5, 11,12,13,14,15,16, 21 ],
264  # Make also the list of all genParticles, for other analyzers to handle
265  makeAllGenParticles = True,
266  # Make also the splitted lists
267  makeSplittedGenLists = True,
268  allGenTaus = False,
269  # Print out debug information
270  verbose = False,
271  )
272 )
def interestingPdgId(id, includeLeptons=False)
def isNotFromHadronicShower(l)
Definition: genutils.py:65
bool any(const std::vector< T > &v, const T &what)
Definition: ECalSD.cc:34
def realGenDaughters(gp, excludeRadiation=True)
Definition: genutils.py:80
Abs< T >::type abs(const T &t)
Definition: Abs.h:22
static std::string join(char **cmd)
Definition: RemoteFile.cc:18
def __init__(self, cfg_ana, cfg_comp, looperName)
def realGenMothers(gp)
Definition: genutils.py:99