The data files we use in this tutorial contain simulated events of the Higgs boson decaying into pair of tau leptons that consequently decay into light leptons: electrons or muons. In the final state we can therefore have either two electrons (ee), two muons (μμ), or a mixed state (eμ or μe).
Let’s now modify the algorithm class to allow selection of either same-flavour (ee or μμ) or different flavour (eμ or μe) combinations. The lepton flavour is encoded in the n-tuple using variables “tau_0” and “tau_1” for leading and sub-leading lepton, respectively. When the variable is equal to 1 it means the tau lepton decayed into muon (+neutrinos) when it’s equal to 2 it means it decayed into electron (+neutrinos). So to select same-flavour or different-flavour combinations, we just need to require the following conditions:
tau_0 == tau_1 tau_0 != tau_1
Because we do not want to hard-code anything into our c++ code but allow everything to be controlled in Python, we need to add switched that will control which of the two conditions (if any) should be applied. So let’s modify the header file of the c++ class “Algorithm.h” like this:
#ifndef ALGORITHM_H #define ALGORITHM_H #include "TString.h" #include "TH1D.h" #include "Data.h" class Algorithm { public: /** * @brief Construct a new Event Loop object */ Algorithm(); /** * @brief Initialize the algorithm * * @param data - pointer to the data-access class instance */ void initialize(Data* data); /** * @brief Execute. Here the stuff happens */ void execute(); /** * @brief Pointer to the histogram class instance defined as public attribute */ TH1D* h_ditau_m = 0; // must be initialized to 0 /** * @brief Selection cuts */ bool cut_selectSF = false; bool cut_selectDF = false; protected: /** * @brief Apply the selection * * @return true when event passed the selection. */ bool passedSelection(); /** * @brief Fill the histograms */ void fillPlots(); /** * @brief Instance of the data-access class */ Data* m_data = 0; }; #endif
- We have added two public boolean attributes “cut_selectSF” and “cut_selectDF”. They are set to false by default, so it means the same/different-flavour selection will not be applied by default.
- To make code more structured, we have also added two new protected methods: “passedSelection” where we will implement the event selection and “fillPlots” where we will put the histogram-filling code.
In the source file “Algorithm.cpp” we implement the new methods:
#include "Algorithm.h" Algorithm::Algorithm() { } void Algorithm::initialize(Data* data) { m_data = data; } void Algorithm::execute() { // apply selection. Exit if didn't pass if(!passedSelection()) return; // fill the plots fillPlots(); } bool Algorithm::passedSelection() { // select specific flavor combinations if(cut_selectSF && m_data->tau_0!=m_data->tau_1) return false; if(cut_selectDF && m_data->tau_0==m_data->tau_1) return false; // passed all the cuts return true; } void Algorithm::fillPlots() { // here we fill the histograms. We protect the code against the empty pointers. if(h_ditau_m) h_ditau_m->Fill( m_data->ditau_mmc_mlm_m ); }
- We have moved the “h_ditau_m->Fill” code from “execute” into “fillPlots” method. The “fillPlots” is now called from “execute”. This will make the code more organised once we add more plots.
- In the “passedSelection” method we have two conditions for the “cut_selectSF” and “cut_selectDF” cuts. Note that they are implemented as negative conditions! Meaning that the run is interrupted if we require same-flavour combination but the leptons have different flavour. If program reaches the end of the method it means that the selection passed.
- The “passedSelection” is called from “execute” just before the “fillPlots”.
Now that we have the c++ code updated, let’s recompile everything. When you do large modifications to the c++ code like this, it’s good to also clean all the compiled objects. It can be done using command:
> make clean > make
Now we also update the python classes in the “Algorithms.py” file. We will remove the “AlgFineBinning” class which is not that useful and add two new classes for same-flavour and different-flavour selection:
from Algorithm import Algorithm from ROOT import TH1D # ----------------------------------- class AlgDefault(Algorithm): """ Default set of histograms """ def __init__(self, name = "AlgDefault"): # call inherited constructor Algorithm.__init__(self, name) # create histograms self.alg.h_ditau_m = TH1D("ditau_m", ";#it{m}_{#tau#tau} [GeV];Events", 30, 0, 300) # ----------------------------------- class AlgSF(AlgDefault): """ Select only same-flavor leptons """ def __init__(self, name = "AlgSF"): # call inherited constructor AlgDefault.__init__(self, name) # apply selection for the same-flavor lepton combination self.alg.cut_selectSF = True # ----------------------------------- class AlgDF(AlgDefault): """ Select only different-flavor leptons """ def __init__(self, name = "AlgDF"): # call inherited constructor AlgDefault.__init__(self, name) # apply selection for the different-flavor lepton combination self.alg.cut_selectDF = True
- The new classes “AlgSF” and “AlgDF” inherit from “AlgDefault”. This way again we minimise amount of duplicated code. One just have to make sure that in the new classes’ constructors one calls the constructor of the immediate parent, i.e. “AlgDefault” in this case.
- The cuts are enabled by setting their value to “True”. Unlike in c++, the Python keyword for True and False is spelled with capital first character.
Finally, we update the “runMe.py”. We will run the three algorithms in the event loop. One without the selection, one with the same-flavour selection and one with the different-flavour selection:
# here we load the shared library created using the Makefile from ROOT import gSystem gSystem.Load("Analysis.so") # now we can create instance of the class EventLoop from Samples import * eventLoop = SampleGGH() # create algorithm from Algorithms import * algs = [] algs += [ AlgDefault() ] algs += [ AlgSF() ] algs += [ AlgDF() ] # add the algorithm into the event loop eventLoop.addAlgorithms( algs ) # initialize and execute the event loop eventLoop.execute() # save plots from all algorithms in the loop eventLoop.save()
After the execution, you will have three output files in your work folder: histograms.ggH.AlgDefault.root, histograms.ggH.AlgDF.root, and histograms.ggH.AlgSF.root. Now you can open them in ROOT and check that the number of entries in histogram without the selection matches the sum of entries in other two:
> root -l histograms.ggH.AlgDefault.root > ditau_m->GetEntries() (double) 10757.000 > .q > root -l histograms.ggH.AlgSF.root > ditau_m->GetEntries() (double) 4522.0000 > .q > root -l histograms.ggH.AlgDF.root > ditau_m->GetEntries() (double) 6235.0000 > 4522.0000+6235.0000 (double) 10757.000
We are starting to see how the object-oriented design of the analysis program starts to bear fruits. By modifying few lines and adding few items into the list we can create histograms for many different configurations at once. Imagine that we would have everything hardcoded in a single linear macro. We would have to rewrite, recompile and re-execute the macro many times to get the same result.
In the next example we will show how to use the operator overloading technique to implement more complex selection cuts that can be enabled/disabled at will.