Skip to content

Fix MLM clustering through flavour-forbidden merged graphs (apply_flavor_grouping) - #229

Merged
oliviermattelaer merged 1 commit into
mainfrom
fix/mlm-cluster-flavor-filter
Oct 8, 2026
Merged

oliviermattelaer merged 1 commit into
mainfrom
fix/mlm-cluster-flavor-filter

Conversation

@oliviermattelaer

Copy link
Copy Markdown
Contributor

Fixes the pre-existing failure of test_MLM_reweight.py::TestMLMReweightAutoMatch::test_mlm_rewgt_auto_match_qq_to_qq (39.1 sigma, reproduced on main and at ce5ebc6). The failure had two causes: a wrong test, and a real MLM bug that the wrong test was hiding.

1. The test compared different flavour content (factor ~3.8)

With group_subprocesses=False, apply_flavor_grouping=False splits q q~ > q q~ (q = u d s c) over three P-dirs:

P-dir rows channels
P0_uux_uux 8 (flavour-diagonal) s-channel g, t-channel g
P0_uux_ddx 12 (q q~ > q' q~') s-channel g only
P0_udx_udx 12 (q q~' > q q~') t-channel g only

The flavour-grouped P0_QQx_QQx integrates all 32 rows. The test compared it with only the P-dir that has the most channels (P0_uux_uux).

2. Real bug: cluster() used flavour-forbidden graphs

After summing over P-dirs (50k points per channel):

channel fg=True (main) fg=False, summed
s-channel g 134610 ± 782 34680 + 84725 = 119405 ± 438 (17 sigma)
t-channel g 5.028e6 ± 0.070e6 5.095e6 ± 0.049e6 (0.8 sigma)

With ickkw=0 the s-channel numbers agree (134610 vs 134065, 0.6 sigma), so the difference is in REWGT. mlevel=8 traces show the cause. For the q q~ > q' q~' rows (flavour class 1,1,2,2), fg=True clusters beam q with outgoing q' through the t-channel gluon of the merged graph. That vertex does not exist for those flavours, and the merge gives REWGT = 1. The ungrouped P0_uux_ddx only has the s-channel graph: it clusters q' q~' -> g and applies alpha_s(ptij)/alpha_s(mtlast), about 0.85.

cluster() already filtered candidate graphs with flavor_compatible, but never checked that any graph survived. The filtered icgs was a dead local, so the filter had no effect at all.

Fix (Template/LO/SubProcesses/cluster.f, reweight.f)

  • flavor_filter replaces flavor_compatible. It works on the event's merged-flavour indices, i.e. the class pattern from GET_FLAVOR that reaches REWGT, where equal index means equal flavour, as in check_flavor. It does not use the remapped PDG codes.
    • A g/γ/Z/h may join two merged legs only if they have the same index.
    • Everything else stays permissive, as it effectively was: unmerged or internal legs, W, unmapped merged leptons.
    • It is applied to every candidate pair, in the first pass and in the recomputation, and to igraphs after a winner is chosen. Pairs whose graph list is empty are no longer clustered.
    • The old function would have been wrong once switched on: it checked W up/down parity and sign on class indices, rejected unmapped 82 leptons, and rejected boson daughters such as W+ W- -> Z.
    • fg=False passes flavor = 1,1,..., so the filter is a no-op there.
  • setclscales: the first event of a channel, and the "jets fail, recluster" path, force chcluster (this channel's graph only). For a flavour row that forbids that graph (s-channel config, u d~ > u d~ row), clustering now returns .false., which the callers already turn into zero weight. Before, this case would stop. The event has zero multichannel weight there anyway (AMP2(config)=0 for that flavour). Any other clustering failure still stops as before.

Test (tests/acceptance_tests/test_MLM_reweight.py)

  • Compiles every P-dir. Each flavour-grouped channel is compared with the sum of its ungrouped counterparts, matched by s- and t-channel propagators with explicit flavours mapped onto 81/82/83.
  • 50k points per channel: at 5k points the +13% only shows up as 4.4 sigma.
  • run_single_channel deletes results.dat before each run. A Fortran STOP exits with 0 and otherwise leaves the previous channel's result to be read; this hid the chcluster stop above during development.

Validation

  • New test fails on the old Fortran (17.0 sigma: 134610 vs 119405) and passes with the fix: s-channel 119810 ± 677 vs 119405 ± 438 (0.5 sigma), t-channel 0.8 sigma.
  • -p tests/acceptance_tests '.*mlm_rewgt.*': Ran 5 tests, OK. This covers the manual qq, gg>tt~gg and DY+4j cases.
  • test_flavor_grouping_consistency_mlm, test_madevent_dy3j_mlm, test_DY_onejet: Ran 3 tests, OK. No clustering errors in the logs.
  • IOTests not run: the change touches only Template Fortran that the goldens do not cover, plus the acceptance test.

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.
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