progenax.binaries¶
Auto-generated by scripts/build_api_reference.py from progenax source. Re-run after public-API changes; the script is idempotent.
Module path: progenax/binaries/
Public symbols: 30
Contents¶
binaries.KeplerElements¶
class
📇 model card · ∇ gradient-verified — 5 audit cases
KeplerElements(a: ArrayLike, e: ArrayLike = 0.0, i: ArrayLike = 0.0, Omega: ArrayLike = 0.0, omega: ArrayLike = 0.0, M0: ArrayLike = 0.0)Keplerian orbital elements as Equinox module.
Represents an orbit in the two-body problem using classical elements. All angles in radians, distances in current unit system.
Attributes
| Parameter | Description |
|---|---|
a | Semi-major axis [length units] |
e | Eccentricity (0 ≤ e < 1 for bound orbits) |
i | Inclination [rad] (0 to π) |
Omega | Longitude of ascending node [rad] (0 to 2π) |
omega | Argument of periapsis [rad] (0 to 2π) |
M0 | Mean anomaly at epoch [rad] (0 to 2π) |
References. Murray & Dermott (1999) “Solar System Dynamics” Eq 2.122 Binney & Tremaine (2008) “Galactic Dynamics” Ch 3
Examples. >>> # Circular orbit
elements = KeplerElements(a=1.0, e=0.0, i=0.0, Omega=0.0, omega=0.0, M0=0.0) state = elements.to_state(M_total=1.0, G=1.0)
Eccentric orbit¶
import jax.numpy as jnp elements = KeplerElements( ... a=1.0, e=0.5, i=jnp.pi/4, ... Omega=0.0, omega=0.0, M0=0.0 ... ) state = elements.to_state(M_total=1.0, G=1.0)
Source: src/progenax/binaries/kepler.py#L49
binaries.CartesianState¶
class
CartesianState(position: Float[Array, '3'], velocity: Float[Array, '3'])Cartesian phase-space state of a single body.
A JAX-pytree NamedTuple (transparent to jit/grad/vmap); also tuple-iterable,
so pos, vel = state works.
Attributes
| Parameter | Description |
|---|---|
position | (3,) Cartesian position [length units] |
velocity | (3,) Cartesian velocity [velocity units] |
Source: src/progenax/binaries/kepler.py#L18
binaries.BinaryState¶
class
BinaryState(r1: Float[Array, '3'], v1: Float[Array, '3'], r2: Float[Array, '3'], v2: Float[Array, '3'])Resolved barycentric phase-space state of a binary’s two components.
A JAX-pytree NamedTuple; tuple-iterable, so r1, v1, r2, v2 = state works.
Attributes: r1, v1: Position/velocity of the primary [length, velocity units] r2, v2: Position/velocity of the secondary [length, velocity units]
Source: src/progenax/binaries/kepler.py#L33
binaries.compute_period¶
function
compute_period(a: ArrayLike, M_total: ArrayLike, G: ArrayLike) -> Float[Array, '...']Compute orbital period from semi-major axis using Kepler’s 3rd law.
Args
| Parameter | Description |
|---|---|
a | Semi-major axis [length units] |
M_total | Total mass of binary system [M☉] |
G | Gravitational constant (REQUIRED, no default) |
Returns: period: Orbital period [time units] Formula: T = 2π√(a³/(GM))
Examples. >>> # Earth orbit: a=1 AU, M=1 M☉ → T≈1 year
G = 39.478 # AU³/Msun/yr² T = compute_period(a=1.0, M_total=1.0, G=G) print(f"Period: {T:.2f} years") Period: 1.00 years
Stellar cluster orbit: a=1 pc, M=1000 M☉¶
G = 0.00450 # pc³/Msun/Myr² T = compute_period(a=1.0, M_total=1000.0, G=G) print(f"Period: {T:.2f} Myr") Period: 0.94 Myr
References. Kepler’s 3rd Law: T² ∝ a³/M Murray & Dermott (1999) Eq 2.37
Source: src/progenax/binaries/kepler_period.py#L12
binaries.period_to_semimajor_axis¶
function
period_to_semimajor_axis(period: ArrayLike, M_total: ArrayLike, G: ArrayLike) -> Float[Array, '...']Compute semi-major axis from orbital period using Kepler’s 3rd law.
Args
| Parameter | Description |
|---|---|
period | Orbital period [time units] |
M_total | Total mass of binary system [M☉] |
G | Gravitational constant (REQUIRED, no default) |
Returns: a: Semi-major axis [length units] Formula: a = (GMT²/(4π²))^(1/3)
Examples. >>> # Binary with 10 day period, M_total=2 M☉
G = 39.478 # AU³/Msun/yr² P_yr = 10.0 / 365.25 # Convert days to years a = period_to_semimajor_axis(P_yr, M_total=2.0, G=G) print(f"Semi-major axis: {a:.3f} AU") Semi-major axis: 0.089 AU
Star cluster binary: 10 Myr period, M_total=2 M☉¶
G = 0.00450 # pc³/Msun/Myr² a = period_to_semimajor_axis(10.0, M_total=2.0, G=G) print(f"Semi-major axis: {a:.2f} pc") Semi-major axis: 4.64 pc
References. Kepler’s 3rd Law: a³ ∝ T²M Murray & Dermott (1999) Eq 2.37
Source: src/progenax/binaries/kepler_period.py#L61
binaries.BinaryOrbitalState¶
class
BinaryOrbitalState(m1: "Float[Array, '']", m2: "Float[Array, '']", elements: 'KeplerElements') -> NoneBinary orbital state for IC generation.
Combines component masses with Keplerian orbital elements. This is an IC-specific container - for general orbital mechanics, use KeplerElements directly.
Attributes
| Parameter | Description |
|---|---|
m1 | Primary mass [M_sun] |
m2 | Secondary mass [M_sun] |
elements | KeplerElements (a, e, i, Omega, omega, M0). The period/mean motion is derived on demand from (a, m1+m2, G) in to_state, not cached here. |
Example. >>> from jaxstro.units import PLANETARY
PLANETARY.G is per-year, so convert the period days -> years.¶
state = BinaryOrbitalState.from_log_period( ... m1=1.0, m2=0.5, logP_days=2.0, e=0.3, ... inc=0.1, Omega=0.0, omega=0.0, M_anom=0.0, ... G=PLANETARY.G, day_in_time_units=1.0 / 365.25 ... ) r1, v1, r2, v2 = state.to_resolved_positions(G=PLANETARY.G)
Source: src/progenax/binaries/orbital_state.py#L20
binaries.batch_elements_to_resolved¶
function
batch_elements_to_resolved(m1: "Float[Array, 'N']", m2: "Float[Array, 'N']", logP_days: "Float[Array, 'N']", e: "Float[Array, 'N']", inc: "Float[Array, 'N']", Omega: "Float[Array, 'N']", omega: "Float[Array, 'N']", M_anom: "Float[Array, 'N']", *, G: 'float', day_in_time_units: 'float' = 1.0) -> "Tuple[Float[Array, 'N 3'], Float[Array, 'N 3'], Float[Array, 'N 3'], Float[Array, 'N 3']]"Vectorized wrapper to get resolved (r1, v1, r2, v2) for N binaries.
Args
| Parameter | Description |
|---|---|
logP_days | log10(P / day) [N] |
e | Eccentricity [N] |
inc | Inclination [rad] [N] |
Omega | Longitude of ascending node [rad] [N] |
omega | Argument of periapsis [rad] [N] |
M_anom | Mean anomaly [rad] [N] |
G | Gravitational constant (REQUIRED, no default) |
day_in_time_units | Conversion factor days -> code time units |
Returns: r1, v1, r2, v2: Arrays of shape [N, 3]
Source: src/progenax/binaries/orbital_state.py#L195
binaries.LogUniformPeriod¶
class
📇 model card · ∇ gradient-verified — 1 audit case
LogUniformPeriod(log_P_min: 'float' = 0.0, log_P_max: 'float' = 8.0) -> NoneLog-uniform (Öpik) period distribution.
Öpik’s law: binary periods are uniformly distributed in log space. p(log P) = const => p(P) ∝ 1/P
Reference: Öpik (1924) Publications de l’Observatoire Astronomique de l’Université de Tartu
Parameters: log_P_min: Minimum log10(P/days) (default: 0.0 = 1 day) log_P_max: Maximum log10(P/days) (default: 8.0 = ~27,000 years)
Source: src/progenax/binaries/period.py#L23
binaries.LogNormalPeriod¶
class
📇 model card · ∇ gradient-verified — 1 audit case
LogNormalPeriod(mu_log_P: 'float' = 4.8, sigma_log_P: 'float' = 2.3) -> NoneLog-normal period distribution.
log10(P) ~ Normal(mu, sigma)
Reference: Duquennoy & Mayor (1991) A&A 248, 485 For solar-type stars: mu ≈ 4.8, sigma ≈ 2.3 (P in days)
Parameters: mu_log_P: Mean of log10(P/days) (default: 4.8) sigma_log_P: Std dev of log10(P/days) (default: 2.3)
Source: src/progenax/binaries/period.py#L65
binaries.SanaOBPeriod¶
class
📇 model card · ∇ gradient-verified — 1 audit case
SanaOBPeriod(log_P_min: 'float' = 0.15, log_P_max: 'float' = 3.5, power: 'float' = -0.55) -> NoneSana+2012 period distribution for O/B stars.
Power-law distribution in log-space: p(log P) ∝ (log P)^(-0.55)
for log P in [0.15, 3.5] (P in days).
This corresponds to shorter periods than solar-type binaries, consistent with observations of massive star binaries.
Reference: Sana et al. (2012) Science 337, 444 - O-star binary survey. The intrinsic period distribution (their Fig. 2, π = -0.55 ± 0.22) runs from P ~ 1.4 d (log P ~ 0.15) to ~9 yr (log P = 3.5).
Parameters: log_P_min: Minimum log10(P/days) (default: 0.15 = ~1.4 days; Sana 2012 Fig.2). Must be > 0: the model p(logP) ∝ (logP)^power is undefined for logP <= 0 (P <= 1 day), since that raises a non-positive base to a fractional power. log_P_max: Maximum log10(P/days) (default: 3.5 = ~3162 days ~ 9 yr) power: Power-law index (default: -0.55 from Sana+2012)
Source: src/progenax/binaries/period.py#L112
binaries.ThermalEccentricity¶
class
📇 model card · ∇ gradient-verified — 1 audit case
ThermalEccentricity(e_max: 'float' = 0.99) -> NoneThermal eccentricity distribution f(e) = 2e.
Arises from energy equipartition in dynamically relaxed systems. CDF: F(e) = e² => PPF: e = √u
Reference: Heggie (1975) MNRAS 173, 729 Jeans (1919) “Problems of Cosmogony and Stellar Dynamics”
Parameters: e_max: Maximum eccentricity (default: 0.99, avoids singularity)
Source: src/progenax/binaries/eccentricity.py#L24
binaries.UniformEccentricity¶
class
📇 model card · ∇ gradient-verified — 1 audit case
UniformEccentricity(e_min: 'float' = 0.0, e_max: 'float' = 0.9) -> NoneUniform eccentricity distribution.
Simple uniform distribution, useful for circular-dominated populations.
Parameters: e_min: Minimum eccentricity (default: 0.0) e_max: Maximum eccentricity (default: 0.9)
Source: src/progenax/binaries/eccentricity.py#L65
binaries.LogisticThermalEccentricity¶
class
∇ gradient-verified — 1 audit case
LogisticThermalEccentricity(P_circ: 'float' = 10.0, P_thermal: 'float' = 1000.0, e_max: 'float' = 0.99, transition_width: 'float' = 0.5) -> NoneSmooth circular->thermal eccentricity heuristic (period-conditional).
A differentiable heuristic that interpolates a thermal f(e)=2e distribution from near-circular at short P to fully thermal at long P, via a logistic blend in log10(P): e = blend(P) * e_max * sqrt(u), u ~ U(0,1).
This captures the QUALITATIVE three-period structure of solar-type eccentricities measured by Duquennoy & Mayor (1991) (§6.1/§7.2): P < ~10 d tidally circularized (e ~ 0; their circularization period P_circ ~ 11.6 d), P > ~1000 d approaching thermal f(e)=2e (Ambartsumian 1937), with a smooth transition between. The logistic midpoint defaults to the geometric mean of P_circ=10 d and P_thermal=1000 d (log10 P = 2.0).
It is NOT Moe & Di Stefano’s (2017) f(e) ∝ e^η(P, M1) law — for that, use
:class:MoeEccentricity. This class is a smooth, mass-independent surrogate
motivated by the tidal-circularization physics (Zahn 1977).
References. Duquennoy & Mayor (1991) A&A 248, 485 §6.1/§7.2 - three-period e model. Ambartsumian (1937); Heggie (1975) MNRAS 173, 729 - thermal f(e)=2e. Zahn (1977) A&A 57, 383 - tidal circularization.
Parameters: P_circ: Circularization period [days] (default: 10.0; DM91 ~11.6 d) P_thermal: Thermalization period [days] (default: 1000.0; DM91 wide onset) e_max: Maximum eccentricity (default: 0.99) transition_width: Width of transition region in log10(P) (default: 0.5)
Source: src/progenax/binaries/eccentricity.py#L98
binaries.MoeEccentricity¶
class
📇 model card · ∇ gradient-verified — 1 audit case
MoeEccentricity(e_max: 'float' = 0.99) -> NoneMoe & Di Stefano (2017) eccentricity distribution p(e) ∝ e^η(logP, M1).
Faithful implementation of Moe & Di Stefano (2017) §9.2 (Figure 36): the eccentricity follows a power law p(e) ∝ e^η on 0 <= e <= e_max, with the slope η a function of orbital period AND primary mass:
Eq. 17 (late-type, 0.8 < M1 < 3 Msun): η = 0.6 - 0.7 / (logP - 0.5)
Eq. 18 (early-type, M1 > 7 Msun): η = 0.9 - 0.2 / (logP - 0.5)with a linear interpolation in M1 for 3 <= M1 <= 7 Msun. η = 0 is uniform ( = 0.5); η = 1 is thermal f(e) = 2e ( = 2/3). Eqs. 17-18 themselves drive short-period (logP ≲ 1) massive binaries to η < 0 (tidal circularization); solar-type binaries asymptote to η ≈ 0.5 at long P; intermediate-period massive binaries reach η ≈ 0.8 (near-thermal). For short periods where η <= -1 (e^η non-normalizable), the orbit is tidally circularized and this returns e ≈ 0 (Moe notes η is “not well defined” for logP ≲ 1).
Sampling uses the inverse CDF e = e_max(P) * u^(1/(η+1)); as η -> -1+ the exponent diverges so e -> 0 (circular) by construction.
The upper limit is the period-dependent Roche-lobe ceiling (their Eq. 3),
e_max(P) = 1 - (P / 2 days)^(-2/3) for P > 2 d,clipped to [0, e_max], so the components do not overflow their Roche lobes at
periapsis (e.g. e_max(10 d) ≈ 0.66, e_max(100 d) ≈ 0.93); P <= 2 d -> circular.
The e_max field is the long-period numerical ceiling (avoids the e -> 1
singularity), reached only where the Roche relation itself approaches 1.
Reference: Moe & Di Stefano (2017) ApJS 230, 15, §9.2 Eqs. 17-18, Eq. 3, Fig. 36. Sana et al. (2012) Science 337, 444 - short-period O-star binaries are eccentricity-poor; the precise slope is in their supplementary Table S3 (not reproduced in the held main report).
Parameters: e_max: Numerical eccentricity ceiling at long P (default: 0.99); the physical cap is the period-dependent Roche relation (Eq. 3).
Source: src/progenax/binaries/eccentricity.py#L176
binaries.sample_isotropic_orientations¶
function
sample_isotropic_orientations(key: 'PRNGKeyArray', n: 'int') -> "Tuple[Float[Array, 'n'], Float[Array, 'n'], Float[Array, 'n'], Float[Array, 'n']]"Sample isotropic orbital orientations.
For randomly oriented orbits in 3D space:
cos(i) ~ U(-1, 1) => i = arccos(u) where u ~ U(-1, 1)
Ω ~ U(0, 2π) (longitude of ascending node)
ω ~ U(0, 2π) (argument of periapsis)
M₀ ~ U(0, 2π) (mean anomaly at epoch)
Args
| Parameter | Description |
|---|---|
key | JAX random key |
n | Number of orientations to sample |
Returns: Tuple of (inclination, Omega, omega, M_anom) arrays, each shape (n,) - inclination: [0, π] radians - Omega: [0, 2π) radians - omega: [0, 2π) radians - M_anom: [0, 2π) radians Reference: Binney & Tremaine (2008) “Galactic Dynamics” Section 3.1
Source: src/progenax/binaries/orientation.py#L16
binaries.RadialBinaryFraction¶
class
RadialBinaryFraction(fb0: 'float' = 0.5, A: 'float' = 0.5, alpha: 'float' = 1.0, r_scale: 'float' = 1.0) -> NonePhenomenological radially-varying binary fraction.
A simple parametric knob for a spatially varying binary fraction:
f_b(r) = fb0 × (1 + A × (r/r_scale)^(-α)), clipped to [0, 1]where: - A > 0: core-enhanced (more binaries in center) - A < 0: core-depleted (fewer binaries in center) - A = 0: constant binary fraction everywhere
NOTE: this functional form is a PHENOMENOLOGICAL model, NOT taken from any specific paper. The references below establish that the binary fraction varies with primary mass / environment (motivating a spatial knob), but none provides this closed-form radial f_b(r) profile. The (r/r_scale)^(-α) term diverges as r -> 0 for α > 0; the clip to [0, 1] caps it (so the core value is set by the clip, not the power law) — intended for r > 0.
References (motivation only — not the source of this functional form): Raghavan et al. (2010) ApJS 190, 1 - solar-neighborhood multiplicity census. Sana et al. (2012) Science 337, 444 - O-star binary fraction. Moe & Di Stefano (2017) ApJS 230, 15 - binary statistics review.
Parameters: fb0: Baseline binary fraction (default: 0.5) A: Amplitude of radial variation (default: 0.5 for core-enhanced) alpha: Power-law index (default: 1.0) r_scale: Scale radius for radial variation (default: 1.0)
Examples. >>> # Core-enhanced: more binaries in center
rbf = RadialBinaryFraction(fb0=0.5, A=0.5, alpha=1.0, r_scale=1.0) radii = jnp.array([0.1, 1.0, 5.0]) fb_r = rbf.compute(radii) # Higher at r=0.1, lower at r=5.0
Sample binary membership¶
key = jax.random.PRNGKey(42) is_binary = rbf.sample_membership(radii, key)
Source: src/progenax/binaries/mass_dependent.py#L34
binaries.CombinedBinaryFraction¶
class
CombinedBinaryFraction(mass_model: 'eqx.Module', radial_model: 'eqx.Module') -> NoneBinary fraction that modulates a mass-based fraction by a radial one.
f_bin(m, r) = clip(mass_model.probability(m) * radial_model.probability(m, r), 0, 1).
Lets a Moe/Raghavan mass-dependent fraction vary spatially (e.g. core-enhanced binarity) without conflating the two effects. Both sub-models are duck-typed BinaryFractionModel instances. Requires radii at evaluation.
Parameters: mass_model: a mass-based BinaryFractionModel (ignores radii). radial_model: a RadialBinaryFraction-like BinaryFractionModel (uses radii).
Source: src/progenax/binaries/mass_dependent.py#L129
binaries.MassDependentBinaryConfig¶
class
MassDependentBinaryConfig(m_break: 'float', low_mass_period: 'LogNormalPeriod | LogUniformPeriod', high_mass_period: 'SanaOBPeriod', low_mass_eccentricity: 'ThermalEccentricity | UniformEccentricity', high_mass_eccentricity: 'MoeEccentricity | LogisticThermalEccentricity') -> NoneConfiguration for mass-dependent binary orbital parameter sampling.
Routes stars to different period/eccentricity distributions based on mass:
Low-mass stars (M < m_break): Solar-type binaries (Duquennoy & Mayor)
High-mass stars (M >= m_break): O/B-type binaries (Sana+2012, Moe+2017)
References. Sana et al. (2012) Science 337, 444 - O-star binary fraction Moe & Di Stefano (2017) ApJS 230, 15 - Binary statistics review Duquennoy & Mayor (1991) A&A 248, 485 - Solar-type binary periods
Parameters: m_break: Mass threshold [Msun] separating low/high-mass prescriptions (default: 8.0) low_mass_period: Period distribution for M < m_break high_mass_period: Period distribution for M >= m_break low_mass_eccentricity: Eccentricity distribution for M < m_break high_mass_eccentricity: Eccentricity distribution for M >= m_break
Example. >>> config = MassDependentBinaryConfig( ... m_break=8.0, ... low_mass_period=LogNormalPeriod(mu_log_P=4.8, sigma_log_P=2.3), ... high_mass_period=SanaOBPeriod(), ... low_mass_eccentricity=ThermalEccentricity(), ... high_mass_eccentricity=MoeEccentricity(), ... )
masses = jnp.array([1.0, 5.0, 10.0, 20.0]) key = jax.random.PRNGKey(42) periods, ecc = sample_mass_dependent_orbits(masses, config, key)
Source: src/progenax/binaries/mass_dependent.py#L161
binaries.sample_mass_dependent_orbits¶
function
sample_mass_dependent_orbits(masses: "Float[Array, 'N']", config: 'MassDependentBinaryConfig', key: 'PRNGKeyArray') -> "Tuple[Float[Array, 'N'], Float[Array, 'N']]"Sample orbital parameters with mass-dependent prescriptions.
Routes each star to appropriate period/eccentricity distribution based on mass:
M < m_break: Low-mass prescription (e.g., solar-type binaries)
M >= m_break: High-mass prescription (e.g., O/B-type binaries)
Uses JAX-native branching (jnp.where) for JIT compatibility.
Args
| Parameter | Description |
|---|---|
masses | Stellar masses [Msun] (shape N,) |
config | Mass-dependent binary configuration |
key | JAX random key |
Returns: Tuple of: - periods: Orbital periods [days] (shape N,) - eccentricities: Orbital eccentricities (shape N,)
Note. Only the high-mass branch supports period-dependent eccentricity (MoeEccentricity, conditioned on the high-mass periods). The low-mass eccentricity is sampled unconditionally (Thermal/Uniform), enforced by the config type hints.
Example. >>> config = MassDependentBinaryConfig( ... m_break=8.0, ... low_mass_period=LogNormalPeriod(), ... high_mass_period=SanaOBPeriod(), ... low_mass_eccentricity=ThermalEccentricity(), ... high_mass_eccentricity=MoeEccentricity(), ... )
masses = jnp.array([1.0, 5.0, 10.0, 20.0]) key = jax.random.PRNGKey(42) periods, ecc = sample_mass_dependent_orbits(masses, config, key)
Source: src/progenax/binaries/mass_dependent.py#L200
binaries.ResolvedBinaries¶
class
ResolvedBinaries(positions: ForwardRef("Float[Array, 'M 3']"), velocities: ForwardRef("Float[Array, 'M 3']"), masses: ForwardRef("Float[Array, 'M']"), is_real: ForwardRef("Bool[Array, 'M']"), primordial_system_id: ForwardRef("Int[Array, 'M']"), is_primordial_secondary: ForwardRef("Bool[Array, 'M']"))Masked fixed-shape (2N) particle set from resolving N systems.
Attributes
| Parameter | Description |
|---|---|
positions | (2N, 3) component positions [length units]. |
velocities | (2N, 3) component velocities [velocity units]. |
masses | (2N,) component masses [M_sun] (ghosts = 0). |
is_real | (2N,) bool — True for real particles (all primaries + binary secondaries); False for single-star ghost secondaries. |
primordial_system_id | (2N,) int — slots 2i, 2i+1 both = i. |
is_primordial_secondary | (2N,) bool — True on the odd (secondary) slots. |
Source: src/progenax/binaries/assembly.py#L27
binaries.resolve_binary_components¶
function
📇 model card · ∇ gradient-verified — 2 audit cases
resolve_binary_components(com_pos: "Float[Array, 'N 3']", com_vel: "Float[Array, 'N 3']", m1: "Float[Array, 'N']", m2: "Float[Array, 'N']", is_binary: "Bool[Array, 'N']", a: "Float[Array, 'N']", e: "Float[Array, 'N']", inc: "Float[Array, 'N']", Omega: "Float[Array, 'N']", omega: "Float[Array, 'N']", M_anom: "Float[Array, 'N']", *, G: 'float') -> 'ResolvedBinaries'Resolve N system COMs into a masked 2N component set (see module docstring).
Args
| Parameter | Description |
|---|---|
is_binary | (N,) bool — True for binary systems. a, e, inc, Omega, omega, M_anom: (N,) Keplerian elements of the relative orbit (ignored for singles; sanitized internally so grads stay finite). |
G | gravitational constant (REQUIRED). |
Returns: ResolvedBinaries (2N slots + is_real mask + primordial provenance).
Source: src/progenax/binaries/assembly.py#L53
binaries.CompanionElements¶
class
CompanionElements(m2: ForwardRef("Float[Array, 'N']"), a: ForwardRef("Float[Array, 'N']"), e: ForwardRef("Float[Array, 'N']"), inc: ForwardRef("Float[Array, 'N']"), Omega: ForwardRef("Float[Array, 'N']"), omega: ForwardRef("Float[Array, 'N']"), M_anom: ForwardRef("Float[Array, 'N']"))Per-system companion mass + relative-orbit elements (singles: m2=0, a sanitized).
Plugs directly into resolve_binary_components(..., a, e, inc, Omega, omega, M_anom).
Source: src/progenax/binaries/companions.py#L35
binaries.IndependentCompanions¶
class
∇ gradient-verified — 1 audit case
IndependentCompanions(binary_fraction: 'Any', q_distribution: 'Any', period_distribution: 'Any', eccentricity_distribution: 'Any') -> NoneVersatile companion model: independent f_b x q x P x e marginals.
Multiplicity from binary_fraction(m1); q from q_distribution (mass-dependent
or unconditional); period and eccentricity sampled independently. Reproduces the
period-averaged default of build_binary_cluster today. The mass-ratio is the
single owner of q -> m2 = m1 * q (0 for singles).
Entropy layout (the equivalence contract): split(key, 5) ->
[is_binary, q, period, eccentricity, orientation].
Source: src/progenax/binaries/companions.py#L84
binaries.MoeCompanions¶
class
📇 model card · ∇ gradient-verified — 1 audit case
MoeCompanions(q_min: 'float' = 0.1) -> NoneFaithful Moe & Di Stefano (2017) companion model — the P-q-e interrelation.
Multiplicity from Moe’s own mass-dependent MassDependentBinaryFraction (no
f_b supplied — it is set by the IMF masses); orbital parameters from the joint
MoeJointOrbit (logP ~ MoePeriod(M1), q ~ MoeDiStefano2017Full(M1,P),
e ~ MoeEccentricity(P,M1)). The same q sets m2 = m1 * q (self-consistent),
so short-period binaries carry larger q than long-period ones.
Reference: Moe & Di Stefano (2017) ApJS 230, 15 (full joint distribution).
Source: src/progenax/binaries/companions.py#L133
binaries.relative_energy¶
function
relative_energy(r_i: "Float[Array, '3']", r_j: "Float[Array, '3']", v_i: "Float[Array, '3']", v_j: "Float[Array, '3']", m_i: "Float[Array, '']", m_j: "Float[Array, '']", *, G: 'float') -> "Float[Array, '']"The (internal) two-body orbital energy of the pair (i, j).
E_rel = ½ μ |v_j - v_i|² − G m_i m_j / |r_j - r_i|, μ = m_i m_j / (m_i + m_j). E_rel < 0 ⇒ the pair is gravitationally bound. For a bound orbit of semi-major axis a, E_rel = −G m_i m_j / (2a). Differentiable (separation guarded).
Source: src/progenax/binaries/diagnostics.py#L35
binaries.find_bound_pairs¶
function
find_bound_pairs(positions: "Float[Array, 'N 3']", velocities: "Float[Array, 'N 3']", masses: "Float[Array, 'N']", *, G: 'float')Detect bound two-body pairs: mutual nearest neighbours with E_rel < 0.
Returns (pair_idx, E_rel) where pair_idx is (K, 2) with i < j per row, and E_rel is (K,) the pair binding energies. Each particle appears in at most one pair (mutual-NN is a matching). O(N^2); eager (dynamic K).
Scaling. The full N×N separation matrix is materialized, so this is intended for N ≲ a few×10^3 (memory ~ N^2). For larger snapshots use an accelerated neighbour-list finder (cell list / kd-tree) — tracked for a future gravax acceleration, which already has the Hermite-AC neighbour machinery.
Source: src/progenax/binaries/diagnostics.py#L59
binaries.find_bound_multiples¶
function
find_bound_multiples(positions: "Float[Array, 'N 3']", velocities: "Float[Array, 'N 3']", masses: "Float[Array, 'N']", *, G: 'float', max_levels: 'int' = 3)Detect hierarchical bound systems (binaries, triples, quadruples, …).
Iteratively collapses mutual-nearest-neighbour bound pairs into COM
pseudo-bodies and re-runs on the reduced set, to a fixed max_levels depth: a
single bound to a binary-COM becomes a triple, two binaries a quadruple, etc.
Mutual-NN is a matching, so all disjoint merges at a level happen at once.
Fixed-shape (N body slots, bounded lax.scan) ⇒ jit-safe; uses argmin, so
not differentiable (a diagnostic). O(N^2) per level (materializes the N×N separation
matrix) — intended for N ≲ a few×10^3; an accelerated neighbour-list version is
tracked for a future gravax acceleration.
Args
| Parameter | Description |
|---|---|
max_levels | hierarchy depth to resolve (default 3 ⇒ up to ~octuples). |
Returns: (system_id, multiplicity), each (N,): system_id[i] is the body slot of particle i (members of one hierarchy share it); multiplicity[i] is the number of particles in that system.
Source: src/progenax/binaries/diagnostics.py#L105
binaries.primordial_survival¶
function
primordial_survival(current_pairs, primordial_system_id: "Int[Array, 'N']") -> 'dict'Compare the current bound pairing to the t=0 primordial labelling.
Args
| Parameter | Description |
|---|---|
current_pairs | (K, 2) index pairs from :func:find_bound_pairs. |
primordial_system_id | (N,) the IC-time primordial_system_id (paired particles share an id). |
Returns: dict with integer counts: survived (primordial binaries still bound), disrupted (primordial binaries no longer a current pair), and newly_formed (current pairs that were not primordial binaries).
Source: src/progenax/binaries/diagnostics.py#L187
binaries.BinaryEnergyBudget¶
class
BinaryEnergyBudget(E_internal: ForwardRef("Float[Array, '']"), T_com: ForwardRef("Float[Array, '']"), W_com: ForwardRef("Float[Array, '']"), Q_com: ForwardRef("Float[Array, '']"), Q_resolved: ForwardRef("Float[Array, '']"), n_binaries: ForwardRef('int'))Two-scale energy budget of a primordial-binary cluster.
Attributes
| Parameter | Description |
|---|---|
E_internal | total internal orbital energy of the primordial binaries (Σ relative_energy; < 0 if bound). The separate “reservoir” that the global virial scaling (Q) does NOT touch. NB the softening passed to the bound-pair finders does NOT soften this internal binding energy — it only regularizes the inter-system potential (audit S18). T_com, W_com: bulk kinetic / gravitational energy of the system COMs — the scale the cluster is virialized on. |
Q_com | T_com / |W_com| — the virial ratio the cluster was scaled to (≈ the Q passed to build_binary_cluster). |
Q_resolved | T / |W| on the resolved stars — the naive ratio that mixes the cluster and internal-binary scales. The deep internal binary binding dominates |W|, so Q_resolved is DEFLATED below the cluster Q (measured ≈ 0.31 vs the 0.5 the cluster was scaled to), NOT inflated (audit S10). |
n_binaries | number of primordial binaries (two positive-mass members). |
Source: src/progenax/binaries/diagnostics.py#L212
binaries.binary_energy_budget¶
function
binary_energy_budget(positions: "Float[Array, 'N 3']", velocities: "Float[Array, 'N 3']", masses: "Float[Array, 'N']", system_id: "Int[Array, 'N']", *, G: 'float', softening: 'float' = 0.0) -> 'BinaryEnergyBudget'Separate the cluster-COM virial from the internal binary binding energy.
build_binary_cluster virializes the system COMs to Q (binaries as point
masses) and leaves the internal binary binding energy as a separate reservoir
(the McLuster scale-separation convention, Küpper+2011 §A8). This diagnostic makes
that explicit: it reconstructs the system COMs (segment sums over system_id) and
reports Q_com (recovers the virial target), E_internal (the reservoir), and the
naive Q_resolved (which mixes the two scales and is therefore not ~Q).
Keyed on system_id, so it accepts either the compacted ICResult
(primordial_system_id) or the masked ResolvedBinaries (ghost secondaries m=0
contribute exactly 0). The COM reconstruction is differentiable; the binary pairing
is eager (like find_bound_pairs). Handles singles + binaries — systems with > 2
members (triples/quadruples) are skipped (the higher-multiplicity seam).
Args
| Parameter | Description |
|---|---|
system_id | per-star system label (members of one system share it). |
G | gravitational constant (REQUIRED). |
softening | softening for the COM/resolved PE (default 0 = exact, matching the collisional build default). |
Returns: :class:BinaryEnergyBudget.