Skip to main content
Glama
test_integrations_orbits.py11 kB
"""Tests for orbital mechanics module.""" import numpy as np import pytest from aerospace_mcp.integrations.orbits import ( OrbitElements, StateVector, calculate_ground_track, elements_to_state_vector, hohmann_transfer, lambert_solver_simple, orbital_rendezvous_planning, propagate_orbit_j2, state_vector_to_elements, ) class TestOrbitElements: """Test OrbitElements data class.""" def test_valid_orbit_elements(self): """Test creating valid orbit elements.""" elements = OrbitElements( semi_major_axis_m=7000000.0, eccentricity=0.001, inclination_deg=45.0, raan_deg=0.0, arg_periapsis_deg=0.0, true_anomaly_deg=0.0, epoch_utc="2000-01-01T12:00:00", ) assert elements.semi_major_axis_m == 7000000.0 assert elements.eccentricity == 0.001 assert elements.inclination_deg == 45.0 class TestStateVector: """Test StateVector data class.""" def test_valid_state_vector(self): """Test creating valid state vector.""" state = StateVector( position_m=[7000000.0, 0.0, 0.0], velocity_ms=[0.0, 7546.0, 0.0], epoch_utc="2000-01-01T12:00:00", ) assert state.position_m[0] == 7000000.0 assert state.velocity_ms[1] == 7546.0 assert state.epoch_utc == "2000-01-01T12:00:00" class TestElementsToStateVector: """Test orbital elements to state vector conversion.""" def test_circular_equatorial_orbit(self): """Test conversion for circular equatorial orbit.""" elements = OrbitElements( semi_major_axis_m=7000000.0, eccentricity=0.0, inclination_deg=0.0, raan_deg=0.0, arg_periapsis_deg=0.0, true_anomaly_deg=0.0, epoch_utc="2000-01-01T12:00:00", ) state = elements_to_state_vector(elements) # For circular orbit at periapsis, position should be (a, 0, 0) assert abs(state.position_m[0] - 7000000.0) < 1.0 assert abs(state.position_m[1]) < 1.0 assert abs(state.position_m[2]) < 1.0 # Velocity should be (0, sqrt(mu/a), 0) expected_v = np.sqrt(3.986004418e14 / 7000000.0) assert abs(state.velocity_ms[0]) < 1.0 assert abs(state.velocity_ms[1] - expected_v) < 1.0 assert abs(state.velocity_ms[2]) < 1.0 def test_inclined_orbit(self): """Test conversion for inclined orbit.""" elements = OrbitElements( semi_major_axis_m=7000000.0, eccentricity=0.0, inclination_deg=90.0, # Polar orbit raan_deg=0.0, arg_periapsis_deg=0.0, true_anomaly_deg=0.0, epoch_utc="2000-01-01T12:00:00", ) state = elements_to_state_vector(elements) # For polar orbit, should have some z-component in velocity assert abs(state.position_m[0] - 7000000.0) < 1.0 assert abs(state.position_m[1]) < 1.0 # Z position should be zero at equator crossing assert abs(state.position_m[2]) < 1.0 class TestStateVectorToElements: """Test state vector to orbital elements conversion.""" def test_round_trip_conversion(self): """Test that conversion is reversible.""" original_elements = OrbitElements( semi_major_axis_m=7000000.0, eccentricity=0.1, inclination_deg=45.0, raan_deg=30.0, arg_periapsis_deg=60.0, true_anomaly_deg=90.0, epoch_utc="2000-01-01T12:00:00", ) # Convert to state vector and back state = elements_to_state_vector(original_elements) recovered_elements = state_vector_to_elements(state) # Check that we get back approximately the same elements assert ( abs( recovered_elements.semi_major_axis_m - original_elements.semi_major_axis_m ) < 1000.0 ) assert ( abs(recovered_elements.eccentricity - original_elements.eccentricity) < 0.001 ) assert ( abs(recovered_elements.inclination_deg - original_elements.inclination_deg) < 0.1 ) def test_circular_orbit_conversion(self): """Test conversion of circular orbit.""" # Create circular orbit state vector manually r = 7000000.0 mu = 3.986004418e14 v = np.sqrt(mu / r) state = StateVector( position_m=[r, 0.0, 0.0], velocity_ms=[0.0, v, 0.0], epoch_utc="2000-01-01T12:00:00", ) elements = state_vector_to_elements(state) assert abs(elements.semi_major_axis_m - r) < 1000.0 assert elements.eccentricity < 0.01 # Should be nearly circular assert abs(elements.inclination_deg) < 0.1 # Should be equatorial class TestOrbitPropagation: """Test orbit propagation with J2 perturbations.""" def test_propagate_circular_orbit(self): """Test propagation of circular orbit.""" initial_state = StateVector( position_m=[7000000.0, 0.0, 0.0], velocity_ms=[0.0, 7546.0, 0.0], epoch_utc="2000-01-01T12:00:00", ) # Propagate for one hour states = propagate_orbit_j2(initial_state, 3600.0, 300.0) assert len(states) > 1 # States should be different from initial final_state = states[-1] assert abs(final_state.position_m[0] - initial_state.position_m[0]) > 1000.0 assert abs(final_state.position_m[1] - initial_state.position_m[1]) > 1000.0 def test_propagate_short_duration(self): """Test propagation for short duration.""" initial_state = StateVector( position_m=[7000000.0, 0.0, 0.0], velocity_ms=[0.0, 7546.0, 0.0], epoch_utc="2000-01-01T12:00:00", ) # Propagate for 1 hour with 1-minute steps states = propagate_orbit_j2(initial_state, 3600.0, 60.0) assert len(states) == 61 # 60 minutes + initial state # States should be different from initial final_state = states[-1] assert abs(final_state.position_m[0] - initial_state.position_m[0]) > 1000.0 assert abs(final_state.position_m[1] - initial_state.position_m[1]) > 1000.0 class TestGroundTrack: """Test ground track calculation.""" def test_equatorial_orbit_ground_track(self): """Test ground track for equatorial orbit.""" # Create state vectors for equatorial orbit states = [] r = 7000000.0 mu = 3.986004418e14 v = np.sqrt(mu / r) # Create several points along orbit for i in range(5): angle = i * np.pi / 4 # 0 to 180 degrees x = r * np.cos(angle) y = r * np.sin(angle) vx = -v * np.sin(angle) vy = v * np.cos(angle) states.append( StateVector( position_m=[x, y, 0.0], velocity_ms=[vx, vy, 0.0], epoch_utc="2000-01-01T12:00:00", ) ) ground_track = calculate_ground_track(states, 600.0) assert len(ground_track) == len(states) # For equatorial orbit, all latitudes should be near zero for point in ground_track: assert abs(point.latitude_deg) < 5.0 # Relaxed tolerance assert -180.0 <= point.longitude_deg <= 180.0 class TestHohmannTransfer: """Test Hohmann transfer calculations.""" def test_earth_to_higher_orbit(self): """Test Hohmann transfer to higher orbit.""" r1 = 7000000.0 # Initial orbit radius (m) r2 = 10000000.0 # Final orbit radius (m) transfer = hohmann_transfer(r1, r2) assert transfer["delta_v1_ms"] > 0 # Should require positive delta-v assert transfer["delta_v2_ms"] > 0 # Should require positive delta-v assert transfer["total_delta_v_ms"] > 0 assert transfer["transfer_time_s"] > 0 # Semi-major axis of transfer ellipse should be (r1 + r2) / 2 expected_a = (r1 + r2) / 2 assert abs(transfer["transfer_semi_major_axis_m"] - expected_a) < 1000.0 def test_higher_to_lower_orbit(self): """Test Hohmann transfer to lower orbit.""" r1 = 10000000.0 # Initial orbit radius (m) r2 = 7000000.0 # Final orbit radius (m) transfer = hohmann_transfer(r1, r2) # For descending transfer, first burn should be negative (retrograde) assert transfer["delta_v1_ms"] < 0 assert transfer["delta_v2_ms"] < 0 assert transfer["total_delta_v_ms"] > 0 # Total magnitude assert transfer["transfer_time_s"] > 0 class TestOrbitalRendezvous: """Test orbital rendezvous planning.""" def test_coplanar_rendezvous(self): """Test rendezvous between coplanar orbits.""" chaser = OrbitElements( semi_major_axis_m=7000000.0, eccentricity=0.0, inclination_deg=45.0, raan_deg=0.0, arg_periapsis_deg=0.0, true_anomaly_deg=0.0, epoch_utc="2000-01-01T12:00:00", ) target = OrbitElements( semi_major_axis_m=7200000.0, # Slightly higher orbit eccentricity=0.0, inclination_deg=45.0, # Same inclination raan_deg=0.0, arg_periapsis_deg=0.0, true_anomaly_deg=90.0, # Different position epoch_utc="2000-01-01T12:00:00", ) plan = orbital_rendezvous_planning(chaser, target) assert plan["total_delta_v_ms"] > 0 assert plan["time_to_rendezvous_s"] > 0 assert len(plan["maneuvers"]) > 0 # Should have at least one maneuver first_maneuver = plan["maneuvers"][0] assert abs(first_maneuver["delta_v_ms"]) > 0 class TestLambertProblem: """Test Lambert problem solver.""" def test_lambert_half_orbit(self): """Test Lambert problem for half orbit.""" r1 = [7000000.0, 0.0, 0.0] r2 = [-7000000.0, 0.0, 0.0] # Half orbital period for circular orbit mu = 3.986004418e14 period = 2 * np.pi * np.sqrt((7000000.0) ** 3 / mu) tof = period / 2 try: result = lambert_solver_simple(r1, r2, tof) # Should return a dictionary with velocity vectors assert "v1_vec_ms" in result or "initial_velocity_ms" in result # Lambert solver uses simplified approach, might not handle all cases except (NotImplementedError, ValueError): # Lambert solver uses simplified approach, might not handle all cases pytest.skip("Lambert solver uses simplified approach") if __name__ == "__main__": pytest.main([__file__])

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cheesejaguar/aerospace-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server