Belle II Software  release-06-01-15
cdst_calibrateCommonT0.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 
4 
11 
12 # ---------------------------------------------------------------------------------------
13 # Calibrate common T0 with Bhabha (or dimuon) events using new constants from DB
14 #
15 # usage: basf2 cdst_calibrateCommonT0.py experiment run
16 # job: bsub -q s "basf2 cdst_calibrateCommonT0.py experiment run"
17 # ---------------------------------------------------------------------------------------
18 
19 import basf2 as b2
20 from ROOT import Belle2
21 from ROOT import TH1F, TH2F, TFile, TTree, TF1
22 from array import array
23 import math
24 import sys
25 import glob
26 import os
27 
28 # ----- those need to be adjusted before running --------------------------------------
29 #
30 sampleType = 'bhabha' # sample type: 'bhabha' or 'dimuon'
31 data_dir = '/group/belle2/dataprod/Data/release-03-02-02/DB00000635/proc00000009_nofilter'
32 skim_dir = 'skim/hlt_bhabha/cdst/sub00'
33 globalTag = 'data_reprocessing_prompt' # base global tag (fall-back)
34 stagingTags = ['staging_data_reprocessing'] # list of global tags with new calibration
35 localDB = [] # list of local databases with new calibration
36 method = 'BF' # BF: bunch offset fit, LL: likelihood method
37 output_dir = 'commonT0' # main output folder
38 #
39 # -------------------------------------------------------------------------------------
40 
41 # Argument parsing
42 argvs = sys.argv
43 if len(argvs) < 3:
44  print("usage: basf2", argvs[0], "experiment run")
45  sys.exit()
46 experiment = int(argvs[1])
47 run = int(argvs[2])
48 
49 expNo = 'e' + '{:0=4d}'.format(experiment)
50 runNo = 'r' + '{:0=5d}'.format(run)
51 
52 # Make list of files
53 files = []
54 for typ in ['4S', 'Continuum', 'Scan']:
55  folder = data_dir + '/' + expNo + '/' + typ + '/' + runNo + '/' + skim_dir
56  files += glob.glob(folder + '/cdst.*.root')
57 if len(files) == 0:
58  b2.B2ERROR('No cdst files found')
59  sys.exit()
60 
61 # Output folder
62 output_folder = output_dir + '/' + expNo + '/' + sampleType + '/' + method
63 if not os.path.isdir(output_folder):
64  os.makedirs(output_folder)
65  print('New folder created: ' + output_folder)
66 
67 # Output file name
68 fileName = output_folder + '/commonT0-' + expNo + '-' + runNo + '.root'
69 print('Output file:', fileName)
70 
71 
72 class Mask_BS13d(b2.Module):
73  ''' exclude (mask-out) BS 13d '''
74 
75  def event(self):
76  ''' event processing '''
77 
78  for digit in Belle2.PyStoreArray('TOPDigits'):
79  if digit.getModuleID() == 13 and digit.getBoardstackNumber() == 3:
80  digit.setHitQuality(Belle2.TOPDigit.c_Junk)
81 
82 
83 class calibrateGlobalT0Offline(b2.Module):
84  """
85  ** Description **
86  Module to calculate the commont time offset of the TOP detector.
87  It selects two-track events, saves the bunch finder time offset in an histrogram and
88  fits it to extract the global time offset with respect to the RF clock.
89  It should be run in an individual job for each run, but in future it may be further
90  authomatize to run on multiple runs in the same job.
91 
92  **Output**
93  Root file containing:
94  * The histogram of the bunch finder time offset
95  * The fit function
96  * The fit parameters ina tree format (usefuly when merging several files)
97  """
98 
99  def initialize(self):
100  """
101  Creates the histogram used for the commoT0 calculation, and
102  takes the necessary objects from the DataStore
103  """
104 
105 
106  self.filefile = TFile.Open(fileName, 'recreate')
107 
108  geo = Belle2.PyDBObj('TOPGeometry')
109  if not geo.isValid():
110  b2.B2FATAL('TOP geometry not available in database')
111 
112 
113  self.bunchTimeSepbunchTimeSep = geo.getNominalTDC().getSyncTimeBase() / 24
114 
115 
116  self.h1h1 = TH1F("offset", "current offset; offset [ns]", 600, -9.0, 9.0)
117 
118 
119  self.h2h2 = TH2F("offset_vs_event", "current offset versus event number",
120  100, 0.0, 1000000.0, 200, -3.0, 3.0)
121  self.h2h2.SetXTitle("event number")
122  self.h2h2.SetYTitle("offset [ns]")
123 
124  xmi = -self.bunchTimeSepbunchTimeSep / 2
125  xma = self.bunchTimeSepbunchTimeSep / 2
126 
127  self.h1ah1a = TH1F("offset_a", "current offset; offset [ns]", 200, xmi, xma)
128 
129 
130  self.h2ah2a = TH2F("offset_vs_event_a", "current offset versus event number",
131  100, 0.0, 1000000.0, 200, xmi, xma)
132  self.h2ah2a.SetXTitle("event number")
133  self.h2ah2a.SetYTitle("offset [ns]")
134 
135  xmi = 0.0
136  xma = self.bunchTimeSepbunchTimeSep
137 
138  self.h1bh1b = TH1F("offset_b", "current offset; offset [ns]", 200, xmi, xma)
139 
140 
141  self.h2bh2b = TH2F("offset_vs_event_b", "current offset versus event number",
142  100, 0.0, 1000000.0, 200, xmi, xma)
143  self.h2bh2b.SetXTitle("event number")
144  self.h2bh2b.SetYTitle("offset [ns]")
145 
146  evtMetaData = Belle2.PyStoreObj('EventMetaData')
147 
148 
149  self.expNoexpNo = evtMetaData.getExperiment()
150 
151 
152  self.runrun = '{:0=5d}'.format(evtMetaData.getRun())
153 
154 
155  self.t0t0 = 0
156 
157  self.t0Errt0Err = 0
158  commonT0 = Belle2.PyDBObj('TOPCalCommonT0')
159  if commonT0.isValid():
160  if commonT0.isCalibrated():
161  self.t0t0 = commonT0.getT0()
162  self.t0Errt0Err = commonT0.getT0Error()
163  b2.B2INFO('Common T0 used in data processing: ' + str(self.t0t0))
164  else:
165  b2.B2INFO('No common T0 calibration done yet')
166  else:
167  b2.B2ERROR('Common T0 not available in database')
168 
169  def event(self):
170  """
171  Fills the histogram of the time offset
172  """
173  recBunch = Belle2.PyStoreObj('TOPRecBunch')
174  evtMetaData = Belle2.PyStoreObj('EventMetaData')
175  if not recBunch.isValid():
176  return
177  if recBunch.isReconstructed() and recBunch.getNumTracks() == 2:
178  offset = recBunch.getCurrentOffset()
179  evtNum = evtMetaData.getEvent()
180  self.h1h1.Fill(offset)
181  self.h2h2.Fill(evtNum, offset)
182  # wrap-around into [-1/2, 1/2] of bunch cycle
183  a = offset - round(offset / self.bunchTimeSepbunchTimeSep, 0) * self.bunchTimeSepbunchTimeSep
184  self.h1ah1a.Fill(a)
185  self.h2ah2a.Fill(evtNum, a)
186  # wrap-around into [0, 1] of bunch cycle
187  b = offset - round(offset / self.bunchTimeSepbunchTimeSep - 0.5, 0) * self.bunchTimeSepbunchTimeSep
188  self.h1bh1b.Fill(b)
189  self.h2bh2b.Fill(evtNum, b)
190 
191  def getHistogramToFit(self):
192  """
193  Selects a histogram with the peak closest to histogram center
194  """
195 
196  halfbins = self.h1ah1a.GetNbinsX() / 2
197  if abs(self.h1ah1a.GetMaximumBin() - halfbins) < abs(self.h1bh1b.GetMaximumBin() - halfbins):
198  return self.h1ah1a
199  else:
200  return self.h1bh1b
201 
202  def terminate(self):
203  """
204  Performs the fit of the bunch finder offset distribution,
205  using a line + gaussian function.
206  The results of the fit are saved in a tree
207  """
208 
209  # Get histogram to fit
210  h_to_fit = self.getHistogramToFitgetHistogramToFit()
211  maximum = h_to_fit.GetBinCenter(h_to_fit.GetMaximumBin())
212  hmax = h_to_fit.GetMaximum()
213  sigma0 = 0.16
214  xmin = h_to_fit.GetXaxis().GetXmin()
215  xmax = h_to_fit.GetXaxis().GetXmax()
216 
217  # Fit function
218  func = TF1(
219  'func',
220  '([0]/TMath::Sqrt(2*TMath::Pi() * [2]*[2])) * exp(-0.5*((x-[1])/[2])**2) \
221  + [3]*x + [4]',
222  xmin, xmax)
223  func.SetParameter(0, hmax * 0.9 * math.sqrt(2*math.pi) * sigma0)
224  func.SetParameter(1, maximum)
225  func.SetParameter(2, sigma0)
226  func.SetParLimits(2, 0.05, 0.3) # to avoid the sign ambiguity
227  func.SetParameter(3, 0.)
228  func.SetParameter(4, hmax * 0.1)
229  status = h_to_fit.Fit(func, 'L R S')
230 
231  # Tree creation, branches declaration....
232  tree = TTree('tree', '')
233 
234  expNum = array('i', [0])
235  runNum = array('i', [0])
236  fitted_offset = array('f', [0.])
237  offset = array('f', [0.])
238  offsetErr = array('f', [0.])
239  sigma = array('f', [0.])
240  integral = array('f', [0.])
241  nEvt = array('f', [0.])
242  chi2 = array('f', [0.])
243  fitStatus = array('i', [0])
244 
245  tree.Branch('expNum', expNum, 'expNum/I')
246  tree.Branch('runNum', runNum, 'runNum/I')
247  tree.Branch('fitted_offset', fitted_offset, 'fitted_offset/F')
248  tree.Branch('offset', offset, 'offset/F')
249  tree.Branch('offsetErr', offsetErr, 'offsetErr/F')
250  tree.Branch('sigma', sigma, 'sigma/F')
251  tree.Branch('chi2', chi2, 'chi2/F')
252  tree.Branch('integral', integral, 'integral/F')
253  tree.Branch('nEvt', nEvt, 'nEvt/F')
254  tree.Branch('fitStatus', fitStatus, 'fitStatus/I')
255 
256  # Dumps the fit results into the tree
257  expNum[0] = self.expNoexpNo
258  runNum[0] = int(self.runrun)
259  fitted_offset[0] = func.GetParameter(1)
260  new_t0 = fitted_offset[0] + self.t0t0
261  offset[0] = new_t0 - round(new_t0 / self.bunchTimeSepbunchTimeSep, 0) * self.bunchTimeSepbunchTimeSep
262  offsetErr[0] = func.GetParError(1)
263  sigma[0] = func.GetParameter(2)
264  integral[0] = func.GetParameter(0) / h_to_fit.GetBinWidth(1)
265  chi2[0] = func.GetChisquare() / float(func.GetNDF())
266  nEvt[0] = h_to_fit.Integral()
267  fitStatus[0] = int(status)
268 
269  tree.Fill()
270  tree.Write()
271  self.h1h1.Write()
272  self.h1ah1a.Write()
273  self.h1bh1b.Write()
274  self.h2h2.Write()
275  self.h2ah2a.Write()
276  self.h2bh2b.Write()
277  self.filefile.Close()
278 
279  b2.B2RESULT('Calibration for exp' + str(self.expNoexpNo) + '-run' + self.runrun)
280  if self.t0Errt0Err > 0:
281  b2.B2RESULT('Old common T0 [ns]: ' + str(round(self.t0t0, 3)) + ' +- ' +
282  str(round(self.t0Errt0Err, 3)))
283  else:
284  b2.B2RESULT('Old common T0 [ns]: -- not calibrated -- ')
285  b2.B2RESULT('New common T0 [ns]: ' + str(round(offset[0], 3)) + ' +- ' +
286  str(round(offsetErr[0], 3)))
287  b2.B2RESULT("Output written to " + fileName)
288 
289 
290 # Database
291 b2.use_central_database(globalTag)
292 for tag in stagingTags:
293  b2.use_central_database(tag)
294 for db in localDB:
295  if os.path.isfile(db):
296  b2.use_local_database(db, invertLogging=True)
297  else:
298  b2.B2ERROR(db + ": local database not found")
299  sys.exit()
300 
301 # Create path
302 main = b2.create_path()
303 
304 # Input (cdst files)
305 main.add_module('RootInput', inputFileNames=files)
306 
307 # Initialize TOP geometry parameters
308 main.add_module('TOPGeometryParInitializer')
309 
310 # Time Recalibrator
311 main.add_module('TOPTimeRecalibrator', subtractBunchTime=False)
312 
313 # Channel masking
314 main.add_module('TOPChannelMasker')
315 
316 # Exclude BS13d
317 main.add_module(Mask_BS13d())
318 
319 # Bunch finder
320 main.add_module('TOPBunchFinder', usePIDLikelihoods=True, subtractRunningOffset=False)
321 
322 # Common T0 calibration
323 if method == 'BF':
324  main.add_module(calibrateGlobalT0Offline())
325 elif method == 'LL':
326  main.add_module('TOPCommonT0Calibrator', sample=sampleType, outputFileName=fileName)
327 else:
328  b2.B2ERROR('unknown method ' + method)
329  sys.exit()
330 
331 # Print progress
332 progress = b2.register_module('Progress')
333 main.add_module(progress)
334 
335 # Process events
336 b2.process(main)
337 
338 # Print statistics
339 print(b2.statistics)
Class to access a DBObjPtr from Python.
Definition: PyDBObj.h:48
a (simplified) python wrapper for StoreArray.
Definition: PyStoreArray.h:56
a (simplified) python wrapper for StoreObjPtr.
Definition: PyStoreObj.h:67
run
run number formatted as string with leading zeros
h1b
histogram of current offset, wrap-around into [0, 1] of bunch sep.
h1a
histogram of current offset, wrap-around into [-1/2, 1/2] of bunch sep.
h2b
histogram of current offset vs event number, wrap-around into [0, 1]
t0
common T0 used for the calibration of the input file
h2
histogram of current offset vs event number
h2a
histogram of current offset vs event number, wrap-around into [-1/2, 1/2]