25.4.5. Flavor tagging¶
How can we distinguish between a \(B^0\) and a \(\bar B^0\)? This is not as simple as the case where we have a \(B^+\) vs a \(B^-\) and can just consider the charge of the particles. Yet determining this “flavor” of the \(B\) meson is crucial to look into CP violation.
This is where the Flavor Tagger comes in. Used in an analysis, where we have reconstructed a signal \(B_\text{sig}\), the Flavor Tagger looks at the ROE of the \(B_\text{sig}\) (i.e. at the \(B_\text{tag}\)) and applies machine learning techniques (also called multivariate analysis) to determine the flavor of the \(B_\text{tag}\). In this lesson we will not train this model ourselves, but rather use pre-computed weights. So where do we get them?
Luckily, such sets of weights are contained in the conditions database. That means that we only need to use a specific global tag (if you forgot what that is, consult this page again: Conditions Database Overview).
Exercise
Add a line to your previous steering file that adds the
analysis_tools_release-04-02
global tag. For this you need to call the
prepend_globaltag
method of the basf2.conditions
object (which is an
instance of the ConditionsConfiguration
class).
Solution
Simply add this to the top of your steering file:
15# set analysis global tag (needed for flavor tagging)
16b2.conditions.prepend_globaltag("analysis_tools_release-04-02")
Good! Now we have the weights and we have already created the ROE, so we can start right away!
Exercise
Call the flavorTagger.flavorTagger
function (you need to specify your
path and your particle list).
Solution
Remember to import the module:
7import flavorTagger as ft
and call the function:
77# call flavor tagging
78ft.flavorTagger(["B0"], path=main)
Exercise
Add the the
flavorTagger.flavor_tagging
variable collection to your output
variables
Solution
105b_vars += ft.flavor_tagging
Exercise
Run your steering file!
Solution
This is the full steering file at this point:
1#!/usr/bin/env python3
2
3import sys
4import basf2 as b2
5import modularAnalysis as ma
6import stdV0s
7import flavorTagger as ft
8from variables import variables as vm # shorthand for VariableManager
9import variables.collections as vc
10import variables.utils as vu
11
12# get input file number from the command line
13filenumber = sys.argv[1]
14
15# set analysis global tag (needed for flavor tagging)
16b2.conditions.prepend_globaltag("analysis_tools_release-04-02")
17
18# create path
19main = b2.Path()
20
21# load input data from mdst/udst file
22ma.inputMdstList(
23 environmentType="default",
24 filelist=[b2.find_file(f"starterkit/2021/1111540100_eph3_BGx0_{filenumber}.root", "examples")],
25 path=main,
26)
27
28# fill final state particle lists
29ma.fillParticleList(
30 "e+:uncorrected",
31 "electronID > 0.1 and dr < 0.5 and abs(dz) < 2 and thetaInCDCAcceptance",
32 path=main,
33)
34stdV0s.stdKshorts(path=main)
35
36# apply Bremsstrahlung correction to electrons
37vm.addAlias(
38 "goodFWDGamma", "passesCut(clusterReg == 1 and clusterE > 0.075)"
39)
40vm.addAlias(
41 "goodBRLGamma", "passesCut(clusterReg == 2 and clusterE > 0.05)"
42)
43vm.addAlias(
44 "goodBWDGamma", "passesCut(clusterReg == 3 and clusterE > 0.1)"
45)
46vm.addAlias(
47 "goodGamma", "passesCut(goodFWDGamma or goodBRLGamma or goodBWDGamma)"
48)
49ma.fillParticleList("gamma:brems", "goodGamma", path=main)
50ma.correctBrems("e+:corrected", "e+:uncorrected", "gamma:brems", path=main)
51vm.addAlias("isBremsCorrected", "extraInfo(bremsCorrected)")
52
53# combine final state particles to form composite particles
54ma.reconstructDecay(
55 "J/psi:ee -> e+:corrected e-:corrected ?addbrems",
56 cut="dM < 0.11",
57 path=main,
58)
59
60# combine J/psi and KS candidates to form B0 candidates
61ma.reconstructDecay(
62 "B0 -> J/psi:ee K_S0:merged",
63 cut="Mbc > 5.2 and abs(deltaE) < 0.15",
64 path=main,
65)
66
67# match reconstructed with MC particles
68ma.matchMCTruth("B0", path=main)
69
70# build the rest of the event
71ma.buildRestOfEvent("B0", fillWithMostLikely=True, path=main)
72track_based_cuts = "thetaInCDCAcceptance and pt > 0.075 and dr < 5 and abs(dz) < 10"
73ecl_based_cuts = "thetaInCDCAcceptance and E > 0.05"
74roe_mask = ("my_mask", track_based_cuts, ecl_based_cuts)
75ma.appendROEMasks("B0", [roe_mask], path=main)
76
77# call flavor tagging
78ft.flavorTagger(["B0"], path=main)
79
80# perform best candidate selection
81b2.set_random_seed("Belle II StarterKit")
82ma.rankByHighest("B0", variable="random", numBest=1, path=main)
83
84# Create list of variables to save into the output file
85b_vars = []
86
87standard_vars = vc.kinematics + vc.mc_kinematics + vc.mc_truth
88b_vars += vc.deltae_mbc
89b_vars += standard_vars
90
91# ROE variables
92roe_kinematics = ["roeE()", "roeM()", "roeP()", "roeMbc()", "roeDeltae()"]
93roe_multiplicities = [
94 "nROE_Charged()",
95 "nROE_Photons()",
96 "nROE_NeutralHadrons()",
97]
98b_vars += roe_kinematics + roe_multiplicities
99# Let's also add a version of the ROE variables that includes the mask:
100for roe_variable in roe_kinematics + roe_multiplicities:
101 # e.g. instead of 'roeE()' (no mask) we want 'roeE(my_mask)'
102 roe_variable_with_mask = roe_variable.replace("()", "(my_mask)")
103 b_vars.append(roe_variable_with_mask)
104
105b_vars += ft.flavor_tagging
106
107# Variables for final states (electrons, positrons, pions)
108fs_vars = vc.pid + vc.track + vc.track_hits + standard_vars
109b_vars += vu.create_aliases_for_selected(
110 fs_vars + ["isBremsCorrected"],
111 "B0 -> [J/psi -> ^e+ ^e-] K_S0",
112 prefix=["ep", "em"],
113)
114b_vars += vu.create_aliases_for_selected(
115 fs_vars, "B0 -> J/psi [K_S0 -> ^pi+ ^pi-]", prefix=["pip", "pim"]
116)
117# Variables for J/Psi, KS
118jpsi_ks_vars = vc.inv_mass + standard_vars
119b_vars += vu.create_aliases_for_selected(jpsi_ks_vars, "B0 -> ^J/psi ^K_S0")
120# Add the J/Psi mass calculated with uncorrected electrons:
121vm.addAlias(
122 "Jpsi_M_uncorrected", "daughter(0, daughterCombination(M,0:0,1:0))"
123)
124b_vars += ["Jpsi_M_uncorrected"]
125# Also add kinematic variables boosted to the center of mass frame (CMS)
126# for all particles
127cmskinematics = vu.create_aliases(
128 vc.kinematics, "useCMSFrame({variable})", "CMS"
129)
130b_vars += vu.create_aliases_for_selected(
131 cmskinematics, "^B0 -> [^J/psi -> ^e+ ^e-] [^K_S0 -> ^pi+ ^pi-]"
132)
133
134vm.addAlias(
135 "withBremsCorrection",
136 "passesCut(passesCut(ep_isBremsCorrected == 1) or passesCut(em_isBremsCorrected == 1))",
137)
138b_vars += ["withBremsCorrection"]
139
140# Save variables to an output file (ntuple)
141ma.variablesToNtuple(
142 "B0",
143 variables=b_vars,
144 filename="Bd2JpsiKS.root",
145 treename="tree",
146 path=main,
147)
148
149# Start the event loop (actually start processing things)
150b2.process(main)
151
152# print out the summary
153print(b2.statistics)
Good! Now let’s talk about the output of the flavor tagger. This is the value \(q\cdot r\), where \(q=-1\) corresponds to a \(\bar B^0\) and \(q=+1\) to \(B^0\). \(r\) is called the dilution factor. It’s 0 if the algorighm can’t decide between both options for \(q\) and 1 if the algorithm is certain about it’s decision.
The variable FBDT_qrCombined
is the \(q\cdot r\) result of one of the
models of the Flavor Tagger (a fast boosted decision tree).
It can also be NaN
to signal that not a single charged
track in the ROE was found, so that the algorithm can’t work.
Note
In releases before release-05, a value of \(\pm 2\) was used instead of
NaN
.
Part of the variables you just added was also qrMC
for the “true” (MC level) flavor of the \(B_\text{tag}\).
It can take the numbers
\(\pm 1\), as well as 0 (no flavor defined in the MC) and \(\pm 2\)
(some problems with MC matching in the ROE).
That means that we can check how well our flavor tagger performed by comparing
it to FBDT_qrCombined
!
Exercise (optional)
Plot a histogram of FBDT_qrCombined
and qrMC
.
Exercise
Only consider candidates with clearly defined MC level flavor tag.
Compare the output of qrMC
to that of FBDT_qrCombined
.
Hint
For clearly defined MC flavor tag, you only have to distinguish between
qrMC == 0
and qrMC == 1
. For these two cases you can then plot
the distribution of FBDT_qrCombined
.
Solution
1##########################################################################
2# basf2 (Belle II Analysis Software Framework) #
3# Author: The Belle II Collaboration #
4# #
5# See git log for contributors and copyright holders. #
6# This file is licensed under LGPL-3.0, see LICENSE.md. #
7##########################################################################
8import matplotlib.pyplot as plt
9import root_pandas
10
11
12plt.style.use("belle2")
13
14df = root_pandas.read_root("Bd2JpsiKS.root")
15
16fig, ax = plt.subplots()
17ax.hist(
18 df.query("qrMC == -1.")["FBDT_qrCombined"],
19 histtype="step",
20 linewidth=1.5,
21 label=r"True $\bar B^0$",
22 bins=30,
23)
24ax.hist(
25 df.query("qrMC == 1.")["FBDT_qrCombined"],
26 histtype="step",
27 linewidth=1.5,
28 label=r"True $B^0$",
29 bins=30,
30)
31ax.set_xlabel("FBDT_qrCombined")
32ax.legend()
33fig.savefig("flavor_tags.svg")
You can clearly see that the flavor tagger is by no means perfect, but definitely allows to do better than just guessing!
Key points
The flavor tagger is used to discriminate between \(B_\text{tag}^0\) and \(\bar B_\text{tag}^0\)
The output is of the form \(\pm 1\) times the confidence between 0 and 1.
Stuck? We can help!
If you get stuck or have any questions to the online book material, the #starterkit-workshop channel in our chat is full of nice people who will provide fast help.
Refer to Collaborative Tools. for other places to get help if you have specific or detailed questions about your own analysis.
Improving things!
If you know how to do it, we recommend you to report bugs and other requests
with JIRA. Make sure to use the
documentation-training
component of the Belle II Software
project.
If you just want to give very quick feedback, use the last box “Quick feedback”.
Please make sure to be as precise as possible to make it easier for us to fix things! So for example:
typos (where?)
missing bits of information (what?)
bugs (what did you do? what goes wrong?)
too hard exercises (which one?)
etc.
If you are familiar with git and want to create your first pull request for the software, take a look at How to contribute. We’d be happy to have you on the team!
Quick feedback!
Authors of this lesson
Kilian Lieret