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.

`progenax.builders`

San Diego State University

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) -> None

Target 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) -> None

Target 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) -> None

Target 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) -> None

Result 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

ParameterDescription
positionsParticle positions (N, 3) [length units]
velocitiesParticle velocities (N, 3) [velocity units]
massesParticle masses (N,) [M_sun]
stellar_radiiStellar radii (N,) [R_sun]
idsParticle 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

ParameterDescription
massesParticle 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

ParameterDescription
positionsParticle positions (N, 3)
velocitiesParticle velocities (N, 3)
massesParticle 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

ParameterDescription
positionsParticle positions (N, 3)
velocitiesParticle velocities (N, 3)
massesParticle masses (N,)
Q_targetTarget virial ratio (0.5 for equilibrium)
GGravitational constant
softeningSoftening 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.ICResult

Build initial conditions from spatial profile and velocity DF.

Args

ParameterDescription
profileSpatial density profile (must implement SpatialProfile protocol)
massesParticle masses (N,) [M_sun]
velocity_dfVelocity distribution function (must implement VelocityDF protocol)
keyJAX random key
GGravitational constant (REQUIRED - no default)
QVirial ratio target Q = T/|V| (0.5 for equilibrium, None to disable)
softeningSoftening 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_offsetOffset 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

ParameterDescription
primary_imfprimary-star IMF with sample(key, n) -> m1 [Msun].
companion_modela 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).
targetpopulation-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).
keyJAX random key.
unitsUnitSystem (carries G + the time scale for the day->time-unit conversion).
Qsystem-level virial ratio target (0.5 = equilibrium; None to disable).
softeningvirial-scaling softening for the COM cluster (default 0 = exact; NOT stored).
compactTrue (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