Repository navigation
MadSpin: six fixes for the frame-projected polarised density - #119
Merged
oliviermattelaer merged 4 commits intoSep 15, 2026
Conversation
Rescued verbatim from the worktree they were developed in, on the base they were written against (PR #103's head). Not yet ported to main -- that base is ~100 commits behind and the gap contains PR #63, the MadSpin performance rewrite, so the port is a real one and is kept separate from preserving the work. Together these are what make a polarised NLO production decayable at all; before them `p p > z{0} z{0} [QCD]` + MadSpin ran at ~450 s per event and was killed at event 40 of 4000. After, 20000 events decay in 15.6 s. 1. _production_polarization parsed the process line with MadSpin's tree-level mg5cmd, which refuses the perturbation-order bracket. MadSpin only warned, so the density was left unrestricted and every NLO polarised production got UNPOLARISED decay angles -- with the right cross section and no crash. Strip the bracket; it is irrelevant to the polarisation. 2. _frame_boost called get_momenta without merged_map, raising ValueError under flavour grouping. 3. get_density called get_pdg (which matches by exact momentum equality) on the BOOSTED momenta, so no particle was found. Read the pdgs off the lab momenta before boosting. 4. get_density's cached branch recomputed the tag with a bare get_tag_and_order(), losing get_pdir's 1->N antiparticle fallback. Cache the tag get_pdir resolved instead. 5. The offshell mass-stage weight and the joint weight divided a me_frame-boosted Tr(rho_off) by a LAB-frame |M_prod|^2. A helicity-restricted matrix element is not Lorentz invariant, so the two are different projections; for an unpolarised production the trace is invariant and they agree, which is why only polarised runs were affected. _onshell_production_norm returns calculate_matrix_ element unchanged when there is no frame boost -- so unpolarised runs are bit-for-bit identical -- and otherwise the trace of the on-shell rho in the same frame. This is the load-bearing one: reverting it alone puts the mass-stage bound back to 7.9e6 while leaving the two angular bounds bit-identical. 6. The same frame fix applied to the joint path. Verified safe there: over 5414 production events the denominator was bit-identical across every joint trial of the same event, so it cancels out of the accept/reject; Route B's published numbers move by <= 0.5 sigma. Upstream 3.8.0 carries an independent version of fix 5 (mg5amcnlo#407) and of the joint symmetry factor (#408/#409). When MadGraph7 merges 3.8.0 these need reconciling rather than both being applied -- the MadGraph7 get_density signature carries flavour-grouping arguments that 3.8.0 has no equivalent of. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The six density fixes were written against PR #103's head; #65's head has nine commits on top, three of which added stubs that the fixes now outgrow. All three are the double falling behind the real class, not a problem with the fixes, so the stubs move rather than the production code: * _FrameStub gains _revert_merged. MadSpinInterface carries it as a class attribute ({} unless flavour grouping is on) and _frame_boost now passes it to get_momenta as merged_map; the stub does not inherit from MadSpinInterface, so it did not have it. Guarding the production side with getattr would have hidden a real attribute behind a test-only default. * _MomentaEvent.get_momenta gains merged_map=None, matching lhe_parser.Event.get_momenta, which has carried it all along. * _offshell_fixture gains _onshell_production_norm, delegating to the calculate_matrix_element it already fakes. That is exactly what the real helper does when there is no frame boost, which is the only case a stub with no boost can represent -- so the fixture's own comment, "|M_prod|^2 on shell, the denominator of the offshell mass-set weight", still describes what the test measures. Borrowing the real helper instead would have dragged _frame_boost and get_density into the stub's hand-kept borrow list. 502 tests, OK -- the same count and result as the base without the fixes, so nothing was skipped to get there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#65 has merged main, which brings upstream mg5amcnlo 3.8.0 and with it mg5amcnlo#407 -- an independent implementation of this branch's fix 5. Three conflicts, all in interface_madspin, and in every one the executable code on both sides is byte-identical: only the prose differs. So this reconciles text, not behaviour. * _onshell_production_norm: kept upstream's docstring, which is the more accurate of the two (it names both the sequential mass-set weight and the joint weight, and the polarisation-weight trigger), and appended the one paragraph this branch had that upstream does not: the 1.4e-7 agreement with calculate_matrix_element when the boost is left out, and the still-unexplained boosted-trace deficit. Keeping upstream's wording for the shared part means future 3.8.0 merges do not re-conflict on it. * sequential call site: upstream's comment, identical call. * joint call site: kept this branch's comment, which carries the evidence that the site is not polarised-only and is safe anyway -- the denominator bit-identical across every joint trial of the same event over 5414 events, and the end-to-end redecay numbers. Upstream has no equivalent. After the merge there is exactly one definition of the helper and both call sites use it. The other five fixes are untouched: they address flavour grouping, which 3.8.0 has no counterpart for. 637 unit tests OK (test_madspin, test_lhe_parser). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Pull request overview
Improves correctness and runtime of MadSpin when decaying polarised NLO productions by making production-density parsing and normalization frame-consistent (and fixing flavour-grouping interactions).
Changes:
- Strip perturbation-order brackets from NLO process lines before MadSpin’s tree-level parser reads polarisation braces.
- Fix flavour-grouping / merged-pdg handling in
_frame_boost()andget_density()(merged map propagation + cached tag). - Ensure
get_density()uses lab-frame momenta for exact-momentum PDG matching, and align mass-stage/joint-path normalization with the numerator frame.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
MadSpin/interface_madspin.py |
Core fixes for process parsing, flavour-grouping momentum retrieval, density tagging, and frame-consistent normalization. |
tests/unit_tests/madspin/test_madspin.py |
Updates stubs/fixtures to match new production behavior API expectations (merged map + normalization helper). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Test the perturbation-order bracket stripping in
_production_polarization: the stub parser now refuses a bracket the way
MadSpin's tree-level mg5cmd does, and the new test fails ({} != ...)
with the stripping reverted.
- Trim the long benchmark comments on the sequential mass stage, the joint
path and _onshell_production_norm down to the invariant; drop the
reference to a FINDINGS.md that is not in the repository. The
measurements stay in the PR description.
- get_tag_and_order: pass the merged map by keyword.
- p_lab: say why a reference is enough (_boost_momenta returns new tuples
and never mutates its input) instead of copying.
madspin unit tests: 609 OK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
oliviermattelaer
merged commit Sep 15, 2026
d6378cc
into
claude/nlo-polarised-boost-assessment-d97794
517 checks passed
oliviermattelaer
added a commit
that referenced
this pull request
Sep 15, 2026
One conflict, in MadSpin/interface_madspin.py get_density. Main (#111/#112, b84acfb) now projects the aMC@NLO partons back onto the massless shell in the lab and only then applies the frame boost; the branch (#119) boosted first and kept a p_lab reference so get_pdg could still match the event's momenta exactly. Taking main's order: the boost moves after project_massless_partons (keeping it before as well would boost twice and would project the boosted momenta, which is what made Tr rho frame dependent), and p_lab goes because p is the lab momentum when get_pdg runs. The resolved get_density equals main's apart from #119's tag caching. Auto-merged files touched on both sides were checked: main brings no polarisation hunk into madgraph_interface.py (tutorial and check precision only); the RunCardNLO parton_shower default moving to PYTHIA8 does not reach the polarised tests (test_polarised_nlo_me_frame is fixed order and test_polarised_nlo_ps_me_frame pins PYTHIA8 itself). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Targets #65's branch, so it lands with the polarisation feature it completes.
These six fixes are what make a polarised NLO production decayable at all. Before them,
p p > z{0} z{0} [QCD]+ MadSpin ran at ~450 s per event and was killed at event 40 of 4000. After, the same 20000 production events decay in 15.6 s — a factor ~1.3e5 — and the sample closes on the physics:f00 = 0.9995 +- 0.0228for a pure-longitudinal production, against 1.They also fill in the "polarised ME + MadSpin" column of the three-route comparison in #65's own study, which had to be left empty. Every entry now agrees with the other two routes within 1.3 sigma.
The six
_production_polarizationparsed the process line with MadSpin's tree-level mg5cmd, which refuses a perturbation-order bracket. MadSpin only warned, so the density was left unrestricted and every NLO polarised production got unpolarised decay angles — with the right cross section and no crash. The bracket is irrelevant to the polarisation, so it is stripped before parsing._frame_boostcalledget_momentawithoutmerged_map, raisingValueErrorunder flavour grouping.get_densitycalledget_pdg— which matches particles by exact momentum equality — on the boosted momenta, so nothing was found. The pdgs are now read off the lab momenta, before the boost.get_density's cached branch recomputed the tag with a bareget_tag_and_order(), losing the 1->N antiparticle fallback thatget_pdirowns. It now caches the tagget_pdirresolved.me_frame-boostedTr(rho_off)by a lab-frame|M_prod|^2. A helicity-restricted matrix element is not Lorentz invariant, so the two are different projections; for an unpolarised production the trace is invariant and they agree, which is why only polarised runs were affected. Reverting this one alone puts the mass-stage bound back to 7.9e6 while leaving both angular bounds bit-identical — that is the whole difference between 450 s/event and 15.6 s/20000._onshell_production_normreturnscalculate_matrix_elementunchanged when there is no frame boost, so unpolarised runs are bit-for-bit identical.Safety of fix 6 on the joint path
That call site is also on the unpolarised polarisation-weight path, so it had to be shown safe rather than assumed. Over 5414 production events the denominator was bit-identical across every joint trial of the same event — it is a per-production-event constant, and a constant cancels out of an accept/reject, so the accepted decay distribution is unchanged in law, not merely in measurement. End to end, redecaying 150000 events moves every published fraction by <= 0.5 sigma and leaves the nine-row
ms_polclosure intact.The test commit
The fixes were written against PR #103's head; #65's head has nine commits on top, three of which added stubs the fixes outgrow. All three are the double falling behind the real class, so the stubs move, not the production code:
_FrameStubgains_revert_merged—MadSpinInterfacecarries it as a class attribute, and the stub does not inherit from it. Guarding the production side withgetattrwould have hidden a real attribute behind a test-only default._MomentaEvent.get_momentagainsmerged_map=None, matchinglhe_parser.Event.get_momenta, which has always had it._offshell_fixturegains_onshell_production_norm, delegating to thecalculate_matrix_elementit already fakes — exactly what the real helper does with no frame boost, which is the only case a boost-free stub can represent. Borrowing the real helper would have dragged_frame_boostandget_densityinto the stub's hand-kept borrow list.502 unit tests OK — the same count and result as the base without the fixes, so nothing was skipped to get there.
For the reviewer
Upstream 3.8.0 carries an independent version of fix 5 (mg5amcnlo#407) and of a related joint symmetry factor (#408, #409). When MadGraph7 merges 3.8.0 (#117) these need reconciling rather than both being applied — and MadGraph7's
get_densitysignature carries flavour-grouping arguments 3.8.0 has no equivalent of, so it will not be a clean either/or. Flagging it here because the commit history is the only place that context survives.Known limit:
p p > z z j [QCD]still aborts, on a separate pre-existing flavour-ordering bug that reproduces identically on the unpolarised process. #114 fixes that one; it is unrelated to these six.🤖 Generated with Claude Code