Rivet analyses
Measurement of ttbar production with a veto on additional central jet activity
Experiment: ATLAS (LHC)
Inspire ID: 1094568
Status: VALIDATED
Authors: - Kiran Joshi
References: - Expt page: ATLAS-TOPQ-2011-21 - arXiv: 1203.5015 - Eur.Phys.J. C72 (2012) 2043
Beams: p+ p+
Beam energies: (3500.0, 3500.0)GeV
Run details: - Require dileptonic ttbar events at 7TeV. It is important to not include semileptonic decay channels in the runs, as they can not be vetoed in the analysis in a generator-independent fashion but have been subtracted from the particle level measurement. The tau decay channels also count as leptonic.
A measurement of the additional jet activity in dileptonic ttbar events. The fraction of events passing a veto requirement are shown as a function the veto scale for four central rapidity intervals. Two veto definitions are used: events are vetoed if they contain an additional jet in the rapidity interval with transverse momentum above a threshold, or alternatively, if the scalar transverse momentum sum of all additional jets in the rapidity interval is above a threshold.
Source
code:ATLAS_2012_I1094568.cc
// -*- C++ -*-
#include "Rivet/Analysis.hh"
#include "Rivet/Projections/FinalState.hh"
#include "Rivet/Projections/IdentifiedFinalState.hh"
#include "Rivet/Projections/FastJets.hh"
#include "Rivet/Projections/HeavyHadrons.hh"
namespace Rivet {
/// Top pair production with central jet veto
class ATLAS_2012_I1094568 : public Analysis {
public:
/// Constructor
RIVET_DEFAULT_ANALYSIS_CTOR(ATLAS_2012_I1094568);
struct Plots {
// Track which veto region this is, to match the autobooked histograms
int region_index;
// Lower rapidity boundary or veto region
double y_low;
// Upper rapidity boundary or veto region
double y_high;
double veto_Q0;
double veto_Qsum;
// Histograms to store the veto jet pT and sum(veto jet pT) histograms.
Histo1DPtr h_veto_Q0;
Histo1DPtr h_veto_Qsum;
// Estimate1Ds for the gap fractions
Estimate1DPtr gapFrac_Q0;
Estimate1DPtr gapFrac_Qsum;
};
/// Book histograms and initialise projections before the run
void init() {
const FinalState fs(Cuts::abseta < 4.5);
/// Get electrons from truth record
FinalState elec_fs(Cuts::abspid == PID::ELECTRON && Cuts::abseta < 2.47 && Cuts::pT > 25*GeV);
declare(elec_fs, "ELEC_FS");
/// Get muons which pass the initial kinematic cuts:
FinalState muon_fs(Cuts::abspid == PID::MUON && Cuts::abseta < 2.5 && Cuts::pT > 20*GeV);
declare(muon_fs, "MUON_FS");
/// Get all neutrinos. These will not be used to form jets.
/// We'll use the highest 2 pT neutrinos to calculate the MET
IdentifiedFinalState neutrino_fs(Cuts::abseta < 4.5);
neutrino_fs.acceptNeutrinos();
declare(neutrino_fs, "NEUTRINO_FS");
// Get the jets
FastJets jets(fs, JetAlg::ANTIKT, 0.4, JetMuons::NONE, JetInvisibles::NONE);
declare(fs, "jet_input");
declare(jets, "JETS");
// get b-hadrons
declare(HeavyHadrons(Cuts::pT > 5*GeV), "BHadrons");
// Initialise weight counter
book(m_total_weight, "_total_weight");
// Init histogramming for the various regions
m_plots[0].region_index = 1;
m_plots[0].y_low = 0.0;
m_plots[0].y_high = 0.8;
initializePlots(m_plots[0]);
//
m_plots[1].region_index = 2;
m_plots[1].y_low = 0.8;
m_plots[1].y_high = 1.5;
initializePlots(m_plots[1]);
//
m_plots[2].region_index = 3;
m_plots[2].y_low = 1.5;
m_plots[2].y_high = 2.1;
initializePlots(m_plots[2]);
//
m_plots[3].region_index = 4;
m_plots[3].y_low = 0.0;
m_plots[3].y_high = 2.1;
initializePlots(m_plots[3]);
}
void initializePlots(Plots& plots) {
plots.veto_Q0 = 0.0;
const string veto_Q0_name = "TMP/vetoJetPt_Q0_" + to_str(plots.region_index);
book(plots.h_veto_Q0, veto_Q0_name, 200, 0.0, 1000.0);
book(plots.gapFrac_Q0, plots.region_index, 1, 1);
plots.veto_Qsum = 0.0;
const string veto_Qsum_name = "TMP/vetoJetPt_Qsum_" + to_str(plots.region_index);
book(plots.h_veto_Qsum, veto_Qsum_name, 200, 0.0, 1000.0);
book(plots.gapFrac_Qsum, plots.region_index, 2, 1);
}
/// Perform the per-event analysis
void analyze(const Event& event) {
/// Get the various sets of final state particles
const Particles& elecFS = apply<FinalState>(event, "ELEC_FS").particlesByPt();
const Particles& muonFS = apply<FinalState>(event, "MUON_FS").particlesByPt();
const Particles& neutrinoFS = apply<IdentifiedFinalState>(event, "NEUTRINO_FS").particlesByPt();
// Get all jets with pT > 25 GeV and |y| < 2.4
Jets jets = apply<FastJets>(event, "JETS").jetsByPt(Cuts::pT > 25*GeV && Cuts::absrap < 2.4);
// For each of the jets that pass the rapidity cut, only keep those that are not
// too close to any leptons
idiscardIfAnyDeltaRLess(jets, elecFS, 0.4);
idiscardIfAnyDeltaRLess(jets, muonFS, 0.4);
// Get b hadrons with pT > 5 GeV
const Particles& bHadrons = apply<HeavyHadrons>(event, "BHadrons").bHadrons();
// For each of the good jets, check whether any are b-jets (via dR matching)
size_t nMatches = 0;
Jets bJets, vetoJets;
for (const Jet& jet : jets) {
bool isBjet = any(bHadrons, DeltaRLess(jet, 0.3));
if (isBjet) { ++nMatches; bJets += jet; }
if (!isBjet || nMatches > 2) vetoJets += jet;
}
// Get the MET by taking the vector sum of all neutrinos
/// @todo Use MissingMomentum instead?
double MET = 0;
FourMomentum p_MET;
for(const Particle& p: neutrinoFS) {
p_MET = p_MET + p.momentum();
}
MET = p_MET.pT();
// Now we have everything we need to start doing the event selections
bool passed_ee = false;
// We want exactly 2 electrons...
if (elecFS.size() == 2) {
// ... with opposite sign charges.
if (charge(elecFS[0]) != charge(elecFS[1])) {
// Check the MET
if (MET >= 40*GeV) {
// Do some dilepton mass cuts
const double dilepton_mass = (elecFS[0].momentum() + elecFS[1].momentum()).mass();
if (dilepton_mass >= 15*GeV) {
if (fabs(dilepton_mass - 91.0*GeV) >= 10.0*GeV) {
// We need at least 2 b-jets
passed_ee = bJets.size() > 1;
}
}
}
}
}
bool passed_mumu = false;
// Now do the same checks for the mumu channel
// So we now want 2 good muons...
if (muonFS.size() == 2) {
// ...with opposite sign charges.
if (charge(muonFS[0]) != charge(muonFS[1])) {
// Check the MET
if (MET >= 40*GeV) {
// and do some di-muon mass cuts
const double dilepton_mass = (muonFS.at(0).momentum() + muonFS.at(1).momentum()).mass();
if (dilepton_mass >= 15*GeV) {
if (fabs(dilepton_mass - 91.0*GeV) >= 10.0*GeV) {
// Need at least 2 b-jets
passed_mumu = bJets.size() > 1;
}
}
}
}
}
bool passed_emu = false;
// Finally, the same again with the emu channel
// We want exactly 1 electron and 1 muon
if (elecFS.size() == 1 && muonFS.size() == 1) {
// With opposite sign charges
if (charge(elecFS[0]) != charge(muonFS[0])) {
// Calculate HT: scalar sum of the pTs of the leptons and all good jets
double HT = sum(jets, pT, 0.);
HT += elecFS[0].pT();
HT += muonFS[0].pT();
// Keep events with HT > 130 GeV
if (HT > 130.0*GeV) {
// And again we want 2 or more b-jets
passed_emu = bJets.size() > 1;
}
}
}
if (passed_ee || passed_mumu || passed_emu) {
// If the event passes the selection, we use it for all gap fractions
m_total_weight->fill();
// Loop over each veto jet
for (const Jet& j : vetoJets) {
const double pt = j.pT();
const double rapidity = j.absrap();
// Loop over each region
for (size_t i = 0; i < 4; ++i) {
// If the jet falls into this region, get its pT and increment sum(pT)
if (inRange(rapidity, m_plots[i].y_low, m_plots[i].y_high)) {
m_plots[i].veto_Qsum += pt;
// If we've already got a veto jet, don't replace it
if (m_plots[i].veto_Q0 == 0.0) m_plots[i].veto_Q0 = pt;
}
}
}
for (size_t i = 0; i < 4; ++i) {
m_plots[i].h_veto_Q0->fill(m_plots[i].veto_Q0);
m_plots[i].h_veto_Qsum->fill(m_plots[i].veto_Qsum);
m_plots[i].veto_Q0 = 0.0;
m_plots[i].veto_Qsum = 0.0;
}
}
}
/// Normalise histograms etc., after the run
void finalize() {
const double totalWeight = m_total_weight->val();
for (size_t i = 0; i < 4; ++i) {
finalizeGapFraction(totalWeight, m_plots[i].gapFrac_Q0, m_plots[i].h_veto_Q0);
finalizeGapFraction(totalWeight, m_plots[i].gapFrac_Qsum, m_plots[i].h_veto_Qsum);
}
}
/// Convert temporary histos to cumulative efficiency scatters
/// @todo Should be possible to replace this with a couple of YODA one-lines for diff -> integral and "efficiency division"
void finalizeGapFraction(const double total_weight, Estimate1DPtr gapFrac, Histo1DPtr vetoPt) {
// Stores the cumulative frequency of the veto jet pT histogram
double vetoPtWeightSum = 0.0;
// Keep track of which gap fraction point we're currently populating (#final_points != #tmp_bins)
size_t fgap_point = 0;
for (size_t i = 0; i < vetoPt->numBins(); ++i) {
// If we've done the last "final" point, stop
if (fgap_point == gapFrac->numBins()) break;
// Increment the cumulative vetoPt counter for this temp histo bin
/// @todo Get rid of this and use vetoPt->integral(i+1) when points and bins line up?
vetoPtWeightSum += vetoPt->bin(i).sumW();
// If this temp histo bin's upper edge doesn't correspond to the reference point, don't finalise the scatter.
// Note that points are ON the bin edges and have no width: they represent the integral up to exactly that point.
if ( !fuzzyEquals(vetoPt->bin(i).xMax(), gapFrac->bin(fgap_point+1).xMid()) ) continue;
// Calculate the gap fraction and its uncertainty
const double frac = (total_weight != 0.0) ? vetoPtWeightSum/total_weight : 0;
const double fracErr = (total_weight != 0.0) ? sqrt(frac*(1-frac)/total_weight) : 0;
gapFrac->bin(fgap_point+1).set(frac, fracErr);
++fgap_point;
}
}
private:
CounterPtr m_total_weight;
Plots m_plots[4];
};
RIVET_DECLARE_PLUGIN(ATLAS_2012_I1094568);
}