You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
118 lines
5.2 KiB
118 lines
5.2 KiB
#include <catch2/catch_test_macros.hpp> |
|
#include <catch2/matchers/catch_matchers_floating_point.hpp> |
|
#include "../src/physics.h" |
|
#include "../src/simulation.h" |
|
#include "../src/config_loader.h" |
|
#include "../src/test_utilities.h" |
|
#include <cmath> |
|
|
|
using Catch::Matchers::WithinAbs; |
|
|
|
SCENARIO("Parabolic orbit - escape trajectory and initial conditions", |
|
"[parabolic][energy][escape][initial]") { |
|
// Fixture constants |
|
const double TIME_STEP = 60.0; |
|
const double DAYS_TO_SIMULATE = 300.0; |
|
const double SECONDS_PER_DAY = 86400.0; |
|
const double AU = 1.496e11; |
|
|
|
// Precalculated expected values from scripts/precalc_parabolic_orbit.py |
|
const double initial_expected_velocity = 42127.865427; // 42.127865 km/s |
|
const double final_expected_velocity = 26708.624837; // 26.708625 km/s |
|
const double expected_distance = 372192353748.3338; // 2.487917 AU |
|
|
|
SimulationState* sim = create_simulation(10, 0, 0, TIME_STEP); |
|
REQUIRE(load_system_config(sim, "tests/test_parabolic_orbit.toml")); |
|
|
|
const int COMET_INDEX = 1; |
|
const int SUN_INDEX = 0; |
|
CelestialBody* comet = &sim->bodies[COMET_INDEX]; |
|
CelestialBody* sun = &sim->bodies[SUN_INDEX]; |
|
|
|
// Initial state |
|
const double initial_distance = vec3_magnitude(comet->global_position); |
|
const double initial_velocity = vec3_magnitude(comet->global_velocity); |
|
const double initial_kinetic = calculate_kinetic_energy(comet); |
|
const double initial_potential = calculate_potential_energy_pair(comet, sun); |
|
const double initial_total_energy = initial_kinetic + initial_potential; |
|
|
|
INFO("Initial distance: " << initial_distance / AU << " AU"); |
|
INFO("Initial velocity: " << initial_velocity / 1000.0 << " km/s"); |
|
INFO("Initial kinetic energy: " << initial_kinetic); |
|
INFO("Initial potential energy: " << initial_potential); |
|
INFO("Initial total energy: " << initial_total_energy); |
|
|
|
SECTION("velocity matches escape velocity") { |
|
const double distance = vec3_distance(comet->global_position, sun->global_position); |
|
const double escape_velocity = sqrt(2.0 * G * sun->mass / distance); |
|
const double circular_velocity = sqrt(G * sun->mass / distance); |
|
|
|
INFO("Distance: " << distance / AU << " AU"); |
|
INFO("Actual velocity: " << initial_velocity / 1000.0 << " km/s"); |
|
INFO("Escape velocity: " << escape_velocity / 1000.0 << " km/s"); |
|
INFO("Circular velocity: " << circular_velocity / 1000.0 << " km/s"); |
|
|
|
const double velocity_error = fabs(initial_velocity - escape_velocity) / escape_velocity; |
|
INFO("Velocity error from escape velocity: " << velocity_error * 100.0 << "%"); |
|
REQUIRE_THAT(velocity_error, WithinAbs(0.0, V_TOL)); |
|
} |
|
|
|
SECTION("eccentricity equals 1.0") { |
|
INFO("Eccentricity: " << comet->orbit.eccentricity); |
|
REQUIRE_THAT(comet->orbit.eccentricity, WithinAbs(1.0, E_TOL)); |
|
} |
|
|
|
SECTION("total energy near zero (relative to KE)") { |
|
// For a parabolic orbit, total energy should be zero. Due to |
|
// floating-point cancellation of two large terms (~8.87e22), the |
|
// absolute value is ~1.68e7 J, but the relative error is ~2e-16. |
|
const double relative_error = fabs(initial_total_energy) / initial_kinetic; |
|
INFO("Initial total energy: " << initial_total_energy << " J"); |
|
INFO("Relative error: " << relative_error); |
|
REQUIRE_THAT(relative_error, WithinAbs(0.0, REL_TOL)); |
|
} |
|
|
|
SECTION("initial velocity matches precalculated") { |
|
INFO("Initial velocity: " << initial_velocity << " m/s"); |
|
REQUIRE_THAT(initial_velocity, WithinAbs(initial_expected_velocity, V_TOL)); |
|
} |
|
|
|
const double max_time = DAYS_TO_SIMULATE * SECONDS_PER_DAY; |
|
while (sim->time < max_time) { |
|
update_simulation(sim); |
|
} |
|
|
|
// Final state |
|
const double final_distance = vec3_magnitude(comet->global_position); |
|
const double final_velocity = vec3_magnitude(comet->global_velocity); |
|
const double final_kinetic = calculate_kinetic_energy(comet); |
|
const double final_potential = calculate_potential_energy_pair(comet, sun); |
|
const double final_total_energy = final_kinetic + final_potential; |
|
|
|
INFO("Final distance: " << final_distance / AU << " AU"); |
|
INFO("Final velocity: " << final_velocity / 1000.0 << " km/s"); |
|
INFO("Final kinetic energy: " << final_kinetic); |
|
INFO("Final potential energy: " << final_potential); |
|
INFO("Final total energy: " << final_total_energy); |
|
|
|
SECTION("final distance matches escape trajectory") { |
|
REQUIRE_THAT(final_distance, WithinAbs(expected_distance, R_TOL)); |
|
} |
|
|
|
SECTION("final velocity matches escape trajectory") { |
|
REQUIRE(final_velocity < initial_velocity); |
|
REQUIRE_THAT(final_velocity, WithinAbs(final_expected_velocity, V_TOL)); |
|
} |
|
|
|
SECTION("energy drift near zero") { |
|
const double energy_drift = fabs(final_total_energy - initial_total_energy); |
|
const double avg_kinetic = (initial_kinetic + final_kinetic) / 2.0; |
|
const double drift_pct = (energy_drift / avg_kinetic) * 100.0; |
|
|
|
INFO("Energy drift: " << energy_drift << " J"); |
|
INFO("Energy drift percent: " << drift_pct << "%"); |
|
REQUIRE_THAT(drift_pct, WithinAbs(0.0, DRIFT_TOL)); |
|
} |
|
|
|
destroy_simulation(sim); |
|
}
|
|
|