import numpy as np
import pytest
from astrora._core import (
compute_eclipse_state,
eclipse_duration,
solar_beta_angle,
solar_beta_angle_precise,
sun_synchronous_inclination,
)
class TestEclipseState:
def test_sunlit_satellite(self):
r_sat = np.array([6778.0, 0.0, 0.0]) r_sun = np.array([149.6e6, 0.0, 0.0])
state = compute_eclipse_state(r_sat, r_sun)
assert state == "Sunlit"
def test_umbra_satellite(self):
r_sat = np.array([-6778.0, 0.0, 0.0]) r_sun = np.array([149.6e6, 0.0, 0.0])
state = compute_eclipse_state(r_sat, r_sun)
assert state == "Umbra"
def test_eclipse_edge_case(self):
r_sat = np.array([-6778.0, 1000.0, 0.0]) r_sun = np.array([149.6e6, 0.0, 0.0])
state = compute_eclipse_state(r_sat, r_sun)
assert state in ["Umbra", "Penumbra", "Sunlit"]
def test_geo_satellite_sunlit(self):
r_sat = np.array([42164.0, 0.0, 0.0]) r_sun = np.array([149.6e6, 0.0, 0.0])
state = compute_eclipse_state(r_sat, r_sun)
assert state == "Sunlit"
class TestSolarBetaAngle:
def test_beta_zero_orbit_contains_sun(self):
i = 90.0 raan = 0.0 solar_lon = 0.0
beta = solar_beta_angle(i, raan, solar_lon)
assert abs(beta) < 0.1
def test_beta_90_perpendicular(self):
i = 90.0 raan = 90.0 solar_lon = 0.0
beta = solar_beta_angle(i, raan, solar_lon)
assert abs(abs(beta) - 90.0) < 0.1
def test_iss_like_orbit(self):
i = 51.6 raan = 90.0 solar_lon = 0.0
beta = solar_beta_angle(i, raan, solar_lon)
assert -90 <= beta <= 90
def test_equatorial_orbit(self):
i = 0.0 raan = 0.0 solar_lon = 0.0
beta = solar_beta_angle(i, raan, solar_lon)
assert abs(beta) < 0.1
def test_beta_angle_range(self):
for i in [0, 30, 60, 90]:
for raan in [0, 45, 90, 180]:
for solar_lon in [0, 90, 180, 270]:
beta = solar_beta_angle(float(i), float(raan), float(solar_lon))
assert -90 <= beta <= 90
class TestSolarBetaAnglePrecise:
def test_precise_vs_simple_similar(self):
i = 51.6
raan = 90.0
solar_lon = 0.0
beta_simple = solar_beta_angle(i, raan, solar_lon)
beta_precise = solar_beta_angle_precise(i, raan, solar_lon)
assert abs(beta_simple - beta_precise) < 10.0
def test_precise_range(self):
i = 98.0 raan = 45.0
solar_lon = 90.0
beta = solar_beta_angle_precise(i, raan, solar_lon)
assert -90 <= beta <= 90
class TestSunSynchronousInclination:
def test_600km_altitude(self):
altitude = 600.0 ecc = 0.0
inclination = sun_synchronous_inclination(altitude, ecc)
assert 97.0 <= inclination <= 99.0
def test_800km_altitude(self):
altitude = 800.0 ecc = 0.0
inclination = sun_synchronous_inclination(altitude, ecc)
assert 97.0 <= inclination <= 99.0
def test_leo_range(self):
for altitude in [400, 600, 800, 1000]:
inclination = sun_synchronous_inclination(float(altitude), 0.0)
assert 90.0 < inclination < 105.0
def test_invalid_altitude_raises_error(self):
altitude = 50000.0
with pytest.raises(ValueError):
sun_synchronous_inclination(altitude, 0.0)
class TestEclipseDuration:
def test_iss_maximum_eclipse(self):
altitude = 400.0 beta = 0.0
duration = eclipse_duration(altitude, beta)
assert 30.0 <= duration <= 40.0
def test_iss_no_eclipse(self):
altitude = 400.0 beta = 85.0
duration = eclipse_duration(altitude, beta)
assert duration < 5.0
def test_geo_eclipse(self):
altitude = 35786.0 beta = 0.0
duration = eclipse_duration(altitude, beta)
assert 50.0 <= duration <= 75.0
def test_eclipse_duration_decreases_with_altitude(self):
beta = 0.0
duration_400 = eclipse_duration(400.0, beta)
duration_800 = eclipse_duration(800.0, beta)
duration_1200 = eclipse_duration(1200.0, beta)
assert duration_400 > duration_800 > duration_1200
def test_beta_90_no_eclipse(self):
altitude = 600.0
beta = 90.0
duration = eclipse_duration(altitude, beta)
assert duration < 0.1
class TestIntegration:
def test_sun_sync_orbit_workflow(self):
altitude = 600.0
inclination = sun_synchronous_inclination(altitude, 0.0)
assert 97.0 <= inclination <= 99.0
raan = 0.0
solar_lon = 0.0
beta = solar_beta_angle(inclination, raan, solar_lon)
duration = eclipse_duration(altitude, beta)
assert 0.0 <= duration <= 50.0
def test_eclipse_state_consistency(self):
test_cases = [
([7000.0, 0.0, 0.0], [149.6e6, 0.0, 0.0], "Sunlit"),
([-7000.0, 0.0, 0.0], [149.6e6, 0.0, 0.0], "Umbra"),
]
for r_sat, r_sun, expected in test_cases:
state = compute_eclipse_state(np.array(r_sat), np.array(r_sun))
assert state == expected
class TestEdgeCases:
def test_very_low_altitude(self):
altitude = 100.0
inclination = sun_synchronous_inclination(altitude, 0.0)
assert 90.0 < inclination < 105.0
duration = eclipse_duration(altitude, 0.0)
assert duration > 0.0
def test_negative_beta_angle(self):
altitude = 600.0
beta = -45.0
duration = eclipse_duration(altitude, beta)
duration_positive = eclipse_duration(altitude, 45.0)
assert abs(duration - duration_positive) < 0.1
def test_raan_wrapping(self):
i = 51.6
raan1 = 90.0
raan2 = 450.0 solar_lon = 0.0
beta1 = solar_beta_angle(i, raan1, solar_lon)
beta2 = solar_beta_angle(i, raan2, solar_lon)
assert abs(beta1 - beta2) < 0.01
if __name__ == "__main__":
pytest.main([__file__, "-v"])