rivet is hosted by Hepforge, IPPP Durham

Rivet analyses reference

ATLAS_2013_I1243871

Measurement of jet shapes in top quark pair events at $\sqrt{s} = 7$ TeV with ATLAS
Experiment: ATLAS (LHC)
Inspire ID: 1243871
Status: VALIDATED
Authors:
  • Javier Llorente
References: Beams: p+ p+
Beam energies: (3500.0, 3500.0) GeV
Run details:
  • Top quark pair production in $pp$ collisions at $\sqrt{s} = 7$ TeV

Measurement of jet shapes in top pair events in the ATLAS 7 TeV run. b-jets are shown to have a wider energy density distribution than light-quark induced jets.

Source code: ATLAS_2013_I1243871.cc
  1// -*- C++ -*-
  2#include "Rivet/Analysis.hh"
  3#include "Rivet/Tools/Logging.hh"
  4#include "Rivet/Projections/FinalState.hh"
  5#include "Rivet/Projections/IdentifiedFinalState.hh"
  6#include "Rivet/Projections/VetoedFinalState.hh"
  7#include "Rivet/Projections/FastJets.hh"
  8#include "Rivet/Tools/ParticleIdUtils.hh"
  9#include "Rivet/Particle.hh"
 10
 11namespace Rivet {
 12
 13
 14  class ATLAS_2013_I1243871 : public Analysis {
 15  public:
 16
 17    /// Constructor
 18    ATLAS_2013_I1243871()
 19      : Analysis("ATLAS_2013_I1243871")
 20    {    }
 21
 22
 23    /// Book histograms and initialise projections before the run
 24    void init() {
 25      // Set up projections
 26      const FinalState fs((Cuts::etaIn(-4.5, 4.5)));
 27      declare(fs, "ALL_FS");
 28
 29      /// Get electrons from truth record
 30      IdentifiedFinalState elec_fs(Cuts::abseta < 2.47 && Cuts::pT > 25*GeV);
 31      elec_fs.acceptIdPair(PID::ELECTRON);
 32      declare(elec_fs, "ELEC_FS");
 33
 34      /// Get muons which pass the initial kinematic cuts:
 35      IdentifiedFinalState muon_fs(Cuts::abseta < 2.5 && Cuts::pT > 20*GeV);
 36      muon_fs.acceptIdPair(PID::MUON);
 37      declare(muon_fs, "MUON_FS");
 38
 39      // Final state used as input for jet-finding.
 40      // We include everything except the muons and neutrinos
 41      VetoedFinalState jet_input(fs);
 42      jet_input.vetoNeutrinos();
 43      jet_input.addVetoPairId(PID::MUON);
 44      declare(jet_input, "JET_INPUT");
 45
 46      // Get the jets
 47      FastJets jets(jet_input, FastJets::ANTIKT, 0.4);
 48      declare(jets, "JETS");
 49
 50      // Book histograms
 51      for (size_t d = 0; d < 5; ++d) {
 52        book(_p_b_rho[d] ,d+1, 1, 1);
 53        book(_p_l_rho[d] ,d+1, 2, 1);
 54        book(_p_b_Psi[d] ,d+1, 1, 2);
 55        book(_p_l_Psi[d] ,d+1, 2, 2);
 56      }
 57    }
 58
 59
 60    /// Perform the per-event analysis
 61    void analyze(const Event& event) {
 62      /// Get the various sets of final state particles
 63      const Particles& elecFS = apply<IdentifiedFinalState>(event, "ELEC_FS").particlesByPt();
 64      const Particles& muonFS = apply<IdentifiedFinalState>(event, "MUON_FS").particlesByPt();
 65
 66      // Get all jets with pT > 7 GeV (ATLAS standard jet collection)
 67      /// @todo Why rewrite the jets collection as a vector of pointers?
 68      const Jets& jets = apply<FastJets>(event, "JETS").jetsByPt(7*GeV);
 69      vector<const Jet*> allJets;
 70      for (const Jet& j : jets) allJets.push_back(&j);
 71
 72      // Keep any jets that pass the pt cut
 73      vector<const Jet*> pt_jets;
 74      for (const Jet* j : allJets) {
 75        /// @todo Use direct kinematics access
 76        const double pt = j->momentum().pT();
 77        const double eta = j->momentum().eta();
 78        if (pt > 25*GeV && fabs(eta) < 2.5) pt_jets.push_back(j);
 79      }
 80
 81      // Remove jets too close to an electron
 82      vector<const Jet*> good_jets;
 83      for (const Jet* j : pt_jets) {
 84        bool isElectron = 0;
 85        for (const Particle& e : elecFS) {
 86          const double elec_jet_dR = deltaR(e.momentum(), j->momentum());
 87          if (elec_jet_dR < 0.2) { isElectron = true; break; }
 88        }
 89        if (!isElectron) good_jets.push_back(j);
 90      }
 91
 92      // Classify the event type
 93      const size_t nElec = elecFS.size();
 94      const size_t nMuon = muonFS.size();
 95      bool isSemilepton = false, isDilepton = false;
 96      if (nElec == 1 && nMuon == 0) {
 97        isSemilepton = true;
 98      } else if (nElec == 0 && nMuon == 1) {
 99        isSemilepton = true;
100      } else if (nElec == 2 && nMuon == 0) {
101        if (charge(elecFS[0]) != charge(elecFS[1])) isDilepton = true;
102      } else if (nElec == 1 && nMuon == 1) {
103        if (charge(elecFS[0]) != charge(muonFS[0])) isDilepton = true;
104      } else if (nElec == 0 && nMuon == 2) {
105        if (charge(muonFS[0]) != charge(muonFS[1])) isDilepton = true;
106      }
107      const bool isGoodEvent = (isSemilepton && good_jets.size() >= 4) || (isDilepton && good_jets.size() >= 2);
108      if (!isGoodEvent) vetoEvent;
109
110      // Select b-hadrons
111      /// @todo Use built-in identification on Particle, avoid HepMC
112      vector<ConstGenParticlePtr> b_hadrons;
113      vector<ConstGenParticlePtr> allParticles = HepMCUtils::particles(event.genEvent());
114      for (size_t i = 0; i < allParticles.size(); i++) {
115        ConstGenParticlePtr p = allParticles.at(i);
116        if ( !(PID::isHadron( p->pdg_id() ) && PID::hasBottom( p->pdg_id() )) ) continue;
117        if (p->momentum().perp() < 5*GeV) continue;
118        b_hadrons.push_back(p);
119      }
120
121      // Select b-jets as those containing a b-hadron
122      /// @todo Use built-in dR < 0.3 Jet tagging, avoid HepMC
123      vector<const Jet*> b_jets;
124      for (const Jet* j : good_jets) {
125        bool isbJet = false;
126        for (ConstGenParticlePtr b : b_hadrons) {
127          /// @todo Use direct momentum accessor / delta functions
128          const FourMomentum hadron = b->momentum();
129          const double hadron_jet_dR = deltaR(j->momentum(), hadron);
130          if (hadron_jet_dR < 0.3) { isbJet = true; break; }
131        }
132        // Check if it is overlapped to any other jet
133        bool isOverlapped = false;
134        for (const Jet* k : allJets) {
135          if (j == k) continue;
136          double dRjj = deltaR(j->momentum(), k->momentum());
137          if (dRjj < 0.8) { isOverlapped = true; break; }
138        }
139        if (isbJet && !isOverlapped) b_jets.push_back(j);
140      }
141      MSG_DEBUG(b_jets.size() << " b-jets selected");
142
143
144      // Select light-jets as the pair of non-b-jets with invariant mass closest to the W mass
145      /// @todo Use built-in b-tagging (dR < 0.3 defn), avoid HepMC
146      const double nominalW = 80.4*GeV;
147      double deltaM = 500*GeV;
148      const Jet* light1 = NULL; const Jet* light2 = NULL; // NB: const Jets, not const pointers!
149      for (const Jet* i : good_jets) {
150        bool isbJet1 = false;
151        for (ConstGenParticlePtr b : b_hadrons) {
152          /// @todo Use direct momentum accessor / delta functions
153          const FourMomentum hadron = b->momentum();
154          const double hadron_jet_dR = deltaR(i->momentum(), hadron);
155          if (hadron_jet_dR < 0.3) { isbJet1 = true; break; }
156        }
157        if (isbJet1) continue;
158        for (const Jet* j : good_jets) {
159          bool isbJet2 = false;
160          for (ConstGenParticlePtr b : b_hadrons) {
161            FourMomentum hadron = b->momentum();
162            double hadron_jet_dR = deltaR(j->momentum(), hadron);
163            if (hadron_jet_dR < 0.3) { isbJet2 = true; break; }
164          }
165          if (isbJet2) continue;
166          double invMass = (i->momentum()+j->momentum()).mass();
167          if (fabs(invMass-nominalW) < deltaM){
168            deltaM = fabs(invMass - nominalW);
169            light1 = i;
170            light2 = j;
171          }
172        }
173      }
174
175      // Check that both jets are not overlapped, and populate the light jets list
176      vector<const Jet*> light_jets;
177      const bool hasGoodLight = light1 != NULL && light2 != NULL && light1 != light2;
178      if (hasGoodLight) {
179        bool isOverlap1 = false, isOverlap2 = false;
180        for (const Jet* j : allJets) {
181          if (light1 == j) continue;
182          const double dR1j = deltaR(light1->momentum(), j->momentum());
183          if (dR1j < 0.8) { isOverlap1 = true; break; }
184        }
185        for (const Jet* j : allJets) {
186          if (light2 == j) continue;
187          const double dR2j = deltaR(light2->momentum(), j->momentum());
188          if (dR2j < 0.8) { isOverlap2 = true; break; }
189        }
190        if (!isOverlap1 && !isOverlap2) {
191          light_jets.push_back(light1);
192          light_jets.push_back(light2);
193        }
194      }
195      MSG_DEBUG(light_jets.size() << " light jets selected");
196
197
198      // Calculate the jet shapes
199      /// @todo Use C++11 vector/array initialization
200      const double binWidth = 0.04; // -> 10 bins from 0.0-0.4
201      vector<double> ptEdges; ptEdges += {{ 30, 40, 50, 70, 100, 150 }};
202
203      // b-jet shapes
204      MSG_DEBUG("Filling b-jet shapes");
205      for (const Jet* bJet : b_jets) {
206        // Work out jet pT bin and skip this jet if out of range
207        const double jetPt = bJet->momentum().pT();
208        MSG_DEBUG("Jet pT = " << jetPt/GeV << " GeV");
209        if (!inRange(jetPt/GeV, 30., 150.)) continue;
210        /// @todo Use YODA bin index lookup tools
211        size_t ipt; for (ipt = 0; ipt < 5; ++ipt) if (inRange(jetPt/GeV, ptEdges[ipt], ptEdges[ipt+1])) break;
212        MSG_DEBUG("Jet pT index = " << ipt);
213
214        // Calculate jet shape
215        vector<double> rings(10, 0);
216        for (const Particle& p : bJet->particles()) {
217          const double dR = deltaR(bJet->momentum(), p.momentum());
218          const size_t idR = (size_t) floor(dR/binWidth);
219          for (size_t i = idR; i < 10; ++i) rings[i] += p.pT();
220        }
221
222        // Fill each dR bin of the histos for this jet pT
223        for (int iBin = 0; iBin < 10; ++iBin) {
224          const double rcenter = 0.02 + iBin*binWidth;
225          const double rhoval = (iBin != 0 ? (rings[iBin]-rings[iBin-1]) : rings[iBin]) / binWidth / rings[9];
226          const double psival = rings[iBin] / rings[9];
227          MSG_DEBUG(rcenter << ", " << rhoval << ", " << psival);
228          _p_b_rho[ipt]->fill(rcenter, rhoval);
229          _p_b_Psi[ipt]->fill(rcenter, psival);
230        }
231      }
232
233      // Light jet shapes
234      MSG_DEBUG("Filling light jet shapes");
235      for (const Jet* lJet : light_jets) {
236        // Work out jet pT bin and skip this jet if out of range
237        const double jetPt = lJet->momentum().pT();
238        MSG_DEBUG("Jet pT = " << jetPt/GeV << " GeV");
239        if (!inRange(jetPt/GeV, 30., 150.)) continue;
240        /// @todo Use YODA bin index lookup tools
241        size_t ipt; for (ipt = 0; ipt < 5; ++ipt) if (inRange(jetPt/GeV, ptEdges[ipt], ptEdges[ipt+1])) break;
242        MSG_DEBUG("Jet pT index = " << ipt);
243
244        // Calculate jet shape
245        vector<double> rings(10, 0);
246        for (const Particle& p : lJet->particles()) {
247          const double dR = deltaR(lJet->momentum(), p.momentum());
248          const size_t idR = (size_t) floor(dR/binWidth);
249          for (size_t i = idR; i < 10; ++i) rings[i] += p.pT();
250        }
251
252        // Fill each dR bin of the histos for this jet pT
253        for (int iBin = 0; iBin < 10; ++iBin) {
254          const double rcenter = 0.02 + iBin*binWidth;
255          const double rhoval = (iBin != 0 ? (rings[iBin]-rings[iBin-1]) : rings[iBin]) / binWidth / rings[9];
256          const double psival = rings[iBin] / rings[9];
257          _p_l_rho[ipt]->fill(rcenter, rhoval);
258          _p_l_Psi[ipt]->fill(rcenter, psival);
259        }
260      }
261
262    }
263
264    /// @todo why does this routine not have a finalize method? 
265    /// not clear how you would combine different samples slices 
266    /// correctly if you don't weight by cross-section
267
268
269  private:
270
271    Profile1DPtr _p_b_rho[5];
272    Profile1DPtr _p_l_rho[5];
273    Profile1DPtr _p_b_Psi[5];
274    Profile1DPtr _p_l_Psi[5];
275
276  };
277
278
279  // The hook for the plugin system
280  RIVET_DECLARE_PLUGIN(ATLAS_2013_I1243871);
281
282}