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