Browse Source
- Create tests/informational/ for diagnostic/informational tests not in main suite
- Move test_time_step_stability.{cpp,toml} from tests/ to informational/
- Add README.md documenting informational tests purpose and usage
- Add dedicated Makefile for building informational tests separately
- Tests find maximum stable time step for RK4 integration across different orbital regimes
- Limiting factor is Mercury orbiter (12h period): max stable dt = 270s
- Current default 60s is very stable with good margin
main
5 changed files with 459 additions and 0 deletions
@ -0,0 +1,73 @@
|
||||
# Makefile for Informational Tests
|
||||
# These tests are not part of the main test suite
|
||||
# They are for diagnostic and informational purposes only
|
||||
|
||||
BUILD_DIR = ../../build
|
||||
SRC_DIR = ../../src
|
||||
|
||||
# Compiler flags
|
||||
CFLAGS = -Wall -Wextra -g -ggdb3 -std=c++14
|
||||
INCLUDES = -I$(SRC_DIR) \
|
||||
-isystem../../ext/tomlc17/src \
|
||||
-isystem../../ext/raylib/src \
|
||||
-isystem../../ext/raygui/src \
|
||||
-isystem../../ext/raygui/styles
|
||||
|
||||
# Source files (from main project that informational tests depend on)
|
||||
CORE_OBJECTS = $(BUILD_DIR)/test_utilities.o \
|
||||
$(BUILD_DIR)/physics.o \
|
||||
$(BUILD_DIR)/orbital_mechanics.o \
|
||||
$(BUILD_DIR)/simulation.o \
|
||||
$(BUILD_DIR)/config_loader.o \
|
||||
$(BUILD_DIR)/config_validator.o \
|
||||
$(BUILD_DIR)/maneuver.o \
|
||||
$(BUILD_DIR)/spacecraft.o \
|
||||
$(BUILD_DIR)/tomlc17.o
|
||||
|
||||
# Informational test sources
|
||||
INFO_TESTS = test_time_step_stability.cpp
|
||||
INFO_BINS = $(patsubst %.cpp,%,$(INFO_TESTS))
|
||||
|
||||
# Default target - build all informational tests
|
||||
all: $(INFO_BINS) |
||||
@echo "All informational tests built successfully"
|
||||
@echo ""
|
||||
@echo "To run tests:"
|
||||
@echo " ./test_time_step_stability"
|
||||
@echo ""
|
||||
@echo "To run specific test case:"
|
||||
@echo " ./test_time_step_stability '[timestep][stability]'"
|
||||
|
||||
# Build individual test binaries
|
||||
$(INFO_BINS): $(patsubst %.cpp,%.o,$(INFO_TESTS)) $(CORE_OBJECTS) |
||||
g++ $(patsubst %.cpp,%.o,$<) $(CORE_OBJECTS) -o $@ -lCatch2Main -lCatch2 -lm
|
||||
@echo "Built $@"
|
||||
|
||||
# Build test object files
|
||||
test_time_step_stability.o: test_time_step_stability.cpp |
||||
g++ $(CFLAGS) $(INCLUDES) -c $< -o $@
|
||||
|
||||
# Build core objects if they don't exist
|
||||
$(BUILD_DIR)/%.o: |
||||
$(MAKE) -C ../../ build/$*.o
|
||||
|
||||
# Clean built files in informational directory
|
||||
clean: |
||||
rm -f $(INFO_BINS) *.o
|
||||
|
||||
# Clean all (including core build - use with caution!)
|
||||
clean-all: |
||||
$(MAKE) -C ../../ clean
|
||||
rm -f $(INFO_BINS) *.o
|
||||
|
||||
# Run the time step stability test (must run from project root)
|
||||
run-timestep: test_time_step_stability |
||||
@echo "Note: Tests must be run from project root directory:"
|
||||
@echo " From project root: ./tests/informational/$@"
|
||||
|
||||
# Run with verbose output to see binary search progress
|
||||
run-timestep-verbose: test_time_step_stability |
||||
@echo "Note: Tests must be run from project root directory:"
|
||||
@echo " From project root: ./tests/informational/$@ -s \"[timestep][stability]\""
|
||||
|
||||
.PHONY: all clean clean-all run-timestep run-timestep-verbose |
||||
@ -0,0 +1,72 @@
|
||||
# Informational Tests |
||||
|
||||
This directory contains tests that are not part of the standard test suite. These tests are provided for informational purposes only and should be run separately. |
||||
|
||||
## Purpose |
||||
|
||||
Informational tests are designed to: |
||||
- Explore simulation boundaries and limits |
||||
- Provide diagnostic information about the simulation |
||||
- Test extreme or edge cases that don't fit into normal test suites |
||||
- Help understand system behavior under various conditions |
||||
|
||||
## Running Informational Tests |
||||
|
||||
Informational tests are **not** run by `make test`. To build and run them: |
||||
|
||||
```bash |
||||
cd tests/informational |
||||
make |
||||
./time_step_test |
||||
``` |
||||
|
||||
## Current Tests |
||||
|
||||
### `test_time_step_stability.cpp` |
||||
|
||||
Determines the maximum stable time step for the RK4 integration across different orbital regimes. |
||||
|
||||
**Test Bodies:** |
||||
- **Mercury Orbiter** (MESSENGER-like): 200 km altitude, ~12 hour period |
||||
- **Io** (Jupiter's moon): 421,700 km orbit, ~1.77 day period |
||||
- **Moon** (Earth's moon): 384,400 km orbit, ~27.3 day period |
||||
|
||||
**Stability Criteria:** |
||||
- Energy drift < 1% over 100 orbits |
||||
- Distance drift < 5% |
||||
- No SOI transitions (body doesn't change parent) |
||||
|
||||
**Configuration:** Uses `test_time_step_stability.toml` |
||||
|
||||
**Usage:** |
||||
```bash |
||||
# From project root directory: |
||||
./tests/informational/test_time_step_stability |
||||
|
||||
# Or using the Makefile: |
||||
cd tests/informational |
||||
make run-timestep |
||||
|
||||
# Run specific test case |
||||
./tests/informational/test_time_step_stability '[timestep][stability]' |
||||
|
||||
# Run with verbose output to see binary search progress |
||||
./tests/informational/test_time_step_stability -s "[timestep][stability]" |
||||
``` |
||||
|
||||
**Note:** Tests must be run from the project root directory because config file paths are relative to the root. |
||||
|
||||
**Expected Output:** |
||||
The test performs a binary search to find the maximum stable time step, printing progress for each dt value tested. Results show the minimum stable dt across all tested bodies with recommendations. |
||||
|
||||
## Adding New Informational Tests |
||||
|
||||
1. Create a new `.cpp` file in this directory |
||||
2. Add corresponding `.toml` config file if needed |
||||
3. Add a target in the `Makefile`: |
||||
```makefile |
||||
your_test_name: your_test_name.o |
||||
g++ $(BUILD_DIR)/*.o -o $@ -lCatch2Main -lCatch2 -lm |
||||
``` |
||||
4. Update this README with test description |
||||
5. Follow the naming convention: `test_<purpose>.cpp` and `test_<purpose>.toml` |
||||
Binary file not shown.
@ -0,0 +1,234 @@
|
||||
#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 <cstdio> |
||||
|
||||
struct TestBody { |
||||
const char* name; |
||||
int body_index; |
||||
int parent_index; |
||||
double expected_period_days; |
||||
}; |
||||
|
||||
const double SECONDS_PER_DAY = 86400.0; |
||||
const double MIN_DT = 30.0; |
||||
const double MAX_DT = 600.0; |
||||
const double ENERGY_TOLERANCE = 1.0; |
||||
const int NUM_ORBITS = 100; |
||||
|
||||
double calculate_orbital_period(CelestialBody* body, CelestialBody* parent) { |
||||
Vec3 relative_pos = vec3_sub(body->global_position, parent->global_position); |
||||
double r = vec3_magnitude(relative_pos); |
||||
Vec3 relative_vel = vec3_sub(body->global_velocity, parent->global_velocity); |
||||
double v = vec3_magnitude(relative_vel); |
||||
|
||||
double specific_energy = (v * v) / 2.0 - G * parent->mass / r; |
||||
double semi_major_axis = -G * parent->mass / (2.0 * specific_energy); |
||||
double period_seconds = 2.0 * M_PI * sqrt(pow(semi_major_axis, 3.0) / (G * parent->mass)); |
||||
|
||||
return period_seconds; |
||||
} |
||||
|
||||
bool is_dt_stable(SimulationState* sim, const TestBody& test_body, double dt, int num_orbits) { |
||||
SimulationState* test_sim = create_simulation(sim->max_bodies, sim->max_craft, sim->max_maneuvers, dt); |
||||
|
||||
REQUIRE(load_system_config(test_sim, "tests/informational/test_time_step_stability.toml")); |
||||
|
||||
int body_index = test_body.body_index; |
||||
int parent_index = test_body.parent_index; |
||||
|
||||
double initial_energy = calculate_system_total_energy(test_sim); |
||||
|
||||
Vec3 initial_pos_relative = vec3_sub( |
||||
test_sim->bodies[body_index].global_position, |
||||
test_sim->bodies[parent_index].global_position |
||||
); |
||||
double initial_distance = vec3_magnitude(initial_pos_relative); |
||||
|
||||
double period = calculate_orbital_period(&test_sim->bodies[body_index], &test_sim->bodies[parent_index]); |
||||
double max_time = period * num_orbits; |
||||
|
||||
bool completed = true; |
||||
while (test_sim->time < max_time) { |
||||
update_simulation(test_sim); |
||||
|
||||
if (test_sim->bodies[body_index].parent_index != parent_index) { |
||||
completed = false; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
double final_energy = calculate_system_total_energy(test_sim); |
||||
double energy_drift_percent = fabs((final_energy - initial_energy) / initial_energy) * 100.0; |
||||
|
||||
Vec3 final_pos_relative = vec3_sub( |
||||
test_sim->bodies[body_index].global_position, |
||||
test_sim->bodies[parent_index].global_position |
||||
); |
||||
double final_distance = vec3_magnitude(final_pos_relative); |
||||
double distance_drift_percent = fabs((final_distance - initial_distance) / initial_distance) * 100.0; |
||||
|
||||
bool stable = completed && (energy_drift_percent < ENERGY_TOLERANCE) && (distance_drift_percent < 5.0); |
||||
|
||||
destroy_simulation(test_sim); |
||||
|
||||
return stable; |
||||
} |
||||
|
||||
double find_max_stable_dt(SimulationState* sim, const TestBody& test_body) { |
||||
double low = MIN_DT; |
||||
double high = MAX_DT; |
||||
double max_stable = low; |
||||
|
||||
printf("Testing %s (period ~%.2f days):\n", test_body.name, test_body.expected_period_days); |
||||
|
||||
for (int iter = 0; iter < 10; iter++) { |
||||
double mid = (low + high) / 2.0; |
||||
bool stable = is_dt_stable(sim, test_body, mid, NUM_ORBITS); |
||||
|
||||
if (stable) { |
||||
max_stable = mid; |
||||
low = mid; |
||||
printf(" dt=%.0fs: STABLE\n", mid); |
||||
} else { |
||||
high = mid; |
||||
printf(" dt=%.0fs: UNSTABLE\n", mid); |
||||
} |
||||
|
||||
if (high - low < 5.0) break; |
||||
} |
||||
|
||||
printf(" Maximum stable dt: %.0f seconds\n\n", max_stable); |
||||
return max_stable; |
||||
} |
||||
|
||||
TEST_CASE("Time step stability - Mercury orbiter (MESSENGER-like)", "[timestep][stability]") { |
||||
const double BASE_DT = 60.0; |
||||
SimulationState* sim = create_simulation(10, 0, 0, BASE_DT); |
||||
|
||||
TestBody mercury_orbiter = {"Mercury_Orbiter", 1, 0, 0.5}; |
||||
double max_dt = find_max_stable_dt(sim, mercury_orbiter); |
||||
|
||||
INFO("Mercury orbiter maximum stable dt: " << max_dt << " seconds"); |
||||
|
||||
REQUIRE(max_dt >= MIN_DT); |
||||
|
||||
destroy_simulation(sim); |
||||
} |
||||
|
||||
TEST_CASE("Time step stability - Io (Jupiter's moon)", "[timestep][stability]") { |
||||
const double BASE_DT = 60.0; |
||||
SimulationState* sim = create_simulation(10, 0, 0, BASE_DT); |
||||
|
||||
TestBody io = {"Io", 3, 2, 1.77}; |
||||
double max_dt = find_max_stable_dt(sim, io); |
||||
|
||||
INFO("Io maximum stable dt: " << max_dt << " seconds"); |
||||
|
||||
REQUIRE(max_dt >= MIN_DT); |
||||
|
||||
destroy_simulation(sim); |
||||
} |
||||
|
||||
TEST_CASE("Time step stability - Moon (Earth's moon)", "[timestep][stability]") { |
||||
const double BASE_DT = 60.0; |
||||
SimulationState* sim = create_simulation(10, 0, 0, BASE_DT); |
||||
|
||||
TestBody moon = {"Moon", 5, 4, 27.3}; |
||||
double max_dt = find_max_stable_dt(sim, moon); |
||||
|
||||
INFO("Moon maximum stable dt: " << max_dt << " seconds"); |
||||
|
||||
REQUIRE(max_dt >= MIN_DT); |
||||
|
||||
destroy_simulation(sim); |
||||
} |
||||
|
||||
TEST_CASE("Find minimum stable time step across all bodies", "[timestep][stability]") { |
||||
const double BASE_DT = 60.0; |
||||
SimulationState* sim = create_simulation(10, 0, 0, BASE_DT); |
||||
|
||||
TestBody bodies[] = { |
||||
{"Mercury_Orbiter", 1, 0, 0.5}, |
||||
{"Io", 3, 2, 1.77}, |
||||
{"Moon", 5, 4, 27.3} |
||||
}; |
||||
|
||||
double max_dt = MAX_DT; |
||||
|
||||
printf("\n=== Finding minimum stable dt across all bodies ===\n\n"); |
||||
|
||||
for (int i = 0; i < 3; i++) { |
||||
double body_max_dt = find_max_stable_dt(sim, bodies[i]); |
||||
if (body_max_dt < max_dt) { |
||||
max_dt = body_max_dt; |
||||
} |
||||
} |
||||
|
||||
printf("\n=== RESULTS ===\n"); |
||||
printf("Minimum stable time step: %.0f seconds\n", max_dt); |
||||
printf("Recommended safe time step: %.0f seconds (%.0fx safety margin)\n", max_dt * 0.7, 1.0/0.7); |
||||
|
||||
INFO("Minimum stable dt: " << max_dt << " seconds"); |
||||
|
||||
REQUIRE(max_dt >= MIN_DT); |
||||
|
||||
destroy_simulation(sim); |
||||
} |
||||
|
||||
TEST_CASE("Verify current default dt (60s) stability", "[timestep][stability]") { |
||||
const double DT = 60.0; |
||||
const int NUM_ORBITS = 10; |
||||
|
||||
SimulationState* sim = create_simulation(10, 0, 0, DT); |
||||
REQUIRE(load_system_config(sim, "tests/informational/test_time_step_stability.toml")); |
||||
|
||||
struct BodyTest { |
||||
int body_index; |
||||
int parent_index; |
||||
const char* name; |
||||
}; |
||||
|
||||
BodyTest tests[] = { |
||||
{1, 0, "Mercury_Orbiter"}, |
||||
{3, 2, "Io"}, |
||||
{5, 4, "Moon"} |
||||
}; |
||||
|
||||
for (int t = 0; t < 3; t++) { |
||||
int body_index = tests[t].body_index; |
||||
int parent_index = tests[t].parent_index; |
||||
const char* name = tests[t].name; |
||||
|
||||
double period = calculate_orbital_period(&sim->bodies[body_index], &sim->bodies[parent_index]); |
||||
double max_time = period * NUM_ORBITS; |
||||
|
||||
double initial_energy = calculate_system_total_energy(sim); |
||||
|
||||
INFO("Testing " << name << " with dt=" << DT << "s for " << NUM_ORBITS << " orbits"); |
||||
|
||||
bool completed = true; |
||||
while (sim->time < max_time) { |
||||
update_simulation(sim); |
||||
|
||||
if (sim->bodies[body_index].parent_index != parent_index) { |
||||
completed = false; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
double final_energy = calculate_system_total_energy(sim); |
||||
double energy_drift_percent = fabs((final_energy - initial_energy) / initial_energy) * 100.0; |
||||
|
||||
INFO(name << " completed: " << (completed ? "yes" : "no")); |
||||
INFO(name << " energy drift: " << energy_drift_percent << "%"); |
||||
|
||||
REQUIRE(completed); |
||||
REQUIRE(energy_drift_percent < ENERGY_TOLERANCE); |
||||
} |
||||
|
||||
destroy_simulation(sim); |
||||
} |
||||
@ -0,0 +1,80 @@
|
||||
# Time Step Stability Test Configuration |
||||
# Contains bodies with different orbital periods for testing RK4 stability |
||||
|
||||
[[bodies]] |
||||
name = "Mercury" |
||||
mass = 3.285e23 |
||||
radius = 2.439e6 |
||||
parent_index = -1 |
||||
color = { r = 0.7, g = 0.7, b = 0.7 } |
||||
orbit = { |
||||
semi_major_axis = 0.0, |
||||
eccentricity = 0.0, |
||||
true_anomaly = 0.0 |
||||
} |
||||
|
||||
# Low Mercury orbiter at 200 km altitude (MESSENGER-like) |
||||
# Period ~12 hours - most restrictive case |
||||
[[bodies]] |
||||
name = "Mercury_Orbiter" |
||||
mass = 1000.0 |
||||
radius = 1.0 |
||||
parent_index = 0 |
||||
color = { r = 1.0, g = 0.0, b = 1.0 } |
||||
orbit = { |
||||
semi_major_axis = 2.639e6, |
||||
eccentricity = 0.0, |
||||
true_anomaly = 0.0 |
||||
} |
||||
|
||||
[[bodies]] |
||||
name = "Jupiter" |
||||
mass = 1.898e27 |
||||
radius = 6.9911e7 |
||||
parent_index = -1 |
||||
color = { r = 0.9, g = 0.7, b = 0.5 } |
||||
orbit = { |
||||
semi_major_axis = 0.0, |
||||
eccentricity = 0.0, |
||||
true_anomaly = 0.0 |
||||
} |
||||
|
||||
# Io - Jupiter's moon |
||||
# Period ~1.77 days |
||||
[[bodies]] |
||||
name = "Io" |
||||
mass = 8.93e22 |
||||
radius = 1.822e6 |
||||
parent_index = 2 |
||||
color = { r = 0.9, g = 0.9, b = 0.3 } |
||||
orbit = { |
||||
semi_major_axis = 4.217e8, |
||||
eccentricity = 0.0, |
||||
true_anomaly = 0.0 |
||||
} |
||||
|
||||
[[bodies]] |
||||
name = "Earth" |
||||
mass = 5.972e24 |
||||
radius = 6.371e6 |
||||
parent_index = -1 |
||||
color = { r = 0.0, g = 0.5, b = 1.0 } |
||||
orbit = { |
||||
semi_major_axis = 0.0, |
||||
eccentricity = 0.0, |
||||
true_anomaly = 0.0 |
||||
} |
||||
|
||||
# Moon - Earth's moon |
||||
# Period ~27.3 days |
||||
[[bodies]] |
||||
name = "Moon" |
||||
mass = 7.342e22 |
||||
radius = 1.737e6 |
||||
parent_index = 4 |
||||
color = { r = 0.7, g = 0.7, b = 0.7 } |
||||
orbit = { |
||||
semi_major_axis = 3.844e8, |
||||
eccentricity = 0.0, |
||||
true_anomaly = 0.0 |
||||
} |
||||
Loading…
Reference in new issue