Skip to content

loop-induced: stop the run when the poles do not cancel - #80

Open
oliviermattelaer wants to merge 6 commits into
claude/nlo-polarised-boost-assessment-d97794from
claude/loop-induced-pole-check
Open

loop-induced: stop the run when the poles do not cancel#80
oliviermattelaer wants to merge 6 commits into
claude/nlo-polarised-boost-assessment-d97794from
claude/loop-induced-pole-check

Conversation

@oliviermattelaer

Copy link
Copy Markdown
Contributor

A loop-induced amplitude is finite: its single- and double-pole coefficients must
vanish. Nothing checked that. This adds a check that stops the run when they do not.

Nothing was checking the poles

Three things looked like a pole check and none was one:

  • check_poles never reaches a loop-induced process. It is linked only by the FKS
    exporter; [noborn=] is exported through the LO/madevent templates, which have no
    check_poles.f and no BinothLHA.f. It is a different test in any case — it compares
    MadLoop's poles to the MadFKS Born-proportional ones.
  • The ##W04/##W05 blocks in madgraph/loop/loop_exporters.py are emitted as
    Fortran comments AND live only in the non-optimized exporter. The optimized exporter —
    the default, and what every loop-induced madevent run uses — had no pole test at all.
  • A runtime test at matrix_loop_induced_madevent.inc:418 is dead three ways:
    WRITE-only, capped at 10 points, and it sums the signed OneEps+TwoEps, so a
    positive single pole can cancel a negative double pole and report clean.

The check

New MLPoleCheckThres MadLoopParams option, default 1.0d-3, negative to disable,
emitted inside the generated MadLoop matrix element and gated on loop-induced at
generation time. That is the one place with access to every guard, and it covers
madevent, check_sa, f2py and OLP at once.

Also fixed along the way: the ##W04 block tested each loop amplitude individually,
which is wrong physics — individual loop diagrams do have poles, only the sum is finite,
and it compared them against a squared-ME scale, so it was dimensionally wrong too. The
non-optimized loop-induced squaring was linear in the pole while ANS(1) is quadratic;
the historical 10**5 fudge existed only to paper over that, so the squaring is fixed
rather than the fudge inherited. The signed sum in the madevent driver becomes
ABS(OneEps)+ABS(TwoEps). The standalone exporter no longer disables COLLIER's pole
computation, otherwise the check could never run there.

The threshold comes from measurement

1048 physics-phase points over four processes, check disabled so nothing truncates the
sample:

process median max
g g > z z 8.6e-14 5.9e-10
g g > h h 1.3e-14 3.9e-09
g g > z z g 2.1e-11 4.0e-05
g g > h h g 4.7e-13 5.6e-05

Ceiling 6e-5. 1e-4 was rejected at 1.8x headroom; 1e-3 gives ~20x and detects errors
of 0.7% or more, against 6% at the old 1e-2, with no additional false positive on this
sample. The 2->2 / 2->3 gap is a singular region appearing, not conditioning degrading
with leg count, so a 2->4 process adds more regions of the same character rather than
another four orders.

Evidence it is not vacuous

Injecting a 10% error on the eps^-1 residue of a dominant loop amplitude gives
##E03 and exit 1. End to end at the shipped default: clean run exit 0 (median 1.69e-14),
0.5% error not caught, 1% caught, 10% caught. Verified independently by a second reviewer,
who also confirmed the corrupted value genuinely travels through
COMPUTE_COLOR_FLOWS -> JAMPL -> ANS(2,0) rather than being fed back into the test.

Limitations, stated in the card rather than omitted

  • Points on a soft or collinear singularity can legitimately reach 5e-2. madevent
    never sees them (cuts run before the matrix element), but check_sa and launch feed
    uncut phase-space points. The card says so and says to raise the value there. Note this
    is not new to the threshold change: such a point exceeds the old 1e-2 too. The durable
    fix is to warn for the first N violations and stop only on a repeat — recorded as a
    follow-up, not implemented here.
  • MadLoop's own stability verdict does not bound the pole residue: the outlier above
    returns rc=217, H=2, "stable, no rescue needed".
  • An error on a subdominant amplitude is not caught at any usable threshold.
  • The madevent event loop with COLLIER installed stays inert by design (poles are
    turned off there for speed and the card cannot override it). The check is live in
    standalone, check_sa, launch, on any install without COLLIER, and in madevent when
    nhel != 0. The ##INFO announcing the skip no longer offers a remedy that cannot work.
  • Re-enabling COLLIER's poles in standalone costs ~2.5x per point (marginal, N=1000).

Non-loop-induced output is untouched

g g > t t~ [virt=QCD] with PYTHONHASHSEED=0: ten generated files md5-identical
base vs branch. IOTest goldens added for the optimized loop-induced path, which had none.

The vanishing-pole test of a loop-induced matrix element was emitted as
Fortran comments (##W04/##W05) in the default MadLoop output and was
absent altogether from the optimized one, which is the output every
loop-induced madevent run uses. Nothing checked the poles.

- add MLPoleCheckThres to MadLoopParams (default 1e-2, <=0 disables)
- optimized output: compare |ANS(2,0)|+|ANS(3,0)| to ANS(1,0) once the
  result is final and STOP on failure; skipped when the reduction tool
  is not computing the poles (COLLIER with the pole flags off), with a
  one-time INFO saying so
- default output: same check, plus the loop-induced squaring now builds
  the genuine eps^-1/eps^-2 coefficients of |M|^2 instead of a quantity
  linear in the pole (which is what needed the unexplained 10**5 fudge)
- drop the dead ##W04 block: it tested each loop amplitude separately,
  but individual loop diagrams do have poles; only their sum is finite
- gate the new declarations/blocks so a process with a Born produces the
  exact same loop_matrix.f as before (verified for g g > t t~ [virt=QCD]
  and by the short_ML_SMQCD_default/optimized IO tests)
- keep the COLLIER pole computation on for standalone loop-induced output,
  otherwise the check can never run there
- madevent loop-induced: compare |1eps|+|2eps|, not the signed sum, so a
  positive single pole cannot hide a negative double pole
- update the short_ML_SMQCD_LoopInduced/gg_hh reference
- ##E02 was already taken by the zero-reference error: use ##E03
- the ##INFO told users to flip COLLIERComputeUV/IRpoles in the card, which
  a madevent run overrides at init; say so instead
- name the guard conditions (POLES_COMPUTED / DO_POLE_CHECK) so the writer
  stops splitting a 230-char line mid-identifier
- say in the STOP message how to change or disable the tolerance
- drop the 10*ACCURACY(0) valve. ACCURACY is the accuracy of the *finite
  part* and it does not bound the pole residue: a g g > z z g point at
  threshold comes back H=2, "stable, no rescue needed", with a residue of
  5.2e-2. Keying the tolerance on it was simply the wrong instrument. It
  also never fired -- ACCURACY(0) is -1 whenever the stability test did
  not run -- so removing it leaves the effective tolerance unchanged.
- non-optimized output: use WRITE_MOM like the optimized one, and stop
  leaving blank lines where ##W05 used to be (Born output unchanged)
- IOTest gg_hh now covers BOTH exporters; the optimized one is what every
  real loop-induced run uses and had no golden file.

process_info.inc needs 'git add -f': .gitignore's PROC* pattern matches it
on a case-insensitive filesystem, so plain 'git add' skips it silently and
the test then dies with "Missing ref. files".
Distribution of (|ANS(2,0)|+|ANS(3,0)|)/|ANS(1,0)| over 812 physics-phase
points of g g > z z, g g > h h, g g > z z g and g g > h h g, at sqrt(s)
from 1.02x to 80x threshold:

  process       median     p99       max
  g g > z z     8.6e-14   3.5e-10   5.9e-10
  g g > h h     1.3e-14   1.0e-10   3.9e-09
  g g > z z g   2.1e-11   8.1e-06   4.0e-05
  g g > h h g   4.7e-13   1.2e-05   5.6e-05

The ceiling is 6e-5, so 1e-3 keeps a factor ~20 of headroom.

The response is linear in the injected error, but only for the amplitudes
that dominate the pole: perturbing the eps^-1 residue of one of those by d
gives a ratio of 0.13*d (z z) to 0.22*d (h h), so 1e-3 catches an error of
0.7% and up where 1e-2 needed 6%. A subdominant amplitude is invisible at
any usable threshold -- 10% on amplitude 5 of g g > h h, whose pole sits 3
orders of magnitude below the leading ones, reaches only 8e-6.

The gap between the 2->2 and 2->3 rows is not multiplicity scaling: a 2->2
at fixed s has no soft or collinear region and a 2->3 does, so what the
jump shows is a singular region appearing, not conditioning degrading with
leg count. A 2->4 adds more regions of the same character rather than
another four orders of magnitude, which is why 6e-5 is the ceiling worth
keeping headroom over and why 1e-4, at 1.8x, is too tight.

1e-3 adds no false positive over 1e-2 on this sample. The only point above
either threshold is a soft-gluon g g > z z g configuration at 1.0002x
threshold (ratio 5.2e-2) which fails at 1e-2 identically. madevent never
samples it because cuts are applied before the ME call, but check_sa and
launch do; the durable fix there is to warn on the first violations and
stop only on a repeat, which is left as a follow-up.
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