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