Browse Source
- Created tests/test_moon_orbits.cpp with 4 test cases: * Moon orbital stability around Earth * Galilean moons orbital stability around Jupiter * Titan orbital stability around Saturn * Combined solar system parent stability - Enhanced test_utilities.h/cpp: * Added min_time_days field to OrbitTracker * Added create_orbit_tracker_with_min_time() for short-period orbits - Fixed parent indexes in tests/configs/solar_system.toml: * Moon: parent 3 (Mars) → 2 (Earth) * Io/Europa/Ganymede/Callisto: parent 5 (Saturn) → 4 (Jupiter) * Titan: parent 6 (Uranus) → 5 (Saturn) - Updated semi_major_axis for moons to actual orbital distances Tests reveal need for hierarchical orbit physics implementation. Net change: +368 linesmain
5 changed files with 425 additions and 3 deletions
@ -0,0 +1,141 @@ |
|||||||
|
# Solar System Configuration |
||||||
|
|
||||||
|
[[bodies]] |
||||||
|
name = "Sun" |
||||||
|
mass = 1.989e30 # kg |
||||||
|
radius = 6.96e8 # m |
||||||
|
position = { x = 0.0, y = 0.0, z = 0.0 } |
||||||
|
parent_index = -1 |
||||||
|
color = { r = 1.0, g = 1.0, b = 0.0 } |
||||||
|
eccentricity = 0.0 |
||||||
|
semi_major_axis = 0.0 |
||||||
|
|
||||||
|
[[bodies]] |
||||||
|
name = "Venus" |
||||||
|
mass = 4.867e24 |
||||||
|
radius = 6.0518e6 |
||||||
|
position = { x = 1.082e11, y = 0.0, z = 0.0 } |
||||||
|
parent_index = 0 |
||||||
|
color = { r = 0.9, g = 0.7, b = 0.3 } |
||||||
|
eccentricity = 0.0 |
||||||
|
semi_major_axis = 1.082e11 |
||||||
|
|
||||||
|
[[bodies]] |
||||||
|
name = "Earth" |
||||||
|
mass = 5.972e24 |
||||||
|
radius = 6.371e6 |
||||||
|
position = { x = 1.496e11, y = 0.0, z = 0.0 } |
||||||
|
parent_index = 0 |
||||||
|
color = { r = 0.0, g = 0.5, b = 1.0 } |
||||||
|
eccentricity = 0.0 |
||||||
|
semi_major_axis = 1.496e11 |
||||||
|
|
||||||
|
[[bodies]] |
||||||
|
name = "Mars" |
||||||
|
mass = 6.39e23 |
||||||
|
radius = 3.3895e6 |
||||||
|
position = { x = 2.279e11, y = 0.0, z = 0.0 } |
||||||
|
parent_index = 0 |
||||||
|
color = { r = 0.8, g = 0.3, b = 0.1 } |
||||||
|
eccentricity = 0.0 |
||||||
|
semi_major_axis = 2.279e11 |
||||||
|
|
||||||
|
[[bodies]] |
||||||
|
name = "Jupiter" |
||||||
|
mass = 1.898e27 |
||||||
|
radius = 6.9911e7 |
||||||
|
position = { x = 7.785e11, y = 0.0, z = 0.0 } |
||||||
|
parent_index = 0 |
||||||
|
color = { r = 0.9, g = 0.7, b = 0.5 } |
||||||
|
eccentricity = 0.0 |
||||||
|
semi_major_axis = 7.785e11 |
||||||
|
|
||||||
|
[[bodies]] |
||||||
|
name = "Saturn" |
||||||
|
mass = 5.683e26 |
||||||
|
radius = 5.8232e7 |
||||||
|
position = { x = 1.434e12, y = 0.0, z = 0.0 } |
||||||
|
parent_index = 0 |
||||||
|
color = { r = 0.9, g = 0.8, b = 0.6 } |
||||||
|
eccentricity = 0.0 |
||||||
|
semi_major_axis = 1.434e12 |
||||||
|
|
||||||
|
[[bodies]] |
||||||
|
name = "Uranus" |
||||||
|
mass = 8.681e25 |
||||||
|
radius = 2.5362e7 |
||||||
|
position = { x = 2.871e12, y = 0.0, z = 0.0 } |
||||||
|
parent_index = 0 |
||||||
|
color = { r = 0.5, g = 0.8, b = 0.9 } |
||||||
|
eccentricity = 0.0 |
||||||
|
semi_major_axis = 2.871e12 |
||||||
|
|
||||||
|
[[bodies]] |
||||||
|
name = "Neptune" |
||||||
|
mass = 1.024e26 |
||||||
|
radius = 2.4622e7 |
||||||
|
position = { x = 4.495e12, y = 0.0, z = 0.0 } |
||||||
|
parent_index = 0 |
||||||
|
color = { r = 0.2, g = 0.4, b = 0.9 } |
||||||
|
eccentricity = 0.0 |
||||||
|
semi_major_axis = 4.495e12 |
||||||
|
|
||||||
|
[[bodies]] |
||||||
|
name = "Moon" |
||||||
|
mass = 7.342e22 |
||||||
|
radius = 1.737e6 |
||||||
|
position = { x = 1.500e11, y = 0.0, z = 0.0 } |
||||||
|
parent_index = 2 |
||||||
|
color = { r = 0.7, g = 0.7, b = 0.7 } |
||||||
|
eccentricity = 0.0 |
||||||
|
semi_major_axis = 3.844e8 |
||||||
|
|
||||||
|
[[bodies]] |
||||||
|
name = "Io" |
||||||
|
mass = 8.93e22 |
||||||
|
radius = 1.822e6 |
||||||
|
position = { x = 8.207e11, y = 0.0, z = 0.0 } |
||||||
|
parent_index = 4 |
||||||
|
color = { r = 0.9, g = 0.9, b = 0.3 } |
||||||
|
eccentricity = 0.0 |
||||||
|
semi_major_axis = 4.217e8 |
||||||
|
|
||||||
|
[[bodies]] |
||||||
|
name = "Europa" |
||||||
|
mass = 4.80e22 |
||||||
|
radius = 1.561e6 |
||||||
|
position = { x = 8.456e11, y = 0.0, z = 0.0 } |
||||||
|
parent_index = 4 |
||||||
|
color = { r = 0.8, g = 0.8, b = 0.7 } |
||||||
|
eccentricity = 0.0 |
||||||
|
semi_major_axis = 6.709e8 |
||||||
|
|
||||||
|
[[bodies]] |
||||||
|
name = "Ganymede" |
||||||
|
mass = 1.48e23 |
||||||
|
radius = 2.634e6 |
||||||
|
position = { x = 8.853e11, y = 0.0, z = 0.0 } |
||||||
|
parent_index = 4 |
||||||
|
color = { r = 0.6, g = 0.6, b = 0.5 } |
||||||
|
eccentricity = 0.0 |
||||||
|
semi_major_axis = 1.070e9 |
||||||
|
|
||||||
|
[[bodies]] |
||||||
|
name = "Callisto" |
||||||
|
mass = 1.08e23 |
||||||
|
radius = 2.410e6 |
||||||
|
position = { x = 9.670e11, y = 0.0, z = 0.0 } |
||||||
|
parent_index = 4 |
||||||
|
color = { r = 0.5, g = 0.5, b = 0.4 } |
||||||
|
eccentricity = 0.0 |
||||||
|
semi_major_axis = 1.883e9 |
||||||
|
|
||||||
|
[[bodies]] |
||||||
|
name = "Titan" |
||||||
|
mass = 1.345e23 |
||||||
|
radius = 2.575e6 |
||||||
|
position = { x = 1.556e12, y = 0.0, z = 0.0 } |
||||||
|
parent_index = 5 |
||||||
|
color = { r = 0.9, g = 0.6, b = 0.3 } |
||||||
|
eccentricity = 0.0 |
||||||
|
semi_major_axis = 1.222e9 |
||||||
@ -0,0 +1,264 @@ |
|||||||
|
#include <catch2/catch_test_macros.hpp> |
||||||
|
#include "../src/physics.h" |
||||||
|
#include "../src/simulation.h" |
||||||
|
#include "../src/config_loader.h" |
||||||
|
#include "../src/test_utilities.h" |
||||||
|
#include <cmath> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
TEST_CASE("Moon orbital stability around Earth", "[moon][earth]") { |
||||||
|
const double TIME_STEP = 60.0; |
||||||
|
const double EXPECTED_PERIOD_DAYS = 27.3; |
||||||
|
const double SECONDS_PER_DAY = 86400.0; |
||||||
|
const double MAX_SIMULATION_DAYS = 35.0; |
||||||
|
const double MOON_DISTANCE_FROM_EARTH = 384400000.0; |
||||||
|
|
||||||
|
SimulationState* sim = create_simulation(20, TIME_STEP); |
||||||
|
|
||||||
|
REQUIRE(load_system_config(sim, "tests/configs/solar_system.toml")); |
||||||
|
|
||||||
|
const int EARTH_INDEX = 2; |
||||||
|
const int MOON_INDEX = 8; |
||||||
|
|
||||||
|
double max_time = MAX_SIMULATION_DAYS * SECONDS_PER_DAY; |
||||||
|
OrbitTracker* tracker = create_orbit_tracker_with_min_time(MOON_INDEX, 5.0); |
||||||
|
|
||||||
|
int initial_parent = sim->bodies[MOON_INDEX].parent_index; |
||||||
|
Vec3 initial_pos_relative_to_earth = vec3_sub( |
||||||
|
sim->bodies[MOON_INDEX].position, |
||||||
|
sim->bodies[EARTH_INDEX].position |
||||||
|
); |
||||||
|
double initial_distance = vec3_magnitude(initial_pos_relative_to_earth); |
||||||
|
|
||||||
|
INFO("Moon initial distance from Earth: " << initial_distance << " m"); |
||||||
|
INFO("Expected distance: " << MOON_DISTANCE_FROM_EARTH << " m"); |
||||||
|
|
||||||
|
while (sim->time < max_time && !tracker->orbit_completed) { |
||||||
|
update_simulation(sim); |
||||||
|
|
||||||
|
int current_parent = sim->bodies[MOON_INDEX].parent_index; |
||||||
|
|
||||||
|
REQUIRE(current_parent == initial_parent); |
||||||
|
|
||||||
|
if (current_parent != EARTH_INDEX) { |
||||||
|
INFO("Moon parent changed from " << initial_parent << " to " << current_parent); |
||||||
|
REQUIRE(current_parent == EARTH_INDEX); |
||||||
|
} |
||||||
|
|
||||||
|
Vec3 current_pos_relative_to_earth = vec3_sub( |
||||||
|
sim->bodies[MOON_INDEX].position, |
||||||
|
sim->bodies[EARTH_INDEX].position |
||||||
|
); |
||||||
|
double current_distance = vec3_magnitude(current_pos_relative_to_earth); |
||||||
|
|
||||||
|
double distance_drift = fabs(current_distance - initial_distance); |
||||||
|
double drift_percentage = (distance_drift / initial_distance) * 100.0; |
||||||
|
|
||||||
|
REQUIRE(drift_percentage < 20.0); |
||||||
|
|
||||||
|
update_orbit_tracker(tracker, &sim->bodies[MOON_INDEX], &sim->bodies[EARTH_INDEX], sim->time); |
||||||
|
} |
||||||
|
|
||||||
|
REQUIRE(tracker->orbit_completed); |
||||||
|
|
||||||
|
double measured_period_days = tracker->time_at_completion / SECONDS_PER_DAY; |
||||||
|
double period_error_days = fabs(measured_period_days - EXPECTED_PERIOD_DAYS); |
||||||
|
|
||||||
|
INFO("Expected Moon period: " << EXPECTED_PERIOD_DAYS << " days"); |
||||||
|
INFO("Measured Moon period: " << measured_period_days << " days"); |
||||||
|
INFO("Period error: " << period_error_days << " days"); |
||||||
|
|
||||||
|
REQUIRE(period_error_days < 3.0); |
||||||
|
|
||||||
|
Vec3 final_pos_relative_to_earth = vec3_sub( |
||||||
|
sim->bodies[MOON_INDEX].position, |
||||||
|
sim->bodies[EARTH_INDEX].position |
||||||
|
); |
||||||
|
double final_distance = vec3_magnitude(final_pos_relative_to_earth); |
||||||
|
|
||||||
|
double final_drift_percentage = (fabs(final_distance - initial_distance) / initial_distance) * 100.0; |
||||||
|
|
||||||
|
INFO("Final distance from Earth: " << final_distance << " m"); |
||||||
|
INFO("Initial distance from Earth: " << initial_distance << " m"); |
||||||
|
INFO("Final drift: " << final_drift_percentage << "%"); |
||||||
|
|
||||||
|
REQUIRE(final_drift_percentage < 10.0); |
||||||
|
|
||||||
|
destroy_orbit_tracker(tracker); |
||||||
|
destroy_simulation(sim); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE("Galilean moons orbital stability around Jupiter", "[moon][jupiter]") { |
||||||
|
const double TIME_STEP = 60.0; |
||||||
|
const double SECONDS_PER_DAY = 86400.0; |
||||||
|
const double MAX_SIMULATION_DAYS = 20.0; |
||||||
|
|
||||||
|
const double IO_PERIOD_DAYS = 1.77; |
||||||
|
const double EUROPA_PERIOD_DAYS = 3.55; |
||||||
|
const double GANYMEDE_PERIOD_DAYS = 7.15; |
||||||
|
const double CALLISTO_PERIOD_DAYS = 16.69; |
||||||
|
|
||||||
|
SimulationState* sim = create_simulation(20, TIME_STEP); |
||||||
|
|
||||||
|
REQUIRE(load_system_config(sim, "tests/configs/solar_system.toml")); |
||||||
|
|
||||||
|
const int JUPITER_INDEX = 4; |
||||||
|
const int IO_INDEX = 9; |
||||||
|
const int EUROPA_INDEX = 10; |
||||||
|
const int GANYMEDE_INDEX = 11; |
||||||
|
const int CALLISTO_INDEX = 12; |
||||||
|
|
||||||
|
OrbitTracker* io_tracker = create_orbit_tracker_with_min_time(IO_INDEX, 1.0); |
||||||
|
OrbitTracker* europa_tracker = create_orbit_tracker_with_min_time(EUROPA_INDEX, 2.0); |
||||||
|
OrbitTracker* ganymede_tracker = create_orbit_tracker_with_min_time(GANYMEDE_INDEX, 5.0); |
||||||
|
OrbitTracker* callisto_tracker = create_orbit_tracker_with_min_time(CALLISTO_INDEX, 10.0); |
||||||
|
|
||||||
|
double max_time = MAX_SIMULATION_DAYS * SECONDS_PER_DAY; |
||||||
|
|
||||||
|
while (sim->time < max_time) { |
||||||
|
update_simulation(sim); |
||||||
|
|
||||||
|
REQUIRE(sim->bodies[IO_INDEX].parent_index == JUPITER_INDEX); |
||||||
|
REQUIRE(sim->bodies[EUROPA_INDEX].parent_index == JUPITER_INDEX); |
||||||
|
REQUIRE(sim->bodies[GANYMEDE_INDEX].parent_index == JUPITER_INDEX); |
||||||
|
REQUIRE(sim->bodies[CALLISTO_INDEX].parent_index == JUPITER_INDEX); |
||||||
|
|
||||||
|
update_orbit_tracker(io_tracker, &sim->bodies[IO_INDEX], &sim->bodies[JUPITER_INDEX], sim->time); |
||||||
|
update_orbit_tracker(europa_tracker, &sim->bodies[EUROPA_INDEX], &sim->bodies[JUPITER_INDEX], sim->time); |
||||||
|
update_orbit_tracker(ganymede_tracker, &sim->bodies[GANYMEDE_INDEX], &sim->bodies[JUPITER_INDEX], sim->time); |
||||||
|
update_orbit_tracker(callisto_tracker, &sim->bodies[CALLISTO_INDEX], &sim->bodies[JUPITER_INDEX], sim->time); |
||||||
|
} |
||||||
|
|
||||||
|
REQUIRE(io_tracker->orbit_completed); |
||||||
|
REQUIRE(europa_tracker->orbit_completed); |
||||||
|
REQUIRE(ganymede_tracker->orbit_completed); |
||||||
|
REQUIRE(callisto_tracker->orbit_completed); |
||||||
|
|
||||||
|
double io_period_days = io_tracker->time_at_completion / SECONDS_PER_DAY; |
||||||
|
double europa_period_days = europa_tracker->time_at_completion / SECONDS_PER_DAY; |
||||||
|
double ganymede_period_days = ganymede_tracker->time_at_completion / SECONDS_PER_DAY; |
||||||
|
double callisto_period_days = callisto_tracker->time_at_completion / SECONDS_PER_DAY; |
||||||
|
|
||||||
|
INFO("Io period: " << io_period_days << " days (expected: " << IO_PERIOD_DAYS << ")"); |
||||||
|
INFO("Europa period: " << europa_period_days << " days (expected: " << EUROPA_PERIOD_DAYS << ")"); |
||||||
|
INFO("Ganymede period: " << ganymede_period_days << " days (expected: " << GANYMEDE_PERIOD_DAYS << ")"); |
||||||
|
INFO("Callisto period: " << callisto_period_days << " days (expected: " << CALLISTO_PERIOD_DAYS << ")"); |
||||||
|
|
||||||
|
REQUIRE(fabs(io_period_days - IO_PERIOD_DAYS) < 0.5); |
||||||
|
REQUIRE(fabs(europa_period_days - EUROPA_PERIOD_DAYS) < 1.0); |
||||||
|
REQUIRE(fabs(ganymede_period_days - GANYMEDE_PERIOD_DAYS) < 2.0); |
||||||
|
REQUIRE(fabs(callisto_period_days - CALLISTO_PERIOD_DAYS) < 4.0); |
||||||
|
|
||||||
|
destroy_orbit_tracker(io_tracker); |
||||||
|
destroy_orbit_tracker(europa_tracker); |
||||||
|
destroy_orbit_tracker(ganymede_tracker); |
||||||
|
destroy_orbit_tracker(callisto_tracker); |
||||||
|
destroy_simulation(sim); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE("Titan orbital stability around Saturn", "[moon][saturn]") { |
||||||
|
const double TIME_STEP = 60.0; |
||||||
|
const double EXPECTED_PERIOD_DAYS = 15.95; |
||||||
|
const double SECONDS_PER_DAY = 86400.0; |
||||||
|
const double MAX_SIMULATION_DAYS = 25.0; |
||||||
|
|
||||||
|
SimulationState* sim = create_simulation(20, TIME_STEP); |
||||||
|
|
||||||
|
REQUIRE(load_system_config(sim, "tests/configs/solar_system.toml")); |
||||||
|
|
||||||
|
const int SATURN_INDEX = 5; |
||||||
|
const int TITAN_INDEX = 13; |
||||||
|
|
||||||
|
OrbitTracker* tracker = create_orbit_tracker_with_min_time(TITAN_INDEX, 10.0); |
||||||
|
|
||||||
|
Vec3 initial_pos_relative_to_saturn = vec3_sub( |
||||||
|
sim->bodies[TITAN_INDEX].position, |
||||||
|
sim->bodies[SATURN_INDEX].position |
||||||
|
); |
||||||
|
double initial_distance = vec3_magnitude(initial_pos_relative_to_saturn); |
||||||
|
|
||||||
|
double max_time = MAX_SIMULATION_DAYS * SECONDS_PER_DAY; |
||||||
|
|
||||||
|
while (sim->time < max_time && !tracker->orbit_completed) { |
||||||
|
update_simulation(sim); |
||||||
|
|
||||||
|
REQUIRE(sim->bodies[TITAN_INDEX].parent_index == SATURN_INDEX); |
||||||
|
|
||||||
|
Vec3 current_pos_relative_to_saturn = vec3_sub( |
||||||
|
sim->bodies[TITAN_INDEX].position, |
||||||
|
sim->bodies[SATURN_INDEX].position |
||||||
|
); |
||||||
|
double current_distance = vec3_magnitude(current_pos_relative_to_saturn); |
||||||
|
|
||||||
|
double drift_percentage = (fabs(current_distance - initial_distance) / initial_distance) * 100.0; |
||||||
|
REQUIRE(drift_percentage < 10.0); |
||||||
|
|
||||||
|
update_orbit_tracker(tracker, &sim->bodies[TITAN_INDEX], &sim->bodies[SATURN_INDEX], sim->time); |
||||||
|
} |
||||||
|
|
||||||
|
REQUIRE(tracker->orbit_completed); |
||||||
|
|
||||||
|
double measured_period_days = tracker->time_at_completion / SECONDS_PER_DAY; |
||||||
|
double period_error_days = fabs(measured_period_days - EXPECTED_PERIOD_DAYS); |
||||||
|
|
||||||
|
INFO("Expected Titan period: " << EXPECTED_PERIOD_DAYS << " days"); |
||||||
|
INFO("Measured Titan period: " << measured_period_days << " days"); |
||||||
|
INFO("Period error: " << period_error_days << " days"); |
||||||
|
|
||||||
|
REQUIRE(period_error_days < 3.0); |
||||||
|
|
||||||
|
destroy_orbit_tracker(tracker); |
||||||
|
destroy_simulation(sim); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_CASE("Combined solar system with all moons - parent stability", "[moon][integration]") { |
||||||
|
const double TIME_STEP = 60.0; |
||||||
|
const double SECONDS_PER_DAY = 86400.0; |
||||||
|
const double MAX_SIMULATION_DAYS = 60.0; |
||||||
|
|
||||||
|
SimulationState* sim = create_simulation(20, TIME_STEP); |
||||||
|
|
||||||
|
REQUIRE(load_system_config(sim, "tests/configs/solar_system.toml")); |
||||||
|
|
||||||
|
struct ParentChange { |
||||||
|
double time_days; |
||||||
|
int body_index; |
||||||
|
int old_parent; |
||||||
|
int new_parent; |
||||||
|
}; |
||||||
|
|
||||||
|
std::vector<ParentChange> parent_changes; |
||||||
|
|
||||||
|
int initial_parents[14]; |
||||||
|
for (int i = 0; i < 14; i++) { |
||||||
|
initial_parents[i] = sim->bodies[i].parent_index; |
||||||
|
} |
||||||
|
|
||||||
|
double max_time = MAX_SIMULATION_DAYS * SECONDS_PER_DAY; |
||||||
|
|
||||||
|
while (sim->time < max_time) { |
||||||
|
update_simulation(sim); |
||||||
|
|
||||||
|
for (int i = 0; i < sim->body_count; i++) { |
||||||
|
if (sim->bodies[i].parent_index != initial_parents[i]) { |
||||||
|
ParentChange change; |
||||||
|
change.time_days = sim->time / SECONDS_PER_DAY; |
||||||
|
change.body_index = i; |
||||||
|
change.old_parent = initial_parents[i]; |
||||||
|
change.new_parent = sim->bodies[i].parent_index; |
||||||
|
parent_changes.push_back(change); |
||||||
|
initial_parents[i] = sim->bodies[i].parent_index; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
INFO("Total parent changes detected: " << parent_changes.size()); |
||||||
|
for (const auto& change : parent_changes) { |
||||||
|
INFO("Body " << sim->bodies[change.body_index].name |
||||||
|
<< " (index " << change.body_index << ") changed parent " |
||||||
|
<< "from " << change.old_parent << " to " << change.new_parent |
||||||
|
<< " at day " << change.time_days); |
||||||
|
} |
||||||
|
|
||||||
|
destroy_simulation(sim); |
||||||
|
} |
||||||
Loading…
Reference in new issue