Rivet analyses
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++ -*-
#include "Rivet/Analysis.hh"
#include "Rivet/Projections/FinalState.hh"
#include "Rivet/Projections/FastJets.hh"
#include "fastjet/contrib/SoftDrop.hh"
#include "fastjet/contrib/EnergyCorrelator.hh"
#include <algorithm>
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);
}