Rivet analyses


title: CMS_2026_I3132411

Jet mass of W bosons decaying to qq in pp collisions at 13 TeV

Experiment: CMS (LHC)

Inspire ID: 3132411

Status: VALIDATED

Authors: - cms-pag-conveners-smp@cern.ch - Andreas Hinzmann

References: - arXiv: 2603.19963 - Expt page: CMS-SMP-24-012 - submitted to JHEP

Beams: p+ p+

Beam energies: (6500.0, 6500.0)GeV

Run details: - pp to W+jets to all-jets final state at $\sqrt{s}=13$ TeV. Data collected by CMS during the years 2016-2018. Set MATCHING=TRUE to enable "W-match" distributions where the q/g combinatorial background is removed.

The jet mass of W bosons decaying to a quark-antiquark pair is measured in W+jets events from proton-proton collisions at a center-of-mass energy of 13 TeV. The data used were collected by the CMS experiment at the CERN LHC and correspond to an integrated luminosity of 138 fb. Hadronic decays of W bosons with high momenta produce strongly collimated decay products due to the large Lorentz boost, and are reconstructed as single large-radius jets. These jets have a characteristic substructure that is exploited to distinguish them from the large background of quark- and gluon-initiated jets. The jet mass is computed using the soft-drop algorithm, which suppresses soft wide-angle radiation that leads to a broadening of the jet mass distribution. For the first time, unfolded measurements are presented of the double-differential W+jets cross section as a function of the jet transverse momentum and soft-drop mass.

Source code:CMS_2026_I3132411.cc

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

include "Rivet/Analysis.hh"

include "Rivet/Projections/FastJets.hh"

include "Rivet/Projections/FinalState.hh"

include "fastjet/contrib/EnergyCorrelator.hh"

include "fastjet/contrib/SoftDrop.hh"

include

namespace Rivet {

/// @brief Boosted W jet mass at 13 TeV class CMS_2026_I3132411 : public Analysis { public:

/// Constructor
RIVET_DEFAULT_ANALYSIS_CTOR(CMS_2026_I3132411);


/// @name Analysis methods
/// @{

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

  _doMatching = getOption("MATCHING", false);

  // Initialise and register projections
  FinalState fs(Cuts::abseta < 5 && Cuts::pT > 0 * GeV);

  declare(FastJets(fs, JetAlg::ANTIKT, 0.8, JetMuons::ALL, JetInvisibles::NONE), "JetsAK8");

  // Book histograms
  // resize vectors appropriately
  uint nHistsPt = _ptBinsGen.size() + 1; // including inclusive bin
  _h_wjm.resize(nHistsPt);
  _h_wjm_n2.resize(nHistsPt);
  if (_doMatching) {
    _h_wjm_match.resize(nHistsPt);
    _h_wjm_n2_match.resize(nHistsPt);
  }
  // Now book histos and map to numbering in HepData
  const vector<int> hepdataindex = {6, 7, 8, 9};
  const vector<int> hepdataindex_n2 = {5, 1, 2, 3};
  for (uint ptInd = 0; ptInd < nHistsPt; ptInd++) {
    book(_h_wjm[ptInd], hepdataindex[ptInd], 1, 1);
    book(_h_wjm_n2[ptInd], hepdataindex_n2[ptInd], 1, 1);
    if (_doMatching) {
      const vector<int> hepdataindex_match = {14, 15, 16, 17};
      const vector<int> hepdataindex_n2_match = {13, 10, 11, 12};
      book(_h_wjm_match[ptInd], hepdataindex_match[ptInd], 1, 1);
      book(_h_wjm_n2_match[ptInd], hepdataindex_n2_match[ptInd], 1, 1);
    }
  }
}


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

  const Jets& jets = apply<FastJets>(event, "JetsAK8").jetsByPt(Cuts::pT > 650 * GeV);
  if (jets.empty()) vetoEvent;

  const Jet& rjet1 = jets[0];
  const PseudoJet& jet1 = rjet1.pseudojet();

  fastjet::contrib::SoftDrop sd(0, 0.1, 0.8);
  PseudoJet groomedJet = sd(jet1);

  auto itr = std::lower_bound(_ptBinsGen.begin(), _ptBinsGen.end(), jet1.pt());
  uint ptBinInd = itr - _ptBinsGen.begin() - 1;
  _h_wjm[0]->fill(groomedJet.m() / GeV); // inclusive bin
  _h_wjm[ptBinInd + 1]->fill(groomedJet.m() / GeV);

  fastjet::contrib::EnergyCorrelatorN2 N2(1, fastjet::contrib::EnergyCorrelator::pt_R);
  double n2 = N2(jet1);
  if (n2 < 0.2) {
    _h_wjm_n2[0]->fill(groomedJet.m() / GeV); // inclusive bin
    _h_wjm_n2[ptBinInd + 1]->fill(groomedJet.m() / GeV);
  }

  // for matching studies
  if (_doMatching) {
    bool match = false;
    std::vector<Particle> genWs = event.allParticles(Cuts::abspid == 24);
    if (genWs.size() > 0) {
      Particle genW = genWs[0];
      while (genW.children().size() > 0 && genW.children().size() < 2)
        genW = genW.children()[0]; // find last copy of W
      if (genW.children().size() == 2) {
        Particle genQ1 = genW.children()[0];
        Particle genQ2 = genW.children()[1];
        double dR1 = deltaR(genQ1, rjet1);
        double dR2 = deltaR(genQ2, rjet1);
        match = ((dR1 < 0.8) && (dR2 < 0.8));
      }
    }
    if (match) {
      _h_wjm_match[0]->fill(groomedJet.m() / GeV); // inclusive bin
      _h_wjm_match[ptBinInd + 1]->fill(groomedJet.m() / GeV);
      if (n2 < 0.2) {
        _h_wjm_n2_match[0]->fill(groomedJet.m() / GeV); // inclusive bin
        _h_wjm_n2_match[ptBinInd + 1]->fill(groomedJet.m() / GeV);
      }
    }
  }
}

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

  const double sf = crossSection() / femtobarn / sumOfWeights();
  scale(_h_wjm, sf);
  scale(_h_wjm_n2, sf);
  if (_doMatching) {
    scale(_h_wjm_match, sf);
    scale(_h_wjm_n2_match, sf);
  }
}

/// @}


/// @name Histograms
/// @{
const std::vector<float> _ptBinsGen = {650, 800, 1200};

bool _doMatching;

// since each pt bin has its own normalised distribution
vector<Histo1DPtr> _h_wjm;
vector<Histo1DPtr> _h_wjm_n2;
vector<Histo1DPtr> _h_wjm_match;
vector<Histo1DPtr> _h_wjm_n2_match;

/// @}

};

RIVET_DECLARE_PLUGIN(CMS_2026_I3132411);

} ```