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