Using Electrons in Analysis

Last update: 16 Nov 2022 [History] [Edit]

The following section will walk you through scheduling the electron analysis sequence, applying selection cuts, and writing them to the output ntuple.

Schedule electron analysis sequence

Begin by scheduling the electron analysis sequence. Do this by adding the following lines to makeSequence() in MyAnalysisAlgorithms.py after the GRL and pileup sequences:

    # Include and set up the electron analysis sequence:
    from EgammaAnalysisAlgorithms.ElectronAnalysisSequence import makeElectronAnalysisSequence
    workingpoint = 'LooseLHElectron.Loose_VarRad'
    electronSequence = makeElectronAnalysisSequence( dataType, workingpoint, postfix = 'loose',
                                                     recomputeLikelihood=False, shallowViewOutput = False )
    electronSequence.configure( inputName = 'Electrons',
                                outputName = 'AnaElectrons_%SYS%' )
    
    # Add the electron analysis sequence to the job:
    algSeq += electronSequence

This schedules electron calibration as well as implementing requirements on the electron identification. It adds a decoration to each electron named baselineSelection_loose, where loose is set by the postfix argument we pass to makeElectronAnalysisSequence. It is saved as a char with a value of 0 or 1, indicating whether the electron passes the selection. The use of a char is to save disk space compared to using an int or bool.

Rerun to make sure this is correctly implemented.

Schedule electron selection algorithm

Now let’s apply some basic selection criteria for our electrons. We will not use electrons with pT < 27 GeV or |η| > 2.47, so we will use a CP::AsgPtEtaSelectionTool to apply these requirements.

First, we need to set the configurations for these cuts. Add the following lines to MyAnalysisAlgorithms.py. It is useful, but not required, to add them before makeSequence() is defined:

electronMinPt = 27e3 # Minimum pt in MeV
electronMaxEta = 2.47

Put the following lines in makeSequence() after the electron analysis sequence:

    # Schedule selection algorithms for all objects

    # Include and set up electron selection algorithm:
    selAlgEl = createAlgorithm( 'CP::AsgSelectionAlg', 'UserElectronSelectionAlg' )
    addPrivateTool( selAlgEl, 'selectionTool', 'CP::AsgPtEtaSelectionTool' )
    if electronMinPt is not None :
        selAlgEl.selectionTool.minPt = electronMinPt
    if electronMaxEta is not None :
        selAlgEl.selectionTool.maxEta = electronMaxEta
    selAlgEl.selectionDecoration = 'selectPtEta,as_char'
    selAlgEl.particles = 'AnaElectrons_%SYS%'

    # Add the electron selection algorithm to the job:
    algSeq += selAlgEl

This algorithm checks whether each electron passes the pT and η requirements and decorates them with a flag names selectPtEta that we can access later to easily determine whether the electron passes the requirements. The ,as_char causes the decoration to be saved as a char with values of 0 or 1.

As indicated by the comments in the code, all of the object selection algorithms will be placed together here. All analysis sequences for objects (except overlap removal which will be introduced later) are recommended to be before this block of selection algorithms.

Rerun to make sure this is correctly implemented.

Retrieve electron container

Now that we have run the electron analysis sequence and applied some basic selection cuts, we need to write the resulting electron information to the output ntuple. In order to do this, we need to access the electron container.

First, we need to add the correct library to the CMakeLists.txt file. For electrons and photons, the proper library is xAODEgamma. Add this to the LINK_LIBRARIES section where you previously added xAODEventInfo.

Next, make sure the algorithm itself recognizes the type of container we need. At the top of MyxAODAnalysis.cxx, add the following include statement:

#include <xAODEgamma/ElectronContainer.h>

Now that our algorithm knows what electrons are, we can retrieve the electron container (just like the Events container). However, we shouldn’t use the Electrons container. Instead, we want to use the AnaElectrons_NOSYS container that was created by the algorithms we scheduled. Add the following lines to execute() before the TTree is filled.

  // Retrieve electron container from the event store
  const xAOD::ElectronContainer* allElectrons = 0;
  ANA_CHECK (evtStore()->retrieve(allElectrons, "AnaElectrons_NOSYS"));

tip NOSYS refers to the nominal (i.e., no systematic variations applied) collection. Later, we will look at the collections that result from systematic variations being applied.

Write information to ntuple

Finally, we can write information about our electrons to the output ntuple so we can look at them and use them later in the analysis. This will be an extension of the ntuple that you previously created. We are just adding electron information to it so it has a more complete set of information about the event.

In any data or MC event, there is a variable number of electrons (as well as any other physics object), so we need to use vectors to store information about them. We can write a wide variety of information about the electrons to the ntuple, but for now we will store their four momenta.

First, declare the variables we want to use for our branches in the header file:

  std::vector<float> *m_elEta = nullptr;
  std::vector<float> *m_elPhi = nullptr;
  std::vector<float> *m_elPt = nullptr;
  std::vector<float> *m_elM = nullptr;

Notice these are all pointers to vectors and therefore are initialized to nullptr.

Now we can instantiate our branches. We are going to be writing the vectors to the output. For each vector pointer, we need to create a new vector. Add the following lines in initialize():

  m_elEta = new std::vector<float>();
  mytree->Branch ("el_eta", &m_elEta);
  m_elPhi = new std::vector<float>();
  mytree->Branch ("el_phi", &m_elPhi);
  m_elPt = new std::vector<float>();
  mytree->Branch ("el_pt", &m_elPt);
  m_elM = new std::vector<float>();
  mytree->Branch ("el_m", &m_elM);

Before we start filling these vectors with values, we want to be sure the vectors are empty since we reuse the same vector for every event. In execute() add the following:

  // Clear electron 4-momenta vectors
  m_elEta->clear();
  m_elPhi->clear();
  m_elPt->clear();
  m_elM->clear();

Once the vectors are cleared, we can start filling them with the values for each electron in the event. We can use a simple for-loop over the electron container to accomplish this. Here, the data type of the iterator el is defined as an xAOD::Electron*. You can also use auto as the data type, but you need to be wary that this comes with its own set of potential pitfalls.

  // Loop over electrons and store four-momentum information
  for (const xAOD::Electron* el : *allElectrons) {
    m_elEta->push_back (el->eta ());
    m_elPhi->push_back (el->phi ());
    m_elPt-> push_back (el->pt ());
    m_elM->  push_back (el->m ());
  }

In the initialize() function we created new vectors for our objects. As we continue to add more object information, the memory management of all the vectors can become overburdened. It is important to delete these vectors to ensure the algorithm runs smoothly. To do this, we need to define a custom destructor for our algorithm. Add the following to your header (MyxAODAnalysis.h):

public:
  ~MyxAODAnalysis ();

In your source code (MyxAODAnalysis.cxx) add the destructor (given below) after the finalize() function:

MyxAODAnalysis :: ~MyxAODAnalysis () {
  delete m_elEta;
  delete m_elPhi;
  delete m_elPt;
  delete m_elM;
}

Now recompile and rerun your job. Look at the ntuple output to see if the changes are as expected. In particular, look at the ElPt distribution.

Congrats, we have electrons!

Write information to histograms

Follow the instructions in Making Histograms to book and fill a histogram for each of the four-momentum components.

tip You will likely need several iterations of adjusting the binning to show the information optimally. Note that some of the variables may also have negative values. Also, for pT and mass, it is usually useful to divide by 1000 when filling histograms to convert from MeV to GeV.

Apply electron selection cuts

Now that we are able to write electron information to the ntuple, let’s cut on the baselineSelection_loose as well as the pT and η selection criteria that were applied by the algorithms. Recall that the selection algorithm applied a decoration called selectPtEta. We will use this flag to skip electrons that failed the criteria.

To select only electrons that pass these criteria, let’s add a check in the loop over electrons that skips any that fail the criteria:

for (const xAOD::Electron *el : *allElectrons) {
  if(!el->auxdata< char >("baselineSelection_loose")) continue;
  if(!el->auxdata< char >("selectPtEta")) continue;
  ...
}

tip Note that it is necessary to cast the char as an int if you want to print it to screen to check its value.

Recompile and rerun your job. Check the output ntuple to see how the selection criteria changed the electron pT distribution.

Commit and push your code changes.


⭐️ Bonus Exercise

Add an ElectronPtCut as a property of the algorithm, set it to 30 GeV, and add it to your electron selection in MyxAODAnalysis.cxx. This is an alternate way of implementing object-level selection criteria. Run again and look at your output ntuple to see if the cut has an effect. What happens if you apply a cut of 20 GeV? Think about how this cut is applied and what other cuts are applied in the electeron algorithms.