import math
import pytest
from astrora import _core
gravity_assist = _core.gravity_assist
periapsis_from_b_parameter = _core.periapsis_from_b_parameter
GM_JUPITER = _core.constants.GM_JUPITER
GM_EARTH = _core.constants.GM_EARTH
GM_VENUS = _core.constants.GM_VENUS
R_JUPITER = _core.constants.R_JUPITER
R_EARTH = _core.constants.R_MEAN_EARTH
class TestGravityAssistBasic:
def test_jupiter_flyby(self):
v_infinity = 5640.0 r_periapsis = 3.0 * R_JUPITER mu = GM_JUPITER
result = gravity_assist(v_infinity, r_periapsis, mu)
assert "v_infinity" in result
assert "r_periapsis" in result
assert "mu" in result
assert "eccentricity" in result
assert "delta" in result
assert "delta_v_magnitude" in result
assert "semi_major_axis" in result
assert "theta_infinity" in result
assert "b_parameter" in result
assert "specific_energy" in result
assert result["eccentricity"] > 1.0
assert 0.0 < result["delta"] < math.pi
assert result["delta_v_magnitude"] > 0.0
assert result["semi_major_axis"] < 0.0
assert result["specific_energy"] > 0.0
def test_close_flyby_high_deflection(self):
v_infinity = 20000.0 r_periapsis_close = 1.5 * R_JUPITER r_periapsis_far = 10.0 * R_JUPITER mu = GM_JUPITER
result_close = gravity_assist(v_infinity, r_periapsis_close, mu)
result_far = gravity_assist(v_infinity, r_periapsis_far, mu)
assert result_close["delta"] > result_far["delta"]
assert result_close["eccentricity"] < result_far["eccentricity"]
assert result_close["delta_v_magnitude"] > result_far["delta_v_magnitude"]
def test_high_speed_vs_low_speed(self):
r_periapsis = 3.0 * R_JUPITER
v_low = 3000.0 v_high = 15000.0 mu = GM_JUPITER
result_low = gravity_assist(v_low, r_periapsis, mu)
result_high = gravity_assist(v_high, r_periapsis, mu)
assert result_low["delta"] > result_high["delta"]
assert result_high["delta_v_magnitude"] > result_low["delta_v_magnitude"]
def test_venus_flyby(self):
v_infinity = 10000.0 r_venus = 6051.8e3 r_periapsis = 1.2 * r_venus mu = GM_VENUS
result = gravity_assist(v_infinity, r_periapsis, mu)
assert result["eccentricity"] > 1.0
assert 0.0 < result["delta"] < math.pi
class TestGravityAssistPhysics:
def test_energy_conservation(self):
v_infinity = 5640.0
r_periapsis = 3.0 * R_JUPITER
mu = GM_JUPITER
result = gravity_assist(v_infinity, r_periapsis, mu)
expected_energy = v_infinity**2 / 2.0
assert result["specific_energy"] == pytest.approx(expected_energy, rel=1e-10)
def test_eccentricity_formula(self):
v_infinity = 5640.0
r_periapsis = 3.0 * R_JUPITER
mu = GM_JUPITER
result = gravity_assist(v_infinity, r_periapsis, mu)
expected_e = 1.0 + (r_periapsis * v_infinity**2) / mu
assert result["eccentricity"] == pytest.approx(expected_e, rel=1e-10)
def test_turning_angle_formula(self):
v_infinity = 5640.0
r_periapsis = 3.0 * R_JUPITER
mu = GM_JUPITER
result = gravity_assist(v_infinity, r_periapsis, mu)
theta_inf = result["theta_infinity"]
expected_delta = 2.0 * theta_inf - math.pi
assert result["delta"] == pytest.approx(expected_delta, rel=1e-10)
def test_theta_infinity_from_eccentricity(self):
v_infinity = 5640.0
r_periapsis = 3.0 * R_JUPITER
mu = GM_JUPITER
result = gravity_assist(v_infinity, r_periapsis, mu)
e = result["eccentricity"]
expected_theta_inf = math.acos(-1.0 / e)
assert result["theta_infinity"] == pytest.approx(expected_theta_inf, rel=1e-10)
def test_semi_major_axis_formula(self):
v_infinity = 5640.0
r_periapsis = 3.0 * R_JUPITER
mu = GM_JUPITER
result = gravity_assist(v_infinity, r_periapsis, mu)
expected_sma = -mu / (v_infinity**2)
assert result["semi_major_axis"] == pytest.approx(expected_sma, rel=1e-10)
def test_b_parameter_formula(self):
v_infinity = 5640.0
r_periapsis = 3.0 * R_JUPITER
mu = GM_JUPITER
result = gravity_assist(v_infinity, r_periapsis, mu)
a = result["semi_major_axis"]
e = result["eccentricity"]
expected_b = abs(a) * math.sqrt(e**2 - 1.0)
assert result["b_parameter"] == pytest.approx(expected_b, rel=1e-10)
class TestBPlaneParameters:
def test_periapsis_from_b_parameter_roundtrip(self):
v_infinity = 5000.0
r_periapsis_original = 200000e3 mu = GM_JUPITER
result = gravity_assist(v_infinity, r_periapsis_original, mu)
b_parameter = result["b_parameter"]
r_periapsis_recovered = periapsis_from_b_parameter(v_infinity, b_parameter, mu)
assert r_periapsis_recovered == pytest.approx(r_periapsis_original, rel=1e-6)
def test_large_impact_parameter_distant_flyby(self):
v_infinity = 5000.0
mu = GM_JUPITER
b_small = 100000e3 b_large = 500000e3
r_p_small = periapsis_from_b_parameter(v_infinity, b_small, mu)
r_p_large = periapsis_from_b_parameter(v_infinity, b_large, mu)
assert r_p_large > r_p_small
class TestGravityAssistEdgeCases:
def test_negative_v_infinity_error(self):
with pytest.raises(Exception): gravity_assist(-1000.0, 3.0 * R_JUPITER, GM_JUPITER)
def test_negative_periapsis_error(self):
with pytest.raises(Exception):
gravity_assist(5000.0, -100000e3, GM_JUPITER)
def test_zero_v_infinity_error(self):
with pytest.raises(Exception):
gravity_assist(0.0, 3.0 * R_JUPITER, GM_JUPITER)
def test_very_distant_flyby(self):
v_infinity = 2000.0 r_periapsis = 100.0 * R_JUPITER mu = GM_JUPITER
result = gravity_assist(v_infinity, r_periapsis, mu)
assert result["eccentricity"] > 1.0
assert 0.0 < result["delta"] < math.pi
def test_grazing_flyby(self):
v_infinity = 25000.0 r_periapsis = 1.01 * R_JUPITER mu = GM_JUPITER
result = gravity_assist(v_infinity, r_periapsis, mu)
assert result["eccentricity"] > 1.0
assert 0.0 < result["delta"] < math.pi
class TestMissionValidation:
def test_voyager_2_jupiter_flyby_approximate(self):
v_infinity = 10000.0 r_periapsis = 10.0 * R_JUPITER
mu = GM_JUPITER
result = gravity_assist(v_infinity, r_periapsis, mu)
assert 0.1 < result["delta"] < math.pi
assert 1000.0 < result["delta_v_magnitude"] < 20000.0
def test_cassini_venus_flyby_approximate(self):
v_infinity = 8000.0 r_venus = 6051.8e3 r_periapsis = 1.5 * r_venus mu = GM_VENUS
result = gravity_assist(v_infinity, r_periapsis, mu)
assert result["eccentricity"] > 1.0
assert result["delta"] > 0.0
assert result["delta_v_magnitude"] > 0.0
class TestNumericalPrecision:
def test_delta_v_magnitude_formula(self):
v_infinity = 5640.0
r_periapsis = 3.0 * R_JUPITER
mu = GM_JUPITER
result = gravity_assist(v_infinity, r_periapsis, mu)
delta = result["delta"]
expected_dv = 2.0 * v_infinity * math.sin(delta / 2.0)
assert result["delta_v_magnitude"] == pytest.approx(expected_dv, rel=1e-10)
def test_consistency_across_range(self):
mu = GM_JUPITER
r_periapsis = 5.0 * R_JUPITER
for v_inf in [1000.0, 5000.0, 10000.0, 20000.0]:
result = gravity_assist(v_inf, r_periapsis, mu)
assert result["eccentricity"] > 1.0
assert 0.0 < result["delta"] < math.pi
assert result["delta_v_magnitude"] > 0.0
assert result["semi_major_axis"] < 0.0
assert result["specific_energy"] > 0.0
if __name__ == "__main__":
pytest.main([__file__, "-v"])