Rivet analyses

Fiducial cross section measurement of WZγ production in the leptonic final state at 13 TeV

Experiment: CMS (LHC)

Inspire ID: 2905870

Status: VALIDATED NOHEPDATA

Authors: - cms-pag-conveners-smp@cern.ch - Sen Deng

References: - Expt page: CMS-SMP-22-018 - arxiv:2503.21977 - Phys.Rev.D 112 (2025) 1, 012009

Beams: p+ p+

Beam energies: (6500.0, 6500.0)GeV

Run details: - Production of WZgamma in leptonic channels. Data collected by the CMS experiment in 2016, 2017 and 2018.

The WZγ production cross section in leptonic channels is measured in a fiducial volume at the particle level. Cuts: eee/μμμ/eeμ/eμμ final state, with at least one opposite sign same flavor pair to reconstruct a Z boson candidate. In cases where multiple OSSF lepton combinations are possible, the pair with an invariant mass closest to 91.188 GeV is selected. Leptons which originate from tau leptons are not included; using dressed leptons with ΔR < 0.1; lepton pT > 15 GeV, |η| < 2.5; photon pT > 15 GeV, |η| < 2.5; ΔR(γ, ) > 0.3; Z candidate 60 < mZ < 120 GeV

Source code:CMS_2025_I2905870.cc

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

namespace Rivet {


  /// @brief WZgamma production in 3-lepton channels at 13 TeV
  class CMS_2025_I2905870 : public Analysis {
  public:

    /// Constructor
    RIVET_DEFAULT_ANALYSIS_CTOR(CMS_2025_I2905870);


    /// @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
      const FinalState fs(Cuts::abseta < 4.9);
      declare(fs, "finalstate");

      // The final-state particles declared above are clustered using FastJet with
      // the anti-kT algorithm and a jet-radius parameter 0.4
      // muons and neutrinos are excluded from the clustering
      FastJets jetfs(fs, JetAlg::ANTIKT, 0.4, JetMuons::NONE, JetInvisibles::NONE);
      declare(jetfs, "jets");

      // FinalState of prompt photons and bare muons and electrons in the event
      PromptFinalState photons(Cuts::abspid == PID::PHOTON);
      PromptFinalState bare_leps(Cuts::abspid == PID::MUON || Cuts::abspid == PID::ELECTRON);

      // Dress the bare prompt leptons with prompt photons within dR < 0.1,
      // and apply some fiducial cuts on the dressed leptons
      Cut lepton_cuts = Cuts::abseta < 2.5 && Cuts::pT > 15*GeV;
      // Prompt photons used for 'dressed leptons'
      PromptFinalState prompt_photons(Cuts::abspid == PID::PHOTON);
      prompt_photons.acceptMuonDecays(true);
      prompt_photons.acceptTauDecays(true);
      LeptonFinder dressed_leps(bare_leps, prompt_photons, 0.1, lepton_cuts);
      declare(prompt_photons, "prompt_photons");
      declare(photons, "photons");
      declare(dressed_leps, "leptons");

      // Book histograms
      // specify custom binning
      // take binning from reference data using HEPData ID (digits in "d01-x01-y01" etc.)
      book(_h_xsec_exp, 7, 1, 1);
    }


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

      // Retrieve dressed leptons, sorted by pT
      Particles leptons = apply<LeptonFinder>(event, "leptons").particlesByPt();
      Particles muons = select(leptons, Cuts::abspid == PID::MUON);
      Particles electrons = select(leptons, Cuts::abspid == PID::ELECTRON);
      Particles photons = apply<PromptFinalState>(event,"photons").particlesByPt(Cuts::abseta < 2.5 && Cuts::pT > 15*GeV);
      const Particles& fsparticles = apply<FinalState>(event, "finalstate").particles();
      Particles iso_photons;

      if (leptons.size() != 3) vetoEvent;

      if (photons.size() == 0) vetoEvent;

      // Overlap removal: discard photons too close to any lepton
      double _lepton_photon_dr_cut = 0.3;
      idiscardIfAnyDeltaRLess(photons, leptons, _lepton_photon_dr_cut);

      if (photons.size() == 0) vetoEvent;

      // photon et and dr cut
      double _phoIsoConeSize = 0.4;
      double _phoMaxRelIso = 0.5;
      for (const auto& photon : photons) {

        double photonptsum = 0;
        for (const auto& fsparticle : fsparticles) {
          if (isSame(fsparticle, photon))  continue;

          if (deltaR(fsparticle, photon) > _phoIsoConeSize)  continue;

          photonptsum += fsparticle.pt();
        }

        if (photonptsum/photon.pt() > _phoMaxRelIso)  continue;

        iso_photons.push_back(photon);
      }

      if (iso_photons.size() == 0) vetoEvent;

      // extract mZ
      double mZ = -1;
      double temp_mZ1 = -1;
      double temp_mZ2 = -1;
      // emumu and muee final states
      if ((muons.size() == 2) && (electrons.size() == 1)) {
        if (muons[0].pid() * muons[1].pid() > 0) vetoEvent;
        mZ = (muons[0].mom() + muons[1].mom()).mass();
      }
      if ((muons.size() == 1) && (electrons.size() == 2)) {
        if (electrons[0].pid() * electrons[1].pid() > 0) vetoEvent;
        mZ = (electrons[0].mom() + electrons[1].mom()).mass();
      }

      // eee and mumumu final states
      if (muons.size() == 3) {
        if ((muons[0].pid() * muons[1].pid() > 0) && (muons[0].pid() * muons[2].pid() > 0)) vetoEvent;
        if (muons[0].pid() * muons[1].pid() < 0) {
          temp_mZ1 = (muons[0].mom() + muons[1].mom()).mass();
          if (muons[0].pid() * muons[2].pid() < 0) {
            temp_mZ2 = (muons[0].mom() + muons[2].mom()).mass();
          } else {
            temp_mZ2 = (muons[1].mom() + muons[2].mom()).mass();
          }
        } else {
          temp_mZ1 = (muons[0].mom() + muons[2].mom()).mass();
          temp_mZ2 = (muons[1].mom() + muons[2].mom()).mass();
        }
        if (fabs(temp_mZ1 - 91.188*GeV) > fabs(temp_mZ2 - 91.188*GeV)){
          mZ = temp_mZ2;
        } else {
          mZ = temp_mZ1;
        }
      }

      if (electrons.size() == 3) {
        if ((electrons[0].pid() * electrons[1].pid() > 0) && (electrons[0].pid() * electrons[2].pid() > 0)) vetoEvent;
        if (electrons[0].pid() * electrons[1].pid() < 0) {
          temp_mZ1 = (electrons[0].mom() + electrons[1].mom()).mass();
          if (electrons[0].pid() * electrons[2].pid() < 0) {
            temp_mZ2 = (electrons[0].mom() + electrons[2].mom()).mass();
          } else {
            temp_mZ2 = (electrons[1].mom() + electrons[2].mom()).mass();
          }
        } else {
          temp_mZ1 = (electrons[0].mom() + electrons[2].mom()).mass();
          temp_mZ2 = (electrons[1].mom() + electrons[2].mom()).mass();
        }
        if (fabs(temp_mZ1 - 91.188*GeV) > fabs(temp_mZ2 - 91.188*GeV)){
          mZ = temp_mZ2;
        } else {
          mZ = temp_mZ1;
        }
      }

      if ((mZ > 120*GeV) || (mZ < 60*GeV)) vetoEvent;

      _h_xsec_exp->fill();

    }


    /// Normalise histograms etc., after the run
    void finalize() {

      scale(_h_xsec_exp, crossSection() / femtobarn / sumOfWeights()); // norm to generated cross-section (after cuts)

    }

    /// @}


    /// @name Histograms
    /// @{
    CounterPtr _h_xsec_exp;
    /// @}


  };


  RIVET_DECLARE_PLUGIN(CMS_2025_I2905870);

}