CMS 3D CMS Logo

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