Rivet analyses

γγ production at 13 TeV

Experiment: ATLAS (LHC)

Inspire ID: 1887997

Status: VALIDATED

Authors: - Frank Siegert

References: - arXiv: 2107.09330

Beams: p+ p+

Beam energies: (6500.0, 6500.0)GeV

Run details: - pp -> yy production at 13 TeV

Fiducial and differential measurements of γγ production. The photons are required to be isolated and have a transverse momentum of pT > 40(30) GeV for the leading (sub-leading) photon. The differential cross sections as functions of several observables for the diphoton system are measured.

Source code:ATLAS_2021_I1887997.cc

// -*- C++ -*-
#include "Rivet/Analysis.hh"
#include "Rivet/Projections/PromptFinalState.hh"
#include "Rivet/Projections/VisibleFinalState.hh"
#include "Rivet/Projections/VetoedFinalState.hh"
#include "Rivet/Math/MathUtils.hh"
#include "Rivet/Projections/FastJets.hh"

namespace Rivet {


  /// @brief Isolated diphoton + X differential cross-sections with full run-2
  class ATLAS_2021_I1887997 : public Analysis {
  public:

    // Constructor
    RIVET_DEFAULT_ANALYSIS_CTOR(ATLAS_2021_I1887997);


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

      // Calorimeter particles for photon isolation
      VisibleFinalState visFS;
      VetoedFinalState calo_fs(visFS);
      calo_fs.addVetoPairId(PID::MUON);
      declare(calo_fs, "calo_fs");

      // Photons
      declare(PromptFinalState(Cuts::abspid == PID::PHOTON), "Photons");

      // Jets for UE subtraction with jet-area method
      FastJets fj(FinalState(), JetAlg::KT, 0.5, JetMuons::NONE, JetInvisibles::NONE);
      fj.useJetArea(new fastjet::AreaDefinition(fastjet::VoronoiAreaSpec(0.9)));
      declare(fj, "KtJetsD05");

      // Histograms
      book(_xs, "yy_xs");
      _observables = {"ph1_pt","ph2_pt","yy_cosTS","yy_m",
                      "yy_phiStar","yy_piMDphi","yy_pT","yy_pTt"};
      for (auto name : _observables) {
        book(_h[name], name);
      }
    }

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

      // Require at least 2 prompt photons in final state
      Particles photons = apply<PromptFinalState>(event, "Photons").particlesByPt();
      if (photons.size() < 2) vetoEvent;
      photons.resize(2);
      const FourMomentum ph1 = photons[0];
      const FourMomentum ph2 = photons[1];

      // Leading photon should have pT > 40 GeV, subleading > 30 GeV
      const double ph1_pt = ph1.pT();
      const double ph2_pt = ph2.pT();
      if (ph1_pt < 40.*GeV || ph2_pt < 30.*GeV) vetoEvent;

      // Apply photon eta cuts
      iselect(photons, (Cuts::abseta < 2.37) && ( (Cuts::abseta <= 1.37) || (Cuts::abseta >= 1.52) ));
      if (photons.size() < 2) vetoEvent;

      // Require the two photons to be separated in dR
      if (deltaR(ph1,ph2) < 0.4) vetoEvent;

      // Get UE pt densities rho for subtraction later
      const vector<double> eta_bins = {0.0, 1.5, 3.0};
      vector<double> rho(eta_bins.size()-1, 0.0);
      FastJets ktjets = apply<FastJets>(event, "KtJetsD05");
      for (size_t ieta = 0; ieta < eta_bins.size()-1; ++ieta) {
        fastjet::Selector fjselector(fastjet::SelectorAbsRapRange(eta_bins[ieta], eta_bins[ieta+1]));
        double sigma, area;
        ktjets.clusterSeqArea()->get_median_rho_and_sigma(fjselector, true,
                                                          rho[ieta], sigma, area);
      }

      // Loop over photons and require isolation
      const double isoRCone=0.2;
      for (const Particle& photon : photons) {
        // Compute calo isolation via particles within a cone around the photon
        const Particles fs = apply<VetoedFinalState>(event, "calo_fs").particles();
        FourMomentum mom_in_EtCone;
        for (const Particle& p : fs) {
          // Reject if not in cone
          if (deltaR(photon.momentum(), p.momentum()) > isoRCone)  continue;
          // Sum momentum
          mom_in_EtCone += p.momentum();
        }
        // subtract core photon
        mom_in_EtCone -= photon.momentum();
        // UE subtraction energy
        double UEpT = M_PI*sqr(isoRCone) * rho[binIndex(fabs(photon.eta()), eta_bins)];
        // Use photon if energy in isolation cone is low enough
        if (mom_in_EtCone.Et() - UEpT > 0.09*photon.momentum().pT()) vetoEvent;
      }

      map<string, double> obs;
      obs["ph1_pt"] = ph1_pt;
      obs["ph2_pt"] = ph2_pt;
      const FourMomentum yy = ph1 + ph2;
      obs["yy_m"] = yy.mass();
      obs["yy_pT"] = yy.pT();
      obs["yy_piMDphi"] = PI-mapAngle0ToPi(ph1.phi() - ph2.phi());

      obs["yy_cosTS"] = fabs(sinh(( ph1.eta() - ph2.eta() ))*2.0*ph1_pt*ph2_pt/sqrt(sqr(obs["yy_m"])+sqr(obs["yy_pT"]))/obs["yy_m"]); // Collins Soper frame
      const double yy_cosTSLab = fabs(tanh(( ph1.eta() - ph2.eta() ) / 2.)); // Lab frame
      const double sinthetastar_ = sqrt(1. - pow(yy_cosTSLab, 2));
      obs["yy_phiStar"] = tan(0.5 * obs["yy_piMDphi"]) * sinthetastar_;

      // a_t
      const Vector3 t_hat(ph1.x()-ph2.x(), ph1.y()-ph2.y(), 0.);
      const double factor = t_hat.mod();
      const Vector3 t_hatx(t_hat.x()/factor, t_hat.y()/factor, t_hat.z()/factor);
      const Vector3 At(ph1.x()+ph2.x(), ph1.y()+ph2.y(), 0.);
      // Compute a_t transverse component with respect to t_hat
      obs["yy_pTt"] = At.cross(t_hatx).mod();

      // Fill fiducial cross section
      _xs->fill();

      // Fill histograms
      for (auto name : _observables) {
        _h[name]->fill(obs[name]);
      }
    }


    // Normalise histograms etc., after the run
    void finalize() {
      const double sf = crossSection() / (picobarn * sumOfWeights());
      scale(_xs, sf);
      scale(_h, sf);
    }


  private:

    CounterPtr _xs;
    map<string, Histo1DPtr> _h;
    vector<string> _observables;

  };


  RIVET_DECLARE_PLUGIN(ATLAS_2021_I1887997);

}