virgil.orbits
Keplerian orbits of a binary's secondary about its primary, in virgil's
conventions: dra East, ddec North and dz away from the observer (mas);
inc below 90° turns the position angle forward; Omega is the position
angle of the node where the secondary recedes; omega is the secondary's
argument of periastron; and times are days since a static float64 t_ref.
Kepler's equation is solved by jaxoplanet, installed with
pip install "virgil-astro[orbits]".
Radial velocities from several spectrographs can share an orbit fit without
fitting their zero points: RVData(..., instrument=labels) with
RVData.term(params, marginalize_offsets=(mean, sd)) marginalizes one velocity
zero point per instrument analytically (Luger, Foreman-Mackey & Hogg 2017,
arXiv:1710.11136), and term.posterior(values) reports them after the fit.
With a broad prior this is the profile likelihood plus a log-determinant
correction; only a finite prior width is supported. The prior must be
stated: marginalize_offsets=True is an error, not a default of N(0, 1000²)
km/s.
Angles without a wrap. orientation_priors() samples the node and
periastron as angle vectors: 2Ω and ϖ = Ω + ω for positions
alone, whose (Ω, ω) and (Ω + 180°, ω + 180°) then fall on one point, or Ω and
ϖ (positions_only=False) when RVs fix the node.
KeplerOrbit.from_varpi(..., varpi, a_mas, two_Omega=...) builds the orbit.
Uniform angles with a prior uniform in cos i are the invariant prior on the
orientation; the map to (2Ω, ϖ) has a constant Jacobian. The vectors follow
Octofitter's UniformCircular and exoplanet's Angle (see
virgil.angles).
The position angle at a reference epoch. For short arcs,
KeplerOrbit.from_position_angle(period, theta, ecc, inc, omega, Omega, a_mas,
t_ref) takes θ, the position angle at t_ref, which astrometry measures
directly, in place of dt_peri (after Thompson et al. 2023). Sample θ as an
angle vector, and add position_angle_prior(orbit_fn) to the
likelihoods=. It adds log|∂M/∂θ|, so that the prior stays uniform in the
time of periastron, which is the invariant prior, rather than uniform in θ.
The map is singular at i = 90°, where the position angle takes only two values.
Near edge-on, keep dt_peri or use StateVectorOrbit.
Positions measured by an instrument whose North or plate scale is
uncertain take per-dataset calibration terms:
PositionData.term(orbit, north_angle="north_b", plate_scale="scale_b")
compares the data with m R(δ) times the orbit's positions, so that every
measured position angle is the true one plus δ and every separation is m
times the true one. "north_b" and "scale_b" are fitted values, with
priors you supply (there is no default width; a Gaussian should come from
the instrument's astrometric calibration). These terms follow Octofitter
(Thompson et al. 2023, AJ 166, 164). The interferometric counterparts are
the noise= terms north_angle and wavel_scale
(see OIData.with_north_angle).
Credit and related software. Kepler's equation is solved by
jaxoplanet (Hattori et al.,
doi:10.5281/zenodo.10736936), the
JAX successor to exoplanet (Foreman-Mackey et al. 2021, JOSS 6, 3285); please
cite it with virgil when you fit orbits. The Thiele–Innes solve in
starting_orbits is the classical method (Thiele 1883, AN 104, 245;
Hartkopf, McAlister & Franz 1989, AJ 98, 1014). Analytic marginalization of RV
zero points is also done by orvara (Brandt et al. 2021, AJ 162, 186). For
orbit fits to relative and absolute astrometry and RVs without an
interferometric scene, mature codes exist:
orbitize! (Blunt et al. 2020, AJ 159, 89),
Octofitter (Thompson et al. 2023,
AJ 166, 164; it also fits closure phases and kernel phases of point sources)
and orvara (Brandt et al. 2021). They
document the same conventions as virgil (the secondary's ω, +z away from the
observer), not yet checked numerically. virgil's orbits exist to drive
time-dependent scenes, extended components included, in JAX; for Hipparcos and
Gaia absolute astrometry use one of those codes and bring the result in as a
prior.
Keplerian orbits of a binary's secondary about its primary.
The conventions are:
drais positive East andddecpositive North (mas), as everywhere in virgil, anddzis positive away from the observer, so that (dra, ddec, dz) is right-handed anddzgrows while the secondary recedes. The vector runs from the primary (the scene's reference component) to the secondary.incin [0°, 180°): below 90° the position angle increases with time (counterclockwise on the sky, North through East).Omega: the position angle of the ascending node, the node where the secondary recedes. Positions alone fix it only modulo 180°.omega: the secondary's argument of periastron (the visual-binary convention); the spectroscopic ω of the primary isomega - 180°.dt_peri: the time of periastron minus the static float64t_ref(days), so that float32 keeps it precise;periodin days;a_masthe angular semimajor axis of the relative orbit.
Kepler's equation is solved by jaxoplanet (an optional dependency,
pip install "virgil-astro[orbits]"), with its exact derivatives; the
positions follow from the Thiele–Innes constants (§2.4). jaxoplanet's own
conventions differ (its ω is the primary's, and its third axis points
toward the observer) and stay inside :meth:KeplerOrbit.to_jaxoplanet and
:meth:KeplerOrbit.from_jaxoplanet.
KeplerOrbit
Bases: Base
A Keplerian orbit of the secondary relative to the primary.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
period
|
float
|
Orbital period (days). |
required |
dt_peri
|
float
|
Time of periastron minus |
required |
ecc
|
float
|
Eccentricity, |
required |
inc
|
float
|
Inclination (degrees, |
required |
omega
|
float
|
The secondary's argument of periastron (degrees), measured from the ascending node in the direction of motion. |
required |
Omega
|
float
|
Position angle of the ascending node, where the secondary recedes (degrees, North through East). |
required |
a_mas
|
float
|
Angular semimajor axis of the relative orbit (mas). |
required |
t_ref
|
float
|
Reference time (MJD, float64, static). Times are measured from it, so that float32 keeps them precise. |
0.0
|
Notes
(Omega + 180°, omega + 180°) gives the same sky positions with dz
reversed: positions alone cannot tell them apart.
period = np.asarray(period, dtype=float)
instance-attribute
dt_peri = np.asarray(dt_peri, dtype=float)
instance-attribute
ecc = np.asarray(ecc, dtype=float)
instance-attribute
inc = np.asarray(inc, dtype=float)
instance-attribute
omega = np.asarray(omega, dtype=float)
instance-attribute
Omega = np.asarray(Omega, dtype=float)
instance-attribute
a_mas = np.asarray(a_mas, dtype=float)
instance-attribute
t_ref = float(t_ref)
class-attribute
instance-attribute
__init__(period, dt_peri, ecc, inc, omega, Omega, a_mas, t_ref=0.0)
__check_init__()
from_varpi(period, dt_peri, ecc, inc, varpi, a_mas, *, Omega=None, two_Omega=None, t_ref=0.0)
classmethod
The orbit with longitude of periastron varpi = Ω + ω.
Give two_Omega (2Ω, degrees) for positions alone, which fix Ω
only modulo 180°, or Omega when other data (RVs) fix the node;
see orientation_priors,
which samples them as angle vectors. omega is varpi - Omega,
the secondary's argument of periastron, as everywhere in virgil.
from_position_angle(period, theta, ecc, inc, omega, Omega, a_mas, t_ref=0.0)
classmethod
The orbit whose position angle at t_ref is theta.
An alternative to dt_peri for short arcs, after Thompson et al.
(2023, AJ 166, 164): astrometry measures the position angle at an
epoch directly. With φ = θ - Ω, the argument of latitude u = ω + f
at t_ref follows from (cos φ, sin φ) ∝ (cos u, sin u cos i):
u = atan2(sin φ / cos i, cos φ); then the true anomaly f = u - ω,
the eccentric and mean anomalies, and dt_peri = -M P / 2π (in
[-P/2, P/2)). Sample theta as an
AngleVector, and add
position_angle_prior to
the likelihood terms, so that the prior stays uniform in the time
of periastron.
Singular at i = 90°, where the position angle takes only two
values (Ω and Ω + 180°) and does not fix the phase; a concrete
inc of 90° is rejected. Near edge-on the map is badly
conditioned: keep dt_peri, or use
StateVectorOrbit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
theta
|
float
|
Position angle of the secondary at |
required |
period
|
As for |
required | |
ecc
|
As for |
required | |
inc
|
As for |
required | |
omega
|
As for |
required | |
Omega
|
As for |
required | |
a_mas
|
As for |
required | |
t_ref
|
As for |
required |
thiele_innes()
The Thiele–Innes constants (A, B, F, G, C, H) (mas).
ddec = A X + F Y, dra = B X + G Y and dz = C X + H Y,
with X = cos E - e and Y = √(1 - e²) sin E.
relative(mjd)
Position of the secondary relative to the primary (mas).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mjd
|
array - like
|
Times (MJD). Concrete times are offset from |
required |
Returns:
| Type | Description |
|---|---|
tuple of arrays
|
|
relative_velocity(mjd)
d(dra, ddec, dz)/dt (mas per day), exactly.
frame(mjd)
Angles of the binary frame at mjd (degrees), by name.
line_pa: position angle of the line of centres, primary to secondary;towards_primaryisline_pa + 180.line_tilt: the line of centres' elevation out of the sky,arcsin(dz / |r|), positive when the secondary is farther.node_pa:Omega, the line of nodes of the orbital plane.inc: the orbit's inclination (0–180);apparent_inc:arccos|cos inc|(0–90), the projected tilt, for components whoseincis an apparent inclination.
separation_pa(mjd)
Separation (mas) and position angle (degrees, North through East, in [0, 360)) of the secondary from the primary.
to_thiele_innes()
The same sky orbit as a :class:ThieleInnesOrbit.
to_jaxoplanet()
A jaxoplanet OrbitalBody for this orbit, and the factor that
turns its relative positions into mas.
jaxoplanet's times are days since t_ref, its axes are
(X, Y, Z) = (North, East, toward the observer), and its ω is the
primary's: (dra, ddec, dz) = (Y, X, -Z) * scale.
from_jaxoplanet(body, a_mas, t_ref=0.0)
classmethod
The orbit of a jaxoplanet OrbitalBody whose times are days
since t_ref, with angular semimajor axis a_mas.
ThieleInnesOrbit
Bases: Base
A sky orbit in Thiele–Innes form: linear in A, B, F, G.
ddec = A X + F Y and dra = B X + G Y, with X = cos E - e and
Y = √(1 - e²) sin E. For fixed (period, dt_peri, ecc) the
positions are linear in the four constants, so a starting orbit is a
linear least-squares solve on a grid of those three.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
period
|
float
|
As in :class: |
required |
dt_peri
|
float
|
As in :class: |
required |
ecc
|
float
|
As in :class: |
required |
A
|
float
|
Thiele–Innes constants (mas). |
required |
B
|
float
|
Thiele–Innes constants (mas). |
required |
F
|
float
|
Thiele–Innes constants (mas). |
required |
G
|
float
|
Thiele–Innes constants (mas). |
required |
t_ref
|
float
|
Reference time (MJD, static float64). |
0.0
|
period = np.asarray(period, dtype=float)
instance-attribute
dt_peri = np.asarray(dt_peri, dtype=float)
instance-attribute
ecc = np.asarray(ecc, dtype=float)
instance-attribute
A = np.asarray(A, dtype=float)
instance-attribute
B = np.asarray(B, dtype=float)
instance-attribute
F = np.asarray(F, dtype=float)
instance-attribute
G = np.asarray(G, dtype=float)
instance-attribute
t_ref = float(t_ref)
class-attribute
instance-attribute
__init__(period, dt_peri, ecc, A, B, F, G, t_ref=0.0)
__check_init__()
sky(mjd)
(dra, ddec) of the secondary from the primary (mas).
to_kepler()
The :class:KeplerOrbit with these sky positions.
Positions fix Omega only modulo 180°: the result has
0 <= Omega < 180, and (Omega + 180, omega + 180) is the
other solution, with dz reversed.
StateVectorOrbit
Bases: Base
An orbit given by the relative position and velocity at t_ref.
For short arcs, where the measured quantities (the position and its rate of change) are well determined but the Keplerian elements are not: sampling these instead of the elements avoids long curved degeneracies. The line-of-sight position and velocity, and the gravitational parameter, carry the physical priors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dra
|
float
|
Position of the secondary from the primary at |
required |
ddec
|
float
|
Position of the secondary from the primary at |
required |
vra
|
float
|
Its velocity at |
required |
vdec
|
float
|
Its velocity at |
required |
dz
|
float
|
Line-of-sight position (mas) and velocity (mas/yr), positive away from the observer. |
required |
vz
|
float
|
Line-of-sight position (mas) and velocity (mas/yr), positive away from the observer. |
required |
mu
|
float
|
Gravitational parameter in angular units, |
required |
t_ref
|
float
|
The epoch of the state (MJD, static float64). |
0.0
|
Notes
Use to_kepler for the
elements. Only bound states (negative energy) are orbits.
dra = np.asarray(dra, dtype=float)
instance-attribute
ddec = np.asarray(ddec, dtype=float)
instance-attribute
vra = np.asarray(vra, dtype=float)
instance-attribute
vdec = np.asarray(vdec, dtype=float)
instance-attribute
dz = np.asarray(dz, dtype=float)
instance-attribute
vz = np.asarray(vz, dtype=float)
instance-attribute
mu = np.asarray(mu, dtype=float)
instance-attribute
t_ref = float(t_ref)
class-attribute
instance-attribute
__init__(dra, ddec, vra, vdec, dz, vz, mu, t_ref=0.0)
__check_init__()
from_kepler(orbit)
classmethod
The state of orbit at its t_ref.
to_kepler()
The KeplerOrbit of this state.
The periastron and the direction of motion there give the
Thiele–Innes constants directly, which fixes the angles in virgil's
conventions; the line-of-sight components then pick the node, which
positions alone leave ambiguous by 180°. A circular orbit (e = 0)
has no periastron: the position at t_ref is used instead.
relative(mjd)
(dra, ddec, dz) (mas), as for
KeplerOrbit.relative.
PositionData
Bases: Base
Measured positions of the secondary relative to the primary.
For starting orbits from per-epoch binary fits, and for published positions with no raw data. It ignores the scene: when the source is more than two point stars, positions fitted at about λ/D resolution can be biased, and the orbit should be fitted to the visibilities.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mjd
|
array - like
|
Time of each position (MJD). |
required |
dra
|
array - like
|
Positions (mas), East and North. |
required |
ddec
|
array - like
|
Positions (mas), East and North. |
required |
cov
|
array - like
|
Covariance of |
required |
t_ref
|
float
|
Reference time (MJD, static float64); by default the first epoch. |
None
|
t_ref = float(mjd.min() if t_ref is None else t_ref)
class-attribute
instance-attribute
dt = np.asarray(mjd - self.t_ref)
instance-attribute
dra = np.asarray(dra)
instance-attribute
ddec = np.asarray(ddec)
instance-attribute
whitener = np.asarray(onp.linalg.inv(onp.linalg.cholesky(cov)))
instance-attribute
__init__(mjd, dra, ddec, cov, t_ref=None)
from_sep_pa(mjd, sep, pa, sep_err, pa_err, t_ref=None)
classmethod
Positions given as separation (mas) and position angle (degrees, North through East), with independent errors on each.
model(orbit, north_angle=None, plate_scale=None)
The positions (dra, ddec) these data would measure (mas).
The orbit's sky positions, seen through this dataset's astrometric
calibration: m R(δ) (dra, ddec) with δ = north_angle
(degrees), m = plate_scale and
R(δ) = [[cos δ, sin δ], [-sin δ, cos δ]]. So a companion at
true PA θ and separation ρ is measured at PA θ + δ and separation
m ρ: north_angle is the error added to every measured
position angle, as in
OIData.with_north_angle.
None (the default) leaves the positions untouched.
whitened_residuals(orbit, north_angle=None, plate_scale=None)
L⁻¹ (data - model) for every epoch, flattened (2n,).
north_angle and plate_scale are this dataset's calibration
terms (see model).
loglike(orbit, north_angle=None, plate_scale=None)
Gaussian log-likelihood of the positions under orbit.
term(orbit, north_angle=None, plate_scale=None)
A likelihood term for fit's likelihoods.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
orbit
|
callable
|
Maps the fitted values (a dict, by path or keyword) to a
|
required |
north_angle
|
str
|
Names of fitted values holding this dataset's North angle δ
(degrees, added to every measured position angle) and plate
scale m (a factor, 1 when calibrated), as in
|
None
|
plate_scale
|
str
|
Names of fitted values holding this dataset's North angle δ
(degrees, added to every measured position angle) and plate
scale m (a factor, 1 when calibrated), as in
|
None
|
RVData
Bases: Base
Radial velocities of one star of the binary.
Radial velocities are the only data that fix the node absolutely: from positions alone (Omega + 180, omega + 180) fits equally well. They need a physical scale, the distance, to turn the orbit's angular velocities into km/s, and the mass ratio to share the motion between the stars.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mjd
|
array - like
|
Times (MJD). |
required |
rv
|
array - like
|
Radial velocities and their errors (km/s, positive receding). |
required |
d_rv
|
array - like
|
Radial velocities and their errors (km/s, positive receding). |
required |
star
|
(primary, secondary)
|
Which star they are of. |
"primary"
|
t_ref
|
float
|
Reference time (MJD, static float64); by default the first epoch. |
None
|
instrument
|
array - like
|
One label per epoch naming the spectrograph. Only used when
|
None
|
t_ref = float(mjd.min() if t_ref is None else t_ref)
class-attribute
instance-attribute
dt = np.asarray(mjd - self.t_ref)
instance-attribute
rv = np.asarray(rv)
instance-attribute
d_rv = np.asarray(d_rv)
instance-attribute
star = star
class-attribute
instance-attribute
instruments = labels
class-attribute
instance-attribute
inst = np.asarray(codes.reshape(mjd.shape))
instance-attribute
__init__(mjd, rv, d_rv, star='primary', t_ref=None, instrument=None)
model(orbit, q, gamma, distance_pc)
Predicted radial velocities (km/s).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
orbit
|
KeplerOrbit
|
The relative orbit (secondary about primary). |
required |
q
|
float
|
Mass ratio, secondary / primary. |
required |
gamma
|
float
|
Systemic velocity (km/s). |
required |
distance_pc
|
float
|
Distance (pc), which turns mas/day into km/s. |
required |
errors(jitter=0.0)
Effective errors sqrt(d_rv² + jitter²) (km/s).
whitened_residuals(orbit, q, gamma, distance_pc, jitter=0.0)
(rv - model) / sqrt(d_rv² + jitter²) for every epoch.
loglike(orbit, q, gamma, distance_pc, jitter=0.0)
Gaussian log-likelihood of the velocities.
jitter (km/s) adds an extra scatter in quadrature to every error;
the normalization then depends on it.
marginal_whitened_residuals(orbit, q, gamma, distance_pc, jitter=0.0, prior=None)
Whitened residuals of the zero-point-marginalized Gaussian.
The data are d ~ N(m + Aμ, C + AΛAᵀ) with m the Keplerian
model (gamma included), A the indicator matrix of the
instruments and w ~ N(μ, Λ) the zero points. Returns u of
length N with uᵀu = rᵀ(C + AΛAᵀ)⁻¹r, r = d - m - Aμ, by the
dense Woodbury whitening of
virgil._linear (an exact square root, with no
eigendecomposition).
prior is (mean, sd) per instrument, as
term builds it.
marginal_log_norm(jitter=0.0, prior=None)
½ log det(C + AΛAᵀ).
This is the normalization that depends on the jitter, in the same
convention as the plain Σ log σ_eff.
marginal_loglike(orbit, q, gamma, distance_pc, jitter=0.0, prior=None)
Normalized log density of the zero-point-marginalized Gaussian.
Equal to a dense N(m + Aμ, C + AΛAᵀ) log density, computed in
O(N k²). A flat prior is the limit Λ → ∞ only up to a constant
(-½ Σ log Λ); the finite prior width is part of the model.
zero_point_posterior(orbit, q, gamma, distance_pc, jitter=0.0, prior=None)
Mean and covariance of the zero points w given the orbit.
w | d, θ ~ N(S⁻¹(Λ⁻¹μ + AᵀC⁻¹(d - m)), S⁻¹) with
S = Λ⁻¹ + AᵀC⁻¹A. Entry j is the velocity zero point of
self.instruments[j]; m includes gamma, so with
gamma = 0 they are the systemic velocity seen by each
instrument, and differences between them are the offsets.
term(params, jitter=None, marginalize_offsets=None)
A likelihood term for fit's likelihoods.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
params
|
callable
|
Maps the fitted values to |
required |
jitter
|
str
|
Path of a fitted value holding an RV jitter |
None
|
marginalize_offsets
|
(mean, sd)
|
Analytically marginalize one velocity zero point per instrument
(Luger, Foreman-Mackey & Hogg 2017, arXiv:1710.11136). The model
is |
None
|
AxialVonMises
Bases: Distribution
A prior on an angle (degrees) known only modulo 180°.
For a node position angle from a source whose convention is in doubt:
the density is a von Mises in 2θ, so θ and θ + 180° are equally likely,
exp(kappa cos 2(θ - mean)), normalized over [0, 360).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mean
|
float
|
Mean angle (degrees); |
required |
kappa
|
float
|
Concentration (of the doubled angle); larger is tighter, with a
width of about |
required |
arg_constraints = {'mean_deg': constraints.real, 'kappa': constraints.positive}
class-attribute
instance-attribute
support = constraints.interval(0.0, 360.0)
class-attribute
instance-attribute
mean_deg = np.asarray(mean, dtype=float)
instance-attribute
kappa = np.asarray(kappa, dtype=float)
instance-attribute
__init__(mean, kappa, *, validate_args=None)
log_prob(value)
sample(key, sample_shape=())
period_grid(times, p_min, p_max, k=_PHASE_COHERENCE)
Trial periods that stay in phase across the observations.
A period that is wrong by δP drifts by T δP / P² cycles over the
time baseline T, so trial periods must be spaced by no more than δP =
P²/(kT) for the best of them to stay within a fraction of a cycle of
the truth at every epoch. That is a uniform grid in frequency 1/P, with
a step of 1/(kT): many more periods at short periods than a
log-spaced grid, which misses short-period orbits on a long baseline.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
times
|
array - like
|
The times (days, e.g. MJD) of the epochs that seed the orbits; their range is the baseline T. |
required |
p_min
|
float
|
The shortest and longest trial periods (days), e.g. from the period prior. |
required |
p_max
|
float
|
The shortest and longest trial periods (days), e.g. from the period prior. |
required |
k
|
float
|
Neighbouring trial periods drift apart by at most |
_PHASE_COHERENCE
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The periods (days), increasing from |
starting_orbits(positions, periods, eccs=None, n_phase=36, n_best=5)
Good starting orbits for a set of positions, from a grid search.
At fixed period, eccentricity and time of periastron the positions are linear in the Thiele–Innes constants, so each grid point is an exact weighted least-squares solve. This is the classical way to start an orbit fit: it needs no random restarts and handles the several minima of a short arc. Refine the best orbits with a fit to the visibilities or to the positions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
positions
|
PositionData
|
The measured positions. |
required |
periods
|
array - like
|
Trial periods (days), dense enough to stay in phase across the
positions' time baseline: see
|
required |
eccs
|
array - like
|
Trial eccentricities; by default 0 to 0.9 in steps of 0.05. |
None
|
n_phase
|
int
|
Number of trial times of periastron, spread over each period. |
36
|
n_best
|
int
|
Number of orbits to return. |
5
|
Returns:
| Type | Description |
|---|---|
list of (KeplerOrbit, float)
|
The best orbits and their χ², best first. Each has
|
total_mass(orbit, distance_pc)
Total mass (solar masses) from the orbit at a distance (pc).
Kepler's third law, M = 4π² a³ / (G P²), with a in au
(a_mas · D / 1000) and P in days, and the IAU 2015 nominal
G M☉ = 1.3271244e20 m³ s⁻², au = 149597870700 m and day = 86400 s. (The
shortcut M = a³ / P² with P in Julian years holds only for the Gaussian
year, 365.256898 d, and is 4e-5 off in M.) Report it as a function of
distance, or with the distance's uncertainty: positions alone do not
fix it.
distance_pc(orbit, total_mass)
The distance (pc) at which orbit has this total mass (M☉): the
dynamical parallax, the inverse of total_mass
(same constants).
orientation_priors(positions_only=True, *, prefix='', ring_width=0.25, inclination=False)
Angle-vector priors for an orbit's node and periastron.
Vectors remove the wrap at 0°/360° (see
AngleVector); these choose which angles
to sample, so that exact symmetries become single points rather than
separate modes:
positions_only=True:"two_Omega"(2Ω, i.e. Ω modulo 180°) and"varpi"(ϖ = Ω + ω, the longitude of periastron). Positions alone cannot tell (Ω, ω) from (Ω + 180°, ω + 180°); both have the same 2Ω and ϖ, so the two modes collapse to one.positions_only=False, when RVs or other data fix the node:"Omega"and"varpi".
Near face-on, positions fix ϖ but not Ω and ω separately; ϖ is then
the well-measured angle and Ω the broad one. Build the orbit with
KeplerOrbit.from_varpi.
The angles are uniform, which with a prior uniform in cos i is the invariant (Haar) prior on the orbit's orientation: (Ω, ω) → (2Ω, ϖ) is linear with a constant Jacobian, so uniform (Ω, ω) is uniform (2Ω, ϖ).
With inclination=True the inclination prior
IsotropicInclination (density
∝ sin i on 0° to 180°) is added under "inc", so one call gives the
full Haar (isotropic) prior on the orientation. It is off by default so
that existing code, which fixes or places its own prior on inc,
keeps getting exactly the keys it did.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
positions_only
|
bool
|
Whether only positions constrain the orbit (default |
True
|
prefix
|
str
|
Prepended to the keys, e.g. |
''
|
ring_width
|
float
|
Passed to |
0.25
|
inclination
|
bool
|
Also return |
False
|
Returns:
| Type | Description |
|---|---|
dict
|
Priors keyed |
Examples:
>>> priors = {**orientation_priors(), "ecc": dist.Uniform(0.0, 0.9)}
>>> def orbit_fn(v):
... return KeplerOrbit.from_varpi(
... 400.0, 30.0, v["ecc"], 60.0, v["varpi"], 20.0,
... two_Omega=v["two_Omega"], t_ref=60500.0,
... )
orientation_from_varpi(varpi, *, Omega=None, two_Omega=None)
(omega, Omega) (degrees) from ϖ = Ω + ω and the node.
Give exactly one of Omega and two_Omega. From two_Omega,
Ω is reported in [0°, 180°), as by
starting_orbits; ω is in
[0°, 360°).
position_angle_prior(orbit_fn)
The prior term that keeps a θ-sampled orbit uniform in t_peri.
Pass it in likelihoods= to fit or
numpyro_model, beside the data
terms, when orbit_fn(values) builds its orbit with
KeplerOrbit.from_position_angle.
It adds position_angle_log_jacobian
at the orbit's position angle at t_ref (its θ) to the log
posterior. It has no least-squares form, so fit then defaults to
L-BFGS; its χ² in info is 0 over 0 points.
Examples:
>>> priors = {"theta": AngleVector(), "ecc": dist.Uniform(0.0, 0.9)}
>>> def orbit_fn(v):
... return KeplerOrbit.from_position_angle(
... 400.0, v["theta"], v["ecc"], 60.0, 40.0, 110.0, 20.0,
... t_ref=60500.0,
... )
>>> terms = [positions.term(orbit_fn), position_angle_prior(orbit_fn)]
position_angle_log_jacobian(theta, ecc, inc, omega, Omega)
log|∂M/∂θ| at fixed (e, i, ω, Ω), for a prior uniform in t_peri.
The invariant prior on the epoch is uniform in the time of periastron,
i.e. in the mean anomaly M at t_ref (a translation), not in the
position angle θ there. Sampling θ uniformly (an
AngleVector) and adding this term gives
back the uniform prior in M:
log|∂M/∂θ| = 3/2 log(1 - e²) - 2 log(1 + e cos f) + log|cos i|
- log(cos²φ cos²i + sin²φ), φ = θ - Ω,
from dM/df = (1 - e²)^{3/2}/(1 + e cos f)² and du/dφ = cos i /
(cos²φ cos²i + sin²φ). Over a full turn of θ it integrates to 2π, so
the prior stays normalized. It diverges at i = 90° (see
KeplerOrbit.from_position_angle).
All angles in degrees.