Source code for adam_core.coordinates.cometary
from __future__ import annotations
import numpy as np
import quivr as qv
from ..time import Timestamp
from . import cartesian, keplerian, spherical
from .covariances import CoordinateCovariances, transform_covariances_jacobian
from .origin import Origin
__all__ = [
"CometaryCoordinates",
]
[docs]
class CometaryCoordinates(qv.Table):
# TODO: Time of periapse passage could perhaps be represented
# as a Times object. We could then modify self.values to only
# grab the MJD column. That said, we would want to force it
# the time scale to be in TDB..
q = qv.Float64Column()
e = qv.Float64Column()
i = qv.Float64Column()
raan = qv.Float64Column()
ap = qv.Float64Column()
tp = qv.Float64Column()
time = Timestamp.as_column(nullable=True)
covariance = CoordinateCovariances.as_column(nullable=True)
origin = Origin.as_column()
frame = qv.StringAttribute(default="unspecified")
@property
def values(self) -> np.ndarray:
return np.array(self.table.select(["q", "e", "i", "raan", "ap", "tp"]))
@property
def sigma_q(self) -> np.ndarray:
"""
1-sigma uncertainty in periapsis distance.
"""
return self.covariance.sigmas[:, 0]
@property
def sigma_e(self) -> np.ndarray:
"""
1-sigma uncertainty in eccentricity.
"""
return self.covariance.sigmas[:, 1]
@property
def sigma_i(self) -> np.ndarray:
"""
1-sigma uncertainty in inclination.
"""
return self.covariance.sigmas[:, 2]
@property
def sigma_raan(self):
"""
1-sigma uncertainty in right ascension of the ascending node.
"""
return self.covariance.sigmas[:, 3]
@property
def sigma_ap(self) -> np.ndarray:
"""
1-sigma uncertainty in argument of periapsis.
"""
return self.covariance.sigmas[:, 4]
@property
def sigma_tp(self) -> np.ndarray:
"""
1-sigma uncertainty in time of periapse passage.
"""
return self.covariance.sigmas[:, 5]
@property
def a(self) -> np.ndarray:
"""
Semi-major axis.
"""
from ..dynamics.kepler import calc_semi_major_axis
return np.array(calc_semi_major_axis(self.q.to_numpy(), self.e.to_numpy()))
@a.setter
def a(self, value):
err = (
"Cannot set semi-major axis (a) as it is"
" derived from the periapsis distance (q) and eccentricity (e)."
)
raise ValueError(err)
@a.deleter
def a(self):
err = (
"Cannot delete semi-major axis (a) as it is"
" derived from the periapsis distance (q) and eccentricity (e)."
)
raise ValueError(err)
@property
def Q(self) -> np.ndarray:
"""
Apoapsis distance.
"""
from ..dynamics.kepler import calc_apoapsis_distance
return np.array(calc_apoapsis_distance(self.a, self.e.to_numpy()))
@Q.setter
def Q(self, value):
err = (
"Cannot set apoapsis distance (Q) as it is"
" derived from the semi-major axis (a) and eccentricity (e)."
)
raise ValueError(err)
@Q.deleter
def Q(self):
err = (
"Cannot delete apoapsis distance (Q) as it is"
" derived from the semi-major axis (a) and eccentricity (e)."
)
raise ValueError(err)
@property
def p(self) -> np.ndarray:
"""
Semi-latus rectum.
"""
from ..dynamics.kepler import calc_semi_latus_rectum
return np.array(calc_semi_latus_rectum(self.a, self.e.to_numpy()))
@p.setter
def p(self, value):
err = (
"Cannot set semi-latus rectum (p) as it is"
" derived from the semi-major axis (a) and eccentricity (e)."
)
raise ValueError(err)
@p.deleter
def p(self):
err = (
"Cannot delete semi-latus rectum (p) as it is"
" derived from the semi-major axis (a) and eccentricity (e)."
)
raise ValueError(err)
@property
def P(self) -> np.ndarray:
"""
Period.
"""
from ..dynamics.kepler import calc_period
return np.array(calc_period(self.a, self.origin.mu()))
@P.setter
def P(self, value):
err = (
"Cannot set period (P) as it is"
" derived from the semi-major axis (a) and gravitational parameter (mu)."
)
raise ValueError(err)
@P.deleter
def P(self):
err = (
"Cannot delete period (P) as it is"
" derived from the semi-major axis (a) and gravitational parameter (mu)."
)
raise ValueError(err)
@property
def n(self):
"""
Mean motion in degrees.
"""
from ..dynamics.kepler import calc_mean_motion
return np.degrees(np.array(calc_mean_motion(self.a, self.origin.mu())))
@n.setter
def n(self, value):
err = (
"Cannot set mean motion (n) as it is"
" derived from semi-major axis (a) and gravitational parameter (mu)."
)
raise ValueError(err)
@n.deleter
def n(self):
err = (
"Cannot delete mean motion (n) as it is"
" derived from semi-major axis (a) and gravitational parameter (mu)."
)
raise ValueError(err)
[docs]
def to_cartesian(self) -> cartesian.CartesianCoordinates:
from .transform import _cometary_to_cartesian, cometary_to_cartesian
if self.time is None:
err = (
"To convert Cometary coordinates to Cartesian coordinates, the times\n"
"at which the Coordinates coordinates are defined is required to give\n"
"the time of periapsis passage context."
)
raise ValueError(err)
# Extract gravitational parameter from origin
mu = self.origin.mu()
coords_cartesian = cometary_to_cartesian(
self.values,
t0=self.time.to_numpy(),
mu=mu,
max_iter=100,
tol=1e-15,
)
coords_cartesian = np.array(coords_cartesian)
if not self.covariance.is_all_nan():
cometary_covariances = self.covariance.to_matrix()
covariances_cartesian = transform_covariances_jacobian(
self.values,
cometary_covariances,
_cometary_to_cartesian,
in_axes=(0, 0, 0, None, None),
out_axes=0,
t0=self.time.to_numpy(),
mu=mu,
max_iter=100,
tol=1e-15,
)
else:
covariances_cartesian = np.empty(
(len(coords_cartesian), 6, 6), dtype=np.float64
)
covariances_cartesian.fill(np.nan)
covariances_cartesian = CoordinateCovariances.from_matrix(covariances_cartesian)
coords = cartesian.CartesianCoordinates.from_kwargs(
x=coords_cartesian[:, 0],
y=coords_cartesian[:, 1],
z=coords_cartesian[:, 2],
vx=coords_cartesian[:, 3],
vy=coords_cartesian[:, 4],
vz=coords_cartesian[:, 5],
time=self.time,
covariance=covariances_cartesian,
origin=self.origin,
frame=self.frame,
)
return coords
[docs]
@classmethod
def from_cartesian(
cls, cartesian: cartesian.CartesianCoordinates
) -> CometaryCoordinates:
from .transform import _cartesian_to_cometary, cartesian_to_cometary
if cartesian.time is None:
err = (
"To convert Cometary coordinates to Cartesian coordinates, the times\n"
"at which the Cartesian coordinates are defined is required to calculate\n"
"the time of periapsis passage."
)
raise ValueError(err)
# Extract gravitational parameter from origin
mu = cartesian.origin.mu()
coords_cometary = cartesian_to_cometary(
cartesian.values,
cartesian.time.to_numpy(),
mu=mu,
)
coords_cometary = np.array(coords_cometary)
if not cartesian.covariance.is_all_nan():
cartesian_covariances = cartesian.covariance.to_matrix()
covariances_cometary = transform_covariances_jacobian(
cartesian.values,
cartesian_covariances,
_cartesian_to_cometary,
in_axes=(0, 0, 0),
out_axes=0,
t0=cartesian.time.to_numpy(),
mu=mu,
)
else:
covariances_cometary = np.empty(
(len(coords_cometary), 6, 6), dtype=np.float64
)
covariances_cometary.fill(np.nan)
covariances_cometary = CoordinateCovariances.from_matrix(covariances_cometary)
coords = cls.from_kwargs(
q=coords_cometary[:, 0],
e=coords_cometary[:, 1],
i=coords_cometary[:, 2],
raan=coords_cometary[:, 3],
ap=coords_cometary[:, 4],
tp=coords_cometary[:, 5],
time=cartesian.time,
covariance=covariances_cometary,
origin=cartesian.origin,
frame=cartesian.frame,
)
return coords
[docs]
def to_keplerian(self) -> keplerian.KeplerianCoordinates:
return keplerian.KeplerianCoordinates.from_cartesian(self.to_cartesian())
[docs]
@classmethod
def from_keplerian(
cls, keplerian_coordinates: keplerian.KeplerianCoordinates
) -> CometaryCoordinates:
return cls.from_cartesian(keplerian_coordinates.to_cartesian())
[docs]
def to_spherical(self) -> spherical.SphericalCoordinates:
return spherical.SphericalCoordinates.from_cartesian(self.to_cartesian())
[docs]
@classmethod
def from_spherical(
cls, spherical_coordinates: spherical.SphericalCoordinates
) -> CometaryCoordinates:
return cls.from_cartesian(spherical_coordinates.to_cartesian())