Belle II Software light-2609-luna
applyModeSelector.py
1#!/usr/bin/env python3
2
3
10
11
23
24import argparse
25import os
26
27import basf2 as b2
28import modeSelector
29import modularAnalysis as ma
30import variables.utils as vu
31from b2pandas_utils import VariablesToTable
32from variables import variables as vm
33
34parser = argparse.ArgumentParser()
35parser.add_argument('--input', nargs='+', default=None,
36 help='Input ROOT file(s) with FEI B meson candidates')
37parser.add_argument('--output', default='modeSelector_output',
38 help='Output parquet filename or stem (default: modeSelector_output)')
39parser.add_argument('--cat-model', default=None,
40 help='Path to category MVA ONNX weightfile (omit to use payloads)')
41parser.add_argument('--main-model', default=None,
42 help='Path to main MVA ONNX weightfile (omit to use payloads)')
43parser.add_argument('--cat-payload-name', default=None,
44 help='Conditions DB payload name for the category model (default: derived from the contract version)')
45parser.add_argument('--main-payload-name', default=None,
46 help='Conditions DB payload name for the main model (default: derived from the contract version)')
47parser.add_argument('--globaltag', default=None,
48 help='Additional globaltag holding the ModeSelector payloads, prepended to the analysis globaltag')
49parser.add_argument('--data', action='store_true',
50 help='Run in data mode: keep 10% of events with eventRandom and drop MC-only output variables')
51args = parser.parse_args()
52
53# Resolved after parsing so that --input works without the validation file installed
54if args.input is None:
55 args.input = [b2.find_file('udst16_feiHadronic.root', 'validation')]
56
57output_root, output_suffix = os.path.splitext(args.output)
58if output_suffix.lower() in ['.pq', '.parquet']:
59 final_output = args.output
60elif output_suffix != '':
61 final_output = output_root + '.pq'
62else:
63 final_output = args.output + '.pq'
64
65# Set up logging
66b2.set_random_seed(1337)
67
68# Create path
69my_path = b2.create_path()
70
71ma.inputMdstList(
72 filelist=args.input,
73 path=my_path
74)
75
76# analysis globaltag for accessing payloads
77b2.conditions.prepend_globaltag(ma.getAnalysisGlobaltag())
78
79# The ModeSelector payloads are not in the analysis globaltag yet, so the
80# globaltag holding them has to be given explicitly when loading from the
81# conditions database.
82if args.globaltag:
83 b2.conditions.prepend_globaltag(args.globaltag)
84
85# FEI list identifier
86fei_identifier = 'feiHadronic'
87
88# Define the particle lists to process
89particle_lists = [f'B+:{fei_identifier}', f'B0:{fei_identifier}']
90
91if args.data:
92 ma.applyEventCuts('eventRandom < 0.1', path=my_path)
93else:
94 # MC truth matching
95 for plist in particle_lists:
96 ma.matchMCTruth(plist, path=my_path)
97
98 # Keep a complementary high-band holdout sample for smaller MC output.
99 ma.applyEventCuts('eventRandom > 0.95', path=my_path)
100
101# Define ROE masks for continuum suppression
102track_mask = "[[dr < 2] and [abs(dz) < 4] and [pt > 0.2] and [thetaInCDCAcceptance==1]]"
103ecl_mask = ("[[[[clusterReg==1] and [E>0.080]] or [[clusterReg==2] and [E > 0.03]] "
104 "or [[clusterReg==3] and [E > 0.06]]] and [clusterNHits > 1.5] "
105 "and [abs(clusterTiming) < 200] and [thetaInCDCAcceptance==1]]")
106cleanMask = ("cleanMask", track_mask, ecl_mask)
107
108# Apply FEI preselection and build continuum suppression on kept events.
109for b in ['B+', 'B0']:
110 ma.applyCuts(f'{b}:{fei_identifier}', '[Mbc > 5.23] and [-0.15 < deltaE < 0.1]', path=my_path)
111
112 # Build the Rest of Event
113 ma.buildRestOfEvent(f'{b}:{fei_identifier}', path=my_path)
114 ma.appendROEMasks(f'{b}:{fei_identifier}', [cleanMask], path=my_path)
115
116 # Build continuum suppression (provides cosTBTO variable)
117 ma.buildContinuumSuppression(f'{b}:{fei_identifier}', 'cleanMask', path=my_path)
118
119 # Apply cosTBTO cut
120 ma.applyCuts(f'{b}:{fei_identifier}', 'cosTBTO < 0.9', path=my_path)
121
122 # rank by sigProb, do not cut yet
123 ma.rankByHighest(f'{b}:{fei_identifier}', 'sigProb', outputVariable='sigProb_rank', path=my_path)
124
125# Build event shape variables (sphericity, thrust, etc.) only for kept events.
126ma.buildEventShape(
127 allMoments=False,
128 cleoCones=False,
129 jets=False,
130 collisionAxis=False,
131 harmonicMoments=True,
132 foxWolfram=True,
133 sphericity=True,
134 thrust=True,
135 path=my_path
136)
137
138# Set debug=True to print feature values for comparison.
140 bp_list=f'B+:{fei_identifier}',
141 b0_list=f'B0:{fei_identifier}',
142 cat_model_path=args.cat_model,
143 main_model_path=args.main_model,
144 payload_cat_model=args.cat_payload_name,
145 payload_main_model=args.main_payload_name,
146 output_variable='BplusScore',
147 store_fei_calib_weight=not args.data,
148 debug=False, # Enable debug output
149 debug_max_events=10, # Print info for first 10 events, if debug is enabled
150 path=my_path
151)
152
153# Candidates are ranked by two criteria stored for offline comparison:
154# 1. sigProb_rank: pure sigProb ranking
155# 2. modeSelector_rank: ModeSelector-based rank written by the module
156
157# cut only AFTER running modeSelector
158# Keep at most 2 candidates per list: rank-1 by sigProb and rank-1 by eqSigProb.
159# isBestCandidate_sigProb stored in output for offline selection.
160for b in ['B+', 'B0']:
161 ma.applyCuts(f'{b}:{fei_identifier}', '[sigProb_rank == 1] or [modeSelector_rank == 1]', path=my_path)
162
163if not args.data:
164 modeSelector.addGeneratedDecayWeights(
165 bp_list=f'B+:{fei_identifier}',
166 b0_list=f'B0:{fei_identifier}',
167 # store_btag_candidate_signature=True,
168 path=my_path
169 )
170
171ma.fillParticleList(decayString="pi+:test", cut='', path=my_path)
172
173# global rank
174vm.addAlias('sigProbRank_global', f'sigProbRank(B+:{fei_identifier}, B0:{fei_identifier})')
175# isBestCandidate_sigProb: rank-1 sigProb candidate in the sector with the higher rank-1 sigProb
176vm.addAlias('isBestCandidate_sigProb', 'conditionalVariableSelector(sigProbRank_global == 1, 1, 0)')
177
178# Create aliases for cleaner branch names in output ntuple
179vm.addAlias('sigProb', 'extraInfo(SignalProbability)')
180vm.addAlias('dmID', 'extraInfo(decayModeID)')
181
182candidate_variables = [
183 'sigProb_rank',
184 'modeSelector_rank',
185 'modeSelector_eqSigProb',
186 'Dstp_deltaMassDiff',
187 'Dstp_chiProb',
188 'Dst0_deltaMassDiff',
189 'Dst0_chiProb',
190 'genDecayModeID',
191 'genFEICalibWeight',
192]
193vu.create_aliases(candidate_variables, wrapper='extraInfo({variable})')
194
195event_output_variables = [
196 'BplusScore',
197 'modeSelector_catB0',
198 'modeSelector_catBp',
199 'modeSelector_catCont',
200]
201vu.create_aliases(event_output_variables, wrapper='eventExtraInfo({variable})')
202
203# Define output variables
204output_variables = [
205 # Basic kinematics
206 'Mbc', 'deltaE', 'M',
207 # Continuum suppression
208 'cosTBTO',
209 # FEI signal probability
210 'sigProb',
211 'dmID',
212 # ModeSelector output (candidate-level)
213 'modeSelector_eqSigProb',
214 # ModeSelector output (event-level)
215 'BplusScore',
216 'modeSelector_catB0',
217 'modeSelector_catBp',
218 'modeSelector_catCont',
219 'PDG',
220 # D* veto variables
221 'Dstp_deltaMassDiff',
222 'Dstp_chiProb',
223 'Dst0_deltaMassDiff',
224 'Dst0_chiProb',
225 # ranking variables
226 'sigProb_rank',
227 'modeSelector_rank',
228 'sigProbRank_global',
229 'isBestCandidate_sigProb',
230 'eventRandom',
231]
232
233if not args.data:
234 vm.addAlias('modeSelector_feiCalibWeight', 'eventExtraInfo(modeSelector_feiCalibWeight)')
235 output_variables.extend([
236 'genDecayModeID',
237 'genFEICalibWeight',
238 'isSignal',
239 'isContinuumEvent',
240 'mostcommonBTagDeltaP',
241 'mostcommonBTagPDG',
242 'modeSelector_feiCalibWeight',
243 ])
244
245# Wrap B+ and B0 in a common Upsilon(4S) candidate and merge lists
246for b, b_str in zip(['B+', 'B0'], ['Bp', 'B0']):
247 ma.reconstructDecay(
248 f'Upsilon(4S):{b_str} -> {b}:{fei_identifier}',
249 '',
250 allowChargeViolation=True,
251 path=my_path
252 )
253
254ma.copyLists(
255 outputListName='Upsilon(4S):all',
256 inputListNames=['Upsilon(4S):Bp', 'Upsilon(4S):B0'],
257 path=my_path
258)
259
260# Save daughter(0, ...) variables with B_ prefix in output columns
261merged_output_variables = vu.create_daughter_aliases(
262 output_variables,
263 [0],
264 prefix='B',
265 include_indices=False
266)
267
268v2t = VariablesToTable(
269 'Upsilon(4S):all',
270 variables=merged_output_variables,
271 filename=final_output,
272 event_buffer_size=500_000,
273)
274my_path.add_module(v2t)
275
276# Process events
277b2.process(my_path)
278
279# Print statistics
280print(b2.statistics)
modeSelector(bp_list, b0_list, payload_cat_model=None, payload_main_model=None, output_variable='BplusScore', cat_model_path=None, main_model_path=None, addDstarVetoReco=True, training_mode=False, skip_nn_evaluation=False, store_fei_calib_weight=False, debug=False, debug_max_events=10, path=None)
Definition __init__.py:57