Skip to content

Event selection

Observables and selections

We'll start by computing some of the physics observables needed for the event selection in the 1Lbb analysis. The set of observables used within the 1Lbb analysis is---obviously---by no means exhaustive. For most high-level observables (built from 4-vector components of different objects), there likely already is an implementation within the AnalysisClass.

For example, an observable commonly used in final states with one isolated lepton is the transverse mass, reconstructed from the lepton p_\mathrm{T} and the E_\mathrm{T}^\mathrm{miss}. Instead of manually computing this quantity, there is a convenience method already implemented for you in AnalysisClass:

1
static float calcMT(const AnalysisObject &lepton, const AnalysisObject &met);
If you find that a common observable is missing, feel free to implement it as a generic function in the AnalysisClass instead of embedding it in the analysis code. That way other analyses can rely on the same implementation if needed.

Let's start by defining some useful observables, but only if we have exactly one lepton and two b-jets in the event. Notice how we make use of the observables already implemented in the AnalysisClass. Also note that you can simply use .size() because object lists are essentially std::vectors of AnalysisObjects:

1
2
3
4
5
6
7
float mt = 0., mct = 0., mbb = 0., mlb1 = 0.;
if (signalLeptons.size() == 1 && signalBJets.size() == 2 ) {
  mt   = calcMT(signalLeptons[0], met_Vect);
  mct = calcMCT(signalBJets[0],signalBJets[1],met_Vect);
  mbb = (signalBJets[0]+signalBJets[1]).M();
  mlb1 = (signalBJets[0]+signalLeptons[0]).M();
}

Boost-corrected contransverse mass

The implementation of the contransverse mass automatically switches to the boost-corrected version if you provide the E_\mathrm{T}^\mathrm{miss} vector as an argument.

Analysis objects as TLorentVectors

AnalysisObjects is a subclass of TLorentzVector, adding some convenience features for SimpleAnalysis. This means that you can do the usual things like computing the invariant mass of two AnalysisObjects obj1 and obj2 by simply doing (obj1 + obj2).M().

Preselection

Next, we will apply a very general preselection of exactly one signal lepton, exactly one baseline lepton, 2 b-tagged jets with one possible additional light jet, m_\mathrm{T} > 50 GeV and E_\mathrm{T}^\mathrm{miss} > 220 GeV. Note how we simply break out of the code for this event by calling return;, that way everything downstream (i.e. accepting regions and filling ntuples) won't be called (i.e. we won't accept any regions).

1
2
3
4
5
6
7
// Preselection
if(baselineLeptons.size() != 1) return;
if(signalLeptons.size() != 1) return;
if(signalBJets.size() != 2) return;
if(signalJets.size() > 3 || signalJets.size() < 2) return;
if(mt < 50.) return;
if(met < 220.) return;

Filling histograms

This is also a good place to fill the two histograms we have initialised in the init part of the tutorial:

1
2
3
fill("hist_met", met); // fill 1D histogram
fill("hist_mt", mt); // fill 1D histogram
fill("hist_metvsmt", met, mt); // fill 2D histogram
Histograms are filled using the event weight. You can optionally specify an additional weight to be multiplied to the default event weight as an additional argument.

Filling ntuple branches

If you want to have a look at kinematic distributions, you can also let SimpleAnalysis fill ntuple branches with event level observables instead of filling histograms. To do that, just use:

1
void ntupVar(const std::string &label,float value);
and provide a label as well as a value for the branch.

Some trivial examples:

1
2
3
// Can fill integers, floats, doubles, booleans
ntupVar("met", met);
ntupVar("mbb", mbb);
Note that you can even fill entire analysis objects or even lists of analysis objects into the ntuple. Let's do that e.g. for jets:
1
2
// Can even fill entire analysis objects
ntupVar("signalJets",signalJets);

Default entries in SimpleAnalysis

In case the analysis code filling a variable branch is not reached in a given event (because e.g. some requirement was not met), the value filled into the branch will be 0 (or an empty vector in case of vector branches). This has to be taken into account in case 0 is a valid value for an ntuple variable.

Accepting regions

This leaves only one thing left to do: actually applying the different region definitions. SimpleAnalysis provides a convenience method for accepting regions that have been initialised through the addRegion() method in the MyAnalysisName::Init() step:

1
virtual void accept(const std::string &name, double weight=1);
This will fill accept the event for that region, weighted by the MC event. By default, the eventWeight at index 0 in the array of MC event weights is taken as weight (this is usually the desired behaviour). The user can, however change this behaviour by providing the --mcweight argument in the command line.

The accept() method accepts an additional weight argument that will be multiplied to the eventWeight when accepting the event. You can use this option if you want to give a specific event some other weight apart from the MC event weight.

Since the preselection is already applied, let's go ahead and accept the "preselection" region that we have initialised previously. In this case, it is not necessary to provide an additional weight, so we let SimpleAnalysis simply accept the event weighted by the default MC event weight:

1
accept("preselection");

Analysis objects as TLorentVectors

It is perfectly fine to accept multiple regions per event (and you probably will have to if not all of your ROIs are orthogonal to each other). The analysis code is not stopped by accepting an event but continues to execute the rest of the event processing.

Signal regions

For further use within this tutorial, we will now implement the different exclusion signal regions used in our example 1Lbb analysis. Depending on how many bins your analysis features, this can be a tedious and error-prone process.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// Avoiding too many curly braces for the sake of readability. I know, try not to snap.
// Exclusion signal regions
if(met > 240 && mbb > 100 && mbb <= 140 && mct > 180) {
  if(mt > 100 && mt < 160) {
    if(mct > 180 && mct <= 230) accept("SR_h_Low_bin1");
    else if(mct > 230 && mct <= 280) accept("SR_h_Low_bin2");
    else accept("SR_h_Low_bin3");  
  }
  else if(mt > 160 && mt < 240) {
    if(mct > 180 && mct <= 230) accept("SR_h_Med_bin1");
    else if(mct > 230 && mct <= 280) accept("SR_h_Med_bin2");
    else accept("SR_h_Med_bin3");
  }
  else if(mt > 240 && mlb1 > 120) {
    if(mct > 180 && mct <= 230) accept("SR_h_High_bin1");
    else if(mct > 230 && mct <= 280) accept("SR_h_High_bin2");
    else accept("SR_h_High_bin3");
  }
}

That's it. We have now made the object definitions, performed the overlap removal and done the actual event selection for the different regions of interest. All that is left to do now, is to actually run this analysis over some inputs.


Last update: February 16, 2021