Skip to content

loop-induced: allow a polarised massive particle, and cover the last-leg frame - #96

Open
oliviermattelaer wants to merge 11 commits into
claude/polarised-nlo-psfrom
claude/loop-induced-polarisation
Open

loop-induced: allow a polarised massive particle, and cover the last-leg frame#96
oliviermattelaer wants to merge 11 commits into
claude/polarised-nlo-psfrom
claude/loop-induced-polarisation

Conversation

@oliviermattelaer

Copy link
Copy Markdown
Contributor

Stacked on #79. Lifts the refusal of a polarised massive particle for the
loop-induced NLO modes ([noborn=…], [sqrvirt=…]).

Why the restriction was an omission

Loop-induced processes are exported through the LO madevent template, so they
already have me_frame and the LO boost — nothing needed building. Verified in the
generated code rather than argued: auto_dsig1.f carries
IF(FRAME_ID.NE.6) CALL BOOST_TO_FRAME(PP, FRAME_ID, P1) and passes P1 to
SMATRIX1, which hands it to ML5_0_0_1_SLOOPMATRIX_THRES. Source/run_card.inc
reads FRAME_ID = 8 for me_frame=[3] and 16 for [4].

Measurement

g g > z{0} z{0} [noborn=QCD], shipped card, nevents=100:

me_frame FRAME_ID sigma
[3] 8 2.543e-02 +- 9.901e-05 pb
[4] 16 2.543e-02 +- 9.901e-05 pb

results.dat byte-identical between the two, which is the right expectation:
the two Z bosons are identical and both longitudinal, so the frames must agree
pointwise. Before PR #91 this pair read 2.543e-02 vs 3.211e-02 — that PR fixed
improve_ps dumping the momentum-conservation residual on leg NEXTERNAL and
destroying the exact zero the polarisation axis depends on. Both numbers here were
reproduced independently by a second agent on a fresh tree.

Tests

  • test_polarised_loop_induced_me_frame (me_frame=[3]) and
    test_polarised_loop_induced_me_frame_last_leg ([4]), both wired into
    acceptancetest_107 by explicit name — the workflow invokes tests by name, so an
    unwired test never runs. The [4] test was added skipped while PR MadLoop: keep an external leg exactly at rest through improve_ps #91 was
    outstanding; the skip is deleted here.
  • The assertions are not weakened. check_parton_output demands
    |Δ|/√(5e-4² + σ²) < 3; the measured 0.02543 sits 0.18 sigma from the asserted
    0.02534, an unboosted run (5.805e-02) would be ~47 sigma out, and a PR MadLoop: keep an external leg exactly at rest through improve_ps #91 revert
    (3.211e-02) ~9-13 sigma out.
  • .github/workflows/unittest.yml now also runs test_polarisation_nlo_regimes,
    the base branch's dedicated test for the three-regime split, which was defined but
    never invoked by CI.

One test assertion changed direction, deliberately

g{L} g > z z [noborn=QCD] moved from the rejected list to the accepted one. That is
a consequence of a base-branch decision, not a new one here: base commit
ee63ba492 scoped the colour refusal to the subtracted regime and added its own
positive assertion (g g > t{L} t~ [noborn=QCD]) with the comment "colour is no
restriction where there is no subtraction". The line added here is redundant
coverage of an assertion the base already makes. Upstream main has no colour check
at all; the refusal is this chain's own invention, narrowed to where the FKS-emitter
hazard actually exists.

The doc previously said the colour refusal "applies in every mode and stays", which
has been false since the rebase and is corrected — that sentence is exactly what a
future conflict resolver would read to justify restoring a blanket colour check,
which would silently re-close this feature.

Stated limitation

No coloured polarised loop-induced process has been run anywhere in this chain.
Nothing in the LO boost path assumes a colourless polarised leg — boost_to_frame is
purely kinematic and colour enters nowhere between the boost and the matrix element —
and the specific case added here (g{L}, massless) needs no frame at all, since
massless helicity is Lorentz invariant. But permitting it is a format decision, not a
validated one, and the doc now says so.

Notes for the merger

  • Commit 05a7887b1 is nearly empty: the guard it introduced went upstream into the
    base, so only the test_cmd.py additions survive in it, and its message still
    describes the guard change. History was left intact rather than rewritten; harmless
    under a squash merge.
  • acceptancetest_107 runs both acceptance tests in one job — 23.4 min + 21.0 min on
    an 18-core machine, so likely longer on a CI runner. Worth splitting if that slot
    becomes a bottleneck.

oliviermattelaer and others added 11 commits September 5, 2026 10:12
Loop-induced ([noborn=...]/[sqrvirt=...]) is exported through the LO
madevent template, so the generated auto_dsig already boosts to me_frame
before the matrix-element call.  The massive-particle refusal there was an
omission, not a physics limitation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
M5 in the plan doc: the LO boost was already in the generated loop-induced
code; verified at runtime that the selected leg reaches MadLoop exactly at
rest and that improve_ps keeps it there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
g g > z{0} z{0} [noborn=QCD]: 2.534e-02 +- 5.0e-05 pb with me_frame=[3]
against 5.805e-02 +- 4.8e-04 pb in the partonic c.m., same card.

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

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

The exactly-at-rest property does not survive MadLoop for a frame built on
the last external leg: PSMC overwrites NEWP(1:3,NEXTERNAL) with the
momentum-conservation residual before any rescaling. me_frame=[4] gives
3.211e-02 pb against 2.543e-02 at [3]. Regression test added, skipped until
the shared improve_ps fix lands.

sqrvirt is LoopOption='virt'/HasBorn=False and goes to standalone MadLoop,
not the LO madevent template. Still allowed, for the virt reason.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
g g > z{0} z{0} [noborn=QCD]: me_frame=[3] and [4] both give
2.543e-02 +- 9.9e-05 pb with byte-identical results.dat. Wired into
acceptancetest_107.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
M6's 'what is not touched' still claimed the colour-charged refusal 'applies
in every mode and stays'. It does not: ee63ba4 on the base branch scoped it
to the subtracted regime, which is why this branch's own unit test now accepts
g{L} g > z z [noborn=QCD]. The doc said the opposite of the test in the same
commit range -- exactly the sentence a later merge would use to justify
re-adding the check and silently closing the feature again.

Also: put the 'still refused' loop-induced paragraph outside the [QED QCD]
bullet (markdown lazy continuation folded it in, so it rendered as if
loop-induced were still refused), qualify 'the quantisation axis survives
MadLoop' with the me_frame it was measured on, and state the colour rule in
help_polarization, where it was documented nowhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The base branch added it (6e51741/ee63ba492) as the dedicated assertion that
the three polarised NLO regimes are checked separately -- including that colour
is refused in the subtracted regime and not in loop-induced, which is why this
branch's test_check_generate now accepts g{L} g > z z [noborn=QCD]. CI runs
unit tests by explicit name and that name was in no job, so nothing guarded it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant