harv.kepler package¶
Keplerian orbit mechanics and related utilities.
- class harv.kepler.KeplerianBody¶
Bases:
ModuleOrbital parameters of a Keplerian body (companion, i.e. body 2).
This class represents the orbital parameters of a companion or second body (relative to some barycenter). So, all parameters represent the orbital elements of a specific body relative to the barycenter.
The primary parameterization uses: - Period (P) - Eccentricity (e) - Semi-major axis (a) or
semi_major_axis- Time of pericenter ort_periAlternative constructors support different parameterizations.
- Parameters:
period (
Real[Quantity[PhysicalType('time')], '']) – Orbital period.eccentricity (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']) – Orbital eccentricity.semi_major_axis (
Real[Quantity[PhysicalType('length')], '']) – The semi-major axis of the body relative to its system barycenter.t_peri (
Real[Quantity[PhysicalType('time')], '']) – Time of pericenter passage.orientation (
KeplerianOrientation) – Optional: Orientation of the orbit.ecc_zero_tol (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']) – Optional: Tolerance for treating eccentricity as zero in circular orbit shortcuts. This is used to avoid numerical issues in the Kepler solver when eccentricity is very small, and can be set to a small multiple of machine epsilon for the data type used.
Examples
>>> from unxt import Q >>> from harv.kepler.body import KeplerianBody >>> body = KeplerianBody( ... period=Q(365.25, "day"), ... eccentricity=0.1, ... semi_major_axis=Q(1.0, "AU"), ... t_peri=Q(0.0, "day"), ... )
- __init__(period, eccentricity, semi_major_axis, t_peri, orientation=KeplerianOrientation(), *, ecc_zero_tol=np.float64(2.220446049250313e-15))¶
- Parameters:
period (
Real[Quantity[PhysicalType('time')], ''])eccentricity (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])semi_major_axis (
Real[Quantity[PhysicalType('length')], ''])t_peri (
Real[Quantity[PhysicalType('time')], ''])orientation (
KeplerianOrientation)ecc_zero_tol (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])
- Return type:
None
- static __new__(cls, *args, **kwargs)¶
-
ecc_zero_tol:
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']= np.float64(2.220446049250313e-15)¶
- classmethod from_masses(period, eccentricity, m_total, m_body, t_peri, **kwargs)¶
Construct body’s barycentric orbit from masses and period.
Computes this body’s barycentric semi-major axis from Kepler’s 3rd law: 1. Compute relative orbit: a_rel = (G m_total P^2 / 4 pi^2)^(1/3) 2. Convert to barycentric: a_body = a_rel * (1 - m_body / m_total)
- Parameters:
period (
Real[Quantity[PhysicalType('time')], '']) – Orbital period.eccentricity (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']) – Orbital eccentricity.m_total (
Real[Quantity[PhysicalType('mass')], '']) – Total system mass.m_body (
Real[Quantity[PhysicalType('mass')], '']) – Mass of this body.t_peri (
Real[Quantity[PhysicalType('time')], '']) – Time of pericenter passage.orientation – Optional: Orientation of the orbit.
kwargs (
Any) – Additional keyword arguments to pass to the main constructor.
- Return type:
- Returns:
The body’s orbit about the system barycenter.
- get_mass(m_total)¶
Recover this body’s mass from the total system mass.
This inverts from_masses: given the total system mass and the stored barycentric semi-major axis, it recovers the body mass via
\[ \begin{align}\begin{aligned}a_\mathrm{rel} = \left( \frac{G\, m_\mathrm{total}\, P^2}{4\pi^2}\right)^{1/3}\\m_\mathrm{body} = m_\mathrm{total}\left( 1 - \frac{a_\mathrm{body}}{a_\mathrm{rel}}\right)\end{aligned}\end{align} \]- Parameters:
m_total (
Real[Quantity[PhysicalType('mass')], '']) – Total system mass (sum of all bodies).- Return type:
Real[Quantity[PhysicalType('mass')], '']- Returns:
The mass of this body.
- get_position(time, orientation=None)¶
Get 3D position of the body in its orbit at given time(s).
By definition and convention of this class, this is the position of the body relative to the system barycenter, accounting for the orbit orientation.
- Parameters:
time (
Real[Quantity[PhysicalType('time')], '*batch'])orientation (
KeplerianOrientation|None)
- Return type:
Real[Quantity[PhysicalType('length')], '3 *batch']
- get_velocity(time, orientation=None)¶
Get 3D velocity of the body relative to the system barycenter.
- Parameters:
time (
Real[Quantity[PhysicalType('time')], '*batch'])orientation (
KeplerianOrientation|None)
- Return type:
Real[Quantity[PhysicalType({'speed', 'velocity'})], '3 *batch']
-
orientation:
KeplerianOrientation= KeplerianOrientation()¶
-
period:
Real[Quantity[PhysicalType('time')], '']¶
-
eccentricity:
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']¶
-
semi_major_axis:
Real[Quantity[PhysicalType('length')], '']¶
-
t_peri:
Real[Quantity[PhysicalType('time')], '']¶
- class harv.kepler.KeplerianOrientation¶
Bases:
ModuleOrientation of a Keplerian orbit in 3D space.
Stores the three Euler angles that define how the orbital plane is oriented relative to the observer’s reference frame: - Inclination (i): tilt of orbital plane from sky plane - Longitude of ascending node (Omega): where orbit crosses sky plane - Argument of pericenter (omega): orientation of ellipse within orbital plane
Angles are stored as sin/cos pairs for numerical stability.
Examples
>>> from unxt import Q >>> from harv.kepler.orientation import KeplerianOrientation >>> orient = KeplerianOrientation.from_angles( ... arg_peri=Q(0.5, "rad"), ... lon_asc_node=Q(1.0, "rad"), ... inclination=Q(0.3, "rad"), ... )
- Parameters:
sin_arg_peri (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])cos_arg_peri (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])sin_lon_asc_node (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])cos_lon_asc_node (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])sin_i (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])cos_i (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])
- __init__(sin_arg_peri=0.0, cos_arg_peri=1.0, sin_lon_asc_node=0.0, cos_lon_asc_node=1.0, sin_i=0.0, cos_i=1.0)¶
- Parameters:
sin_arg_peri (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])cos_arg_peri (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])sin_lon_asc_node (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])cos_lon_asc_node (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])sin_i (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])cos_i (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])
- Return type:
None
- static __new__(cls, *args, **kwargs)¶
- property arg_peri: Real[Quantity[PhysicalType('angle')], '']¶
Argument of pericenter (omega).
-
cos_arg_peri:
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']= 1.0¶
-
cos_i:
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']= 1.0¶
-
cos_lon_asc_node:
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']= 1.0¶
- classmethod from_angles(arg_peri=Quantity(Array(0., dtype=float32, weak_type=True), unit='rad'), lon_asc_node=Quantity(Array(0., dtype=float32, weak_type=True), unit='rad'), inclination=Quantity(Array(0., dtype=float32, weak_type=True), unit='rad'))¶
Construct from angle values.
- Parameters:
arg_peri (
Real[Quantity[PhysicalType('angle')], ''])lon_asc_node (
Real[Quantity[PhysicalType('angle')], ''])inclination (
Real[Quantity[PhysicalType('angle')], ''])
- Return type:
- classmethod from_thiele_innes(A, B, F, G)¶
Construct orientation from Thiele-Innes constants.
Inverts the Thiele-Innes constants to recover (omega, Omega, i, a). This loosely follows Appendix A of https://arxiv.org/abs/2206.05726 but my convention for angles is different. For this implementation:
0 < i < pi/2 0 < omega < 2pi 0 < Omega < 2pi
whereas the paper linked above assumes:
0 < i < pi 0 < omega < 2pi 0 < Omega < pi
- Return type:
tuple[KeplerianOrientation,Real[Quantity[PhysicalType('length')], '']|Real[Quantity[PhysicalType('angle')], '']]- Returns:
orientation – KeplerianOrientation object
semi_major_axis – Recovered semi-major axis
- Parameters:
A (
Real[Quantity[PhysicalType('length')], '']|Real[Quantity[PhysicalType('angle')], ''])B (
Real[Quantity[PhysicalType('length')], '']|Real[Quantity[PhysicalType('angle')], ''])F (
Real[Quantity[PhysicalType('length')], '']|Real[Quantity[PhysicalType('angle')], ''])G (
Real[Quantity[PhysicalType('length')], '']|Real[Quantity[PhysicalType('angle')], ''])
- property inclination: Real[Quantity[PhysicalType('angle')], '']¶
Inclination (i).
- property lon_asc_node: Real[Quantity[PhysicalType('angle')], '']¶
Longitude of ascending node (Omega).
- property rotation_matrix: Array¶
Compute rotation matrix from orbital plane to observer frame.
Returns the rotation matrix R such that: r_observer_frame = R @ r_orbital_frame
The rotation is composed of three sequential rotations: 1. R_z(omega): Rotate by argument of pericenter, omega, in orbital plane 2. R_x(i): Rotate by inclination, i, to tilt orbital plane 3. R_z(Omega): Rotate by longitude of ascending node, Omega, on sky plane
The full rotation matrix is therefore: R = R_z(Omega) @ R_x(i) @ R_z(omega)
We build the matrix directly from the sin/cos pairs for numerical stability and speed, but using the notation below, it is equivalent to:
R1 = jnp.array([[c_w, -s_w, 0], [s_w, c_w, 0], [0, 0, 1]]) R2 = jnp.array([[1., 0, 0], [0, c_i, -s_i], [0, s_i, c_i]]) R3 = jnp.array([[c_W, -s_W, 0], [s_W, c_W, 0], [0, 0, 1]]) R = R3 @ R2 @ R1
Or, alternatively: omega = arg_peri.to_value(“rad”) Omega = lon_asc_node.to_value(“rad”) i = inclination.to_value(“rad”) R = Rotation.from_euler(‘ZXZ’, [Omega, i, omega]).as_matrix()
-
sin_arg_peri:
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']= 0.0¶
-
sin_i:
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']= 0.0¶
-
sin_lon_asc_node:
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']= 0.0¶
- thiele_innes_constants(semi_major_axis=None)¶
Compute Thiele-Innes constants (A, B, F, G).
These constants linearize the relationship between orbital position and sky-plane projection. See Appendix A of: https://arxiv.org/abs/2206.05726
- Parameters:
semi_major_axis (
Real[Quantity[PhysicalType('length')], '']|Real[Quantity[PhysicalType('angle')], '']|None) – Semi-major axis of the orbit- Returns:
The four Thiele-Innes constants.
- Return type:
A, B, F, G
- class harv.kepler.AbstractNBodySystem¶
Bases:
ModuleAbstract base class for Keplerian N-body systems.
- __init__()¶
- Return type:
None
- static __new__(cls, *args, **kwargs)¶
- property m_total: Real[Quantity[PhysicalType('mass')], '']¶
Total system mass.
- class harv.kepler.TwoBodySystem¶
Bases:
AbstractNBodySystemA system with a primary body and one companion.
Bodies are indexed as: - 0: Primary body - 1: Companion
- Parameters:
m_primary (
Real[Quantity[PhysicalType('mass')], ''])companion (
KeplerianBody)
- __init__(m_primary, companion)¶
- Parameters:
m_primary (
Real[Quantity[PhysicalType('mass')], ''])companion (
KeplerianBody)
- Return type:
None
- static __new__(cls, *args, **kwargs)¶
- property m_companion: Real[Quantity[PhysicalType('mass')], '']¶
Companion mass.
- property m_total: Real[Quantity[PhysicalType('mass')], '']¶
Total system mass, derived from Kepler’s 3rd law.
The companion’s semi-major axis is defined relative to the system barycenter, so we can use Kepler’s 3rd law to derive the total mass from the companion’s orbital parameters and the primary mass: a_body = a_rel * m_primary / m_total a_rel^3 = G * m_total * P^2 / 4 pi^2 a_body^3 = G * m_primary^3 * P^2 / (4 pi^2 * m_total^2) (solve for m_total)
- position_barycentric(time, body_idx)¶
Get barycentric position of specified body at given time(s).
- Parameters:
time (
Real[Quantity[PhysicalType('time')], '*batch']) – Time(s) to evaluatebody_idx (
int) – Index of body (0=primary, 1=companion)
- Return type:
Real[Quantity[PhysicalType('length')], '3 *batch']- Returns:
3D position vector(s) of specified body relative to barycenter
- position_relative(time)¶
Position of companion relative to primary.
- Parameters:
time (
Real[Quantity[PhysicalType('time')], '*batch']) – Time(s) to evaluate- Return type:
Real[Quantity[PhysicalType('length')], '3 *batch']- Returns:
3D position vector(s) of companion relative to primary
- velocity_barycentric(time, body_idx)¶
Get barycentric velocity of specified body at given time(s).
- Parameters:
time (
Real[Quantity[PhysicalType('time')], '*batch']) – Time(s) to evaluatebody_idx (
int) – Index of body (0=primary, 1=companion)
- Return type:
Real[Quantity[PhysicalType({'speed', 'velocity'})], '3 *batch']- Returns:
3D velocity vector(s) of specified body relative to barycenter
- velocity_relative(time)¶
Velocity of companion relative to primary.
- Parameters:
time (
Real[Quantity[PhysicalType('time')], '*batch']) – Time(s) to evaluate- Return type:
Real[Quantity[PhysicalType({'speed', 'velocity'})], '3 *batch']- Returns:
3D velocity vector(s) of companion relative to primary
-
m_primary:
Real[Quantity[PhysicalType('mass')], '']¶
-
companion:
KeplerianBody¶
- harv.kepler.astrometric_mass_function(a_physical, period)¶
Astrometric mass function from a physical orbit size and period.
\[f(m) = \frac{4\pi^2\,a^3}{G\,P^2}\]With
athe semi-major axis of the primary’s barycentric orbit – the photocentre orbit when the companion is dark or faint – this equals \(m_2^3 / (m_1 + m_2)^2\). See docs/spec.md (“Mass functions”).- Parameters:
a_physical (
Real[Quantity[PhysicalType('length')], '*batch']) – Physical semi-major axis (a length), e.g. fromsemi_major_axis_physical().period (
Real[Quantity[PhysicalType('time')], '*batch']) – Orbital period.
- Return type:
Real[Quantity[PhysicalType('mass')], '*batch']- Returns:
The astrometric mass function, in solar masses.
Examples
>>> from unxt import Q, ustrip >>> from harv.kepler.masses import astrometric_mass_function >>> mf = astrometric_mass_function(Q(1.0, "AU"), Q(1.0, "yr")) >>> round(float(ustrip("Msun", mf)), 4) 1.0
- harv.kepler.astrometric_orbit_at_times(times, period, eccentricity, t_peri, arg_peri, cos_i, lon_asc_node, semi_major_axis)¶
Compute sky-plane astrometric orbit (Deltara, Deltadec) at given times.
Uses the Thiele-Innes parameterization following the Gaia local plane coordinate (LPC) convention (Lindegren & Bastian, GAIA-C3-TN-LU-LL-061-08, Eq. 4):
Deltara = (B*cos f + G*sin f) * a (RA / ``a`` direction) Deltadec = (A*cos f + F*sin f) * a (Dec / ``d`` direction)
where A, B, F, G are the unit Thiele-Innes constants and a is the photocentric semi-major axis.
- Parameters:
times (
Real[Quantity[PhysicalType('time')], '*batch']) – Observation times.period (
Real[Quantity[PhysicalType('time')], '']) – Orbital period.eccentricity (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']) – Orbital eccentricity.t_peri (
Real[Quantity[PhysicalType('time')], '']) – Time of periastron passage. In the likelihood layer this is derived from the dimensionlessphase_periast_peri = phase_peri * period(see_solve_kepler).arg_peri (
Real[Quantity[PhysicalType('angle')], '']) – Argument of periastron omega.cos_i (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']) – Cosine of orbital inclination.lon_asc_node (
Real[Quantity[PhysicalType('angle')], '']) – Longitude of the ascending node Omega.semi_major_axis (
Real[Quantity[PhysicalType('angle')], '']) – Photocentric semi-major axis.
- Return type:
tuple[Real[Quantity[PhysicalType('angle')], '*batch'],Real[Quantity[PhysicalType('angle')], '*batch']]- Returns:
(delta_ra, delta_dec)— sky-plane offsets in the same unit assemi_major_axis.
Examples
>>> from unxt import Q >>> from harv.kepler.orbits import astrometric_orbit_at_times >>> times = Q([0.0, 100.0, 200.0], "day") >>> dra, ddec = astrometric_orbit_at_times( ... times, ... period=Q(300.0, "day"), ... eccentricity=0.3, ... t_peri=Q(0.0, "day"), ... arg_peri=Q(1.2, "rad"), ... cos_i=0.5, ... lon_asc_node=Q(0.8, "rad"), ... semi_major_axis=Q(3.0, "mas"), ... ) >>> dra.unit Unit("mas")
- harv.kepler.binary_mass_function(period, rv_semiamp, eccentricity)¶
Binary mass function from radial-velocity orbital elements.
\[f(m) = \frac{m_2^3 \sin^3 i}{(m_1 + m_2)^2} = \frac{P\,K^3\,(1 - e^2)^{3/2}}{2\pi G}\]- Parameters:
period (
Real[Quantity[PhysicalType('time')], '*batch']) – Orbital period.rv_semiamp (
Real[Quantity[PhysicalType({'speed', 'velocity'})], '*batch']) – Radial-velocity semi-amplitudeK.eccentricity (
Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch']) – Orbital eccentricity (dimensionless).
- Return type:
Real[Quantity[PhysicalType('mass')], '*batch']- Returns:
The binary mass function, in solar masses.
Examples
>>> from unxt import Q, ustrip >>> from harv.kepler.masses import binary_mass_function >>> mf = binary_mass_function(Q(100.0, "day"), Q(10.0, "km/s"), 0.1) >>> round(float(ustrip("Msun", mf)), 4) 0.0102
- harv.kepler.companion_mass_from_mass_function(mass_function, m1, sini=1.0)¶
Solve the mass function for the companion mass \(m_2\).
Inverts \(m_2^3 \sin^3 i / (m_1 + m_2)^2 = f\) for \(m_2\), given the mass function
f, primary massm1, andsin i.sini = 1(the default) yields the minimum companion mass.The cubic has a single positive root, so it is solved by bisection – which is robust and
jax.jit/jax.vmapfriendly. The root is bracketed by[0, max(4 f_eff, (4 f_eff m_1^2)^{1/3})]with \(f_\mathrm{eff} = f / \sin^3 i\).- Parameters:
mass_function (
Real[Quantity[PhysicalType('mass')], '*batch']) – Binary or astrometric mass function (a mass).m1 (
Quantity[PhysicalType('mass')]) – Primary mass.sini (
Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch']) – Sine of the orbital inclination. Default 1 (edge-on -> minimum mass).
- Return type:
Real[Quantity[PhysicalType('mass')], '*batch']- Returns:
The companion mass \(m_2\), in solar masses.
Examples
>>> from unxt import Q, ustrip >>> from harv.kepler.masses import companion_mass_from_mass_function >>> m2 = companion_mass_from_mass_function(Q(0.25, "Msun"), Q(1.0, "Msun")) >>> round(float(ustrip("Msun", m2)), 3) 1.0
- harv.kepler.compute_true_anomaly_components(time, period, eccentricity, t_peri)¶
Compute true anomaly at given times.
- Parameters:
time (
Real[Quantity[PhysicalType('time')], '*batch']) – Observation times, shape (n,)period (
Real[Quantity[PhysicalType('time')], '']) – Orbital periodeccentricity (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']) – Orbital eccentricityt_peri (
Real[Quantity[PhysicalType('time')], '']) – Time of pericenter passage
- Return type:
tuple[Float[Array, '*batch']|Real[Quantity[PhysicalType('dimensionless')], '*batch'],Float[Array, '*batch']|Real[Quantity[PhysicalType('dimensionless')], '*batch']]- Returns:
(sin_f, cos_f)— true anomaly components, each shape(n,).
Examples
>>> from unxt import Q >>> from harv.kepler.orbits import compute_true_anomaly_components >>> sin_f, cos_f = compute_true_anomaly_components( ... time=Q([0.0, 25.0, 50.0], "day"), ... period=Q(100.0, "day"), ... eccentricity=0.3, ... t_peri=Q(0.0, "day"), ... )
- harv.kepler.rv_at_times(times, period, eccentricity, t_peri, arg_peri, rv_semiamp, v_sys)¶
Compute the RV model: K*[cos(omega + f(t)) + e*cos(omega)] + v_0.
- Parameters:
times (
Real[Quantity[PhysicalType('time')], '*batch']) – Observation times.period (
Real[Quantity[PhysicalType('time')], '']) – Orbital period.eccentricity (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']) – Orbital eccentricity.t_peri (
Real[Quantity[PhysicalType('time')], '']) – Time of periastron passage. In the likelihood layer this is derived from the dimensionlessphase_periast_peri = phase_peri * period(see_solve_kepler).arg_peri (
Real[Quantity[PhysicalType('angle')], '']) – Argument of periastron omega.rv_semiamp (
Real[Quantity[PhysicalType({'speed', 'velocity'})], '']) – RV semi-amplitude.v_sys (
Real[Quantity[PhysicalType({'speed', 'velocity'})], '']) – Systemic velocity.
- Return type:
Real[Quantity[PhysicalType({'speed', 'velocity'})], '*batch']- Returns:
Radial velocities in the same unit as
rv_semiampandv_sys.
Examples
>>> from unxt import Q >>> from harv.kepler.orbits import rv_at_times >>> times = Q([0.0, 50.0, 100.0], "day") >>> rv = rv_at_times( ... times, ... period=Q(200.0, "day"), ... eccentricity=0.3, ... t_peri=Q(50.0, "day"), ... arg_peri=Q(1.2, "rad"), ... rv_semiamp=Q(8.0, "km/s"), ... v_sys=Q(-5.0, "km/s"), ... ) >>> rv.unit Unit("km / s")
- harv.kepler.mean_anomaly(dt, period)¶
Compute mean anomaly from elapsed time and period.
M = 2pi * dt / period, returned as aQwith angle units (radians).Examples
>>> from unxt import Q >>> from harv.kepler.orbits import mean_anomaly >>> M = mean_anomaly(Q(50.0, "day"), Q(100.0, "day")) >>> M.unit Unit("rad")
- Parameters:
dt (
Real[Quantity[PhysicalType('time')], '*batch'])period (
Real[Quantity[PhysicalType('time')], ''])
- Return type:
Real[Quantity[PhysicalType('angle')], '*batch']
- harv.kepler.rv_shape(sin_f, cos_f, eccentricity, arg_peri)¶
RV shape function: cos(omega + f) + e*cos(omega).
Returns the dimensionless RV amplitude factor for each observation.
arg_perimay be a plain float/array in radians or aQwith angle units;jnp.coshandles both via quax dispatch.eccentricitymay likewise be a dimensionlessQor a plain scalar.Examples
>>> from unxt import Q >>> from harv.kepler.orbits import true_anomaly_from_mean, rv_shape >>> sin_f, cos_f = true_anomaly_from_mean(Q(1.0, "rad"), 0.3) >>> shape = rv_shape(sin_f, cos_f, 0.3, Q(0.5, "rad"))
- Parameters:
sin_f (
Float[Array, '*batch']|Real[Quantity[PhysicalType('dimensionless')], '*batch'])cos_f (
Float[Array, '*batch']|Real[Quantity[PhysicalType('dimensionless')], '*batch'])eccentricity (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])arg_peri (
Real[Quantity[PhysicalType('angle')], '']|Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])
- Return type:
Float[Array, '*batch']|Real[Quantity[PhysicalType('dimensionless')], '*batch']
- harv.kepler.semi_major_axis_physical(a_angular, parallax)¶
Physical semi-major axis (AU) from an angular size and parallax.
For an orbit at heliocentric distance \(d\), the angular semi-major axis is \(\alpha = a / d\) and the parallax is \(\varpi = 1\,\mathrm{AU} / d\), so \(a = (\alpha / \varpi)\; \mathrm{AU}\).
- Parameters:
a_angular (
Real[Quantity[PhysicalType('angle')], '*batch']) – Angular semi-major axis (an angle, e.g.mas).parallax (
Real[Quantity[PhysicalType('angle')], '*batch']) – Parallax (an angle, e.g.mas).
- Return type:
Real[Quantity[PhysicalType('length')], '*batch']- Returns:
The physical semi-major axis, in AU.
Examples
>>> from unxt import Q >>> from harv.kepler.masses import semi_major_axis_physical >>> a = semi_major_axis_physical(Q(10.0, "mas"), Q(5.0, "mas")) >>> float(a.value) 2.0
- harv.kepler.thiele_innes_ABFG(cos_arg_peri, sin_arg_peri, cos_lon_asc_node, sin_lon_asc_node, cos_i)¶
Compute unit Thiele-Innes constants (A, B, F, G).
Returns the constants with an implicit semi-major axis of 1. Multiply each by
ato recover the physical Thiele-Innes constants.Inputs are dimensionless and may be scalar or batched (plain scalars, JAX arrays, or dimensionless
Q); the computation broadcasts elementwise.The sky-plane orbital displacement uses the Thiele-Innes coordinates
X = (cos E - e)andY = sqrt(1-e^2) sin E, or equivalently in terms of true anomaly:X = r/a cos f,Y = r/a sin fwherer/a = (1-e^2)/(1+e cos f).In the Gaia LPC convention (Lindegren & Bastian, GAIA-C3-TN-LU-LL-061-08, Eq. 4), B and G project into the RA (
a) direction, while A and F project into the Dec (d) direction.See also Eq. A.1 of https://arxiv.org/abs/2206.05726.
Examples
>>> import quaxed.numpy as jnp >>> from unxt import Q >>> from harv.kepler.orbits import thiele_innes_ABFG >>> A, B, F, G = thiele_innes_ABFG( ... cos_arg_peri=jnp.cos(Q(0.5, "rad")), ... sin_arg_peri=jnp.sin(Q(0.5, "rad")), ... cos_lon_asc_node=jnp.cos(Q(1.0, "rad")), ... sin_lon_asc_node=jnp.sin(Q(1.0, "rad")), ... cos_i=jnp.cos(Q(0.3, "rad")), ... )
- Parameters:
cos_arg_peri (
Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch'])sin_arg_peri (
Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch'])cos_lon_asc_node (
Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch'])sin_lon_asc_node (
Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch'])cos_i (
Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch'])
- Return type:
tuple[Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch'],Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch'],Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch'],Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch']]
- harv.kepler.true_anomaly_from_mean(M, eccentricity)¶
Solve Kepler’s equation: mean anomaly -> (sin f, cos f).
Wraps
jaxoplanet.core.kepler.kepler. The mean anomaly is stripped to radians internally.Examples
>>> from unxt import Q >>> from harv.kepler.orbits import true_anomaly_from_mean >>> sin_f, cos_f = true_anomaly_from_mean(Q(1.0, "rad"), 0.3)
- Parameters:
- Return type:
tuple[Float[Array, '*batch']|Real[Quantity[PhysicalType('dimensionless')], '*batch'],Float[Array, '*batch']|Real[Quantity[PhysicalType('dimensionless')], '*batch']]
Submodules¶
harv.kepler.body module¶
Keplerian orbit implementation with units support and JAX compatibility.
- class harv.kepler.body.KeplerianBody¶
Bases:
ModuleOrbital parameters of a Keplerian body (companion, i.e. body 2).
This class represents the orbital parameters of a companion or second body (relative to some barycenter). So, all parameters represent the orbital elements of a specific body relative to the barycenter.
The primary parameterization uses: - Period (P) - Eccentricity (e) - Semi-major axis (a) or
semi_major_axis- Time of pericenter ort_periAlternative constructors support different parameterizations.
- Parameters:
period (
Real[Quantity[PhysicalType('time')], '']) – Orbital period.eccentricity (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']) – Orbital eccentricity.semi_major_axis (
Real[Quantity[PhysicalType('length')], '']) – The semi-major axis of the body relative to its system barycenter.t_peri (
Real[Quantity[PhysicalType('time')], '']) – Time of pericenter passage.orientation (
KeplerianOrientation) – Optional: Orientation of the orbit.ecc_zero_tol (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']) – Optional: Tolerance for treating eccentricity as zero in circular orbit shortcuts. This is used to avoid numerical issues in the Kepler solver when eccentricity is very small, and can be set to a small multiple of machine epsilon for the data type used.
Examples
>>> from unxt import Q >>> from harv.kepler.body import KeplerianBody >>> body = KeplerianBody( ... period=Q(365.25, "day"), ... eccentricity=0.1, ... semi_major_axis=Q(1.0, "AU"), ... t_peri=Q(0.0, "day"), ... )
-
period:
Real[Quantity[PhysicalType('time')], '']¶
-
eccentricity:
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']¶
-
semi_major_axis:
Real[Quantity[PhysicalType('length')], '']¶
-
t_peri:
Real[Quantity[PhysicalType('time')], '']¶
-
orientation:
KeplerianOrientation= KeplerianOrientation()¶
-
ecc_zero_tol:
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']= np.float64(2.220446049250313e-15)¶
- classmethod from_masses(period, eccentricity, m_total, m_body, t_peri, **kwargs)¶
Construct body’s barycentric orbit from masses and period.
Computes this body’s barycentric semi-major axis from Kepler’s 3rd law: 1. Compute relative orbit: a_rel = (G m_total P^2 / 4 pi^2)^(1/3) 2. Convert to barycentric: a_body = a_rel * (1 - m_body / m_total)
- Parameters:
period (
Real[Quantity[PhysicalType('time')], '']) – Orbital period.eccentricity (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']) – Orbital eccentricity.m_total (
Real[Quantity[PhysicalType('mass')], '']) – Total system mass.m_body (
Real[Quantity[PhysicalType('mass')], '']) – Mass of this body.t_peri (
Real[Quantity[PhysicalType('time')], '']) – Time of pericenter passage.orientation – Optional: Orientation of the orbit.
kwargs (
Any) – Additional keyword arguments to pass to the main constructor.
- Return type:
- Returns:
The body’s orbit about the system barycenter.
- get_mass(m_total)¶
Recover this body’s mass from the total system mass.
This inverts from_masses: given the total system mass and the stored barycentric semi-major axis, it recovers the body mass via
\[ \begin{align}\begin{aligned}a_\mathrm{rel} = \left( \frac{G\, m_\mathrm{total}\, P^2}{4\pi^2}\right)^{1/3}\\m_\mathrm{body} = m_\mathrm{total}\left( 1 - \frac{a_\mathrm{body}}{a_\mathrm{rel}}\right)\end{aligned}\end{align} \]- Parameters:
m_total (
Real[Quantity[PhysicalType('mass')], '']) – Total system mass (sum of all bodies).- Return type:
Real[Quantity[PhysicalType('mass')], '']- Returns:
The mass of this body.
- get_position(time, orientation=None)¶
Get 3D position of the body in its orbit at given time(s).
By definition and convention of this class, this is the position of the body relative to the system barycenter, accounting for the orbit orientation.
- Parameters:
time (
Real[Quantity[PhysicalType('time')], '*batch'])orientation (
KeplerianOrientation|None)
- Return type:
Real[Quantity[PhysicalType('length')], '3 *batch']
- get_velocity(time, orientation=None)¶
Get 3D velocity of the body relative to the system barycenter.
- Parameters:
time (
Real[Quantity[PhysicalType('time')], '*batch'])orientation (
KeplerianOrientation|None)
- Return type:
Real[Quantity[PhysicalType({'speed', 'velocity'})], '3 *batch']
- __init__(period, eccentricity, semi_major_axis, t_peri, orientation=KeplerianOrientation(), *, ecc_zero_tol=np.float64(2.220446049250313e-15))¶
- Parameters:
period (
Real[Quantity[PhysicalType('time')], ''])eccentricity (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])semi_major_axis (
Real[Quantity[PhysicalType('length')], ''])t_peri (
Real[Quantity[PhysicalType('time')], ''])orientation (
KeplerianOrientation)ecc_zero_tol (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])
- Return type:
None
harv.kepler.constants module¶
Constants used in Keplerian calculations.
harv.kepler.masses module¶
Mass functions and physical-quantity helpers for Keplerian orbits.
These functions turn posterior orbital elements into physical masses and physical (AU) orbit sizes. They are pure, unit-aware, and broadcast over batched inputs (one value per posterior sample), so callers can apply them directly to arrays of samples. See docs/spec.md (“Mass functions”).
- harv.kepler.masses.binary_mass_function(period, rv_semiamp, eccentricity)¶
Binary mass function from radial-velocity orbital elements.
\[f(m) = \frac{m_2^3 \sin^3 i}{(m_1 + m_2)^2} = \frac{P\,K^3\,(1 - e^2)^{3/2}}{2\pi G}\]- Parameters:
period (
Real[Quantity[PhysicalType('time')], '*batch']) – Orbital period.rv_semiamp (
Real[Quantity[PhysicalType({'speed', 'velocity'})], '*batch']) – Radial-velocity semi-amplitudeK.eccentricity (
Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch']) – Orbital eccentricity (dimensionless).
- Return type:
Real[Quantity[PhysicalType('mass')], '*batch']- Returns:
The binary mass function, in solar masses.
Examples
>>> from unxt import Q, ustrip >>> from harv.kepler.masses import binary_mass_function >>> mf = binary_mass_function(Q(100.0, "day"), Q(10.0, "km/s"), 0.1) >>> round(float(ustrip("Msun", mf)), 4) 0.0102
- harv.kepler.masses.astrometric_mass_function(a_physical, period)¶
Astrometric mass function from a physical orbit size and period.
\[f(m) = \frac{4\pi^2\,a^3}{G\,P^2}\]With
athe semi-major axis of the primary’s barycentric orbit – the photocentre orbit when the companion is dark or faint – this equals \(m_2^3 / (m_1 + m_2)^2\). See docs/spec.md (“Mass functions”).- Parameters:
a_physical (
Real[Quantity[PhysicalType('length')], '*batch']) – Physical semi-major axis (a length), e.g. fromsemi_major_axis_physical().period (
Real[Quantity[PhysicalType('time')], '*batch']) – Orbital period.
- Return type:
Real[Quantity[PhysicalType('mass')], '*batch']- Returns:
The astrometric mass function, in solar masses.
Examples
>>> from unxt import Q, ustrip >>> from harv.kepler.masses import astrometric_mass_function >>> mf = astrometric_mass_function(Q(1.0, "AU"), Q(1.0, "yr")) >>> round(float(ustrip("Msun", mf)), 4) 1.0
- harv.kepler.masses.companion_mass_from_mass_function(mass_function, m1, sini=1.0)¶
Solve the mass function for the companion mass \(m_2\).
Inverts \(m_2^3 \sin^3 i / (m_1 + m_2)^2 = f\) for \(m_2\), given the mass function
f, primary massm1, andsin i.sini = 1(the default) yields the minimum companion mass.The cubic has a single positive root, so it is solved by bisection – which is robust and
jax.jit/jax.vmapfriendly. The root is bracketed by[0, max(4 f_eff, (4 f_eff m_1^2)^{1/3})]with \(f_\mathrm{eff} = f / \sin^3 i\).- Parameters:
mass_function (
Real[Quantity[PhysicalType('mass')], '*batch']) – Binary or astrometric mass function (a mass).m1 (
Quantity[PhysicalType('mass')]) – Primary mass.sini (
Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch']) – Sine of the orbital inclination. Default 1 (edge-on -> minimum mass).
- Return type:
Real[Quantity[PhysicalType('mass')], '*batch']- Returns:
The companion mass \(m_2\), in solar masses.
Examples
>>> from unxt import Q, ustrip >>> from harv.kepler.masses import companion_mass_from_mass_function >>> m2 = companion_mass_from_mass_function(Q(0.25, "Msun"), Q(1.0, "Msun")) >>> round(float(ustrip("Msun", m2)), 3) 1.0
- harv.kepler.masses.semi_major_axis_physical(a_angular, parallax)¶
Physical semi-major axis (AU) from an angular size and parallax.
For an orbit at heliocentric distance \(d\), the angular semi-major axis is \(\alpha = a / d\) and the parallax is \(\varpi = 1\,\mathrm{AU} / d\), so \(a = (\alpha / \varpi)\; \mathrm{AU}\).
- Parameters:
a_angular (
Real[Quantity[PhysicalType('angle')], '*batch']) – Angular semi-major axis (an angle, e.g.mas).parallax (
Real[Quantity[PhysicalType('angle')], '*batch']) – Parallax (an angle, e.g.mas).
- Return type:
Real[Quantity[PhysicalType('length')], '*batch']- Returns:
The physical semi-major axis, in AU.
Examples
>>> from unxt import Q >>> from harv.kepler.masses import semi_major_axis_physical >>> a = semi_major_axis_physical(Q(10.0, "mas"), Q(5.0, "mas")) >>> float(a.value) 2.0
harv.kepler.nbody_system module¶
Keplerian orbit implementation with units support and JAX compatibility.
- class harv.kepler.nbody_system.AbstractNBodySystem¶
Bases:
ModuleAbstract base class for Keplerian N-body systems.
- property m_total: Real[Quantity[PhysicalType('mass')], '']¶
Total system mass.
- __init__()¶
- Return type:
None
- class harv.kepler.nbody_system.TwoBodySystem¶
Bases:
AbstractNBodySystemA system with a primary body and one companion.
Bodies are indexed as: - 0: Primary body - 1: Companion
- Parameters:
m_primary (
Real[Quantity[PhysicalType('mass')], ''])companion (
KeplerianBody)
-
m_primary:
Real[Quantity[PhysicalType('mass')], '']¶
-
companion:
KeplerianBody¶
- property m_total: Real[Quantity[PhysicalType('mass')], '']¶
Total system mass, derived from Kepler’s 3rd law.
The companion’s semi-major axis is defined relative to the system barycenter, so we can use Kepler’s 3rd law to derive the total mass from the companion’s orbital parameters and the primary mass: a_body = a_rel * m_primary / m_total a_rel^3 = G * m_total * P^2 / 4 pi^2 a_body^3 = G * m_primary^3 * P^2 / (4 pi^2 * m_total^2) (solve for m_total)
- property m_companion: Real[Quantity[PhysicalType('mass')], '']¶
Companion mass.
- position_barycentric(time, body_idx)¶
Get barycentric position of specified body at given time(s).
- Parameters:
time (
Real[Quantity[PhysicalType('time')], '*batch']) – Time(s) to evaluatebody_idx (
int) – Index of body (0=primary, 1=companion)
- Return type:
Real[Quantity[PhysicalType('length')], '3 *batch']- Returns:
3D position vector(s) of specified body relative to barycenter
- position_relative(time)¶
Position of companion relative to primary.
- Parameters:
time (
Real[Quantity[PhysicalType('time')], '*batch']) – Time(s) to evaluate- Return type:
Real[Quantity[PhysicalType('length')], '3 *batch']- Returns:
3D position vector(s) of companion relative to primary
- velocity_barycentric(time, body_idx)¶
Get barycentric velocity of specified body at given time(s).
- Parameters:
time (
Real[Quantity[PhysicalType('time')], '*batch']) – Time(s) to evaluatebody_idx (
int) – Index of body (0=primary, 1=companion)
- Return type:
Real[Quantity[PhysicalType({'speed', 'velocity'})], '3 *batch']- Returns:
3D velocity vector(s) of specified body relative to barycenter
- velocity_relative(time)¶
Velocity of companion relative to primary.
- Parameters:
time (
Real[Quantity[PhysicalType('time')], '*batch']) – Time(s) to evaluate- Return type:
Real[Quantity[PhysicalType({'speed', 'velocity'})], '3 *batch']- Returns:
3D velocity vector(s) of companion relative to primary
- __init__(m_primary, companion)¶
- Parameters:
m_primary (
Real[Quantity[PhysicalType('mass')], ''])companion (
KeplerianBody)
- Return type:
None
harv.kepler.orbits module¶
Helper functions for Keplerian orbits.
Implementations of core orbit computations used by harv.kepler, harv.likelihood,
and harv.simulate.
All functions accept Q objects (including dimensionless ones)
as well as plain JAX arrays and Python scalars.
- harv.kepler.orbits.mean_anomaly(dt, period)¶
Compute mean anomaly from elapsed time and period.
M = 2pi * dt / period, returned as aQwith angle units (radians).Examples
>>> from unxt import Q >>> from harv.kepler.orbits import mean_anomaly >>> M = mean_anomaly(Q(50.0, "day"), Q(100.0, "day")) >>> M.unit Unit("rad")
- Parameters:
dt (
Real[Quantity[PhysicalType('time')], '*batch'])period (
Real[Quantity[PhysicalType('time')], ''])
- Return type:
Real[Quantity[PhysicalType('angle')], '*batch']
- harv.kepler.orbits.rv_shape(sin_f, cos_f, eccentricity, arg_peri)¶
RV shape function: cos(omega + f) + e*cos(omega).
Returns the dimensionless RV amplitude factor for each observation.
arg_perimay be a plain float/array in radians or aQwith angle units;jnp.coshandles both via quax dispatch.eccentricitymay likewise be a dimensionlessQor a plain scalar.Examples
>>> from unxt import Q >>> from harv.kepler.orbits import true_anomaly_from_mean, rv_shape >>> sin_f, cos_f = true_anomaly_from_mean(Q(1.0, "rad"), 0.3) >>> shape = rv_shape(sin_f, cos_f, 0.3, Q(0.5, "rad"))
- Parameters:
sin_f (
Float[Array, '*batch']|Real[Quantity[PhysicalType('dimensionless')], '*batch'])cos_f (
Float[Array, '*batch']|Real[Quantity[PhysicalType('dimensionless')], '*batch'])eccentricity (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])arg_peri (
Real[Quantity[PhysicalType('angle')], '']|Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])
- Return type:
Float[Array, '*batch']|Real[Quantity[PhysicalType('dimensionless')], '*batch']
- harv.kepler.orbits.thiele_innes_ABFG(cos_arg_peri, sin_arg_peri, cos_lon_asc_node, sin_lon_asc_node, cos_i)¶
Compute unit Thiele-Innes constants (A, B, F, G).
Returns the constants with an implicit semi-major axis of 1. Multiply each by
ato recover the physical Thiele-Innes constants.Inputs are dimensionless and may be scalar or batched (plain scalars, JAX arrays, or dimensionless
Q); the computation broadcasts elementwise.The sky-plane orbital displacement uses the Thiele-Innes coordinates
X = (cos E - e)andY = sqrt(1-e^2) sin E, or equivalently in terms of true anomaly:X = r/a cos f,Y = r/a sin fwherer/a = (1-e^2)/(1+e cos f).In the Gaia LPC convention (Lindegren & Bastian, GAIA-C3-TN-LU-LL-061-08, Eq. 4), B and G project into the RA (
a) direction, while A and F project into the Dec (d) direction.See also Eq. A.1 of https://arxiv.org/abs/2206.05726.
Examples
>>> import quaxed.numpy as jnp >>> from unxt import Q >>> from harv.kepler.orbits import thiele_innes_ABFG >>> A, B, F, G = thiele_innes_ABFG( ... cos_arg_peri=jnp.cos(Q(0.5, "rad")), ... sin_arg_peri=jnp.sin(Q(0.5, "rad")), ... cos_lon_asc_node=jnp.cos(Q(1.0, "rad")), ... sin_lon_asc_node=jnp.sin(Q(1.0, "rad")), ... cos_i=jnp.cos(Q(0.3, "rad")), ... )
- Parameters:
cos_arg_peri (
Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch'])sin_arg_peri (
Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch'])cos_lon_asc_node (
Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch'])sin_lon_asc_node (
Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch'])cos_i (
Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch'])
- Return type:
tuple[Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch'],Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch'],Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch'],Float[Array, '*batch']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '*batch']]
- harv.kepler.orbits.true_anomaly_from_mean(M, eccentricity)¶
Solve Kepler’s equation: mean anomaly -> (sin f, cos f).
Wraps
jaxoplanet.core.kepler.kepler. The mean anomaly is stripped to radians internally.Examples
>>> from unxt import Q >>> from harv.kepler.orbits import true_anomaly_from_mean >>> sin_f, cos_f = true_anomaly_from_mean(Q(1.0, "rad"), 0.3)
- Parameters:
- Return type:
tuple[Float[Array, '*batch']|Real[Quantity[PhysicalType('dimensionless')], '*batch'],Float[Array, '*batch']|Real[Quantity[PhysicalType('dimensionless')], '*batch']]
- harv.kepler.orbits.campbell_from_thiele_innes(A, B, F, G)¶
Invert Thiele-Innes constants to Campbell orbital elements.
This follows Halbwachs, Pourbaix, et al. 2023 (see the appendix):
\[\begin{split}u &= \frac{1}{2}(A^2+B^2+F^2+G^2) \\ v &= A\,G - B\,F \\ a_0 &= \sqrt{u + \sqrt{\max(u^2 - v^2, 0)}} \\ \omega + \Omega &= \mathrm{atan2}(B - F, A + G) \\ \omega - \Omega &= \mathrm{atan2}(-B - F, A - G) \\ \cos i &= v / a_0^2\end{split}\]The returned
cos_iis signed: it preserves the sign of \(v = AG - BF\), which determines whether the orbit is prograde (cos_i > 0) or retrograde (cos_i < 0) under the LPC convention. This is required for the round-tripthiele_innes_from_campbell(*campbell_from_thiele_innes(...))to recover the original TI constants – a positive-only convention would silently flip the sky orbit for retrograde fits.
- harv.kepler.orbits.thiele_innes_from_campbell(semi_major_axis, arg_peri, lon_asc_node, cos_i)¶
Convert Campbell orbital elements to physical Thiele-Innes constants.
The forward direction of the change of variables inverted by
campbell_from_thiele_innes(). The unit Thiele-Innes constants fromthiele_innes_ABFG()are scaled by the semi-major axis:\[(A, B, F, G) = a_0 \cdot \mathrm{thiele\_innes\_ABFG}(\cos\omega, \sin\omega, \cos\Omega, \sin\Omega, \cos i)\]Examples
>>> from unxt import Q >>> from harv.kepler.orbits import thiele_innes_from_campbell >>> A, B, F, G = thiele_innes_from_campbell( ... Q(2.0, "mas"), Q(0.5, "rad"), Q(1.0, "rad"), Q(0.3, ""), ... ) >>> A.unit Unit("mas")
- Parameters:
semi_major_axis (
Real[Quantity[PhysicalType('angle')], '*batch'])arg_peri (
Real[Quantity[PhysicalType('angle')], '*batch'])lon_asc_node (
Real[Quantity[PhysicalType('angle')], '*batch'])cos_i (
Real[Quantity[PhysicalType('dimensionless')], '*batch'])
- Return type:
tuple[Real[Quantity[PhysicalType('angle')], '*batch'],Real[Quantity[PhysicalType('angle')], '*batch'],Real[Quantity[PhysicalType('angle')], '*batch'],Real[Quantity[PhysicalType('angle')], '*batch']]
- harv.kepler.orbits.ecc_omega_from_ecosw_esinw(ecosw, esinw)¶
Convert
(e cos omega, e sin omega)to eccentricity and arg. of periastron.The inverse of
ecosw_esinw_from_ecc_omega():\[\begin{split}e &= \sqrt{\mathrm{ecosw}^2 + \mathrm{esinw}^2} \\ \omega &= \mathrm{atan2}(\mathrm{esinw}, \mathrm{ecosw})\end{split}\]The returned
arg_perilies in(-pi, pi](the range ofatan2); useharv.samplers.Samples.wrap_angles()to wrap it into[0, 2*pi)if a prior requires that range.Examples
>>> from unxt import Q >>> from harv.kepler.orbits import ecc_omega_from_ecosw_esinw >>> ecc, arg_peri = ecc_omega_from_ecosw_esinw(Q(0.6, ""), Q(0.8, "")) >>> float(ecc.value) 1.0 >>> arg_peri.unit Unit("rad")
- Parameters:
ecosw (
Real[Quantity[PhysicalType('dimensionless')], '*batch'])esinw (
Real[Quantity[PhysicalType('dimensionless')], '*batch'])
- Return type:
tuple[Real[Quantity[PhysicalType('dimensionless')], '*batch'],Real[Quantity[PhysicalType('angle')], '*batch']]
- harv.kepler.orbits.ecosw_esinw_from_ecc_omega(eccentricity, arg_peri)¶
Convert eccentricity and arg. of periastron to
(e cos omega, e sin omega).The inverse of
ecc_omega_from_ecosw_esinw():\[\begin{split}\mathrm{ecosw} &= e \cos\omega \\ \mathrm{esinw} &= e \sin\omega\end{split}\]Examples
>>> from unxt import Q >>> from harv.kepler.orbits import ecosw_esinw_from_ecc_omega >>> ecosw, esinw = ecosw_esinw_from_ecc_omega(Q(0.5, ""), Q(0.0, "rad")) >>> float(ecosw.value) 0.5 >>> float(esinw.value) 0.0
- Parameters:
eccentricity (
Real[Quantity[PhysicalType('dimensionless')], '*batch'])arg_peri (
Real[Quantity[PhysicalType('angle')], '*batch'])
- Return type:
tuple[Real[Quantity[PhysicalType('dimensionless')], '*batch'],Real[Quantity[PhysicalType('dimensionless')], '*batch']]
- harv.kepler.orbits.compute_true_anomaly_components(time, period, eccentricity, t_peri)¶
Compute true anomaly at given times.
- Parameters:
time (
Real[Quantity[PhysicalType('time')], '*batch']) – Observation times, shape (n,)period (
Real[Quantity[PhysicalType('time')], '']) – Orbital periodeccentricity (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']) – Orbital eccentricityt_peri (
Real[Quantity[PhysicalType('time')], '']) – Time of pericenter passage
- Return type:
tuple[Float[Array, '*batch']|Real[Quantity[PhysicalType('dimensionless')], '*batch'],Float[Array, '*batch']|Real[Quantity[PhysicalType('dimensionless')], '*batch']]- Returns:
(sin_f, cos_f)— true anomaly components, each shape(n,).
Examples
>>> from unxt import Q >>> from harv.kepler.orbits import compute_true_anomaly_components >>> sin_f, cos_f = compute_true_anomaly_components( ... time=Q([0.0, 25.0, 50.0], "day"), ... period=Q(100.0, "day"), ... eccentricity=0.3, ... t_peri=Q(0.0, "day"), ... )
- harv.kepler.orbits.rv_at_times(times, period, eccentricity, t_peri, arg_peri, rv_semiamp, v_sys)¶
Compute the RV model: K*[cos(omega + f(t)) + e*cos(omega)] + v_0.
- Parameters:
times (
Real[Quantity[PhysicalType('time')], '*batch']) – Observation times.period (
Real[Quantity[PhysicalType('time')], '']) – Orbital period.eccentricity (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']) – Orbital eccentricity.t_peri (
Real[Quantity[PhysicalType('time')], '']) – Time of periastron passage. In the likelihood layer this is derived from the dimensionlessphase_periast_peri = phase_peri * period(see_solve_kepler).arg_peri (
Real[Quantity[PhysicalType('angle')], '']) – Argument of periastron omega.rv_semiamp (
Real[Quantity[PhysicalType({'speed', 'velocity'})], '']) – RV semi-amplitude.v_sys (
Real[Quantity[PhysicalType({'speed', 'velocity'})], '']) – Systemic velocity.
- Return type:
Real[Quantity[PhysicalType({'speed', 'velocity'})], '*batch']- Returns:
Radial velocities in the same unit as
rv_semiampandv_sys.
Examples
>>> from unxt import Q >>> from harv.kepler.orbits import rv_at_times >>> times = Q([0.0, 50.0, 100.0], "day") >>> rv = rv_at_times( ... times, ... period=Q(200.0, "day"), ... eccentricity=0.3, ... t_peri=Q(50.0, "day"), ... arg_peri=Q(1.2, "rad"), ... rv_semiamp=Q(8.0, "km/s"), ... v_sys=Q(-5.0, "km/s"), ... ) >>> rv.unit Unit("km / s")
- harv.kepler.orbits.astrometric_orbit_at_times(times, period, eccentricity, t_peri, arg_peri, cos_i, lon_asc_node, semi_major_axis)¶
Compute sky-plane astrometric orbit (Deltara, Deltadec) at given times.
Uses the Thiele-Innes parameterization following the Gaia local plane coordinate (LPC) convention (Lindegren & Bastian, GAIA-C3-TN-LU-LL-061-08, Eq. 4):
Deltara = (B*cos f + G*sin f) * a (RA / ``a`` direction) Deltadec = (A*cos f + F*sin f) * a (Dec / ``d`` direction)
where A, B, F, G are the unit Thiele-Innes constants and a is the photocentric semi-major axis.
- Parameters:
times (
Real[Quantity[PhysicalType('time')], '*batch']) – Observation times.period (
Real[Quantity[PhysicalType('time')], '']) – Orbital period.eccentricity (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']) – Orbital eccentricity.t_peri (
Real[Quantity[PhysicalType('time')], '']) – Time of periastron passage. In the likelihood layer this is derived from the dimensionlessphase_periast_peri = phase_peri * period(see_solve_kepler).arg_peri (
Real[Quantity[PhysicalType('angle')], '']) – Argument of periastron omega.cos_i (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']) – Cosine of orbital inclination.lon_asc_node (
Real[Quantity[PhysicalType('angle')], '']) – Longitude of the ascending node Omega.semi_major_axis (
Real[Quantity[PhysicalType('angle')], '']) – Photocentric semi-major axis.
- Return type:
tuple[Real[Quantity[PhysicalType('angle')], '*batch'],Real[Quantity[PhysicalType('angle')], '*batch']]- Returns:
(delta_ra, delta_dec)— sky-plane offsets in the same unit assemi_major_axis.
Examples
>>> from unxt import Q >>> from harv.kepler.orbits import astrometric_orbit_at_times >>> times = Q([0.0, 100.0, 200.0], "day") >>> dra, ddec = astrometric_orbit_at_times( ... times, ... period=Q(300.0, "day"), ... eccentricity=0.3, ... t_peri=Q(0.0, "day"), ... arg_peri=Q(1.2, "rad"), ... cos_i=0.5, ... lon_asc_node=Q(0.8, "rad"), ... semi_major_axis=Q(3.0, "mas"), ... ) >>> dra.unit Unit("mas")
harv.kepler.orientation module¶
Keplerian orbit implementation with units support and JAX compatibility.
- class harv.kepler.orientation.KeplerianOrientation¶
Bases:
ModuleOrientation of a Keplerian orbit in 3D space.
Stores the three Euler angles that define how the orbital plane is oriented relative to the observer’s reference frame: - Inclination (i): tilt of orbital plane from sky plane - Longitude of ascending node (Omega): where orbit crosses sky plane - Argument of pericenter (omega): orientation of ellipse within orbital plane
Angles are stored as sin/cos pairs for numerical stability.
Examples
>>> from unxt import Q >>> from harv.kepler.orientation import KeplerianOrientation >>> orient = KeplerianOrientation.from_angles( ... arg_peri=Q(0.5, "rad"), ... lon_asc_node=Q(1.0, "rad"), ... inclination=Q(0.3, "rad"), ... )
- Parameters:
sin_arg_peri (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])cos_arg_peri (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])sin_lon_asc_node (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])cos_lon_asc_node (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])sin_i (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])cos_i (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])
-
sin_arg_peri:
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']= 0.0¶
-
cos_arg_peri:
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']= 1.0¶
-
sin_lon_asc_node:
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']= 0.0¶
-
cos_lon_asc_node:
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']= 1.0¶
-
sin_i:
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']= 0.0¶
-
cos_i:
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], '']= 1.0¶
- classmethod from_angles(arg_peri=Quantity(Array(0., dtype=float32, weak_type=True), unit='rad'), lon_asc_node=Quantity(Array(0., dtype=float32, weak_type=True), unit='rad'), inclination=Quantity(Array(0., dtype=float32, weak_type=True), unit='rad'))¶
Construct from angle values.
- Parameters:
arg_peri (
Real[Quantity[PhysicalType('angle')], ''])lon_asc_node (
Real[Quantity[PhysicalType('angle')], ''])inclination (
Real[Quantity[PhysicalType('angle')], ''])
- Return type:
- classmethod from_thiele_innes(A, B, F, G)¶
Construct orientation from Thiele-Innes constants.
Inverts the Thiele-Innes constants to recover (omega, Omega, i, a). This loosely follows Appendix A of https://arxiv.org/abs/2206.05726 but my convention for angles is different. For this implementation:
0 < i < pi/2 0 < omega < 2pi 0 < Omega < 2pi
whereas the paper linked above assumes:
0 < i < pi 0 < omega < 2pi 0 < Omega < pi
- Return type:
tuple[KeplerianOrientation,Real[Quantity[PhysicalType('length')], '']|Real[Quantity[PhysicalType('angle')], '']]- Returns:
orientation – KeplerianOrientation object
semi_major_axis – Recovered semi-major axis
- Parameters:
A (
Real[Quantity[PhysicalType('length')], '']|Real[Quantity[PhysicalType('angle')], ''])B (
Real[Quantity[PhysicalType('length')], '']|Real[Quantity[PhysicalType('angle')], ''])F (
Real[Quantity[PhysicalType('length')], '']|Real[Quantity[PhysicalType('angle')], ''])G (
Real[Quantity[PhysicalType('length')], '']|Real[Quantity[PhysicalType('angle')], ''])
- property arg_peri: Real[Quantity[PhysicalType('angle')], '']¶
Argument of pericenter (omega).
- property lon_asc_node: Real[Quantity[PhysicalType('angle')], '']¶
Longitude of ascending node (Omega).
- property inclination: Real[Quantity[PhysicalType('angle')], '']¶
Inclination (i).
- property rotation_matrix: Array¶
Compute rotation matrix from orbital plane to observer frame.
Returns the rotation matrix R such that: r_observer_frame = R @ r_orbital_frame
The rotation is composed of three sequential rotations: 1. R_z(omega): Rotate by argument of pericenter, omega, in orbital plane 2. R_x(i): Rotate by inclination, i, to tilt orbital plane 3. R_z(Omega): Rotate by longitude of ascending node, Omega, on sky plane
The full rotation matrix is therefore: R = R_z(Omega) @ R_x(i) @ R_z(omega)
We build the matrix directly from the sin/cos pairs for numerical stability and speed, but using the notation below, it is equivalent to:
R1 = jnp.array([[c_w, -s_w, 0], [s_w, c_w, 0], [0, 0, 1]]) R2 = jnp.array([[1., 0, 0], [0, c_i, -s_i], [0, s_i, c_i]]) R3 = jnp.array([[c_W, -s_W, 0], [s_W, c_W, 0], [0, 0, 1]]) R = R3 @ R2 @ R1
Or, alternatively: omega = arg_peri.to_value(“rad”) Omega = lon_asc_node.to_value(“rad”) i = inclination.to_value(“rad”) R = Rotation.from_euler(‘ZXZ’, [Omega, i, omega]).as_matrix()
- thiele_innes_constants(semi_major_axis=None)¶
Compute Thiele-Innes constants (A, B, F, G).
These constants linearize the relationship between orbital position and sky-plane projection. See Appendix A of: https://arxiv.org/abs/2206.05726
- Parameters:
semi_major_axis (
Real[Quantity[PhysicalType('length')], '']|Real[Quantity[PhysicalType('angle')], '']|None) – Semi-major axis of the orbit- Returns:
The four Thiele-Innes constants.
- Return type:
A, B, F, G
- __init__(sin_arg_peri=0.0, cos_arg_peri=1.0, sin_lon_asc_node=0.0, cos_lon_asc_node=1.0, sin_i=0.0, cos_i=1.0)¶
- Parameters:
sin_arg_peri (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])cos_arg_peri (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])sin_lon_asc_node (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])cos_lon_asc_node (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])sin_i (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])cos_i (
Float[Array, '']|floating[Any] |float|int|Real[Quantity[PhysicalType('dimensionless')], ''])
- Return type:
None