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