progenax.builders¶
Auto-generated by scripts/build_api_reference.py from progenax source. Re-run after public-API changes; the script is idempotent.
Module path: progenax/builders/
Public symbols: 11
Contents¶
builders.Systems¶
class
Systems(n: int) -> NoneTarget a fixed number of stellar systems (singles + binaries).
Companions are not counted toward the count — the observational convention of
Rosen, Confidently Wrong (N = observed systems; primaries from the IMF,
companions attached on top, so total stars = n + n_binary). The only
fixed-shape => differentiable target (supports the masked compact=False path).
Source: src/progenax/builders.py#L42
builders.Stars¶
class
Stars(n: int) -> NoneTarget a fixed number of resolved stars (primaries + real secondaries).
Companions count toward the total — the dynamical-IC convention of McLuster
(Kuepper+2011 draws N stars then forms N*b/2 binaries). Draw whole systems
in draw order until the resolved star count first reaches n (overshoot <= 1 star
— a binary is never split, so the result is n or n+1 stars). The
data-dependent system count makes this eager only (compact=True).
Source: src/progenax/builders.py#L54
builders.TotalMass¶
class
TotalMass(m: float) -> NoneTarget a fixed total stellar mass Σ(m1+m2) [M_sun] (companions counted).
Whole-system, McLuster-style mass filling: draw until the cumulative system
mass first reaches m (overshoot ≤ one system). Eager only (compact=True).
Source: src/progenax/builders.py#L67
builders.ICResult¶
class
ICResult(positions: Float[Array, 'N 3'], velocities: Float[Array, 'N 3'], masses: Float[Array, 'N'], stellar_radii: Float[Array, 'N'], ids: Optional[Float[Array, 'N']] = None, primordial_system_id: Optional[Int[Array, 'N']] = None, is_primordial_secondary: Optional[Bool[Array, 'N']] = None, component_id: Optional[Int[Array, 'N']] = None) -> NoneResult from initial conditions generation — pure physical state.
Immutable Equinox module (a JAX PyTree) containing all particle data. No dependency on gravax; can be converted to any state format and passed through jit/vmap/grad boundaries.
Softening is intentionally NOT stored here: it is a force-model / integration choice (selected on the integrator, e.g. ε=0 for collisional Hermite/IAS15), not a property of the initial conditions.
Attributes
| Parameter | Description |
|---|---|
positions | Particle positions (N, 3) [length units] |
velocities | Particle velocities (N, 3) [velocity units] |
masses | Particle masses (N,) [M_sun] |
stellar_radii | Stellar radii (N,) [R_sun] |
ids | Particle IDs (N,) or None |
primordial_system_id | (N,) int — which primordial system each particle belongs to (paired particles share an id); None for single-only ICs. PROVENANCE at t=0 only — goes stale under dynamical evolution (ionization / formation / exchange). Measure the current binary population with binaries.diagnostics.find_bound_pairs, not this. |
is_primordial_secondary | (N,) bool — True for the secondary of a primordial binary; None for single-only ICs. |
component_id | (N,) int — which population component each particle was drawn from (multi-component generators, e.g. MultiComponentCluster); None for single-population ICs. Like primordial_system_id, this is PROVENANCE at t=0 — a label of the generating component, not a dynamical invariant. |
Source: src/progenax/builders.py#L118
builders.compute_stellar_radii¶
function
compute_stellar_radii(masses: Float[Array, 'N']) -> Float[Array, 'N']Main-sequence stellar radii from mass, in SOLAR RADII.
Demircan & Kahraman (1991), Ap&SS 181, 313, Table II — the EMPIRICAL fits to MS binary data (log R = a + b log M, so R = 10^a M^b), NOT their ZAMS fit: R/Rsun = 1.06 (M/Msun)^0.945 for 0.08 <= M < 1.66 Msun (a=0.026, b=0.945) R/Rsun = 1.33 (M/Msun)^0.555 for M >= 1.66 Msun (a=0.124, b=0.555) The 1.66 Msun break is D&K91’s measured knee (1.66 +/- 0.08, their Section 4). (D&K91’s ZAMS fit is different: R ~ 0.89 M^0.89 / 1.01 M^0.57 — not used here.) Brown dwarfs (M < 0.08): R ~ 0.1 Rsun plateau (electron degeneracy), R = 0.1 (M/0.08)^0.08. The D&K branches meet at 1.66 Msun with their own ~3.5% fit discontinuity; the low-mass branch meets the BD plateau nearly continuously (1.06 * 0.08^0.945 = 0.0975).
Audit R6: the previous exponents were INVERTED vs MS homology (M^0.8 above 1 Msun instead of below), giving 10 Msun -> 6.3 Rsun (ZAMS ~4.8) and 0.2 Msun -> 0.40 Rsun (observed ~0.23), with a factor-2.4 jump at the hydrogen-burning limit.
Used for collision radii in downstream N-body; empirical MS radii, no evolution.
See :func:progenax.zams_radius for the Tout+1996 photometric ZAMS radii (this
function is the D&K91 collision radius — a different relation for a different use).
.. note::
TEMPORARY single-relation stand-in. This will be REPLACED by the
startrax package once it lands: first Tout et al. (1996) ZAMS radii
(metallicity-dependent rational-polynomial fits), then MIST and the
Hurley+2000 SSE tracks (mass- AND age-dependent radii with real stellar
evolution). The D&K91 fit here is a static empirical main-sequence
approximation chosen only to be cited and correct until then.
Args
| Parameter | Description |
|---|---|
masses | Particle masses (N,) [M☉] |
Returns: Radii in R☉
Source: src/progenax/builders.py#L160
builders.compute_kinetic_energy¶
function
compute_kinetic_energy(velocities: Float[Array, 'N 3'], masses: Float[Array, 'N']) -> Float[Array, '']Compute total kinetic energy: T = 0.5 * sum(m_i * v_i^2).
Source: src/progenax/dynamics/virial.py#L16
builders.compute_potential_energy¶
function
compute_potential_energy(positions: Float[Array, 'N 3'], masses: Float[Array, 'N'], G: float, softening: float = 0.0, block_size: int = 256) -> Float[Array, '']Total potential energy V = -G * sum_{i<j} m_i m_j / r_ij (Plummer-softened).
Blocked row-scan (lax.scan over row blocks of block_size stars vs ALL
columns): peak transient memory is O(block_size * N), not O(N^2), for the
forward AND backward pass — the dense kernel measured 32.8 GB at N = 2e4
(2026-06-10); blocked at the default 256 it is ~0.12 GB. The backward pass
stays O(block_size * N) via jax.checkpoint rematerialization: each
block’s forward is recomputed during the vjp instead of stored, so no
O(N^2) stacked residuals accumulate across scan iterations. Identical pair
set and per-pair arithmetic; only float64 summation ORDER changes across
blocks (re-association at the 1e-15 relative level). block_size is a
Python int and must be static under jax.jit.
Differentiable at softening=0: the i<j mask feeds excluded entries
(diagonal, lower triangle, padded rows) a safe value before sqrt so no
masked-out sqrt(0) cotangent can NaN-poison the gradient. This is the
single canonical energy implementation; progenax.builders re-exports it.
Source: src/progenax/dynamics/virial.py#L34
builders.to_com_frame¶
function
to_com_frame(positions: Float[Array, 'N 3'], velocities: Float[Array, 'N 3'], masses: Float[Array, 'N']) -> tuple[Float[Array, 'N 3'], Float[Array, 'N 3']]Transform to center-of-mass frame.
Args
| Parameter | Description |
|---|---|
positions | Particle positions (N, 3) |
velocities | Particle velocities (N, 3) |
masses | Particle masses (N,) |
Returns: (positions_com, velocities_com): Transformed coordinates
Source: src/progenax/builders.py#L220
builders.virial_scale¶
function
virial_scale(positions: Float[Array, 'N 3'], velocities: Float[Array, 'N 3'], masses: Float[Array, 'N'], Q_target: float, G: float, softening: float = 0.0) -> Float[Array, 'N 3']Scale velocities to achieve target virial ratio Q = T/|V|.
Physical interpretation: - Q = 0.5: Virial equilibrium (2T + V = 0) - Q < 0.5: Sub-virial (cold), system will collapse - Q > 0.5: Super-virial (hot), system will expand/unbind
Args
| Parameter | Description |
|---|---|
positions | Particle positions (N, 3) |
velocities | Particle velocities (N, 3) |
masses | Particle masses (N,) |
Q_target | Target virial ratio (0.5 for equilibrium) |
G | Gravitational constant |
softening | Softening length (default: 0) |
Returns: Scaled velocities Cold input (T=0) raises for concrete inputs / yields NaN under tracing — see the delegate’s docstring.
References. Goodwin & Whitworth (2004) A&A 413, 929 - Sub-virial clusters Baumgardt & Kroupa (2007) MNRAS 380, 1589 - Cluster dissolution
Source: src/progenax/builders.py#L245
builders.build_spatial_ic¶
function
∇ gradient-verified — 2 audit cases
build_spatial_ic(profile: progenax.protocols.SpatialProfile, masses: Float[Array, 'N'], velocity_df: progenax.protocols.VelocityDF, key: PRNGKeyArray, G: float, Q: Optional[float] = 0.5, softening: float = 0.0, id_offset: int = 0) -> progenax.builders.ICResultBuild initial conditions from spatial profile and velocity DF.
Args
| Parameter | Description |
|---|---|
profile | Spatial density profile (must implement SpatialProfile protocol) |
masses | Particle masses (N,) [M_sun] |
velocity_df | Velocity distribution function (must implement VelocityDF protocol) |
key | JAX random key |
G | Gravitational constant (REQUIRED - no default) |
Q | Virial ratio target Q = T/|V| (0.5 for equilibrium, None to disable) |
softening | Softening length used ONLY to virial-scale the IC under the same force law it will be integrated with (default: 0.0 = exact Newtonian, matching the analytic equilibrium DFs and collisional integration). Pass ε>0 for a collisionless science case so the IC is virialized consistently. This is a force-model knob (a future shared ForceModel will supply it); it is not stored on the returned ICResult. |
id_offset | Offset for particle IDs (default: 0) |
Returns: ICResult (pure physical state — no softening field)
Source: src/progenax/builders.py#L286
builders.build_binary_cluster¶
function
📇 model card · ∇ gradient-verified — 1 audit case
build_binary_cluster(profile: progenax.protocols.SpatialProfile, velocity_df: progenax.protocols.VelocityDF, primary_imf, companion_model, target, key: PRNGKeyArray, *, units, Q: Optional[float] = 0.5, softening: float = 0.0, compact: bool = True)Assemble a star cluster with a primordial binary population (SoTA composition).
Composes five independent axes: profile x velocity_df (spatial), primary_imf
(the primary-star IMF — vary alpha freely), companion_model (the single owner
of the binary statistics: f_b -> is_binary AND q -> m2, P -> a, e, orientation), and
target (what the population size holds fixed).
Pipeline: draw target-many systems -> companion_model.sample -> system masses
m1 + m2 -> budget cut -> build_spatial_ic on the system masses (COMs virialized
treating binaries as point masses, eps=0 by default) -> resolve_binary_components
places each binary’s two components around its COM (COM preserved exactly).
Conventions. primary_imf is the IMF of primaries; companions are generated
conditionally (m2 = q*m1, with q | M1 from the companion model), so the all-stars
mass function is a derived consequence — not the input IMF (Rosen, Confidently
Wrong, S9.6; the conditional q | M1 parameterization follows Moe & Di Stefano 2017).
The COM virialization treats each binary as a single CoM particle and replaces it with
its two constituents only at the end (the McLuster convention, Kuepper+2011 SA8);
internal binary binding energy is a separate reservoir untouched by Q (measure it
with binaries.diagnostics).
Args
| Parameter | Description |
|---|---|
primary_imf | primary-star IMF with sample(key, n) -> m1 [Msun]. |
companion_model | a CompanionModel (e.g. IndependentCompanions, MoeCompanions) owning multiplicity + (q, P, e); sample(key, m1, *, G, day_in_time_units) -> (is_binary, CompanionElements). No separate binary_fraction arg — f_b lives in the model (Moe sets it from the masses). |
target | population-size budget — Systems(n) (count systems; companions not counted; the only differentiable / compact=False target), Stars(n) (count resolved stars, companions included), or TotalMass(M) [Msun]. Stars/TotalMass have data-dependent counts and are eager only (compact=True). |
key | JAX random key. |
units | UnitSystem (carries G + the time scale for the day->time-unit conversion). |
Q | system-level virial ratio target (0.5 = equilibrium; None to disable). |
softening | virial-scaling softening for the COM cluster (default 0 = exact; NOT stored). |
compact | True (default) -> eagerly compacted ICResult; False -> the masked fixed-shape ResolvedBinaries (jit/grad-safe; requires a Systems target). |
Returns: ICResult (compact=True) or ResolvedBinaries (compact=False).
Source: src/progenax/builders.py#L406