Belle II Software development
caf_ecms.py
1
8
9"""
10Airflow script to perform eCMS calibration (combination of the had-B and mumu method).
11"""
12
13from prompt import CalibrationSettings, INPUT_DATA_FILTERS
14from prompt.calibrations.caf_boostvector import settings as boostvector
15
16from basf2 import create_path, register_module, get_file_metadata, B2INFO, B2WARNING
17from reconstruction import prepare_cdst_analysis
18import modularAnalysis as ma
19import vertex
20import stdCharged
21import stdPi0s
22import os
23
24
25
26settings = CalibrationSettings(
27 name="Ecms Calibrations",
28 expert_username="zlebcr",
29 description=__doc__,
30 input_data_formats=["cdst"],
31 input_data_names=["hadron4S", "mumu4S", "mumuOff"],
32 input_data_filters={
33 "hadron4S": [
34 INPUT_DATA_FILTERS["Data Tag"]["btocharm_calib"],
35 INPUT_DATA_FILTERS["Run Type"]["physics"],
36 INPUT_DATA_FILTERS["Beam Energy"]["4S"],
37 INPUT_DATA_FILTERS["Data Quality Tag"]["Good Or Recoverable"],
38 INPUT_DATA_FILTERS["Magnet"]["On"]],
39 "mumu4S": [
40 INPUT_DATA_FILTERS["Data Tag"]["mumu_tight_or_highm_calib"],
41 INPUT_DATA_FILTERS["Run Type"]["physics"],
42 INPUT_DATA_FILTERS["Beam Energy"]["4S"],
43 INPUT_DATA_FILTERS["Data Quality Tag"]["Good Or Recoverable"],
44 INPUT_DATA_FILTERS["Magnet"]["On"]],
45 "mumuOff": [
46 INPUT_DATA_FILTERS["Data Tag"]["mumu_tight_or_highm_calib"],
47 INPUT_DATA_FILTERS["Run Type"]["physics"],
48 INPUT_DATA_FILTERS["Beam Energy"]["Continuum"],
49 INPUT_DATA_FILTERS['Beam Energy']['Scan'],
50 INPUT_DATA_FILTERS["Data Quality Tag"]["Good Or Recoverable"],
51 INPUT_DATA_FILTERS["Magnet"]["On"]]
52 },
53 expert_config={
54 "outerLoss": "pow(0.000010e0*rawTime, 2) + 1./nEv",
55 "innerLoss": "pow(0.000120e0*rawTime, 2) + 1./nEv",
56 "runHadB": True,
57 "eCMSmumuSpread": 5.2e-3,
58 "eCMSmumuShift": 10e-3,
59 "minPXDhits": 0},
60 depends_on=[boostvector])
61
62
63
64
65def get_hadB_path(isCDST):
66 """ Selects the hadronic B decays, function returns corresponding path """
67
68 # module to be run prior the collector
69 rec_path_1 = create_path()
70 if isCDST:
71 prepare_cdst_analysis(path=rec_path_1, components=['SVD', 'CDC', 'ECL', 'KLM'])
72
73 stdCharged.stdPi(listtype='loose', path=rec_path_1)
74 stdCharged.stdK(listtype='good', path=rec_path_1)
75 stdPi0s.stdPi0s(listtype='eff40_May2020', path=rec_path_1)
76
77 ma.cutAndCopyList("pi+:my", "pi+:loose", "[abs(dz)<2.0] and [abs(dr)<0.5]", path=rec_path_1)
78 ma.cutAndCopyList("K+:my", "K+:good", "[abs(dz)<2.0] and [abs(dr)<0.5]", path=rec_path_1)
79
80 ma.cutAndCopyList("pi0:my", "pi0:eff40_May2020", "", path=rec_path_1)
81
82
85
86 DcutLoose = '1.7 < M < 2.1'
87 Dcut = '1.830 < M < 1.894'
88 # Reconstructs D0s and sets decay mode identifiers
89 ma.reconstructDecay(decayString='D0:Kpi -> K-:my pi+:my', cut=DcutLoose, dmID=1, path=rec_path_1)
90 ma.reconstructDecay(decayString='D0:Kpipi0 -> K-:my pi+:my pi0:my',
91 cut=DcutLoose, dmID=2, path=rec_path_1)
92 ma.reconstructDecay(decayString='D0:Kpipipi -> K-:my pi+:my pi-:my pi+:my',
93 cut=DcutLoose, dmID=3, path=rec_path_1)
94
95 # Performs mass constrained fit for all D0 candidates
96 vertex.kFit(list_name='D0:Kpi', conf_level=0.0, fit_type='mass', path=rec_path_1)
97 # vertex.kFit(list_name='D0:Kpipi0', conf_level=0.0, fit_type='mass', path=rec_path_1)
98 vertex.kFit(list_name='D0:Kpipipi', conf_level=0.0, fit_type='mass', path=rec_path_1)
99
100 ma.applyCuts("D0:Kpi", Dcut, path=rec_path_1)
101 ma.applyCuts("D0:Kpipi0", Dcut, path=rec_path_1)
102 ma.applyCuts("D0:Kpipipi", Dcut, path=rec_path_1)
103
104 DStarcutLoose = 'massDifference(0) < 0.16'
105
106 # Reconstructs D*-s and sets decay mode identifiers
107 ma.reconstructDecay(decayString='D*+:D0pi_Kpi -> D0:Kpi pi+:my', cut=DStarcutLoose, dmID=1, path=rec_path_1)
108 ma.reconstructDecay(decayString='D*+:D0pi_Kpipi0 -> D0:Kpipi0 pi+:my',
109 cut=DStarcutLoose, dmID=2, path=rec_path_1)
110 ma.reconstructDecay(decayString='D*+:D0pi_Kpipipi -> D0:Kpipipi pi+:my',
111 cut=DStarcutLoose, dmID=3, path=rec_path_1)
112
113 BcutLoose = '[ useCMSFrame(p) < 1.6 ] and [abs(dM) < 0.25]'
114 Bcut = '[ useCMSFrame(p) < 1.2 ] and [abs(dM) < 0.05]'
115
116 # Reconstructs the signal B0 candidates from Dstar
117 ma.reconstructDecay(decayString='B0:Dstpi_D0pi_Kpi -> D*-:D0pi_Kpi pi+:my',
118 cut=BcutLoose,
119 dmID=1, path=rec_path_1)
120 ma.reconstructDecay(decayString='B0:Dstpi_D0pi_Kpipi0 -> D*-:D0pi_Kpipi0 pi+:my',
121 cut=BcutLoose,
122 dmID=2, path=rec_path_1)
123 ma.reconstructDecay(decayString='B0:Dstpi_D0pi_Kpipipi -> D*-:D0pi_Kpipipi pi+:my',
124 cut=BcutLoose,
125 dmID=3, path=rec_path_1)
126
127 vertex.treeFit('B0:Dstpi_D0pi_Kpi', updateAllDaughters=True, ipConstraint=True, path=rec_path_1)
128 vertex.treeFit('B0:Dstpi_D0pi_Kpipi0', updateAllDaughters=True, ipConstraint=True, path=rec_path_1)
129 vertex.treeFit('B0:Dstpi_D0pi_Kpipipi', updateAllDaughters=True, ipConstraint=True, path=rec_path_1)
130
131
134
135 # Reconstructs charged D mesons and sets decay mode identifiers
136 ma.reconstructDecay(decayString='D-:Kpipi -> K+:my pi-:my pi-:my',
137 cut=DcutLoose, dmID=4, path=rec_path_1)
138
139 vertex.kFit(list_name='D-:Kpipi', conf_level=0.0, fit_type='mass', path=rec_path_1)
140 ma.applyCuts("D-:Kpipi", '1.844 < M < 1.894', path=rec_path_1)
141
142 # Reconstructs the signal B candidates
143 ma.reconstructDecay(decayString='B0:Dpi_Kpipi -> D-:Kpipi pi+:my',
144 cut=BcutLoose, dmID=4, path=rec_path_1)
145
146
149
150 # Reconstructs the signal B- candidates
151 ma.reconstructDecay(decayString='B-:D0pi_Kpi -> D0:Kpi pi-:my',
152 cut=BcutLoose,
153 dmID=5, path=rec_path_1)
154 ma.reconstructDecay(decayString='B-:D0pi_Kpipi0 -> D0:Kpipi0 pi-:my',
155 cut=BcutLoose,
156 dmID=6, path=rec_path_1)
157 ma.reconstructDecay(decayString='B-:D0pi_Kpipipi -> D0:Kpipipi pi-:my',
158 cut=BcutLoose,
159 dmID=7, path=rec_path_1)
160
161 vertex.treeFit('B-:D0pi_Kpi', updateAllDaughters=True, ipConstraint=True, path=rec_path_1)
162 vertex.treeFit('B-:D0pi_Kpipi0', updateAllDaughters=True, ipConstraint=True, path=rec_path_1)
163 vertex.treeFit('B-:D0pi_Kpipipi', updateAllDaughters=True, ipConstraint=True, path=rec_path_1)
164
165 ma.copyLists(
166 outputListName='B0:merged',
167 inputListNames=[
168 'B0:Dstpi_D0pi_Kpi',
169 'B0:Dstpi_D0pi_Kpipi0',
170 'B0:Dstpi_D0pi_Kpipipi',
171 'B0:Dpi_Kpipi'
172 ],
173 path=rec_path_1)
174
175 ma.copyLists(
176 outputListName='B-:merged',
177 inputListNames=[
178 'B-:D0pi_Kpi',
179 'B-:D0pi_Kpipi0',
180 'B-:D0pi_Kpipipi',
181 ],
182 path=rec_path_1)
183
184 # Builds the rest of event object, which contains all particles not used in the reconstruction of B0 candidates.
185 ma.buildRestOfEvent(target_list_name='B0:merged', path=rec_path_1)
186
187 # Calculates the continuum suppression variables
188 cleanMask = ('cleanMask', 'nCDCHits > 0 and useCMSFrame(p)<=3.2', 'p >= 0.05 and useCMSFrame(p)<=3.2')
189 ma.appendROEMasks(list_name='B0:merged', mask_tuples=[cleanMask], path=rec_path_1)
190 ma.buildContinuumSuppression(list_name='B0:merged', roe_mask='cleanMask', path=rec_path_1)
191
192 # Builds the rest of event object, which contains all particles not used in the reconstruction of B- candidates.
193 ma.buildRestOfEvent(target_list_name='B-:merged', path=rec_path_1)
194
195 # Calculates the continuum suppression variables
196 cleanMask = ('cleanMask', 'nCDCHits > 0 and useCMSFrame(p)<=3.2', 'p >= 0.05 and useCMSFrame(p)<=3.2')
197 ma.appendROEMasks(list_name='B-:merged', mask_tuples=[cleanMask], path=rec_path_1)
198 ma.buildContinuumSuppression(list_name='B-:merged', roe_mask='cleanMask', path=rec_path_1)
199
200 ma.applyCuts("B0:merged", "[R2 < 0.3] and " + Bcut, path=rec_path_1)
201 ma.applyCuts("B-:merged", "[R2 < 0.3] and " + Bcut, path=rec_path_1)
202
203 return rec_path_1
204
205
206def get_mumu_path(isCDST, kwargs):
207 """ Selects the ee -> mumu events, function returns corresponding path """
208
209 # module to be run prior the collector
210 rec_path_1 = create_path()
211 if isCDST:
212 prepare_cdst_analysis(path=rec_path_1, components=['SVD', 'CDC', 'ECL', 'KLM'])
213
214 minPXDhits = kwargs['expert_config']['minPXDhits']
215 muSelection = '[p>1.0]'
216 muSelection += ' and abs(dz)<2.0 and abs(dr)<0.5'
217 muSelection += f' and nPXDHits >= {minPXDhits} and nSVDHits >= 8 and nCDCHits >= 20'
218
219 ma.fillParticleList('mu+:BV', muSelection, path=rec_path_1)
220 ma.reconstructDecay('Upsilon(4S):BV -> mu+:BV mu-:BV', '9.5<M<11.5', path=rec_path_1)
221 vertex.treeFit('Upsilon(4S):BV', updateAllDaughters=True, ipConstraint=True, path=rec_path_1)
222
223 return rec_path_1
224
225
226def get_data_info(inData, kwargs):
227 """ Filter the input data and returns the IOVs """
228
229 # In this script we want to use one sources of input data.
230 # Get the input files from the input_data variable
231 file_to_iov_physics = inData
232
233 # We might have requested an enormous amount of data across a run range.
234 # There's a LOT more files than runs!
235 # Lets set some limits because this calibration doesn't need that much to run.
236 max_files_per_run = 1000000
237
238 # We filter out any more than 100 files per run. The input data files are sorted alphabetically by b2caf-prompt-run
239 # already. This procedure respects that ordering
240 from prompt.utils import filter_by_max_files_per_run
241
242 reduced_file_to_iov_physics = filter_by_max_files_per_run(file_to_iov_physics, max_files_per_run)
243 input_files_physics = list(reduced_file_to_iov_physics.keys())
244 B2INFO(f"Total number of files actually used as input = {len(input_files_physics)}")
245
246 # Get the overall IoV we our process should cover. Includes the end values that we may want to ignore since our output
247 # IoV should be open ended. We could also use this as part of the input data selection in some way.
248 requested_iov = kwargs.get("requested_iov", None)
249
250 from caf.utils import IoV
251 # The actual value our output IoV payload should have. Notice that we've set it open ended.
252 output_iov = IoV(requested_iov.exp_low, requested_iov.run_low, -1, -1)
253
254 return input_files_physics, output_iov
255
256
257def is_cDST_file(fName):
258 """ Check if the file is cDST based on the metadata """
259
260 metaData = get_file_metadata(fName)
261 description = metaData.getDataDescription()
262
263 # if dataLevel is missing, determine from file name
264 if 'dataLevel' not in description:
265 B2WARNING('The cdst/mdst info is not stored in file metadata')
266 return ('cdst' in os.path.basename(fName))
267
268 return (description['dataLevel'] == 'cdst')
269
270
271def get_calibrations(input_data, **kwargs):
272 """
273 Required function used by b2caf-prompt-run tool.
274 This function return a list of Calibration objects we assign to the CAF process.
275
276 Parameters:
277 input_data (dict): Should contain every name from the 'input_data_names' variable as a key.
278 Each value is a dictionary with {"/path/to/file_e1_r5.root": IoV(1,5,1,5), ...}. Useful for
279 assigning to calibration.files_to_iov
280
281 **kwargs: Configuration options to be sent in. Since this may change we use kwargs as a way to help prevent
282 backwards compatibility problems. But you could use the correct arguments in b2caf-prompt-run for this
283 release explicitly if you want to.
284
285 Currently only kwargs["output_iov"] is used. This is the output IoV range that your payloads should
286 correspond to. Generally your highest ExpRun payload should be open ended e.g. IoV(3,4,-1,-1)
287
288 Returns:
289 list(caf.framework.Calibration): All of the calibration objects we want to assign to the CAF process
290 """
291
292 from caf.framework import Calibration
293 from caf.strategies import SingleIOV
294
295 from ROOT import Belle2 # noqa: make the Belle2 namespace available
296 from ROOT.Belle2 import InvariantMassAlgorithm
297 from caf.framework import Collection
298
299 input_files_Had, output_iov_Had = get_data_info(input_data["hadron4S"], kwargs)
300 input_files_MuMu4S, output_iov_MuMu4S = get_data_info(input_data["mumu4S"], kwargs)
301 input_files_MuMuOff, output_iov_MuMuOff = get_data_info(input_data["mumuOff"], kwargs)
302
303 # Determine if the input files are cDSTs
304 isCDST_had = is_cDST_file(input_files_Had[0]) if len(input_files_Had) > 0 else True
305 isCDST_mumu = is_cDST_file((input_files_MuMu4S + input_files_MuMuOff)[0])
306
307 rec_path_HadB = get_hadB_path(isCDST_had)
308 rec_path_MuMu = get_mumu_path(isCDST_mumu, kwargs)
309
310 collector_HadB = register_module('EcmsCollector')
311 collector_MuMu = register_module('BoostVectorCollector', Y4SPListName='Upsilon(4S):BV')
312
313 algorithm_ecms = InvariantMassAlgorithm()
314 algorithm_ecms.setOuterLoss(kwargs['expert_config']['outerLoss'])
315 algorithm_ecms.setInnerLoss(kwargs['expert_config']['innerLoss'])
316
317 algorithm_ecms.includeHadBcalib(kwargs['expert_config']['runHadB'])
318 algorithm_ecms.setMuMuEcmsSpread(kwargs['expert_config']['eCMSmumuSpread'])
319 algorithm_ecms.setMuMuEcmsOffset(kwargs['expert_config']['eCMSmumuShift'])
320
321 calibration_ecms = Calibration('eCMS', algorithms=algorithm_ecms)
322
323 collection_HadB = Collection(collector=collector_HadB,
324 input_files=input_files_Had,
325 pre_collector_path=rec_path_HadB)
326 collection_MuMu4S = Collection(collector=collector_MuMu,
327 input_files=input_files_MuMu4S,
328 pre_collector_path=rec_path_MuMu)
329 collection_MuMuOff = Collection(collector=collector_MuMu,
330 input_files=input_files_MuMuOff,
331 pre_collector_path=rec_path_MuMu)
332
333 calibration_ecms.add_collection(name='dimuon_4S', collection=collection_MuMu4S)
334 calibration_ecms.add_collection(name='dimuon_Off', collection=collection_MuMuOff)
335 calibration_ecms.add_collection(name='hadB_4S', collection=collection_HadB)
336
337 calibration_ecms.strategies = SingleIOV
338
339 # Do this for the default AlgorithmStrategy to force the output payload IoV
340 # It may be different if you are using another strategy like SequentialRunByRun
341 for algorithm in calibration_ecms.algorithms:
342 algorithm.params = {"iov_coverage": output_iov_Had}
343
344 # Most other options like database chain and backend args will be overwritten by b2caf-prompt-run.
345 # So we don't bother setting them.
346
347 # You must return all calibrations you want to run in the prompt process, even if it's only one
348 return [calibration_ecms]
349
350
def stdK(listtype=_defaultlist, path=None, writeOut=True)
Definition: stdCharged.py:141
def stdPi(listtype=_defaultlist, path=None, writeOut=True)
Definition: stdCharged.py:130
def stdPi0s(listtype="eff60_May2020", path=None, beamBackgroundMVAWeight="", fakePhotonMVAWeight="", biasCorrectionTable="")
Definition: stdPi0s.py:22
def kFit(list_name, conf_level, fit_type='vertex', constraint='', daughtersUpdate=False, decay_string='', massConstraint=[], recoilMass=0, smearing=0, path=None)
Definition: vertex.py:126
def treeFit(list_name, conf_level=0.001, massConstraint=[], ipConstraint=False, updateAllDaughters=False, massConstraintDecayString='', massConstraintMassValues=[], customOriginConstraint=False, customOriginVertex=[0.001, 0, 0.0116], customOriginCovariance=[0.0048, 0, 0, 0, 0.003567, 0, 0, 0, 0.0400], originDimension=3, treatAsInvisible='', ignoreFromVertexFit='', path=None)
Definition: vertex.py:237