Belle II Software development
caf_vxdcdc_alignment.py
1
8"""
9
10Full Simultaneous Global and Local VXD and CDC alignment with Millepede II
11
12The 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
20import basf2
21import ROOT
22
23from prompt import CalibrationSettings, INPUT_DATA_FILTERS
24from prompt.calibrations.caf_cdc import settings as cdc_calibration
25from prompt.calibrations.caf_svd_time import settings as svd_time_calibration
26
27from prompt.utils import events_in_basf2_file
28from caf.utils import IoV
29from caf import strategies
30
31from softwaretrigger.constants import HLT_INPUT_OBJECTS
32import rawdata as raw
33import reconstruction as reco
34import modularAnalysis as ana
35import vertex as vtx
36
37from random import choice
38from random import seed
39
40import millepede_calibration as mpc
44
46
47collection_names = ["cosmic", "hadron", "mumu", "offip"]
48
49default_config = {
50 "only_prompt": False,
51 "cosmic.max_processed_events_per_file": 4000,
52 "hadron.max_processed_events_per_file": 1000,
53 "mumu.max_processed_events_per_file": 5000,
54 "offip.max_processed_events_per_file": 2000,
55 "beamspot.min_pxd_hits": 0,
56 "stage1.method": "decomposition",
57 "stage1.z_offset": True
58}
59
60quality_flags = [INPUT_DATA_FILTERS["Run Type"]["physics"],
61 INPUT_DATA_FILTERS["Data Quality Tag"]["Good Or Recoverable"],
62 INPUT_DATA_FILTERS["Magnet"]["On"]]
63
64
65settings = CalibrationSettings(name="Full VXD and CDC Alignment",
66 expert_username="tadeas.bilka",
67 subsystem="alignment",
68 description=__doc__,
69 input_data_formats=["raw"],
70 input_data_names=collection_names,
71 input_data_filters={
72 "cosmic": [INPUT_DATA_FILTERS["Data Tag"]["cosmic_calib"]] + quality_flags,
73 "mumu": [INPUT_DATA_FILTERS["Data Tag"]["mumu_tight_or_highm_calib"]] + quality_flags,
74 "hadron": [INPUT_DATA_FILTERS["Data Tag"]["hadron_calib"]] + quality_flags,
75 "offip": [INPUT_DATA_FILTERS["Data Tag"]["offip_calib"]] + quality_flags
76 },
77
78 expert_config=default_config,
79 depends_on=[cdc_calibration, svd_time_calibration],
80 produced_payloads=["VXDAlignment", "CDCAlignment"])
81
82
83def select_files(all_input_files, min_events, max_processed_events_per_file):
84 """
85 Select files randomly
86
87 Parameters
88 ----------
89 all_input_files : list(str)
90 List of all input file names
91 min_events : int
92 Minimum number of events to select from files
93 max_processed_events_per_file : int
94 Maximum number of events to consider per file
95 """
96 all_input_files = all_input_files[:]
97 # Let's iterate, taking a sample of files from the total (no repeats or replacement) until we get enough events
98 total_events = 0
99 chosen_files = []
100 while total_events < min_events:
101 # If the set is empty we must have used all available files. Here we break and continue. But you may want to
102 # raise an Error...
103 if not all_input_files:
104 break
105 # Randomly select a file
106 new_file_choice = choice(all_input_files)
107 # Remove it from the list so it can't be chosen again
108 all_input_files.remove(new_file_choice)
109 # Find the number of events in the file
110 total_events_in_file = events_in_basf2_file(new_file_choice)
111 if not total_events_in_file:
112 # Uh Oh! Zero event file, skip it
113 continue
114
115 events_contributed = min(total_events_in_file, max_processed_events_per_file)
116
117 chosen_files.append(new_file_choice)
118 total_events += events_contributed
119
120 basf2.B2INFO(f"Total chosen files = {len(chosen_files)}")
121 basf2.B2INFO(f"Total events in chosen files = {total_events}")
122 if total_events < min_events:
123 basf2.B2WARNING(
124 f"There weren't enough files events selected when max_processed_events_per_file={max_processed_events_per_file}")
125 return chosen_files
126
127
128def create_std_path():
129 """
130 Returns default path for collections with standard reconstruction
131 """
132 path = basf2.create_path()
133 path.add_module('Progress')
134 path.add_module('RootInput')
135 path.add_module('Gearbox')
136 path.add_module('Geometry')
137 raw.add_unpackers(path)
138 path.add_module('SetupGenfitExtrapolation')
139 reco.add_reconstruction(
140 path,
141 pruneTracks=False,
142 skipGeometryAdding=True,
143 )
144 path.add_module('DAFRecoFitter')
145 return path
146
147
148def create_dimuon_validation_path():
149 """
150 Returns default path for dimuon validation collection
151 """
152 path = create_std_path()
153 ana.fillParticleList('mu+:validation', 'p > 1.0 and abs(dz) < 2.0 and abs(dr) < 0.5 and nTracks == 2', path=path)
154 ana.reconstructDecay('Upsilon(4S):validation -> mu+:validation mu-:validation', '9.5<M<11.5', path=path)
155 vtx.treeFit('Upsilon(4S):validation', path=path)
156
157 track_variables = [
158 'd0', 'z0', 'phi0', 'omega', 'tanLambda', 'pt',
159 'z0FromIP', 'd0FromIP', 'phi0FromIP',
160 'electronID', 'muonID',
161 'nVXDHits', 'nPXDHits', 'nSVDHits', 'nCDCHits',
162 'nTracks',
163 'x', 'y', 'z', 'M'
164 ]
165
166 ana.variablesToNtuple('Upsilon(4S):validation',
167 variables=[
168 'chiProb',
169 'nTracks',
170 'pt', 'pz',
171 'p', 'E', 'theta', 'phi',
172 'InvM', 'M',
173 'date', 'eventTimeSeconds',
174 'IPX', 'IPY', 'IPZ'
175 ]
176 + variables.collections.vertex
177 + ['daughter(0, {})'.format(var) for var in track_variables]
178 + ['daughter(1, {})'.format(var) for var in track_variables]
179 + ['useRestFrame(daughter(0, {}))'.format(var) for var in track_variables]
180 + ['useRestFrame(daughter(1, {}))'.format(var) for var in track_variables]
181 + ['useCMSFrame(daughter(0, {}))'.format(var) for var in track_variables]
182 + ['useCMSFrame(daughter(1, {}))'.format(var) for var in track_variables]
183 + ['V0d0(0)', 'V0d0(1)', 'V0z0(0)', 'V0z0(1)'],
184 filename='dimuon_ana.root',
185 path=path
186 )
187
188 path.add_module("TrackDQM")
189 path.add_module("AlignDQM")
190
191 return path
192
193
194def create_cosmic_validation_path():
195 """
196 Returns validation path for cosmic collection
197 """
198 path = basf2.create_path()
199 path.add_module('Progress')
200 path.add_module('RootInput')
201 path.add_module('Gearbox')
202 path.add_module('Geometry')
203
204 raw.add_unpackers(path)
205 path.add_module('SetupGenfitExtrapolation')
206 reco.add_cosmics_reconstruction(
207 path,
208 pruneTracks=False,
209 skipGeometryAdding=True,
210 addClusterExpertModules=False,
211 merge_tracks=False
212 )
213
214 path.add_module('SetRecoTrackMomentum', automatic=True)
215 path.add_module('DAFRecoFitter', pdgCodesToUseForFitting=[13])
216
217 ana.fillParticleList(
218 'mu+:goodForVXDCDCAlignment',
219 '[z0 <= 57. or abs(d0) >= 26.5] and abs(dz) > 0.4 and nTracks == 2',
220 path=path)
221 path.add_module('SkimFilter', particleLists=['mu+:goodForVXDCDCAlignment']).if_false(basf2.create_path())
222
223 path.add_module(
224 "CDCCosmicAnalysis",
225 StoreTrackParErrors=True,
226 Output="cosmic_ana.root")
227
228 path.add_module("TrackDQM")
229 path.add_module("AlignDQM")
230
231 return path
232
233
234def create_cosmics_path():
235 """
236 Returns default path for cosmic collection
237 """
238 path = basf2.create_path()
239 path.add_module('Progress')
240 path.add_module('RootInput')
241 path.add_module('Gearbox')
242 path.add_module('Geometry')
243
244 raw.add_unpackers(path)
245 path.add_module('SetupGenfitExtrapolation')
246 reco.add_cosmics_reconstruction(
247 path,
248 pruneTracks=False,
249 skipGeometryAdding=True,
250 addClusterExpertModules=False,
251 merge_tracks=True
252 )
253
254 path.add_module('SetRecoTrackMomentum', automatic=True)
255 path.add_module('DAFRecoFitter', pdgCodesToUseForFitting=[13])
256
257 ana.fillParticleList(
258 'mu+:goodForVXDCDCAlignment',
259 '[z0 <= 57. or abs(d0) >= 26.5] and abs(dz) > 0.4 and nTracks == 1',
260 path=path)
261 path.add_module('SkimFilter', particleLists=['mu+:goodForVXDCDCAlignment']).if_false(basf2.create_path())
262
263 return path
264
265
266def make_mumu_collection(
267 name,
268 files=None,
269 muon_cut='p > 1.0 and abs(dz) < 2.0 and dr < 0.5 and nTracks==2',
270 dimuon_cut='9.5 < M and M < 11.',
271 prescale=1.):
272 """
273 Di-muons with vertex+beam constraint collection
274
275 Parameters
276 ----------
277 name : str
278 Collection name
279 files : list(str)
280 List of input data files
281 muon_cut : str
282 Cut string to select daughter muons
283 dimuon_cut : str
284 Cut string to apply for reconstructed di-muon decay
285 prescale : float
286 Process only 'prescale' fraction of events
287 """
288 path = basf2.create_path()
289 path.add_module('Progress')
290 path.add_module('RootInput')
291 if prescale != 1.:
292 path.add_module('Prescale', prescale=prescale).if_false(basf2.Path(), basf2.AfterConditionPath.END)
293
294 path.add_module('Gearbox')
295 path.add_module('Geometry')
296
297 raw.add_unpackers(path)
298
299 reco.add_reconstruction(path, pruneTracks=False)
300
301 path.add_module('DAFRecoFitter', pdgCodesToUseForFitting=[13])
302
303 ana.fillParticleList(f"mu+:{name}", muon_cut, path=path)
304 ana.reconstructDecay(f"Upsilon(4S):{name} -> mu+:{name} mu-:{name}", dimuon_cut, path=path)
305
306 vtx.raveFit(f"Upsilon(4S):{name}", 0.001, daughtersUpdate=True, silence_warning=True, path=path, constraint="ipprofile")
307
309 name,
310 files=files,
311 path=path,
312 primaryVertices=[f"Upsilon(4S):{name}"])
313
314
315def create_validation(files, cfg, name='VXDCDCalignment_validation'):
316 """
317 Returns configured (original) validation stage alignment
318
319 Parameters
320 ----------
321 files : list(str)
322 Dictionary with all input files by category (name)
323 cfg : dict
324 Expert config dictionary
325 """
326 mumu = select_files(files["mumu"], 0.2e6, cfg["mumu.max_processed_events_per_file"])
327 cosmic = select_files(files["cosmic"], 1e6, cfg["cosmic.max_processed_events_per_file"])
328
329 cal = mpc.create(
330 name=name,
331 dbobjects=['VXDAlignment', 'CDCAlignment'],
332 collections=[
333 mpc.make_collection("cosmic", path=create_cosmic_validation_path(), tracks=["RecoTracks"]),
334 mpc.make_collection("mumu", path=create_dimuon_validation_path(), tracks=["RecoTracks"])
335 ],
336 tags=None,
337 files=dict(mumu=mumu, cosmic=cosmic),
338 timedep=None,
339 constraints=[
340 alignment.constraints.VXDHierarchyConstraints(type=2, pxd=True, svd=True),
341 alignment.constraints.CDCLayerConstraints(z_offset=True, z_scale=False, twist=True)
342 ],
343 fixed=alignment.parameters.vxd_sensors(rigid=False, surface2=False, surface3=False, surface4=False),
344 commands=[
345 "method diagonalization 3 0.1",
346 "threads 10 10",
347 "scaleerrors 1. 1.",
348 "entries 1000"],
349 params=dict(minPValue=0.00001, externalIterations=0, granularity="run"),
350 min_entries=1000000)
351
352 cal.max_iterations = 0
353 cal.save_payloads = False
354
355 return cal
356
357
358def create_prompt(files, cfg, name='VXDCDCalignment_prompt'):
359 """
360 Returns configured (original) prompt stage alignment
361
362 Parameters
363 ----------
364 files : list(str)
365 Dictionary with all input files by category (name)
366 cfg : dict
367 Expert config dictionary
368 """
369 mumu = select_files(files["mumu"], 0.2e6, cfg["mumu.max_processed_events_per_file"])
370 cosmic = select_files(files["cosmic"], 1e6, cfg["cosmic.max_processed_events_per_file"])
371 hadron = select_files(files["hadron"], 0.5e5, cfg["hadron.max_processed_events_per_file"])
372 offip = select_files(files["offip"], 0.2e6, cfg["offip.max_processed_events_per_file"])
373
374 cal = mpc.create(
375 name=name,
376 dbobjects=['VXDAlignment', 'CDCAlignment'],
377 collections=[
378 mpc.make_collection("cosmic", path=create_cosmics_path(), tracks=["RecoTracks"]),
379 mpc.make_collection("hadron", path=create_std_path(), tracks=["RecoTracks"]),
380 mpc.make_collection("mumu", path=create_std_path(), tracks=["RecoTracks"]),
381 mpc.make_collection("offip", path=create_std_path(), tracks=["RecoTracks"])
382 ],
383 tags=None,
384 files=dict(mumu=mumu, cosmic=cosmic, hadron=hadron, offip=offip),
385 timedep=None,
386 constraints=[
387 alignment.constraints.VXDHierarchyConstraints(type=2, pxd=True, svd=True),
388 alignment.constraints.CDCLayerConstraints(z_offset=True, z_scale=False, twist=True)
389 ],
390 fixed=alignment.parameters.vxd_sensors(rigid=False, surface2=False, surface3=False, surface4=False),
391 commands=[
392 "method diagonalization 3 0.1",
393 "threads 10 10",
394 "scaleerrors 1. 1.",
395 "entries 1000"],
396 params=dict(minPValue=0.00001, externalIterations=0, granularity="run"),
397 min_entries=1000000)
398
399 cal.max_iterations = 5
400
401 return cal
402
403
404def create_beamspot(files, cfg, name='VXDCDCalignment_beamspot'):
405 """
406 Returns configured beamspot calibration
407
408 Parameters
409 ----------
410 files : list(str)
411 Dictionary with all input files by category (name)
412 cfg : dict
413 Expert config dictionary
414 """
415
416 mumu = select_files(files["mumu"], 10e6, cfg["mumu.max_processed_events_per_file"])
417
418
420
421 from ROOT.Belle2 import BeamSpotAlgorithm
422 from basf2 import create_path, register_module
423
424
426
427 from caf.framework import Calibration, Collection
428 from caf.strategies import SingleIOV
429
430 # module to be run prior the collector
431 path = create_path()
432
433 path.add_module('Progress')
434 path.add_module('RootInput')
435 path.add_module('Gearbox')
436 path.add_module('Geometry')
437 raw.add_unpackers(path)
438 path.add_module('SetupGenfitExtrapolation')
439 reco.add_reconstruction(path, skipGeometryAdding=True)
440
441 muSelection = '[p>1.0]'
442 muSelection += ' and abs(dz)<2.0 and abs(dr)<0.5'
443 muSelection += f' and nPXDHits >= {cfg["beamspot.min_pxd_hits"]} and nSVDHits >= 8 and nCDCHits >= 20'
444 ana.fillParticleList('mu+:BS', muSelection, path=path)
445 ana.reconstructDecay('Upsilon(4S):BS -> mu+:BS mu-:BS', '9.5<M<11.5', path=path)
446
447 collector_bs = register_module('BeamSpotCollector', Y4SPListName='Upsilon(4S):BS')
448 algorithm_bs = BeamSpotAlgorithm()
449 # algorithm_bs.setOuterLoss(cfg['outerLoss'])
450 # algorithm_bs.setInnerLoss(cfg['innerLoss'])
451
452 collection_bs = Collection(collector=collector_bs,
453 input_files=mumu,
454 pre_collector_path=path)
455
456 calibration_bs = Calibration(name, algorithms=algorithm_bs)
457 calibration_bs.add_collection("mumu", collection_bs)
458
459 calibration_bs.strategies = SingleIOV
460
461 return calibration_bs
462
463
464def create_stage1(files, cfg, name='VXDCDCalignment_stage1'):
465 """
466 Returns configured stage1 alignment (full constant alignment with wires, beamspot fixed)
467
468 Parameters
469 ----------
470 files : list(str)
471 Dictionary with all input files by category (name)
472 cfg : dict
473 Expert config dictionary
474 """
475
476 mumu = select_files(files["mumu"], 1.5e6, cfg["mumu.max_processed_events_per_file"])
477 cosmic = select_files(files["cosmic"], 0.7e6, cfg["cosmic.max_processed_events_per_file"])
478 hadron_and_offip = select_files(files["hadron"] + files["offip"], int(4.0e6 / 10.), cfg["hadron.max_processed_events_per_file"])
479
480 cal = mpc.create(
481 name=name,
482 dbobjects=['VXDAlignment', 'CDCAlignment', 'BeamSpot'],
483 collections=[
484 mpc.make_collection("cosmic", path=create_cosmics_path(), tracks=["RecoTracks"]),
485 mpc.make_collection("hadron", path=create_std_path(), tracks=["RecoTracks"]),
486 make_mumu_collection(name="mumu")
487 ],
488 tags=None,
489 files=dict(mumu=mumu, cosmic=cosmic, hadron=hadron_and_offip),
490 timedep=None,
491 constraints=[
492 alignment.constraints.VXDHierarchyConstraints(type=2, pxd=True, svd=True),
493 alignment.constraints.CDCLayerConstraints(z_offset=cfg["stage1.z_offset"], z_scale=False, twist=False),
494 alignment.constraints.CDCWireConstraints(layer_rigid=True, layer_radius=[53], cdc_radius=True, hemisphere=[55])
495 ],
496 fixed=alignment.parameters.vxd_sensors(rigid=False, surface2=False, surface3=False, surface4=False)
498 commands=[
499 f"method {cfg['stage1.method']} 6 0.001",
500 "entries 1000",
501 "threads 10 10"],
502 params=dict(minPValue=0.00001, externalIterations=0, granularity="run"),
503 min_entries=500000)
504
505 # Ignore results for BeamSpot
506 std_components = ROOT.vector('string')()
507 for component in ['VXDAlignment', 'CDCAlignment']:
508 std_components.push_back(component)
509 cal.algorithms[0].algorithm.setComponents(std_components)
510
511 cal.max_iterations = 0
512
513 return cal
514
515
516def create_stage2(files, cfg, name='VXDCDCalignment_stage2'):
517 """
518 Returns configured stage2 alignment (run-dependent alignment)
519
520 Parameters
521 ----------
522 files : list(str)
523 Dictionary with all input files by category (name)
524 cfg : dict
525 Expert config dictionary
526 """
527 mumu = select_files(files["mumu"], 10e6, cfg["mumu.max_processed_events_per_file"])
528 cosmic = select_files(files["cosmic"], 2e6, cfg["cosmic.max_processed_events_per_file"])
529
530 cal = mpc.create(
531 name=name,
532 dbobjects=['VXDAlignment', 'CDCAlignment', 'BeamSpot'],
533 collections=[
534 mpc.make_collection("cosmic", path=create_cosmics_path(), tracks=["RecoTracks"]),
535 make_mumu_collection(name="mumu")],
536 tags=None,
537 files=dict(mumu=mumu, cosmic=cosmic),
538 timedep=None,
539 constraints=[
540 alignment.constraints.VXDHierarchyConstraints(type=2, pxd=True, svd=True),
541 alignment.constraints.CDCLayerConstraints(z_offset=True, z_scale=False, twist=False)],
542 fixed=alignment.parameters.vxd_sensors(layers=[3, 4, 5, 6]) +
543 alignment.parameters.vxd_sensors(layers=[1, 2], rigid=False, surface2=False, surface3=False, surface4=True),
544 commands=["method inversion 6 0.001", "entries 1000", "threads 10 10"],
545 params=dict(minPValue=0.00001, externalIterations=0, granularity="run"),
546 min_entries=80000)
547
548 # Ignore results for BeamSpot
549 std_components = ROOT.vector('string')()
550 for component in ['VXDAlignment', 'CDCAlignment']:
551 std_components.push_back(component)
552 cal.algorithms[0].algorithm.setComponents(std_components)
553
554 # Sequential run-by-run strategy
555 cal.max_iterations = 0
556 cal.algorithms[0].strategy = strategies.SequentialRunByRun
557
558 return cal
559
560
563
564
565def get_calibrations(input_data, **kwargs):
566 """
567 Required function called by b2caf-prompt-run.
568 Returns full configured 4-stage final alignment for prompt
569
570 """
571 seed(1234)
572
573 cfg = kwargs['expert_config']
574 files = dict()
575
576 for colname in collection_names:
577 file_to_iov = input_data[colname]
578 input_files = list(file_to_iov.keys())
579 files[colname] = input_files
580
581 prompt = create_prompt(files, cfg)
582 beamspot = create_beamspot(files, cfg)
583 stage1 = create_stage1(files, cfg)
584 stage2 = create_stage2(files, cfg)
585
586 # Add last prompt alignment as validation (does not save payloads)
587 validation = create_validation(files, cfg)
588
589 requested_iov = kwargs.get("requested_iov", None)
590 output_iov = IoV(requested_iov.exp_low, requested_iov.run_low, -1, -1)
591
592 for cal in [prompt, beamspot, stage1, stage2, validation]:
593 for colname in collection_names:
594 if colname not in cal.collections.keys():
595 continue
596 max_processed_events_per_file = cfg[f'{colname}.max_processed_events_per_file']
597 basf2.set_module_parameters(
598 cal.collections[colname].pre_collector_path,
599 'RootInput',
600 entrySequences=[f'0:{max_processed_events_per_file}'], branchNames=HLT_INPUT_OBJECTS)
601
602 for algorithm in cal.algorithms:
603 algorithm.params = {"apply_iov": output_iov}
604
605 # Bugfix for Condor:
606 for cal in [prompt, stage1, stage2]:
607 from alignment.prompt_utils import fix_mille_paths_for_algo
608 fix_mille_paths_for_algo(cal.algorithms[0])
609
610 beamspot.depends_on(prompt)
611 stage1.depends_on(beamspot)
612 stage2.depends_on(stage1)
613 # finally a validation
614 validation.depends_on(stage2)
615
616 # Do not save BeamSpot payloads in the final output database
617 # because alignment is changed -> BeamSpot payloads now invalid
618 beamspot.save_payloads = False
619
620 if cfg["only_prompt"]:
621 return [prompt]
622
623 return [prompt, beamspot, stage1, stage2, validation]
624
625
626if __name__ == '__main__':
627 get_calibrations(dict(cosmic=dict(), mumu=dict(), hadron=dict(), offip=dict()),
628 requested_iov=IoV(0, 0, -1, -1), expert_config=default_config)
make_collection(name, files=None, path=None, **argk)
vxd_sensors(layers=None, rigid=True, surface=True, surface2=True, surface3=True, surface4=True, parameters=None)