Engine A (MultiComponentCluster.from_components /
.from_mass_segregation / .from_imf) is progenax’s differentiable
multi-component lowered-isothermal family (Gieles & Zocchi (2015), §2.2):
every component shares ONE self-consistent potential solved from the
coupled Poisson equation, with a single free scale per component — the
velocity-scale ratio (mass segregation is
). Heavier components are more centrally concentrated
as an equilibrium property, not an imposed reshuffle — the contrast with
the primordial generator on the
mass-segregation page. Test files:
tests/validation/test_multimass_equilibrium_physics.py plus the DF-table
unit suites (tests/unit/profiles/test_limepy_tables.py; table threading in
tests/unit/profiles/test_limepy_multimass.py) — see the
test dashboard for the live per-suite counts. Figures + PASS/FAIL:
scripts/validate_multimass_equilibrium.py,
scripts/validate_multimass_anisotropy.py, scripts/validate_df_tables.py.
What is verified — isotropic equilibrium¶
Each row maps to assertions in test_multimass_equilibrium_physics.py;
Measured values are from the 2026-06-10 run of
scripts/validate_multimass_equilibrium.py (two-component model, ,
, , ALL PASS) unless noted.
Property | Tolerance (as tested) | Measured | Anchor |
|---|---|---|---|
Theoretical per-component (mean-field, no sampling) | , all | 0.5001–0.5002 (both components, ) | exact-quadrature virial oracle — the rigorous “each mass group is in equilibrium” statement |
Sampled global , unscaled | () | 0.497–0.500 (5 seeds 8000) | |
Per-component vs analytic LIMEPY moment | rel per component (core bin, k) | across resolved bins (figure panel b) | each component drawn from ITS OWN equilibrium DF |
Sampled per-group (light, -body observable, softening ) | 0.496–0.497 across | finite- estimator of the exact 0.5 | |
Sampled per-group (heavy) | () | as | documented finite- positive bias (see note) |
is the single-mass model; segregation grows with | ratio at 0, strictly monotone | over | controlled equipartition knob |

Figure 1:Multi-mass equilibrium summary (scripts/validate_multimass_equilibrium.py,
PASS). (a) Per-component density profiles: the heavy component is more
centrally concentrated — segregation built in as an equilibrium. (b) Sampled
(points) on the analytic LIMEPY moment (lines) for both
components — the proof each mass group is drawn from its own equilibrium DF
(agreement ). (c) Segregation strength
vs : 1.0 at , rising
monotonically to 2.6 at . (d) Per-group virial: theory
flat at 0.5 (thick faint lines); sampled light + global tight on 0.5; the
heavy component’s positive finite- offset grows with .
What is verified — anisotropic sampler¶
With a finite anisotropy radius, each component’s DF is the
Michie/Osipkov-Merritt LIMEPY form
,
. The sampler must carry the right anisotropy,
not merely “some”: sampled is gated
against the DF’s own -quadrature — including the LIMEPY
signature rise to a radial-bias peak near and turnover toward
(truncation removes the most radial orbits at the edge). Measured
values from the 2026-06-10 run of scripts/validate_multimass_anisotropy.py
(, , , ALL PASS).
Property | Tolerance (as tested) | Measured | Anchor |
|---|---|---|---|
Sampled vs DF quadrature (resolved bins, ) | per bin, seed-averaged (8 bins checked) | all 8 bins pass; e.g. vs DF 0.219 at (8 seeds 60k) | the DF’s own 2nd moments — rise + truncation turnover reproduced |
peak location/height | (shape, not gated separately) | peak near , turning over toward | LIMEPY truncation signature |
Edge bins (): ratio-noise convergence | not gated (plotted with errors); convergence study | 16-seed average over 0.65–: vs DF 0.193 | is a ratio statistic; at the edge, so single-seed is noise-dominated — it converges to the DF under seed-averaging |
Global (anisotropic), unscaled | , all | 0.495–0.501 over (4 seeds 20k) | the scalar virial theorem is anisotropy-blind |

Figure 2:Anisotropic sampler validation (scripts/validate_multimass_anisotropy.py,
PASS). (a) Seed-averaged sampled (± sem, 8 seeds
60k) on the DF’s own quadrature for both components — the rise to the
radial-bias peak and the truncation turnover are both reproduced; the
outermost bin shows the honest ratio-noise error bar. (b) and
separately, sampled vs DF: in the bias
region — the kinematic content of . (c) Global vs for
anisotropic models: 0.5 with no rescale. (d) Analytic
for : the anisotropy is a controlled
knob (smaller stronger radial bias).
DF tables — the accelerated path is budget-asserted against its oracle¶
Phase 1.5 replaced pointwise quadrature in the coupled-Poisson RHS and both
sampler branches with three differentiable table primitives
(profiles/limepy_tables.py): AnisoDensityTable (cubic-Lagrange on a
grid), SpeedCDFTable (
isotropic inverse speed CDF, gated to ), and
AnisoSpeedCDFTable ( speed marginal — the angular
conditional stays exact). The exact quadrature
survives as a selectable oracle (aniso_method="quadrature"), so every budget
below is a one-line regression test:
Quantity | Measured | Budget |
|---|---|---|
Density table vs quadrature oracle ( grid) | 10-5 | |
Coupled solve , 3 configs () | each | |
Mass CDF | ||
Iso / aniso speed moments vs DF quadrature | / | statistical gates |
vs the DF’s own quadrature | (unchanged by the tables) | |
of a table-built model vs the quadrature oracle | 0.5 | |
AD vs central-FD gradient through the table solve (, ) | rtol 10-3 | |
Table-AD vs quadrature-AD (FD-free cross-check) | report |
Oracle independence: component_virial_ratios is deliberately
quadrature-only — the equilibrium oracle must not share the approximation
it checks. The table-built model proving
against that independent oracle is the end-to-end closure.
Performance (warm, measured at close-out): anisotropic construction ms (5.6×); sampling at : isotropic 67× (s/star), anisotropic 21.7× (s/star). The anisotropic table build dominates at small — break-even k stars (documented; below that, use the quadrature path).
Memory is bounded alongside speed: the virial kernels run in
fixed-size blocks with rematerialization (forward and gradient
), and the standalone DFs route through the
same tables. Measured peak RSS (scripts/profile_cluster_memory.py,
all stages PASS):
Stage | Peak RSS | |
|---|---|---|
| — | 0.19 GB |
Engine A build + sample, isotropic | 105 | 1.4 GB |
Engine A build + sample, anisotropic (OM) | 105 | 2.2 GB |
Engine B halo+core build + sample | 105 | 2.5 GB |
Virial / potential-energy kernel | 0.41 GB | |
Per-group virial oracle | 0.60 GB | |
Standalone anisotropic LIMEPY DF sampling | 2.3 GB |

Figure 3:DF-table validation (scripts/validate_df_tables.py, ALL PASS).
(a) Pointwise density error vs the quadrature oracle: the grid
sits under the 10-5 budget (max ); the coarser
in-solve grid is reported honestly (it is sized to the solve
budget, not the pointwise one). (b)
for the three solve configs, each under its budget (dashed).
(c) Warm wall times: solve and construction speedups with the shared table.
(d) AD vs central-FD gradient bars in ,
max rel diff .
Differentiability¶
The whole Engine A pipeline — coupled Poisson solve (table RHS included),
mass CDF, and jax.lax.scan sampling — is differentiable in the structural
parameters : AD matches central FD through
the table-backed solve to , and the table-path AD
matches the quadrature-path AD (an FD-free check) to . A
review-caught NaN-gradient bug at the truncation boundary (the
cotangent under jax.grad, masked only in the primal by where)
was fixed pre-merge and is regression-tested.
How to run¶
# physics tests (6 validation tests; marked slow — each samples >= 8000 stars)
pytest tests/validation/test_multimass_equilibrium_physics.py -q
# DF-table unit suites (budgets + threading)
pytest tests/unit/profiles/test_limepy_tables.py tests/unit/profiles/test_limepy_multimass.py -q
# regenerate the figures with printed PASS/FAIL tables
env -u VIRTUAL_ENV uv run --no-sync python scripts/validate_multimass_equilibrium.py
env -u VIRTUAL_ENV uv run --no-sync python scripts/validate_multimass_anisotropy.py
env -u VIRTUAL_ENV uv run --no-sync python scripts/validate_df_tables.pyWhat this suite does not test¶
No N-body integration of the ICs — equilibrium is established by the exact-quadrature virial oracle, DF-moment matches, and the sampled estimators on the initial conditions, not by evolving them.
— the model family approaches the Spitzer-unstable limit; the sampled heavy-group gate covers and the theory oracle only.
The outermost anisotropy bin () is plotted with its error bar but not gated — it is finite- ratio noise, shown converging to the DF under seed-averaging, not asserted per-seed.
Small- anisotropic table efficiency — below the k-star break-even the table build dominates; correctness is unaffected (budgets hold at all ), only speed.
References¶
Gieles & Zocchi (2015) (the multimass lowered-isothermal family; per-paper note in the bibliography, including the App. B density index and the 2018 erratum). The density-defined companion engine is validated at Multi-component Eddington equilibria (Engine B); the primordial (non-equilibrium) segregation generator at Mass segregation validation. This validation backs the multimass LIMEPY-equilibrium and DF-tables close-out.
- Gieles, M., & Zocchi, A. (2015). A family of lowered isothermal models. Monthly Notices of the Royal Astronomical Society, 454, 576–592. 10.1093/mnras/stv1848