Belle II Software development
klm_time.py
1
8
9'''
10Validation of KLM time calibration (cable delay and time constants).
11'''
12
13
14import basf2
15from prompt import ValidationSettings
16import ROOT
17from ROOT.Belle2 import KLMCalibrationChecker
18import sys
19import subprocess
20import math
21import os
22import json
23from ROOT import TH1F, TH2F, TCanvas, TFile, gStyle
24
25
26settings = ValidationSettings(name='KLM time',
27 description=__doc__,
28 download_files=['stdout'],
29 expert_config={
30 "chunk_size": 100,
31 "LowerTimeBoundaryRPC": -800.0,
32 "UpperTimeBoundaryRPC": -600.0,
33 "LowerTimeBoundaryScintillatorsBKLM": -4800.0,
34 "UpperTimeBoundaryScintillatorsBKLM": -4600.0,
35 "LowerTimeBoundaryScintillatorsEKLM": -4850.0,
36 "UpperTimeBoundaryScintillatorsEKLM": -4600.0,
37 "UpperSigmaAxisEventT0": 20.0,
38 })
39
40# Avoid looking for the release globaltag
41basf2.conditions.override_globaltags()
42
43
44def run_validation(job_path, input_data_path, requested_iov, expert_config):
45 '''
46 Run the validation.
47 Note:
48 - job_path will be replaced with path/to/calibration_results
49 - input_data_path will be replaced with path/to/data_path used for calibration, e.g. /group/belle2/dataprod/Data/PromptSkim/
50 '''
51
52 # Grab the expert configurations.
53 expert_config = json.loads(expert_config)
54 print(expert_config)
55 chunk_size = expert_config['chunk_size']
56 LowerTimeBoundaryRPC = expert_config['LowerTimeBoundaryRPC']
57 UpperTimeBoundaryRPC = expert_config['UpperTimeBoundaryRPC']
58 LowerTimeBoundaryScintillatorsBKLM = expert_config['LowerTimeBoundaryScintillatorsBKLM']
59 UpperTimeBoundaryScintillatorsBKLM = expert_config['UpperTimeBoundaryScintillatorsBKLM']
60 LowerTimeBoundaryScintillatorsEKLM = expert_config['LowerTimeBoundaryScintillatorsEKLM']
61 UpperTimeBoundaryScintillatorsEKLM = expert_config['UpperTimeBoundaryScintillatorsEKLM']
62 UpperSigmaAxisEventT0 = expert_config['UpperSigmaAxisEventT0']
63
64 # Ignore the ROOT command line options.
65 ROOT.PyConfig.IgnoreCommandLineOptions = True # noqa
66 # Run ROOT in batch mode.
67 ROOT.gROOT.SetBatch(True)
68 # Set the Belle II style.
69 ROOT.gROOT.SetStyle("BELLE2")
70 # And unset the stat box.
71 ROOT.gStyle.SetOptStat(0)
72
73 # Path to the database.txt file.
74 database_file = os.path.join(f'{job_path}', 'KLMTime', 'outputdb', 'database.txt')
75
76 # Check the list of runs from the file database.txt for all payloads.
77 exp_run_dict_cabledelay = {}
78 exp_run_dict_constants = {}
79 exp_run_dict_resolution = {}
80 previous_exp_cabledelay = -666
81 previous_exp_constants = -666
82 previous_exp_resolution = -666
83
84 with open(database_file) as f:
85 for line in f:
86 fields = line.split(' ')
87 if fields[0] == 'dbstore/KLMTimeCableDelay':
88 iov = fields[2].split(',')
89 exp = int(iov[0])
90 run = int(iov[1])
91 if exp != previous_exp_cabledelay:
92 exp_run_dict_cabledelay[exp] = [run]
93 previous_exp_cabledelay = exp
94 else:
95 exp_run_dict_cabledelay[exp].append(run)
96 elif fields[0] == 'dbstore/KLMTimeConstants':
97 iov = fields[2].split(',')
98 exp = int(iov[0])
99 run = int(iov[1])
100 if exp != previous_exp_constants:
101 exp_run_dict_constants[exp] = [run]
102 previous_exp_constants = exp
103 else:
104 exp_run_dict_constants[exp].append(run)
105 elif fields[0] == 'dbstore/KLMEventT0HitResolution':
106 iov = fields[2].split(',')
107 exp = int(iov[0])
108 run = int(iov[1])
109 if exp != previous_exp_resolution:
110 exp_run_dict_resolution[exp] = [run]
111 previous_exp_resolution = exp
112 else:
113 exp_run_dict_resolution[exp].append(run)
114
115 # Tweak the IoV range if the first run is 0.
116 # This is needed for display purposes.
117 for exp, run_list in exp_run_dict_cabledelay.items():
118 run_list.sort()
119 if len(run_list) > 1:
120 if run_list[0] == 0 and run_list[1] > 5:
121 run_list[0] = run_list[1] - 5
122
123 for exp, run_list in exp_run_dict_constants.items():
124 run_list.sort()
125 if len(run_list) > 1:
126 if run_list[0] == 0 and run_list[1] > 5:
127 run_list[0] = run_list[1] - 5
128
129 for exp, run_list in exp_run_dict_resolution.items():
130 run_list.sort()
131 if len(run_list) > 1:
132 if run_list[0] == 0 and run_list[1] > 5:
133 run_list[0] = run_list[1] - 5
134
135 # Run the KLMCalibrationChecker class for cable delay.
136 for exp, run_list in exp_run_dict_cabledelay.items():
137 for run in run_list:
138 checker = KLMCalibrationChecker()
139 checker.setExperimentRun(exp, run)
140 checker.setTestingPayload(database_file)
141 basf2.B2INFO(f'Creating time cable delay results tree for experiment {exp}, run {run}.')
142 checker.setTimeCableDelayResultsFile(f'time_cabledelay_exp{exp}_run{run}.root')
143 checker.checkTimeCableDelay()
144
145 # Run the KLMCalibrationChecker class for time constants.
146 for exp, run_list in exp_run_dict_constants.items():
147 for run in run_list:
148 checker = KLMCalibrationChecker()
149 checker.setExperimentRun(exp, run)
150 checker.setTestingPayload(database_file)
151 basf2.B2INFO(f'Creating time constants results tree for experiment {exp}, run {run}.')
152 checker.setTimeConstantsResultsFile(f'time_constants_exp{exp}_run{run}.root')
153 checker.checkTimeConstants()
154
155 # Run the KLMCalibrationChecker class for EventT0 hit resolution.
156 for exp, run_list in exp_run_dict_resolution.items():
157 for run in run_list:
158 checker = KLMCalibrationChecker()
159 checker.setExperimentRun(exp, run)
160 checker.setTestingPayload(database_file)
161 basf2.B2INFO(f'Creating EventT0 hit resolution results tree for experiment {exp}, run {run}.')
162 checker.setEventT0HitResolutionResultsFile(f'eventT0HitResolution_exp{exp}_run{run}.root')
163 checker.checkEventT0HitResolution()
164
165 # Run the validation for cable delay.
166 for exp, run_list in exp_run_dict_cabledelay.items():
167 # For each experiment, merge the files in chunks of some runs.
168 chunks = math.ceil(len(run_list) / chunk_size)
169 chunk_files = []
170 for chunk in range(chunks):
171 file_name = f'time_cabledelay_exp{exp}_chunk{chunk}.root'
172 run_files = [
173 f'time_cabledelay_exp{exp}_run{run}.root' for run in run_list[chunk * chunk_size:(chunk + 1) * chunk_size]]
174 subprocess.run(['hadd', '-f', file_name] + run_files, check=True)
175 chunk_files.append(file_name)
176
177 # Let's delete the files for single IoVs.
178 for run_file in run_files:
179 try:
180 os.remove(run_file)
181 except OSError as e:
182 basf2.B2ERROR(f'The file {run_file} can not be removed: {e.strerror}')
183
184 # Now merge all chunks into one final file for plotting
185 final_file_name = f'time_cabledelay_exp{exp}_all.root'
186 subprocess.run(['hadd', '-f', final_file_name] + chunk_files, check=True)
187 input_file = ROOT.TFile(final_file_name)
188
189 gStyle.SetOptStat(1111111)
190
191 barrel_RPC = TH1F("barrel_RPC", "time cable delay for Barrel RPC", 100, LowerTimeBoundaryRPC, UpperTimeBoundaryRPC)
192 barrel_scintillator = TH1F("barrel_scintillator", "time cable delay for Barrel scintillator",
193 100, LowerTimeBoundaryScintillatorsBKLM, UpperTimeBoundaryScintillatorsBKLM)
194 endcap_scintillator = TH1F("endcap_scintillator", "time cable delay for endcap scintillator",
195 100, LowerTimeBoundaryScintillatorsEKLM, UpperTimeBoundaryScintillatorsEKLM)
196
197 tree = input_file.Get("cabledelay")
198 assert isinstance(tree, ROOT.TTree) == 1
199 myC_cd = TCanvas("myC_cd")
200
201 tree.Draw("timeDelay>>barrel_RPC", "subdetector==1 & layer>=3")
202 barrel_RPC.GetXaxis().SetTitle("T_{cable} (ns)")
203 barrel_RPC.GetYaxis().SetTitle("Entries")
204 myC_cd.Print("barrel_RPC.png")
205
206 tree.Draw("timeDelay>>barrel_scintillator", "subdetector==1 & layer<3")
207 barrel_scintillator.GetXaxis().SetTitle("T_{cable} (ns)")
208 barrel_scintillator.GetYaxis().SetTitle("Entries")
209 myC_cd.Print("barrel_scintillator.png")
210
211 tree.Draw("timeDelay>>endcap_scintillator", "subdetector==2")
212 endcap_scintillator.GetXaxis().SetTitle("T_{cable} (ns)")
213 endcap_scintillator.GetYaxis().SetTitle("Entries")
214 myC_cd.Print("endcap_scintillator.png")
215
216 # Write out histograms
217 fout = TFile("KLMTimeCableDelay.root", "recreate")
218 barrel_RPC.Write()
219 barrel_scintillator.Write()
220 endcap_scintillator.Write()
221 input_file.Close()
222 fout.Close()
223
224 # Delete the chunk files
225 for chunk_file in chunk_files:
226 try:
227 os.remove(chunk_file)
228 except OSError as e:
229 basf2.B2ERROR(f'The file {chunk_file} can not be removed: {e.strerror}')
230
231 # Run the validation for time constants.
232 for exp, run_list in exp_run_dict_constants.items():
233 # For each experiment, merge the files in chunks of some runs.
234 chunks = math.ceil(len(run_list) / chunk_size)
235 chunk_files = []
236 for chunk in range(chunks):
237 file_name = f'time_constants_exp{exp}_chunk{chunk}.root'
238 run_files = [
239 f'time_constants_exp{exp}_run{run}.root' for run in run_list[chunk * chunk_size:(chunk + 1) * chunk_size]]
240 subprocess.run(['hadd', '-f', file_name] + run_files, check=True)
241 chunk_files.append(file_name)
242
243 # Let's delete the files for single IoVs.
244 for run_file in run_files:
245 try:
246 os.remove(run_file)
247 except OSError as e:
248 basf2.B2ERROR(f'The file {run_file} can not be removed: {e.strerror}')
249
250 # Now merge all chunks into one final file for plotting
251 final_file_name = f'time_constants_exp{exp}_all.root'
252 subprocess.run(['hadd', '-f', final_file_name] + chunk_files, check=True)
253 input_file = ROOT.TFile(final_file_name)
254 gStyle.SetOptStat(1111111)
255
256 barrel_RPCPhi = TH2F("barrel_RPCPhi",
257 "time constants for Barrel RPC phi readout", 100, 30000, 65000, 100, 0.004, 0.015)
258 barrel_RPCZ = TH2F("barrel_RPCZ",
259 "time constants for Barrel RPC Z readout", 100, 30000, 65000, 100, 0.0, 0.0025)
260 barrel_scintillator_constants = TH2F("barrel_scintillator_constants",
261 "time constants for Barrel scintillator", 100, 30000, 65000, 100, 0.075, 0.09)
262 endcap_scintillator_constants = TH2F("endcap_scintillator_constants",
263 "time constants for endcap scintillator", 100, 0, 16000, 100, 0.060, 0.075)
264
265 tree = input_file.Get("constants")
266 assert isinstance(tree, ROOT.TTree) == 1
267 myC_const = TCanvas("myC_const")
268
269 tree.Draw("delayRPCPhi:channelNumber>>barrel_RPCPhi", "subdetector==1 & layer>=3", "colz")
270 barrel_RPCPhi.GetXaxis().SetTitle("Channel number")
271 barrel_RPCPhi.GetYaxis().SetTitle("Time Delay constants")
272 myC_const.Print("barrel_RPCPhi.png")
273
274 tree.Draw("delayRPCZ:channelNumber>>barrel_RPCZ", "subdetector==1 & layer>=3", "colz")
275 barrel_RPCZ.GetXaxis().SetTitle("Channel number")
276 barrel_RPCZ.GetYaxis().SetTitle("Time Delay constants")
277 myC_const.Print("barrel_RPCZ.png")
278
279 tree.Draw("delayBKLM:channelNumber>>barrel_scintillator_constants", "subdetector==1 & layer<3", "colz")
280 barrel_scintillator_constants.GetXaxis().SetTitle("Channel number")
281 barrel_scintillator_constants.GetYaxis().SetTitle("Time Delay constants")
282 myC_const.Print("barrel_scintillator_constants.png")
283
284 tree.Draw("delayEKLM:channelNumber>>endcap_scintillator_constants", "subdetector==2", "colz")
285 endcap_scintillator_constants.GetXaxis().SetTitle("Channel number")
286 endcap_scintillator_constants.GetYaxis().SetTitle("Time Delay constants")
287 myC_const.Print("endcap_scintillator_constants.png")
288
289 fout = TFile("KLMTimeConstants.root", "recreate")
290 barrel_RPCPhi.Write()
291 barrel_RPCZ.Write()
292 barrel_scintillator_constants.Write()
293 endcap_scintillator_constants.Write()
294 input_file.Close()
295 fout.Close()
296
297 # Delete the chunk files
298 for chunk_file in chunk_files:
299 try:
300 os.remove(chunk_file)
301 except OSError as e:
302 basf2.B2ERROR(f'The file {chunk_file} can not be removed: {e.strerror}')
303
304 # Run the validation for EventT0 hit resolution.
305 # Category enum (must match KLMEventT0HitResolution::Category):
306 # 1 = EKLM Scint, 2 = BKLM Scint, 3 = RPC combined, 4 = RPC Phi, 5 = RPC Z
307 category_labels = {1: "EKLM Scint", 2: "BKLM Scint", 3: "RPC combined", 4: "RPC Phi", 5: "RPC Z"}
308 category_order = [2, 4, 5, 3, 1] # BKLM Scint, RPC Phi, RPC Z, RPC comb, EKLM Scint
309
310 for exp, run_list in exp_run_dict_resolution.items():
311 # For each experiment, merge the files in chunks of some runs.
312 chunks = math.ceil(len(run_list) / chunk_size)
313 chunk_files = []
314 for chunk in range(chunks):
315 file_name = f'eventT0HitResolution_exp{exp}_chunk{chunk}.root'
316 run_files = [
317 f'eventT0HitResolution_exp{exp}_run{run}.root' for run in run_list[chunk * chunk_size:(chunk + 1) * chunk_size]]
318 subprocess.run(['hadd', '-f', file_name] + run_files, check=True)
319 chunk_files.append(file_name)
320
321 # Let's delete the files for single IoVs.
322 for run_file in run_files:
323 try:
324 os.remove(run_file)
325 except OSError as e:
326 basf2.B2ERROR(f'The file {run_file} can not be removed: {e.strerror}')
327
328 # Now merge all chunks into one final file for plotting
329 final_file_name = f'eventT0HitResolution_exp{exp}_all.root'
330 subprocess.run(['hadd', '-f', final_file_name] + chunk_files, check=True)
331 input_file = ROOT.TFile(final_file_name)
332
333 gStyle.SetOptStat(0)
334
335 tree = input_file.Get("eventT0HitResolution")
336 assert isinstance(tree, ROOT.TTree) == 1
337
338 # Mean sigma per category (across runs), with stat error on the mean.
339 nCats = len(category_order)
340 hSigma = TH1F("eventT0_sigma_per_category",
341 f"KLM EventT0 hit resolution (exp {exp});Detector;#sigma_{{t}} (ns)",
342 nCats, 0, nCats)
343
344 for iBin, cat in enumerate(category_order, start=1):
345 sumS = 0.0
346 sumS2 = 0.0
347 n = 0
348 for entry in tree:
349 if int(entry.category) == cat:
350 sumS += entry.sigma
351 sumS2 += entry.sigma * entry.sigma
352 n += 1
353 if n > 0:
354 mean = sumS / n
355 var = max(sumS2 / n - mean * mean, 0.0)
356 stderr = (var ** 0.5) / (n ** 0.5) if n > 1 else 0.0
357 else:
358 mean = 0.0
359 stderr = 0.0
360 hSigma.SetBinContent(iBin, mean)
361 hSigma.SetBinError(iBin, stderr)
362 hSigma.GetXaxis().SetBinLabel(iBin, category_labels[cat])
363
364 hSigma.SetMinimum(0.0)
365 hSigma.SetMaximum(UpperSigmaAxisEventT0)
366 hSigma.SetMarkerStyle(20)
367 hSigma.SetMarkerSize(1.2)
368 hSigma.SetLineWidth(2)
369
370 myC_evt = TCanvas("myC_evt", "", 800, 600)
371 hSigma.Draw("E1P")
372 myC_evt.Print(f"eventT0HitResolution_exp{exp}.png")
373
374 # Write out histogram
375 fout = TFile("KLMEventT0HitResolution.root", "recreate")
376 hSigma.Write()
377 input_file.Close()
378 fout.Close()
379
380 # Delete the chunk files
381 for chunk_file in chunk_files:
382 try:
383 os.remove(chunk_file)
384 except OSError as e:
385 basf2.B2ERROR(f'The file {chunk_file} can not be removed: {e.strerror}')
386
387
388if __name__ == "__main__":
389 run_validation(*sys.argv[1:])