Browse Source

Add moon orbit test suite and fix solar system config

- 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 lines
main
cinnaboot 6 months ago
parent
commit
c53c598ffe
  1. 4
      Makefile
  2. 17
      src/test_utilities.cpp
  3. 2
      src/test_utilities.h
  4. 141
      tests/configs/solar_system.toml
  5. 264
      tests/test_moon_orbits.cpp

4
Makefile

@ -81,6 +81,8 @@ test-build:
-c $(TEST_DIR)/test_orbital_period.cpp -o build/test_orbital_period.o -c $(TEST_DIR)/test_orbital_period.cpp -o build/test_orbital_period.o
$(CXX) $(CXXFLAGS) -I/usr/include/catch2 \ $(CXX) $(CXXFLAGS) -I/usr/include/catch2 \
-c $(TEST_DIR)/test_comet_orbit.cpp -o build/test_comet_orbit.o -c $(TEST_DIR)/test_comet_orbit.cpp -o build/test_comet_orbit.o
$(CXX) $(CXXFLAGS) -I/usr/include/catch2 \
-c $(TEST_DIR)/test_moon_orbits.cpp -o build/test_moon_orbits.o
$(CXX) $(CXXFLAGS) \ $(CXX) $(CXXFLAGS) \
-c $(SRC_DIR)/test_utilities.cpp -o build/test_utilities.o -c $(SRC_DIR)/test_utilities.cpp -o build/test_utilities.o
$(CXX) $(CXXFLAGS) \ $(CXX) $(CXXFLAGS) \
@ -91,7 +93,7 @@ test-build:
-c $(SRC_DIR)/config_loader.cpp -o build/test_config_loader.o -c $(SRC_DIR)/config_loader.cpp -o build/test_config_loader.o
$(CC) -Wall -Wextra -g -ggdb3 -std=c99 -I./src -I./ext/tomlc17/src \ $(CC) -Wall -Wextra -g -ggdb3 -std=c99 -I./src -I./ext/tomlc17/src \
-c $(SRC_DIR)/../ext/tomlc17/src/tomlc17.c -o build/test_tomlc17.o -c $(SRC_DIR)/../ext/tomlc17/src/tomlc17.c -o build/test_tomlc17.o
$(CXX) build/test_main.o build/test_integration.o build/test_energy.o build/test_orbital_period.o build/test_comet_orbit.o build/test_utilities.o build/test_physics.o build/test_simulation.o build/test_config_loader.o build/test_tomlc17.o \ $(CXX) build/test_main.o build/test_integration.o build/test_energy.o build/test_orbital_period.o build/test_comet_orbit.o build/test_moon_orbits.o build/test_utilities.o build/test_physics.o build/test_simulation.o build/test_config_loader.o build/test_tomlc17.o \
-o $(TEST_TARGET) -lCatch2Main -lCatch2 -lm -o $(TEST_TARGET) -lCatch2Main -lCatch2 -lm
# Run automated test suite # Run automated test suite

17
src/test_utilities.cpp

@ -53,6 +53,19 @@ OrbitTracker* create_orbit_tracker(int body_index) {
tracker->quadrant_transitions = 0; tracker->quadrant_transitions = 0;
tracker->orbit_completed = false; tracker->orbit_completed = false;
tracker->time_at_completion = 0.0; tracker->time_at_completion = 0.0;
tracker->min_time_days = 100.0;
return tracker;
}
OrbitTracker* create_orbit_tracker_with_min_time(int body_index, double min_time_days) {
OrbitTracker* tracker = (OrbitTracker*)malloc(sizeof(OrbitTracker));
tracker->body_index = body_index;
tracker->initial_angle = 0.0;
tracker->previous_angle = 0.0;
tracker->quadrant_transitions = 0;
tracker->orbit_completed = false;
tracker->time_at_completion = 0.0;
tracker->min_time_days = min_time_days;
return tracker; return tracker;
} }
@ -93,10 +106,10 @@ void update_orbit_tracker(OrbitTracker* tracker, CelestialBody* body, CelestialB
if (total_rotation > M_PI) total_rotation -= 2.0 * M_PI; if (total_rotation > M_PI) total_rotation -= 2.0 * M_PI;
const double SECONDS_PER_DAY = 86400.0; const double SECONDS_PER_DAY = 86400.0;
const double MIN_ORBIT_TIME = 100.0 * SECONDS_PER_DAY; double min_time_seconds = tracker->min_time_days * SECONDS_PER_DAY;
if (tracker->quadrant_transitions >= 2 && if (tracker->quadrant_transitions >= 2 &&
current_time > MIN_ORBIT_TIME && current_time > min_time_seconds &&
fabs(total_rotation) < 0.05) { fabs(total_rotation) < 0.05) {
tracker->orbit_completed = true; tracker->orbit_completed = true;
tracker->time_at_completion = current_time; tracker->time_at_completion = current_time;

2
src/test_utilities.h

@ -20,6 +20,7 @@ struct OrbitTracker {
bool orbit_completed; bool orbit_completed;
double time_at_completion; double time_at_completion;
int body_index; int body_index;
double min_time_days;
}; };
double calculate_kinetic_energy(CelestialBody* body); double calculate_kinetic_energy(CelestialBody* body);
@ -28,6 +29,7 @@ double calculate_system_total_energy(SimulationState* sim);
OrbitalMetrics calculate_orbital_metrics(CelestialBody* body, CelestialBody* parent); OrbitalMetrics calculate_orbital_metrics(CelestialBody* body, CelestialBody* parent);
OrbitTracker* create_orbit_tracker(int body_index); OrbitTracker* create_orbit_tracker(int body_index);
OrbitTracker* create_orbit_tracker_with_min_time(int body_index, double min_time_days);
void reset_orbit_tracker(OrbitTracker* tracker); void reset_orbit_tracker(OrbitTracker* tracker);
void update_orbit_tracker(OrbitTracker* tracker, CelestialBody* body, CelestialBody* parent, double current_time); void update_orbit_tracker(OrbitTracker* tracker, CelestialBody* body, CelestialBody* parent, double current_time);
void destroy_orbit_tracker(OrbitTracker* tracker); void destroy_orbit_tracker(OrbitTracker* tracker);

141
tests/configs/solar_system.toml

@ -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

264
tests/test_moon_orbits.cpp

@ -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…
Cancel
Save