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