Belle II Software  release-08-01-10
caf_cdc.py
1 # disable doxygen check for this file
2 # @cond
3 
4 
11 
12 """Full CDC tracking calibration."""
13 from prompt import CalibrationSettings, INPUT_DATA_FILTERS
14 from prompt.utils import events_in_basf2_file, ExpRun
15 import basf2
16 from ROOT import Belle2
17 from random import choice
18 from caf.framework import Calibration
19 from caf import strategies
20 
21 
22 
23 settings = 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 
59 def 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 
104 def 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 
322 def 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  '0:{}'.format(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  reco_path.add_module('StatisticsSummary').set_name('Sum_Tracking')
384 
385  reco_path.add_module('Progress')
386 
387  for module in reco_path.modules():
388  if module.name() == "TFCDC_WireHitPreparer":
389  module.param({"useBadWires": use_badWires})
390 
391  return reco_path
392 
393 
394 def collector(bField=True, is_cosmic=False, granularity='all'):
395  """
396  Create a cdc calibration collector
397  Parameters:
398  bField [bool] : True if B field is on, else False
399  isCosmic [bool] : True if cosmic events,
400  else (collision) False.
401  Returns:
402  collector : collector module
403  """
404  from basf2 import register_module
405  col = register_module('CDCCalibrationCollector',
406  granularity=granularity,
407  calExpectedDriftTime=True,
408  eventT0Extraction=True,
409  bField=bField,
410  isCosmic=is_cosmic
411  )
412  return col
413 
414 
415 def tz_algo(max_rmsDt=0.25, max_badChannel=50):
416  """
417  Create a T0 calibration algorithm.
418  Returns:
419  algo : T0 algorithm
420  """
421  from ROOT import Belle2
423  algo.storeHisto(True)
424  algo.setMaxMeanDt(0.2)
425  algo.setMaxRMSDt(max_rmsDt)
426  algo.setMaxBadChannel(max_badChannel)
427  algo.setMinimumNDF(25)
428 
429  return algo
430 
431 
432 def tw_algo():
433  """
434  Create a time walk calibration algorithm.
435  Returns:
436  algo : TW algorithm
437  """
438  from ROOT import Belle2
440  algo.setStoreHisto(True)
441  algo.setMode(1)
442  return algo
443 
444 
445 def xt_algo():
446  """
447  Create a XT calibration algorithm.
448  Parameters:
449  prefix : prefixed name for algorithm,
450  which should be consistent with one of collector..
451  Returns:
452  algo : XT algorithm
453  """
454  from ROOT import Belle2
456  algo.setStoreHisto(True)
457  algo.setLRSeparate(True)
458  algo.setThreshold(0.1)
459  return algo
460 
461 
462 def sr_algo():
463  """
464  Create a Spacial resolution calibration algorithm.
465  Parameters:
466  prefix : prefixed name for algorithm,
467  which should be consistent with one of collector..
468  Returns:
469  algo : Spacial algorithm
470  """
471  from ROOT import Belle2
473  algo.setStoreHisto(True)
474  algo.setThreshold(0.1)
475  return algo
476 
477 
478 class CDCCalibration(Calibration):
479  '''
480  CDCCalibration is a specialized calibration class for cdc.
481  Since collector is same in all elements, no need to specify it.
482  '''
483 
484  def __init__(self,
485  name,
486  algorithms,
487  input_file_dict,
488  max_iterations=5,
489  dependencies=None,
490  max_events=[20000, 10000, 20000],
491  use_badWires=False,
492  collector_granularity='All',
493  backend_args=None):
494  for algo in algorithms:
495  algo.setHistFileName(name)
496 
497  super().__init__(name=name,
498  algorithms=algorithms
499  )
500 
501  from caf.framework import Collection
502 
503  for skim_type, file_list in input_file_dict.items():
504  if skim_type == "cosmic_calib":
505  collection = Collection(collector=collector(is_cosmic=True,
506  granularity=collector_granularity),
507  input_files=file_list,
508  pre_collector_path=pre_collector(max_events=max_events[2],
509  is_cosmic=True,
510  use_badWires=use_badWires),
511  backend_args=backend_args)
512  elif skim_type == "hadron_calib":
513  collection = Collection(collector=collector(granularity=collector_granularity),
514  input_files=file_list,
515  pre_collector_path=pre_collector(max_events=max_events[1],
516  use_badWires=use_badWires),
517  backend_args=backend_args)
518  else:
519  collection = Collection(collector=collector(granularity=collector_granularity),
520  input_files=file_list,
521  pre_collector_path=pre_collector(max_events=max_events[0],
522  use_badWires=use_badWires),
523  backend_args=backend_args)
524 
525  self.add_collection(name=skim_type, collection=collection)
526 
527  self.max_iterations = max_iterations
528 
529  if dependencies is not None:
530  for dep in dependencies:
531  self.depends_on(dep)
532 
533 # @endcond
Class to perform xt calibration for drift chamber.