Skip to content

Interference processes: [LIxtree=QCD] and [treextree] left/right syntax - #243

Merged
oliviermattelaer merged 9 commits into
mainfrom
claude/loop-induced-times-tree-812c6d
Oct 11, 2026
Merged

oliviermattelaer merged 9 commits into
mainfrom
claude/loop-induced-times-tree-812c6d

Conversation

@oliviermattelaer

@oliviermattelaer oliviermattelaer commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Two processes on one line, of which only the interference 2 Re(A_left A_right^*) is computed:

generate g g > h > t t~ [LIxtree=QCD] g g > t t~      # loop-induced x tree
generate g g > h > t t~ [LIxtree=QCD]                 # right-hand side implied
generate p p > z > e+ e- [treextree] p p > a > e+ e-  # tree x tree
output madevent

This automates the recipe of the wiki page LoopInducedTimesTree (special model with UVtree copies of the vertices, a BKGQCD order, five source patches) without exposing any of it to the user.

Syntax

  • The keyword is case-insensitive ([LIxtree=QCD], [lixtree=QCD], [TreeXTree]).
  • Each side takes its own s-channel requirements, exclusions and amplitude orders. In the first example the > h > requirement applies to the loops only.
  • Squared-order constraints constrain the interference: they go between ] and the right-hand process, and are refused on the right-hand process.
  • Both sides must have the same external legs in the same order. The right-hand legs may be wider multiparticles.
  • Without a right-hand process, LIxtree interferes with the tree-level process of the same legs, with the orders a plain generate would choose.
  • help generate documents it.

Implementation

  • A hidden coupling order INTERF (hierarchy 0, added to the model in memory, never printed) tags the right-hand amplitudes. INTERF^2==1, also as a split order, keeps the left x right products only, in the generated code too.
  • LIxtree: the right-hand trees are generated per subprocess and stored as loop_UVCT_diagrams with UVCT_orders={INTERF:1} and a unit coupling. The ref_orders of a Born-less loop process now include the UVCT amplitudes; without that MadLoop stops at initialisation ("Could not find squared orders"). A subprocess whose loops are all discarded is dropped together with its trees.
  • treextree: the right-hand diagrams are appended after the left-hand generation, and the squared-order filter acts on the union. HELAS copies the tag onto the amplitude orders, on a copy because those dicts are shared with the model interactions. Crossing reuse is off for these processes.
  • treextree output: madevent, standalone_fortran, and, since PR Interference in mg7: madevent helicity convention, IDWTUP -4, signed-weight fixes #244 (madmatrix split orders on CPU and GPU, signed weights in madspace), mg7 and the C++ standalone. The other formats have no squared split orders and would return |L+R|^2, so they are refused before anything is written. LIxtree needs a loop backend: madevent and standalone_fortran only.

Run card and systematics

  • Interference processes get the existing interference defaults (dynamical_scale_choice = 3, systematics off by default). Before this, the detection relied on ^2 appearing in the printed process.
  • config_nqcd.inc: one power of alpha_s for every channel, half the QCD order of the left x right products. For QCD u d > u d against its electroweak counterpart this is 1, where the channels would have given 2 or 0.
  • systematics.py: the summary printed no scale variation and wrong-signed percentages for a negative total cross section. This affects any interference sample. The file has diverged from mg5 3.7.3, so the same fix is needed there separately.

Validation

tests/acceptance_tests/test_loop_induced_x_tree.py, at fixed phase-space points:

Quantity Result Reference Agreement
g g > h > t t~ interference -2.3256785052943e-04 wiki recipe, independent tree construction 10 digits
loop squared 8.1044796923476e-05 [sqrvirt=QCD] 10 digits
tree squared (helicity filter off) 0.59262610090352 tree-level g g > t t~ 10 digits
u u~ Z x photon 1.2397455147009e-03 |Z+a|^2 - |Z|^2 - |a|^2 10 digits

madevent:

Sample This PR Reference
g g > h > t t~, mH = 500 GeV, wH = 30 GeV, 13 TeV -0.957 ± 0.008 pb -0.965 ± 0.002 pb (wiki recipe)
p p > e+ e-, mll > 200 GeV 0.0598 ± 0.0028 pb 0.0629 ± 0.0048 pb (three squared samples)

Tests

  • New unit tests: interface/test_interference_syntax.py (17), loop/test_loop_induced_x_tree.py (7), core/test_tree_interference.py (6).
  • Full unit suite before merging main: 2448/2449, the failure being a guard on decay-chain amplitudes, since fixed. After merging main: core, interface, madspin and loop suites and the two acceptance tests pass.

Notes

  • In the standalone check of a LIxtree process, the loop-squared and tree-squared lines are diagnostics. They are complete only with HelicityFilterLevel 0, since MadLoop filters helicities on the selected interference. The interference itself is exact either way.
  • check refuses interference processes and points to output standalone_fortran.

Two processes on one line, of which only the interference
2 Re(A_left A_right^*) is computed:

  generate g g > h > t t~ [LIxtree=QCD] g g > t t~    (loop-induced x tree)
  generate g g > h > t t~ [LIxtree=QCD]               (right side implied)
  generate p p > z > e+ e- [treextree] p p > a > e+ e-  (tree x tree)

This automates the recipe of the wiki page LoopInducedTimesTree (UVtree
copies of the vertices in a special model, BKGQCD order, five source
patches) without exposing it.

Syntax (madgraph_interface, master_interface, base_objects)
- keyword case-insensitive; each side takes its own s-channels,
  exclusions and amplitude orders; squared-order constraints constrain
  the interference and go after ']' (refused on the right-hand side);
  both sides must have the same legs in the same order (right may be
  wider); LIxtree without right side = tree process of the same legs.
- Process keys interference_mode / interference_process, carried by
  get_process_with_legs, printed back in the input syntax.

Generation
- A hidden coupling order INTERF (hierarchy 0, added to the model in
  memory, never printed) tags the right-hand amplitudes; INTERF^2==1
  and INTERF as split order keep the left x right products only, also in
  the generated code.
- LIxtree: the right-hand trees are generated per subprocess and stored
  as loop_UVCT_diagrams (UVCT_orders {INTERF:1}, unit coupling); the
  ref_orders of a Born-less loop process now include the UVCT amplitudes
  (without it MadLoop stops: "Could not find squared orders").
  Subprocesses without surviving loops are dropped with their trees.
- treextree: the right-hand diagrams are appended after the left-hand
  generation and the squared-order filter is applied to the union; HELAS
  copies the tag onto the amplitude orders; no crossing reuse.
- treextree output limited to madevent and standalone_fortran (the C++
  outputs have no squared split orders and would return |L+R|^2).

Run card and systematics
- interference processes get the existing interference defaults
  (dynamical_scale_choice 3, no systematics by default);
- config_nqcd.inc: one alpha_s power for all channels, half the QCD order
  of the left x right products (QCD x EW u d > u d: 1, not 2 or 0);
- systematics summary: no scale line and wrong-signed percentages for a
  negative total cross-section (bounds started at 0/maxsize).

Validation (tests/acceptance_tests/test_loop_induced_x_tree.py)
- g g > h > t t~ [LIxtree=QCD] at a fixed point: interference equal to
  the wiki recipe, loop^2 to [sqrvirt=QCD], tree^2 (helicity filter off)
  to the tree-level ME, to 10 digits;
- u u~ Z x photon: equal to |Z+a|^2 - |Z|^2 - |a|^2 to 10 digits.
madevent: mH=500 GeV LIxtree -0.957(8) pb vs wiki recipe -0.965(2) pb;
p p > e+ e-, mll>200: treextree 0.0598(28) pb vs 0.0629(48) pb from the
three squared samples.
- Left-hand amplitude constraints ('==', '>') no longer become squared-order
  constraints on the left x right product: 'u d > u d QED==0 [treextree]
  u d > u d QCD==0' got QED^2==0 and no interference at all.
- Order constraints after the bracket must be identifiers followed by a
  process: '25 > 6 -6' or 'h > 5 -5' as right-hand process were eaten as
  'ORDER > n'.
- The LIxtree bracket is printed with the right-hand process, after the
  left-hand exclusions, so that a subprocess string parses back.
- alpha_s power: the configs.inc writers take one power per subprocess and
  use that of the subprocess owning each channel; an interference process
  never shares a subprocess group with an ordinary one (the interference
  mode is part of the process class; ordinary grouping unchanged); warning
  when the subprocesses of one group have different powers.
- The hidden order no longer leaks into later processes of the session: no
  zero INTERF in diagram orders, no extra level in get_particles_hierarchy.
- --diagram_filter also filters the right-hand diagrams.
- A Process pickled before the interference keys prints again.
- LIxtree: trees whose loop partners are removed by the loop filters run
  after the squared-order selection are dropped.

Tests for each; full unit suite 2464 OK, acceptance interference tests OK.
- The run card asks the process whether it keeps only some squared orders
  (Process.has_squared_order_selection: an interference, or a squared-order
  constraint not implied by an amplitude '=='/'>'), instead of searching
  '^2' in its printed form; same answer for ordinary processes (tested).
- The squared-order test of interference_alpha_s_power reuses
  Diagram.pass_squared_order_constraints instead of a copy of its rules.
- One place for hiding the internal order in process strings
  (base_objects.visible_orders).
- The interference help is written once, for 'help generate' and 'help add'.
- No mutable default argument in extract_interference_process; unused alias
  removed from loop_diagram_generation.

Full unit suite 2465 OK, acceptance interference tests OK.
The hidden order INTERFERENCE_ORDER (hierarchy 0), registered in the model
by an interference process, still leaked into:
- the WEIGHTED definition of the minimal-order search ("WEIGTHED IS
  QCD+2*QED+INTERF", which also reads as weight 1): orders of hierarchy 0
  are skipped, and the WEIGTHED typo is fixed;
- 'display coupling_order' (" INTERF : weight = 0");
- the orders set by default_unset_couplings (INTERF<=N was imposed);
- the debug summary of the loop squared-order selection (an INTERF column);
- a later NLO process of the same session: squared-order warning, split
  orders and perturbation couplings ("[ all = INTERF QCD QED ]").
…imes-tree-812c6d

# Conflicts:
#	madgraph/various/systematics.py
…12c6d' into claude/loop-induced-times-tree-812c6d
…alone

Since PR #244 (now in main) the madmatrix color sum pairs the amplitude split
orders on every backend, CPU and GPU, and madspace integrates and unweights
the signed weights of an interference. Allow 'output' (mg7) and 'output
standalone' for treextree; madevent and standalone_fortran stay, and LIxtree
still needs a loop backend (madevent, standalone_fortran). The madmatrix
message announcing CPU-only builds comes corrected with the merge of main.

Checked:
- u u~ > z > e+ e- [treextree] u u~ > a > e+ e-, C++ standalone built in
  double precision: equal to |Z+a|^2 - |Z|^2 - |a|^2 from the same code to
  2e-16, and to the Fortran value to 1e-15 (new acceptance test; the default
  mixed-precision build agrees to 7e-8 only);
- mg7, p p > e+ e-, m(ee) > 200 GeV: interference 0.0650(9) pb over four
  seeds, |Z+a|^2 - |Z|^2 - |a|^2 0.0649(9) pb; the Z+a, Z and a cross
  sections agree with madevent. madevent's interference comes out 7.5%
  lower (0.0601(17) pb, 2.6 sigma), its known iteration-combination bias on
  sign-changing integrands; same matrix element (agrees to 1e-15).

Full unit suite 2499 OK, interference acceptance tests OK.
@oliviermattelaer
oliviermattelaer merged commit 5863924 into main Oct 11, 2026
185 of 186 checks passed
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