Belle II Software  release-08-02-06
caf_ecms.py
1 
8 
9 """
10 Airflow script to perform eCMS calibration (combination of the had-B and mumu method).
11 """
12 
13 from prompt import CalibrationSettings, INPUT_DATA_FILTERS
14 from prompt.calibrations.caf_boostvector import settings as boostvector
15 from softwaretrigger.constants import ALWAYS_SAVE_OBJECTS, RAWDATA_OBJECTS
16 import rawdata as rd
17 import reconstruction as re
18 
19 from basf2 import create_path, register_module, get_file_metadata, B2INFO, B2WARNING
20 import modularAnalysis as ma
21 import vertex
22 import stdCharged
23 import stdPi0s
24 import os
25 
26 
27 
28 settings = 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 
67 def 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 
210 def 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 
232 def 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 
263 def 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 
277 def 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.Belle2 import InvariantMassAlgorithm
302  from caf.framework import Collection
303 
304  input_files_Had, output_iov_Had = get_data_info(input_data["hadron4S"], kwargs)
305  input_files_MuMu4S, output_iov_MuMu4S = get_data_info(input_data["mumu4S"], kwargs)
306  input_files_MuMuOff, output_iov_MuMuOff = get_data_info(input_data["mumuOff"], kwargs)
307 
308  # Determine if the input files are cDSTs
309  isCDST_had = is_cDST_file(input_files_Had[0]) if len(input_files_Had) > 0 else True
310  isCDST_mumu = is_cDST_file((input_files_MuMu4S + input_files_MuMuOff)[0])
311 
312  rec_path_HadB = get_hadB_path(isCDST_had)
313  rec_path_MuMu = get_mumu_path(isCDST_mumu, kwargs)
314 
315  collector_HadB = register_module('EcmsCollector')
316  collector_MuMu = register_module('BoostVectorCollector', Y4SPListName='Upsilon(4S):BV')
317 
318  algorithm_ecms = InvariantMassAlgorithm()
319  algorithm_ecms.setOuterLoss(kwargs['expert_config']['outerLoss'])
320  algorithm_ecms.setInnerLoss(kwargs['expert_config']['innerLoss'])
321 
322  algorithm_ecms.includeHadBcalib(kwargs['expert_config']['runHadB'])
323  algorithm_ecms.setMuMuEcmsSpread(kwargs['expert_config']['eCMSmumuSpread'])
324  algorithm_ecms.setMuMuEcmsOffset(kwargs['expert_config']['eCMSmumuShift'])
325 
326  calibration_ecms = Calibration('eCMS', algorithms=algorithm_ecms)
327 
328  collection_HadB = Collection(collector=collector_HadB,
329  input_files=input_files_Had,
330  pre_collector_path=rec_path_HadB)
331  collection_MuMu4S = Collection(collector=collector_MuMu,
332  input_files=input_files_MuMu4S,
333  pre_collector_path=rec_path_MuMu)
334  collection_MuMuOff = Collection(collector=collector_MuMu,
335  input_files=input_files_MuMuOff,
336  pre_collector_path=rec_path_MuMu)
337 
338  calibration_ecms.add_collection(name='dimuon_4S', collection=collection_MuMu4S)
339  calibration_ecms.add_collection(name='dimuon_Off', collection=collection_MuMuOff)
340  calibration_ecms.add_collection(name='hadB_4S', collection=collection_HadB)
341 
342  calibration_ecms.strategies = SingleIOV
343 
344  # Do this for the default AlgorithmStrategy to force the output payload IoV
345  # It may be different if you are using another strategy like SequentialRunByRun
346  for algorithm in calibration_ecms.algorithms:
347  algorithm.params = {"iov_coverage": output_iov_Had}
348 
349  # Most other options like database chain and backend args will be overwritten by b2caf-prompt-run.
350  # So we don't bother setting them.
351 
352  # You must return all calibrations you want to run in the prompt process, even if it's only one
353  return [calibration_ecms]
354 
355 
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:129
def treeFit(list_name, conf_level=0.001, massConstraint=[], ipConstraint=False, updateAllDaughters=False, 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:239