Rivet analyses

Z+b(b) and Z+c at 13 TeV

Experiment: ATLAS (LHC)

Inspire ID: 2771257

Status: VALIDATED

Authors: - Lucrezia Boccardo - Federico Sforza

References: - Expt page: ATLAS-STDM-2018-43 - arXiv: 2403.15093

Beams: p+ p+

Beam energies: (6500.0, 6500.0)GeV

Run details: - pp -> ee/mumu + jets

This paper presents a measurement of the production cross-section of a Z boson in association with b- or c-jets, in proton–proton collisions at $ =13TeVwiththeATLASexperimentattheLargeHadronColliderusingdatacorrespondingtoanintegratedluminosityof140fb^{-1}$. Inclusive and differential cross-sections are measured for events containing a Z boson decaying into electrons or muons and produced in association with at least one b-jet, at least one c-jet, or at least two b-jets with transverse momentum pT > 20GeV and rapidity |y| < 2.5. Predictions from several Monte Carlo generators based on next-to-leading-order matrix elements interfaced with a parton-shower simulation, with different choices of flavour schemes for initial-state partons, are compared with the measured cross-sections. The results are also compared with novel predictions, based on infrared and collinear safe jet flavour dressing algorithms. Selected Z+ ≥ 1 c-jet observables, optimized for sensitivity to intrinsic-charm, are compared with benchmark models with different intrinsic-charm fractions.

Source code:ATLAS_2024_I2771257.cc

// -*- C++ -*-
#include "Rivet/Analysis.hh"
#include "Rivet/Projections/DileptonFinder.hh"
#include "Rivet/Projections/VetoedFinalState.hh"
#include "Rivet/Projections/PromptFinalState.hh"
#include "Rivet/Projections/HeavyHadrons.hh"
#include "Rivet/Projections/FastJets.hh"

namespace Rivet {

  /// @brief Z+b(b) and Z+c at 13 TeV
  class ATLAS_2024_I2771257 : public Analysis {
  public:

    /// Constructor
    RIVET_DEFAULT_ANALYSIS_CTOR(ATLAS_2024_I2771257);

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

      // Initialise and register projections

      _mode = 0;
      if ( getOption("LMODE") == "EL" ) _mode = 1;
      if ( getOption("LMODE") == "MU" ) _mode = 2;

      const FinalState fs;
      // Define fiducial cuts for the leptons in the ZFinder
      const Cut lepcuts = (Cuts::pT > 27*GeV) && (Cuts::abseta < 2.5);
      DileptonFinder zfinderE(91.2*GeV, 0.1, lepcuts && Cuts::abspid == PID::ELECTRON, Cuts::massIn(76*GeV, 106*GeV));
      DileptonFinder zfinderM(91.2*GeV, 0.1, lepcuts && Cuts::abspid == PID::MUON, Cuts::massIn(76*GeV, 106*GeV));
      declare(zfinderE, "zfinderE");
      declare(zfinderM, "zfinderM");

      //Build jets using ATLAS AntiKt4WZtruthJets definition removing dressed W, Z leptons

      // Photons
      FinalState photons(Cuts::abspid == PID::PHOTON);

      // Muons
      PromptFinalState bare_mu(Cuts::abspid == PID::MUON, TauDecaysAs::PROMPT);
      LeptonFinder all_dressed_mu(bare_mu, photons, 0.1, Cuts::abseta < 2.5);

      // Electrons
      PromptFinalState bare_el(Cuts::abspid == PID::ELECTRON, TauDecaysAs::PROMPT);
      LeptonFinder all_dressed_el(bare_el, photons, 0.1, Cuts::abseta < 2.5);

      //Jet forming
      VetoedFinalState vfs(FinalState(Cuts::abseta < 4.5));
      vfs.addVetoOnThisFinalState(all_dressed_el);
      vfs.addVetoOnThisFinalState(all_dressed_mu);

      FastJets jetfs(vfs, JetAlg::ANTIKT, 0.4, JetMuons::ALL, JetInvisibles::DECAY);
      declare(jetfs, "jets");

      //HF hadrons for b and c jet def
      declare(HeavyHadrons(), "HFHadrons");

      // fiducial1B
      book(_h["fiducial1B_Z_Pt"],         4, 1, 1);
      book(_h["fiducial1B_leadBJet_Pt"],  5, 1, 1);
      book(_h["fiducial1B_ZleadBJet_DR"], 6, 1, 1);

      // fiducial2B
      book(_h["fiducial2B_diBJets_DPhi"], 7, 1, 1);
      book(_h["fiducial2B_diBJets_M"],    8, 1, 1);

      // fiducial1C
      book(_h["fiducial1C_Z_Pt"],         9, 1, 1);
      book(_h["fiducial1C_leadCJet_Pt"], 10, 1, 1);
      book(_h["fiducial1C_leadCJet_xF"], 11, 1, 1);

    }

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

      // --------------------------------------- object sel: leptons -----------------------------------------------

      const DileptonFinder& zfinderE = apply<DileptonFinder>(event, "zfinderE");
      const Particles& els = zfinderE.constituents();
      const DileptonFinder& zfinderM = apply<DileptonFinder>(event, "zfinderM");
      const Particles& mus = zfinderM.constituents();

     // default is to run average of Z->ee and Z->mm
     // use LMODE option to pick one channel
      if ( (els.size() + mus.size()) != 2 )  vetoEvent;

      if (      _mode == 0 && !(els.size()==2 || mus.size()==2) )  vetoEvent;
      else if ( _mode == 1 && !(els.size() == 2 && mus.empty()) )  vetoEvent;
      else if ( _mode == 2 && !(els.empty() && mus.size() == 2) )  vetoEvent;

      double Z_pT = 0., Zrap = 0., Zphi = 0.;
      if ( els.size()==2 ) {
        Z_pT = zfinderE.boson().pt()/GeV;
        Zphi = zfinderE.boson().phi();
        Zrap = zfinderE.boson().rapidity();
      } else {
        Z_pT = zfinderM.boson().pt()/GeV;
        Zphi = zfinderM.boson().phi();
        Zrap = zfinderM.boson().rapidity();
      }

      // --------------------------------------- object sel: jets ---------------------------------------------------
      // Retrieve clustered jets, sorted by pT, with a minimum pT cut
      Jets jets = apply<FastJets>(event, "jets").jetsByPt(Cuts::pT > 20*GeV && Cuts::absrap < 2.5);

      // --------------------------------------- object sel: OR ---------------------------------------------------
      //OR 0.4 between jets and dressed leptons
      idiscardIfAnyDeltaRLess(jets, els, 0.4);
      idiscardIfAnyDeltaRLess(jets, mus, 0.4);

      //b- and c- jet definition according to cone R=0.4 labelling
      Jets bjets, cjets;
      const HeavyHadrons &ha = apply<HeavyHadrons>(event,"HFHadrons");
      const Particles allBs = ha.bHadrons(Cuts::pT > 5*GeV);
      const Particles allCs = ha.cHadrons(Cuts::pT > 5*GeV);
      Particles matchedBs, matchedCs;

      std::vector<int> bJet_idx_v;
      int jet_idx =0;

      for (const Jet& j : jets) {
        Jet closest_j;
        Particle closest_b;
        double minDR_j_b = 10;

        for (const Particle& bHad : allBs) {
          bool alreadyMatched = false;
          for (const Particle& bMatched : matchedBs) {
            alreadyMatched |= bMatched.isSame(bHad);
          }
          if (alreadyMatched)  continue;

          double DR_j_b = deltaR(j, bHad);
          if (DR_j_b <= 0.3 && DR_j_b < minDR_j_b) {
            minDR_j_b = DR_j_b;
            closest_j = j;
            closest_b = bHad;
          }
        }

        if (minDR_j_b < 0.3) {
          bjets += closest_j;
          matchedBs += closest_b;
          bJet_idx_v.push_back(jet_idx);
        }
        ++jet_idx;
      }

      jet_idx = 0;
      for (const Jet& j : jets) {
        Jet closest_j;
        Particle closest_c;
        double minDR_j_c = 10;

        for (const Particle& cHad : allCs) {
          bool alreadyMatched = false;
          for (const Particle& cMatched : matchedCs) {
            alreadyMatched |= cMatched.isSame(cHad);
          }

          // Do not match c-jets if already b-matched
          for (size_t bJet_i  = 0; bJet_i<bJet_idx_v.size(); ++bJet_i) {
            if (jet_idx == bJet_idx_v[bJet_i]) alreadyMatched = true;
          }

          if (alreadyMatched)  continue;

          double DR_j_c = deltaR(j, cHad);
          if ( DR_j_c <= 0.3 && DR_j_c < minDR_j_c) {
            minDR_j_c = DR_j_c;
            closest_j = j;
            closest_c = cHad;
          }
        }

        if (minDR_j_c < 0.3) {
          cjets += closest_j;
          matchedCs += closest_c;
        }
        ++jet_idx;
      }

      // --------------------------------------- event sel: Nbjets ------------------------------------------------------
      if (bjets.size() < 1 && cjets.size() < 1)  vetoEvent;

      // --------------------------------------- event classification ------------------------------------------------------
      // string region = "";
      bool pass_fid1B = false;
      bool pass_fid1C = false;
      bool pass_fid2B = false;

      // based on the flavour of the leading HF jet
      if ( (bjets.size()>=1 && cjets.size()==0) ||
           (bjets.size()>=1 && cjets.size()>=1 && bjets[0].pT() > cjets[0].pT()) ) {
        pass_fid1B = true;
      }
      if ( (cjets.size()>=1 && bjets.size()==0) ||
           (cjets.size()>=1 && bjets.size()>=1 && bjets[0].pT() < cjets[0].pT()) ) {
        pass_fid1C = true;
      }

      // based on the flavour of the leading HF jet
      if ( (bjets.size()>=2 && cjets.size()==0) ||
           (bjets.size()>=2 && cjets.size()>=1 && bjets[0].pT() > cjets[0].pT()) ) {
        pass_fid2B = true;
      }
      // -------------------------------------------------------------------------------------------------------------------



      // --------------------------------------- observable cal -------------------------------------------------------

      double leadBJet_Pt = -100, ZleadBJet_DR = -100, dRapZb = -100, dPhiZb = -100, diBJets_M = -100, diBJets_DPhi = -100;

      if (pass_fid1B){
        leadBJet_Pt = bjets[0].pT();
        dRapZb=fabs(Zrap-bjets[0].rap());
        dPhiZb=acos(cos(fabs(Zphi-bjets[0].phi())));
        ZleadBJet_DR = sqrt(dRapZb*dRapZb+dPhiZb*dPhiZb);
      }

      if (pass_fid2B){
        diBJets_M = (bjets[0].momentum() + bjets[1].momentum()).mass();
        diBJets_DPhi = acos(cos(fabs(bjets[0].phi()-bjets[1].phi())));
      }

      // Feynam scaling variable defined as (2 pT*sinh(eta))/sqrt(s) will have the negative range
      // and pT*sinh(eta) == pZ, which is not exactly accurate for massive objects, such as jet
      double leadCJet_Pt = -100, leadCJet_xF = -100;

      if (pass_fid1C){
        leadCJet_Pt = cjets[0].pT();
        leadCJet_xF = 2*fabs(cjets[0].pz()/GeV)/13000.;
      }

      // ------------------------------------------ hist fill ---------------------------------------------------------
      if (pass_fid1B){
        _h["fiducial1B_Z_Pt"]->fill(Z_pT/GeV);
        _h["fiducial1B_leadBJet_Pt"]->fill(leadBJet_Pt/GeV);
        _h["fiducial1B_ZleadBJet_DR"]->fill(ZleadBJet_DR);
      }

      if (pass_fid2B){
        _h["fiducial2B_diBJets_M"]->fill(diBJets_M/GeV);
        _h["fiducial2B_diBJets_DPhi"]->fill(diBJets_DPhi);
      }

      if (pass_fid1C){
        _h["fiducial1C_Z_Pt"]->fill(Z_pT/GeV);
        _h["fiducial1C_leadCJet_Pt"]->fill(leadCJet_Pt/GeV);
        _h["fiducial1C_leadCJet_xF"]->fill(leadCJet_xF);
      }

      // -------------------------------------------------------------------------------------------------------------

    }


    /// Normalise histograms etc., after the run
    void finalize() {
      const double sf = _mode? 1.0 : 0.5;
      scale(_h, sf * crossSectionPerEvent());
    }

  private:

     size_t _mode;

    map<string, Histo1DPtr> _h;

  };


  RIVET_DECLARE_PLUGIN(ATLAS_2024_I2771257);

}