Belle II Software  release-06-00-14
cdst_timeResoNtuple.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 
4 
11 
12 # ---------------------------------------------------------------------------------------
13 # Makes ntuple w/ variable size arrays to study single photon time resolution with di-muon data
14 # by comparing photon times w.r.t leading PDF peak position.
15 # Provides also full information of a PDF peak assocoated with a detected photon.
16 # The following draw command must show a peak that represents an overall time resolution:
17 # tree->Draw("time-t0>>h(100,-1,2)", "t0<10")
18 #
19 # Usage: basf2 cdst_timeResoNtuple.py -i <cdst_file-dimuon_skim.root> [mc]
20 # ---------------------------------------------------------------------------------------
21 
22 import basf2 as b2
23 from ROOT import Belle2
24 from ROOT import TH1F, TH2F
25 from ROOT import AddressOf, gROOT
26 from ROOT import TLorentzVector
27 import math
28 import ROOT
29 import sys
30 
31 MC = False
32 if len(sys.argv) > 1:
33  MC = True
34  file_num = sys.argv[1]
35 
36 
37 gROOT.ProcessLine('struct TreeStruct {\
38  int run; /* run number */ \
39  int itrk; /* track counter */ \
40  float offset; /* current bunch offset */ \
41  int usedTrk; /* number of tracks used for bunch reconstruction */ \
42  int slot; /* slot ID */ \
43  float p; /* extHit momentum */ \
44  float cth; /* extHit local cos(theta) (global must be pretty much the same) */ \
45  float phi; /* extHit local phi */ \
46  float z; /* extHit local z */ \
47  float x; /* extHit local x */ \
48  float tof; /* extHit time-of-flight */ \
49  int charge; /* particle charge */ \
50  float poca_x; /* POCA x */ \
51  float poca_y; /* POCA y */ \
52  float poca_z; /* POCA z */ \
53  int hitsCDC; /* number of CDC hits */ \
54  float dEcms; /* delta Ecms for muon */ \
55  float rec_t0; /* t0 of this track determined with bunch finder */ \
56  int valid_t0; /* reconstruction status of t0 */ \
57  int nfot; /* number of photons */ \
58  int channel[1000]; /* channel */ \
59  int pixel[1000] ; /* pixel ID */ \
60  float time[1000]; /* photon time */ \
61  float timeErr[1000]; /* photon time uncertainty */ \
62  int pulseHeight[1000]; /* photon pulse height */ \
63  float pulseWidth[1000]; /* photon pulse width */ \
64  int sample[1000]; /* sample number modulo 256 */ \
65  int status[1000]; /* calibration status bits */ \
66  float t0[1000]; /* leading PDF peak: position */ \
67  float wid0[1000]; /* leading PDF peak: width w/o TTS */ \
68  float t1[1000]; /* next to leading PDF peak: position */ \
69  float t_pdf[1000]; /* associated pdf peak time */ \
70  int type[1000]; /* 0 bkg, 1 direct, 2 reflected */ \
71  int nx[1000]; /* total number of reflections in x */ \
72  int ny[1000]; /* total number of reflections in y */ \
73  int nxm[1000]; /* number of reflections in x before mirror */ \
74  int nym[1000]; /* number of reflections in y before mirror */ \
75  int nxe[1000]; /* number of reflections in x in prism */ \
76  int nye[1000]; /* number of reflections in y in prism */ \
77  int nys[1000]; /* number of reflections on slanted surface of prism */ \
78  float xd[1000]; /* unfolded x coordinate of a pixel */ \
79  float yd[1000]; /* unfolded y coordinate of a pixel */ \
80  float xm[1000]; /* unfolded x coordinate of a reconstructed point on mirror */ \
81  float kx[1000]; /* reconstructed photon direction in x at emission */ \
82  float ky[1000]; /* reconstructed photon direction in y at emission */ \
83  float alpha[1000]; /* impact angle on photo-cathode [degrees] */ \
84 };')
85 
86 int_arrays = ['channel', 'pixel', 'pulseHeight', 'sample', 'status', 'type', 'nx', 'ny',
87  'nxm', 'nym', 'nxe', 'nye', 'nys']
88 float_arrays = ['time', 'timeErr', 'pulseWidth', 't0', 'wid0', 't1', 't_pdf', 'alpha',
89  'xd', 'yd', 'xm', 'kx', 'ky']
90 
91 from ROOT import TreeStruct # noqa
92 
93 
94 class Ntuple(b2.Module):
95  ''' Makes a flat ntuple '''
96 
97 
98  nhisto = 0
99 
100  def initialize(self):
101  ''' initialize: open root file, construct ntuple '''
102 
103  evtMetaData = Belle2.PyStoreObj('EventMetaData')
104  expNo = evtMetaData.obj().getExperiment()
105  runNo = evtMetaData.obj().getRun()
106  exp_run = '-e' + '{:0=4d}'.format(expNo) + '-r' + '{:0=5d}'.format(runNo)
107  if not MC:
108  outName = 'out_data/timeResoNtuple' + exp_run + '.root'
109  else:
110  outName = 'out_mc/timeResoNtuple' + exp_run + '-' + file_num + '.root'
111 
112 
113  self.filefile = ROOT.TFile(outName, 'recreate')
114 
115 
116  self.bunchOffsetbunchOffset = TH1F("bunchOffset", "bunch offset", 100, -1.0, 1.0)
117  self.bunchOffsetbunchOffset.SetXTitle("bunch offset [ns]")
118 
119  self.h_cth_vs_ph_cth_vs_p = TH2F("cth_vs_p", "local cos #theta vs. p", 100, 0.0, 10.0,
120  100, -1.0, 1.0)
121  self.h_cth_vs_ph_cth_vs_p.SetXTitle("p [GeV/c]")
122  self.h_cth_vs_ph_cth_vs_p.SetYTitle("cos #theta")
123 
124  self.h_momentumh_momentum = TH1F("momentum", "momentum", 100, 0.0, 10.0)
125  self.h_momentumh_momentum.SetXTitle("p [GeV/c]")
126 
127  self.h_cthh_cth = TH1F("cos_theta", "local cos #theta", 100, -1.0, 1.0)
128  self.h_cthh_cth.SetXTitle("cos #theta")
129 
130  self.h_phih_phi = TH1F("local_phi", "local phi", 100, -math.pi, math.pi)
131  self.h_phih_phi.SetXTitle("local #phi")
132 
133  self.h_zh_z = TH1F("local_z", "local z", 100, -140.0, 140.0)
134  self.h_zh_z.SetXTitle("local z [cm]")
135 
136  self.h_xh_x = TH1F("local_x", "local x", 100, -23.0, 23.0)
137  self.h_xh_x.SetXTitle("local x [cm]")
138 
139  self.h_chargeh_charge = TH1F("charge", "charge", 3, -1.5, 1.5)
140  self.h_chargeh_charge.SetXTitle("charge")
141 
142  self.h_poca_xyh_poca_xy = TH2F("poca_xy", "POCA distribution in x-y", 100, -1.0, 1.0,
143  100, -1.0, 1.0)
144  self.h_poca_xyh_poca_xy.SetXTitle("x [cm]")
145  self.h_poca_xyh_poca_xy.SetYTitle("y [cm]")
146 
147  self.h_poca_zh_poca_z = TH1F("poca_z", "POCA distribution in z", 100, -2.0, 2.0)
148  self.h_poca_zh_poca_z.SetXTitle("z [cm]")
149 
150  self.h_hitsCDCh_hitsCDC = TH1F("cdc_hits", "CDC hits", 100, 0.0, 100.0)
151  self.h_hitsCDCh_hitsCDC.SetXTitle("number of CDC hits")
152 
153  self.h_Ecmsh_Ecms = TH1F("Ecms", "c.m.s. energy of muon", 300, 4.5, 6.0)
154  self.h_Ecmsh_Ecms.SetXTitle("E_{cm} [GeV/c]")
155 
156 
157  self.treetree = ROOT.TTree('tree', 'time resolution')
158 
159  self.datadata = TreeStruct()
160  self.datadata.itrk = 0
161 
162  for key in TreeStruct.__dict__.keys():
163  if '__' not in key:
164  formstring = '/F'
165  if isinstance(self.datadata.__getattribute__(key), int):
166  formstring = '/I'
167  if key in int_arrays:
168  formstring = '[nfot]/I'
169  elif key in float_arrays:
170  formstring = '[nfot]/F'
171  self.treetree.Branch(key, AddressOf(self.datadata, key), key + formstring)
172 
173  def sortPeaks(self, unsortedPeaks):
174  ''' sort PDF peaks according to their positions '''
175 
176  py_list = [x for x in unsortedPeaks]
177  return sorted(py_list, key=lambda x: (x.mean))
178 
179  def pdfHistogram(self, pdf):
180  ''' make histogram of PDF peak positions for the first 20 tracks '''
181 
182  self.nhistonhisto += 1
183  if self.nhistonhisto > 20:
184  return
185  h = TH2F('pdf' + str(self.nhistonhisto), 'muon PDF, itrk = ' + str(self.datadata.itrk),
186  512, 0.0, 512.0, 1000, 0.0, 75.0)
187  h.SetXTitle('pixelID - 1')
188  h.SetYTitle('peak positions [ns]')
189  for x in range(512):
190  peaks = pdf[x]
191  for peak in peaks:
192  h.Fill(x, peak.mean)
193  h.Write()
194 
195  def getEcms(self, tfit, chargedStable):
196  ''' returns c.m.s. energy of a particle '''
197  mom = tfit.getMomentum()
198  lorentzLab = TLorentzVector()
199  mass = chargedStable.getMass()
200  lorentzLab.SetXYZM(mom.X(), mom.Y(), mom.Z(), mass)
202  lorentzCms = T.labToCms(lorentzLab)
203  return [lorentzCms.Energy(), T.getCMSEnergy() / 2]
204 
205  def trackSelected(self):
206  ''' returns true if track fulfills selection criteria '''
207 
208  if abs(self.datadata.dEcms) > 0.1:
209  return False
210  if abs(self.datadata.poca_x) > 0.2:
211  return False
212  if abs(self.datadata.poca_y) > 0.2:
213  return False
214  if abs(self.datadata.poca_z) > 0.5:
215  return False
216  return True
217 
218  def getTOF(self, track, pdg, slot):
219  ''' returns timo-of-flight of extrapolated track '''
220 
221  extHits = track.getRelationsWith('ExtHits')
222  extEnter = None
223  extExit = None
224  for extHit in extHits:
225  if abs(extHit.getPdgCode()) != pdg:
226  continue
227  if extHit.getDetectorID() != Belle2.Const.TOP:
228  continue
229  if extHit.getCopyID() != slot:
230  continue
231  if extHit.getStatus() == Belle2.EXT_ENTER:
232  extEnter = extHit
233  elif extHit.getStatus() == Belle2.EXT_EXIT:
234  extExit = extHit
235  if extEnter:
236  return (extEnter.getTOF() + extExit.getTOF()) / 2
237  return 0
238 
239  def event(self):
240  ''' event processing '''
241 
242  recBunch = Belle2.PyStoreObj('TOPRecBunch')
243  if not recBunch:
244  b2.B2ERROR('no TOPRecBunch')
245  return
246  if not recBunch.isReconstructed():
247  return
248  self.bunchOffsetbunchOffset.Fill(recBunch.getCurrentOffset())
249 
250  evtMetaData = Belle2.PyStoreObj('EventMetaData')
251  self.datadata.run = evtMetaData.getRun()
252  self.datadata.offset = recBunch.getCurrentOffset()
253  self.datadata.usedTrk = recBunch.getUsedTracks()
254 
255  for track in Belle2.PyStoreArray('Tracks'):
256  pdfs = track.getRelated('TOPPDFCollections')
257  if not pdfs:
258  continue
259  self.datadata.slot = pdfs.getModuleID()
260  momentum = pdfs.getAssociatedLocalMomentum()
261  position = pdfs.getAssociatedLocalHit()
262  self.datadata.p = momentum.Mag()
263  self.datadata.cth = momentum.CosTheta()
264  self.datadata.phi = momentum.Phi()
265  self.datadata.z = position.Z()
266  self.datadata.x = position.X()
267  self.datadata.tof = self.getTOFgetTOF(track, 13, self.datadata.slot)
268  try:
269  tfit = track.getTrackFitResultWithClosestMass(Belle2.Const.muon)
270  except BaseException:
271  b2.B2ERROR("No trackFitResult available")
272  continue
273  self.datadata.charge = tfit.getChargeSign()
274  pocaPosition = tfit.getPosition()
275  self.datadata.poca_x = pocaPosition.X()
276  self.datadata.poca_y = pocaPosition.Y()
277  self.datadata.poca_z = pocaPosition.Z()
278  self.datadata.hitsCDC = tfit.getHitPatternCDC().getNHits()
279  Ecms = self.getEcmsgetEcms(tfit, Belle2.Const.muon)
280  self.datadata.dEcms = Ecms[0] - Ecms[1]
281  if not self.trackSelectedtrackSelected():
282  continue
283  try:
284  topll = track.getRelated('TOPLikelihoods')
285  extHit = topll.getRelated('ExtHits')
286  timeZero = extHit.getRelated('TOPTimeZeros')
287  self.datadata.rec_t0 = timeZero.getTime()
288  self.datadata.valid_t0 = timeZero.isValid()
289  except BaseException:
290  self.datadata.rec_t0 = 0
291  self.datadata.valid_t0 = 0
292 
293  self.h_cth_vs_ph_cth_vs_p.Fill(self.datadata.p, self.datadata.cth)
294  self.h_momentumh_momentum.Fill(self.datadata.p)
295  self.h_cthh_cth.Fill(self.datadata.cth)
296  self.h_phih_phi.Fill(self.datadata.phi)
297  self.h_zh_z.Fill(self.datadata.z)
298  self.h_xh_x.Fill(self.datadata.x)
299  self.h_chargeh_charge.Fill(self.datadata.charge)
300  self.h_poca_xyh_poca_xy.Fill(self.datadata.poca_x, self.datadata.poca_y)
301  self.h_poca_zh_poca_z.Fill(self.datadata.poca_z)
302  self.h_hitsCDCh_hitsCDC.Fill(self.datadata.hitsCDC)
303  self.h_Ecmsh_Ecms.Fill(Ecms[0])
304  try:
305  pdf = pdfs.getHypothesisPDF(13)
306  except BaseException:
307  b2.B2ERROR("No PDF available for PDG = 13")
308  continue
309  self.datadata.itrk += 1
310  self.pdfHistogrampdfHistogram(pdf)
311  x0 = position.X() - momentum.X() / momentum.Y() * position.Y() # emission
312  z0 = position.Z() - momentum.Z() / momentum.Y() * position.Y() # emission
313  self.datadata.nfot = 0
314  for digit in Belle2.PyStoreArray('TOPDigits'):
315  if digit.getModuleID() == self.datadata.slot and digit.getHitQuality() == 1:
316  k = self.datadata.nfot
317  if k >= 1000:
318  continue
319  self.datadata.nfot += 1
320  self.datadata.channel[k] = digit.getChannel()
321  self.datadata.pixel[k] = digit.getPixelID()
322  self.datadata.time[k] = digit.getTime()
323  self.datadata.timeErr[k] = digit.getTimeError()
324  self.datadata.pulseHeight[k] = digit.getPulseHeight()
325  self.datadata.pulseWidth[k] = digit.getPulseWidth()
326  self.datadata.sample[k] = digit.getModulo256Sample()
327  self.datadata.status[k] = digit.getStatus()
328  peaks = pdf[digit.getPixelID() - 1]
329  if peaks.empty():
330  self.datadata.t0[k] = 0
331  self.datadata.wid0[k] = 0
332  self.datadata.t1[k] = 0
333  else:
334  sorted_peaks = self.sortPeakssortPeaks(peaks)
335  self.datadata.t0[k] = sorted_peaks[0].mean
336  self.datadata.wid0[k] = sorted_peaks[0].width
337  self.datadata.t1[k] = self.datadata.t0[k] + 100
338  if peaks.size() > 1:
339  self.datadata.t1[k] = sorted_peaks[1].mean
340  self.datadata.t_pdf[k] = 0
341  self.datadata.type[k] = 0
342  self.datadata.nx[k] = 0
343  self.datadata.ny[k] = 0
344  self.datadata.nxm[k] = 0
345  self.datadata.nym[k] = 0
346  self.datadata.nxe[k] = 0
347  self.datadata.nye[k] = 0
348  self.datadata.nys[k] = 0
349  self.datadata.xd[k] = 0
350  self.datadata.yd[k] = 0
351  self.datadata.xm[k] = 0
352  self.datadata.kx[k] = 0
353  self.datadata.ky[k] = 0
354  self.datadata.alpha[k] = 0
355  assocPDF = digit.getRelated('TOPAssociatedPDFs')
356  if assocPDF:
357  pik = assocPDF.getSinglePeak()
358  if pik:
359  self.datadata.t_pdf[k] = pik.position
360  self.datadata.type[k] = pik.type
361  self.datadata.nx[k] = pik.nx
362  self.datadata.ny[k] = pik.ny
363  self.datadata.nxm[k] = pik.nxm
364  self.datadata.nym[k] = pik.nym
365  self.datadata.nxe[k] = pik.nxe
366  self.datadata.nye[k] = pik.nye
367  if pik.kyd > 0:
368  self.datadata.nys[k] = int(pik.nye / 2)
369  else:
370  self.datadata.nys[k] = int((pik.nye + 1) / 2)
371  self.datadata.xd[k] = pik.xd
372  self.datadata.yd[k] = pik.yd
373  if pik.kze > 0:
374  self.datadata.xm[k] = x0 + pik.kxe / pik.kze * (130.0 - z0)
375  self.datadata.kx[k] = pik.kxe
376  self.datadata.ky[k] = pik.kye
377  self.datadata.alpha[k] = math.degrees(math.acos(abs(pik.kzd)))
378 
379  self.treetree.Fill()
380 
381  def terminate(self):
382  ''' terminate: close root file '''
383 
384  self.filefile.cd()
385  self.filefile.Write()
386  self.filefile.Close()
387 
388 
389 # Create path
390 main = b2.create_path()
391 
392 # Input: cdst file(s), use -i option
393 main.add_module('RootInput')
394 
395 # Initialize TOP geometry parameters (creation of Geant geometry is not needed)
396 main.add_module('TOPGeometryParInitializer')
397 
398 # Channel masking
399 main.add_module('TOPChannelMasker')
400 
401 # Make a muon PDF available at datastore
402 main.add_module('TOPPDFDebugger', pdgCodes=[13]) # default
403 
404 # Write ntuple
405 main.add_module(Ntuple())
406 
407 # Print progress
408 progress = b2.register_module('Progress')
409 main.add_module(progress)
410 
411 # Process events
412 b2.process(main)
413 
414 # Print call statistics
415 print(b2.statistics)
Class to hold Lorentz transformations from/to CMS and boost vector.
a (simplified) python wrapper for StoreArray.
Definition: PyStoreArray.h:56
a (simplified) python wrapper for StoreObjPtr.
Definition: PyStoreObj.h:67
def sortPeaks(self, unsortedPeaks)
h_cth
histogram of cos(theta)
int nhisto
histogram counter
h_cth_vs_p
histogram cos(theta) vs, momentum
h_phi
histogram of local phi
h_hitsCDC
histogram of number of hits in CDC
h_poca_xy
histogram of POCA x-y
def getEcms(self, tfit, chargedStable)
def getTOF(self, track, pdg, slot)
h_momentum
histogram of momentum
bunchOffset
histogram of bunch offset
static ExpRun getRun(map< ExpRun, pair< double, double >> runs, double t)
Get exp number + run number from time.
Definition: Splitter.cc:264