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