from typing import Dict
import numpy as np
import pytest
try:
import astrora._core as core
from astrora._core import (
CartesianState,
Duration,
Epoch,
OrbitalElements,
constants,
)
ASTRORA_AVAILABLE = True
except ImportError:
ASTRORA_AVAILABLE = False
pytest.skip("astrora not available", allow_module_level=True)
pytest_plugins = ["tests.test_reference_data"]
@pytest.fixture(scope="session")
def numerical_tolerances() -> Dict[str, float]:
return {
"position_m": 1e-6, "velocity_m_s": 1e-9, "angle_rad": 1e-12, "energy_relative": 1e-10, "momentum_relative": 1e-10, "time_s": 1e-9, "mass_kg": 1e-12, "integration_position_m": 1e-3, "integration_velocity_m_s": 1e-6, "validation_position_m": 1.0, "validation_velocity_m_s": 1e-3, }
@pytest.fixture(scope="session")
def standard_epochs() -> Dict[str, Epoch]:
return {
"j2000": Epoch.j2000_epoch(), "gps_epoch": Epoch.from_midnight_utc(1980, 1, 6), "unix_epoch": Epoch.from_midnight_utc(1970, 1, 1), "year_2025": Epoch.from_midnight_utc(2025, 1, 1),
"year_2030": Epoch.from_midnight_utc(2030, 1, 1),
}
@pytest.fixture(scope="session")
def earth_params() -> Dict[str, float]:
return {
"gm": constants.GM_EARTH, "radius": constants.R_EARTH, "j2": constants.J2_EARTH,
"angular_velocity": 7.292115e-5, }
@pytest.fixture(scope="session")
def sun_params() -> Dict[str, float]:
return {
"gm": constants.GM_SUN, "radius": constants.R_SUN, }
@pytest.fixture(scope="session")
def moon_params() -> Dict[str, float]:
return {
"gm": constants.GM_MOON, "radius": constants.R_MOON, }
@pytest.fixture
def leo_state(earth_params: Dict[str, float]) -> CartesianState:
gm = earth_params["gm"]
r_earth = earth_params["radius"]
altitude = 400000.0 r = r_earth + altitude
v = np.sqrt(gm / r)
return CartesianState([r, 0.0, 0.0], [0.0, v, 0.0])
@pytest.fixture
def meo_state(earth_params: Dict[str, float]) -> CartesianState:
gm = earth_params["gm"]
r_earth = earth_params["radius"]
altitude = 20200000.0 r = r_earth + altitude
v = np.sqrt(gm / r)
return CartesianState([r, 0.0, 0.0], [0.0, v, 0.0])
@pytest.fixture
def geo_state(earth_params: Dict[str, float]) -> CartesianState:
gm = earth_params["gm"]
r = (gm / (earth_params["angular_velocity"] ** 2)) ** (1 / 3)
v = np.sqrt(gm / r)
return CartesianState([r, 0.0, 0.0], [0.0, v, 0.0])
@pytest.fixture
def gto_state(earth_params: Dict[str, float]) -> CartesianState:
gm = earth_params["gm"]
r_earth = earth_params["radius"]
r_perigee = r_earth + 300000.0 r_apogee = (gm / (earth_params["angular_velocity"] ** 2)) ** (1 / 3)
a = (r_perigee + r_apogee) / 2
e = (r_apogee - r_perigee) / (r_apogee + r_perigee)
v = np.sqrt(gm * (2 / r_perigee - 1 / a))
return CartesianState([r_perigee, 0.0, 0.0], [0.0, v, 0.0])
@pytest.fixture
def heo_state(earth_params: Dict[str, float]) -> CartesianState:
gm = earth_params["gm"]
r_earth = earth_params["radius"]
period = 43200.0 a = (gm * (period / (2 * np.pi)) ** 2) ** (1 / 3)
e = 0.74
i = np.radians(63.4)
r_perigee = a * (1 - e)
v_perigee = np.sqrt(gm * (2 / r_perigee - 1 / a))
return CartesianState(
[r_perigee, 0.0, 0.0], [0.0, v_perigee * np.cos(i), v_perigee * np.sin(i)]
)
@pytest.fixture
def sso_state(earth_params: Dict[str, float]) -> CartesianState:
gm = earth_params["gm"]
r_earth = earth_params["radius"]
altitude = 800000.0 r = r_earth + altitude
v = np.sqrt(gm / r)
i = np.radians(98.0)
return CartesianState([r, 0.0, 0.0], [0.0, v * np.cos(i), v * np.sin(i)])
@pytest.fixture
def lunar_transfer_state(earth_params: Dict[str, float]) -> CartesianState:
gm = earth_params["gm"]
r_earth = earth_params["radius"]
r_moon = 384400000.0
r_departure = r_earth + 200000.0 a = (r_departure + r_moon) / 2
v = np.sqrt(gm * (2 / r_departure - 1 / a))
return CartesianState([r_departure, 0.0, 0.0], [0.0, v, 0.0])
@pytest.fixture
def circular_equatorial_elements(earth_params: Dict[str, float]) -> OrbitalElements:
r_earth = earth_params["radius"]
a = r_earth + 500000.0
return OrbitalElements(a=a, e=0.0, i=0.0, raan=0.0, argp=0.0, nu=0.0)
@pytest.fixture
def eccentric_inclined_elements(earth_params: Dict[str, float]) -> OrbitalElements:
r_earth = earth_params["radius"]
a = r_earth + 10000000.0
return OrbitalElements(
a=a,
e=0.3,
i=np.radians(45.0),
raan=np.radians(30.0),
argp=np.radians(60.0),
nu=np.radians(90.0),
)
@pytest.fixture
def polar_orbit_elements(earth_params: Dict[str, float]) -> OrbitalElements:
r_earth = earth_params["radius"]
a = r_earth + 700000.0
return OrbitalElements(a=a, e=0.001, i=np.radians(90.0), raan=0.0, argp=0.0, nu=0.0)
@pytest.fixture
def test_true_anomalies() -> np.ndarray:
return np.linspace(0, 2 * np.pi, 100)
@pytest.fixture
def test_eccentricities() -> np.ndarray:
return np.array([0.0, 0.1, 0.3, 0.5, 0.7, 0.9, 0.95, 0.99])
@pytest.fixture
def test_inclinations() -> np.ndarray:
return np.radians([0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180])
@pytest.fixture
def short_propagation_times() -> np.ndarray:
return np.linspace(0, 6000, 50)
@pytest.fixture
def medium_propagation_times() -> np.ndarray:
return np.linspace(0, 86400, 100)
@pytest.fixture
def long_propagation_times() -> np.ndarray:
return np.linspace(0, 604800, 200)
def pytest_configure(config):
pass
def pytest_collection_modifyitems(config, items):
for item in items:
if "benchmark_" in str(item.fspath):
item.add_marker(pytest.mark.benchmark)
if "validation" in str(item.fspath):
item.add_marker(pytest.mark.validation)
if "integration" in str(item.fspath) or "_integration" in str(item.fspath):
item.add_marker(pytest.mark.integration)
if "regression" in str(item.fspath):
item.add_marker(pytest.mark.regression)
fspath_str = str(item.fspath)
if "propagat" in fspath_str:
item.add_marker(pytest.mark.propagation)
if "coordinate" in fspath_str or "frame" in fspath_str or "transform" in fspath_str:
item.add_marker(pytest.mark.coordinates)
if "maneuver" in fspath_str or "transfer" in fspath_str:
item.add_marker(pytest.mark.maneuvers)
if (
"perturbation" in fspath_str
or "_j2" in fspath_str
or "drag" in fspath_str
or "srp" in fspath_str
):
item.add_marker(pytest.mark.perturbations)
if "satellite" in fspath_str or "sgp4" in fspath_str or "tle" in fspath_str:
item.add_marker(pytest.mark.satellite)
if "plot" in fspath_str or "animation" in fspath_str:
item.add_marker(pytest.mark.plotting)
def pytest_report_header(config):
return [
f"astrora version: {core.__version__ if hasattr(core, '__version__') else 'unknown'}",
f"NumPy version: {np.__version__}",
]
@pytest.fixture
def benchmark_arrays():
return {
"tiny": 10,
"small": 100,
"medium": 1000,
"large": 10000,
"huge": 100000,
}