Skip to content

MLM: diagram input, scale floor, merged subprocesses, and no clustering through flavour-forbidden merged graphs (madevent + madspace) - #231

Merged
oliviermattelaer merged 3 commits into
claude/mlm-diagram-historyfrom
claude/code-review-5eb764
Oct 8, 2026
Merged

oliviermattelaer merged 3 commits into
claude/mlm-diagram-historyfrom
claude/code-review-5eb764

Conversation

@oliviermattelaer

Copy link
Copy Markdown
Contributor

Stacked on #157 (claude/mlm-diagram-history), the top of the MLM stack (#72 ← #156 ← #157). Three commits. The samples behind the MG7 paper's MLM figures (Overleaf 1b262a6) come from this branch tip, 03c532a.

46b609b: scale floor, diagram input, merged subprocesses, decays

  • Scale floor: min_scale only vetoes under MLM. Every other scale choice clamps the scales into the PDF grid's band instead. A run without beam densities gets no range at all; a decay of a particle under 2 GeV used to come out with zero width.
  • Diagram input: under MLM the matrix element goes back to random_diagram_in. Neither backend reads diagram_in, so madmatrix had drawn every event's diagram and colour flow at a fixed random number of 0.5.
  • Merged subprocesses: MLM turns merge_subprocesses off, with a warning. Merged, it clustered gluon-initiated matrix elements with a quark beam. p p > e+ e- j at xqcut 20 gives 312.3(6) pb merged against 319.0(7) unmerged.
  • Decays: dynamical_scale_choice = mlm on a decay uses the decaying mass as a fixed scale.

317a05f: madevent, no clustering through flavour-forbidden merged graphs

Cherry-pick of #229 (against main). With apply_flavor_grouping, cluster.f filtered candidate graphs with flavor_compatible, but never checked that any graph survived, so the filter had no effect.

Take the q q~ > q' q~' rows of q q~ > q q~. The clustering went through the q–q' t-channel gluon of the merged graph, which does not exist for those flavours, so the αs reweighting of the q' q~' > g clustering was skipped (REWGT = 1).

The fix:

  • flavor_filter: works on the event's merged-flavour indices. A g/γ/Z/h may join two merged legs only if they have the same index.
  • chcluster: when the clustering is forced onto a channel graph the event's flavours forbid, it now returns zero weight instead of stopping madevent.
  • Test: test_mlm_rewgt_auto_match_qq_to_qq compared 32 flavour rows with 8, a factor 3.8 by construction. It now sums every ungrouped P-dir with the same topology. At 50k points it fails at 17σ without the fix and passes at 0.5σ with it.

03c532a: madspace, the same for the C++ clustering

One MLMClustering serves every flavour of a merged subprocess, over the union of its diagrams. The fix:

  • Compiler: marks a clustering (TRACE_SAME_FLAVOR, trace bit 16) when it joins two bare external fermions and every diagram left puts a g/γ/Z/h on that line. This is decided per diagram; the union's single line meta would prefer a W.
  • Kernel: new instruction inputs flavor_index and leg_flavors (the abs PDG ids of each leg per flavour option, from pid_options). A marked clustering whose legs differ is skipped. If a state offers nothing else, the walk falls back to the best skipped candidate.
  • Integrand, per-diagram histories (diagram, madevent): already clustered after the flavour is sampled; they now receive it.
  • Integrand, all_diagrams (the default): clusters before sampling, for the flavour prior. When the clustering depends on the flavours, the event is clustered again after sampling and its densities are re-evaluated at the new scales. Nothing changes for an integrand without a flavour-dependent clustering.
  • Python: MLMClusteringWithFlavors exposes this, as MLMClusteringAlongDiagram does for the along-diagram path. Two new tests in test_mlm.py.

W + 4 quarks (p p > e+ ve j j, quark-only j, the paper's MLM settings, 5×10⁴ events), flavour grouping on / off, in pb:

clustering_history before after
all_diagrams 149.66 / 134.25 134.13 / 134.18
madevent 134.23 / 122.87 123.03 / 122.87
diagram 121.66 / 121.61 unchanged

Validation, on this branch

  • madspace: pytest 1667 passed, 11 skipped. The torch-only test_flow.py and test_mlp.py were not run.
  • madevent MLM: ./tests/test_manager.py -t0 -p tests/acceptance_tests '.*mlm_rewgt.*': Ran 5 tests, OK.
  • madevent A/B, same points, W+2j 4-quark subprocesses: P3_qq_lvlqq 125.7(3.2) → 115.0(2.7), P6_qq_lvlqq 102.7(13.6) → 91.4(11.4). Single channels move by −19% at 6–7σ.
  • Paper samples: W±+0,1,2j (mg7 and madevent) and tt̄+0,1,2j (three variants), showered with Pythia8. Every new DJR table is compatible with the previous one (χ²/ndf ≈ 1). W mg7/madevent after the veto is 1.0021. 4-quark events are 1.4% of the W sample, so the effect stays below the statistics of the figures.

For reviewers

  • The instruction signature of mlm_clustering_hadronic/_leptonic changes in instruction_set.yaml, so the headers are regenerated. A madspace build rebuilt with the old source hash will refuse to run existing mg7 run directories; regenerate them.
  • Only pairs of external legs are checked, as in the Fortran fix. A flavour-forbidden vertex involving an internal line is not caught.
  • The madevent A/B and the acceptance tests on main are in Fix MLM clustering through flavour-forbidden merged graphs (apply_flavor_grouping) #229. That PR carries only the Fortran fix; main has no madspace MLM code.

oliviermattelaer and others added 3 commits October 6, 2026 22:40
min_scale only vetoes under MLM. madevent's setclscales drops a merged
event whose factorisation scale is under 2 GeV, but for any other scale
choice it lets LHAPDF freeze the density, so the other choices now clamp
the scales into the grid's band without a scale_weight. A run without beam
densities (lepton collider, decay) gets no range at all: a decay of a
particle lighter than 2 GeV came out with zero width. ta- > vt e- ve~ now
gives 4.07e-13 GeV.

Go back to random_diagram_in for the matrix element under MLM. Neither
backend reads diagram_in: madmatrix ignores it and the mg7 api.cpp rejects
it. So madmatrix drew its diagram, and with it the colour flow, at a fixed
random number of 0.5 for every event.

MLM turns merge_subprocesses off with a warning. A merged subprocess
bundles matrix elements whose legs differ (q q~ > e+ e- g next to
q g > e+ e- q), while the clustering is built from one flavour per leg.
It crashed before (no pdg_color_types in the merged meta, three-level
diagram indices), and patched past that it clustered the gluon-initiated
elements with a quark beam. p p > e+ e- j at xqcut 20: 312.3(6) pb merged
against 319.0(7) unmerged. With the fallback it gives 319.3(1.4). It also
removes the per-diagram history's single-matrix-element crash.

dynamical_scale_choice = mlm on a decay warns and uses the decaying mass
as a fixed scale, instead of building a clustering with one incoming leg.
The MLM EnergyScale constructor left the closed-form scale members
uninitialised; they now have defaults.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
With apply_flavor_grouping, cluster() filtered the candidate graphs of a
leg pair by flavour (flavor_compatible) but never checked that any graph
survived, so the filter had no effect: for the q q~ > q' q~' rows of
q q~ > q q~ the clustering took the q-q' t-channel gluon of the merged
graphs, which does not exist for those flavours, and skipped the alpha_s
reweighting of the q' q~' > g clustering that the ungrouped
P0_uux_ddx applies (+13% on the s-channel gluon channel at xqcut=20).

- flavor_filter replaces flavor_compatible and works on the event's
  merged-flavour indices instead of the remapped PDG codes: a g/a/Z/h
  can only join two merged legs with the same index. Everything else
  (unmerged or internal legs, W, unmapped merged leptons) stays
  permissive, as it effectively was. It is applied to every candidate
  pair (first pass and recomputation) and to igraphs after a winner, and
  pairs whose graph list ends up empty are no longer clustered.
- setclscales: a chcluster-forced clustering on a channel whose graph is
  forbidden for the event's flavours (s-channel config, u d~ > u d~ row)
  now returns .false. (zero weight, which the multichannel factor gives
  anyway) instead of stopping madevent.

test_mlm_rewgt_auto_match_qq_to_qq compared the flavour-grouped channel
(all 32 flavour rows) with a single ungrouped P directory (8 rows, the
one with most channels), a factor ~3.8 by construction. It now compiles
every P directory, matches channels by s- and t-channel propagators with
flavours mapped onto the merged codes, compares each grouped channel
with the sum of its ungrouped counterparts, and uses 50k points (5k left
the +13% at 4.4 sigma). run_single_channel removes results.dat before a
run, since a Fortran STOP exits with 0 and left the previous channel's
result to be read.
The madspace twin of the cluster.f fix (317a05f). One MLMClustering
serves every flavour of a merged subprocess, and the union of its
diagrams has vertices that exist for some flavours only: for the
q q~ > q' q~' rows of q q~ > q q~ the q-q' t-channel gluon, which (as the
initial-state measure always wins a 2 -> 2) every such event was
clustered through. For W + 4 quarks (p p > e+ ve j j, quark-only j, the
paper's run_ms.sh settings) flavour grouping on vs off:

  clustering_history   before            after
  all_diagrams         149.66 / 134.25   134.13 / 134.18
  madevent             134.23 / 122.87   123.03 / 122.87
  diagram              121.66 / 121.61   unchanged

- The compiler marks a clustering TRACE_SAME_FLAVOR (trace bit 16) when it
  joins two bare external fermions and every diagram left puts a g, a, Z
  or h on that line (per diagram, not from the union's one line meta,
  which prefers a W).
- The kernel takes the flavour of each leg (flavor_index into a
  leg_flavors table of abs pdg ids, new inputs of mlm_clustering_*) and
  skips a marked clustering whose legs differ; when a state offers
  nothing else it falls back to the best skipped one, so the walk always
  goes on. A row of zeros keeps every clustering.
- Integrand: the per-diagram histories (diagram, madevent) are clustered
  after the flavour is sampled and now get it. all_diagrams clusters
  before, for the flavour prior; when the clustering depends on the
  flavours the event is clustered again once the flavour is sampled and
  its densities re-evaluated at the new scales (the prior keeps the
  first ones). Nothing changes for a subprocess without such a vertex.
- MLMClusteringWithFlavors exposes build_with_flavors to Python, as
  MLMClusteringAlongDiagram does build_along_diagram; two tests in
  test_mlm.py (marking, and u u~ > d d~ / u d~ > u d~ following the
  s- / t-channel diagram).

madspace tests: 1667 passed, 11 skipped (torch-only test_flow/test_mlp
not run).
@oliviermattelaer
oliviermattelaer merged commit 970a87f into claude/mlm-diagram-history Oct 8, 2026
514 checks passed
@oliviermattelaer
oliviermattelaer deleted the claude/code-review-5eb764 branch October 8, 2026 07:17
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