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.

Two-component populations — worked examples

San Diego State University

The simplest non-trivial multi-component IC has two populations in one shared potential. This page builds one through each engine of MultiComponentCluster: a cold/hot pair where the DFs define the model (Engine A), and a halo+core pair where prescribed densities define it (Engine B). Both are true joint equilibria — no external virial rescale anywhere — and both come with the measured numbers that prove it.

Engine A: a cold population inside a hot one

Engine A components are lowered-isothermal DFs (theory); the one free per-component scale is the velocity-scale ratio wj=sj/sw_j = s_j/s. A colder component (wj<1w_j < 1) sees the shared potential at the deeper rescaled depth Wj=ψ/wj2W_j = \psi/w_j^2 and concentrates — concentration here is an equilibrium outcome, not a sampling choice:

import jax
import jax.numpy as jnp
from jaxstro.units import STELLAR
from progenax import MultiComponentCluster

model = MultiComponentCluster.from_components(
    alpha_j=jnp.array([0.5, 0.5]),   # central density fractions
    w_j=jnp.array([0.7, 1.0]),       # cold (0.7) + hot (1.0)
    m_j=jnp.array([1.0, 1.0]),       # equal stellar masses: pure w_j physics
    W0=7.0, g=1.0, r_c=1.0,          # King-like (g = 1) truncation
)
ic = model.sample_cluster(jax.random.PRNGKey(0), n_stars=20_000, G=STELLAR.G)
# ic.component_id: 0 = cold, 1 = hot

Measured (from scripts/validate_cluster_ic.py, with rc=1r_c = 1 pc): the cold (w=0.7w = 0.7) population’s median radius is 1.50 pc, inside the hot population’s 8.36 pc — spatial segregation from velocity scale alone, with equal stellar masses. The exact-quadrature equilibrium oracle reads Qj=[0.5,0.5]Q_j = [0.5,\, 0.5] for both components.

For mass-driven versions of the same physics, use from_mass_segregation (wj=μjδw_j = \mu_j^{-\delta}) or from_imf; for a GC 1G/2G setup, give the concentrated 2G the smaller wjw_j.

Engine B: a Plummer halo plus an EFF core

Engine B starts from prescribed densities (theory): the headline decomposition is an extended Plummer halo carrying 60% of the mass plus a compact truncated EFF core (Elson et al. (1987)) carrying 40%:

import jax
import jax.numpy as jnp
from jaxstro.units import STELLAR
from progenax import MultiComponentCluster, PlummerProfile, EFFProfile

model = MultiComponentCluster.from_density_profiles(
    profiles=[PlummerProfile(r_h=2.0),                  # halo
              EFFProfile(a=0.8, gamma=5.0, r_t=9.0)],   # core
    mass_fractions=jnp.array([0.6, 0.4]),               # M_j / M_total
    m_j=jnp.array([0.5, 1.0]),                          # stellar-mass labels
)
ic = model.sample_cluster(jax.random.PRNGKey(0), n_stars=30_000, G=STELLAR.G)

The shared potential is one direct quadrature pass over the summed prescribed density; each component’s DF is its Eddington inversion in that shared potential. Measured at close-out: theoretical Qj=[0.50038,0.50012]Q_j = [0.50038,\, 0.50012] (gate 0.5±3×1030.5 \pm 3\times 10^{-3}), sampled global Q=0.4976Q = 0.4976 at N=30,000N = 30{,}000, unscaled.

Honest physics 1: not every decomposition exists

Eddington inversion is a two-way street: a prescribed density in a given potential corresponds to a unique candidate f(E)f(E), and if that ff is negative anywhere, the component cannot exist as an equilibrium in that potential — full stop. The originally drafted version of this very example used a shallower EFF core, aEFF=0.4a_{\rm EFF} = 0.4, and it is genuinely unrealizable: its Eddington DF has minf/maxf=0.20\min f / \max|f| = -0.20, resolution-independent (verified against the closed-form two-Plummer oracle, since γ=5\gamma = 5 EFF is Plummer). A close-out sweep located the realizability boundary between a=0.65a = 0.65 (refused, 6.4×103-6.4\times 10^{-3}) and a=0.68a = 0.68 (realizable, +1.9×102+1.9\times 10^{-2}); the headline uses a=0.8a = 0.8 (fmin,j=+1.6×102f_{\min,j} = +1.6\times 10^{-2} halo, +1.2×104+1.2\times 10^{-4} core).

progenax treats this as physics, not failure: the constructor raises a ValueError naming the component and the remedy (steepen it, raise its mass fraction, or raise its ra,jr_{a,j}), and always stores the f_min_j diagnostic. See the realizability gate for the mechanism.

Honest physics 2: the truncation-edge QjQ_j plateau

The hard-truncated halo’s sampled per-component virial ratio plateaus slightly below 0.5 — and this is verified physics, not a bias to be rescaled away. A sharply truncated prescribed density has ρ(rt)>0\rho(r_t) > 0, a constant edge offset that no ergodic f(E)f(E) can carry (the Eddington pair represents ρ(Ψ)ρ(0)\rho(\Psi) - \rho(0)). Engine B samples a hybrid — positions from the prescribed ρj\rho_j, speeds from fjf_j — so the exact-quadrature hybrid expectation predicts the offset: predicted Qhalo=0.4953Q_{\rm halo} = 0.4953, sampled 0.4947±0.00140.4947 \pm 0.0014 (18 seeds × 16k stars, a 0.4σ0.4\sigma agreement). The validation gate is against the prediction, never a tuned offset — and emphatically not a rescale to 0.5, which would destroy the core’s equilibrium to cosmetically fix the halo’s edge.

Choosing between the engines for two-component work

You have…

Use

Because

A dynamical model in mind (relaxed, tidally truncated populations; mass segregation; fit g,W0,wj,δg, W_0, w_j, \delta to data)

Engine A (from_components et al.)

The lowered-isothermal family is the model; equilibrium and equipartition are built in.

Observed/prescribed density shapes (surface-brightness decomposition, halo+core, literature profiles)

Engine B (from_density_profiles)

Densities go in verbatim; Eddington tells you whether the decomposition is dynamically realizable.

The two engines agree where they overlap: a single King component built both ways matches to a radial KS distance of 2×1042\times 10^{-4} and σB/σA13×104|\sigma_B/\sigma_A - 1| \le 3\times 10^{-4} (the A-vs-B trust anchor; see The Eddington engine (Engine B)).

Domain of validity

  1. True joint equilibrium, by construction. Unlike the retired layered superposition (each component sampled from its own isolated-cluster DF), both engines solve/integrate one shared potential and prove Qj=0.5Q_j = 0.5 per component with no rescale.

  2. Truncation edges are approximate (Engine B): hard-truncated prescribed profiles are only approximately stationary at the edge; the deviation is predicted and gated, not hidden (see above).

  3. Deliberate non-equilibrium ICs (sub-/super-virial, mismatched DFs) remain a separate, explicit workflow — see Velocity distribution functions and Virial Q convention (Q = T/|V|).

Implementation, validation & references

References
  1. 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
  2. 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
  3. Merritt, D. (1985). Spherical stellar systems with spheroidal velocity distributions. The Astronomical Journal, 90, 1027–1037. 10.1086/113810
  4. Küpper, A. H. W., Maschberger, T., Kroupa, P., & Baumgardt, H. (2011). Mass segregation and fractal substructure in young massive clusters. Monthly Notices of the Royal Astronomical Society, 417, 2300–2317. 10.1111/j.1365-2966.2011.19412.x