Rivet analyses

ttbar + gamma at 13 TeV

Experiment: ATLAS (LHC)

Inspire ID: 2768921

Status: VALIDATED

Authors: - Carmen Diez Pardos - Stefanie Mueller

References: - Expt page: ATLAS-TOPQ-2018-24 - arXiv: 2403.09452

Beams: p+ p+

Beam energies: (6500.0, 6500.0)GeV

Run details: - pp -> ttbar + photon at 13TeV

Inclusive and differential cross-sections are measured at particle level for the associated production of a top quark pair and a photon (tγ). The analysis is performed using an integrated luminosity of 140 fb−1 of proton-proton collisions at a centre-of-mass energy of 13 TeV collected by the ATLAS detector. The measurements are performed in the single-lepton and dilepton top quark pair decay channels focusing on tγ topologies where the photon is radiated from an initial-state parton or one of the top quarks. The absolute and normalised differential cross-sections are measured for several variables characterising the photon, lepton and jet kinematics as well as the angular separation between those objects. The observables are found to be in good agreement with the Monte Carlo predictions. The photon transverse momentum differential distribution is used to set limits on effective field theory parameters related to the electroweak dipole moments of the top quark. The combined limits using the photon and the Z boson transverse momentum measured in t production in associations with a Z boson are also set.

Source code:ATLAS_2024_I2768921.cc

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

namespace Rivet {

  /// @brief ttbar + gamma at 13 TeV
  class ATLAS_2024_I2768921 : public Analysis {
  public:


    // Constructor
    RIVET_DEFAULT_ANALYSIS_CTOR(ATLAS_2024_I2768921);


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

      // Set running mode (default is both channels)
      _mode = 3;
      if ( getOption("LMODE") == "SINGLE" )   _mode = 1;
      if ( getOption("LMODE") == "DILEPTON" ) _mode = 2;
      if ( getOption("LMODE") == "ALL" )      _mode = 3;

      // Cuts
      Cut eta_full = Cuts::abseta < 5.0;
      Cut lepCuts  = Cuts::abseta < 2.5 && Cuts::pT > 25.0*GeV;
      Cut lepCutsVeto = Cuts::abseta < 2.5 && Cuts::pT > 7.0*GeV;

      // Charged particles for signal photon isolation
      declare(ChargedFinalState(), "CFS");

      // Signal photons
      PromptFinalState photons(Cuts::abspid == PID::PHOTON && Cuts::pT > 15*GeV && Cuts::abseta < 2.5);
      declare(photons, "Photons");

      // Invisibles
      InvisibleFinalState prompt_invis(OnlyPrompt::YES, TauDecaysAs::PROMPT);

      // Leptons
      PromptFinalState leptons(Cuts::abspid == PID::MUON || Cuts::abspid == PID::ELECTRON, TauDecaysAs::PROMPT);

      // Dress the leptons
      FinalState dressPhotons(Cuts::abspid == PID::PHOTON);
      LeptonFinder dressedLeptons(leptons, dressPhotons, 0.1, lepCuts);
      declare(dressedLeptons, "DressedLeptons");
      // Full dressed leptons
      LeptonFinder fulldressedleptons(leptons, dressPhotons, 0.1, eta_full);

      // Add new lepton collection for veto of 2nd lepton pt > 7 GeV
      LeptonFinder vetodressedLeptons(leptons, dressPhotons, 0.1, lepCutsVeto);
      declare(vetodressedLeptons, "VetoDressedLeptons");

      // Jet alg input
      VetoedFinalState vfs;
      vfs.addVetoOnThisFinalState(fulldressedleptons);
      vfs.addVetoOnThisFinalState(prompt_invis);

      // Jet clustering
      FastJets jets(vfs, JetAlg::ANTIKT, 0.4, JetMuons::ALL, JetInvisibles::ALL);
      declare(jets, "Jets");

      dualbook("sldl_ph_pt",     18);
      dualbook("sldl_ph_abseta", 20);

      // Book histograms
      if (_mode == 1 || _mode == 3) { // Single lepton channel
        dualbook("sl_ph_bjet_dR",  6);
        dualbook("sl_ph_l_dR",     8);
        dualbook("sl_l_jet_dR",   10);
        dualbook("sl_ph_pt",      44);
        dualbook("sl_ph_abseta",  46);
        dualbook("sl_jet1_pt",    48);
      }

      if (_mode == 2 || _mode == 3) { // Dilepton channel
        dualbook("dl_ph_bjet_dR", 12);
        dualbook("dl_ph_l_dR",    14);
        dualbook("dl_l_jet_dR",   16);
        dualbook("dl_ph_pt",      50);
        dualbook("dl_ph_abseta",  52);
        dualbook("dl_jet1_pt",    54);
      }
    }


    void analyze(const Event& event) {

      FourMomentum lepton(0.,0.,0.,0.);

      // Fetch objects
      Particles photons          = apply<PromptFinalState>(event, "Photons").particlesByPt();
      DressedLeptons leptons     = apply<LeptonFinder>(event, "DressedLeptons").dressedLeptons();
      DressedLeptons vetoleptons = apply<LeptonFinder>(event, "VetoDressedLeptons").dressedLeptons();
      Jets jets                  = apply<FastJets>(event, "Jets").jetsByPt(Cuts::abseta < 2.5 && Cuts::pT > 25*GeV);


      // Immediate veto on events without photon
      if (photons.empty())  vetoEvent;

      // Overlap removal of leptons near jets
      idiscardIfAnyDeltaRLess(leptons, jets, 0.4);
      idiscardIfAnyDeltaRLess(vetoleptons, jets, 0.4);

      // Photon isolation with overlap removal of jets near isolated photon
      Particles iso_photons;
      const ChargedFinalState& chargedFS = apply<ChargedFinalState>(event, "CFS");
      for (const Particle& ph : photons) {
        const double conePt = sum(chargedFS.particles(deltaRLess(ph, 0.2)), Kin::pT, 0.0);
        if (conePt/ph.pT() < 0.05) {
          iso_photons += ph;
          idiscard(jets, deltaRLess(ph, 0.4));
        }
      }

      // Veto on events without one good photon
      if (iso_photons.size() != 1) vetoEvent;

      // Require at least one bjet
      Jets bjets;
      for (const Jet& jet : jets) {
       if (jet.bTagged(Cuts::pT > 5*GeV)) bjets += jet;
      }
      if (bjets.empty()) vetoEvent;


      const Particle& photon = iso_photons[0];
      // Apply cuts to isolated photon, after requiring exactly 1 photon
      if (photon.pT() <= 20*GeV || photon.abseta() >= 2.37) vetoEvent;

      // Veto event if photon too close to a lepton
      if ( any(leptons, deltaRLess(photon, 0.4)) )  vetoEvent;


      // Fill histos in single lepton channel, last bin contains overflow events
      if (_mode == 1 || _mode == 3) {
        if (jets.size() >= 4 && leptons.size() == 1 && vetoleptons.size() < 2) {

          double mindR_ph_l = 1e3;
          for (const DressedLepton& lep : leptons) {
            double dr = deltaR(photon, lep);
            if (dr <  mindR_ph_l)  mindR_ph_l = dr;
          }
          if (mindR_ph_l >= 3.4)  mindR_ph_l = 3.;

          // dRmin(photon,bjet)
          double mindR_ph_bjet = 1e3;
          for (const Jet& bjet : bjets) {
            double dr = deltaR(bjet, photon);
            if (dr < mindR_ph_bjet) mindR_ph_bjet = dr;
          }
          if (mindR_ph_bjet >= 3.4) mindR_ph_bjet = 3.;

          //dRmin(lepton,jet)
          double mindR_l_jet = 1e3;
          for (const DressedLepton& lep : leptons) {
            for (const Jet& jet : jets) {
              double dr = deltaR(lep, jet);
              if (dr < mindR_l_jet) mindR_l_jet = dr;
            }
          }
          if (mindR_l_jet >= 3.4) mindR_l_jet = 3.;

          double ph_pt = photon.pT();
          if (ph_pt >= 500) ph_pt = 450.;

          double jet1_pt = jets[0].pT();
          if (jet1_pt >= 450.) jet1_pt = 405;

          dualfill("sl_ph_pt",       ph_pt/GeV);
          dualfill("sl_ph_abseta",   photon.abseta());
          dualfill("sl_ph_l_dR",     mindR_ph_l);
          dualfill("sl_ph_bjet_dR",  mindR_ph_bjet);
          dualfill("sl_l_jet_dR",    mindR_l_jet);
          dualfill("sl_jet1_pt",     jet1_pt/GeV);
          dualfill("sldl_ph_pt",     ph_pt/GeV);
          dualfill("sldl_ph_abseta", photon.abseta());

        }
      }


      // Fill histos in dilepton channel, last bin contains overflow events
      if ( _mode == 2 || _mode == 3 ) {
        if ( jets.size() >= 2 && leptons.size() == 2 && vetoleptons.size() < 3) {

          // dRmin(photon, lepton)
          double mindR_ph_l = 1e3;
          for (const DressedLepton& lep : leptons) {
            double dr = deltaR(lep, photon);
            if (dr < mindR_ph_l)  mindR_ph_l = dr;
          }
          if (mindR_ph_l >= 3.4)  mindR_ph_l = 3.;

          // dRmin(photon,bjet)
          double mindR_ph_bjet = 1e3;
          for (const Jet& bjet : bjets) {
            double dr = deltaR(bjet, photon);
            if (dr < mindR_ph_bjet ) mindR_ph_bjet = dr;
          }
          if (mindR_ph_bjet >= 3.4) mindR_ph_bjet = 3.;

          //dRmin(lepton,jet)
          double mindR_l_jet = 999.0;
          for (const DressedLepton& lep :leptons) {
            for (const Jet& jet : jets) {
             double dr = deltaR(lep, jet);
             if (dr < mindR_l_jet)  mindR_l_jet = dr;
            }
          }
          if (mindR_l_jet >= 3.4) mindR_l_jet = 3.;

          double ph_pt = photon.pT();
          if (ph_pt >= 500.) ph_pt = 450.;

          double jet1_pt = jets[0].pT();
          if (jet1_pt >= 450.) jet1_pt = 405.;

          dualfill("dl_ph_pt",       ph_pt/GeV);
          dualfill("dl_ph_abseta",   photon.abseta());
          dualfill("dl_ph_l_dR",     mindR_ph_l);
          dualfill("dl_ph_bjet_dR",  mindR_ph_bjet);
          dualfill("dl_l_jet_dR",    mindR_l_jet);
          dualfill("dl_jet1_pt",     jet1_pt/GeV);
          dualfill("sldl_ph_pt",     ph_pt/GeV);
          dualfill("sldl_ph_abseta", photon.abseta());

        }
      }
    }



    void finalize() {

      // Normalise histogram to absolute fiducial cross section or to unity
      scale(_h, crossSection() / femtobarn / sumOfWeights());
      normalize(_n, 1.0, true);

    }


    void dualbook(const string& name, unsigned int id) {
      book(_h["abs_"+name],    id, 1, 1);
      book(_n["norm_"+name], 1+id, 1, 1);
    }

    void dualfill(const string& name, const double value) {
      _h["abs_"+name]->fill(value);
      _n["norm_"+name]->fill(value);
    }

  private:

    /// @name Histograms
    map<string, Histo1DPtr> _h, _n;

    size_t _mode;

  };

  // The hook for the plugin system
  RIVET_DECLARE_PLUGIN(ATLAS_2024_I2768921);
}