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.

The Eddington engine (Engine B)

San Diego State University

Engine B is the density-defined route into MultiComponentCluster: you prescribe each component’s density shape (Plummer, EFF, or King density) and its mass-fraction amplitude, and the engine constructs the one shared self-consistent potential they jointly generate, recovers each component’s distribution function by Eddington inversion in that shared potential, and samples a true joint equilibrium — or refuses, with the physics named, when the decomposition does not exist as one.

from progenax import MultiComponentCluster, PlummerProfile, EFFProfile

model = MultiComponentCluster.from_density_profiles(
    profiles=[PlummerProfile(r_h=2.0), EFFProfile(a=0.8, gamma=5.0, r_t=9.0)],
    mass_fractions=jnp.array([0.6, 0.4]),   # M_j / M_total, must sum to 1
    m_j=jnp.array([0.5, 1.0]),              # stellar-mass labels (N_frac_j ∝ f_j/m_j)
    r_a_j=None,                              # optional per-component OM radii
)

One quadrature pass, no ODE

The structural contrast with Engine A is this: Engine A’s density is a functional of the potential (the DF defines ρ(ψ)\rho(\psi)), so finding the model requires solving a coupled Poisson ODE. Engine B’s total density is prescribed,

ρtot(r)  =  jMjMtotρ^j(r)(each ρ^j normalized to unit truncated mass),\rho_{\rm tot}(r) \;=\; \sum_j \frac{M_j}{M_{\rm tot}}\, \hat\rho_j(r) \quad \text{(each } \hat\rho_j \text{ normalized to unit truncated mass)},

so the shared potential follows from one cumulative-trapezoid pass — no ODE, no iteration:

M(<r)=4π ⁣0rρtots2ds,Φ(r)=G[M(<r)r+4π ⁣rrtρtotsds],M(<r) = 4\pi\!\int_0^r \rho_{\rm tot}\, s^2\, \mathrm{d}s, \qquad \Phi(r) = -G\left[\frac{M(<r)}{r} + 4\pi\!\int_r^{r_t} \rho_{\rm tot}\, s\, \mathrm{d}s\right],

↗ model card

with the relative potential Ψ=Φ(rt)Φ\Psi = \Phi(r_t) - \Phi (so Ψ(rt)=0\Psi(r_t) = 0, increasing inward) and dΨ/dr=GM(<r)/r2\mathrm{d}\Psi/\mathrm{d}r = -GM(<r)/r^2 analytic from the enclosed mass. The implementation (profiles/density_poisson.py) works in dimensionless units (G=1G = 1, total truncated mass 1) on a fixed nr=6000n_r = 6000 grid — sqrt-stretched toward the core (r=rfloor+(rtrfloor)u2r = r_{\rm floor} + (r_t - r_{\rm floor})\,u^2, audit S2), with non-uniform trapezoid weights — and stores per-component enclosed-mass CDFs Mj(<r)M_j(<r) for the position sampler.

Per-component Eddington inversion in the shared Ψ

Each component’s ergodic DF is the standard Eddington inversion — but performed in the shared relative potential, not the component’s own isolated one:

fj(E)  =  18π2[0Ed2ρjdΨ2dΨEΨ  +  1EdρjdΨΨ=0].f_j(E) \;=\; \frac{1}{\sqrt{8}\,\pi^2}\left[ \int_0^{E} \frac{\mathrm{d}^2\rho_j}{\mathrm{d}\Psi^2}\, \frac{\mathrm{d}\Psi}{\sqrt{E-\Psi}} \;+\; \frac{1}{\sqrt{E}}\left.\frac{\mathrm{d}\rho_j}{\mathrm{d}\Psi}\right|_{\Psi=0} \right].

Two numerical points are load-bearing:

The inverter (eddington_invert, in kinematics/eddington.py) was extracted bit-identically from the validated EFF Eddington table, so the existing EFF physics suite pins its behavior. Its strongest truth test bypasses all of progenax’s own potential numerics: fed the analytic Plummer (ρ,Ψ)(\rho, \Psi) pair it reproduces the closed-form f(E)E7/2f(E) \propto E^{7/2} law to 1.06×1041.06\times 10^{-4}with the untruncated zero point; the truncated case is covered by an exact closed form including the boundary term, matched to 8.86×1068.86\times 10^{-6}.

Osipkov–Merritt anisotropy

Per-component radial anisotropy uses the augmented-density device of Merritt (1985): replacing ρj\rho_j by

ρQ,j(r)  =  (1+r2ra,j2)ρj(r)\rho_{Q,j}(r) \;=\; \left(1 + \frac{r^2}{r_{a,j}^2}\right)\rho_j(r)

↗ model card

in (3) yields fj(Q)f_j(Q) with Q=EJ2/2ra,j2Q = E - J^2/2r_{a,j}^2, i.e. the Osipkov–Merritt anisotropy profile βj(r)=r2/(r2+ra,j2)\beta_j(r) = r^2/(r^2 + r_{a,j}^2). Each component carries its own ra,jr_{a,j} (inf = isotropic). The sampled anisotropy realizes the target profile: max βsampledr2/(r2+ra2)=0.028|\beta_{\rm sampled} - r^2/(r^2+r_a^2)| = 0.028 (4 seeds × 20k stars, gate 0.05).

The density-defined route, measured (seed 32, 3\times10^5 stars): a
Plummer halo (60%) + EFF \gamma{=}5 core (40%) in ONE shared potential.
Left: sampled per-component densities riding the prescribed curves through
the crossover. Right: the same well, two different dispersion profiles —
the halo component is HOTTER at small r (its wide-orbit stars pass through
the core), a pure shared-\Psi Eddington effect; the theory oracle reads
Q_1 = Q_2 = 0.500. Regenerate: python -m laboratory.icviz --only engine-b-mix.

Figure 1:The density-defined route, measured (seed 32, 3×1053\times10^5 stars): a Plummer halo (60%) + EFF γ=5\gamma{=}5 core (40%) in ONE shared potential. Left: sampled per-component densities riding the prescribed curves through the crossover. Right: the same well, two different dispersion profiles — the halo component is HOTTER at small rr (its wide-orbit stars pass through the core), a pure shared-Ψ\Psi Eddington effect; the theory oracle reads Q1=Q2=0.500Q_1 = Q_2 = 0.500. Regenerate: python -m laboratory.icviz --only engine-b-mix.

Derived domains (the model decides, never the code)

A component’s radial extent is part of the prescribed model, so the cluster truncation radius rtr_t is derived, with the rule that set it stored as provenance:

  1. Component extents: Plummer is infinite; EFF and King end at their own rtr_t.

  2. Cluster edge = max finite extent. If any component is finite, rtr_t is the largest finite extent (provenance names the winning profile and component index).

  3. All-infinite mixes (e.g. pure Plummer): rtr_t is the radius enclosing fenc=0.995f_{\rm enc} = 0.995 (configurable) of the summed analytic mass, found by a fixed 80-step bisection.

  4. Explicit r_t= override winsexcept that an override which would re-truncate a King component below its natural edge raises a ValueError: a King model’s lowered-Maxwellian edge is prescribed physics, never silently cut.

Per-component truncated-mass fractions Mj(<rt)/Mj()M_j(<r_t)/M_j(\infty) are stored as diagnostics, so a “too small rtr_t” cannot hide.

The realizability gate: fj0f_j \ge 0 or refuse

Eddington inversion is the unique candidate ergodic (or OM) DF for a density in a potential. If that candidate is negative anywhere, the prescribed component does not exist as an equilibrium in this shared potential — typically a too-shallow component in a concentrated companion’s potential. Engine B treats this as physics:

This is not a corner case. The originally drafted halo+core example (aEFF=0.4a_{\rm EFF} = 0.4 inside a Plummer halo) is genuinely unrealizable (fmin=0.20f_{\min} = -0.20, resolution-independent), with the gate flip measured between a=0.65a = 0.65 and 0.68 — see the worked example for the full story.

What the DF can and cannot represent

The Eddington pair (ρj,fj)(\rho_j, f_j) represents ρQ,j(Ψ)ρQ,j(0)\rho_{Q,j}(\Psi) - \rho_{Q,j}(0): any constant edge offset is invisible to an ergodic DF. A hard-truncated prescribed density has ρj(rt)>0\rho_j(r_t) > 0, and no f(E)f(E) can carry that offset; with OM anisotropy the unrepresentable offset is amplified by the augmentation factor 1+rt2/ra21 + r_t^2/r_a^2. The consequence is a small, predictable deficit between the DF-reconstructed density ρDF,j=4πw2fjdw/(1+r2/ra2)\rho_{{\rm DF},j} = 4\pi\int w^2 f_j\,\mathrm{d}w / (1+r^2/r_a^2) and the prescribed ρj\rho_j near the edge. Interior fidelity is gated: maxρDF/ρpresc1=1.06×103\max|\rho_{\rm DF}/\rho_{\rm presc} - 1| = 1.06\times 10^{-3} (OM build) and 2.4×1042.4\times 10^{-4} (isotropic) inside the component half-mass radius, against a 5×1035\times 10^{-3} gate.

Hybrid sampling and the predict-the-offset QjQ_j

sample_cluster for an Engine B model is a hybrid: positions come from the prescribed ρj\rho_j (per-component inverse-CDF on Mj(<r)M_j(<r)), speeds from the component’s fjf_j table at the star’s Ψ(ri)\Psi(r_i), directions from the OM stretched split at the star’s component ra,jr_{a,j}, and the velocity scale from the actual sampled mass imi\sum_i m_i (never an independent input total — energies must use the realized cluster mass). There is no external virial rescale.

Because of the edge-offset physics above, a hard-truncated component’s sampled QjQ_j plateaus slightly below 0.5 — and Engine B predicts the plateau with an exact-quadrature hybrid expectation (prescribed-ρ weights × DF speed moments × prescribed-total Clausius field): predicted Qhalo=0.4953Q_{\rm halo} = 0.4953, sampled 0.4947±0.00140.4947 \pm 0.0014 over 18 seeds — verified truncation-edge physics, gated against the prediction. The pure-DF theory oracle (component_virial_ratios, which weights by ρDF\rho_{\rm DF} — the density the DF actually represents) reads Qj=0.5Q_j = 0.5 to a few ×104\times 10^{-4}, as the steady-state identity demands. Do not rescale Engine B output to “fix” the plateau.

A numerics lesson: never differentiate interpolated data

The King-density branch initially computed dW/dr\mathrm{d}W/\mathrm{d}r by jnp.gradient of the interpolated King ψ\psi grid. Piecewise linear interpolation makes that derivative a staircase, and the Eddington d2ρ/dΨ2\mathrm{d}^2\rho/\mathrm{d}\Psi^2 kernel plus the Abel 1/EΨ1/\sqrt{E-\Psi} weight focus the staircase ringing exactly into f(EΨ0)f(E \to \Psi_0) — a single King component (whose true ergodic DF is strictly positive) read fmin=0.679f_{\min} = -0.679, squarely inside the realizability gate’s field of view. The fix integrates King’s own Poisson identity,

dψdξ  =  9ρ^0ξ20ξρ^(ψ(s))s2ds,\frac{\mathrm{d}\psi}{\mathrm{d}\xi} \;=\; -\frac{9}{\hat\rho_0}\,\xi^{-2}\int_0^{\xi} \hat\rho(\psi(s))\, s^2\, \mathrm{d}s,

↗ model card

by cumulative trapezoid of the closed-form density — after which fmin=+5.1×107f_{\min} = +5.1\times 10^{-7}. The general rule is worth the emphasis: in any Abel-type inversion, differentiate closed forms or exact identities, never interpolated data.

Check yourself

1. Why is the halo hotter in the core?

In Figure 1 both components share one Ψ(r)\Psi(r), yet σr,halo(0)>σr,core(0)\sigma_{r,\rm halo}(0) > \sigma_{r,\rm core}(0). Explain via the Eddington construction: a shallower ρj(Ψ)\rho_j(\Psi) requires a hotter fj(E)f_j(E) to be self-consistent in the same well — the halo’s stars seen at small rr are passing through on wide, energetic orbits.

2. Make the gate refuse

Rebuild the mix with the EFF core shrunk to a=0.4a = 0.4. The Eddington candidate DF for the halo goes genuinely negative (fmin0.20f_{\min} \approx -0.20, resolution-independent) and construction raises with the physics named — see Two-component populations — worked examples for why, and where the realizable boundary sits (a0.65a \approx 0.650.68).

Validation summary

All anchors from scripts/validate_multicomponent_eddington.py (11/11 PASS); the full evidence page is at Multi-component Eddington equilibria (Engine B).

Engine B measured anchors (2026-06-10 close-out).

Check

Measured

Gate

King A-vs-B radial KS distance (two independent engines, N=2×104N=2\times 10^4)

2×1042\times 10^{-4}

<0.02< 0.02

King A-vs-B max σB/σA1|\sigma_B/\sigma_A - 1| (interior bins)

3×1043\times 10^{-4}

<0.02< 0.02

Plummer f(E)E7/2f(E) \propto E^{7/2} (untruncated zero point)

1.06×1041.06\times 10^{-4}

<103< 10^{-3}

Plummer f(E)f(E) vs exact truncated closed form

8.86×1068.86\times 10^{-6}

<104< 10^{-4}

Halo+core theory QjQ_j (DF-weighted oracle)

[0.50038, 0.50012][0.50038,\ 0.50012]

0.5±3×1030.5 \pm 3\times 10^{-3}

Halo+core sampled global QQ (N=3×104N=3\times 10^4, unscaled)

0.4976

0.5±0.020.5 \pm 0.02

Hard-truncated halo QjQ_j vs hybrid prediction

sampled 0.4947±0.00140.4947 \pm 0.0014 vs predicted 0.4953

Δ<0.012|\Delta| < 0.012

OM β(r)\beta(r) realization (max deviation)

0.028

<0.05< 0.05

DF-density fidelity, interior (OM / isotropic)

1.06×1031.06\times 10^{-3} / 2.4×1042.4\times 10^{-4}

<5×103< 5\times 10^{-3}

AD-vs-FD gradients (rhr_h, mass-fraction tt, rar_{a})

5.6×1095.6\times 10^{-9} / 7.8×1077.8\times 10^{-7} / 2.0×1082.0\times 10^{-8}

<103< 10^{-3}

The King anchor deserves emphasis: a single King component is the only configuration both engines describe identically (Engine A at g=1g=1; Engine B from the King density), through entirely disjoint numerics (coupled ODE + lowered-DF sampling vs. quadrature potential + Eddington inversion). Their agreement at the 10-4 level is the cross-engine trust statement for the whole MultiComponentCluster design.

Differentiability

The build is differentiable in the profile parameters (e.g. halo rhr_h), the mass fractions (via a reparametrized scalar), and the ra,jr_{a,j} — AD matches finite differences to 7.8×107\le 7.8\times 10^{-7} (table above). Two deliberate exceptions: the domain choice (derive_r_t) concretizes — picking which component’s extent wins is a construction-time decision, not differentiated through — and the King component’s internal subgraph (its own ODE solution) is constant with respect to the differentiated parameters.

Implementation, validation & references

References
  1. Merritt, D. (1985). Spherical stellar systems with spheroidal velocity distributions. The Astronomical Journal, 90, 1027–1037. 10.1086/113810
  2. Plummer, H. C. (1911). On the problem of distribution in globular star clusters. Monthly Notices of the Royal Astronomical Society, 71, 460–470. 10.1093/mnras/71.5.460
  3. Elson, R. A. W., Fall, S. M., & Freeman, K. C. (1987). The structure of young star clusters in the Large Magellanic Cloud. The Astrophysical Journal, 323, 54–78. 10.1086/165807
  4. King, I. R. (1966). The structure of star clusters. III. Some simple dynamical models. The Astronomical Journal, 71, 64–75. 10.1086/109857
  5. 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