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