Rivet analyses

Colour flow in hadronic top decay at 8 TeV

Experiment: ATLAS (LHC)

Inspire ID: 1376945

Status: VALIDATED

Authors: - Ben Nachman - Christian Gutschow

References: - Expt page: ATLAS-TOPQ-2014-09 - arXiv: 1506.05629

Beams: p+ p+

Beam energies: (4000.0, 4000.0)GeV

Run details: - ttbar production with one W decaying leptonically, the other one hadronically

The distribution and orientation of energy inside jets is predicted to be an experimental handle on colour connections between the hard-scatter quarks and gluons initiating the jets. This is a measurement of the distribution of one such variable, the jet pull angle. The pull angle is measured for jets produced in t events with one W boson decaying leptonically and the other decaying to jets using 20.3,fb−1 of data recorded with the ATLAS detector at a centre-of-mass energy of $\sqrt{s} = 8$,TeV at the LHC. The jet pull angle distribution is corrected for detector resolution and acceptance effects.

Source code:ATLAS_2015_I1376945.cc

// -*- C++ -*-
#include "Rivet/Analysis.hh"
#include "Rivet/Projections/FinalState.hh"
#include "Rivet/Projections/PromptFinalState.hh"
#include "Rivet/Projections/IdentifiedFinalState.hh"
#include "Rivet/Projections/LeptonFinder.hh"
#include "Rivet/Projections/VetoedFinalState.hh"
#include "Rivet/Projections/FastJets.hh"

namespace Rivet {


  /// @brief Colour flow in hadronic top decay at 8 TeV
  class ATLAS_2015_I1376945 : public Analysis {
  public:

    /// Constructor
    RIVET_DEFAULT_ANALYSIS_CTOR(ATLAS_2015_I1376945);


    /// @name Analysis methods
    /// @{

    /// Book histograms and initialise projections before the run
    void init() {

      const FinalState fs;

      PromptFinalState promptFs(fs);
      promptFs.acceptTauDecays(true);
      promptFs.acceptMuonDecays(false);

      IdentifiedFinalState neutrino_fs(promptFs);
      neutrino_fs.acceptNeutrinos();
      declare(neutrino_fs, "NEUTRINO_FS");

      IdentifiedFinalState Photon(fs);
      Photon.acceptIdPair(PID::PHOTON);

      IdentifiedFinalState bare_muons_fs(promptFs);
      bare_muons_fs.acceptIdPair(PID::MUON);

      IdentifiedFinalState bare_elecs_fs(promptFs);
      bare_elecs_fs.acceptIdPair(PID::ELECTRON);

      Cut lep_cuts = (Cuts::abseta < 2.5) && (Cuts::pT > 1*MeV);
      LeptonFinder muons(bare_muons_fs, Photon, 0.1, lep_cuts);
      declare(muons, "MUONS");

      LeptonFinder elecs(bare_elecs_fs, Photon, 0.1, lep_cuts);
      declare(elecs, "ELECS");

      VetoedFinalState vfs;
      vfs.addVetoOnThisFinalState(muons);
      vfs.addVetoOnThisFinalState(elecs);
      vfs.addVetoOnThisFinalState(neutrino_fs);

      FastJets fjets(vfs, JetAlg::ANTIKT, 0.4);
      fjets.useInvisibles();
      declare(fjets, "jets");

      book(h_pull_all     ,4,1,1);
      book(h_pull_charged ,5,1,1);
    }


    /// Perform the per-event analysis
    void analyze(const Event& event) {

      /**************
       *    JETS    *
       **************/
      const Jets& allJets = apply<FastJets>(event, "jets").jetsByPt(Cuts::pT > 25.0*GeV && Cuts::absrap < 2.5);
      const DressedLeptons& all_elecs = apply<LeptonFinder>(event, "ELECS").dressedLeptons();
      const DressedLeptons& all_muons = apply<LeptonFinder>(event, "MUONS").dressedLeptons();
      Jets goodJets;
      for (const Jet & j : allJets) {
        bool keep = true;
        for (const DressedLepton & el : all_elecs)  keep &= deltaR(j, el) >= 0.2;
        if (keep)  goodJets += j;
      }
      if ( goodJets.size() < 4 )  vetoEvent;

      /****************
       *    LEPTONS   *
       ****************/
      DressedLeptons muons, vetoMuons;
      for (const DressedLepton & mu : all_muons) {
        bool keep = true;
        for (const Jet & j : goodJets)  keep &= deltaR(j, mu) >= 0.4;
        if (keep && mu.pt() > 15*GeV) {
          vetoMuons.push_back(mu);
          if (mu.pt() > 25*GeV)  muons.push_back(mu);
        }
      }

      DressedLeptons elecs, vetoElecs;
      for (const DressedLepton & el : all_elecs) {
        bool keep = true;
        for (const Jet & j : goodJets)  keep &= deltaR(j, el) >= 0.4;
        if (keep && el.pt() > 15*GeV) {
          vetoElecs.push_back(el);
          if (el.pt() > 25*GeV)  elecs.push_back(el);
        }
      }

      if (muons.empty() && elecs.empty())  vetoEvent;

      bool muCandidate = !( muons.size() < 1 || vetoMuons.size() > 1 || vetoElecs.size() > 0 );
      bool elCandidate = !( elecs.size() < 1 || vetoElecs.size() > 1 || vetoMuons.size() > 0 );

      if (!elCandidate && !muCandidate)  vetoEvent;

      /******************************
       *    ELECTRON-MUON OVERLAP   *
       ******************************/
      for (const DressedLepton & electron : elecs) {
        for (const DressedLepton & muon : muons) {
          double d_theta = fabs(muon.theta() - electron.theta());
          double d_phi = deltaPhi(muon.phi(), electron.phi());
          if (d_theta < 0.005 && d_phi < 0.005)  vetoEvent;
        }
      }

      /****************
       *  NEUTRINOS   *
       ****************/
      const Particles& neutrinos = apply<IdentifiedFinalState>(event, "NEUTRINO_FS").particlesByPt();
      FourMomentum metVector = FourMomentum(0.,0.,0.,0.);
      for (const Particle& n : neutrinos) {
        metVector += n.momentum();
      }
      double met = metVector.pt();
      if (met <= 20*GeV)  vetoEvent;

      if ( (_mT(muCandidate? muons[0] : elecs[0], metVector) + met) <= 60. )  vetoEvent;

      /****************
       *    B-JETS    *
       ****************/
      Jets bJets, wJets;
      for(Jet & j : goodJets) {
        bool b_tagged = false;
        Particles bTags = j.bTags();
        for ( Particle & b : bTags ) {
          b_tagged |= b.pT() > 5*GeV;
        }
        if (b_tagged)  bJets += j;
        if (!b_tagged && j.abseta() < 2.1)  wJets += j;
      }

      if ( bJets.size() < 2 || wJets.size() < 2 )  vetoEvent;

      double pull_angle = fabs(CalculatePullAngle(wJets[0], wJets[1], 0));
      h_pull_all->fill(pull_angle / Rivet::PI);

      double pull_angle_charged = fabs(CalculatePullAngle(wJets[0], wJets[1], 1));
      h_pull_charged->fill(pull_angle_charged / Rivet::PI);

    }

    Vector3 CalculatePull(Jet& jet, bool &isCharged) {
      Vector3 pull(0.0, 0.0, 0.0);
      double PT = jet.pT();
      Particles& constituents = jet.particles();
      Particles charged_constituents;
      if (isCharged) {
        for (Particle & p : constituents) {
          if (p.charge3() != 0)  charged_constituents += p;
        }
        constituents = charged_constituents;
      }
      // calculate axis
      FourMomentum axis;
      for (Particle& p : constituents)  axis += p.momentum();
      Vector3 J(axis.rap(), axis.phi(MINUSPI_PLUSPI), 0.0);
      // calculate pull
      for (Particle & p : constituents) {
        Vector3 ri = Vector3(p.rap(), p.phi(MINUSPI_PLUSPI), 0.0) - J;
        while (ri.y() >  Rivet::PI) ri.setY(ri.y() - Rivet::TWOPI);
        while (ri.y() < -Rivet::PI) ri.setY(ri.y() + Rivet::TWOPI);
        pull.setX(pull.x() + (ri.mod() * ri.x() * p.pT()) / PT);
        pull.setY(pull.y() + (ri.mod() * ri.y() * p.pT()) / PT);
      }
      return pull;
    }

    double CalculatePullAngle(Jet& jet1, Jet& axisjet, bool isCharged) {
      Vector3 pull_vector = CalculatePull(jet1, isCharged);
      pull_vector = Vector3(1000.*pull_vector.x(), 1000.*pull_vector.y(), 0.);
      double drap = axisjet.rap() - jet1.rap();
      double dphi = axisjet.phi(MINUSPI_PLUSPI) - jet1.phi(MINUSPI_PLUSPI);
      Vector3 j2_vector(drap, dphi, 0.0);
      return mapAngleMPiToPi(deltaPhi(pull_vector, j2_vector));
    }

    double _mT(const FourMomentum &l, FourMomentum &nu) const {
      return sqrt( 2 * l.pT() * nu.pT() * (1 - cos(deltaPhi(l, nu))) );
    }

    /// Normalise histograms etc., after the run
    void finalize() {
      normalize(h_pull_all);
      normalize(h_pull_charged);
    }

    /// @}


  private:

    Histo1DPtr h_pull_all;
    Histo1DPtr h_pull_charged;

  };


  RIVET_DECLARE_PLUGIN(ATLAS_2015_I1376945);

}