11D* Veto reconstruction for ModeSelector.
13This module reconstructs D* candidates from D mesons (daughters of B candidates)
14combined with soft pions or pi0s from the Rest of Event. The reconstructed
15D* mass difference (deltaMassDiff) and vertex fit chi2 probability are stored
16as ExtraInfo on the B candidates.
18This helps identify B -> D* X decays that were reconstructed as B -> D X
19by the FEI, which the ModeSelector uses as input features (feature blocks 5-8).
25import modularAnalysis
as ma
26from ROOT
import Belle2
27from variables
import variables
as vm
28from vertex
import kFit, treeFit
32 """Set missing D* veto ExtraInfo keys to NaN on all B candidates.
34 This ensures every B candidate has Dstp_deltaMassDiff and Dst0_deltaMassDiff
35 set (even when the corresponding D* type is not its first daughter and no veto
36 candidate is reconstructed).
40 """Set up with lists of particle list names and ExtraInfo key names."""
48 """Set missing ExtraInfo keys to NaN for every candidate in each list."""
51 if not plist.isValid():
53 for i
in range(plist.obj().getListSize()):
54 p = plist.obj().getParticle(i)
55 for key
in self.
_keys:
56 if not p.hasExtraInfo(key):
57 p.addExtraInfo(key, math.nan)
60def add_dstar_veto_aliases():
62 Add variable aliases needed for D* veto reconstruction.
65 vm.addAlias(
'trueM',
'M - dM')
66 vm.addAlias(
'massDiffInvM',
'formula(InvM - daughter(0, M))')
67 vm.addAlias(
'trueMassDiff',
'trueM - daughter(0,trueM)')
68 vm.addAlias(
'deltaMassDiffInvM',
'massDiffInvM - trueMassDiff')
69 vm.addAlias(
'deltaMassDiff',
'massDifference(0) - trueMassDiff')
71 vm.addAlias(
'dmID',
'extraInfo(decayModeID)')
77 deltaMassDiffCut: tuple = (-0.02, 0.02),
78 dMassCut: tuple = (-0.03, 0.03),
79 writeExtraInfo: bool =
True,
80 skipTreeFit: bool =
True
83 Add D* veto reconstruction to the path for B meson particle lists.
85 This function reconstructs D* candidates by combining D mesons (first daughter
86 of B candidates) with soft pions or pi0s from the Rest of Event, to identify
87 cases where the FEI reconstructed B -> D X but the true decay was B -> D* X.
89 For B candidates with D0 as first daughter:
90 - D*+ -> D0 pi+ (from ROE)
91 - D*0 -> D0 pi0 (from ROE)
93 For B candidates with D+ as first daughter:
94 - D*+ -> D+ pi0 (from ROE)
96 The veto builds its own ROE on private particles that wrap the B candidates, so it
97 neither uses nor creates an ROE related to the B candidates. An ROE built by the user
98 on the input lists, before or after this function, is independent of the veto.
101 particleLists (str or list): Name(s) of B meson particle list(s)
102 (e.g., 'B+:feiHadronic' or ['B+:feiHadronic', 'B0:feiHadronic'])
103 path (basf2.Path): The basf2 path to add modules to.
104 deltaMassDiffCut (tuple): Cut on deltaMassDiff (D* mass diff - true mass diff) in GeV.
105 dMassCut (tuple): Cut on D and D* mass deviation (dM) in GeV.
106 writeExtraInfo (bool): Whether to write ExtraInfo to particles.
107 skipTreeFit (bool): If True, skip the vertex TreeFit (significant speedup). The
108 deltaMassDiff will use InvM-based computation instead of fit-based, and chiProb
109 will not be available (stored as NaN). Candidates are ranked by
110 abs(deltaMassDiffInvM) instead of chiProb. Default: True
112 The following ExtraInfo fields are added to B candidates:
114 For D0 daughter (D*+ and D*0 veto):
115 - Dstp_deltaMassDiff: Delta mass difference for D*+ -> D0 pi+
116 - Dstp_chiProb: Vertex fit chi2 probability for D*+ (NaN if skipTreeFit)
117 - Dst0_deltaMassDiff: Delta mass difference for D*0 -> D0 pi0
118 - Dst0_chiProb: Vertex fit chi2 probability for D*0 (NaN if skipTreeFit)
120 For D+ daughter (D*+ veto only):
121 - Dstp_deltaMassDiff: Delta mass difference for D*+ -> D+ pi0
122 - Dstp_chiProb: Vertex fit chi2 probability for D*+ (NaN if skipTreeFit)
125 b2.B2FATAL(
"Path is required for addDstarVeto")
127 if isinstance(particleLists, str):
128 particleLists = [particleLists]
130 add_dstar_veto_aliases()
134 'daughter(0,chiProb)':
'Dstp_chiProb',
135 'daughter(0,deltaMassDiff)':
'Dstp_deltaMassDiff',
138 'daughter(0,chiProb)':
'Dst0_chiProb',
139 'daughter(0,deltaMassDiff)':
'Dst0_deltaMassDiff',
143 extra_info_veto_dstp = {
144 'daughter(0,deltaMassDiffInvM)':
'Dstp_deltaMassDiff',
146 extra_info_veto_dst0 = {
147 'daughter(0,deltaMassDiffInvM)':
'Dst0_deltaMassDiff',
150 extra_info_veto_dstp[
'daughter(0,chiProb)'] =
'Dstp_chiProb'
151 extra_info_veto_dst0[
'daughter(0,chiProb)'] =
'Dst0_chiProb'
154 from_ip =
"[[dr < 2] and [abs(dz) < 4]]"
155 p_cut =
" and [p > 0.05] and [useCMSFrame(p) < 0.5]"
156 ma.fillParticleList(
'pi+:dstVeto', from_ip + p_cut, path=path)
163 photon_cuts =
'[clusterNHits > 1.5] and thetaInCDCAcceptance and ' \
164 '[[clusterReg == 1 and E > 0.025] or [clusterReg == 2 and E > 0.025] or [clusterReg == 3 and E > 0.040]]'
165 ma.fillParticleList(
'gamma:dstVeto', photon_cuts, path=path)
167 ma.getBeamBackgroundProbability(
'gamma:dstVeto', weight=
'MC16rd', path=path)
168 ma.getFakePhotonProbability(
'gamma:dstVeto', weight=
'MC16rd', path=path)
169 ma.reconstructDecay(
'pi0:dstVetoAll -> gamma:dstVeto gamma:dstVeto',
'0.105 < InvM < 0.150', dmID=1, path=path)
170 kFit(
'pi0:dstVetoAll', 0.0,
'mass', path=path)
173 pi0Cuts =
'[useCMSFrame(p) < 0.5]'
174 pi0Cuts +=
' and [daughter(0,beamBackgroundSuppression) > 0.5] and [daughter(0,fakePhotonSuppression) > 0.1]'
175 pi0Cuts +=
' and [daughter(1,beamBackgroundSuppression) > 0.5] and [daughter(1,fakePhotonSuppression) > 0.1]'
177 ma.cutAndCopyList(
'pi0:dstVeto',
'pi0:dstVetoAll', pi0Cuts, path=path)
180 for particleList
in particleLists:
181 particle_type = particleList.split(
':')[0]
182 list_label = particleList.split(
':')[1]
if ':' in particleList
else ''
186 dstp_daughter_list = f
'{particle_type}:dstVeto_Dstp_{list_label}'
187 dst0_daughter_list = f
'{particle_type}:dstVeto_Dst0_{list_label}'
189 ma.cutAndCopyList(dstp_daughter_list, particleList,
190 '[abs(daughter(0,PDG)) == 413]', path=path)
191 ma.cutAndCopyList(dst0_daughter_list, particleList,
192 '[abs(daughter(0,PDG)) == 423]', path=path)
195 ma.variablesToExtraInfo(dstp_daughter_list, extra_info_dstp, option=0, path=path)
196 ma.variablesToExtraInfo(dst0_daughter_list, extra_info_dst0, option=0, path=path)
199 for d_pdg, d_str
in [(421,
'D0'), (411,
'Dp')]:
200 channel_name = f
'{particle_type}:dstVeto_{d_str}_{list_label}'
201 d_particle =
'D0' if d_pdg == 421
else 'D+'
203 ma.cutAndCopyList(channel_name, particleList,
204 f
'abs(daughter(0,PDG)) == {d_pdg}', path=path)
212 wrapper_list = f
'Xsd:dstVeto_{d_str}_{"Bp" if particle_type == "B+" else "B0"}_{list_label}'
213 ma.reconstructDecay(f
'{wrapper_list} -> {channel_name}',
'', allowChargeViolation=
True, path=path)
214 path.modules()[-1].set_log_level(b2.LogLevel.ERROR)
215 ma.buildRestOfEvent(wrapper_list, path=path)
219 dead_end_path = b2.Path()
221 ma.signalSideParticleFilter(wrapper_list,
'', roe_path, dead_end_path)
224 roe_condition = f
'[isInRestOfEvent == 1] or [isDescendantOfList({channel_name},1) == 1]'
225 ma.cutAndCopyList(
'pi0:dstVetoROE',
'pi0:dstVeto', roe_condition, path=roe_path)
229 dst_daughters = [
'pi+',
'pi0']
230 ma.cutAndCopyList(
'pi+:dstVetoROE',
'pi+:dstVeto', roe_condition, path=roe_path)
233 dst_daughters = [
'pi0']
236 ma.fillSignalSideParticleList(f
'{d_particle}:dstVetoSig', f
'Xsd -> [{particle_type} -> ^{d_particle}]',
242 for i, dst_daughter
in enumerate(dst_daughters):
243 if d_str ==
'Dp' or (d_str ==
'D0' and dst_daughter ==
'pi+'):
244 dst_list = f
'D*+:dstVeto_{i}'
246 dst_list = f
'D*0:dstVeto_{i}'
248 ma.reconstructDecay(f
'{dst_list} -> {d_particle}:dstVetoSig {dst_daughter}:dstVetoROE',
249 '', dmID=i, path=roe_path)
252 cut_str = f
'[{deltaMassDiffCut[0]} < deltaMassDiffInvM < {deltaMassDiffCut[1]}]'
253 cut_str += f
' and [{dMassCut[0]} < dM < {dMassCut[1]}]'
254 cut_str += f
' and [{dMassCut[0]} < daughter(0,dM) < {dMassCut[1]}]'
255 ma.applyCuts(dst_list, cut_str, path=roe_path)
258 if dst_daughter ==
'pi0':
259 ma.rankByHighest(dst_list,
'daughter(1,chiProb)', 1, path=roe_path)
260 elif dst_daughter ==
'pi+':
262 ma.rankByLowest(dst_list,
'abs(deltaMassDiffInvM)', 1, path=roe_path)
271 updateAllDaughters=
False,
272 massConstraint=[
"D*+",
"D*0",
"D+",
"D0",
"K_S0",
"pi0"],
276 if dst_daughter ==
'pi+':
277 ma.rankByHighest(dst_list,
'chiProb', 1, path=roe_path)
279 if 'D*+:dstVeto' in dst_list:
280 dstp_lists.append(dst_list)
282 dst0_lists.append(dst_list)
286 ma.copyLists(
'D*+:dstVeto', dstp_lists, writeOut=
False, path=roe_path)
287 ma.applyCuts(
'D*+:dstVeto',
'useCMSFrame(p) < 3', path=roe_path)
288 ma.rankByLowest(
'D*+:dstVeto',
'dmID', 1, path=roe_path)
291 ma.copyLists(
'D*0:dstVeto', dst0_lists, writeOut=
False, path=roe_path)
292 ma.applyCuts(
'D*0:dstVeto',
'useCMSFrame(p) < 3', path=roe_path)
293 ma.rankByLowest(
'D*0:dstVeto',
'dmID', 1, path=roe_path)
297 ma.reconstructDecay(
'Xsd:dstVetoDstp -> D*+:dstVeto',
'', allowChargeViolation=
True, path=roe_path)
298 roe_path.modules()[-1].set_log_level(b2.LogLevel.ERROR)
300 ma.variableToSignalSideExtraInfo(
'Xsd:dstVetoDstp', extra_info_veto_dstp, path=roe_path)
303 ma.reconstructDecay(
'Xsd:dstVetoDst0 -> D*0:dstVeto',
'', allowChargeViolation=
True, path=roe_path)
304 roe_path.modules()[-1].set_log_level(b2.LogLevel.ERROR)
306 ma.variableToSignalSideExtraInfo(
'Xsd:dstVetoDst0', extra_info_veto_dst0, path=roe_path)
309 path.for_each(
'RestOfEvent',
'RestOfEvents', roe_path)
313 veto_keys = list(extra_info_veto_dstp.values())
315 veto_keys += list(extra_info_veto_dst0.values())
316 ma.variablesToDaughterExtraInfo(
317 wrapper_list, f
'Xsd -> ^{particle_type}',
318 {f
'extraInfo({key})': key
for key
in veto_keys}, path=path)
328 [
'Dstp_deltaMassDiff',
'Dst0_deltaMassDiff'],
331 b2.B2INFO(f
"DstarVeto: Added D* veto reconstruction for {particleLists}")
a (simplified) python wrapper for StoreObjPtr.
_particle_lists
Particle list names to iterate over.
__init__(self, particle_lists, keys)
_keys
ExtraInfo key names to set to NaN if missing.