Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

3.2 Beyond the Basics: Tracking and Vertexing

Original authors: Emmanuel Francois Perez, Clement Helsens

Getting the FCCAnalyses

The FCCAnalyses framework is provided already compiled in the Key4hep stack, however once the stack is released this version of the FCCAnalyses can’t be updated anymore. If one wants to benefit from the changes in the upstream FCCAnalyses, they need to compile the latest version of it. The compilation process is quite streamlined and requires only few steps.

As a first step create a fork of the FCCAnalyses project on GitHub. More details about FCC Software development workflow can be found in Development workflow.

After a short while the forking should be done and you can download the FCCAnalyses to your machine. Go inside the area that you have setup for this tutorial (for example mkdir vtx-tutorial) and clone your FCCAnalyses repository:

cd vtx-tutorial
git clone --branch pre-edm4hep1 https://github.com/<your-github-handle>/FCCAnalyses.git

Go inside the FCCAnalyses directory and run the building of the FCCAnalyses with:

cd FCCAnalyses
source ./setup.sh
fccanalysis build

After the successful building of the FCCAnalyses you can return back to the main directory:

cd ..

To check if you are using the locally build version of FCCAnalyses use

which fccanalysis

It should lead to executable in your local install directory.

Adding analyzers to the FCCAnalyses

In order to extend the number of the available FCCAnalyses analyzers (C++ functions or functors used in the .Define() and .Filter methods of the ROOT RDataFrame) we can either provide them in the additional C++ header file(s) or edit the source code of the FCCAnalyses analyzers library.

For a quick addition of a few analyzers the first method is recommended, however since in this method the additional C++ header file(s) need to be JIT compiled every time the analysis is run the start-up time of the analysis can become very long.

To register the C++ header file with the additional analyzers in your analysis use includePaths attribute of the analysis script:

includePaths = ["analyzers.h"]

The second method involves directly editing the source code located in the analyzers/dataframe directory of FCCAnalyses and afterwards running fccanalysis build command.

Exercise

Let’s add a dummy analyzer which extracts xx component of the particle momentum from the collection of reconstructed particles.

Reconstruction of the primary vertex and of primary tracks

Let’s start by running primary vertex reconstruction on a few events of one test file:

wget https://fccsw.web.cern.ch/fccsw/tutorials/vtx-tutorial/analysis_primary_vertex.py
fccanalysis run analysis_primary_vertex.py --test --nevents 1000 --output primary_Zuds.root

Note: with the option --test, we process the file that is hard-coded in the attribute testFile inside of the analysis_primary_vertex.py script. In this case, it is a file of Z→qqˉZ \rightarrow q \bar{q} with q=u,d,sq=u,d,s.

The resulting ntuple primary_Zuds.root contains the MC event vertex MC_PrimaryVertex, and the reconstructed primary vertex PrimaryVertex.

To produce example plots, run the ROOT plotting macro plot_primary_vertex.C.

wget https://fccsw.web.cern.ch/fccsw/tutorials/vtx-tutorial/plot_primary_vertex.C

This produces normalized χ2\chi^2 of the primary vertex fit, the resolutions in xx, yy, zz, and the pulls of the fitted vertex position.

Exercise 1

Add the number of primary and secondary tracks into the ntuple using the function ReconstructedParticle2Track::getTK_n(ROOT::VecOps::RVec<edm4hep::TrackState> x), see the definition here.

Exercise 2

Add the total pTp_{T} that is carried by the primary tracks. This requires some simple analysis code to be written (added to our analyzers.h file). Then, the analysis script needs to be updated to include includePaths = ["analyzers.h"] attribute.

Exercise 3

Compare these distributions in Z→udsZ \rightarrow uds events and in Z→bbˉZ \rightarrow b\bar{b} events.

Exercise 4

Exercise to go beyond

The reconstruction of all secondary vertices following the LCFI+ algorithm has been implemented in FCCAnalyses by Kunal Gautam and Armin Ilg in the pull request PR#206. It contains example analysis script examples/FCCee/vertex_lcfiplus/analysis_SV.py which:

Reconstruction of displaced vertices in an exclusive decay chain

We consider here Z→bbˉZ \rightarrow b \bar{b} events. When a BsB_s is produced, it is forced to decay into J/ΨΦJ/\Psi \Phi with J/Ψ→μ+μ−J/\Psi \rightarrow \mu^+\mu^- and Φ→K+K−\Phi \rightarrow K^+ K^-. We want to reconstruct the BsB_s decay vertex and determine the resolution on the position of this vertex. Here, we use the MC-matching information to figure out which are the reconstructed tracks that are matched to the BsB_s decay products, and we fit these tracks to a common vertex. That means, we “seed” the vertex reconstruction using the MC-truth information. Let’s run the following:

wget https://fccsw.web.cern.ch/fccsw/tutorials/vtx-tutorial/analysis_Bs2JpsiPhi_MCseeded.py
fccanalysis run analysis_Bs2JpsiPhi_MCseeded.py --test --nevents 1000 --output Bs2JpsiPhi_MCseeded.root

The ntuple Bs2JpsiPhi_MCseeded.root contains the MC decay vertex of the BsB_s, and the reconstructed decay vertex.

When you run the root macro plot_Bs2JsiPhi.C it will produce various plots showing the vertex χ2\chi^2, the vertex resolutions and the pulls of the vertex fit

wget https://fccsw.web.cern.ch/fccsw/tutorials/vtx-tutorial/plots_Bs2JsiPhi.C
root -b -q "plot_Bs2JsiPhi.C()"

Analysis of τ→3μ\tau \rightarrow 3 \mu

The analysis showcased in Reconstruction of displaced vertices in an exclusive decay chain is used as a stepping stone for the following set of exercises.

Exercise 1

Start from the analysis_Bs2JpsiPhi_MCseeded.py analysis script and adapt it to the decay of τ→3μ\tau \rightarrow 3 \mu.

cp analysis_Bs2JpsiPhi_MCseeded.py analysis_Tau3Mu_MCseeded.py

# or

wget https://fccsw.web.cern.ch/fccsw/tutorials/vtx-tutorial/analysis_Bs2JpsiPhi_MCseeded.py -O analysis_Tau3Mu_MCseeded.py

change the testFile to:

testFile = "/eos/experiment/fcc/ee/generation/DelphesEvents/spring2021/IDEA/p8_noBES_ee_Ztautau_ecm91_EvtGen_TauMinus2MuMuMu/events_189205650.root"

Modify the call to MCParticle::get_indices to retrieve properly the indices of the decay of interest. Subsequently rename the Bs2MuMuKK_indices into the name you chose --- and, to have meaningful variable names, rename Bsxxx into Tauxxx.

Exercise 2

Add the reconstructed τ\tau mass to the ntuple (you will need to write new code). Check that the mass resolution is improved when it is determined from the track momenta at the tau decay vertex, compared to a blunt 3-muon mass determined from the default track momenta (taken at the distance of closest approach).

Suggested implementation: analyzers with the following signatures need to be added to your analyzers header file analyzers_Tau3Mu.h (created by copying the analyzers.h from previous section):

double tau3mu_vertex_mass(const VertexingUtils::FCCAnalysesVertex& vertex);
double tau3mu_raw_mass(const ROOT::VecOps::RVec<edm4hep::ReconstructedParticleData>& legs);

Exercise 3

So far, everything was done using “Monte-Carlo seeding”, which gives the resolutions that we expect, in the absence of possible combinatoric issues. The next step is to write a new analysis script which starts from th reconstructed muons.

cp analysis_Tau3Mu_MCseeded.py  analysis_Tau3Mu.py

and keep only the skeleton of the analysers() method such that you delete all of the defines and aliases, the result should look something like this:

class RDFanalysis():
    def analysers(df):
        df2 = (
            df
        )
        return df2

Clear out also the branchList, and insert new defines into the analysers() method:

    # Use the "AllMuons" collection, which contains also non-isolated muons (in
    # contrast to the "Muons" collection)
    #
    # Actually, "Muon" or ("AllMuon") just contain pointers (indices) to the
    # RecoParticle collections, hence one needs to first retrieve the
    # RecoParticles corresponding to these muons (for more detail about the
    # subset collections, see:
    # https://github.com/HEP-FCC/FCCAnalyses/tree/master/examples/basics)
    .Alias("Muon0", "AllMuon#0.index")
    .Define("muons", "ReconstructedParticle::get(Muon0, ReconstructedParticles)")
    .Define("n_muons", "ReconstructedParticle::get_n(muons)")

We now want to write a method that builds muon triplets --- actually, since the MC files produced for this tutorial only forced the decay of the τ−\tau^-, we are interested in triplets with total charge equal to -1.

Sub-exercise 1: create a function in your analyzers_Tau3Mu.h that builds such triplet.

Sub-exercise 2: create a function in your analyzers_Tau3Mu.h to retrieve all tau candidates, and their corresponding tau mass.

Exercise 4

We now want to look at the background. Create a new script file by copying your analysis_Tau3Mu.py into a new file:

cp analysis_Tau3Mu.py analysis_Tau3Mu_stage1.py

Note: The _stage1 suffix indicates that it is a first step in the chain of analysis scripts.

The main background is expected to come from τ→3πν\tau \rightarrow 3 \pi \nu decays, when the charged pions are misidentified as muons. But there is no “fakes” in Delphes: all “Muon” objects that you have in the EDM4hep file do originate from genuine muons (which may, of course, come from a hadron decay). To alleviate this limitation, we first select the ReconstructedParticles that are matched to a stable, charged hadron. Edit your analysis_Tau3Mu_stage1.py script file and add the following lines right before the .Define("n_muons", ... ):

    # -----------------------------------------
    # Add fake muons from pi -> mu
    # This selects the charged hadrons:
    .Alias("MCRecoAssociations0", "MCRecoAssociations#0.index")
    .Alias("MCRecoAssociations1", "MCRecoAssociations#1.index")
    .Define("ChargedHadrons",
            "ReconstructedParticle2MC::selRP_ChargedHadrons(MCRecoAssociations0, MCRecoAssociations1, ReconstructedParticles, Particle)")

(As mentioned earlier, the matching between reconstructed particles and Monte Carlo particles requires 4 collections. See here for more detail).

and further select the ones that are above 2 GeV --- since only particles above 2 GeV will make it to the muon detector:

    # Only the ones with  p > 2 GeV could be selected as muons:
    .Define("ChargedHadrons_pgt2",
            "ReconstructedParticle::sel_p(2.)(ChargedHadrons)")

Now we want to apply a “flat” fake rate, i.e. accept a random fraction of the above particles as muons.

Exercise itself: create an analyzer (functor) in your analyzers_Tau3Mu.h that does that.

The files analysis_Tau3Mu_stage1.py and analyzers_Tau3Mu.h with all the changes discussed above can be downloaded here and here.

Exercise 5

We now have a simple analysis that can be used to process the signal and background samples, and plot the mass of the τ→3μ\tau \rightarrow 3\mu candidates. We can now process the full statistics. In order for you to have access to the dataset metadata provided in the /afs/cern.ch/work/f/fccsw/public/FCCDicts/, you need to have CERN computing account and have the AFS Workspaces service activated.

All samples that have been centrally produced can be found on this web page. We use spring2021 samples (in campaign), and the files made with IDEA detector concept. If you enter TauMinus2MuMuMu and TauMinus2PiPiPinus in the search field, you will see the datasets produced for the signal and the τ→3πν\tau \rightarrow 3\pi \nu background. The first column shows the dataset names, in this case p8_noBES_ee_Ztautau_ecm91_EvtGen_TauMinus2MuMuMu and p8_noBES_ee_Ztautau_ecm91_EvtGen_TauMinus2PiPiPinu.

To run fccanalysis over these datasets (and not anymore over one test file), the list of datasets to be processed should be inserted into your analysis analysis_Tau3Mu_stage1.py:

import ROOT

processList = {
    'p8_noBES_ee_Ztautau_ecm91_EvtGen_TauMinus2MuMuMu': {},
    'p8_noBES_ee_Ztautau_ecm91_EvtGen_TauMinus2PiPiPinu': {}
}

as well as information where to look for the dataset metadata information

# Mandatory: Production tag when running over centrally produced events, this
# points to the yaml files for getting the dataset statistics
prodTag = "FCCee/spring2021/IDEA/"

and we tell fccanalysis to put all files into the Tau3Mu directory:

# Optional: output directory, default is local running directory
outputDir = "Tau3Mu"

This produces flat ntuples:

fccanalysis run analysis_Tau3Mu_stage1.py

After a few minutes, you will see two ntuples, one for the signal, the other for the background, in the Tau3Mu directory.

Now that you have ntuples with the variables you need for your analysis, you may analyze them using the tools you prefer. As explained here, you can also use fccanalysis to produce histograms of some variables, with some sets of cuts, and to plot them. The normalisation is then taken care of by fccanalyses, thanks to a JSON file (FCCee_procDict_spring2021_IDEA.json) which contains the cross-section of the MC processes. To use these functionalities:

The analysis_Tau3Mu_final.py analysis script produces histograms of selected variables, with some selection:

wget https://fccsw.web.cern.ch/fccsw/tutorials/vtx-tutorial/analysis_Tau3Mu_final.py
fccanalysis final analysis_Tau3Mu_final.py

This produces three histograms, for three sets of selections. The histogram files appear in the TauMu/final directory. There are now 6 files (2 processes, 3 sets of cuts). The histograms are normalised to a luminosity of one inverse pb.

Finally, the plotting script analysis_Tau3Mu_plots.py makes few plots:

wget https://fccsw.web.cern.ch/fccsw/tutorials/vtx-tutorial/analysis_Tau3Mu_plots.py
fccanalysis plots analysis_Tau3Mu_plots.py

which appear in the Tau3Mu/plots directory. Look for example at the plot mTaTau3Mu_nostack_log in Tau3Mu/plots/sel1. The candidates in the background sample, corresponding to τ→3πν\tau \rightarrow 3 \pi \nu decays, are reconstructed at a mass that is usually below the τ\tau mass due to the presence of the neutrino. Note that the background sample corresponds to a very low statistics (50M have been produced, we expect 14B) and we see fluctuations. The signal cross-section used here corresponds to B(τ→3μ)=2×10−8B( \tau \rightarrow 3 \mu) = 2 \times 10^{-8}, which is roughly the current upper limit. The luminosity for these final plots is entered in analysis_Tau3Mu_plots.py.

Exercise 6

Exercise to go beyond

Interested in studying the FCC-ee sensitivity to τ→3μ\tau \rightarrow 3 \mu?
Please contact <alberto.lusiani@pi.infn.it> and <monteil@in2p3.fr>.

Note that the input files used in this tutorial are low statistics examples. Larger datasets that are more accurate (KKMC instead of Pythia) can be produced if someone is interested.