Skip to content

Recoil calibration for the V+jets backgrounds, plus two options and a nuisance naming fix - #716

Open
davidwalter2 wants to merge 5 commits into
WMass:mainfrom
davidwalter2:260825_recoil
Open

Recoil calibration for the V+jets backgrounds, plus two options and a nuisance naming fix#716
davidwalter2 wants to merge 5 commits into
WMass:mainfrom
davidwalter2:260825_recoil

Conversation

@davidwalter2

Copy link
Copy Markdown
Collaborator

Summary

The recoil calibration was applied to the W signal only. This branch applies it to the V+jets backgrounds as well, fixes two bugs found along the way, and adds two options used to study the calibration.

Backgrounds in the calibration (88394e5a)

wprocs_recoil now contains Wtaunu, Ztautau, Zmumu and Zmumu10to50 in addition to the signal. The proxy for the boson differs per process:

process invisible momentum boson proxy
W→μν gen ν gen ν + reco μ
W→τν, Z→ττ sum of all post-FSR neutrinos Σν + reco μ
Z→μμ partner muon, if it is not in the MET gen partner μ + reco μ

For the tau processes the charge partner used for W→μν picks up the muon neutrino only and misses the tau neutrinos (|pdgId| = 16), hence the sum over postfsrNeutrinos_mom4.

For Z→μμ the partner muon is subtracted from the MET like the selected muon whenever it is inside the tracker acceptance: it leaves a track and is part of the PF MET even when it fails the muon reconstruction, the CVH refit or the veto selection, and also for 2.4 < |η| < 2.5 where there is a track but no muon. Treating it as invisible instead gave a large tail in the residual above qT = 50 GeV (mean +16 instead of +7 GeV, rms 25 instead of 20, 6.6% instead of 1.2% beyond |u| = 60 GeV).

Effect on the mT categories of the extended ABCD (mt_corr_rec / mt_corr_xy, isolated region):

process mT<20 20-40 >40
Wminusmunu / Wplusmunu 0.875 / 0.918 0.932 / 0.926 1.003 / 1.003
Wminustaunu / Wplustaunu 0.934 / 0.951 0.963 / 0.944 1.015 / 1.016
Ztautau 0.891 1.008 1.101
Zmumu 0.911 0.970 1.014

The recoil uncertainty in setupRabbit is applied to the processes for which the variations are stored instead of to the signal samples only, so it follows the list above while older histmaker outputs keep working.

Bug fixes

  • v_gen_phi was aliased to the proxy pt instead of the proxy phi in setup_gen_reco_vars_Z, corrupting the gen recoil columns of the Z path (88394e5a)
  • the wlike histmaker booked nominal_noSF unconditionally although nominal_weight_noSF is only defined together with the auxiliary histograms, so --onlyMainHistograms crashed at graph build (88394e5a)
  • recoil_stat and recoil_syst both used systAxes = ["recoil_unc"] and therefore produced nuisances with the same names. Since rabbit collects the names in a set and overwrites dict_logk, the two background variations silently replaced the stat variations 0 and 1, and both names ended up in both impact groups: the fit of 260609_abcd has 1100 parameters matching recoil for 1102 booked variations. They are now recoil_stat[0-1099] and recoil_calibration_bkg[0-1] (8534fbac). This renames the parameters, so pulls and impacts of older fits cannot be compared by name.

Two options, both off by default

  • --recoilQtMax (0495eaa9) evaluates the calibration at min(ptV, RECOILQTMAX). The calibration is derived from Z events and has no statistics above ~150 GeV, where the model extrapolates: for DeepMETPVRobust the response correction at ut_para+qT = 0 grows from +3.4 GeV at qT = 80 to +6.8 at 120, +13.1 at 150, +32.1 at 200 and +68.0 at 300, while the slope of the map collapses from 1.05 to 0.12. Only the conditioning variable of the model is capped; the true boson pt and phi are kept for the decomposition, so that the transformation stays invertible. The uncapped calibration is stored as nominal_recoilQtExtrap and added as a mirrored nuisance. Measured with a cap at 100 GeV: the cap affects 0.62% of the events and changes their MET by up to -41 GeV, but the migration matrix between the mT categories is exactly diagonal, so the variation is null in the nominal binning and only becomes relevant with a finer binning in mT.
  • --recoilGenProxy (734ba8b3) applies the calibration in the Z analyses along the gen boson proxy, as done for the W, instead of the reconstructed dilepton. This is a closure test of the W application on Z events; it changes the central value of the Z analyses (corrected MET mean by -2 MeV, transverse mass mean by +0.7 MeV, parallel recoil residual mean by -27 MeV, at most 0.05% migration between W-like MET bins) and adds no variation.

Validation

All numbers above come from histmaker runs on this branch (1 to 300 files per dataset, --recoilHists). The signal templates are unchanged by the refactoring: the mT migration factors of Wplusmunu and Wminusmunu are identical to four decimals before and after.

Not addressed here

The calibration is conditional on the boson pt only, while the residual depends on more than that: in MC the response differs by ~0.7% of qT between W and Z (300 files each, e.g. +0.343 ± 0.045 GeV at qT = 40-50), and by as much between W+ and W- (+0.449 ± 0.024 GeV at qT = 30-40). A transfer/parametrisation uncertainty of that size is still missing.

🤖 Generated with Claude Code

davidwalter2 and others added 5 commits August 25, 2026 14:03
Apply the recoil calibration to Wtaunu, Ztautau, Zmumu and Zmumu10to50 in
addition to the W signal, so that the MET resolution of every process with a
gen boson is corrected to data and not only the one of the signal.

For the tau processes the invisible momentum is the sum of all post-FSR
neutrinos (postfsrNeutrinos_mom4). The charge partner used for W->munu picks
up the muon neutrino only and misses the tau neutrinos, which have
|pdgId| = 16 and are outside the postFSR(anti)leps selection.

For Z->mumu the partner of the selected muon is subtracted from the MET like
the selected muon whenever it is inside the tracker acceptance: it leaves a
track and is therefore part of the PF MET even when it fails the muon
reconstruction, the CVH refit or the veto selection, and also for
2.4 < |eta| < 2.5 where there is a track but no muon. Treating it as invisible
instead gave a large tail in the residual above qt = 50 GeV (mean +16 instead
of +7 GeV, rms 25 instead of 20, 6.6% instead of 1.2% beyond |u| = 60 GeV).

The recoil uncertainty in setupRabbit is applied to the processes for which
the variations are stored instead of to the signal samples only, so that it
follows the list above while older histmaker outputs keep working.

Two bug fixes:
 - v_gen_phi was aliased to the proxy pt instead of the proxy phi in
   setup_gen_reco_vars_Z, corrupting the gen recoil columns of the Z path
   (unused by the wlike nominal, which does not take the doGEN branch)
 - the wlike histmaker booked nominal_noSF unconditionally although
   nominal_weight_noSF is only defined together with the auxiliary
   histograms, so --onlyMainHistograms crashed at graph build

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The calibration is derived from Z events and has no statistics above ~150 GeV
(the vpt reweighting is unity there), so the model extrapolates: for
DeepMETPVRobust the response correction at ut_para+qt = 0 grows from +3.4 GeV
at qt = 80 to +6.8 at 120, +13.1 at 150, +32.1 at 200 and +68.0 at 300, while
the slope of the map collapses from 1.05 to 0.12, i.e. the corrected residual
is squashed onto a point. RawPFMET saturates at 200 GeV instead.

With --recoilQtMax the calibration is evaluated at min(ptV, RECOILQTMAX). Only
the conditioning variable of the model is capped, the true boson pt and phi
are kept for the decomposition and for the MET, so that the transformation
stays invertible. The same calibration evaluated at the uncapped boson pt is
stored as nominal_recoilQtExtrap and added as a mirrored nuisance, covering
the extrapolation. The nuisance is enabled from the presence of the histogram,
so nothing has to be configured in setupRabbit.

Measured with a cap at 100 GeV on one file of Wmunu: the cap affects 0.62% of
the events, changes their MET by up to -41 GeV and their transverse mass by up
to -8.75 GeV, but the migration matrix between the mt categories of the
extended ABCD ([0, 20, 40, inf)) is exactly diagonal and nominal and variation
are identical in all 17280 bins. The variation is therefore expected to be
null in the nominal binning (the transverse mass is by construction
insensitive to the boson pt, so the affected events sit near the Jacobian peak
and far from the category edges) and to become relevant only with a finer
binning in the transverse mass.

Note that the option makes the model run twice per event for the processes
with a calibration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…xy in Z

In the Z analyses the calibration is applied with the reconstructed dilepton,
while for the W it is applied with the gen boson proxy (gen partner of the
selected muon plus the reco muon). The branch to do the same in the Z was
already there but hard coded to False, and it was broken by the v_gen_phi
alias fixed in an earlier commit. With --recoilGenProxy it can be enabled,
which gives a closure test of the W application on Z events.

This changes the central value of the Z analyses, it does not add a
systematic variation. Measured on 15 files of Zmumu in the wlike selection:
the corrected MET mean moves by -2 MeV, the transverse mass mean by +0.7 MeV
and the mean of the parallel recoil residual by -27 MeV, with at most 0.05%
migration between the W-like MET bins. The gen based parametrisation of the
calibration is therefore not a source of bias for the W application.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The nuisances are named from the label of the systematic axis, which defaults
to the axis name, and both recoil_stat and recoil_syst use systAxes =
["recoil_unc"]. Both therefore produced nuisances called recoil_unc0,
recoil_unc1, ... In rabbit book_systematic collects the names in a set and
dict_logk[channel][process][name] is overwritten, so the two background
variations of the calibration silently replaced the stat variations 0 and 1
instead of being added as their own parameters, and the two names ended up in
both the recoil_stat and the recoil_syst impact group. The fit of
260609_abcd has 1100 parameters matching recoil for the 1102 booked
variations, which is the collision.

Give the two sets different labels, so that they are now called
recoil_stat[0-1099] and recoil_calibration_bkg[0-1] (0 is the parallel and 1
the perpendicular component of the background variation). Both the highPU and
the lowPU branch are affected.

This renames the parameters, so pulls and impacts of fits made before this
commit cannot be compared to the new ones by name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The ttbar background was taken from POWHEG+Pythia8 without any correction to
the top quark pt spectrum and with a normalisation uncertainty as its only
systematic. Apply the NNLO QCD + NLO EW over POWHEG+Pythia8 reweighting
parametrised by the TOP PAG (JHEP 1710 (2017) 186),

  SF(pt) = 0.103*exp(-0.0118*pt) - 0.000134*pt + 0.973,  w = sqrt(SF(t)*SF(tbar))

evaluated with the parton level top quarks ('isLastCopy', after radiation and
before decay) and frozen above 500 GeV. Following the recommendations in
https://twiki.cern.ch/twiki/bin/view/CMS/TopPtReweighting the weight is only
applied to the ttbar samples, not to single top, and the size of the
correction itself is taken as its uncertainty, as a mirrored variation built
from the uncorrected prediction (nominal_topPtNNLO).

Measured on the aggregated Top group of the W analysis, the correction is a
-0.33% shift of the normalisation with a shape of about 0.5% across the mt
categories (iso: 0.9924, 0.9944, 0.9977 for mt < 20, 20-40, > 40), to be
compared to the 6% normalisation uncertainty CMS_Top. In the wlike and the
dilepton analyses the correction is -0.29% and -0.23%.

The correction is applied in all three histmakers. The uncertainty is added
only for the W analysis, since it is the only one with a separate Top process
group: in the Z analyses the ttbar sample is part of the 'Other' group, whose
members do not all have the variation, and the uncertainty is not added there
(the group carries a 15% normalisation uncertainty instead).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@cippy cippy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only had a minor comment, otherwise this PR looks good to me (I didn't double check the correctness of all the Defines, but the logic seems correct and I am sure the AI is better than me for that).

Edit: an additional comment, the nominal_weight_noSF was mainly intended to be able to plot the nominal histograms with no scale factors applied (it came up during the journal review), to show the effect of the efficiency corrections on the event yields and better highlight the smallness of the residual discrepancies with respect to the uncorrected case. These plots are probably (or possible to produce) only when using the standard external scale factors. That said, I think it is fine to produce them only when not calling --onlyMainHistograms.

self.df = self.df.Define(
"antilepGen",
"ROOT::Math::PtEtaPhiMVector(GenPart_pt[postFSRantileps][postFSRantilepIdx], GenPart_eta[postFSRantileps][postFSRantilepIdx], GenPart_phi[postFSRantileps][postFSRantilepIdx], GenPart_mass[postFSRantileps][postFSRantilepIdx])",
"gen_partner", "lep_corr_charge == 1 ? lepGen : antilepGen"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth checking lep_corr_charge > 0 instead of == 1? It is practically the same, but more general in case future developments introduce charges different from unity (but I don't know if we currently expect invalid charge values like -99 o +99, although the current code would not discard them and would simply pick the second alternative already).
Same comments apply to few similar lines below

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants