Wire up the parton-shower ISR/FSR nuisances - #118
Conversation
Registers the correction and declares ps_isr and ps_fsr as shape nuisances. Pairs with
the Corrections change (Add_PS_Uncertainties), which holds the producer, and with the
FLAF change that fixes the denominator to select weights by source rather than by scale.
The correction's stages mirror pu, because it is the same mechanism: the weights are
defined at AnaTuple, where the inclusive sums of
weight_gen * weight_pu_Central * weight_ps_<var> are accumulated into the anaCache
denominators, and AnaTupleMerge reads the persisted branches back to build
weight_base_isr{Up,Down}_rel and weight_base_fsr{Up,Down}_rel. Dividing each variation by
its own denominator is what makes the nuisance shape-only.
AnaTupleMerge stays disabled deliberately: the merged tuple holds PS_Weight, not
PSWeight, so an enabled producer there would hit its missing-input path. It raises
rather than shadowing the persisted values, but the config is what should prevent it
arising.
No `processes:` on the correction and none on the datacard entries: one ps_isr and one
ps_fsr applied to every process including signal. Unlike PDF, where TT is powheg/NNPDF31
and DY madgraph/NNPDF40 so member k of one has no relationship to member k of the other,
this is literally the same Pythia CP5 shower parameter varied in every sample, so a
shared nuisance has a physical basis.
No era token in `name:` and no `eras:` in the cards, which is what keeps them correlated
across eras. All seven weights.yaml files, including the 2024-2026 stubs, and all three
datacards -- the 1D card has silently rotted from exactly this omission before.
No `requires:` on the weights.yaml entries. weights_this_process is
corrections.to_apply at the HistTuple stage, and parton_shower lives at
AnaTuple/AnaTupleMerge, so a `requires` would never match and would turn the entry into
a permanent silent no-op. PileUp_Lumi_MC has none for the same reason.
Measured on a signal anaTuple, in quintiles of genHbb_pt: ISR up shifts the rate -2.0%
with a -4.0% tilt, ISR down +2.5% and +4.7%, FSR up +0.5% and +0.7%, FSR down -0.8% and
-1.4%. The tilt is what survives the shape-only normalisation. For scale, the PDF tilt
on the same sample is 0.008-0.08%.
One thing to watch, recorded here because it will show up in the fit: FSR costs a lot of
effective MC statistics. N_eff falls 13.5% for FSR up and 42.5% for FSR down against a
nominal of 51126, with weights reaching 37.9 and 8.2% of events above 2. No single event
dominates, so clipping would not help. autoMCStats covers nominal templates only and
StatInference does no template smoothing, so the FSR templates should be inspected in
the sparsest categories before that nuisance is trusted.
Verified: the base block will build exactly the four _rel branches the expressions name;
all seven era files carry both entries with identical, era-free names; and the datacard
name plus _Up matches the histogram name HistMerger builds. black and yamllint clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VxRjUQZDMve3Zuv93btrui
Run3_2024, Run3_2025 and Run3_2026 crash in HistTupleProducer on PS_ISR Up while the other four eras pass. Setup builds config by concatenating the YAML text of every file along config_path_order and calling safe_load once, so a duplicate top-level key is resolved last-one-wins wholesale: an era-level `corrections:` block replaces the top-level one instead of merging into it. Those three eras are the only ones that declare their own block, and none of them lists parton_shower. registerShapeWeights then skips it, weight_base_isr*_rel is never defined, and the PS_ISR expression in their weights.yaml names a column that does not exist -- which histTupleDef hands straight to Define(), so ROOT's eager JIT dies on an undeclared identifier. CMS_pileup survives there for the mirror reason: pu is in their copied block. Copy the block verbatim into all three. Identical text and no era token, so ps_isr and ps_fsr stay single correlated nuisances; identical across the three also matters because 2025 and 2026 reuse 2024's MC and share its anaCache denominators. Comparing the era blocks against the top-level one by parsed value, parton_shower was the only key missing and btag the only key that genuinely differs -- the other fourteen are byte-identical copies. So the duplication will swallow the next top-level correction the same way. Left as-is here to keep the diff reviewable; the companion FLAF check now makes it fail loudly at PR time. Also correct the `requires:` note in all seven weights.yaml. Omitting the key was right, but the stated reason was not: weights entries do not support `requires:` on this branch at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VxRjUQZDMve3Zuv93btrui
f4aa93b to
f2eae08
Compare
|
pipeline#15675335 started |
|
pipeline#15676366 started |
|
pipeline#15676872 started |
Corrections main now carries the PS uncertainties (#129) and FLAF main the b-tag shape and denominator weight fixes (#313, #314). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BcuTugYjKUt32T2iPS2pm9
6e92b08 recorded a FLAF hash that does not exist; main is 1609b7ad. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BcuTugYjKUt32T2iPS2pm9
|
@cms-flaf-bot please test |
|
pipeline#15753086 started |
|
pipeline#15753086 passed |
There was a problem hiding this comment.
🔵 Needs a closer look
Correctness hinges on unverifiable companion changes in the FLAF/Corrections submodules and on a physics-sensitive nuisance the author flags as needing template inspection, so it needs human physics review.
Pull request overview
This PR wires up parton-shower ISR/FSR as shape nuisances in the HH→bb̄WW statistical inference chain. It is purely a configuration change in this repository and depends on companion changes in cms-flaf/Corrections (the PS weight producer) and cms-flaf/FLAF (the base-producer denominator fix); the actual weight_base_isr*_rel / weight_base_fsr*_rel branch construction lives in those submodules, which are not populated in this checkout and were not verifiable here.
Changes:
- Registers a new
parton_showercorrection (stages: [AnaTuple, AnaTupleMerge],branch: PSWeight, merge stage disabled) inconfig/global.yamland in the three per-era files that override the corrections block wholesale (2024/2025/2026); the remaining eras inherit the top-level entry. - Adds
PS_ISR/PS_FSRnorm-weight entries producingps_isr_{Up,Down}/ps_fsr_{Up,Down}histograms in all sevenweights.yamlfiles, with era-free names to keep the nuisances correlated across eras. - Declares
ps_israndps_fsrastype: shapenuisances (noeras:, noprocesses:) in the SL and DL run-3 datacards.
I verified the config side is internally consistent: all 7 weights.yaml files carry both entries with identical names, only 2024/2025/2026 have their own complete corrections block (so coverage is complete), branch: PSWeight matches the ("PSWeight", "PS_Weight") mapping in AnaProd/anaTupleDef.py:190, and the ps_isr→ps_isr_Up naming follows the existing pileup pattern. The one review item is that no documentation was updated for this user-observable change.
File summaries
| File | Description |
|---|---|
| config/global.yaml | Adds parton_shower correction (top-level, covers 2022–2023BPix) |
| config/Run3_2024/global.yaml, Run3_2025/global.yaml, Run3_2026/global.yaml | Re-declares parton_shower in each era's wholesale-override corrections block |
| config/Run3_2022/weights.yaml … Run3_2026/weights.yaml (7 files) | Adds PS_ISR/PS_FSR norm weights with era-free names |
| config/Datacards/x_hh_bbww_SL_run3.yaml, x_hh_bbww_DL_run3.yaml | Declares ps_isr/ps_fsr shape nuisances |
Review details
- Files reviewed: 15/15 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| - name: ps_isr | ||
| type: shape | ||
| - name: ps_fsr | ||
| type: shape |
Picks up the Corrections checkout in the setup loading test (#316). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BcuTugYjKUt32T2iPS2pm9
Registers the correction and declares ps_isr and ps_fsr as shape nuisances. Pairs with the Corrections change (Add_PS_Uncertainties), which holds the producer, and with the FLAF change that fixes the denominator to select weights by source rather than by scale.
The correction's stages mirror pu, because it is the same mechanism: the weights are defined at AnaTuple, where the inclusive sums of
weight_gen * weight_pu_Central * weight_ps_ are accumulated into the anaCache denominators, and AnaTupleMerge reads the persisted branches back to build weight_base_isr{Up,Down}_rel and weight_base_fsr{Up,Down}_rel. Dividing each variation by its own denominator is what makes the nuisance shape-only.
AnaTupleMerge stays disabled deliberately: the merged tuple holds PS_Weight, not PSWeight, so an enabled producer there would hit its missing-input path. It raises rather than shadowing the persisted values, but the config is what should prevent it arising.
No
processes:on the correction and none on the datacard entries: one ps_isr and one ps_fsr applied to every process including signal. Unlike PDF, where TT is powheg/NNPDF31 and DY madgraph/NNPDF40 so member k of one has no relationship to member k of the other, this is literally the same Pythia CP5 shower parameter varied in every sample, so a shared nuisance has a physical basis.No era token in
name:and noeras:in the cards, which is what keeps them correlated across eras. All seven weights.yaml files, including the 2024-2026 stubs, and all three datacards -- the 1D card has silently rotted from exactly this omission before.No
requires:on the weights.yaml entries. weights_this_process is corrections.to_apply at the HistTuple stage, and parton_shower lives at AnaTuple/AnaTupleMerge, so arequireswould never match and would turn the entry into a permanent silent no-op. PileUp_Lumi_MC has none for the same reason.Measured on a signal anaTuple, in quintiles of genHbb_pt: ISR up shifts the rate -2.0% with a -4.0% tilt, ISR down +2.5% and +4.7%, FSR up +0.5% and +0.7%, FSR down -0.8% and -1.4%. The tilt is what survives the shape-only normalisation. For scale, the PDF tilt on the same sample is 0.008-0.08%.
One thing to watch, recorded here because it will show up in the fit: FSR costs a lot of effective MC statistics. N_eff falls 13.5% for FSR up and 42.5% for FSR down against a nominal of 51126, with weights reaching 37.9 and 8.2% of events above 2. No single event dominates, so clipping would not help. autoMCStats covers nominal templates only and StatInference does no template smoothing, so the FSR templates should be inspected in the sparsest categories before that nuisance is trusted.
Verified: the base block will build exactly the four _rel branches the expressions name; all seven era files carry both entries with identical, era-free names; and the datacard name plus _Up matches the histogram name HistMerger builds. black and yamllint clean.
Claude-Session: https://claude.ai/code/session_01VxRjUQZDMve3Zuv93btrui