Belle II Software development
caf_cdc.py
1# disable doxygen check for this file
2# @cond
3
4
11
12"""Full CDC tracking calibration."""
13from prompt import CalibrationSettings, INPUT_DATA_FILTERS
14from prompt.utils import events_in_basf2_file, ExpRun
15import basf2
16from ROOT import Belle2
17from random import choice
18from caf.framework import Calibration
19from caf import strategies
20
21
22
23settings = CalibrationSettings(name="CDC Tracking",
24 expert_username="dvthanh",
25 description=__doc__,
26 input_data_formats=["raw"],
27 input_data_names=["mumu_tight_or_highm_calib", "hadron_calib", "cosmic_calib"],
28 input_data_filters={"mumu_tight_or_highm_calib":
29 [INPUT_DATA_FILTERS["Data Tag"]["mumu_tight_or_highm_calib"],
30 INPUT_DATA_FILTERS["Data Quality Tag"]["Good"],
31 INPUT_DATA_FILTERS["Magnet"]["On"]],
32 "hadron_calib":
33 [INPUT_DATA_FILTERS["Data Tag"]["hadron_calib"],
34 INPUT_DATA_FILTERS["Data Quality Tag"]["Good"],
35 INPUT_DATA_FILTERS["Magnet"]["On"]],
36 "cosmic_calib":
37 [INPUT_DATA_FILTERS["Data Tag"]["cosmic_calib"],
38 INPUT_DATA_FILTERS["Data Quality Tag"]["Good"],
39 INPUT_DATA_FILTERS["Magnet"]["On"]]},
40 depends_on=[],
41 expert_config={
42 "min_events_per_file": 500,
43 "max_events_per_file": 20000,
44 "max_events_per_file_hadron_for_tz_tw": 5000,
45 "max_events_per_file_hadron_for_xt_sr": 12000,
46 "min_events_for_tz_tw_calibration": 500000,
47 "max_events_for_tz_tw_calibration": 1000000,
48 "min_events_for_xt_sr_calibration": 1000000, # 1M
49 "max_events_for_xt_sr_calibration": 10000000, # 10M
50 "fractions_for_each_type": [0.5, 1, 0.5], # [mumu, hadron, cosmic]
51 "max_job_for_each_type": [400, 700, 400],
52 "calib_mode": "quick", # manual or predefined: quick, full
53 "calibration_procedure": {"tz0": 1, "xt0": 0, "sr_tz0": 0, "tz2": 0},
54 "payload_boundaries": [],
55 "backend_args": {"request_memory": "4 GB"}
56 })
57
58
59def select_files(all_input_files, min_events, max_events, max_processed_events_per_file, max_job=800, min_events_per_file=500):
60 basf2.B2INFO(f"Minimum number of events: {min_events}")
61 basf2.B2INFO(f"Maximum number of events: {max_events}")
62 basf2.B2INFO(f"Conditions: ({min_events_per_file} < #Event/file < {max_processed_events_per_file}) and max_job = {max_job}")
63 # Let's iterate, taking a sample of files from the total (no repeats or replacement) until we get enough events
64 total_events = 0
65 chosen_files = []
66 njob = 0
67 while total_events < max_events and njob < max_job:
68 # If the set is empty we must have used all available files. Here we break and continue. But you may want to
69 # raise an Error...
70 if not all_input_files:
71 break
72 # Randomly select a file
73 new_file_choice = choice(all_input_files)
74 # Remove it from the list so it can't be chosen again
75 all_input_files.remove(new_file_choice)
76 # Find the number of events in the file
77 total_events_in_file = events_in_basf2_file(new_file_choice)
78 if not total_events_in_file or total_events_in_file < min_events_per_file:
79 # Uh Oh! Zero event file, skip it
80 continue
81 events_contributed = 0
82 if total_events_in_file < max_processed_events_per_file:
83 # The file contains less than the max amount we have set (entrySequences)
84 events_contributed = total_events_in_file
85 else:
86 events_contributed = max_processed_events_per_file
87 chosen_files.append(new_file_choice)
88 total_events += events_contributed
89 njob += 1
90
91 basf2.B2INFO(f"Total chosen files = {len(chosen_files)}")
92 basf2.B2INFO(f"Total events in chosen files = {total_events}")
93 if total_events < min_events:
94 basf2.B2FATAL(
95 f"There is not enough required events with setup max_processed_events_per_file = {max_processed_events_per_file}")
96 return chosen_files
97
98
99
102
103
104def get_calibrations(input_data, **kwargs):
105 import basf2
106 # from prompt.utils import filter_by_max_files_per_run
107 # read expert_config values
108 expert_config = kwargs.get("expert_config")
109 calib_mode = expert_config["calib_mode"]
110 # max_files_per_run = expert_config["max_files_per_run"]
111 min_events_per_file = expert_config["min_events_per_file"]
112 max_events_per_file = expert_config["max_events_per_file"]
113
114 min_events_for_tz_tw = expert_config["min_events_for_tz_tw_calibration"] # for t0, tw calib.
115 max_events_for_tz_tw = expert_config["max_events_for_tz_tw_calibration"] # for t0, tw calib.
116 max_events_per_file_hadron_for_tz_tw = expert_config["max_events_per_file_hadron_for_tz_tw"]
117
118 min_events_for_xt_sr = expert_config["min_events_for_xt_sr_calibration"] # for xt, sr calib.
119 max_events_for_xt_sr = expert_config["max_events_for_xt_sr_calibration"] # for xt, sr calib.
120 max_events_per_file_hadron_for_xt_sr = expert_config["max_events_per_file_hadron_for_xt_sr"]
121
122 Max_events_per_file_for_tz_tw = [max_events_per_file, max_events_per_file_hadron_for_tz_tw, max_events_per_file]
123 Max_events_per_file_for_xt_sr = [max_events_per_file, max_events_per_file_hadron_for_xt_sr, max_events_per_file]
124
125 fraction_of_event_for_types = expert_config["fractions_for_each_type"]
126 max_jobs = expert_config["max_job_for_each_type"]
127 basf2.B2INFO(f"Number of job for each type are limitted at [di-muon, hadron, cosmic]: {max_jobs}")
128 basf2.B2INFO(f"Fraction for [di-muon, hadron, cosmic]: {fraction_of_event_for_types}")
129 if len(fraction_of_event_for_types) != 3:
130 basf2.B2FATAL("fraction of event must be an array with the size of 3, with order [mumu, hadron, cosmic]")
131
132 payload_boundaries = []
133 payload_boundaries.extend([ExpRun(*boundary) for boundary in expert_config["payload_boundaries"]])
134 basf2.B2INFO(f"Payload boundaries from expert_config: {payload_boundaries}")
135
136 files_for_xt_sr_dict = {}
137 files_for_tz_tw_dict = {}
138
139 # select data file for t0 and tw calibration
140 if fraction_of_event_for_types[1] > 0:
141 basf2.B2INFO("*********************** Select Hadron data for calibration ****************")
142 min_hadron_events_for_tz_tw = fraction_of_event_for_types[1] * min_events_for_tz_tw
143 max_hadron_events_for_tz_tw = fraction_of_event_for_types[1] * max_events_for_tz_tw
144 min_hadron_events_for_xt_sr = fraction_of_event_for_types[1] * min_events_for_xt_sr
145 max_hadron_events_for_xt_sr = fraction_of_event_for_types[1] * max_events_for_xt_sr
146
147 file_to_iov_hadron = input_data["hadron_calib"]
148 # select data file for tw, t0
149 basf2.B2INFO("----> For T0 and Time walk correction")
150 chosen_files_hadron_for_tz_tw = select_files(list(file_to_iov_hadron.keys()),
151 min_hadron_events_for_tz_tw,
152 max_hadron_events_for_tz_tw,
153 max_events_per_file_hadron_for_tz_tw,
154 max_jobs[1],
155 min_events_per_file)
156 # select data file for xt, sigma
157 basf2.B2INFO("----> For XT, space resolution calib")
158 chosen_files_hadron_for_xt_sr = select_files(list(file_to_iov_hadron.keys()),
159 min_hadron_events_for_xt_sr,
160 max_hadron_events_for_xt_sr,
161 max_events_per_file_hadron_for_xt_sr,
162 max_jobs[1],
163 min_events_per_file)
164
165 files_for_xt_sr_dict["hadron_calib"] = chosen_files_hadron_for_xt_sr
166 files_for_tz_tw_dict["hadron_calib"] = chosen_files_hadron_for_tz_tw
167
168 if fraction_of_event_for_types[0] > 0:
169 basf2.B2INFO("***********************Select di-muon data for calibration ***************")
170 min_mumu_events_for_xt_sr = fraction_of_event_for_types[0] * min_events_for_xt_sr
171 max_mumu_events_for_xt_sr = fraction_of_event_for_types[0] * max_events_for_xt_sr
172 min_mumu_events_for_tz_tw = fraction_of_event_for_types[0] * min_events_for_tz_tw
173 max_mumu_events_for_tz_tw = fraction_of_event_for_types[0] * max_events_for_tz_tw
174 file_to_iov_mumu = input_data["mumu_tight_or_highm_calib"]
175 basf2.B2INFO("----> For T0 and Time walk correction")
176 chosen_files_mumu_for_tz_tw = select_files(list(file_to_iov_mumu.keys()),
177 min_mumu_events_for_tz_tw,
178 max_mumu_events_for_tz_tw,
179 max_events_per_file,
180 max_jobs[0],
181 min_events_per_file)
182
183 # select data file for xt, sigma calibration
184 basf2.B2INFO("----> For XT, space resolution calib")
185
186 chosen_files_mumu_for_xt_sr = select_files(list(file_to_iov_mumu.keys()), # input_files_mumu[:],
187 min_mumu_events_for_xt_sr,
188 max_mumu_events_for_xt_sr,
189 max_events_per_file,
190 max_jobs[0],
191 min_events_per_file)
192
193 files_for_xt_sr_dict["mumu_tight_or_highm_calib"] = chosen_files_mumu_for_xt_sr
194 files_for_tz_tw_dict["mumu_tight_or_highm_calib"] = chosen_files_mumu_for_tz_tw
195
196 ''' For cosmic data '''
197 if fraction_of_event_for_types[2] > 0:
198 basf2.B2INFO("********************* Select cosmic data for calibration *******************")
199 min_cosmic_events_for_tz_tw = fraction_of_event_for_types[2] * min_events_for_tz_tw
200 max_cosmic_events_for_tz_tw = fraction_of_event_for_types[2] * max_events_for_tz_tw
201 min_cosmic_events_for_xt_sr = fraction_of_event_for_types[2] * min_events_for_xt_sr
202 max_cosmic_events_for_xt_sr = fraction_of_event_for_types[2] * max_events_for_xt_sr
203
204 file_to_iov_cosmic = input_data["cosmic_calib"]
205
206 # Select cosmic data for tw and t0 calibration
207 basf2.B2INFO("---->For T0 and Time walk correction")
208 chosen_files_cosmic_for_tz_tw = select_files(list(file_to_iov_cosmic.keys()),
209 min_cosmic_events_for_tz_tw,
210 max_cosmic_events_for_tz_tw,
211 max_events_per_file,
212 max_jobs[2],
213 min_events_per_file)
214
215 # select cosmics data for xt and sigma calibration
216 basf2.B2INFO("----> For T0 and Time walk correction")
217 chosen_files_cosmic_for_xt_sr = select_files(list(file_to_iov_cosmic.keys()),
218 min_cosmic_events_for_xt_sr,
219 max_cosmic_events_for_xt_sr,
220 max_events_per_file,
221 max_jobs[2],
222 min_events_per_file)
223 files_for_xt_sr_dict["cosmic_calib"] = chosen_files_cosmic_for_xt_sr
224 files_for_tz_tw_dict["cosmic_calib"] = chosen_files_cosmic_for_tz_tw
225
226 basf2.B2INFO("Complete input data selection.")
227
228 # Get the overall IoV we want to cover, including the end values
229 requested_iov = kwargs.get("requested_iov", None)
230
231 from caf.utils import IoV
232 # The actual IoV we want for any prompt request is open-ended
233 output_iov = IoV(requested_iov.exp_low, requested_iov.run_low, -1, -1)
234
235 # for SingleIOV stratrgy, it's better to set the granularity to 'all' so that the collector jobs will run faster
236 collector_granularity = 'all'
237 if payload_boundaries:
238 basf2.B2INFO('Found payload_boundaries: set collector granularity to run')
239 collector_granularity = 'run'
240 if calib_mode == "full":
241 calibration_procedure = {
242 "tz0": 4,
243 "xt0": 0,
244 "sr_tz0": 0,
245 "xt1": 0,
246 "sr_tz1": 0,
247 "tw0": 1,
248 "tz1": 1,
249 "xt2": 0,
250 "sr_tz2": 0,
251 "tz2": 0
252 }
253 elif calib_mode == "quick":
254 calibration_procedure = {
255 "tz0": 3,
256 "xt0": 0,
257 "sr_tz0": 0,
258 "tw0": 1,
259 "tz1": 1,
260 "xt1": 0,
261 "sr_tz1": 0,
262 "tz2": 0
263 }
264 elif calib_mode == "manual":
265 calibration_procedure = expert_config["calibration_procedure"]
266 else:
267 basf2.B2FATAL(f"Calibration mode is not defined {calib_mode}, should be quick, full, or manual")
268 # t0
269 calib_keys = list(calibration_procedure)
270 cals = [None]*len(calib_keys)
271 basf2.B2INFO(f"Run calibration mode = {calib_mode}:")
272 print(calib_keys)
273 for i in range(len(cals)):
274 max_iter = calibration_procedure[calib_keys[i]]
275 alg = None
276 data_files = None
277 cal_name = ''.join([i for i in calib_keys[i] if not i.isdigit()])
278 if cal_name == "tz":
279 alg = [tz_algo()]
280 elif cal_name == "tw":
281 alg = [tw_algo()]
282 elif cal_name == "xt":
283 alg = [xt_algo()]
284 elif cal_name == "sr_tz":
285 alg = [sr_algo(), tz_algo()]
286 else:
287 basf2.B2FATAL(f"The calibration is not defined, check spelling: calib {i}: {calib_keys[i]}")
288
289 if cal_name == "xt" or cal_name == "sr_tz":
290 max_event = Max_events_per_file_for_xt_sr
291 data_files = files_for_xt_sr_dict
292 else:
293 max_event = Max_events_per_file_for_tz_tw
294 data_files = files_for_tz_tw_dict
295 basf2.B2INFO(f"calibration for {calib_keys[i]} with number of iteration={max_iter}")
296 cals[i] = CDCCalibration(name=calib_keys[i],
297 algorithms=alg,
298 input_file_dict=data_files,
299 max_iterations=max_iter,
300 max_events=max_event,
301 use_badWires=True if calib_keys[i] == "tz" else False,
302 collector_granularity=collector_granularity,
303 backend_args=expert_config["backend_args"],
304 dependencies=[cals[i-1]] if i > 0 else None
305 )
306 if payload_boundaries:
307 basf2.B2INFO("Found payload_boundaries: calibration strategies set to SequentialBoundaries.")
308 cals[0].strategies = strategies.SequentialBoundaries
309 for alg in cals:
310 for algorithm in alg.algorithms:
311 algorithm.params = {"iov_coverage": output_iov, "payload_boundaries": payload_boundaries}
312
313 else:
314 for alg in cals:
315 for algorithm in alg.algorithms:
316 algorithm.params = {"apply_iov": output_iov}
317
318 return cals
319
320
321
322def pre_collector(max_events=None, is_cosmic=False, use_badWires=False):
323 """
324 Define pre collection (reconstruction in our purpose).
325 Probably, we need only CDC and ECL data.
326 Parameters:
327 max_events [int] : number of events to be processed.
328 All events by Default.
329 Returns:
330 path : path for pre collection
331 """
332 from basf2 import create_path, register_module
333 from softwaretrigger.constants import HLT_INPUT_OBJECTS
334 reco_path = create_path()
335 if max_events is None:
336 root_input = register_module(
337 'RootInput',
338 branchNames=HLT_INPUT_OBJECTS
339 )
340 else:
341 root_input = register_module(
342 'RootInput',
343 branchNames=HLT_INPUT_OBJECTS,
344 entrySequences=[
345 f'0:{max_events}'])
346 reco_path.add_module(root_input)
347
348 gearbox = register_module('Gearbox')
349 reco_path.add_module(gearbox)
350 reco_path.add_module('Geometry', useDB=True)
351 Components = ['CDC', 'ECL']
352
353 from rawdata import add_unpackers
354 add_unpackers(reco_path, components=Components)
355
356 if is_cosmic:
357 from reconstruction import add_cosmics_reconstruction
358 # Add cdc tracking reconstruction modules
359 add_cosmics_reconstruction(path=reco_path,
360 components=Components,
361 pruneTracks=False,
362 merge_tracks=False,
363 posttracking=False)
364 else:
365 from reconstruction import default_event_abort, add_prefilter_pretracking_reconstruction
366 from tracking import add_prefilter_tracking_reconstruction
367
368 # Do not even attempt at reconstructing events w/ abnormally large occupancy.
369 doom = reco_path.add_module("EventsOfDoomBuster")
370 default_event_abort(doom, ">=1", Belle2.EventMetaData.c_ReconstructionAbort)
371 reco_path.add_module('StatisticsSummary').set_name('Sum_EventsofDoomBuster')
372
373 # Add modules that have to be run BEFORE track reconstruction
374 add_prefilter_pretracking_reconstruction(reco_path, components=Components)
375
376 # Add tracking reconstruction modules
377 add_prefilter_tracking_reconstruction(path=reco_path,
378 components=Components,
379 trackFitHypotheses=[211],
380 prune_temporary_tracks=False,
381 fit_tracks=True,
382 append_full_grid_cdc_eventt0=True,
383 skip_full_grid_cdc_eventt0_if_svd_time_present=False)
384 reco_path.add_module('StatisticsSummary').set_name('Sum_Tracking')
385
386 reco_path.add_module('Progress')
387
388 for module in reco_path.modules():
389 if module.name() == "TFCDC_WireHitPreparer":
390 module.param({"useBadWires": use_badWires})
391
392 return reco_path
393
394
395def collector(bField=True, is_cosmic=False, granularity='all'):
396 """
397 Create a cdc calibration collector
398 Parameters:
399 bField [bool] : True if B field is on, else False
400 isCosmic [bool] : True if cosmic events,
401 else (collision) False.
402 Returns:
403 collector : collector module
404 """
405 from basf2 import register_module
406 col = register_module('CDCCalibrationCollector',
407 granularity=granularity,
408 calExpectedDriftTime=True,
409 eventT0Extraction=True,
410 bField=bField,
411 isCosmic=is_cosmic
412 )
413 return col
414
415
416def tz_algo(max_rmsDt=0.25, max_badChannel=50):
417 """
418 Create a T0 calibration algorithm.
419 Returns:
420 algo : T0 algorithm
421 """
422 from ROOT import Belle2
424 algo.storeHisto(True)
425 algo.setMaxMeanDt(0.2)
426 algo.setMaxRMSDt(max_rmsDt)
427 algo.setMaxBadChannel(max_badChannel)
428 algo.setMinimumNDF(25)
429
430 return algo
431
432
433def tw_algo():
434 """
435 Create a time walk calibration algorithm.
436 Returns:
437 algo : TW algorithm
438 """
439 from ROOT import Belle2
441 algo.setStoreHisto(True)
442 algo.setMode(1)
443 return algo
444
445
446def xt_algo():
447 """
448 Create a XT calibration algorithm.
449 Parameters:
450 prefix : prefixed name for algorithm,
451 which should be consistent with one of collector..
452 Returns:
453 algo : XT algorithm
454 """
455 from ROOT import Belle2
457 algo.setStoreHisto(True)
458 algo.setLRSeparate(True)
459 algo.setThreshold(0.1)
460 return algo
461
462
463def sr_algo():
464 """
465 Create a Spacial resolution calibration algorithm.
466 Parameters:
467 prefix : prefixed name for algorithm,
468 which should be consistent with one of collector..
469 Returns:
470 algo : Spacial algorithm
471 """
472 from ROOT import Belle2
474 algo.setStoreHisto(True)
475 algo.setThreshold(0.1)
476 return algo
477
478
479class CDCCalibration(Calibration):
480 '''
481 CDCCalibration is a specialized calibration class for cdc.
482 Since collector is same in all elements, no need to specify it.
483 '''
484
485 def __init__(self,
486 name,
487 algorithms,
488 input_file_dict,
489 max_iterations=5,
490 dependencies=None,
491 max_events=[20000, 10000, 20000],
492 use_badWires=False,
493 collector_granularity='All',
494 backend_args=None):
495 for algo in algorithms:
496 algo.setHistFileName(name)
497
498 super().__init__(name=name,
499 algorithms=algorithms
500 )
501
502 from caf.framework import Collection
503
504 for skim_type, file_list in input_file_dict.items():
505 if skim_type == "cosmic_calib":
506 collection = Collection(collector=collector(is_cosmic=True,
507 granularity=collector_granularity),
508 input_files=file_list,
509 pre_collector_path=pre_collector(max_events=max_events[2],
510 is_cosmic=True,
511 use_badWires=use_badWires),
512 backend_args=backend_args)
513 elif skim_type == "hadron_calib":
514 collection = Collection(collector=collector(granularity=collector_granularity),
515 input_files=file_list,
516 pre_collector_path=pre_collector(max_events=max_events[1],
517 use_badWires=use_badWires),
518 backend_args=backend_args)
519 else:
520 collection = Collection(collector=collector(granularity=collector_granularity),
521 input_files=file_list,
522 pre_collector_path=pre_collector(max_events=max_events[0],
523 use_badWires=use_badWires),
524 backend_args=backend_args)
525
526 self.add_collection(name=skim_type, collection=collection)
527
528 self.max_iterations = max_iterations
529
530 if dependencies is not None:
531 for dep in dependencies:
532 self.depends_on(dep)
533
534# @endcond
Class to perform xt calibration for drift chamber.