Belle II Software  release-08-01-10
millepede_calibration.py
1 #!/usr/bin/env python3
2 
3 
10 
11 import basf2
12 
13 from caf.framework import Calibration, CentralDatabase, Collection, LocalDatabase
14 from caf import strategies
15 
16 import os
17 
18 import alignment.parameters as parameters # noqa
19 import alignment.constraints # noqa
20 
21 import alignment.collections # noqa
22 from alignment.collections import make_collection # noqa
23 
24 
25 def 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 
33 def 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 
83 def 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 
109 def create_algorithm(dbobjects, min_entries=10, ignore_undetermined=True):
110  """
111  Create Belle2.MillepedeAlgorithm
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 
140 def 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 
163 def 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 
205 def 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
def serialize_path(path)
Definition: pickle_path.py:94