The Rest of Event (ROE)
Contents
3.4.3. The Rest of Event (ROE)#
After the reconstruction of the signal particle list it is very useful
to look into the the particles that are not associated to the signal particle list.
In basf2
these particles are called “Rest of Event” and this is the main topic of the chapter.
The Rest of Event (ROE) can contain a lot of information: in case of B-physics, the ROE of one B-meson includes particles from the partner B-meson and in case of charm and tau analysis, the ROE of the lepton has the partner lepton.
Exercise
Find the documentation of the ROE module. What are its use cases for tagged and untagged analyses?
Hint
It’s included in the “Advanced Topics” section of the analysis module.
Solution
Documentation is here: Rest Of Event. There is quite a bit of content. For now, make sure to take a look at the Overview and the “Basic usage” sections.
Basics#
In this chapter we will continue our work on the steering file from the last lesson. Remember that you have reconstructed a \(B^0\) particle list. We now want to reconstruct the Rest of Event of the \(B^0\).
Exercise
Look up how to create a Rest Of Event for your particle list
(it’s a single line of code).
Add the fillWithMostLikely=True
option.
Hint
You have already found the Rest Of Event chapter in the last exercise. Take a look at the basic usage example.
Solution
# build the rest of the event [S10|S30|S40]
ma.buildRestOfEvent("B0", fillWithMostLikely=True, path=main) # [E10]
That’s it, the ROE has been reconstructed!
Behind these python curtains, a RestOfEvent
object is created for each particle in the \(B^0\)
particle list and it includes all other charged or neutral particles, that have not been
associated to the corresponding signal candidate. By default, the charged particles are assumed to be pions,
and the neutral particles use the photon or \(K_L^0\) hypothesis.
ROE variables#
In principle, one can already try to use some of the Rest of Event variables.
Exercise
Find documentation for the Rest Of Event variables.
Hint
Use the search feature in the basf2 documentation, or use the offline help by typing b2help-variables
in your bash terminal (for example b2help-variables | grep -i roe
).
Solution
The Rest Of Event variables are in Rest Of Event section of VariableManager
page,
which starts from bssMassDifference
variable.
Among the most universal and useful are ROE invariant mass roeM
or ROE energy roeE
. Also, one can call
nROE_Charged
or nROE_Photons
to know how many charged particles or
photons entered the ROE.
Remember that we were collecting all variables in the b_vars
list.
Let’s include the following lines to have a useful selection of them:
# ROE variables [S20|S50]
roe_kinematics = ["roeE()", "roeM()", "roeP()", "roeMbc()", "roeDeltae()"]
roe_multiplicities = [
"nROE_Charged()",
"nROE_Photons()",
"nROE_NeutralHadrons()",
]
b_vars += roe_kinematics + roe_multiplicities # [E20]
Exercise
Run your steering file and check that it completes without error.
In principle we could already start to do an analysis. However, the ROE variables that we have just defined are not quite useful yet: we first need to “clean up” the ROE. For this, we define ROE masks.
ROE masks#
The main philosophy of the ROE is to include every particle in the event, that has not been associated to the signal candidate. That is why a typical ROE contains not only the partner particle (e.g. the tag or signal B), but also all other particles, like hadron split-off particles, \(\delta\)-rays, unused radiative photons, beam-induced background particles or products of kaon or pion decays. It is up to the analyst to decide what particles actually matter for the analysis. This is called “cleaning up” the ROE. For this procedure, ROE masks are used.
ROE masks are just sets of selection cuts to be applied on the ROE particles.
For our example, let’s start by defining the following selection cut strings:
# build the rest of the event [S10|S30|S40]
ma.buildRestOfEvent("B0", fillWithMostLikely=True, path=main) # [E10]
track_based_cuts = "thetaInCDCAcceptance and pt > 0.075 and dr < 5 and abs(dz) < 10"
ecl_based_cuts = "thetaInCDCAcceptance and E > 0.05" # [E30]
Here we created different cuts for charged particles, like electrons or charged pions, and for photons, because of different methods of measurement used to detect these particles.
Tip
These are example cuts, please use official guidelines from Charged or Neutral Performance groups to set up your own selection in a “real” analysis.
Exercise
Create a ROE mask using the charged_cuts
and photon_cuts
strings with the
appendROEMask
or appendROEMasks
function.
Hint
A mask is defined as a tuple with three values. Use appendROEMasks
to
“activate” it.
Solution
# build the rest of the event [S10|S30|S40]
ma.buildRestOfEvent("B0", fillWithMostLikely=True, path=main) # [E10]
track_based_cuts = "thetaInCDCAcceptance and pt > 0.075 and dr < 5 and abs(dz) < 10"
ecl_based_cuts = "thetaInCDCAcceptance and E > 0.05" # [E30]
roe_mask = ("my_mask", track_based_cuts, ecl_based_cuts)
ma.appendROEMasks("B0", [roe_mask], path=main) # [E40]
Now we have created a mask with a name my_mask
, that will only allow track-based
particles that pass selection cuts track_based_cuts
and ECL-based particles, that pass
ecl_based_cuts
.
The analyst can create as many ROE masks as needed and use them in different ROE-dependent
algorithms or ROE variables.
For ROE variables, the mask is specified as an argument, like roeM(my_mask)
or roeE(my_mask)
.
In the last section, we defined two lists of ROE variables (roe_kinematics
and roe_multiplicities
).
Now we want to have
the same variables but with the my_mask
argument. Since we’re lazy, we use a python
loop to insert this argument.
Exercise
Write a for
loop that runs over roe_kinematics + roe_multiplicities
and
replaces the ()
of each variable with (my_mask)
. Add these new
variables to the b_vars
list.
Hint
The variables are nothing more than a string, which has a replace
method:
>>> "roeE()".replace("()", "(my_mask)")
'roeE(my_mask)'
Hint
Fill the missing bit of code:
for roe_variable in roe_kinematics + roe_multiplicities:
roe_variable_with_mask = your_code_here
b_vars.append(roe_variable_with_mask)
Solution
# ROE variables [S20|S50]
roe_kinematics = ["roeE()", "roeM()", "roeP()", "roeMbc()", "roeDeltae()"]
roe_multiplicities = [
"nROE_Charged()",
"nROE_Photons()",
"nROE_NeutralHadrons()",
]
b_vars += roe_kinematics + roe_multiplicities # [E20]
# Let's also add a version of the ROE variables that includes the mask:
for roe_variable in roe_kinematics + roe_multiplicities:
# e.g. instead of 'roeE()' (no mask) we want 'roeE(my_mask)'
roe_variable_with_mask = roe_variable.replace("()", "(my_mask)")
b_vars.append(roe_variable_with_mask) # [E50]
Tip
There are also KLM-based hadrons in ROE, like \(K_L^0\) or neutrons, but they are
not participating in ROE 4-momentum computation, because of various temporary
difficulties in KLM reconstruction. Nevertheless, one can count them using
nROE_NeutralHadrons
variable.
Exercise
Your steering file is now complete. Run it!
Solution
Your steering file should look like this:
#!/usr/bin/env python3
import sys
import basf2 as b2
import modularAnalysis as ma
import stdV0s
import variables.collections as vc
import variables.utils as vu
# get input file number from the command line
filenumber = sys.argv[1]
# create path
main = b2.Path()
# load input data from mdst/udst file
ma.inputMdstList(
filelist=[b2.find_file(f"starterkit/2021/1111540100_eph3_BGx0_{filenumber}.root", "examples")],
path=main,
)
# fill final state particle lists
ma.fillParticleList(
"e+:uncorrected",
"electronID > 0.1 and dr < 0.5 and abs(dz) < 2 and thetaInCDCAcceptance",
path=main,
)
stdV0s.stdKshorts(path=main)
# combine final state particles to form composite particles
ma.reconstructDecay(
"J/psi:ee -> e+:uncorrected e-:uncorrected", cut="abs(dM) < 0.11", path=main
)
# combine J/psi and KS candidates to form B0 candidates
ma.reconstructDecay(
"B0 -> J/psi:ee K_S0:merged",
cut="Mbc > 5.2 and abs(deltaE) < 0.15",
path=main,
)
# match reconstructed with MC particles
ma.matchMCTruth("B0", path=main)
# build the rest of the event [S10|S30|S40]
ma.buildRestOfEvent("B0", fillWithMostLikely=True, path=main) # [E10]
track_based_cuts = "thetaInCDCAcceptance and pt > 0.075 and dr < 5 and abs(dz) < 10"
ecl_based_cuts = "thetaInCDCAcceptance and E > 0.05" # [E30]
roe_mask = ("my_mask", track_based_cuts, ecl_based_cuts)
ma.appendROEMasks("B0", [roe_mask], path=main) # [E40]
# Create list of variables to save into the output file
b_vars = []
standard_vars = vc.kinematics + vc.mc_kinematics + vc.mc_truth
b_vars += vc.deltae_mbc
b_vars += standard_vars
# ROE variables [S20|S50]
roe_kinematics = ["roeE()", "roeM()", "roeP()", "roeMbc()", "roeDeltae()"]
roe_multiplicities = [
"nROE_Charged()",
"nROE_Photons()",
"nROE_NeutralHadrons()",
]
b_vars += roe_kinematics + roe_multiplicities # [E20]
# Let's also add a version of the ROE variables that includes the mask:
for roe_variable in roe_kinematics + roe_multiplicities:
# e.g. instead of 'roeE()' (no mask) we want 'roeE(my_mask)'
roe_variable_with_mask = roe_variable.replace("()", "(my_mask)")
b_vars.append(roe_variable_with_mask) # [E50]
# Variables for final states (electrons, positrons, pions)
fs_vars = vc.pid + vc.track + vc.track_hits + standard_vars
b_vars += vu.create_aliases_for_selected(
fs_vars,
"B0 -> [J/psi -> ^e+ ^e-] [K_S0 -> ^pi+ ^pi-]",
prefix=["ep", "em", "pip", "pim"],
)
# Variables for J/Psi, KS
jpsi_ks_vars = vc.inv_mass + standard_vars
b_vars += vu.create_aliases_for_selected(jpsi_ks_vars, "B0 -> ^J/psi ^K_S0")
# Also add kinematic variables boosted to the center of mass frame (CMS)
# for all particles
cmskinematics = vu.create_aliases(
vc.kinematics, "useCMSFrame({variable})", "CMS"
)
b_vars += vu.create_aliases_for_selected(
cmskinematics, "^B0 -> [^J/psi -> ^e+ ^e-] [^K_S0 -> ^pi+ ^pi-]"
)
# Save variables to an output file (ntuple)
ma.variablesToNtuple(
"B0",
variables=b_vars,
filename="Bd2JpsiKS.root",
treename="tree",
path=main,
)
# Start the event loop (actually start processing things)
b2.process(main)
# print out the summary
print(b2.statistics)
Quick plots#
Exercise
Plot ROE invariant mass and number of charged particles in ROE distributions and compare masked and unmasked ROE. Column names in the ntuple:
Unmasked ROE |
|
|
ROE with |
|
|
Hint: Plotting side by side
One can use matplotlib
functions to plot several histograms on one figure side-by-side.
Documentation is on this page, but you also saw some examples in your
python training.
Hint: Outliers
Some of the distributions contain outliers, which need to be rejected in order to
get meaningful plots (this means to manually set the plotting range).
Proposed ranges: roeM
from 0 to 10 and nROE_Charged
from 0 to 15.
Hint: Styling
Another hint is that comparison plots look better if they are a bit transparent, which can be achieved by supplying
alpha=0.6
argument to the plotting functions. Alternatively you might look into thehisttype
argument to only show the outlines of the distributions.As we are plotting many distributions on one figure legends and axis titles are important
Hint: Fill in the blanks
import uproot
import matplotlib.pyplot as plt
plt.style.use('belle2')
var_list = ['roeM__bo__bc', 'roeM__bomy_mask__bc', 'nROE_Charged__bo__bc', 'nROE_Charged__bomy_mask__bc']
df = uproot.open('Bd2JpsiKS.root:tree').arrays(var_list, library='pd')
m_bins = 50
m_range = (0, 10)
fig, ax = plt.subplots(1,2, figsize=(15, 7))
# Left subplot of ROE mass:
ax[0].hist(...)
ax[0].hist(...)
ax[0].set_xlim(m_range)
ax[0].set_xlabel('ROE mass [GeV/$c^2$]')
# Right subplot of number of charged ROE particles:
m_bins = 15
m_range = (0, 15)
ax[1].hist(...)
ax[1].hist(...)
ax[1].set_xlim(m_range)
ax[1].set_xlabel('# of charged ROE particles')
ax[1].legend()
fig.tight_layout()
fig.savefig('roe_mask_comparison.svg')
Solution
import uproot
import matplotlib.pyplot as plt
plt.style.use('belle2')
var_list = ['roeM__bo__bc', 'roeM__bomy_mask__bc', 'nROE_Charged__bo__bc', 'nROE_Charged__bomy_mask__bc']
df = uproot.open('Bd2JpsiKS.root:tree').arrays(var_list, library='pd')
m_bins = 50
m_range = (0, 10)
fig, ax = plt.subplots(1,2, figsize=(15, 7))
# Left subplot of ROE mass:
ax[0].hist(df['roeM__bo__bc'], label='No mask',
bins = m_bins, range=m_range, alpha=0.6)
ax[0].hist(df['roeM__bomy_mask__bc'], label='"my_mask" applied',
bins = m_bins, range=m_range, alpha=0.6)
ax[0].set_xlim(m_range)
ax[0].set_xlabel('ROE mass [GeV/$c^2$]')
# Right subplot of number of charged ROE particles:
m_bins = 15
m_range = (0, 15)
ax[1].hist(df['nROE_Charged__bo__bc'], label='No mask',
bins = m_bins, range=m_range, alpha=0.6)
ax[1].hist(df['nROE_Charged__bomy_mask__bc'], label='"my_mask" applied',
bins = m_bins, range=m_range, alpha=0.6)
ax[1].set_xlim(m_range)
ax[1].set_xlabel('# of charged ROE particles')
ax[1].legend()
fig.tight_layout()
fig.savefig('roe_mask_comparison.svg')
The resulting plot should look like the figure below.
Fig. 3.23 shows a comparison of roeM
and nROE_Charged
distributions
for ROE with mask my_mask
case and ROE with no mask applied.
Strange variable names
You might wonder where these strange variable names came from. This is because
it is tried to avoid branch names (columns of your output ROOT file) that contain
special characters (parentheses, spaces and so on).
For example, every (
is replaced by _bo
for “bracket open”. What does
_bc
stand for?
Exercise
Take another look at Fig. 3.23 and describe what you see. Can you explain the differences between the masked and unmasked variables?
Solution
The invariant mass distribution for masked ROE is much narrower and its mean is a little bit below nominal \(B^0\) mass, contrary to the unmasked ROE distribution. This is expected, because a generic \(B^0\) decay may produce particles, that are not accounted in ROE mass computation, like \(K_L^0\) or neutrinos.
The distribution of the number of charged particles for masked ROE has much more prominent peaks at 4 and 6 particles than its unmasked version, which corresponds to the fact that a correctly reconstructed \(B^0\) will have an even number of charged daughter particles.
This means that even a simple ROE mask like my_mask
does a really good job of cleaning up the particles,
which are not associated to the partner \(B^0\).
This concludes the Rest of Event setup as a middle stage algorithm to run Continuum Suppression (CS), Flavor tagging or tag Vertex fitting.
Key points
The ROE of a selection is build with
buildRestOfEvent
ROE masks are added with
appendROEMask
orappendROEMasks
. Use them to clean up beam-induced or other background particles.For many analyses ROE is used as middleware to get tag vertex fit, continuum suppression or flavor tag.
Usage of ROE without a mask is not recommended.
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 GitLab.
Make sure to use the documentation-training
label of the basf2
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 merge 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
Sviatoslav Bilokin, Kilian Lieret