MadLoop: keep an external leg exactly at rest through improve_ps - #91
Open
oliviermattelaer wants to merge 6 commits into
Open
MadLoop: keep an external leg exactly at rest through improve_ps#91oliviermattelaer wants to merge 6 commits into
oliviermattelaer wants to merge 6 commits into
Conversation
IMPROVE_PS_POINT_PRECISION (PSMC branch) restored exact momentum conservation by dumping the residual into leg NEXTERNAL. If that leg was at rest -- what boost_to_frame arranges for a polarised matrix element -- its exact zero three-momentum was replaced by a ~1e-14 residual, HELAS' 'pp.eq.0' spin-axis branch flipped, and the longitudinal polarisation vector was built along rounding noise. Pick the final-state leg with the largest |p| instead. It can never be a leg at rest, and it is the smallest relative distortion available. The template is instantiated only in quad precision, so the single fix covers both entry points (the DP wrapper and SET_MP_PS). Also record that ROTATE_PS is not a symmetry of a polarised amplitude with a leg at rest. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drives IMPROVE_PS_POINT_PRECISION directly on a g g > z z point boosted to the rest frame of leg 3 and of leg 4 in turn, for both ImprovePSPoint settings and through both entry points (the DP wrapper and the quad routine SET_MP_PS calls). The check is exact, not statistical. Fails on the parent commit with px = -5.68e-14 on leg 4. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
main gave IMPROVE_PS_POINT_PRECISION a KEEP_OFFSHELL_MASS flag array (6ba177c). It is not read inside improve_ps.inc, but the driver has to supply it to link. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The acceptance workflow invokes tests by explicit name, so a test that is not named in a job never runs. Give this one its own job next to the other uncovered MadLoop tests; it needs restore_all for the MadLoop standalone output and gfortran for the driver. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
oliviermattelaer
force-pushed
the
claude/improve-ps-atrest-leg
branch
from
September 2, 2026 19:27
7ce6898 to
5d1279e
Compare
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.
IMPROVE_PS_POINT_PRECISIONdestroyed an exactly-zero three-momentum. This is apre-existing MadLoop-core bug on
main; it is the blocker recorded on #79, which isheld until this lands.
The bug
madgraph/iolibs/template_files/loop/improve_ps.inc, PSMC branch. Step (a) isdescribed as a rescaling, but it restored momentum conservation by absorbing the
residual into leg
NEXTERNALunconditionally:A polarised matrix element is evaluated in a frame where the selected leg is at rest,
and
boost_to_framesets its three-momentum to exactly zero on purpose: HELASvxxxxxreads the spin quantisation axis offpp.eq.rZero— at exactly zero the axisis the frame
z, otherwise it is the momentum direction. When the at-rest leg was thelast one, its exact zero came back as ~5.7e-14 and the longitudinal polarisation vector
was built along rounding noise.
The fix
Absorb into the final-state leg with the largest
|p|(IABSORB, one argmax loop).That leg is never the one at rest, and it is also the smallest relative distortion.
For 2->1, and for an all-at-rest final state, it degrades to the old
NEXTERNALchoice.
Two things that are easy to get wrong here and are worth recording:
write_improve_pssetsmp_prefix='MP_', so there is exactly oneMP_PSMC_IMPROVE_PS_POINT_PRECISION;the DP wrapper converts to
REAL*16and calls it, andSET_MP_PScalls the sameroutine. One edit covers both entry points.
ImprovePSPointdefaults to 2 = ORIG, not PSMC — but ORIG bails withERRCODE=200as soon as the beams carry transverse momentum, which is exactly whata boost to a final-state rest frame produces, so the rescue path runs PSMC every
time. Both settings reach the bug. (The values in
MadLoopParams.datare!-commented; the real defaults are at the bottom ofMadLoopParamReader.f.)Also in this PR: a warning comment on
NRotations_DP/QPin the standaloneMadLoopParams.dat, and the same note above bothROTATE_PSroutines. A rotation isan exact axis permutation, so exact zeros survive it — but it is not a symmetry of a
polarised amplitude with a leg at rest, since HELAS pins that leg's axis to the frame
axes. Enabling rotations on a polarised run gives spurious instability flags. They
default to 0.
Because these edits land in generated
improve_ps.f, the 15 IOTest reference copies ofthat file are regenerated in
update IOTest. The diff there is confined toimprove_ps.fand is the same edit in each.That commit also deletes two references,
IOTestFDGauge/FDgauge_standalone_fortran/{FFV6M_3,VVV1_0}.f, which is correct butworth a sentence: they were already dead.
testIO_FDgauge_standalone_fortrantargetsFD_fortran/Source/DHELAS/*.f, but it generates withoutput standalone, which nolonger produces a Fortran
Source/DHELAS/tree — on currentmainit produces theC++
src/HelAmps_sm.hlayout. So those targets have matched nothing sincestandalonechanged meaning, and-Uremoved the unmatched references. The teststill reports OK with zero references, and it reports OK with a deliberately corrupted
reference too, so it has been vacuous rather than failing. That is a pre-existing
mainbug, not something this PR introduces — the fix is a separate one-word change(
standalone->standalone_fortranattests/acceptance_tests/test_cmd.py:5040)plus regenerating those two goldens under the path they then land in.
Measurement
Two measurements, at different levels.
Cross-section, on
g g > z{0} z{0} [noborn=QCD].me_frame=[3]andme_frame=[4]select the two identical Z's, so they are the same observable measured twice and have
to agree. 100 events,
use_syst=False,FRAME_ID8 and 16 read back fromSource/run_card.inc:me_frame[3][4][3]against[4][4]moves by -20.9% onto[3], which does not move at all — leg 3 is not the lastleg, so it was never the damaged one. That asymmetry is the control: the fix touches
only the frame that was broken, and it is the same conclusion the routine-level rows
below reach by a different route.
An LO control on the same final state gives bit-identical
[3]and[4], so thesplit was a MadLoop artefact rather than physics.
These runs are on
claude/loop-induced-polarisationwith this PR'sMadLoop: absorb the PS residual in the largest final-state legcherry-picked on top,because
check_process_formatonmainrefuses[noborn=QCD]with a polarised legand
boost_to_frameonmaindoes not zero the selected leg (that is the M2 fix on#79's branch). Once this PR lands, the
@unittest.skipontest_polarised_loop_induced_me_frame_last_legcan be removed on that branch — itasserts exactly the
[4]number above.Routine level, on
main, reproducible from this PR alone, and exact rather thanstatistical. The driver puts leg 3, then leg 4, exactly at rest, calls
improve_ps,and prints that leg's three-momentum back.
g g > z z, sqrt(s) = 500 GeV; the columnsare entry point, leg,
ImprovePSPoint, then px, py, pz.With
improve_ps.incas it stands onmain:With this PR, all 8 rows and all 24 components are exactly
0.0000000000000000E+00.Three things those rows show that a pass/fail would not:
NEXTERNAL, the leg the old code dumped theresidual into. That is the bug's signature rather than a general loss of precision,
and it is why a process whose polarised leg normalises away from last is
accidentally safe.
DPwrapper and the
QProutine thatSET_MP_PScalls) and bothImprovePSPointsettings (1 = PSMC, 2 = ORIG, which falls back to PSMC through the
ERRCODE=200rescue as soon as the beams acquire transverse momentum) all reach it. The claim
about ORIG above is measured here, not argued.
pzis exactly half ofpxandpy—so it does not shrink with a better phase-space point and does not average away over
events. It is either exactly zero or it is not.
The test asserts exactly this, and it drives a compiled binary: it builds the driver
with
gfortranagainst the generatedimprove_ps.fand parses the 8RESULTrows outof the running executable, so a compile or link failure fails the test rather than
passing it silently.
Which processes are exposed
MG5 normalises the final-state leg order, so
u u~ > g z{0}andu u~ > z{0} gbothgive
P0_uux_z0gwith the Z as leg 3. Which legme_frameselects is therefore notunder the user's control, and exposure cannot be read off the process string — read
born_leshouche.inc:g g > z{0} z{0} [noborn=QCD],[4]u u~ > w+{0} w-{0} [QCD],[4]p p > j z{0} [QCD],[3]#79's own example was accidentally safe, which is why its CI is green over the hole.
Test
tests/acceptance_tests/test_cmd_madloop.py::test_improve_ps_keeps_a_leg_at_restplus
tests/input_files/improve_ps_at_rest_driver.f. It generatesg g > z z [sqrvirt=QCD]standalone and compiles a driver againstimprove_ps.falone — no CutTools link is needed, the routine wants only
MadLoopParams.incandmp_coupl.inc, and the quad type is plainREAL*16. It asserts an exact0.0for2 at-rest legs x 2
ImprovePSPointsettings x 2 entry points, 8 rows.The test is wired into
.github/workflows/acceptancetest.ymlasacceptancetest_improve_ps_at_rest. The workflow invokes tests by explicit name, soan unwired test never runs.
🤖 Generated with Claude Code