Skip to content

mg7: support the run_card me_frame, the frame the matrix element is evaluated in - #171

Draft
oliviermattelaer wants to merge 2 commits into
claude/madspace-mirror-me-momentafrom
claude/madspace-me-frame-parameter-0111a6
Draft

oliviermattelaer wants to merge 2 commits into
claude/madspace-mirror-me-momentafrom
claude/madspace-me-frame-parameter-0111a6

Conversation

@oliviermattelaer

Copy link
Copy Markdown
Contributor

Stacked on #170 (claude/madspace-mirror-me-momenta) — review that one first;
this PR targets it, so its diff here is only the me_frame commit.

me_frame names the external particles whose momentum sum is put at rest, the
same convention madevent has used for polarised processes. The matrix element is
then evaluated on momenta boosted into that frame; everything else — the cuts,
the scales, the momentum fractions, the event that gets written — keeps the
momenta as generated. It only changes the result for a matrix element that is
not Lorentz invariant, i.e. a polarised one, and it is then the frame the
polarisations are defined in.

The default is [], not madevent's [1, 2]

[] applies no boost at all, so no existing run changes and nothing is paid for
a feature it does not use. [1, 2] asks for the partonic centre of mass, the
frame madevent evaluates in, and [1] is its equivalent for a 1 -> n decay.

This is the one place mg7 deliberately differs from madevent's default. madevent
gets the partonic CM for free, because its matrix element is already handed
partonic-CM momenta and frame_id == 6 is skipped outright. madspace generates
in the lab frame, so the same default would cost a boost per event for every
process. A polarised mg7 run therefore has to ask for me_frame = [1, 2]
explicitly
, and a converter from the LO run_card must carry the value over
rather than treat LO's [1, 2] as "the default, nothing to do" (noted in
RunCardLO_to_MG7_mapping.md).

Two boosts, not one

Because madspace works in the lab frame, the requested frame is reached in two
steps — first into the rest frame of the incoming system, then into the
requested one.

Going straight there from the lab is a different frame: boosts along different
directions do not compose into the boost between the end frames, and the
leftover Wigner rotation turns the polarisation axes away from madevent's. On
g q > z{0} q with me_frame = [3], at a fixed phase-space point, one boost
gives 4.4886e-02 where the two-step gives 6.8931e-02. Only the latter is
independent of the frame the momenta arrive in — after the fix,
ME(frame(lab p)) and ME(frame(cm p)) are bit-identical.

Validation

  • The new boost_to_frame instruction is a port of madevent's boost_to_frame
    (Template/LO/SubProcesses/genps.f), including its rule that a single
    selected particle must come out at exactly zero three-momentum — vxxxxx
    branches on pp == 0 and otherwise builds a massive vector's polarisation
    vectors out of the rounding noise. Compared against the Fortran routine on the
    momenta madevent actually feeds it (partonic CM — feed it lab momenta and its
    trivial_boost branch makes the comparison look broken): agreement to
    <= 4e-16 for [1,2], [3], [4], [3,4] and [3,4,5].
  • Cross sections on p p > z{0} j: [] 2107(15), [1,2] 2104(12),
    [3,4] 2104(12), [3] 1807(16) pb. [1,2] and [3,4] agreeing is the
    self-check — both are the partonic centre of mass.
  • A single massless particle in me_frame has no rest frame and used to
    return NaN silently; it is now rejected, as is an out-of-range particle number
    — before the matrix-element libraries are compiled, rather than minutes later
    when the integrands are built.

madmatrix

Nothing needed: it already emits only the generic ixxxxx/oxxxxx/vxxxxx/
sxxxxx and never the z-axis-optimised ipzxxx/imzxxx/ixzxxx, which assume
the beams run along z and would be wrong in a boosted frame (cudacpp dropped
them with the ALOHAOBJ rewrite). A comment and TestExternalWavefunctionCalls
now pin that down so they cannot come back unnoticed.

Tests

madspace suite 1562 pass; full unit suite unchanged against main.

🤖 Generated with Claude Code

…valuated in

me_frame names the external particles whose momentum sum is put at rest, the
same convention madevent has used for polarised processes. The matrix element
is then evaluated on momenta boosted into that frame; everything else -- the
cuts, the scales, the momentum fractions, the event that gets written -- keeps
the momenta as generated. It only changes the result for a matrix element that
is not Lorentz invariant, i.e. a polarised one, and it is then the frame the
polarisations are defined in.

The default is [], which applies no boost at all, so no existing run changes
and nothing is paid for a feature it does not use. [1, 2] asks for the partonic
centre of mass, the frame madevent evaluates in, and [1] is its equivalent for
a 1 -> n decay. Note this is the one place mg7 deliberately differs from
madevent's default, which is [1, 2]: madevent gets that frame for free because
its matrix element is already handed partonic-CM momenta, whereas here it costs
a boost per event.

madspace generates momenta in the lab frame, which is why the boost is built in
two steps -- first into the rest frame of the incoming system, then into the
requested one. Going straight there from the lab is a different frame: boosts
along different directions do not compose into the boost between the end
frames, and the leftover Wigner rotation turns the polarisation axes away from
madevent's. Measured on g q > z{0} q, me_frame=[3]: one boost gives 4.4886e-02
where the two-step gives 6.8931e-02, and only the latter is independent of the
frame the momenta arrive in.

The new boost_to_frame instruction is a port of madevent's boost_to_frame
(Template/LO/SubProcesses/genps.f), including its rule that a single selected
particle must come out at exactly zero three-momentum -- vxxxxx branches on
pp == 0 and otherwise builds a massive vector's polarisation vectors out of the
rounding noise. Validated against the Fortran routine on the momenta madevent
feeds it: agreement to <= 4e-16 for me_frame = [1,2], [3], [4], [3,4] and
[3,4,5].

madmatrix needs nothing: it already emits only the generic
ixxxxx/oxxxxx/vxxxxx/sxxxxx and never the z-axis-optimised ipzxxx/imzxxx/ixzxxx,
which assume the beams run along z and would be wrong in a boosted frame. A
comment and a test now pin that down.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@oliviermattelaer
oliviermattelaer marked this pull request as draft September 18, 2026 07:00
… me_frame branch

Four conflicts, all from main moving under the branch:

  - .github/workflows/acceptancetest_mg7.yml: main bundled the per-test jobs,
    deleting the block the polarised-mirror job sat in. Took main's structure
    and re-added the test as a step of acceptancetest_mg7_bundle_2, which is
    the bundle that installs madspace and restores heptools (bundle_4, where
    the other mirroring test lives, has neither).
  - madspace/include/.../matrix_element.hpp: main documented the constructor
    with Doxygen @PARAM. Adopted that style and documented me_frame and
    incoming_count in it.
  - madspace/src/python/madspace.cpp: main added the pydoc::doc() docstrings
    and fixed matrix_element_index, which was bound to diagram_count. Kept all
    of that and re-applied the new arguments and the frame_mask /
    reference_mask accessors. The two constant-tensor Value constructors are
    left without a docstring key: it would be an overload-indexed
    "Value::Value#N" that only generate_docstrings.py can assign, and doc() is
    consteval, so a guessed key is a compile error.
  - UpdateNotes.txt: both sides added an entry at the top of 0.3.0.

Verified on the merged tree: madspace builds and its suite passes (1636),
test_me_frame 17, test_mg7_launch / test_madmatrix_writer / test_banner 114,
and test_polarised_mirror_symmetry_mg7 passes.

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

This branch has not been deployed

No deployments
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