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