Skip to content

madspace: stable 2->3 Jacobian when a particle is soft - #174

Merged
ramonpeter merged 8 commits into
mainfrom
fix-two-to-three-gram-precision
Sep 20, 2026
Merged

ramonpeter merged 8 commits into
mainfrom
fix-two-to-three-gram-precision

Conversation

@oliviermattelaer

Copy link
Copy Markdown
Contributor

Problem

ColorOrderedMapping occasionally returns weights up to ~1e20 times the typical one, which can throw an integration off by orders of magnitude. The source is TwoToThreeParticleScattering, the 2→3 peel used for any color-ordered set of three or more particles. It weights each point with the Byckling–Kajantie Jacobian 1/(8 sqrt(-G4)), forming the 4×4 Gram determinant G4 (and cos φ) directly from invariants of order s ~ 1e6 GeV². On the s23 range, G4 factorises:

-G4 = λ(s12, ma², t2)/16 · (s23 − s23_min)(s23_max − s23)

When a particle is soft the range is narrow. Terms of order s⁴ then have to cancel to a result smaller than their rounding error. Beyond ~1e-8 of relative softness the block returns garbage or NaN: 1e14 instead of O(1) at block level, in both the "peeled particle soft" and "previous particle soft" limits.

How often it hits

Massless 4- and 5-body phase-space volume at √s = 1 TeV, 8 batches × 2M points, estimate ÷ analytic (π/2)^(n−1) s^(n−2)/((n−1)!(n−2)!):

color order origin/main, per batch this PR, per batch this PR, combined
single chain [0,2,3,4,5,1] 1039, 0.99, 1.00, 1.01, 1.00, 1.01, 0.99, 1e12 0.986 – 1.022 1.003
1+3 split [0,2,1,3,4,5] 1.02, 105, 0.99, 1.00, 1.01, 24, 1.00, 0.99 0.993 – 1.007 0.998
5-parton chain 1.9, 5455, 1.0, 1.7, 706, 194, 19, 0.99 0.980 – 1.094 1.009

The largest weight in the 4-parton chain falls from 2e18× to 1.6e4× the exact volume.

Fix

Both cos φ and the Jacobian are now computed from u = (s23 − s23_min)/(s23_max − s23_min), the position of s23 in the same range the invariant was sampled from:

  • cos φ = 2u − 1
  • 8 sqrt(−G4) = sqrt(λ) · (s23_max − s23_min) · |sin φ|, with |sin φ| = 2 sqrt(u(1−u))

Any rounding in the range cancels between the invariant's Jacobian and this one. The inverse reads |sin φ| off the momenta. bk_gram4, get_phi_from_s23 and lup_det4 have no remaining users and are removed.

At regular points old and new agree: median relative weight difference 1e-13, 99.9% of points within 1e-6. Forward/inverse round trips improve where the old determinant was ill-conditioned; for example, the worst |det·det_inv − 1| for a 5-body chain with t and W masses goes from 1.0 to 5e-4.

Tests

New madspace/tests/test_two_to_three_soft_limit.py:

  • test_jacobian_with_soft_peeled_particle, test_jacobian_with_soft_third_particle: drive the block into both soft limits down to δ = 1e-14 and require the Jacobian to follow its smooth limit (within a factor of 2).
  • test_color_ordered_massless_volume: the analytic 4-body volume for the single chain and the 1+3 split, averaged over 8 × 2M points, within 5%.

All 4 fail on origin/main and pass here. Full madspace suite: 1571 passed. The only local failures are 4 test_double_t tests needing scipy, the same on origin/main, plus 2 torch-only modules I skipped. Also built with ENABLE_SIMD=ON (arm64); the 2→3, t-channel, cut and new tests (288) pass there. The CUDA/HIP builds could not be tried locally; the kernel only uses functions already used on those backends (min, max, where, sqrt, acos, fabs).

Not addressed

  • 1/sin φ edge peak. It sits at the edges of the s23 range and comes from sampling s23 rather than φ. The mean is correct but the variance is log-divergent; it is the remaining tail in the table above. Sampling u through e.g. u = sin²(πr/2) would cancel it, but that changes the s23 importance sampling, so it is left as a design decision.
  • Soft-limit NaN points. A few points deep in the soft limit (≲ 1e-8) still come out as NaN. They get zero weight, and the region's contribution is negligible.
  • Inverse kernel. The inverse is still unreliable at those points, and forward/inverse round trips with pT cuts fail for ~1% of points on both origin/main and this branch.
  • Relation to madspace: hand every child's pair cuts to ColorOrderedMapping #173. Independent of that PR: different file, and this reproduces with no cuts on origin/main.

🤖 Generated with Claude Code

TwoToThreeParticleScattering weighted each point with the Byckling-Kajantie
Jacobian 1 / (8 sqrt(-G4)), forming the 4x4 Gram determinant G4 (and
cos(phi)) directly from invariants of order s. On its s23 range G4
factorises as

    -G4 = lambda(s12, ma^2, t2) / 16 * (s23 - s23_min) * (s23_max - s23),

so when a particle is soft and the range is narrow, terms of order s^4 had
to cancel to a result smaller than their rounding error. The block then
returned garbage Jacobians or NaN (at block level 1e14 instead of O(1)),
starting from about 1e-8 of relative softness.

In ColorOrderedMapping that is about one point in a few million, enough to
wreck an integration. Checked on the massless 4- and 5-body phase-space
volumes at 1 TeV (8 batches of 2M points), per-batch estimates came out
at 20x, 100x, 700x, 5000x and 1e12x the analytic value for the 4-parton
single chain, the {d}|{u s c} split and the 5-parton chain. With this
change every batch is within 2% (4 partons) or 10% (5 partons), and the
combined estimates are 1.003, 0.998 and 1.009.

cos(phi) = 2u - 1 and the Jacobian 1 / (sqrt(lambda) (s23_max - s23_min)
|sin(phi)|) are now computed from u = (s23 - s23_min) / (s23_max - s23_min),
the position of s23 in the same range the invariant was sampled from. Any
rounding in that range cancels between the two. The inverse reads |sin(phi)|
off the momenta. At regular points the old and new weights agree to 1e-13
(median), and forward/inverse round trips improve where the old
determinant was ill-conditioned. bk_gram4, get_phi_from_s23 and lup_det4
have no remaining users and are removed.

The integrable 1/sin(phi) peak at the edges of the s23 range remains: it
comes from sampling s23 rather than phi, gives correct means with a
log-divergent variance, and is left as a sampling design choice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ants

The previous commit stopped forming the 4x4 Gram determinant, but the
Jacobian and cos(phi) are now read off the s23 range, which s23_min_max
still built from sqrt(G3i * G3im1) of Byckling-Kajantie. Those 3x3 Gram
determinants vanish in the soft limit too and cancel down to the square
of the range width, so below a softness of about 1e-8 the range came out
as noise: NaN on arm64, and NaN or an exact zero Jacobian on x86_64, where
the new soft-limit tests failed in the wheel CI.

In the p12 rest frame with z along pa and p3 in the phi = 0 half-plane,
s23 = m0^2 + m1^2 - 2[(sqrt(s12) + E3) E1 - p3_z p1_z] + 2 p3_x p1_t cos(phi),
so the range has width 4 p3_x p1_t, every factor of which is read off
momenta at full relative precision. With flat s23 sampling the soft-limit
Jacobians now reach their constant limit to 1e-7 down to delta = 1e-14,
and agree with the old code to 7 digits wherever it was finite.

bk_V, bk_sqrt_g3i_g3im1 and the LU helpers in lup_det.hpp have no users
left and go. assert_smooth now demands every point be finite, which
fails on the previous commit on every platform.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ramonpeter
ramonpeter self-requested a review September 19, 2026 08:31
@ramonpeter

Copy link
Copy Markdown
Contributor

@oliviermattelaer , thats nice. I will also check how to update the inverse then.
I need to fully check the math, but if this generally holds this might also be worth to add in the madspace paper.
This would then improve over Rikkerts implementation.

ramonpeter and others added 5 commits September 20, 2026 14:26
On its kinematic range s23 is linear in cos(phi) and the Byckling-Kajantie
Jacobian is 1 / (sqrt(lambda) (s23_max - s23_min) |sin(phi)|), so the
measure is flat in phi. Sampling s23 with any density that is finite at the
kinematic limits (flat or 1/s^nu) leaves an integrable 1/|sin(phi)| in the
weight: correct mean, log-divergent variance. This is the remaining tail
noted in #174.

TwoToThreeParticleScattering now first maps the random number flat in
theta = phi / 2, x = (sin^2 theta - sin^2 theta_a) / (sin^2 theta_b -
sin^2 theta_a), and hands x to the existing s23 Invariant, so the 1/s^nu
importance sampling (and Breit-Wigner) is kept. theta_a, theta_b locate the
sampled range inside the kinematic one: without cuts they are 0 and pi/2 and
dx/dr vanishes like |sin(phi)| at both edges, cancelling the peak; at an
edge set by a cut there is no peak, and dx/dr stays finite there. With
flat s23 sampling the block weight becomes exactly independent of r_s23.

New instructions s23_arcsine / s23_arcsine_inverse; option arcsine_s23
(default true) on TwoToThreeParticleScattering and ColorOrderedMapping.
Massless volumes at 1 TeV, 8 x 1M points, flat -> arcsine:
  4-parton chain  sigma/mu 11.3 -> 4.5, max w/mu 8.1e3 -> 340
  1+3 split       sigma/mu  7.9 -> 4.6, max w/mu 5.4e3 -> 350
  5-parton chain  sigma/mu 25.1 -> 6.0, max w/mu 3.1e4 -> 920
  4-chain, cuts   sigma/mu  8.8 -> 3.4, max w/mu 9.6e3 -> 190
with unchanged volumes (analytic, and the fiducial volume with cuts).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015q34kWgihoQCVRUFwMq2xS
…inverse

With cuts, ColorOrderedMapping failed the forward/inverse round trip for
about 3% of the points (4-parton chain, pt > 20, m_ij > 10, dR > 0.4 at
1 TeV), carrying about 4% of the fiducial volume. Two causes:

- rotate(p, q), the frame of the 2->2 LAB block around the incoming
  momentum, took the identity when q_t == 0 exactly and otherwise an azimuth
  set by atan2(q_y, q_x). The walk builds that momentum along the beam up to
  rounding, differently in forward and inverse, so the recovered r_phi was
  off by an arbitrary angle, often pi/2. rotate is now the minimal rotation
  z -> q (and F R(F q) with F = diag(-1, 1, -1) in the lower hemisphere),
  smooth across the beam axis and equal to the old one on it.

- s23_etmin_clamp applies the smin bound only for a massless peeled
  particle, tested as m2_2 < EPS = 1e-12. The inverse reads m2_2 off the
  momentum, where a massless particle comes out at ~1e-16 E^2, so the bound
  switched off and the sampled s23 range changed. The test is now relative,
  m2_2 < 1e-10 E^2. The forward is unchanged (m2_2 = 0 there).

With both, no point of 200k fails (|det det_inv - 1| < 1e-2), the 99th
percentile is 1e-10, and every random number is recovered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015q34kWgihoQCVRUFwMq2xS
…truction

Light-like momenta of ColorOrderedMapping came out off shell by up to
sqrt|p^2|/E ~ 4e-5 (massless 4-parton chain at 13 TeV), about 1e-3 of the
points above 2e-6; test_t_channel_masses[4 particles, massless-color_ordered]
failed for 2 of 40 seeds on main. Three causes:

- rotate_two_ref, the frame of the 2->3 block, did one Gram-Schmidt pass.
  With p3 close to pa in the p_12 rest frame the x axis kept a component
  along z of relative size eps |p3| / |p3 perp|, the rotation was not
  orthogonal and changed |p1|, and the back-to-back daughters came out off
  shell even for p_12 almost at rest. A second pass fixes it.
- A soft daughter was formed as p_12 - p1 in the lab, or thrown backwards
  through `boost`, and lost a factor gamma^2 (or E_12 / E) of relative
  precision. boost_two_body now boosts the softer daughter on its mass
  shell (boost_light_cone: light-cone components along the boost axis, the
  smaller one from the mass shell) and takes the harder one as p_12 minus
  it. That keeps p1 + p2 = p_12 by construction, as before, and the harder
  one carries at least half the energy, so it loses no precision. Used in
  the 2->3 and the 2->2 LAB kernels.
- In the color-ordered walk each 2->3 block got its system as
  pa + pb - p3 with pb = R_a + R_b - beam + im1, all beam-sized, so a soft
  system had an absolute error of order the beam energy. The kernels now
  take p_12 itself (input p12 in place of pb; TwoToThreeParticleScattering
  forms it as pa + pb - p3 unless p12_condition is set), and the walk hands
  each block the chain output of the previous one. The last particle is the
  chain of the last block, and the inverse sums the chain systems from the
  soft end.

Maximum sqrt|p^2|/E in the partonic frame: 4e-5 -> 3.5e-7 (4-parton chain),
4e-5 -> 3.9e-7 (5-parton chain); none above 2e-6 in 1M points. Momentum
conservation is unchanged: |sum p_out - sum p_in| <= 3.6e-12 at 13 TeV, as
on main, and the existing conservation tests pass unchanged.

The block-level round trip in test_2to3_scattering now runs with and
without the arcsine map; with it, points close to the edges of the s23
range recover r_s23 to ~1e-8 only, since the inverse has to resolve
s23 - s23_min from s23. The beam boost to the hadronic frame (boost_beam)
is untouched and still leaves up to ~1e-5 there (3e-5 on main).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015q34kWgihoQCVRUFwMq2xS
With the arcsine map most weight sits close to the edges of the s23 range,
where u = (s23 - s23_min) / (s23_max - s23_min) is small. Forward and
inverse both went through s23 itself, which only knows s23 - s23_min to
~1e-16 s, so near the edges the round trip recovered r_s23 to ~1e-8 only
and det * det_inv to ~1e-5.

Now the position in the range is carried as the pair u, 1 - u in both
directions:

- s23_arcsine_sample composes the arcsine map with the s23 importance
  sampling of Invariant (flat, logarithmic or 1/(s - m^2)^nu, same
  regularisation) in one kernel, carrying every quantity as its distances
  to both ends of its range, with the power maps through expm1 and log1p.
  s23_arcsine_sample_inverse inverts it from u, 1 - u.
- two_to_three_particle_scattering takes u, 1 - u instead of s23 and
  builds cos(phi) = (u - (1 - u)) / (u + (1 - u)) and |sin(phi)| from
  them; the inverse returns u, 1 - u read off the momentum, the one close
  to zero as p_y^2 / (2 p_t (p_t -+ p_x)).
- Without the arcsine map, or with a Breit-Wigner, s23 is sampled as
  before and s23_position gives u, 1 - u.

Near the edges r_s23 now comes back to ~1e-17 absolute (the precision of
the momenta) and det * det_inv to ~1e-11. The round-trip tests are strict
again, with a new one within 1e-12 of both edges. The mapping, and so the
volumes and weight distributions, are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015q34kWgihoQCVRUFwMq2xS
boost_beam wrote the boost along z as E cosh y + p_z sinh y. A momentum
moving against the boost comes out of the cancellation of terms of order
e^{|y|} E and loses a factor e^{2|y|} of relative precision; after the
fixes in the color-ordered chain this was the largest remaining source,
with |p^2 - m^2| / E^2 up to ~3e-10 (sqrt|p^2| / E ~ 2e-5) for massless
momenta at 13 TeV.

boost_beam and boost_beam_inverse now take the external masses
(PhaseSpaceMapping passes them in the order after the channel permutation,
-1 where permutations disagree). A momentum whose lab energy drops below its
partonic one is boosted in light-cone components along z, the smaller one
from the mass shell. It is only done for momenta that are on their mass
shell to rounding (|p^2 - m^2| <= 1e-13 E^2); others, from degenerate
points or from mappings whose partonic momenta carry larger errors, keep
the linear boost. The shifts this makes are handed to the outgoing momentum
with the largest energy, so momentum is conserved exactly as with the
linear boost.

Color-ordered, massless 4-parton chain at 13 TeV: max |p^2 - m^2| / E^2
2.8e-10 -> 9.5e-13 in the lab frame, conservation unchanged. Other modes
are not worse (propagator 5.9e-11 -> 7.8e-12, RAMBO 5e-12 -> 1.9e-13); what
remains there comes from their partonic momenta.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015q34kWgihoQCVRUFwMq2xS
@ramonpeter

ramonpeter commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

@oliviermattelaer I pushed five commits on top of yours. Your two commits are unchanged. They address the "Not addressed" list (the 1/sin φ edge peak and the unreliable inverse) plus a few precision issues found while checking it. The full madspace suite passes on this branch and also merged with current main (incl. #173), and with ENABLE_SIMD=ON.

1. Arcsine map for s23 (removes the 1/sin φ tail)

s23 is linear in cos φ and the 2→3 measure is flat in φ. Any s23 density that is finite at the kinematic limits therefore leaves an integrable 1/|sin φ| in the weight, with a log-divergent variance. The random number of s23 is now first mapped flat in φ/2 and then handed to the existing s23 importance sampling, which is kept. The weight is bounded at the edges. With flat s23 sampling the block weight is exactly independent of r_s23. New option arcsine_s23 (default on) on TwoToThreeParticleScattering and ColorOrderedMapping.

Massless phase space at 1 TeV, 8 × 1M points, flat → arcsine:

σ/μ max w/μ
4-parton chain 11.3 → 4.5 8.1e3 → 340
1+3 split 7.9 → 4.6 5.4e3 → 350
5-parton chain 25.1 → 6.0 3.1e4 → 920
4-chain, pT/m_ij/ΔR cuts 8.8 → 3.4 9.6e3 → 190

Volumes are unchanged: the analytic value without cuts, and the fiducial volume with cuts.

2. Inverse / round trip

  • With cuts: about 3% of points failed the forward/inverse round trip, carrying about 4% of the fiducial volume. Now none fail in 200k points. There were two causes:
    • rotate (frame of the 2→2 LAB block) switched between two conventions on the exact test q_t == 0. Forward and inverse round the beam-side momentum differently, so r_φ came back rotated, typically by exactly π/2. It is now the minimal rotation z → q, smooth across the beam axis and identical to the old one on it.
    • Block B applied its smin bound only when m2_2 < EPS. The inverse reads m² off the momentum (~1e-11 for a massless particle), so the bound switched off and the sampled s23 range changed. The massless test is now relative to the energy scale. The forward is unchanged.
  • Near the edges of the s23 range: forward and inverse went through s23 itself, which only knows s23 − s23_min to ~1e-16 s. The block now carries u = (s23 − s23_min)/Δ and 1 − u in both directions. The new s23_arcsine_sample does the arcsine map and the importance sampling in one step via expm1/log1p, and the inverse reads u, 1 − u off the momenta. r_s23 now comes back to ~1e-17 absolute (was ~1e-8) and det·det_inv to ~1e-11 (was ~1e-5), also within 1e-12 of the edges.
  • Inverse chain systems: these are now summed from the outgoing momenta, soft end first, instead of from beam-sized differences.

3. On-shell momenta, momentum still conserved by construction

Light-like momenta came out off shell by up to √|p²|/E ≈ 4e-5. This is why test_t_channel_masses[4 particles, massless-color_ordered] is flaky on main (fails for 2 of 40 seeds). Three causes:

  • rotate_two_ref did a single Gram–Schmidt pass. With p3 close to pa the frame was not orthonormal, the rotation changed |p1|, and both daughters came out off shell. A second pass fixes it.
  • Soft daughters formed as p12 − p1 (or boosted against a fast p12) lost precision. Now the softer daughter is boosted onto its mass shell in light-cone components and the harder one is p12 minus it, so p1 + p2 = p12 still holds by construction. This is used in the 2→3 and 2→2 LAB kernels.
  • Each 2→3 block got its system as pa + pb − p3, built from beam-sized momenta. The kernels now take p12 directly (pbp12, formed as before unless p12_condition is set), and the color-ordered walk hands each block the chain output of the previous one.

Max √|p²|/E is now ~4e-7 in the partonic frame (was 4e-5). Momentum conservation is unchanged, and the existing conservation tests pass with their original tolerances.

4. Boost to the hadronic frame

boost_beam lost a factor e^{2|y|} of precision for particles moving against the boost. It now takes the external masses and boosts those particles in light-cone components. It only does so for momenta that are already on shell to rounding; everything else keeps the linear boost. The resulting shifts are absorbed by the hardest outgoing particle, so conservation is exactly as before.

Lab-frame max |p² − m²|/E²:

  • color-ordered: 2.8e-10 → 1e-12
  • propagator: 5.9e-11 → 7.8e-12
  • RAMBO: 5e-12 → 1.9e-13

What remains in the other modes comes from their partonic momenta.

Behaviour changes

  • arcsine_s23 (on by default) and the new rotate change which event a given random number produces. The rotate change also affects the LAB 2→2 block in other modes. Trained flows or VEGAS grids need retraining; arcsine_s23=False restores the old s23 sampling.
  • Kernel signatures in instruction_set.yaml changed: pbp12 in the 2→3 kernels, the scattering kernels take u, u_c instead of s23, and boost_beam takes masses. New instructions: s23_arcsine, s23_arcsine_sample, s23_position (+ inverses).

6. Soft and collinear limits

This fixes the NaNs deep in the soft limit. I probed the block down to 1e-16 softness at the corners of the random numbers, and about a quarter of those points gave a non-finite inverse. There were two causes:

  • rotate_two_ref floored |p3⊥| at 1e-12. A p3 below ~1e-12 GeV therefore gave a non-unit axis, which put the momenta off shell by ~1e4 GeV². The new light-cone boost had the same floor for a p12 almost at rest. Both frames are now normalised without a fixed scale.
  • When p1 or p3 lies along pa, the s23 range has zero width and the weight is zero, but the inverse divided 0/0. Empty ranges were guarded with an absolute EPS, which is smaller than one ulp of s23 or t, so the guard did nothing. The sampling ranges now get a relative minimum width. The Jacobian still uses the true width, so the weight stays zero.

All probed points are now finite in both directions, and on shell to 1e-9 wherever the weight is nonzero. Round trips below ~1e-8 softness are still limited by the precision of the float momenta.

arcsine_s23 no longer supports a Breit–Wigner s23: it now raises an error, since no resonance is expected in this block. Without the arcsine map, Breit–Wigner sampling still works.

New tests: test_two_to_three_arcsine.py, test_color_ordered_precision.py and test_two_to_three_soft_limit.py, plus a strict inverse test in test_2to3_scattering.py. Numbers and details are in the commit messages.

#174 left a few points deep in the soft limit that came out as NaN. Probing
the 2->3 block at softness and collinearity down to 1e-16, at the corners
of the random numbers (6 configurations x 4 deltas x 72 corners), the
forward was always finite, but on #174 about a quarter of the points gave a
non-finite inverse. Three causes:

- rotate_two_ref floored |p3 perp| at sqrt(EPS2) = 1e-12. For p3 below
  ~1e-12 GeV the x axis came out shorter than 1, the rotation shrank p1,
  and the momenta were off shell by ~1e4 GeV^2 at nonzero weight.
  boost_light_cone (from the previous commits) had the same floor on |p|
  of the boost axis, for a p_12 almost at rest. Both frames are now
  normalised scale-free; a p3 with no usable part perpendicular to pa gets
  a fixed perpendicular axis. rotate uses the same normalisation.
- Where p1 or p3 is along pa (t1 at an end of its range, a collinear or
  vanishing p3) the s23 range has zero width, the azimuth is undefined and
  the weight is zero; the inverse divided 0 / 0. The guards for an empty
  range added an absolute EPS = 1e-12, below one ulp of s23 or t for
  values above ~1e4. The ranges handed to the s23 and t sampling now get a
  relative minimum width; the Jacobian of the scattering kernel keeps the
  true width, so the weight there stays zero.

Now every one of these points is finite in both directions, with and
without the arcsine map, and on shell to |p^2 - m^2| / E^2 < 1e-9 wherever
the weight is nonzero. Round trips at softness below ~1e-8 remain limited
by what the momenta can carry.

Also drop the separate s23_arcsine kernels: they only served a
Breit-Wigner s23 sampling combined with the arcsine map, and no resonance
is expected in this block. TwoToThreeParticleScattering now rejects
arcsine_s23 together with s_width != 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015q34kWgihoQCVRUFwMq2xS
@ramonpeter
ramonpeter merged commit f7bc530 into main Sep 20, 2026
280 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.

2 participants