ATLAS Offline Software
Loading...
Searching...
No Matches
PyAlgorithmExample.py
Go to the documentation of this file.
2# File: PyAlgorithmExample.py
3# Created: sss, Apr 2005
4# Purpose: Example of a non-trivial python-based analysis.
5#
6# See PyAlgorithmExample_top.py for a top-level job options file
7# to use to run this.
8#
9
10#
11# This is a simple analysis for H->(Z->ll)(Z->tautau) events.
12# This is done in what's hopefully a pretty straightforward way.
13# It's still significantly short of what would be needed for a real
14# analysis, but there's enough here for a demonstration of something
15# that's more than a toy.
16#
17# First we define a bunch of helper functions and classes.
18# Our algorithm comes last.
19#
20
21
22from EventKernel import ParticleDataType
23import math
24from PyAnalysisUtils import combo
25from PyAnalysisUtils import PDG
26from ROOT import TH1F, gROOT
27
28
29
30
31#
32# Dummy class to hold algorithm arguments.
33#
34class Args:
35 pass
36
37
38#
39# Book a histogram.
40#
41def mybook (name, n, lo, hi):
42 # This will make a AIDA histogram.
43 # I don't use this because i want to use all root methods
44 # on the histograms, and i also want to be able to use root_pickle
45 # with them.
46 #return book ("/stat/" + name, name, n, lo, hi)
47
48 # This will make a native root histogram.
49 gROOT.cd() # otherwise, we can get hists created in input files...
50 return TH1F (name, name, n, lo, hi)
51
52
53#
54# Find the particle in list L closest in DR to the particle P.
55# return a list (DR, PMIN), where PMIN is that closest particle.
56#
57def min_dr (p, l):
58 hlv = p.hlv()
59 dr = 999
60 ppmin = None
61 for pp in l:
62 this_dr = hlv.deltaR (pp.hlv())
63 if this_dr < dr:
64 dr = this_dr
65 ppmin = pp
66 return (dr, ppmin)
67
68
69#
70# Basic histograms for a particle: pt, eta, phi.
71#
73 # Create the three histograms.
74 def __init__ (self, name):
75 self.pt = mybook (name + "_pt", 50, 0, 250*GeV)
76 self.eta = mybook (name + "_eta", 50, -4, 4)
77 self.phi = mybook (name + "_phi", 50, -3.5, 3.5)
78 return
79
80 # Fill the histograms for particle P.
81 def fill (self, p):
82 self.pt.Fill (p.pt())
83 self.eta.Fill (p.eta())
84 self.phi.Fill (p.phi())
85 return
86
87
88#
89# Histograms for a list of particles.
90#
92 # H here is the histogram object for each individual particle.
93 # It should support a fill method.
94 # If defaulted, a Parthists instance is created.
95 def __init__ (self, name, h=None):
96 self.n = mybook (name + "_n", 10, 0, 10)
97 if not h:
98 self.h = Parthists (name)
99 else:
100 self.h = h
101 return
102
103 # Fill the histograms for the list of particles PLIST.
104 def fill (self, plist):
105 self.n.Fill (len (plist))
106 for p in plist:
107 self.h.fill (p)
108 return
109
110
111#
112# An empty class for holding histograms.
113#
114class Holder:
115 pass
116
117
118#
119# Electron parameter codes.
120# Can't seem to get enum codes from pylcgdict.
121#
122EoverP = 0
123
124
125#
126# Particle properties.
127# This should really come from somewhere else.
128#
129MZ = 91.19*GeV
130
131
132#
133# Do the colinear neutrino approximation.
134# Easier to just recode this than to try to wrap it.
135#
137 # numerator
138 numerator = a.px()*b.py() - a.py()*b.px()
139
140 # fraction
141 x_a = numerator/(b.py()*(a.px()+metx)-b.px()*(a.py()+mety))
142 x_b = numerator/(a.px()*(b.py()+mety)-a.py()*(b.px()+metx))
143
144 def make_nu (p, scale):
145 hlv = p.hlv()
146 hlv *= scale
147 hlv.setE(hlv.vect().mag())
148 # Below should really be Neutrino instead of TruthParticle.
149 # But as of 10.2.0, anyway, Neutrino doesn't get dictionary
150 # information generated.
151 nu = g.TruthParticle()
152 nu.set4Mom (hlv)
153 return nu
154
155 return (make_nu (a, 1./x_a - 1),
156 make_nu (b, 1./x_b - 1))
157
158
159
160#
161# Like CompositeParticle, but a) behaves like a python sequence, and
162# b) remembers the original python objects. (If we put an object
163# into CompositeParticle and then get it out again, it will be
164# a different python object.)
165#
166class PyCompositeParticle (g.CompositeParticle):
167 def __init__ (self, pdgid=None, charge=None, plist=None):
168 g.CompositeParticle.__init__ (self)
169 self.__l = []
170 if pdgid != None: self.set_pdgId (pdgid)
171 if charge != None: self.set_charge (charge)
172 if plist != None: self.extend (plist)
173 return
174 def add (self, *plist):
175 for p in plist:
176 g.CompositeParticle.add (self, p)
177 self.__l += plist
178 return
179 def __len__ (self):
180 return len (self.__l)
181 def __getitem__ (self, key):
182 return self.__l[key]
183 def __iter__ (self):
184 return xxx # Not done yet.
185 def __contains__ (self, item):
186 return item in self.__l
187 def append (self, x):
188 return self.add (x)
189 def extend (self, x):
190 for p in x:
191 self.add (p)
192 return
193 def count (self, x):
194 return self.__l.count (x)
195 def index (self, x, i=0, j=999999999):
196 return self.__l.ndex (i, j)
197
198
199#
200# A Z-like composite particle.
201#
203 def __init__ (self, l1, l2, pdgid=PDG.Z0):
204 PyCompositeParticle.__init__ (self, pdgid, 0, [l1, l2])
205 return
206 def dr (self):
207 return self[0].hlv().deltaR (self[1].hlv())
208
209
210
211#
212# Define histograms for electrons.
213# This is based on the generic Parthists set of histograms,
214# and adds additional ones specifically for electrons.
215#
217 def __init__ (self, name):
218 Parthists.__init__ (self, name)
219 self.eoverp = mybook (name + "eoverp", 50, 0, 2)
220 self.isem = mybook (name + "isem", 20, -1.5, 18.5)
221 return
222
223 def fill (self, p):
224 Parthists.fill (self, p)
225 if fulldata (p):
226 self.eoverp.Fill (p.parameter (EoverP))
227 isem = p.isEM()
228 if isem == 0:
229 self.isem.Fill (-1)
230 else:
231 for i in range(0, 16):
232 if isem & (1<<i): self.isem.Fill (i)
233 return
235 return Partlisthists (name, Parthists_Ele (name))
236
237
238
239#
240# Define histograms for muons.
241# This is based on the generic Parthists set of histograms,
242# and adds additional ones specifically for muons.
243#
245 def __init__ (self, name):
246 Parthists.__init__ (self, name)
247 self.chi2 = mybook (name + "chi2", 200, 0, 1000)
248 return
249
250 def fill (self, p):
251 Parthists.fill (self, p)
252 self.chi2.Fill (p.chi2())
253 return
255 return Partlisthists (name, Parthists_Muo (name))
256
257
258
259#
260# Define histograms for tau jets.
261# This is based on the generic Parthists set of histograms,
262# and adds additional ones specifically for tau jets.
263#
265 def __init__ (self, name):
266 Parthists.__init__ (self, name)
267 self.etem = mybook (name + "etem", 100, 0, 200*GeV)
268 self.ethad = mybook (name + "ethad", 100, 0, 200*GeV)
269 self.ntrack = mybook (name + "ntrack", 10, -0.5, 9.5)
270 self.charge = mybook (name + "charge", 10, -4.5, 4.5)
271 self.emrad = mybook (name + "emrad", 100, 0, 50)
272 self.isofrac = mybook (name + "isofrac", 100, 0, 1)
273 self.stripw2 = mybook (name + "stripw2", 100, 0, 1)
274 self.likeli = mybook (name + "likeli", -10, 0, 40)
275 self.pt1 = mybook (name + "pt1", 100, 0, 200*GeV)
276 self.emfrac = mybook (name + "emfrac", 100, 0, 1)
277 self.tote = mybook (name + "tote", 100, 0, 200*GeV)
278 return
279
280 def fill (self, p):
281 Parthists.fill (self, p)
282 self.etem.Fill (p.etEM())
283 self.ethad.Fill (p.etHad())
284 self.ntrack.Fill (p.numTrack())
285 self.charge.Fill (p.charge())
286 self.emrad.Fill (p.EMRadius())
287 self.isofrac.Fill (p.IsoFrac())
288 self.stripw2.Fill (p.stripWidth2())
289 self.likeli.Fill (p.likelihood())
290 if not not p.track(0):
291 self.pt1.Fill (p.track(0).pt())
292 tote = p.etEM() + p.etHad()
293 if tote > 0:
294 emfrac = p.etEM() / tote
295 else:
296 emfrac = 0
297 self.tote.Fill (tote)
298 self.emfrac.Fill (emfrac)
299 return
301 return Partlisthists (name, Parthists_Taujet (name))
302
303
304
305#
306# Define histograms for Z-like particles.
307# This is based on the generic Parthists set of histograms,
308# and adds additional ones specifically for Z's and H's.
309#
311 def __init__ (self, name, m_max = 250*GeV):
312 Parthists.__init__ (self, name)
313 self.m = mybook (name + "m", 100, 0, m_max)
314 self.dr = mybook (name + "dr", 100, 0, 10)
315 return
316
317 def fill (self, p):
318 Parthists.fill (self, p)
319 self.m.Fill (p.m())
320 self.dr.Fill (p.dr())
321 return
322def Partlisthists_Z (name, m_max = 250*GeV):
323 return Partlisthists (name, Parthists_Z (name, m_max))
324
325
326
327
328#
329# Helper to test the data type of P.
330# Returns true if it's from full simulation.
331#
332def fulldata (p):
333 dt = p.dataType()
334 return dt != ParticleDataType.Fast and dt != ParticleDataType.True
335
336
337
343class PyAlgorithmExample (PyAlgorithm):
344 # Initialization. NAME is the algorithm name.
345 # ARGS holds the algorithm parameters.
346 def __init__ (self, name, args):
347 # Initialze base class.
348 PyAlgorithm.__init__ (self, name)
349
350 # Save the arguments.
351 self.args = args
352
353 # Make the class to hold the histograms.
354 # We stick them in a separate instance to that they can be easily
355 # saved via root_pickle.
356 h = Holder()
357 self.h = h
358
359 # Create histograms.
360 h.ele0 = Partlisthists_Ele ('ele0')
361 h.ele1 = Partlisthists_Ele ('ele1')
362 h.ele2 = Partlisthists_Ele ('ele2')
363
364 h.muo0 = Partlisthists_Muo ('muo0')
365 h.muo1 = Partlisthists_Muo ('muo1')
366
367 h.taujet0 = Partlisthists_Taujet ('taujet0')
368 h.taujet1 = Partlisthists_Taujet ('taujet1')
369
370 h.metx = mybook ("metx", 100, 0, 500*GeV)
371 h.mety = mybook ("mety", 100, 0, 500*GeV)
372 h.met = mybook ("met", 100, 0, 500*GeV)
373
374 h.zee = [Partlisthists_Z('zee0'), Partlisthists_Z('zee1')]
375 h.zmm = [Partlisthists_Z('zmm0'), Partlisthists_Z('zmm1')]
376 h.zll = Partlisthists_Z('zll')
377
378 h.ztt = [Partlisthists_Z('ztt0'), Partlisthists_Z('ztt1')]
379
380 h.hzz = [Partlisthists_Z ('hzz0', m_max=500*GeV),
381 Partlisthists_Z ('hzz1', m_max=500*GeV)]
382
383 h.ncand = mybook ("ncand", 10, 0, 10)
384
385 # Initialize event counters.
386 h.n_in = 0
387 h.n_met_cut = 0
388 h.n_zll_cut = 0
389 h.n_4parts_cut = 0
390 h.n_cand_cut = 0
391 h.n_hzz_cut = 0
392 return
393
394
395 # Here's the algorithm code.
396 def execute (self):
397 args = self.args
398 h = self.h
399
400 # Get particles from storegate.
401 eles = self.get_electrons()
402 muos = self.get_muons()
403 taujets = self.get_taujets()
404 met = self.get_met()
405
406 # Find Z->ll decays.
407 # Find them Z->ee and Z->mm separately, then combine the lists,
408 # and then fill histograms from the combined list.
409 zees = self.find_zll (eles, h.zee)
410 zmms = self.find_zll (muos, h.zmm)
411 zlls = zees + zmms
412 h.zll.fill (zlls)
413
414 # Make preliminary cuts.
415 # Count the events passing the cuts.
416 h.n_in += 1
417
418 # Missing et cut.
419 if met.pt < args.met_cut: return 1
420 h.n_met_cut += 1
421
422 # Must find at least one Z->ll.
423 if len (zlls) == 0: return 1
424 h.n_zll_cut += 1
425
426 # Need at least four final-state particles.
427 if len (eles) + len (muos) + len (taujets) < 4: return 1
428 h.n_4parts_cut += 1
429
430 # Combine all final-state particles into a single list.
431 # We'll use this to search for Z->tautau decays.
432 parts = eles + muos + taujets
433
434 # For each Z->ll decay, we look for Z->tautau decays.
435 # In general, for a given Z->ll decay, we may have 0, 1, or more
436 # Z->tautau candidates. In CANDLIST, we make a list of all the
437 # candiate (zll, ztt) decays we find.
438 candlist = []
439
440 # Loop over Z->ll candidates.
441 for z in zlls:
442 # Search for Z->tautau candidates, given a specific Z->ll
443 # candidate.
444 ztts = self.find_ztt (parts, met, z, h.ztt)
445 # Append all of our candidates to the list.
446 for z2 in ztts: candlist.append ((z, z2))
447
448 # Now require that we find at least one
449 # Z->ll, Z->tautau pair.
450 if len (candlist) == 0: return 1
451 h.n_cand_cut += 1
452 h.ncand.Fill (len (candlist))
453
454 # Construct the H->ZZ candidates.
455 hzzs = self.find_hzz (candlist, h.hzz)
456 h.n_hzz_cut += 1
457 return 1
458
459
460 #
461 # Fetch the list of electrons from storegate and apply quality cuts.
462 #
463 def get_electrons (self):
464 args = self.args
465 h = self.h
466
467 # Helper function: apply particle ID cuts to candidate E.
468 def track_select (e):
469 if not fulldata(e): return 1
470 return e.hasTrack() and e.isEM()%16 == 0
471
472 # Helper function: apply kinematic cuts to candidate E.
473 def select (e):
474 return e.pt() > args.ele_pt_cut and abs(e.eta()) < args.ele_eta_cut
475
476 # Fetch electrons from storegate, apply particle ID cuts,
477 # then kinematic cuts, then return the results. Fill histograms
478 # after each step.
479 eles = PyParticleTools.getElectrons (args.electron_container)
480 h.ele0.fill (eles)
481 eles = [e for e in eles if track_select (e)]
482 h.ele1.fill (eles)
483 eles = [e for e in eles if select (e)]
484 h.ele2.fill (eles)
485 return eles
486
487
488 #
489 # Fetch the list of muons from storegate and apply quality cuts.
490 #
491 def get_muons (self):
492 args = self.args
493 h = self.h
494
495 # Helper function: apply kinematic cuts to candidate M.
496 def select (m):
497 return m.pt() > args.muo_pt_cut and abs(m.eta()) < args.muo_eta_cut
498
499 # Fetch muons from storegate, apply
500 # kinematic cuts, then return the results. Fill histograms
501 # after each step.
502 muos = PyParticleTools.getMuons (args.muon_container)
503 h.muo0.fill (muos)
504 muos = [m for m in muos if select (m)]
505 h.muo1.fill (muos)
506 return muos
507
508
509 #
510 # Fetch the list of tau jets from storegate and apply quality cuts.
511 #
512 def get_taujets (self):
513 args = self.args
514 h = self.h
515
516 # Helper function: apply quality and kinematic cuts to candidate T.
517 def select (t):
518 if (t.pt() < args.taujet_pt_cut or
519 abs(t.eta()) > args.taujet_eta_cut):
520 return 0
521 if not fulldata(t): return 1
522 if t.likelihood() < args.taujet_likeli_cut: return 0
523 emfrac = t.etEM() / (t.etEM() + t.etHad())
524 if emfrac > args.taujet_max_emfrac: return 0
525 return 1
526
527 # Fetch tau jets from storegate, apply quality and kinematic cuts,
528 # then return the results. Fill histograms before and after
529 # the cuts.
530 taujets = PyParticleTools.getTauJets (args.taujet_container)
531 h.taujet0.fill (taujets)
532 taujets = [t for t in taujets if select (t)]
533 h.taujet1.fill (taujets)
534 return taujets
535
536
537 #
538 # Fetch missing ET from storegate and fill histograms.
539 #
540 def get_met (self):
541 args = self.args
542 h = self.h
543 met = PyParticleTools.getMissingET (args.met_container)
544 h.metx.Fill (met.etx())
545 h.mety.Fill (met.ety())
546 met.pt = math.hypot (met.etx(), met.ety())
547 h.met.Fill (met.pt)
548 return met
549
550
551 #
552 # Given a list of lepton candidates LLIST, search for Z->ll decays.
553 # H is a list of histogram objects to fill; h[0] will be filled
554 # for all opposite-sign lepton pairs, h[1] will be filled for those passing
555 # quality cuts.
556 #
557 def find_zll (self, llist, h):
558 args = self.args
559
560 # Make a candidate Z particle for each opposite-sign lepton pair
561 # combination and fill histograms.
562 zlist = [Z (l1, l2) for (l1,l2) in combo.combinations (llist, 2)
563 if l1.charge()*l2.charge() < 0]
564 h[0].fill (zlist)
565
566 # Now filter candiates, applying a mass window and DR cut.
567 # Then histogram the results.
568 def select (z):
569 return (z.dr() < args.zll_deltar_cut and
570 abs(z.m() - MZ) < args.zll_deltam_cut)
571 zlist = [z for z in zlist if select (z)]
572 h[1].fill (zlist)
573 return zlist
574
575
576 #
577 # Given a list PARTS of final-state particles
578 # (electrons, muons, taujets), the missing et MET,
579 # and a z->ll candidate zll, search for z->tautau candidates.
580 # Return the list of candidates.
581 # H is the histograms to be filled.
582 #
583 def find_ztt (self, parts, met, zll, h):
584 args = self.args
585
586 # Remove particles that were used in the Z->ll decay.
587 parts = [p for p in parts if p not in zll]
588
589 # List of candidates we're building.
590 zlist = []
591
592 # Loop over all pairs of remaining particles.
593 for (l1, l2) in combo.combinations (parts, 2):
594 # Use only oppositely-charged pairs.
595 # Does this work for tau jets?
596 if l1.charge() * l2.charge() > 0: continue
597
598 # Build the candidate Z.
599 z = Z (l1, l2)
600
601 # Calculate the neutrino momenta, and add them to the Z candidate.
602 (nu1, nu2) = neutrinos_from_colinear_approximation (l1, l2,
603 met.etx(),
604 met.ety())
605 z.add (nu1)
606 z.add (nu2)
607 zlist.append (z)
608
609 # Fill histograms from the initial set of candidates.
610 h[0].fill (zlist)
611
612 # Make mass and dr cuts and fill another set of histograms.
613 def select (z):
614 return (z.dr() < args.ztt_deltar_cut and
615 abs(z.m() - MZ) < args.ztt_deltam_cut)
616 zlist = [z for z in zlist if select (z)]
617 h[1].fill (zlist)
618
619 # Return candidates.
620 return zlist
621
622
623 #
624 # Build H->ZZ candidates.
625 # CANDLIST is a list of (zll,ztt) candidate pairs.
626 # H is the histograms to fill.
627 #
628 def find_hzz (self, candlist, h):
629 # For each candidate pair, construct a higgs object.
630 # Reuse the Z class for this.
631 # Then fill histograms.
632 hzzs = [Z (zll, ztt, PDG.Higgs0) for (zll, ztt) in candlist]
633 h[0].fill (hzzs)
634
635 # Make a DR cut and fill histograms again.
636 def select (hg):
637 return hg.dr() < args.hzz_deltar_cut
638 hzzs = [hg for hg in hzzs if select (hg)]
639 h[1].fill (hzzs)
640
641 # Return the candidate list.
642 return hzzs
Scalar mag() const
mag method
__init__(self, name, m_max=250 *GeV)
__init__(self, pdgid=None, charge=None, plist=None)
__init__(self, l1, l2, pdgid=PDG.Z0)
mybook(name, n, lo, hi)
Partlisthists_Z(name, m_max=250 *GeV)
neutrinos_from_colinear_approximation(a, b, metx, mety)
Definition index.py:1