Rivet analyses

Measurement of charged hadron production in Z-tagged jets at 8 TeV

Experiment: LHCB (LHC)

Inspire ID: 1730448

Status: VALIDATED

Authors: - Alice Xu

References: - Phys. Rev. Lett. 123 (2019) 232001 - DOI:10.1103/PhysRevLett.123.232001 - arXiv: 1904.08878 - Expt page: LHCb-PAPER-2019-012

Beams: p+ p+

Beam energies: (4000.0, 4000.0)GeV

Run details: - Proton-proton interactions at 8 TeV centre-of-mass energy.

The LHCb Collaboration presents measurements of the longitudinal, transverse, and radial distributions of charged hadrons in $Z$-jets using proton-proton data collected at a center-of-mass energy of $\sqrt{s} = 8$TeV. $Z$ bosons are fully reconstructed from the $\mu^- \mu^+$ decay channel. Jets with transverse momentum $p_\mathrm{T,jet} > 20$ GeV are reconstructed within the rapidity range $2.5 < y_\mathrm{jet} < 4.0$ using the anti-$k_\mathrm{T}$ algorithm with resolution paparmeter $R = 0.5$.

Source code:LHCB_2019_I1730448.cc

// -*- C++ -*-
#include "Rivet/Analysis.hh"
#include "Rivet/Projections/DileptonFinder.hh"
#include "Rivet/Projections/DirectFinalState.hh"
#include "Rivet/Projections/FastJets.hh"
#include "Rivet/Projections/FinalState.hh"
#include "Rivet/Projections/MissingMomentum.hh"
#include "fastjet/ClusterSequence.hh"
#include "fastjet/JetDefinition.hh"
#include "fastjet/Selector.hh" // For creating Filter for jet Projector

namespace Rivet {

  /// @brief Measurement of charged hadron production in Z-tagged jets at 8 TeV
  class LHCB_2019_I1730448 : public Analysis {

    /////////////////////////////////////////////////////////////////////////////////////////////////
    // Analysis parameters

    // Particle reconstruction
    const double ETA_MIN_PARTICLES = 2.;  // Min pseudorapidity of final-state particles
    const double ETA_MAX_PARTICLES = 4.5; // Max "

    // Jet reconstruction
    const double JET_R = 0.5;        // Jet resolution parameter for anti-kT
    const double ETA_MIN_JETS = 2.5; // Min rapidity of constructed Z-jets
    const double ETA_MAX_JETS = 4.;  // Max "
    const double PT_MIN_JETS = 20.;  // Min jet transverse momentum
    const double PT_MAX_JETS = 100.; // Max "

    // Z Boson
    const double MASS_MIN_ZBOSONS = 60.;  // Min invariant mass of Z bosons
    const double MASS_MAX_ZBOSONS = 120.; // Max "
    const double PT_MIN_ZBOSONS = 0.;     // Min Z boson transverse momentum
    const double PT_MAX_ZBOSONS = 100.;   // Max "

    // Charged Hadron
    const double P_MIN_HADRONS = 4.;    // Min momentum of charged hadrons
    const double P_MAX_HADRONS = 1000.; // Max "
    const double PT_MIN_HADRONS = 0.25; // Min transverse momentum of charged hadrons


  public:

    // Attaches constituent PID to a PseudoJet
    class JetInfo : public fastjet::PseudoJet::UserInfoBase {
    public:

      JetInfo(const int& _pid)
          : pid(_pid) { };
      int get_pid() const {
        return this->pid;
      }

    private:

      int pid = -999;
    };

    /// Constructor
    RIVET_DEFAULT_ANALYSIS_CTOR(LHCB_2019_I1730448);


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

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

      // The basic final-state projection:
      // all final-state particles within the given eta acceptance
      Cut particle_selector = Cuts::etaIn(ETA_MIN_PARTICLES, ETA_MAX_PARTICLES);
      const FinalState fs(particle_selector);
      declare(fs, "fs_particles");

      Cut Z_boson_selector = Cuts::ptIn(PT_MIN_ZBOSONS, PT_MAX_ZBOSONS)
          & Cuts::massIn(MASS_MIN_ZBOSONS, MASS_MAX_ZBOSONS) & particle_selector;

      // Find *decayed* Z bosons from dimuon children
      // pT > 20 GeV according to JHEP 08 (2015) 039;  DOI:10.1007/JHEP08%282015%29039 referenced indirectly in paper
      DileptonFinder zfinder(fs, 91.2 * GeV, -1., (Cuts::abspid == PID::MUON) && Cuts::pT > 20. * GeV,
                             Z_boson_selector);
      declare(zfinder, "ZFinder");

      // Jet pT bin edges
      const vector<double> PT_BIN_EDGES = {20., 30., 50., 100.};

      // Book histograms
      book(_h_ufld_z, PT_BIN_EDGES);
      book(_h_ufld_jt, PT_BIN_EDGES);
      book(_h_ufld_r, PT_BIN_EDGES);
      for (size_t i = 0; i < _h_ufld_z->numBins(); i++) {
        book(_h_ufld_z->bin(i + 1), 1, 1, i + 1);  // HEPData Table 1
        book(_h_ufld_jt->bin(i + 1), 2, 1, i + 1); // HEPData Table 2
        book(_h_ufld_r->bin(i + 1), 3, 1, i + 1);  // HEPData Table 3
      };
    }


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

      const FinalState& fs = apply<FinalState>(event, "fs_particles");

      if (getLog().isActive(Rivet::Log::DEBUG)) {
        int nmuons = 0;
        for (const Particle& p : fs.particles()) {
          if (abs(p.pid()) == 13) nmuons++;
        }
        MSG_DEBUG("fs size: " << fs.particles().size() << " | muons: " << nmuons << std::endl);
      }

      // The final-state particles declared above are clustered using FastJet with
      // the anti-kT algorithm and a jet-radius parameter 0.5
      Rivet::PseudoJets trimmed_particles;
      for (const Particle& fs_particle : fs.particles()) {
        fastjet::PseudoJet InfoJet = fs_particle.pseudojet();
        InfoJet.set_user_info_shared_ptr(
            fastjet::SharedPtr<fastjet::PseudoJet::UserInfoBase>(new JetInfo(fs_particle.pid())));
        trimmed_particles.push_back(InfoJet);
      }
      fastjet::JetDefinition jet_def(fastjet::antikt_algorithm, JET_R);
      fastjet::ClusterSequence cs(trimmed_particles, jet_def);
      Rivet::PseudoJets jets = sorted_by_pt(cs.inclusive_jets());

      // Retrieve *decayed* Z bosons and muon children
      const DileptonFinder& zfinder = apply<DileptonFinder>(event, "ZFinder");
      if (zfinder.bosons().empty()) vetoEvent;

      const Particles& Z_bosons = zfinder.bosons();
      const Particles& muons = zfinder.constituents(); // mu+ and mu-

      // Retrieve clustered jets, sorted by pT, with applied rapidity and pT cuts
      fastjet::Selector jet_selector = fastjet::SelectorPtRange(PT_MIN_JETS, PT_MAX_JETS)
          && fastjet::SelectorEtaRange(ETA_MIN_JETS, ETA_MAX_JETS);
      jets = jet_selector(jets);
      if (jets.empty()) {
        vetoEvent;
      }

      // Create a vector of jets that are back to back with a Z boson, with no muons in jet cone
      Jets Z_jets;
      for (const Rivet::Jet jet : jets) {
        for (const Particle& Z_boson : Z_bosons) {
          if ((Z_boson.pT() < PT_MIN_ZBOSONS) || (Z_boson.pT() > PT_MAX_ZBOSONS))
            continue; // Check Z pT - already in cuts!!!
          bool muon_in_jet = false;
          for (const Particle& muon : muons) {
            if (deltaR(muon, jet) < JET_R) {
              muon_in_jet = true;
              break;
            }
          }
          if (muon_in_jet) continue;
          if (deltaPhi(Z_boson, jet) > (7 * M_PI) / 8) { // Azimuthal cut
            Z_jets.push_back(jet);
            // Recording the number of jets in each bin, for scaling purposes
            if ((jet.pT() >= 20.) && (jet.pT() < 30.)) {
              num_jets_20_30 += 1.;
            }
            else if ((jet.pT() >= 30.) && (jet.pT() < 50.)) {
              num_jets_30_50 += 1.;
            }
            else if ((jet.pT() >= 50.) && (jet.pT() < 100.)) {
              num_jets_50_100 += 1.;
            }
            MSG_DEBUG("deltaphi: " << deltaPhi(Z_boson, jet) << std::endl
                                   << "jet eta: " << jet.eta() << std::endl
                                   << "jet pt: " << jet.pT() << std::endl
                                   << "N_jet <20-30>: " << num_jets_20_30 << std::endl
                                   << "N_jet <30-50>: " << num_jets_30_50 << std::endl
                                   << "N_jet <50-100>: " << num_jets_50_100 << std::endl);
          }
        }
      }
      if (Z_jets.empty()) {
        vetoEvent;
      }

      MSG_DEBUG(Z_jets.size() << " Z jets found." << std::endl);

      ////////////////////////////////////////////////////////////////////////////////////////

      // Loop over jets and apply operations & fill histograms
      for (const Rivet::Jet& Z_jet : Z_jets) {
        for (const fastjet::PseudoJet& constituent : Z_jet.pseudojet().constituents()) {
          const JetInfo& myinfo = constituent.user_info<JetInfo>();
          Rivet::FourMomentum cmom(constituent.E(), constituent.px(), constituent.py(), constituent.pz());
          // Fill histogram
          if (((abs(myinfo.get_pid()) == PID::PIPLUS) || (abs(myinfo.get_pid()) == PID::KPLUS)
               || (abs(myinfo.get_pid()) == PID::PROTON))
              && (constituent.modp() > P_MIN_HADRONS) && (constituent.modp() < P_MAX_HADRONS)
              && (constituent.pt() > PT_MIN_HADRONS)
              && (deltaR(cmom, Z_jet, Rivet::RapScheme::YRAP) < JET_R)) {
            MSG_DEBUG("Filling histograms for " << myinfo.get_pid() << " ..." << std::endl);
            double num_z = Z_jet.p3().dot(cmom.p3());
            double den_z = Z_jet.p2();
            double num_jt = (Z_jet.p3().cross(cmom.p3())).mod();
            double den_jt = Z_jet.p();
            double r = deltaR(cmom, Z_jet, Rivet::RapScheme::YRAP);
            double zj_pt = Z_jet.pT();
            _h_ufld_z->fill(zj_pt, num_z / den_z);    // Fill histogram with longitudinal momentum
            _h_ufld_jt->fill(zj_pt, num_jt / den_jt); // Fill histogram with transverse momentum
            _h_ufld_r->fill(zj_pt, r);                // Fill histogram with radial profile distribution
          };
        }
      }
    }


    /////////////////////////////////////////////////////////////////////////////////////////////////
    /// Scale histograms etc., after the run
    void finalize() {
      const vector<double> scaleFactors = {1., (fuzzyEquals(0., num_jets_20_30) ? 1. : 1. / num_jets_20_30),
                                           (fuzzyEquals(0., num_jets_30_50) ? 1. : 1. / num_jets_30_50),
                                           (fuzzyEquals(0., num_jets_50_100) ? 1. : 1. / num_jets_50_100),
                                           1.};
      scale(_h_ufld_z, scaleFactors);
      scale(_h_ufld_jt, scaleFactors);
      scale(_h_ufld_r, scaleFactors);
    }

    /// @}

  private:

    /// @name Histograms
    /// @{
    Histo1DGroupPtr _h_ufld_z;
    Histo1DGroupPtr _h_ufld_jt;
    Histo1DGroupPtr _h_ufld_r;
    /// @}

    // Jet reconstruction counters
    double num_jets_20_30 = 0.;  // Total number of Z-tagged jets from the 20<pT<30 GeV bin
    double num_jets_30_50 = 0.;  // " 30<pT<50 GeV bin
    double num_jets_50_100 = 0.; // " 50<pT<100 GeV bin
  };


  RIVET_DECLARE_PLUGIN(LHCB_2019_I1730448);

}