EIC Software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
qaplots.py
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file qaplots.py
1 #!/usr/bin/env python
2 
3 '''
4 Suite of standard QA plots for EIC trees to check they are filled properly.
5 It's fairly slow, but you don't need to run many events to check that all
6 values are being filled properly.
7 '''
8 
9 import argparse
10 from collections import namedtuple
11 
12 # Configure the environment for ROOT/eic-smear.
13 # Do this before importing ROOT, as initialise() will
14 # attempt to set the environment to find libPyROOT
15 # if it isn't already.
16 import eicpy
17 eicpy.initialise(False)
18 
19 import ROOT
20 
21 from binning import bin_log10
22 
23 # Don't pass command line arguments to ROOT so it
24 # doesn't intercept use of --help: we want the help
25 # to be printed for this script, not ROOT.
26 # root.cern.ch/phpBB3/viewtopic.php?f=14&t=13582
27 ROOT.PyConfig.IgnoreCommandLineOptions = True
28 
29 # Speed things up a bit
30 ROOT.SetSignalPolicy(ROOT.kSignalFast)
31 
32 # No need to draw graphics to screen
33 ROOT.gROOT.SetBatch(True)
34 
35 class LogAxis(namedtuple('LogAxis', 'x y z')):
36  '''
37  Utility for setting ROOT pad logarithmic axes.
38  '''
39  def apply(self, pad):
40  '''
41  Apply the log axis settings to a ROOT pad.
42  '''
43  pad.SetLogx(self.x != 0)
44  pad.SetLogy(self.y != 0)
45  pad.SetLogz(self.z != 0)
46 
47  def rebin(self, hist):
48  if self.x:
49  bin_log10(hist.GetXaxis())
50  if self.y and hist.GetDimension() > 1:
51  bin_log10(hist.GetYaxis())
52  if self.z and hist.GetDimension() > 2:
53  bin_log10(hist.GetZaxis())
54 
55 
56 class THWrapper(object):
57  '''
58  Simple wrapper around ROOT histogram adding some functionality.
59  '''
60  def __init__(self, h, log, option):
61  '''
62  Initialise with a histogram and a LogAxis giving whether
63  each axis (x, y, z) should be log10 (1) or not (0).
64  '''
65  self.h = h
66  self.log = log
67  if not h.GetSumw2():
68  self.h.Sumw2()
69  self.h.SetOption(option)
70  self.log.rebin(self.h)
71 
72  def draw(self, pad):
73  '''
74  Draw the histogram to a ROOT pad.
75  '''
76  initial = ROOT.gPad
77  pad.cd()
78  self.log.apply(pad)
79  self.h.Draw()
80  ROOT.gPad = initial
81 
82 
83 class EventHists(object):
84  '''
85  A collection of standard histograms for event-wise properties.
86  '''
87  def __init__(self):
88  self.hists = {}
89  self.hists['process'] = THWrapper(
90  ROOT.TH1D('process', 'Event process number', 200, 0., 200.),
91  LogAxis(0, 1, 0), '')
92  self.hists['tracks'] = THWrapper(
93  ROOT.TH1D('tracks', 'Number of tracks per event', 100, 0., 200),
94  LogAxis(0, 0, 0), '')
95 # self.hists['eInNucl'] = THWrapper(
96 # ROOT.TH1D('eInNucl', 'E of incident lepton in nuclear rest frame',
97 # 40, 0., 10000),
98 # LogAxis(0, 0, 0), '')
99 # self.hists['eScNucl'] = THWrapper(
100 # ROOT.TH1D('eScNucl', 'E of scattered lepton in nuclear rest frame',
101 # 40, 0., 10000),
102 # LogAxis(0, 0, 0), '')
103  self.hists['Q2x'] = THWrapper(
104  ROOT.TH2D('Q2x', 'Q^{2} vs. x', 30, 1.e-5, 10., 16, 0.1, 1000.),
105  LogAxis(1, 1, 1), 'colz')
106  self.hists['y'] = THWrapper(
107  ROOT.TH1D('y', 'y', 48, -0.1, 1.1), LogAxis(0, 1, 0), '')
108  self.hists['W2'] = THWrapper(
109  ROOT.TH1D('W2', 'W^{2} (GeV^{2})', 60, 0.1, 1.e5),
110  LogAxis(1, 0, 0), '')
111  self.hists['nu'] = THWrapper(
112  ROOT.TH1D('nu', '#nu (GeV)', 60, 0.1, 1.e5), LogAxis(1, 0, 0), '')
113  # Jacquet-Blondel and double-angle kinematics.
114  # Clone the original histograms to maintain the same axis ranges.
115  for i in ['y', 'Q2x', 'W2']:
116  orig = self.hists[i]
117  name = i + 'jb'
118  self.hists[name] = THWrapper(orig.h.Clone(name), orig.log,
119  orig.h.GetOption())
120  self.hists[name].h.SetTitle(orig.h.GetTitle() +
121  ' (Jacquet-Blondel)')
122  name = i + 'da'
123  self.hists[name] = THWrapper(orig.h.Clone(name), orig.log,
124  orig.h.GetOption())
125  self.hists[name].h.SetTitle(orig.h.GetTitle() +
126  ' (double-angle)')
127 
128  def output(self, pad, filename):
129  '''
130  Print all histograms to an output PostScript file.
131  '''
132  initial = ROOT.gPad
133  pad.cd()
134  for j in sorted(self.hists):
135  self.hists[j].draw(pad)
136  pad.Print(filename)
137  ROOT.gPad = initial
138 
139  def fill(self, event):
140  '''
141  '''
142  self.hists['process'].h.Fill(event.GetProcess())
143  self.hists['tracks'].h.Fill(event.GetNTracks())
144 # self.hists['eInNucl'].h.Fill(event.ELeptonInNucl)
145 # self.hists['eScNucl'].h.Fill(event.EScatteredInNucl)
146  self.hists['Q2x'].h.Fill(event.GetX(), event.GetQ2())
147  self.hists['y'].h.Fill(event.GetY())
148  self.hists['W2'].h.Fill(event.GetW2())
149  self.hists['nu'].h.Fill(event.GetNu())
150  self.hists['Q2xjb'].h.Fill(event.GetXJacquetBlondel(),
151  event.GetQ2JacquetBlondel())
152  self.hists['yjb'].h.Fill(event.GetYJacquetBlondel())
153  self.hists['W2jb'].h.Fill(event.GetW2JacquetBlondel())
154  self.hists['Q2xda'].h.Fill(event.GetXDoubleAngle(),
155  event.GetQ2DoubleAngle())
156  self.hists['yda'].h.Fill(event.GetYDoubleAngle())
157  self.hists['W2da'].h.Fill(event.GetW2DoubleAngle())
158 
159 
160 class TrackHists(object):
161  '''
162  A collection of standard histograms for track-wise properties.
163  Optionally specify a list of PDG codes to accept.
164  Only tracks with one of those codes will be used when filling histograms.
165  '''
166  def __init__(self):
167  # Particle status
168  self.KS = THWrapper(
169  ROOT.TH1D('KS', 'Track status code', 110, -10., 100.),
170  LogAxis(0, 0, 0), '')
171  # Particle parent index
172  self.orig = THWrapper(
173  ROOT.TH1D('orig', 'Track parent index', 210, -10., 200.),
174  LogAxis(0, 0, 0), '')
175  # Child indicies
176  self.child = THWrapper(
177  ROOT.TH2D('child', 'Child track indices;first;last',
178  210, -10., 200., 210, -10., 200.),
179  LogAxis(0, 0, 0), 'colz')
180  # y vs. x momentum
181  self.pypx = THWrapper(
182  ROOT.TH2D('pypx', 'p_{y} vs. p_{x} (GeV/c);p_{x};p_{y}',
183  60, -3., 3., 60, -3., 3.),
184  LogAxis(0, 0, 1), 'colz')
185  # Transverse vs. longitudinal momentum
186  self.ptpz = THWrapper(
187  ROOT.TH2D('ptpz', 'p_{T} vs. p_{z} (GeV/c);p_{z};p_{T}',
188  60, -40., 260., 60, -1., 11.),
189  LogAxis(0, 0, 1), 'colz')
190  # Energy vs. mass
191  self.em = THWrapper(
192  ROOT.TH2D('em', 'Energy vs. mass (final-state only);m;E',
193  60, -1., 11., 60, -40., 260.),
194  LogAxis(0, 0, 1), 'colz')
195  # Vertex y vs. x
196  self.vxy = THWrapper(
197  ROOT.TH2D('vxy', 'Track vertex y vs. x',
198  51, -100., 100., 51, -100., 100.),
199  LogAxis(0, 0, 1), 'colz')
200  # Energy fraction (z) vs. total momentum
201  self.zp = THWrapper(
202  ROOT.TH2D('zp', 'Energy fraction vs. p (final-state only);p;z',
203  60, -40., 260., 48, -0.1, 1.1),
204  LogAxis(0, 0, 1), 'colz')
205  # Angles theta vs. phi
207  ROOT.TH2D('thetaphi', '#theta vs. #phi (rad)',
208  35, -0.4, 6.6, 40, -0.5, 3.5),
209  LogAxis(0, 0, 1), 'colz')
210  # Pseudorapidity vs. rapidity
211  self.etay = THWrapper(
212  ROOT.TH2D('etay', '#eta vs. rapidity',
213  40, -20., 20., 40, -20., 20.),
214  LogAxis(0, 0, 1), 'colz')
215  # p_T vs. polar angle in photon-proton frame
216  self.gamma = THWrapper(
217  ROOT.TH2D('gamma', 'p_{T} vs. #theta in #gamma-p frame',
218  40, -0.5, 3.5, 35, -1., 6.),
219  LogAxis(0, 0, 1), 'colz')
220 # self.phi'] = THWrapper(
221 # ROOT.TH1D('phi', '#phi in #gamma-p frame', 35, -0.4, 6.6),
222 # LogAxis(0, 0, 0), '')
223  # Feynman x
224  self.xf = THWrapper(
225  ROOT.TH1D('xf', 'Feynman x (final-state only)', 110, -1.1, 1.1),
226  LogAxis(0, 0, 0), '')
227 
228  def output(self, pad, filename):
229  '''
230  Print all histograms to an output PostScript file.
231  '''
232  initial = ROOT.gPad
233  pad.cd()
234  hists = [self.KS, self.child, self.em, self.etay, self.gamma, self.orig,
235  self.ptpz, self.pypx, self.thetaphi, self.vxy, self.xf, self.zp]
236  for j in hists:
237  j.draw(pad)
238  pad.Print(filename)
239  ROOT.gPad = initial
240 
241  def fill(self, tracks):
242  '''
243  Fill all histograms for a list of tracks
244  '''
245  # OK, this is a bit ugly looking, but localising all the track
246  # and histogram methods outside the for loop saves quite a lot
247  # of time when looping over millions of particles.
248  # Here are all the Particle accessor methods we use:
249  Particle = ROOT.erhic.ParticleMC
250  status = Particle.GetStatus
251  parent = Particle.GetParentIndex
252  child1 = Particle.GetChild1Index
253  childN = Particle.GetChildNIndex
254  px = Particle.GetPx
255  py = Particle.GetPy
256  pz = Particle.GetPz
257  pt = Particle.GetPt
258  vert = Particle.GetVertex
259  phi = Particle.GetPhi
260  theta = Particle.GetTheta
261  rap = Particle.GetRapidity
262  eta = Particle.GetEta
263  thetag = Particle.GetThetaVsGamma
264  ptg = Particle.GetPtVsGamma
265  e = Particle.GetE
266  p = Particle.GetP
267  m = Particle.GetM
268  z = Particle.GetZ
269  xf = Particle.GetXFeynman
270  # Here are all the histogram Fill operations we need
271  hks = self.KS.h.Fill
272  horig = self.orig.h.Fill
273  hchild = self.child.h.Fill
274  hpypx = self.pypx.h.Fill
275  hptpz = self.ptpz.h.Fill
276  hvxy = self.vxy.h.Fill
277  hthetaphi = self.thetaphi.h.Fill
278  hetay = self.etay.h.Fill
279  hgamma = self.gamma.h.Fill
280  hem = self.em.h.Fill
281  hzp = self.zp.h.Fill
282  hxf = self.xf.h.Fill
283  # We skip certain species for some of the histograms
284  beams = [2212, 2112, 11]
285  # Now we loop over the tracks
286  for track in tracks:
287  # Only fill final-state particles here as intermediate
288  # state properties (e.g. negative mass for virtual photon)
289  # can make it harder to spot issues.
290  if track.GetStatus() != 1:
291  continue
292  hks(status(track))
293  horig(parent(track))
294  hchild(child1(track), childN(track))
295  hpypx(px(track), py(track))
296  hptpz(pz(track), pt(track))
297  vertex = vert(track)
298  hvxy(vertex.x(), vertex.y())
299  hthetaphi(phi(track), theta(track))
300  hetay(rap(track), eta(track))
301  hgamma(thetag(track), ptg(track))
302  # Skip particles that are not in the final state, or may
303  # be scattered beams, as some quantities don't make sense then.
304  if track.GetPdgCode().Code() not in beams:
305  hem(m(track), e(track))
306  hzp(p(track), z(track))
307  hxf(xf(track))
308 
309 
310 def print_progress(n, max):
311  print 'Event', str(n).rjust(len(str(max))), 'of', max
312 
313 def execute(tree, outname, maxevents):
314  '''
315  '''
317  outfile = ROOT.TFile(outname.replace('.ps', '.root'), 'recreate')
318  h = EventHists()
319  t = TrackHists()
320  nevents = min(maxevents, tree.GetEntries())
321  for i in xrange(0, nevents):
322  tree.GetEntry(i)
323  h.fill(tree.event)
324  tracks = [track for track in tree.event.GetTracks()]
325  t.fill(tracks)
326  if (i + 1) % (nevents / 10) == 0:
327  print_progress(i + 1, nevents)
328  window = ROOT.TCanvas()
329  window.Print(''.join([outname, '[']))
330  h.output(window, outname)
331  t.output(window, outname)
332  window.Print(''.join([outname, ']']))
333  outfile.Write()
334 
335 if __name__ == '__main__':
336  # Process the command line arguments
337  parser = argparse.ArgumentParser()
338  parser.add_argument('infile', help='Input ROOT file')
339  parser.add_argument('outfile', help='Output PostScript file')
340  parser.add_argument('--nevents', type=int, default=1000000000, help='Maximum number of events')
341  args = parser.parse_args()
342  file = ROOT.TFile(args.infile, 'read')
343  execute(file.EICTree, args.outfile, args.nevents)