Repository navigation
MLM merging - #72
Merged
Merged
MLM merging#72
Conversation
Member
|
Thank you. The changes required in MadMatrix are those in madgraph4gpu#1086. Also, let's not forget madgraph4gpu#1090, for the related changes in MadtRex. |
Bring the MLM merging branch up to date with main (699 commits).
Notable conflict resolutions:
* The generated instruction-set headers (compgraphs/*.inc, cpu/gpu
runtime mixins, python/instruction_set.hpp) are no longer checked in:
main generates them from instruction_set.yaml at build time. The
branch's edits to those files are dropped; only the two new
mlm_clustering_{hadronic,leptonic} entries in instruction_set.yaml
are kept, and the headers regenerate from them.
* madevent.py was split into launch.py / run_interface.py. The MLM
hunks (init_beam scale selection, EnergyScale construction, matrix
element diagram input) are re-applied to launch.py. The matrix
element is now built in a loop over backends, so me_inputs is
computed once before that loop.
* run_card.toml is now a template filled from banner.py, so the "mlm"
option and the new beam.jet_radius parameter are declared there.
* LHECompleter was refactored into helper methods on main; main's
version is taken. The branch's TODO about the permutation direction
is dropped: main fixed exactly that by inverting the permutation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The clustering compiled and ran but the histories it produced were not usable. Fixes, in rough order of impact: * update_momenta was called with its i_remove/i_keep arguments swapped. The state machine names the merged pseudo-particle by the lowest bit of the combined mask, i.e. by particle1, so particle1 is the slot that has to survive; keeping particle2 left the merged momentum where no later state looks, and made the "i_keep < 2" initial-state branch dead and inconsistent with the caller's "is_initial = particle1 < 2". The Fortran settles the convention: in cluster_one_step the removed index runs from 3 and the kept one runs 1..i-1, so the kept index is the lower one and "jwin <= 2" means the kept particle is a beam. * The winner selection was only partly reset between clustering steps. win_resonant and win_next_state survived, so after a resonant step no non-resonant candidate could win the next one, and the walk would then jump to the previous step's target. * Nothing guaranteed a winner. A NaN measure never compares less than anything, so a step whose candidates were all NaN left win_next_state at -1 and the kernel read outside the state machine - an actual crash, reproducible at a few events per thousand once the initial-state branch of update_momenta became reachable, since its boost and rotation can leave a pair exactly collinear. dj_clus now zeroes a NaN result the way the Fortran does, and the selection takes the first candidate unconditionally. * find_clusterings expanded a state once per path reaching it, appending a duplicate copy of its transitions each time. * bw_cutoff was threaded into the kernel and never read: the resonance window was mass +- width instead of mass +- bw_cutoff * width. * external_masses is indexed by external leg but was built in topology slot order. The permutation is now applied, and diagrams that disagree on a leg's mass throw instead of silently picking one. * alive was hardcoded to 0xFFFFFF regardless of the particle count, and a dead-end state would have been read out of bounds. The vertex metadata (is_qcd, is_jet1/2, mass_index, massive_in/out) was hardcoded, so massless QCD was assumed everywhere and the resonance branch was unreachable. Masses and widths now come from Topology::Decay and from the t-channel propagator lists; Topology gained t_propagator_pdg_ids() alongside them, since the flavour of a t-channel line changes along the chain. The external pdg ids are not in Topology at all, so MLMClustering takes them as a new optional argument, which launch.py fills from the pids it already computes. Omitting it keeps the old "everything is a QCD jet" behaviour. tests/test_mlm.py replaces the smoke test with 50 tests covering the structure of the compiled state machine, the kernel outputs, the symmetries of the clustering, the resonance window, and the clustering measures against transcriptions of the Fortran. Where a property does not hold exactly, the test pins how far off it is and says why: * the jet-scale assignment flips for a few percent of events under a symmetry transformation, because degenerate candidates make the argmin unstable; * the choice of clustering is not longitudinally boost invariant, because the 1.000001 "against the beam" penalty tests sign(pz); * scales are not exactly homogeneous in the momenta, because update_momenta keeps the Fortran's absolute 100 GeV^2 guard against boosting into a degenerate frame. docs/mlm-merging-status.md records all of this along with the known differences from madevent and what is still open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
p p > e+ ve j at 13 TeV with matched cuts, PDF set and beam energy, 5000 events on each side. The hardest-parton pT distributions agree, so what is left is the scale definitions. Where both codes assign a jet a real clustering scale they agree exactly: pt_clust / pT_jet is 1.000 at every quantile. mu_R differs by a factor 0.61, which follows from the definitions in closed form and is reproduced event by event from the LHE momenta for the q qbar subprocesses. The finding worth acting on is that mg7 leaves 27.2% of jets with pt_clust = 0, where madevent leaves none. It comes from the s-channel g q -> q* -> (W)(q) topology, whose only clusterings are the W formation and (W, jet) - the latter has a colourless parent, so it is not a QCD vertex and books no jet scale. madevent avoids this with an explicit fallback to sqrt(s), which it also applies to non-jet legs, where mg7 writes 0. Since pt_clust is what an MLM veto compares against qcut, a jet reported at zero is indistinguishable from a lepton. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pt_clust is what an MLM veto compares against qcut, so a leg that no QCD clustering assigned a scale to has to come out at a value the veto can never trip on. madevent does this with the "ptclus = etot" fallback in Template/LO/SubProcesses/reweight.f; the kernel wrote 0, which a veto of the form "reject if any pt_clust < qcut" would reject on every event. The fallback value is the collider energy, which the kernel did not have: it is not derivable from the momenta, since those give the partonic energy. So MLMClustering takes it as a constructor argument (cm_energy, filled by launch.py from process.e_cm) and it is threaded through as a new input of the two mlm_clustering instructions. Rerunning the p p > e+ ve j comparison, jets reported at zero go from 27.2% to 0%, and the non-jet legs now read 13000 exactly, the same as madevent. Where both codes assign a real scale they still agree exactly (pt_clust / pT_jet is 1.000 at every quantile), and the assigned-scale medians are 29.50 against 30.67. This is the convention fix, not the underlying one: mg7 still reaches the fallback for 26.4% of jets against madevent's 10.1%, because the s-channel g q -> q* -> (W)(q) history contains no QCD splitting at all. docs/mlm-merging-status.md records what would actually close that gap. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pt_clust_i is the shower starting scale: an MLM run turns on Beams:setProductionScalesFromLHEF (madevent_interface.py), so Pythia reads it into event[i].scale() and limits the shower off that parton with it. The jet-matching veto never reads it, and only FxFx uses the per-particle scale, as a tag. madevent assigns each leg the hardest vertex on the parton *line* it belongs to - the scale at which the line was produced - by following the line with ipartupdate and taking a max. The kernel instead froze the scale at the first clustering the bare leg took part in, which is the last ME emission off that line. That is the CKKW question of where to restart the shower: madevent's choice lets the shower attempt the emissions the MLM veto then rejects, and those trials are the Sudakov suppression; freezing at the softest vertex never attempts them. Both are now selectable with beam.jet_scale_scheme, defaulting to "production" (madevent's). Getting there needed the parton line followed through the clustering, so: * the compiler works out per transition, from the colour representations of the mother and the two daughters, which daughter the line continues into - the first, the second, the harder one, or both after g -> q qbar - and stores it as a third word in the state machine; * the kernel keeps the resulting representative legs per slot, the equivalent of ipart, and compares transverse momenta in the original event when it has to pick the harder daughter, as the Fortran does; * colour representations come from the pdg_color_types map already exported in the subprocess metadata, with an SM fallback, and are inferred from the daughters when a propagator's flavour is unknown. Two fixes fell out of running p p > e+ ve j j, which crashed before: * find_clusterings created a transition whenever *some* diagram allowed the clustering, not whenever one of the diagrams still in play did. With none left the target state could never be expanded, and the walk had nowhere to go. Such transitions are now skipped, and a state whose expansion turns up nothing is dropped rather than pointed at. This blocked every process above 2 -> 3. * is_colored_pdg treated the merged-flavour placeholders 82 and 83 as coloured; clean_pids maps them to an electron and a neutrino. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The differential jet rates that say whether the merging works live on their own branch, since reconstructing them from a showered event record is as useful for madevent as it is for madspace. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reverting each fix in turn and rerunning the suite showed one that no test noticed: indexing external_masses by topology slot instead of by external leg. The shipped fixtures cannot see the difference, because their permutations only exchange identical gluons, so the two orderings agree. Add a topology whose permutations move the top onto a slot another permutation gives to a gluon. The two orderings then disagree about the mass of leg 2, which the kernel cannot represent, so the constructor has to refuse it rather than silently pick one - and with the slot-order bug put back the refusal does not happen and the test fails. Two other reverts also went unnoticed: removing dj_clus's NaN guard, and removing the "take the first candidate" guard. Those are deliberately redundant with the SCALE_MAX sanitisation, so dropping either on its own leaves the others covering it. Say so in the docstring of the test that covers the behaviour they exist for, rather than implying each guard is individually pinned. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MLM merging needs the matrix element to stop describing radiation the parton shower is meant to produce. madevent enforces that with xqcut; madspace had no equivalent, so its samples reached down to whatever the jet cuts allowed and Pythia's matching veto had to throw the difference away. The rule is madevent's, from the "Check xqcut for vertices with jet daughters only" block of Template/LO/SubProcesses/reweight.f: walking the clustering history that was just selected, any step whose daughter is still a bare final-state jet has to be at or above xqcut, or the event is dropped. The test does not ask whether the vertex was a QCD one, and it reuses the same "still a bare external leg" bookkeeping the emission scheme already maintains, so it costs one branch in the loop that is already there. The clustering only runs for events that already passed the phase-space cuts, so the veto comes back as a weight - 1 or 0 - that multiplies the event weight rather than as one of the cuts. An event below xqcut ends up at weight zero and is never unweighted. It is a new trailing output of the two mlm_clustering instructions, which leaves every existing positional index alone. p p > t t~ plus t t~ j at 13 TeV, everything else held fixed: 1462 +- 20 pb with no cut, 1181 +- 17 at 30 GeV, 815 +- 13 at 60 GeV. Off by default, so turning it on is the only thing that can change an existing result. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SCALUP holds one number and the two codes disagree about which: unwgt.f writes sqrt(max(q2fact)), i.e. mu_F, while madspace writes ren_scale. Comparing that field between them is apples to oranges, which is how the earlier claim that the mu_R definition explains the high-multiplicity cross-section difference came to rest on nothing. Emit both explicitly as the standard LHEF3 attributes - mur, muf, and muf1/muf2 since madspace can carry a different factorisation scale per beam - so a comparison does not have to guess. Nothing that was written before changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…er setclscales Review follow-ups to the pdfrwt fix: - rewgt now sets every external identity of the clustered graph to row IPSEL right after setclscales (the jet-only reset further down and the pre-setclscales writes on a possibly stale igraphs(1) are gone), and the leading pdfrwt entry is read from it. - drop the 81 branches that used FLAVOR(:) as a pdg (ickkw<=0 booking and s_rwfact): FLAVOR is the position of the coupling-class representative, and leshouche rows always carry physical pdgs, so ickkw<=0 books idup. - cluster.f: say what the FLAVOR mapping really gives (the class representative, not the event's flavour). - acceptance test test_mlm_pdfrwt_flavour (p p > z + 0,1j, ickkw=1), run in acceptancetest_50; it fails on the old code (5720 wrong entries / 20k). Same-seed samples are identical to the previous commit for ickkw=1 (p p > z + 0,1,2j) and ickkw=0 (p p > z + 0,1j): kinematics, mgrwt and every weight.
…vour # Conflicts: # UpdateNotes.txt
- rewgt: set only the incoming partons and the jets of the clustered graph to row IPSEL (as before the previous commit), so non-QCD legs no longer carry the previous event's flavours into the next event's clustering. - rewgt: stop with a clear message if a leshouche row ever holds a merged id (81-83) for an incoming leg, instead of handing it to pdg2pdf. - cluster.f: map flavour position 6 as well (top merged into 81 when massless), consistent with the comment. - test_mlm_pdfrwt_flavour: identify the beams by the pz sign and check that every ladder quark flavour is present in the event. Same-seed p p > z + 0,1,2j (ickkw=1) sample identical to the previous commit (kinematics, mgrwt, every weight); the acceptance test passes.
…valuate Under clustering_history = "diagram" or "madevent" every accepted event went through the matrix element twice: once at the scales of the history over every diagram (which also gives the diagram weights the history is picked from), and again at the scales of the history it settles on. Only the scales change between the two, and the matrix element sees them only through alpha_s. With one power n of alpha_s for every diagram, |M|^2 = C alpha_s^n and the cross section is linear in each beam density, so the second evaluation is the first one times (alpha_s' / alpha_s)^n f1' f2' / (f1 f2) - to rounding, and exactly where the history did not change, which under "madevent" is most events. The colour, helicity and diagram weights of the first call stay valid: their probabilities are ratios of terms of the same power. Integrand takes the power as mlm_history_qcd_power (launch.py passes the subprocess's qcd_power); -1 (mixed powers, e.g. gluon fusion with VBF) keeps the second evaluation. A first density of exactly zero keeps its zero weight. t t~ + 0,1,2j, 100k events, wall time: madevent history 80 s -> 46 s (all_diagrams: 40 s). Cross sections unchanged: t t~ nominal 500k 2157.10 -> 2157.23 pb, W+-+0,1,2j madevent-like 14656.8 -> 14656.9 pb (MC errors 0.04-0.1 %). No unit test compares the two paths event by event: the Integrand draws its own random numbers, unseeded from Python.
beam.clustering_mean_pdf_ladder (off by default) walks madevent's pdf ladder under clustering_mean: the beam densities, the per-beam factorisation scale they end at and the ladder slots are madevent's, while mu_R, the alpha_s vertex scales, the jet scales and the xqcut veto stay clustering_mean's. Without it clustering_mean evaluates both densities once at the softest clustering scale, which is what moves the W+jets 1-jet rate near qcut by ~25% against madevent. The kernel runs the madevent beam-line walk when pdf_reweighting == 2 and restores the clustering_mean outputs after it; native systematics vary the ladder in that case too. Co-Authored-By: Claude <noreply@anthropic.com>
clustering_mean_pdf_ladder now defaults to true, in the run card and in the madspace API. Against madevent (Pythia8 DJRs) it closes the 12-15% hole above qcut that the single softest-scale density leaves for W+jets and W Z+jets, and halves the tt~+jets excess; it made no sample worse. The unit tests about clustering_mean's own factorisation scale switch it off explicitly. Co-Authored-By: Claude <noreply@anthropic.com>
With two massless partons left in the final state they balance in pT, so clustering either into the beam it travels along scores the same mT. The walk compared rounded values, so an ulp decided which beam took the parton, and a rotation about the beam axis could change it. Which beam it is decides where that beam's line stops being a QCD line, i.e. jcentral, and so the madevent mu_F: 831.8 vs 1195.4 GeV for one ttgg test event in 200, under scale_scheme=madevent and clustering_mean with the pdf ladder alike. madevent's cluster.f has the same tie and settles it by rounding too; in exact arithmetic it keeps the first candidate of its loop order, which is (particle2, particle1) order on madspace's slots. Treat measures within 1e-12 as equal and take that order. It subsumes the earlier initial-beats-final rule for the DJ massless-massive tie, which was madevent-measure only. Add rotation and longitudinal-boost tests for the two beam-line schemes.
oliviermattelaer
marked this pull request as ready for review
October 28, 2026 13:41
…variance MLM: settle equal clustering candidates in cluster.f's loop order (mu_F rotation invariance)
…pdf-ladder MLM: pdf ladder under scale_scheme = clustering_mean, on by default (+ #347)
madevent MLM systematics: leading <pdfrwt> entry used the flavour class, not the event's s/c flavour
Contributor
|
@theoheimel will merge this as soon as the CI is green. |
# Conflicts: # madspace/src/phasespace/integrand.cpp
# Conflicts: # UpdateNotes.txt
#222 leaves weight_after_cuts out of the common part's weight product unless flavor or mirror sampling contribute to it, as it is one by construction otherwise. On feat-mlm build_channel_part also multiplies in the merging weights (alpha_s vertices, pdf ladder and its vetoes) and the scale floor veto when the history is over every diagram, so a single-flavor, unmirrored MLM sample silently lost all of them. test_a_density_too_small_to_divide_by_drops_the_event caught it: the events the pdf ladder vetoes came back with a non-zero weight. Co-Authored-By: Claude <noreply@anthropic.com>
… rule, tests - run card: the MLM clustering options other than max_jet_flavor (jet_radius, jet_scale_scheme, scale_scheme, parton_line_scheme, alphas_reweighting, pdf_reweighting, clustering_mean_pdf_ladder, clustering_measure, clustering_history) are hidden. RunCardMG7.write adds a hidden parameter at the end of its section once it is user set or differs from its declared default, so a set value (also from from_LO, which sets scale_scheme without user=True) survives a rewrite. The pdf ladder comment no longer quotes sample-specific numbers. - Integrand: has_channel_weight_after_cuts() is the one statement of when build_channel_part multiplies a weight into weight_after_cuts. The common part uses it, and build_channel_part throws at graph build if it pushes a weight the helper does not announce, instead of the common part dropping it silently (what the #222 merge did to the MLM weights). - launch.py: the systematics' ladder decision is mlm_pdf_ladder(), with a unit test of its cases. - scale.hpp: the mlm_pdf_reweighting() comment no longer says only scale_scheme madevent walks the ladder. - test_mlm: the ladder veto through the whole Integrand also under clustering_mean + ladder (single flavour, no mirror: the case the weight_after_cuts merge bug hit), and the systematics' pdf product under clustering_mean with and without the ladder. Co-Authored-By: Claude <noreply@anthropic.com>
Under clustering_mean + pdf ladder the beam-line walk used to overwrite mu_R, the alpha_s vertex scales, the jet scales and the merging-jet flags and restore a snapshot of them afterwards. Each of those writes now sits under one madevent_scales flag inside the walk, and madevent's mu_R is kept in a local (walk_ren_scale) that the mu_F fallbacks still use, so nothing has to be put back: a new write in the walk is next to the flag rather than relying on a restore list. The snapshot arrays (about 21 values per event, filled for every scheme) are gone. test_the_ladder_leaves_every_other_clustering_mean_output_alone checks on ttgg and ttggg, for every clustering history and both jet scale schemes, that the ladder leaves every non-pdf output equal to plain clustering_mean's and mu_F and the ladder outputs equal to madevent's. Mutation checks: ungating mu_R fails it in all 13 cases, ungating the merging jets and jet-scale booking fails all 12 new cases (the t-channel test alone missed that one). An ungated alpha_s step cannot be seen: the entries madevent rewrites (the central mT, the raised last scale) are never alpha_s-reweighted vertices in any fixture (0/6000 W+jet events differ either). Co-Authored-By: Claude <noreply@anthropic.com>
…r Pythia8 - RunCardMG7.create_default_for_process: a process generated with several multiplicities, the higher ones only adding jets, on hadronic beams, is set up as an MLM merged sample, as madevent sets ickkw = 1: dynamical_scale_choice = "mlm", xqcut = 30, no jet-jet and jet-lepton delta_r cut. The "only jets added" test is mlm_matching_needed(), shared with RunCardLO. - Pythia8: for an MLM sample Main:InternalAnalysis is switched on (new hidden PY8Card parameters, a user setting wins), so main164 writes the differential jet rates to djrs.dat. - HwUList.from_pythia8_main164 reads that file: main164 writes <run id="0""> (no XML parser takes it) and two columns. Each job of a split shower is scaled by 1e9 n_job / n_total (main164 normalises every job to the full cross section, in mb), the serial shower by 1e9. A DJR read error only warns instead of aborting the shower. - create_plot: ickkw is read through __getitem__ (an mg7 run card answers the legacy key without containing it, so the plots were never made), and an observable without data (main164 writes no pts.dat) is skipped rather than stopping before the rendering. Checked end to end: p p > e+ ve + 0,1j output mg7, launched with only shower=pythia8, runs MLM (xqcut 30, qCut 45), reports the matched cross section (6157 pb) and writes HTML/<run>/tag_1_PY8_plots with the DJR PDF; the d01 integral equals the matched cross section (5915 vs 5953 +- 55 pb). Co-Authored-By: Claude <noreply@anthropic.com>
HwUList.from_pythia8_main164 also reads <jethistograms njet="n"> blocks (returned titled |JETSAMPLE@n, as the plots expect) and a third column with the bin uncertainty. main164 writes neither today; a patched main164 (per-sample histograms from JetMatchingMadgraph::nMEpartons(), errors via Hist::table(..., printError), the <run id> quote fixed) does, and the DJR plots then show the 0/1/2-jet contributions. The former MG5aMC_PY8_interface format is now told apart by the header attribute of its <run>. Today's main164 output reads as before. Co-Authored-By: Claude <noreply@anthropic.com>
…ia8 does it Stock main164 (Pythia8 8.317) histograms the jet rates of its internal analysis summed over the jet samples, so an MLM run's DJR plots cannot show the 0/1/2-jet contributions the former MG5aMC_PY8_interface plotted. Until a Pythia8 release can (a merge request is being prepared), an MLM shower builds a patched copy of main164 when the installed one cannot: - misc.patch_pythia8_main164_djrs: tags each event with the number of matrix-element partons of the MLM matching (JetMatchingMadgraph:: nMEpartons, the former interface's tag), writes the histograms per jet sample in <jethistograms njet="n"> blocks with the bin errors, and fixes the '<run id="0"">' of Pythia8 <= 8.317. Pythia8's own main164.cc is never modified; a main164.cc the patch does not know (anchors checked on 8.316 and 8.317) or one that already splits per jet sample is used as is. - misc.get_pythia8_main164(jet_sample_djrs=True): builds that copy as a variant next to the HepMC ones, <examples>/main164[_hepmcN]_djr (or <proc>/lib/PY8_main164[_hepmcN]_djr for a read-only Pythia8), stamped with the patch version. - do_pythia8 picks the main164 build after the card setup, so that the MLM run's Main:InternalAnalysis selects it; a failed patched build falls back to the stock main164 with a warning. DJRoutput:jetSamples (hidden pythia8_card switch, on by default, not a Pythia8 setting) turns it off. - install pythia8 prebuilds the patched main164. Checked end to end: p p > e+ ve + 0,1j output mg7, launch with only shower=pythia8, builds examples/main164_djr once and writes DJR plots with the 0- and 1-jet samples and their statistical errors. Co-Authored-By: Claude <noreply@anthropic.com>
The kt measure min(pt1^2, pt2^2) * 2 * (cosh(deta) - cos(dphi)) / D^2 of cluster.f subtracts two numbers close to one for a nearly collinear pair and lost up to eight digits there (1.2e-8 relative on the 128 events of test_kt_measure_matches_the_fortran, 2.2e-6 on 20000 g g > g g g events). Scalar libm and SIMD SLEEF round cosh/cos differently in the last bit, so the two gave different scales: test_kt_measure_matches_the_fortran failed in the simd_256/avx512y/simd_512 wheel tests (1.5e-9 > 1e-9). It is now evaluated as 4 * (sinh^2(deta/2) + sin^2(dphi/2)), with eta = asinh(pz/pt) (no cancellation for a forward parton, unlike log((|p| + pz)/(|p| - pz))) and dphi = atan2(cross, dot); the Durham measure 2 min(E1^2, E2^2) (1 - cos_theta) likewise as 4 min(E^2) sin^2(theta/2) with theta = atan2(|p1 x p2|, p1.p2). Identical to cluster.f in exact arithmetic. - On identical momenta, scalar and NEON (simd_128) now give bit-identical scales (200000 values). - Scalar: 539 of 200000 values move by more than 1e-9; every one that could be checked against a 60-digit evaluation (97) moved towards it: old error up to 2.2e-6, new at most 1.2e-12. - test_mlm: the Python reference uses the same form; test_a_nearly_collinear_pair_keeps_its_clustering_scale (kt and Durham, pairs 1e-5 apart) fails with the old formula in any mode (1e-6 off). - madspace suite: 1995 passed in scalar and in simd_128. Co-Authored-By: Claude <noreply@anthropic.com>
The interim patch of main164 now applies exactly the change prepared for Pythia8 (commit 89d24fa4 of a local clone of Pythia8 8.318, to be proposed upstream): the setting InternalAnalysis:jetSamples (default off) writes the DJR histograms per jet sample in <jetsample njet="n"> blocks, two columns, with the jet sample tagged for kt-MLM (nMEpartons().first), shower-kT (getProcessSubset().size()), FxFx (npNLO()) and merging (Merging:nRequested - Merging:nRecluster), and the <run id> quote fixed. On 8.318 the patched file is identical to that commit apart from the two-line MadGraph7 header; it also applies to 8.316 and 8.317. - PY8Card: InternalAnalysis:jetSamples (hidden, default off) replaces the MadGraph7-only DJRoutput:jetSamples. An MLM run sets it on (a user setting wins); a main164 that already knows it is used as is, one that does not is patched, and an unpatchable one does not get the setting in its command file (Pythia8 would refuse it). - HwUList.from_pythia8_main164 reads <jetsample> blocks (and still the <jethistograms> ones of the first interim patch). - Tests follow; one checks the patch against the installed Pythia8. End to end: p p > e+ ve + 0,1j, output mg7, launch with shower=pythia8 only: main164_djr rebuilt, InternalAnalysis:jetSamples=on in the command file, per-sample DJR plots. Co-Authored-By: Claude <noreply@anthropic.com>
test_main164_djr_patch_is_the_proposed_pythia8_change checked the patch against the main164.cc of a Pythia8 under ~/Documents/HEPTools, and was skipped where there is none, as on CI. tests/test_manager.py exits 1 on any skipped test, so unittest_bundle_7 failed although all its tests passed. The patch content stays covered by test_main164_djr_patch; the real Pythia8 8.316-8.318 sources were checked by hand. Co-Authored-By: Claude <noreply@anthropic.com>
zeniheisser
pushed a commit
to zeniheisser/MadGraph7
that referenced
this pull request
Oct 29, 2026
GitHub retires the macos-14 hosted image on 2026-11-02, with brownouts (jobs fail) on 2026-10-29 and 2026-10-30, 14:00-24:00 UTC, and reduced capacity before that: on 2026-10-28 the macOS wheel job of MadGraphTeam#72 was never given a runner. macos-15 is the arm64 replacement GitHub recommends; macos-latest is avoided because it now means macOS 26 and keeps moving. MACOSX_DEPLOYMENT_TARGET stays 14.5, so the wheels still install on macOS 14.5 and later. Co-Authored-By: Claude <noreply@anthropic.com>
MLM review follow-ups: hidden run-card options, one weight_after_cuts rule, tests
MLM automatic for a human user: merging on by default, DJR plots after Pythia8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
This branch implements MLM merging in madspace. The core part is a clustering algorithm that takes momenta as its input and returns:
The first two affect the cross section and PS distributions, whereas the last two are relevant for the LHE output.
The two central parts are:
madspace/src/phasespace/mlm_clustering.cpp: takes the diagram topologies for the given subprocess as input and compiles them into an efficient binary representation of the possible clustering histories. This representation will also nicely generalize to Amplicol.madspace/src/kernels/mlm.hpp: the actual compute kernel run during event generation for CPU and GPU. Based on the previously compiled state machine, it determines a clustering and then assigns the scales. The functionsdjb_clus,dj_clus,compute_scale,update_momentaare directly ported from the NLO fortran code. The actual clustering logic happens inmlm_clustering. The actual scale computation (after a clustering has been determined) is mainly based on my conversations with Rikkert when he was at CERN, and should be similar to what the NLO code does.Things to discuss
Rikkert might have opinions here. I don't really know the physics well enough...
Missing pieces
madspace/src/phasespace/mlm_clustering.cpp: right now the metadata whether a given vertex is QCD, an output is a jet, propagator masses etc. does not get populated. Instead massless QCD is assumed. In principle all the necessary information should be there, so this should be trivial to changeValidation
Here is a three-step plan. The challenging part of validation is that it is not always clear what the baseline is (see things to discuss), also given that to some extent there is no right and wrong here.
Unit tests
Parton level
Shower level