Belle II Software  release-08-02-06
caf_vxdcdc_alignment.py
1 
8 """
9 
10 Full Simultaneous Global and Local VXD and CDC alignment with Millepede II
11 
12 The input collections are:
13 - cosmics (hlt skim) - mandatorry
14 - hadron - for "low" momentum tracks from IP
15 - mumu - mumu_2trk or mumu_tight - for high momentum tracks from IP
16 - offip - tracks from outside IP (beam background, beam-gas)
17 
18 """
19 
20 import basf2
21 import ROOT
22 
23 from prompt import CalibrationSettings, INPUT_DATA_FILTERS
24 from prompt.calibrations.caf_cdc import settings as cdc_calibration
25 from prompt.calibrations.caf_svd_time import settings as svd_time_calibration
26 
27 from prompt.utils import events_in_basf2_file
28 from caf.utils import IoV
29 from caf import strategies
30 
31 from softwaretrigger.constants import HLT_INPUT_OBJECTS
32 import rawdata as raw
33 import reconstruction as reco
34 import modularAnalysis as ana
35 import vertex as vtx
36 
37 from random import choice
38 from random import seed
39 
40 import millepede_calibration as mpc
44 
45 collection_names = ["cosmic", "hadron", "mumu", "offip"]
46 
47 default_config = {
48  "only_prompt": False,
49  "cosmic.max_processed_events_per_file": 4000,
50  "hadron.max_processed_events_per_file": 1000,
51  "mumu.max_processed_events_per_file": 5000,
52  "offip.max_processed_events_per_file": 2000,
53  "stage1.method": "decomposition"
54 }
55 
56 quality_flags = [INPUT_DATA_FILTERS["Run Type"]["physics"],
57  INPUT_DATA_FILTERS["Data Quality Tag"]["Good Or Recoverable"],
58  INPUT_DATA_FILTERS["Magnet"]["On"]]
59 
60 
61 settings = CalibrationSettings(name="Full VXD and CDC Alignment",
62  expert_username="bilkat",
63  description=__doc__,
64  input_data_formats=["raw"],
65  input_data_names=collection_names,
66  input_data_filters={
67  "cosmic": [INPUT_DATA_FILTERS["Data Tag"]["cosmic_calib"]] + quality_flags,
68  "mumu": [INPUT_DATA_FILTERS["Data Tag"]["mumu_tight_or_highm_calib"]] + quality_flags,
69  "hadron": [INPUT_DATA_FILTERS["Data Tag"]["hadron_calib"]] + quality_flags,
70  "offip": [INPUT_DATA_FILTERS["Data Tag"]["offip_calib"]] + quality_flags
71  },
72 
73  expert_config=default_config,
74  depends_on=[cdc_calibration, svd_time_calibration])
75 
76 
77 def select_files(all_input_files, min_events, max_processed_events_per_file):
78  """
79  Select files randomly
80 
81  Parameters
82  ----------
83  all_input_files : list(str)
84  List of all input file names
85  min_events : int
86  Minimum number of events to select from files
87  max_processed_events_per_file : int
88  Maximum number of events to consider per file
89  """
90  all_input_files = all_input_files[:]
91  # Let's iterate, taking a sample of files from the total (no repeats or replacement) until we get enough events
92  total_events = 0
93  chosen_files = []
94  while total_events < min_events:
95  # If the set is empty we must have used all available files. Here we break and continue. But you may want to
96  # raise an Error...
97  if not all_input_files:
98  break
99  # Randomly select a file
100  new_file_choice = choice(all_input_files)
101  # Remove it from the list so it can't be chosen again
102  all_input_files.remove(new_file_choice)
103  # Find the number of events in the file
104  total_events_in_file = events_in_basf2_file(new_file_choice)
105  if not total_events_in_file:
106  # Uh Oh! Zero event file, skip it
107  continue
108 
109  events_contributed = min(total_events_in_file, max_processed_events_per_file)
110 
111  chosen_files.append(new_file_choice)
112  total_events += events_contributed
113 
114  basf2.B2INFO(f"Total chosen files = {len(chosen_files)}")
115  basf2.B2INFO(f"Total events in chosen files = {total_events}")
116  if total_events < min_events:
117  basf2.B2WARNING(
118  f"There weren't enough files events selected when max_processed_events_per_file={max_processed_events_per_file}")
119  return chosen_files
120 
121 
122 def create_std_path():
123  """
124  Returns default path for collections with standard reconstruction
125  """
126  path = basf2.create_path()
127  path.add_module('Progress')
128  path.add_module('RootInput')
129  path.add_module('Gearbox')
130  path.add_module('Geometry')
131  raw.add_unpackers(path)
132  path.add_module('SetupGenfitExtrapolation')
133  reco.add_reconstruction(
134  path,
135  pruneTracks=False,
136  skipGeometryAdding=True,
137  )
138  path.add_module('DAFRecoFitter')
139  return path
140 
141 
142 def create_cosmics_path():
143  """
144  Returns default path for cosmic collection
145  """
146  path = basf2.create_path()
147  path.add_module('Progress')
148  path.add_module('RootInput')
149  path.add_module('Gearbox')
150  path.add_module('Geometry')
151 
152  raw.add_unpackers(path)
153  path.add_module('SetupGenfitExtrapolation')
154  reco.add_cosmics_reconstruction(
155  path,
156  pruneTracks=False,
157  skipGeometryAdding=True,
158  addClusterExpertModules=False,
159  merge_tracks=True
160  )
161 
162  path.add_module('SetRecoTrackMomentum', automatic=True)
163  path.add_module('DAFRecoFitter', pdgCodesToUseForFitting=[13])
164 
165  ana.fillParticleList(
166  'mu+:goodForVXDCDCAlignment',
167  '[z0 <= 57. or abs(d0) >= 26.5] and abs(dz) > 0.4 and nTracks == 1',
168  path=path)
169  path.add_module('SkimFilter', particleLists=['mu+:goodForVXDCDCAlignment']).if_false(basf2.create_path())
170 
171  return path
172 
173 
174 def make_mumu_collection(
175  name,
176  files=None,
177  muon_cut='p > 1.0 and abs(dz) < 2.0 and dr < 0.5 and nTracks==2', dimuon_cut='9.5 < M and M < 11.',
178  prescale=1.):
179  """
180  Di-muons with vertex+beam constraint collection
181 
182  Parameters
183  ----------
184  name : str
185  Collection name
186  files : list(str)
187  List of input data files
188  muon_cut : str
189  Cut string to select daughter muons
190  dimuon_cut : str
191  Cut string to apply for reconstructed di-muon decay
192  prescale : float
193  Process only 'prescale' fraction of events
194  """
195  path = basf2.create_path()
196  path.add_module('Progress')
197  path.add_module('RootInput')
198  if prescale != 1.:
199  path.add_module('Prescale', prescale=prescale).if_false(basf2.Path(), basf2.AfterConditionPath.END)
200 
201  path.add_module('Gearbox')
202  path.add_module('Geometry')
203 
204  raw.add_unpackers(path)
205 
206  reco.add_reconstruction(path, pruneTracks=False)
207 
208  path.add_module('DAFRecoFitter', pdgCodesToUseForFitting=[13])
209 
210  ana.fillParticleList(f"mu+:{name}", muon_cut, path=path)
211  ana.reconstructDecay(f"Upsilon(4S):{name} -> mu+:{name} mu-:{name}", dimuon_cut, path=path)
212 
213  vtx.raveFit(f"Upsilon(4S):{name}", 0.001, daughtersUpdate=True, silence_warning=True, path=path, constraint="ipprofile")
214 
216  name,
217  files=files,
218  path=path,
219  primaryVertices=[f"Upsilon(4S):{name}"])
220 
221 
222 def create_prompt(files, cfg):
223  """
224  Returns configured (original) prompt stage alignment
225 
226  Parameters
227  ----------
228  files : list(str)
229  Dictionary with all input files by category (name)
230  cfg : dict
231  Expert config dictionary
232  """
233  mumu = select_files(files["mumu"], 0.2e6, cfg["mumu.max_processed_events_per_file"])
234  cosmic = select_files(files["cosmic"], 1e6, cfg["cosmic.max_processed_events_per_file"])
235  hadron = select_files(files["hadron"], 0.5e5, cfg["hadron.max_processed_events_per_file"])
236  offip = select_files(files["offip"], 0.2e6, cfg["offip.max_processed_events_per_file"])
237 
238  cal = mpc.create(
239  name='VXDCDCalignment_prompt',
240  dbobjects=['VXDAlignment', 'CDCAlignment'],
241  collections=[
242  mpc.make_collection("cosmic", path=create_cosmics_path(), tracks=["RecoTracks"]),
243  mpc.make_collection("hadron", path=create_std_path(), tracks=["RecoTracks"]),
244  mpc.make_collection("mumu", path=create_std_path(), tracks=["RecoTracks"]),
245  mpc.make_collection("offip", path=create_std_path(), tracks=["RecoTracks"])
246  ],
247  tags=None,
248  files=dict(mumu=mumu, cosmic=cosmic, hadron=hadron, offip=offip),
249  timedep=None,
250  constraints=[
251  alignment.constraints.VXDHierarchyConstraints(type=2, pxd=True, svd=True),
252  alignment.constraints.CDCLayerConstraints(z_offset=True, z_scale=False, twist=True)
253  ],
254  fixed=alignment.parameters.vxd_sensors(rigid=False, surface2=False, surface3=False, surface4=False),
255  commands=[
256  "method diagonalization 3 0.1",
257  "scaleerrors 1. 1.",
258  "entries 1000"],
259  params=dict(minPValue=0.00001, externalIterations=0, granularity="run"),
260  min_entries=1000000)
261 
262  cal.max_iterations = 5
263 
264  return cal
265 
266 
267 def create_beamspot(files, cfg):
268  """
269  Returns configured beamspot calibration
270 
271  Parameters
272  ----------
273  files : list(str)
274  Dictionary with all input files by category (name)
275  cfg : dict
276  Expert config dictionary
277  """
278 
279  mumu = select_files(files["mumu"], 10e6, cfg["mumu.max_processed_events_per_file"])
280 
281 
283 
284  from ROOT.Belle2 import BeamSpotAlgorithm
285  from basf2 import create_path, register_module
286 
287 
289 
290  from caf.framework import Calibration, Collection
291  from caf.strategies import SingleIOV
292 
293  # module to be run prior the collector
294  path = create_path()
295 
296  path.add_module('Progress')
297  path.add_module('RootInput')
298  path.add_module('Gearbox')
299  path.add_module('Geometry')
300  raw.add_unpackers(path)
301  path.add_module('SetupGenfitExtrapolation')
302  reco.add_reconstruction(path, skipGeometryAdding=True)
303 
304  muSelection = '[p>1.0]'
305  muSelection += ' and abs(dz)<2.0 and abs(dr)<0.5'
306  muSelection += ' and nPXDHits >=1 and nSVDHits >= 8 and nCDCHits >= 20'
307  ana.fillParticleList('mu+:BS', muSelection, path=path)
308  ana.reconstructDecay('Upsilon(4S):BS -> mu+:BS mu-:BS', '9.5<M<11.5', path=path)
309 
310  collector_bs = register_module('BeamSpotCollector', Y4SPListName='Upsilon(4S):BS')
311  algorithm_bs = BeamSpotAlgorithm()
312  # algorithm_bs.setOuterLoss(cfg['outerLoss'])
313  # algorithm_bs.setInnerLoss(cfg['innerLoss'])
314 
315  collection_bs = Collection(collector=collector_bs,
316  input_files=mumu,
317  pre_collector_path=path)
318 
319  calibration_bs = Calibration('VXDCDCalignment_beamspot', algorithms=algorithm_bs)
320  calibration_bs.add_collection("mumu", collection_bs)
321 
322  calibration_bs.strategies = SingleIOV
323 
324  return calibration_bs
325 
326 
327 def create_stage1(files, cfg):
328  """
329  Returns configured stage1 alignment (full constant alignment with wires, beamspot fixed)
330 
331  Parameters
332  ----------
333  files : list(str)
334  Dictionary with all input files by category (name)
335  cfg : dict
336  Expert config dictionary
337  """
338 
339  mumu = select_files(files["mumu"], 1.5e6, cfg["mumu.max_processed_events_per_file"])
340  cosmic = select_files(files["cosmic"], 0.7e6, cfg["cosmic.max_processed_events_per_file"])
341  hadron_and_offip = select_files(files["hadron"] + files["offip"], int(4.0e6 / 10.), cfg["hadron.max_processed_events_per_file"])
342 
343  cal = mpc.create(
344  name='VXDCDCalignment_stage1',
345  dbobjects=['VXDAlignment', 'CDCAlignment', 'BeamSpot'],
346  collections=[
347  mpc.make_collection("cosmic", path=create_cosmics_path(), tracks=["RecoTracks"]),
348  mpc.make_collection("hadron", path=create_std_path(), tracks=["RecoTracks"]),
349  make_mumu_collection(name="mumu")
350  ],
351  tags=None,
352  files=dict(mumu=mumu, cosmic=cosmic, hadron=hadron_and_offip),
353  timedep=None,
354  constraints=[
355  alignment.constraints.VXDHierarchyConstraints(type=2, pxd=True, svd=True),
356  alignment.constraints.CDCLayerConstraints(z_offset=False, z_scale=False, twist=False),
357  alignment.constraints.CDCWireConstraints(layer_rigid=True, layer_radius=[53], cdc_radius=True, hemisphere=[55])
358  ],
359  fixed=alignment.parameters.vxd_sensors(rigid=False, surface2=False, surface3=False, surface4=False)
361  commands=[
362  f"method {cfg['stage1.method']} 6 0.001",
363  "entries 1000",
364  "threads 10 10"],
365  params=dict(minPValue=0.00001, externalIterations=0, granularity="run"),
366  min_entries=500000)
367 
368  # Ignore results for BeamSpot
369  std_components = ROOT.vector('string')()
370  for component in ['VXDAlignment', 'CDCAlignment']:
371  std_components.push_back(component)
372  cal.algorithms[0].algorithm.setComponents(std_components)
373 
374  cal.max_iterations = 0
375 
376  return cal
377 
378 
379 def create_stage2(files, cfg):
380  """
381  Returns configured stage2 alignment (run-dependent alignment)
382 
383  Parameters
384  ----------
385  files : list(str)
386  Dictionary with all input files by category (name)
387  cfg : dict
388  Expert config dictionary
389  """
390  mumu = select_files(files["mumu"], 10e6, cfg["mumu.max_processed_events_per_file"])
391  cosmic = select_files(files["cosmic"], 2e6, cfg["cosmic.max_processed_events_per_file"])
392 
393  cal = mpc.create(
394  name='VXDCDCalignment_stage2',
395  dbobjects=['VXDAlignment', 'CDCAlignment', 'BeamSpot'],
396  collections=[
397  mpc.make_collection("cosmic", path=create_cosmics_path(), tracks=["RecoTracks"]),
398  make_mumu_collection(name="mumu")],
399  tags=None,
400  files=dict(mumu=mumu, cosmic=cosmic),
401  timedep=None,
402  constraints=[
403  alignment.constraints.VXDHierarchyConstraints(type=2, pxd=True, svd=True),
404  alignment.constraints.CDCLayerConstraints(z_offset=True, z_scale=False, twist=False)],
405  fixed=alignment.parameters.vxd_sensors(layers=[3, 4, 5, 6]) +
406  alignment.parameters.vxd_sensors(layers=[1, 2], rigid=False, surface2=False, surface3=False, surface4=True),
407  commands=["method inversion 6 0.001", "entries 1000", "threads 10 10"],
408  params=dict(minPValue=0.00001, externalIterations=0, granularity="run"),
409  min_entries=80000)
410 
411  # Ignore results for BeamSpot
412  std_components = ROOT.vector('string')()
413  for component in ['VXDAlignment', 'CDCAlignment']:
414  std_components.push_back(component)
415  cal.algorithms[0].algorithm.setComponents(std_components)
416 
417  # Sequential run-by-run strategy
418  cal.max_iterations = 0
419  cal.algorithms[0].strategy = strategies.SequentialRunByRun
420 
421  return cal
422 
423 
426 
427 
428 def get_calibrations(input_data, **kwargs):
429  """
430  Required function called by b2caf-prompt-run.
431  Returns full configured 4-stage final alignment for prompt
432 
433  """
434  seed(1234)
435 
436  cfg = kwargs['expert_config']
437  files = dict()
438 
439  for colname in collection_names:
440  file_to_iov = input_data[colname]
441  input_files = list(file_to_iov.keys())
442  files[colname] = input_files
443 
444  prompt = create_prompt(files, cfg)
445  beamspot = create_beamspot(files, cfg)
446  stage1 = create_stage1(files, cfg)
447  stage2 = create_stage2(files, cfg)
448 
449  requested_iov = kwargs.get("requested_iov", None)
450  output_iov = IoV(requested_iov.exp_low, requested_iov.run_low, -1, -1)
451 
452  for cal in [prompt, beamspot, stage1, stage2]:
453  for colname in collection_names:
454  if colname not in cal.collections.keys():
455  continue
456  max_processed_events_per_file = cfg[f'{colname}.max_processed_events_per_file']
457  basf2.set_module_parameters(
458  cal.collections[colname].pre_collector_path,
459  'RootInput',
460  entrySequences=[f'0:{max_processed_events_per_file}'], branchNames=HLT_INPUT_OBJECTS)
461 
462  for algorithm in cal.algorithms:
463  algorithm.params = {"apply_iov": output_iov}
464 
465  # Bugfix for Condor:
466  for cal in [prompt, stage1, stage2]:
467  from alignment.prompt_utils import fix_mille_paths_for_algo
468  fix_mille_paths_for_algo(cal.algorithms[0])
469 
470  beamspot.depends_on(prompt)
471  stage1.depends_on(beamspot)
472  stage2.depends_on(stage1)
473 
474  # Do not save BeamSpot payloads in the final output database
475  # because alignment is changed -> BeamSpot payloads now invalid
476  beamspot.save_payloads = False
477 
478  if cfg["only_prompt"]:
479  return [prompt]
480 
481  return [prompt, beamspot, stage1, stage2]
482 
483 
484 if __name__ == '__main__':
485  get_calibrations(dict(cosmic=dict(), mumu=dict(), hadron=dict(), offip=dict()),
486  requested_iov=IoV(0, 0, -1, -1), expert_config=default_config)