Belle II Software development
millepede_calibration.py
1#!/usr/bin/env python3
2
3
10
11import basf2
12
13from caf.framework import Calibration, CentralDatabase, Collection, LocalDatabase
14from caf import strategies
15
16import os
17
18import alignment.parameters as parameters # noqa
19import alignment.constraints # noqa
20
21import alignment.collections # noqa
22from alignment.collections import make_collection # noqa
23
24
25def limit_file_events(calibration, collection_limits):
26 for colname, max_events in collection_limits.items():
27 basf2.set_module_parameters(
28 calibration.collections[colname].pre_collector_path,
29 'RootInput',
30 entrySequences=[f'0:{max_events}'])
31
32
33def collect(calibration, collection, input_files, output_file='CollectorOutput.root', basf2_args=None, bsub=False):
34 """
35 Standalone collection for calibration (without CAF)
36 (experimental)
37
38 Pickles the reprocessing path and collector of given collection and runs it in separate process
39 to collect calibration data in output_file from input_files.
40
41 Parameters
42 ----------
43 calibration : caf.framework.Calibration
44 The configured Millepede calibration (see create(...)) to use for collection
45 collection : str
46 Collection name which should be collected (which re-processing path and collector to use)
47 input_files : list(str)
48 List of input files for this collection job
49 output_file : str
50 Name of output collector file with data/histograms (produced by HistoManager)
51 basf2_args : dict
52 Additional arguments to pass to basf2 job
53 """
54 if basf2_args is None:
55 basf2_args = []
56
57 import pickle
58 import subprocess
59
60 main = calibration.collections[collection].pre_collector_path
61
62 tmp = basf2.Path()
63 for m in main.modules():
64 if m.name() == 'RootInput':
65 m.param('inputFileNames', input_files)
66 tmp.add_module('HistoManager', histoFileName=output_file)
67 tmp.add_module(m)
68 main = tmp
69 main.add_module(calibration.collections[collection].collector)
70
71 path_file_name = calibration.name + '.' + collection + '.' + output_file + '.path'
72 with open(path_file_name, 'bw') as serialized_path_file:
73 pickle.dump(basf2.pickle_path.serialize_path(main), serialized_path_file)
74
75 if bsub:
76 subprocess.call(["bsub", "-o", output_file + ".txt", "basf2", "--execute-path", path_file_name] + basf2_args)
77 else:
78 subprocess.call(["basf2", "--execute-path", path_file_name] + basf2_args)
79
80 return os.path.abspath(output_file)
81
82
83def calibrate(calibration, input_files=None, iteration=0):
84 """
85 Execute the algorithm from configured Millepede calibration over collected
86 files in input_files. The pre_algorithm function is run before the algorithm.
87
88 Parameters
89 ----------
90 calibration : caf.framework.Calibration
91 Configured Millepede calibration (see create(...))
92 input_files : list(str)
93 List of input collected files
94 iteration : int
95 Iteration number to pass to pre_algorithm function
96 """
97 if input_files is None:
98 input_files = ['CollectorOutput.root']
99 """
100 Execute algorithm of the Millepede calibration over
101 """
102 for algo in calibration.algorithms:
103 algo.algorithm.setInputFileNames(input_files)
104 algo.pre_algorithm(algo.algorithm, iteration)
105 algo.algorithm.execute()
106 algo.algorithm.commit()
107
108
109def create_algorithm(dbobjects, min_entries=10, ignore_undetermined=True):
110 """
112
113 Parameters
114 ----------
115 dbobjects : list(str)
116 List of DB objects to calibrate - has to match collector settings
117 min_entries : int
118 Minimum number of collected entries for calibration. Algorithm will return
119 NotEnoughData is less entries collected.
120 ignore_undetermined : bool
121 Whether undetermined parameters should be ignored or the calibration should fail if any
122 """
123 import ROOT
124 from ROOT.Belle2 import MillepedeAlgorithm
125 algorithm = MillepedeAlgorithm()
126
127 std_components = ROOT.vector('string')()
128 for component in dbobjects:
129 std_components.push_back(component)
130 algorithm.setComponents(std_components)
131
132 algorithm.ignoreUndeterminedParams(ignore_undetermined)
133 algorithm.setMinEntries(min_entries)
134
135 algorithm.invertSign(True)
136
137 return algorithm
138
139
140def create_commands():
141 """
142 Create default list of commands for Pede
143 """
144 cmds = []
145 cmds.append('method inversion 3 0.1')
146 cmds.append('skipemptycons')
147
148 import multiprocessing
149 ncpus = multiprocessing.cpu_count()
150
151 cmds.append(f'threads {ncpus} {ncpus}')
152 cmds.append('printcounts 2')
153 cmds.append('closeandreopen')
154
155 cmds.append('hugecut 50.')
156 cmds.append('chiscut 30. 6.')
157 cmds.append('outlierdownweighting 3')
158 cmds.append('dwfractioncut 0.1')
159
160 return cmds
161
162
163def create_collector(dbobjects, **argk):
164 """
165 Create MillepedeCollector module with default configuration
166
167 Parameters
168 ----------
169 dbobject : list(str)
170 List of database objects to be calibrated (global derivatives of others
171 will be disabled) - has to match algorithm settings
172 argk : dict
173 Dictionary of additional module parameters (can override defaults)
174
175 Returns
176 -------
177 MillepedeCollectorModule (configured)
178 """
179 import basf2
180 m = basf2.register_module('MillepedeCollector')
181
182 m.param('granularity', 'all')
183 # Let's enable this always - will be in effect only if BeamSpot is in dbobjects
184 m.param('calibrateVertex', True)
185 # Not yet implemeted -> alwas OFF for now
186 m.param('calibrateKinematics', False)
187 m.param('minUsedCDCHitFraction', 0.8)
188 m.param('minPValue', 0.0)
189 m.param('externalIterations', 0)
190 m.param('tracks', [])
191 m.param('fitTrackT0', True)
192 m.param('components', dbobjects)
193 m.param('useGblTree', False)
194 m.param('absFilePaths', True)
195
196 # By default, hierarchy is disabled, because you need some constraints
197 # - Adding VXDHierarchyConstraints changes the hierarchy type to the correct one
198 m.param('hierarchyType', 0)
199
200 m.param(argk)
201
202 return m
203
204
205def create(name,
206 dbobjects,
207 collections,
208 files=None,
209 tags=None,
210 timedep=None,
211 commands=None,
212 constraints=None,
213 fixed=None,
214 params=None,
215 min_entries=0
216 ):
217 """
218 Create the Millepede Calibration, fully configured in one call
219
220
221 Parameters
222 ----------
223 name : str
224 Calibration name
225 dbobjects : list(str)
226 List of database objects to calibrate, e.g. ['BeamSpot', 'VXDAlignment']
227 Note that by default all parameters of the db object are free (exceptions
228 depend on some constraint and collector configuration) and you might need to fix
229 the unwanted ones (typically higher order sensor deformations) using the 'fixed' parameter.
230
231 collections : list(namedtuple('MillepedeCollection', ['name', 'files', 'path', 'params']))
232 List of collection definitions.
233 - name : str
234 Collection name has to math entry in 'files' dictionary.
235 - files : list(str) | None
236 Optional list of files. Can (should if not set here) be overriden by 'files' parameter
237 - path : basf2.Path
238 The reprocessing path
239 - dict(...) : additional dictionary of parameters passed to the collector.
240 This has to contain the input data sample (tracks / particles / primaryVertices ...) configuration for the collector.
241 Optionally additional arguments can be specified for the collector specific for this collection.
242 (Default arguments for all collections can be set using the 'params' parameter)
243 Use make_collection(str, path=basf2.Path, **argk) for more convenient creation of custom collections.
244 For standard collections to use, see alignment.collections
245
246 files : dict( str -> list(str) )
247 Dictionary of lists of input file paths, key is collection name, value is list of input files.
248 NOTE: This overrides possible list of files assigned during creation of collections (if set)
249 tags : list(str)
250 List of input global tags. Can include absolute file paths to local databases (added to the chain).
251
252 timedep : list(tuple(list(int), list(tuple(int, int, int))))
253 Time-depence configuration.
254 Each list item is 2-tuple with list of parameter numbers (use alignment.parameters to get them) and
255 the (event, run, exp) numbers at which values of these parameters can change.
256 Use with caution. Namely the first event of the lowest run in input data has to be included in (some of the)
257 event lists.
258
259 commands : list(str | tuple(str, None))
260 List of commands for Millepede. Default commands can be overriden be specifing different values for them.
261 A command can be erased completely from the default commands if instead a ('command_name', None) is passed.
262 constraints : list(alignment.Constraints)
263 List of constraints from alignment.constraints to be used.
264 Constraints are generated by the pre-algorithm function by CAF.
265 fixed : list(int)
266 List of fixed parameters (use alignment.parameters to get them)
267
268 params : dict
269 Dictionary of common parameters to set for collectors of all collections.
270 min_entries : int
271 Minimum entries to required by the algorithm. Returns NotEnoughData if less entries is collected.
272
273 Returns
274 ----------
275 caf.framework.Calibration object, fully configured and ready to run.
276 You might want to set/override some options to custom values, like 'max_iterations' etc.
277 """
278
279 print("----------------------------")
280 print(" Calibration: ", name, "")
281 print("----------------------------")
282
283 print("- DB Objects:")
284 for objname in dbobjects:
285 print(" ", objname)
286
287 cmds = create_commands()
288
289 _commands = dict()
290
291 def set_command(command):
292 spec = ''
293 if isinstance(command, tuple):
294 if not len(command) == 2:
295 raise AttributeError("Commands has to be strings or tuple ('command name', None) to remove the command")
296 spec = None
297 command = command[0]
298 words = command.split(" ")
299 cmd_name = words[0]
300
301 if spec == '':
302 spec = command
303
304 if spec is not None:
305 _commands[cmd_name] = spec
306 elif cmd_name in _commands:
307 del _commands[cmd_name]
308
309 for cmd in cmds:
310 set_command(cmd)
311
312 if commands is None:
313 commands = []
314
315 for cmd in commands:
316 set_command(cmd)
317
318 algo = create_algorithm(dbobjects, min_entries=min_entries)
319
320 print("- Commands:")
321 for cmd_name, cmd in _commands.items():
322 print(" ", cmd)
323 algo.steering().command(cmd)
324
325 if constraints is None:
326 constraints = []
327
328 print("- Constraints:")
329 consts = constraints
330 if len(consts):
331 algo.steering().command('FortranFiles')
332 for const in consts:
333 print(" ", const.filename)
334 algo.steering().command(const.filename)
335
336 if timedep is None:
337 timedep = []
338
339 def gen_constraints(algorithm, iteration):
340 import basf2
341 from alignment.constraints import generate_constraints
342
343 data_iov = algorithm.getRunRangeFromAllData().getIntervalOfValidity()
344 init_event = (0, data_iov.getRunLow(), data_iov.getExperimentLow())
345
346 # This function runs with DB chain set up
347 # TODO: we ignore the local DBs from CAF etc., that is, the constraints
348 # are always build only from last valid central GT. As constraints are only
349 # linearizations, small alignment changes have numerical neglible effects on
350 # constraint coefficients. But we can do even better -> should be not difficult
351 # but needs much more testing.
352 # NOTE: rversed (highest priority last) order expected here
353 constraint_tags = [tag for tag in reversed(basf2.conditions.globaltags)]
354
355 if len(consts):
356 generate_constraints(consts, timedep, constraint_tags, init_event)
357
358 algo.steering().command('Parameters')
359
360 def fix(labels):
361 for label in labels:
362 algo.steering().command('{} 0.0 -1.'.format(str(label)))
363
364 if fixed is None:
365 fixed = []
366
367 print("- Fixed parameters:", len(fixed))
368 fix(fixed)
369
370 algo.setTimedepConfig(timedep)
371
372 print("- Tags:")
373
374 def make_database_chain(tags):
375 import os
376 chain = []
377 for tag in tags:
378 if os.path.exists(tag):
379 print(" Local:", os.path.abspath(tag))
380 chain.append(LocalDatabase(os.path.abspath(tag)))
381 else:
382 print(" Global:", tag)
383 chain.append(CentralDatabase(tag))
384 return chain
385
386 dbchain = make_database_chain(tags) if tags is not None else None
387
388 # Note the default collector and path are ignored
389 # The user is supposed to add at least one own collection
390 calibration = Calibration(name,
391 collector=None,
392 algorithms=algo,
393 input_files=None,
394 pre_collector_path=None,
395 database_chain=dbchain,
396 output_patterns=None,
397 backend_args=None
398 )
399
400 if params is None:
401 params = dict()
402
403 print("- Overriden common collector parameters:")
404 for parname, parval in params.items():
405 print(" ", parname, ":", parval)
406
407 if files is None:
408 files = dict()
409
410 print("- Collections:")
411 for col in collections:
412 colname = col.name
413 colfiles = col.files
414 path = col.path
415 args = col.params
416
417 filelist = colfiles
418 if colname in files:
419 if colname in files:
420 filelist = files[colname]
421
422 print(f" - {colname} ({len(filelist)} files)")
423
424 collector = create_collector(dbobjects)
425 if params is not None:
426 collector.param(params)
427
428 for const in consts:
429 const.configure_collector(collector)
430
431 collector.param('timedepConfig', timedep)
432
433 for argname, argval in args.items():
434 print(" ", argname, " : ", argval)
435
436 collector.param(args)
437
438 collection = Collection(collector=collector,
439 input_files=filelist,
440 pre_collector_path=path,
441 database_chain=dbchain)
442
443 calibration.add_collection(colname, collection)
444
445 calibration.strategies = strategies.SingleIOV
446 calibration.max_iterations = 1
447
448 calibration.pre_algorithms = gen_constraints
449
450 print("----------------------------")
451
452 return calibration
Class implementing Millepede calibration algorithm.
def serialize_path(path)
Definition: pickle_path.py:95