Belle II Software  release-08-02-06
caf_klm_alignment.py
1 
8 
9 """
10 Calibration of KLM alignment. It provides calibration constants for the BKLMAlignment
11 and EKLMAlignment database objects.
12 """
13 
14 import collections
15 
16 import basf2
17 from caf.utils import ExpRun, IoV
18 from prompt import CalibrationSettings, INPUT_DATA_FILTERS
19 from prompt.utils import events_in_basf2_file
20 
21 
28 
29 from prompt.calibrations.caf_vxdcdc_alignment import settings as caf_vxdcdc_alignment
30 from prompt.calibrations.caf_cdc import settings as caf_cdc
31 from prompt.calibrations.caf_klm_channel_status import settings as caf_klm_channel_status
32 
33 
41 settings = CalibrationSettings(name="KLM alignmnent",
42  expert_username="oskin",
43  description=__doc__,
44  input_data_formats=["raw"],
45  input_data_names=["raw_physics", "raw_cosmic"],
46  input_data_filters={
47  'raw_physics': [INPUT_DATA_FILTERS['Run Type']['physics'],
48  INPUT_DATA_FILTERS['Data Tag']['mumu_tight_or_highm_calib'],
49  INPUT_DATA_FILTERS['Data Quality Tag']['Good Or Recoverable']],
50  'raw_cosmic': [INPUT_DATA_FILTERS['Run Type']['physics'],
51  INPUT_DATA_FILTERS['Data Tag']['cosmic_calib'],
52  INPUT_DATA_FILTERS['Data Quality Tag']['Good Or Recoverable']]
53  },
54  depends_on=[caf_vxdcdc_alignment, caf_cdc, caf_klm_channel_status],
55  expert_config={
56  "required_events": 5000000,
57  "required_events_experiment": 500000,
58  "events_per_file": 1000,
59  "millepede_entries": 1000000,
60  "millepede_entries_exp7": 500000
61  })
62 
63 
64 
65 
66 
67 def select_input_files(file_to_iov_physics, file_to_iov_cosmic,
68  reduced_file_to_iov_physics, reduced_file_to_iov_cosmic,
69  required_events, required_events_experiment,
70  events_per_file):
71  """
72  Parameters:
73  files_to_iov_physics (dict): Dictionary {run : IOV} for physics data.
74  files_to_iov_cosmic (dict): Dictionary {run : IOV} for cosmic data.
75  reduced_file_to_iov_physics (dict): Selected physics data.
76  reduced_file_to_iov_cosmic (dict): Selected cosmic data.
77  required_events (int): Required total number of events.
78  required_events_experiment (int): Required number of events
79  per experiment.
80  events_per_file (int): Number of events per file. If non-positive, then
81  the number is not limited.
82  """
83  experiment_numbers = set()
84  run_to_files_physics = collections.defaultdict(list)
85  for input_file, file_iov in file_to_iov_physics.items():
86  run = ExpRun(exp=file_iov.exp_low, run=file_iov.run_low)
87  run_to_files_physics[run].append(input_file)
88  experiment_numbers.add(file_iov.exp_low)
89  run_to_files_cosmic = collections.defaultdict(list)
90  for input_file, file_iov in file_to_iov_cosmic.items():
91  run = ExpRun(exp=file_iov.exp_low, run=file_iov.run_low)
92  run_to_files_cosmic[run].append(input_file)
93  experiment_numbers.add(file_iov.exp_low)
94  max_files_per_run = 0
95  for files in run_to_files_physics.values():
96  files_per_run = len(files)
97  if files_per_run > max_files_per_run:
98  max_files_per_run = files_per_run
99  for files in run_to_files_cosmic.values():
100  files_per_run = len(files)
101  if files_per_run > max_files_per_run:
102  max_files_per_run = files_per_run
103  files_per_run = 0
104  collected_events = 0
105  collected_events_experiment = collections.defaultdict(int)
106  select_events_experiment = collections.defaultdict(bool)
107  for exp in experiment_numbers:
108  collected_events_experiment[exp] = 0
109  select_events_experiment[exp] = True
110  while files_per_run < max_files_per_run:
111  for run, files in run_to_files_physics.items():
112  if not select_events_experiment[run.exp]:
113  continue
114  if files_per_run >= len(files):
115  continue
116  input_file = files[files_per_run]
117  events = events_in_basf2_file(input_file)
118  # Reject files without events.
119  if events == 0:
120  continue
121  if events_per_file > 0 and events > events_per_file:
122  events = events_per_file
123  collected_events = collected_events + events
124  collected_events_experiment[run.exp] = \
125  collected_events_experiment[run.exp] + events
126  reduced_file_to_iov_physics[input_file] = IoV(*run, *run)
127  basf2.B2INFO(f'File {input_file} with {events} events is selected.')
128  for run, files in run_to_files_cosmic.items():
129  if not select_events_experiment[run.exp]:
130  continue
131  if files_per_run >= len(files):
132  continue
133  input_file = files[files_per_run]
134  events = events_in_basf2_file(input_file)
135  # Reject files without events.
136  if events == 0:
137  continue
138  if events_per_file > 0 and events > events_per_file:
139  events = events_per_file
140  collected_events = collected_events + events
141  collected_events_experiment[run.exp] = \
142  collected_events_experiment[run.exp] + events
143  reduced_file_to_iov_cosmic[input_file] = IoV(*run, *run)
144  basf2.B2INFO(f'File {input_file} with {events} events is selected.')
145  files_per_run = files_per_run + 1
146  if collected_events >= required_events:
147  all_experiments_selected = True
148  for exp in experiment_numbers:
149  if collected_events_experiment[exp] >= \
150  required_events_experiment:
151  select_events_experiment[exp] = False
152  else:
153  all_experiments_selected = False
154  if all_experiments_selected:
155  break
156  basf2.B2INFO(f'The total number of collected events is {collected_events}.')
157 
158 
167 
168 
169 def get_calibrations(input_data, **kwargs):
170  """
171  Parameters:
172  input_data (dict): Should contain every name from the 'input_data_names' variable as a key.
173  Each value is a dictionary with {"/path/to/file_e1_r5.root": IoV(1,5,1,5), ...}. Useful for
174  assigning to calibration.files_to_iov
175 
176  **kwargs: Configuration options to be sent in. Since this may change we use kwargs as a way to help prevent
177  backwards compatibility problems. But you could use the correct arguments in b2caf-prompt-run for this
178  release explicitly if you want to.
179 
180  Currently only kwargs["requested_iov"] is used. This is the output IoV range that your payloads should
181  correspond to. Generally your highest ExpRun payload should be open ended e.g. IoV(3,4,-1,-1)
182 
183  Returns:
184  list(caf.framework.Calibration): All of the calibration objects we want to assign to the CAF process
185  """
186  # Set up config options
187 
188  # Expert configuration.
189  expert_config = kwargs.get("expert_config")
190 
191  # In this script we want to use one sources of input data.
192  # Get the input files from the input_data variable
193  file_to_iov_physics = input_data["raw_physics"]
194  file_to_iov_cosmic = input_data["raw_cosmic"]
195 
196  # Select input files.
197  reduced_file_to_iov_physics = collections.OrderedDict()
198  reduced_file_to_iov_cosmic = collections.OrderedDict()
199  select_input_files(file_to_iov_physics, file_to_iov_cosmic,
200  reduced_file_to_iov_physics, reduced_file_to_iov_cosmic,
201  expert_config["required_events"],
202  expert_config["required_events_experiment"],
203  expert_config["events_per_file"])
204 
205  input_files_physics = sorted(list(reduced_file_to_iov_physics.keys()))
206  basf2.B2INFO(f"Total number of 'physics' files actually used as input = {len(input_files_physics)}")
207 
208  input_files_cosmic = sorted(list(reduced_file_to_iov_cosmic.keys()))
209  basf2.B2INFO(f"Total number of 'cosmic' files actually used as input = {len(input_files_cosmic)}")
210 
211  if not input_files_physics and not input_files_cosmic:
212  raise Exception("No valid input files found!")
213 
214  # Get the overall IoV we our process should cover. Includes the end values that we may want to ignore since our output
215  # IoV should be open ended. We could also use this as part of the input data selection in some way.
216  requested_iov = kwargs["requested_iov"]
217 
218  from caf.utils import IoV
219  # The actual value our output IoV payload should have. Notice that we've set it open ended.
220  output_iov = IoV(requested_iov.exp_low, requested_iov.run_low, -1, -1)
221 
222 
224 
225  from ROOT import Belle2
226  from ROOT.Belle2 import KLMChannelIndex, KLMElementNumbers
227  from alignment import MillepedeCalibration
228 
229  # Create the algorithm.
230  millepede = MillepedeCalibration(['BKLMAlignment', 'EKLMAlignment', 'EKLMSegmentAlignment'])
231 
232  # Fix module parameters.
233  index = KLMChannelIndex(KLMChannelIndex.c_IndexLevelLayer)
234  index2 = KLMChannelIndex(KLMChannelIndex.c_IndexLevelLayer)
235  while (index != index2.end()):
236  module = index.getKLMModuleNumber()
237  if (index.getSubdetector() == KLMElementNumbers.c_BKLM):
238  for ipar in [1, 2, 3, 4, 5, 6]:
239  # Free parameters are idU, dV, dGamma.
240  if ipar in [1, 2, 6]:
241  continue
242  millepede.fixGlobalParam(Belle2.BKLMAlignment.getGlobalUniqueID(),
243  module, ipar)
244  else:
245  for ipar in [1, 2, 6]:
246  # No parameters are fixed; if necessary, uncomment the following:
247  # millepede.fixGlobalParam(Belle2.EKLMAlignment.getGlobalUniqueID(),
248  # module, ipar)
249  continue
250  index.increment()
251 
252  # Fix EKLM segment parameters.
253  index.setIndexLevel(KLMChannelIndex.c_IndexLevelStrip)
254  index2.setIndexLevel(KLMChannelIndex.c_IndexLevelStrip)
255  index = index2.beginEKLM()
256  index.useEKLMSegments()
257  while (index != index2.endEKLM()):
258  segment = index.getEKLMSegmentNumber()
259  for ipar in [2, 6]:
260  millepede.fixGlobalParam(
262  segment, ipar)
263  index.increment()
264 
265  cal_klm = millepede.create('KLMAlignment', [])
266  # Number of entries is set in algorithm strategy now.
267  millepede.algo.ignoreUndeterminedParams(True)
268  millepede.algo.invertSign()
269 
270 
272 
273  from caf.framework import Collection
274 
275 
277 
278  from klm_calibration_utils import get_alignment_pre_collector_path_physics, get_alignment_pre_collector_path_cosmic
279 
280  entries = ''
281  if expert_config["events_per_file"] > 0:
282  last_entry = expert_config["events_per_file"]
283  entries = f'0:{last_entry}'
284 
285  if input_files_physics:
286  coll_physics = get_collector("raw_physics")
287  rec_path_physics = get_alignment_pre_collector_path_physics(entry_sequence=entries)
288  # remove cdcdedxpid module
289  tmp = basf2.create_path()
290  for m in rec_path_physics.modules():
291  if m.name() not in ["CDCDedxPID", "TOPBunchFinder", "VXDDedxPID"]:
292  tmp.add_module(m)
293  elif m.name() == "CDCDedxPID":
294  basf2.B2INFO('removed CDCDedxPID')
295  elif m.name() == "TOPBunchFinder":
296  basf2.B2INFO('removed TOPBunchFinder')
297  rec_path_physics = tmp
298 
299  collection_physics = Collection(collector=coll_physics,
300  input_files=input_files_physics,
301  pre_collector_path=rec_path_physics)
302 
303  cal_klm.add_collection(name="physics", collection=collection_physics)
304 
305  if input_files_cosmic:
306  coll_cosmic = get_collector("raw_cosmic")
307  rec_path_cosmic = get_alignment_pre_collector_path_cosmic(entry_sequence=entries)
308  # remove cdcdedxpid module
309  tmp = basf2.create_path()
310  for m in rec_path_cosmic.modules():
311  if m.name() not in ["CDCDedxPID", "TOPBunchFinder", "VXDDedxPID"]:
312  tmp.add_module(m)
313  elif m.name() == "CDCDedxPID":
314  basf2.B2INFO('removed CDCDedxPID')
315  elif m.name() == "TOPBunchFinder":
316  basf2.B2INFO('removed TOPBunchFinder')
317  rec_path_cosmic = tmp
318 
319  collection_cosmic = Collection(collector=coll_cosmic,
320  input_files=input_files_cosmic,
321  pre_collector_path=rec_path_cosmic)
322 
323  cal_klm.add_collection(name="cosmic", collection=collection_cosmic)
324 
325 
327 
328  from klm_alignment import KLMAlignment
329 
330  cal_klm.algorithms = [millepede.algo]
331 
332  # Bugfix for Condor:
333  from alignment.prompt_utils import fix_mille_paths_for_algo
334  for algorithm in cal_klm.algorithms:
335  fix_mille_paths_for_algo(algorithm)
336 
337  for algorithm in cal_klm.algorithms:
338  algorithm.strategy = KLMAlignment
339  algorithm.params = {
340  "has_experiment_settings": True,
341  "iov_coverage": output_iov,
342  "millepede_entries": expert_config["millepede_entries"],
343  "millepede_entries_exp7": expert_config["millepede_entries_exp7"]
344  }
345 
346  # You must return all calibrations you want to run in the prompt process, even if it's only one
347  return [cal_klm]
348 
349 
350 
351 
352 def get_collector(input_data_name):
353  """
354  Return the correct MillepedeCollector module setup for each data type.
355  Placed here so it can be different for prompt compared to standard.
356  """
357 
358  if input_data_name == "raw_physics":
359  return basf2.register_module(
360  'MillepedeCollector',
361  components=['BKLMAlignment', 'EKLMAlignment',
362  'EKLMSegmentAlignment'],
363  useGblTree=True,
364  minPValue=1e-5)
365  elif input_data_name == "raw_cosmic":
366  return basf2.register_module(
367  'MillepedeCollector',
368  components=['BKLMAlignment', 'EKLMAlignment',
369  'EKLMSegmentAlignment'],
370  useGblTree=True,
371  minPValue=1e-5)
372 
373  raise Exception("Unknown input data name used when setting up collector")
static unsigned short getGlobalUniqueID()
Get global unique identifier.
Definition: BKLMAlignment.h:63
static unsigned short getGlobalUniqueID()
Get global unique identifier.