Belle II Software  release-05-01-25
cdst_chi2ModuleT0calibration.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 
4 # --------------------------------------------------------------------------------
5 # Calibrate module T0 with Bhabha's or dimuons using time difference between slots
6 # (M. Staric, 2019-07-10)
7 #
8 # usage: basf2 cdst_chi2ModuleT0calibration.py experiment runFirst runLast
9 # job: bsub -q l "basf2 cdst_chi2ModuleT0calibration.py experiment runFirst runLast"
10 #
11 # note: runLast is inclusive
12 # --------------------------------------------------------------------------------
13 
14 from basf2 import *
15 from ROOT import Belle2
16 from ROOT import TFile, TH1F, TH2F, TF1, TMatrixDSym
17 from array import array
18 import math
19 import sys
20 import glob
21 import os
22 
23 # ----- those need to be adjusted before running --------------------------------------
24 #
25 sampleType = 'bhabha' # sample type: 'bhabha' or 'dimuon'
26 data_dir = '/group/belle2/dataprod/Data/release-03-02-02/DB00000635/proc00000009_nofilter'
27 skim_dir = 'skim/hlt_bhabha/cdst/sub00/'
28 globalTag = 'data_reprocessing_prompt' # base global tag
29 stagingTags = ['staging_data_reprocessing'] # list of tags with new calibration
30 localDB = [] # list of local databases with new calibration
31 minEntries = 10 # minimal number of histogram entries required to perform a fit
32 output_dir = 'moduleT0' # main output folder
33 #
34 # -------------------------------------------------------------------------------------
35 
36 # Argument parsing
37 argvs = sys.argv
38 if len(argvs) < 4:
39  print("usage: basf2", argvs[0], "experiment runFirst runLast")
40  sys.exit()
41 experiment = int(argvs[1])
42 run_first = int(argvs[2])
43 run_last = int(argvs[3])
44 
45 expNo = 'e' + '{:0=4d}'.format(experiment)
46 
47 # Make list of files
48 files = []
49 for run in range(run_first, run_last + 1):
50  runNo = 'r' + '{:0=5d}'.format(run)
51  for typ in ['4S', 'Continuum', 'Scan']:
52  folder = data_dir + '/' + expNo + '/' + typ + '/' + runNo + '/' + skim_dir
53  files += glob.glob(folder + '/cdst.*.root')
54 if len(files) == 0:
55  B2ERROR('No cdst files found')
56  sys.exit()
57 
58 # Output folder
59 method = 'chi2'
60 output_folder = output_dir + '/' + expNo + '/' + sampleType + '/' + method
61 if not os.path.isdir(output_folder):
62  os.makedirs(output_folder)
63  print('New folder created: ' + output_folder)
64 
65 # Output file name
66 fileName = output_folder + '/moduleT0-' + expNo + '-'
67 run1 = 'r' + '{:0=5d}'.format(run_first)
68 run2 = 'r' + '{:0=5d}'.format(run_last)
69 fileName += run1 + '_to_' + run2 + '.root'
70 print('Output file:', fileName)
71 
72 
73 class Mask_BS13d(Module):
74  ''' exclude (mask-out) BS 13d '''
75 
76  def event(self):
77  ''' event processing '''
78 
79  for digit in Belle2.PyStoreArray('TOPDigits'):
80  if digit.getModuleID() == 13 and digit.getBoardstackNumber() == 3:
81  digit.setHitQuality(Belle2.TOPDigit.c_Junk)
82 
83 
84 class ModuleT0cal(Module):
85  ''' module T0 calibrator using chi2 minimization of time differences between slots '''
86 
87  def initialize(self):
88  ''' initialize and book histograms '''
89 
90  Belle2.PyStoreArray('TOPTimeZeros').isRequired()
91 
92 
93  self.db_moduleT0 = Belle2.PyDBObj('TOPCalModuleT0')
94 
95 
96  self.h_deltaT0 = {}
97 
98 
99  self.h_slotPairs = TH2F("slots", "slot pairs: number of entries",
100  16, 0.5, 16.5, 16, 0.5, 16.5)
101  self.h_slotPairs.SetXTitle("first slot number")
102  self.h_slotPairs.SetYTitle("second slot number")
103 
104 
105  self.h_slotPairs_acc = TH2F("slots_acc",
106  "slot pairs: #chi^{2} /NDF of " +
107  "successfully fitted time differences",
108  16, 0.5, 16.5, 16, 0.5, 16.5)
109  self.h_slotPairs_acc.SetXTitle("first slot number")
110  self.h_slotPairs_acc.SetYTitle("second slot number")
111 
112 
113  self.h_relModuleT0 = TH1F("relModuleT0", "Module T0 relative to calibration",
114  16, 0.5, 16.5)
115  self.h_relModuleT0.SetXTitle("slot number")
116  self.h_relModuleT0.SetYTitle("module T0 residual [ns]")
117 
118 
119  self.h_moduleT0 = TH1F("moduleT0", "Module T0", 16, 0.5, 16.5)
120  self.h_moduleT0.SetXTitle("slot number")
121  self.h_moduleT0.SetYTitle("module T0 [ns]")
122 
123  def sortedTimeZeros(self, unsortedPyStoreArray):
124  ''' sorting timeZeros according to slot number '''
125 
126  # first convert to a python-list to be able to sort
127  py_list = [x for x in unsortedPyStoreArray]
128  # sort via a hierachy of sort keys
129  return sorted(
130  py_list,
131  key=lambda x: (
132  x.getModuleID(),
133  )
134  )
135 
136  def event(self):
137  ''' event processor: fill histograms for events with two entries in timeZeros '''
138 
139  timeZeros = Belle2.PyStoreArray('TOPTimeZeros')
140  if timeZeros.getEntries() != 2:
141  return
142 
143  tZeros = self.sortedTimeZeros(timeZeros)
144 
145  slot1 = tZeros[0].getModuleID()
146  slot2 = tZeros[1].getModuleID()
147  self.h_slotPairs.Fill(slot1, slot2)
148  if slot1 not in self.h_deltaT0:
149  self.h_deltaT0[slot1] = {}
150  if slot2 not in self.h_deltaT0[slot1]:
151  name = 'deltaT0_' + str(slot1) + '-' + str(slot2)
152  title = 'time difference: slot ' + str(slot1) + ' - slot ' + str(slot2)
153  self.h_deltaT0[slot1][slot2] = TH1F(name, title, 200, -5, 5)
154  self.h_deltaT0[slot1][slot2].SetXTitle('time difference [ns]')
155  dt = tZeros[0].getTime() - tZeros[1].getTime()
156  self.h_deltaT0[slot1][slot2].Fill(dt)
157 
158  def singleFit(self, h):
159  ''' perform a fit of time difference distribution '''
160 
161  xmin = h.GetXaxis().GetXmin()
162  xmax = h.GetXaxis().GetXmax()
163  fun = TF1("fun", "[0]*exp(-0.5*((x-[1])/[2])**2)+[3]", xmin, xmax)
164  fun.SetParameter(0, h.GetMaximum())
165  fun.SetParameter(1, h.GetBinCenter(h.GetMaximumBin()))
166  fun.SetParameter(2, 0.2)
167  fun.SetParameter(3, 0)
168  return h.Fit(fun, "LERSQ")
169 
170  def fitHistograms(self, minEntries):
171  ''' fit all histograms and store the results into lists '''
172 
173 
174  self.delT0 = []
175 
176  self.sigma = []
177 
178  self.A = []
179 
180  self.h_out = []
181 
182  for slot1 in range(1, 10):
183  if slot1 not in self.h_deltaT0:
184  continue
185  for slot2 in range(slot1 + 7, slot1 + 10):
186  if slot2 not in self.h_deltaT0[slot1]:
187  continue
188  h = self.h_deltaT0[slot1][slot2]
189  self.h_out.append(h)
190  if h.GetEntries() > minEntries:
191  result = self.singleFit(h)
192  if int(result) == 0:
193  chi2 = result.Chi2() / result.Ndf()
194  self.h_slotPairs_acc.SetBinContent(slot1, slot2, chi2)
195  self.delT0.append(result.Parameter(1))
196  self.sigma.append(result.ParError(1))
197  a = [0 for i in range(16)]
198  a[slot1 - 1] = 1
199  a[slot2 - 1] = -1
200  self.A.append(a)
201 
202  def minimize(self):
203  '''
204  Minimization procedure. For the method see NIM A 586 (2008) 174-179, sect. 2.2.
205  '''
206 
207  # append the bound (sum of all calibration constants equals to 0)
208  self.delT0.append(0.0)
209  self.sigma.append(0.001) # arbitrary but small compared to calibration precision
210  a = [1 for i in range(16)]
211  self.A.append(a)
212  m = len(self.delT0)
213 
214  # for i, a in enumerate(self.A):
215  # print(a, round(self.delT0[i], 3), round(self.sigma[i], 3))
216 
217  # construct the matrix of a linear system of equations
218  B = TMatrixDSym(16)
219  for i in range(16):
220  for j in range(16):
221  for k in range(m):
222  B[i][j] += self.A[k][i] * self.A[k][j] / self.sigma[k]**2
223 
224  # invert the matrix
225  det = array('d', [0])
226  B.Invert(det)
227  if det[0] == 0:
228  B2ERROR("Matrix inversion failed")
229  return False
230 
231  # construct the right side of a linear system of equations
232  b = [0.0 for i in range(16)]
233  for i in range(16):
234  for k in range(m):
235  b[i] += self.A[k][i] * self.delT0[k] / self.sigma[k]**2
236 
237  # solve for unknown relative module T0
238  x = [0 for i in range(16)] # module T0's
239  e = [0 for i in range(16)] # uncertainties on module T0
240  for i in range(16):
241  for j in range(16):
242  x[i] += B[i][j] * b[j]
243  e[i] = math.sqrt(B[i][i])
244 
245  # calculate chi^2 and ndf
246  chi2 = 0
247  for k in range(m):
248  s = 0
249  for i in range(16):
250  s += self.A[k][i] * x[i]
251  chi2 += ((s - self.delT0[k]) / self.sigma[k])**2
252  ndf = m - 16
253  chi_ndf = 'chi^2/NDF = ' + str(round(chi2, 2)) + '/' + str(ndf)
254  self.h_relModuleT0.SetTitle(self.h_relModuleT0.GetTitle() + ' (' + chi_ndf + ')')
255 
256  # store relative module T0 in a histogram
257  for i in range(16):
258  self.h_relModuleT0.SetBinContent(i+1, x[i])
259  self.h_relModuleT0.SetBinError(i+1, e[i])
260 
261  # calculate arithmetic average of current calibration constants
262  T0 = [0.0 for i in range(16)]
263  average = 0.0
264  num = 0
265  for i in range(16):
266  if self.db_moduleT0.isCalibrated(i+1):
267  T0[i] = self.db_moduleT0.getT0(i+1)
268  average += T0[i]
269  num += 1
270  if num > 0:
271  average /= num
272 
273  # add current calibration to relative one and subtract the average, then store
274  for i in range(16):
275  self.h_moduleT0.SetBinContent(i+1, x[i] + T0[i] - average)
276  self.h_moduleT0.SetBinError(i+1, e[i])
277 
278  B2RESULT("Module T0 calibration constants successfully determined, " + chi_ndf)
279  return True
280 
281  def terminate(self):
282  ''' fit histograms, minimize and write results to file '''
283 
284  # fit time difference distributions
285  self.fitHistograms(minEntries)
286 
287  # minimize
288  OK = self.minimize()
289 
290  # open output file
291  file = TFile.Open(fileName, 'recreate')
292 
293  # write histograms and close the file
294  self.h_slotPairs.Write()
295  self.h_slotPairs_acc.Write()
296  if OK:
297  self.h_relModuleT0.Write()
298  self.h_moduleT0.Write()
299  for h in self.h_out:
300  h.Write()
301  file.Close()
302 
303  B2RESULT("Output written to " + fileName)
304 
305 
306 # Database
307 use_central_database(globalTag)
308 for tag in stagingTags:
309  use_central_database(tag)
310 for db in localDB:
311  if os.path.isfile(db):
312  use_local_database(db, invertLogging=True)
313  else:
314  B2ERROR(db + ": local database not found")
315  sys.exit()
316 
317 # Create paths
318 main = create_path()
319 
320 # Input (cdst files)
321 main.add_module('RootInput', inputFileNames=files)
322 
323 # Initialize TOP geometry parameters
324 main.add_module('TOPGeometryParInitializer')
325 
326 # Time Recalibrator
327 main.add_module('TOPTimeRecalibrator', subtractBunchTime=False)
328 
329 # Channel masking
330 main.add_module('TOPChannelMasker')
331 
332 # Exclude BS13d
333 main.add_module(Mask_BS13d())
334 
335 # Bunch finder
336 main.add_module('TOPBunchFinder', usePIDLikelihoods=True, subtractRunningOffset=False)
337 
338 # Module T0 calibrator
339 main.add_module(ModuleT0cal())
340 
341 # Print progress
342 main.add_module('Progress')
343 
344 # Process events
345 process(main)
346 
347 # Print statistics
348 print(statistics)
cdst_chi2ModuleT0calibration.ModuleT0cal.h_moduleT0
h_moduleT0
histogram to store final results (module T0)
Definition: cdst_chi2ModuleT0calibration.py:119
cdst_chi2ModuleT0calibration.ModuleT0cal.h_deltaT0
h_deltaT0
histograms of time difference between two slots
Definition: cdst_chi2ModuleT0calibration.py:96
cdst_chi2ModuleT0calibration.ModuleT0cal.terminate
def terminate(self)
Definition: cdst_chi2ModuleT0calibration.py:281
cdst_chi2ModuleT0calibration.ModuleT0cal.h_slotPairs_acc
h_slotPairs_acc
histogram to store Chi2/ndf of successfully fitted time differences
Definition: cdst_chi2ModuleT0calibration.py:105
cdst_chi2ModuleT0calibration.ModuleT0cal.event
def event(self)
Definition: cdst_chi2ModuleT0calibration.py:136
cdst_chi2ModuleT0calibration.ModuleT0cal.minimize
def minimize(self)
Definition: cdst_chi2ModuleT0calibration.py:202
cdst_chi2ModuleT0calibration.ModuleT0cal.singleFit
def singleFit(self, h)
Definition: cdst_chi2ModuleT0calibration.py:158
cdst_chi2ModuleT0calibration.ModuleT0cal.sortedTimeZeros
def sortedTimeZeros(self, unsortedPyStoreArray)
Definition: cdst_chi2ModuleT0calibration.py:123
cdst_chi2ModuleT0calibration.ModuleT0cal.h_slotPairs
h_slotPairs
histogram of number of entries per slot pairs
Definition: cdst_chi2ModuleT0calibration.py:99
cdst_chi2ModuleT0calibration.ModuleT0cal.A
A
matrix denoting which slot pair was involved
Definition: cdst_chi2ModuleT0calibration.py:178
cdst_chi2ModuleT0calibration.ModuleT0cal.sigma
sigma
uncertainties of fitted time differences
Definition: cdst_chi2ModuleT0calibration.py:176
cdst_chi2ModuleT0calibration.ModuleT0cal.delT0
delT0
fitted time differences
Definition: cdst_chi2ModuleT0calibration.py:174
cdst_chi2ModuleT0calibration.ModuleT0cal.fitHistograms
def fitHistograms(self, minEntries)
Definition: cdst_chi2ModuleT0calibration.py:170
Belle2::PyDBObj
Class to access a DBObjPtr from Python.
Definition: PyDBObj.h:50
cdst_chi2ModuleT0calibration.ModuleT0cal.h_relModuleT0
h_relModuleT0
histogram to store minimization results (relative module T0)
Definition: cdst_chi2ModuleT0calibration.py:113
cdst_chi2ModuleT0calibration.ModuleT0cal.h_out
h_out
list of histograms selected for output
Definition: cdst_chi2ModuleT0calibration.py:180
cdst_chi2ModuleT0calibration.Mask_BS13d.event
def event(self)
Definition: cdst_chi2ModuleT0calibration.py:76
cdst_chi2ModuleT0calibration.ModuleT0cal.db_moduleT0
db_moduleT0
module T0 from database
Definition: cdst_chi2ModuleT0calibration.py:93
cdst_chi2ModuleT0calibration.ModuleT0cal.initialize
def initialize(self)
Definition: cdst_chi2ModuleT0calibration.py:87
Belle2::PyStoreArray
a (simplified) python wrapper for StoreArray.
Definition: PyStoreArray.h:58
cdst_chi2ModuleT0calibration.Mask_BS13d
Definition: cdst_chi2ModuleT0calibration.py:73
cdst_chi2ModuleT0calibration.ModuleT0cal
Definition: cdst_chi2ModuleT0calibration.py:84