Rivet analyses


title: ATLAS_2024_I2768921

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\bar{t}\gamma$). 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\bar{t}\gamma$ 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\bar{t}$ production in associations with a $Z$ boson are also set.

Source code:ATLAS_2024_I2768921.cc

```c++ // -- C++ --

include "Rivet/Analysis.hh"

include "Rivet/Projections/ChargedFinalState.hh"

include "Rivet/Projections/FastJets.hh"

include "Rivet/Projections/FinalState.hh"

include "Rivet/Projections/InvisibleFinalState.hh"

include "Rivet/Projections/LeptonFinder.hh"

include "Rivet/Projections/PromptFinalState.hh"

include "Rivet/Projections/VetoedFinalState.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); } ```