Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

PP20 ζ(p) transcription fix

San Diego State University

PP20 magnification factor ζ(p) — transcription bug fix

Date: 2026-04-28 Module (at the time): progenax.gravoturb.pp20_magnification → now gravoturb.theory.dense_gas_sfr Companion fix: swindlax.physics.density_gradient (already locked, 29 regression tests)

What changed

PathΔ linesNature of change
src/progenax/gravoturb/pp20_magnification.py+112 / −56Replace buggy ζ(p) with canonical analytic form; add P_MAX = 1.95; rewrite module + function docstrings
tests/unit/physics/test_parmentier.py+178 / −131Re-anchor every numerical assertion on PP20 Eq. 6 / analytic values; replace “p=1.3 singularity” tests with “smooth across p=1.3” + “diverges only at p=2”
tests/unit/physics/test_pp20_zeta_canonical.py+303 / 0New regression file ported from swindlax — 28 tests anchoring ζ(p) on PP20 Eq. 6, the integral-derived analytic form, and Kainulainen+2014

No production code outside pp20_magnification.py was modified. No JSON / NetCDF / committed validation outputs were regenerated.

The bug

Inline form prior to this fix:

zeta = (3.0 - p) / (2.6 - 2.0 * p) ** 1.5

Three transcription errors against PP20 Eq. 6 (Parmentier & Pasquali 2020, ApJ 903, 56, page 2):

  1. The constant 2.6 was moved inside the 3/2 power.

  2. The (3 − p) factor lost its 3/2 exponent.

  3. The multiplicative factor 2.6·(2 − p) was garbled into linear (2.6 − 2p).

Numerical impact: the buggy denominator 2.6 − 2p vanishes at p = 1.3, which the docstring rationalised as a “domain limit.” PP20 Eq. 6 has no singularity at p = 1.3; the only true singularity is at p = 2 (singular isothermal collapse). The buggy form gave ζ(0.5) ≈ 1.235, ζ(1) ≈ 4.30 (clamped via max(., 1.0)), and arbitrary post-clamp values for p ≥ 1.3 — none of which match PP20.

The fix

Canonical form, derived from the integral definition ζ = ∫ρ^(3/2)dV / (M·√⟨ρ⟩) for a power-law profile ρ(r) = ρ_R(r/R)^(−p) on a sphere of outer radius R:

P_MAX = 1.95

@jax.jit
def magnification_factor(p):
    p_safe = jnp.clip(p, 0.0, P_MAX)
    return 2.0 * (3.0 - p_safe) ** 1.5 / (3.0 ** 1.5 * (2.0 - p_safe))

Equivalent to PP20 Eq. 6 to 0.08% — PP20’s “2.6” is the rounded value of 3^(3/2)/2 = 2.598. Spot values now reproduce PP20 / analytic anchors:

pThis fixPP20 Eq. 6Buggy form (pre-fix)
01.0 (exact)0.999 (rounding)clamped to 1.0
0.51.0141.0141.235
1.01.0887 (= 2·2^(3/2)/3^(3/2))1.08794.30
1.5√2 (exact)1.4142∞ (denom → 0)
1.671.7891.789clamped / garbage
1.958.288.29clamped / garbage

P_MAX = 1.95 keeps gradients well-behaved under HMC/NUTS by shielding the p → 2 divergence, matching the same convention used in swindlax.physics.density_gradient and PP20 Fig. 1.

Test-count delta

SuitePre-fixPost-fixΔ
tests/unit/physics/test_parmentier.py34 passing (against the bug)36 passing (against PP20)+2 (replaced p=1.3 singularity tests with PP20-anchored monotonicity / divergence / Kainulainen / exact-analytic anchors)
tests/unit/physics/test_pp20_zeta_canonical.py35 passing+35 new
progenax/tests/ total (excluding pre-existing import error in test_knobs_pipeline.py)777812+35

The test_knobs_pipeline.py ModuleNotFoundError: progenax.profiles.mass_segregation predates this work (commit 13df53f removed the module without removing the test); not in scope.

Verification

Validation artefacts (resolved in the 2026-06 rewrite)

At the time of this fix, the legacy validation/turbulence/bm19_fdf_suite/ suite still held stale, typo-rationalising claims (e.g. a “singularity at p = 1.3” caption in its VALIDATION_SUMMARY.md, and b5/b6/e5 comparison plots drawn against the buggy analytic ζ curve). These were flagged for regeneration rather than fixed in the 2026-04-28 commit.

They were not regenerated — they were removed. The entire bm19_fdf_suite (and the progenax.gravoturb / cluster.fdf* modules it imported) was deleted in the 2026-06 clean-room rewrite. The corrected ζ(p) and the honest gravoturbulent validation now live in the experimental gravoturb package, whose acceptance suite prints its ζ anchors (AC3) and the BM19 cornerstone (AC6) — see src/experimental/gravoturb/VALIDATION_SUMMARY.md. There is no longer a monolithic bm19_pipeline/BM19Result orchestrator; the forward chain is composed from gravoturb.theory.* + gravoturb.realization.*.

References