5 changed files with 2 additions and 519 deletions
@ -1,195 +0,0 @@ |
|||||||
#include "mission_planning.h" |
|
||||||
#include <cstdio> |
|
||||||
#include <cmath> |
|
||||||
|
|
||||||
TransferParameters calculate_hohmann_transfer(double r_departure, double r_arrival, |
|
||||||
double central_mass) { |
|
||||||
TransferParameters params; |
|
||||||
|
|
||||||
params.periapsis = r_departure; |
|
||||||
params.apoapsis = r_arrival; |
|
||||||
params.semi_major_axis = (r_departure + r_arrival) / 2.0; |
|
||||||
params.eccentricity = (r_arrival - r_departure) / (r_arrival + r_departure); |
|
||||||
|
|
||||||
params.transfer_time = M_PI * sqrt(pow(params.semi_major_axis, 3) / (G * central_mass)); |
|
||||||
|
|
||||||
params.departure_velocity = sqrt(G * central_mass * (2.0/r_departure - 1.0/params.semi_major_axis)); |
|
||||||
params.arrival_velocity = sqrt(G * central_mass * (2.0/r_arrival - 1.0/params.semi_major_axis)); |
|
||||||
|
|
||||||
double circular_velocity = sqrt(G * central_mass / r_departure); |
|
||||||
params.delta_v_injection = params.departure_velocity - circular_velocity; |
|
||||||
|
|
||||||
params.delta_v_capture = 0.0; |
|
||||||
|
|
||||||
double arrival_period = 2.0 * M_PI * sqrt(pow(r_arrival, 3) / (G * central_mass)); |
|
||||||
params.phase_angle_deg = calculate_required_phase_angle(params.transfer_time, arrival_period); |
|
||||||
|
|
||||||
return params; |
|
||||||
} |
|
||||||
|
|
||||||
double calculate_angular_position(CelestialBody* body, CelestialBody* center) { |
|
||||||
Vec3 rel_pos = vec3_sub(body->position, center->position); |
|
||||||
|
|
||||||
double angle = atan2(rel_pos.y, rel_pos.x); |
|
||||||
|
|
||||||
if (angle < 0.0) { |
|
||||||
angle += 2.0 * M_PI; |
|
||||||
} |
|
||||||
|
|
||||||
return angle; |
|
||||||
} |
|
||||||
|
|
||||||
double calculate_required_phase_angle(double transfer_time, double arrival_period) { |
|
||||||
double omega_arrival = 2.0 * M_PI / arrival_period; |
|
||||||
|
|
||||||
double alpha_arrival = omega_arrival * transfer_time; |
|
||||||
|
|
||||||
double phase_angle_rad = M_PI - alpha_arrival; |
|
||||||
|
|
||||||
double phase_angle_deg = phase_angle_rad * 180.0 / M_PI; |
|
||||||
|
|
||||||
while (phase_angle_deg < 0.0) { |
|
||||||
phase_angle_deg += 360.0; |
|
||||||
} |
|
||||||
while (phase_angle_deg >= 360.0) { |
|
||||||
phase_angle_deg -= 360.0; |
|
||||||
} |
|
||||||
|
|
||||||
return phase_angle_deg; |
|
||||||
} |
|
||||||
|
|
||||||
bool check_launch_window(SimulationState* sim, int departure_idx, int arrival_idx, |
|
||||||
double required_phase_angle_deg, double tolerance_deg) { |
|
||||||
if (departure_idx < 0 || departure_idx >= sim->body_count) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
if (arrival_idx < 0 || arrival_idx >= sim->body_count) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
CelestialBody* departure = &sim->bodies[departure_idx]; |
|
||||||
CelestialBody* arrival = &sim->bodies[arrival_idx]; |
|
||||||
CelestialBody* sun = &sim->bodies[0]; |
|
||||||
|
|
||||||
double theta_depart = calculate_angular_position(departure, sun); |
|
||||||
double theta_arrival = calculate_angular_position(arrival, sun); |
|
||||||
|
|
||||||
double current_phase_rad = theta_arrival - theta_depart; |
|
||||||
if (current_phase_rad < 0.0) { |
|
||||||
current_phase_rad += 2.0 * M_PI; |
|
||||||
} |
|
||||||
|
|
||||||
double current_phase_deg = current_phase_rad * 180.0 / M_PI; |
|
||||||
|
|
||||||
double error = fabs(current_phase_deg - required_phase_angle_deg); |
|
||||||
if (error > 180.0) { |
|
||||||
error = fabs(error - 360.0); |
|
||||||
} |
|
||||||
|
|
||||||
return error <= tolerance_deg; |
|
||||||
} |
|
||||||
|
|
||||||
void wait_for_launch_window(SimulationState* sim, int departure_idx, int arrival_idx, |
|
||||||
double required_phase_angle_deg, double tolerance_deg) { |
|
||||||
const double TIME_STEP = 60.0; |
|
||||||
const int STEPS_PER_DAY = (int)(86400.0 / TIME_STEP); |
|
||||||
|
|
||||||
while (!check_launch_window(sim, departure_idx, arrival_idx, |
|
||||||
required_phase_angle_deg, tolerance_deg)) { |
|
||||||
for (int i = 0; i < STEPS_PER_DAY; i++) { |
|
||||||
update_simulation(sim); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
printf("Launch window opened at t = %.2f days\n", sim->time / 86400.0); |
|
||||||
} |
|
||||||
|
|
||||||
void initialize_spacecraft_leo(CelestialBody* spacecraft, CelestialBody* parent, |
|
||||||
double altitude_m) { |
|
||||||
|
|
||||||
double orbital_radius = parent->radius + altitude_m; |
|
||||||
|
|
||||||
Vec3 sun_to_earth = vec3_sub(parent->position, |
|
||||||
(Vec3){0.0, 0.0, 0.0}); |
|
||||||
Vec3 direction = vec3_normalize(sun_to_earth); |
|
||||||
|
|
||||||
Vec3 offset = vec3_scale(direction, orbital_radius); |
|
||||||
spacecraft->position = vec3_add(parent->position, offset); |
|
||||||
|
|
||||||
spacecraft->local_position = offset; |
|
||||||
|
|
||||||
double v_leo = sqrt(G * parent->mass / orbital_radius); |
|
||||||
|
|
||||||
Vec3 leo_tangent = (Vec3){direction.y, -direction.x, 0.0}; |
|
||||||
Vec3 leo_velocity = vec3_scale(leo_tangent, v_leo); |
|
||||||
|
|
||||||
spacecraft->velocity = vec3_add(parent->velocity, leo_velocity); |
|
||||||
spacecraft->local_velocity = leo_velocity; |
|
||||||
|
|
||||||
spacecraft->semi_major_axis = orbital_radius; |
|
||||||
|
|
||||||
printf("Spacecraft LEO initialized:\n"); |
|
||||||
printf(" Altitude: %.2f km\n", altitude_m / 1000.0); |
|
||||||
printf(" Orbital radius: %.2e m\n", orbital_radius); |
|
||||||
printf(" LEO velocity: %.2f m/s\n", v_leo); |
|
||||||
printf(" Parent: %s\n", parent->name); |
|
||||||
} |
|
||||||
|
|
||||||
// DEPRECATED: This function is no longer needed. Spacecraft positions and velocities
|
|
||||||
// are now set via TOML config files with semi_major_axis parameter. Use config-based
|
|
||||||
// initialization instead. This function is kept for reference only and will be
|
|
||||||
// removed in a future cleanup.
|
|
||||||
|
|
||||||
void apply_transfer_burn(SimulationState* sim, int spacecraft_idx, |
|
||||||
int departure_idx, TransferParameters* params) { |
|
||||||
CelestialBody* spacecraft = &sim->bodies[spacecraft_idx]; |
|
||||||
CelestialBody* departure = &sim->bodies[departure_idx]; |
|
||||||
CelestialBody* sun = &sim->bodies[0]; |
|
||||||
|
|
||||||
Vec3 sun_to_departure = vec3_sub(departure->position, sun->position); |
|
||||||
Vec3 sun_to_departure_norm = vec3_normalize(sun_to_departure); |
|
||||||
|
|
||||||
Vec3 transfer_dir = (Vec3){-sun_to_departure_norm.y, sun_to_departure_norm.x, 0.0}; |
|
||||||
Vec3 v_transfer_helio = vec3_scale(transfer_dir, params->departure_velocity); |
|
||||||
|
|
||||||
Vec3 old_helio = spacecraft->velocity; |
|
||||||
Vec3 old_local = spacecraft->local_velocity; |
|
||||||
|
|
||||||
Vec3 v_transfer_local = vec3_sub(v_transfer_helio, departure->velocity); |
|
||||||
|
|
||||||
spacecraft->local_velocity = v_transfer_local; |
|
||||||
spacecraft->velocity = vec3_add(departure->velocity, spacecraft->local_velocity); |
|
||||||
|
|
||||||
Vec3 delta_v_local = vec3_sub(spacecraft->local_velocity, old_local); |
|
||||||
Vec3 delta_v_helio = vec3_sub(spacecraft->velocity, old_helio); |
|
||||||
|
|
||||||
printf("Transfer burn applied:\n"); |
|
||||||
printf(" Current heliocentric velocity: (%.2f, %.2f, %.2f) m/s\n", |
|
||||||
old_helio.x, old_helio.y, old_helio.z); |
|
||||||
printf(" Target heliocentric velocity: (%.2f, %.2f, %.2f) m/s\n", |
|
||||||
v_transfer_helio.x, v_transfer_helio.y, v_transfer_helio.z); |
|
||||||
printf(" Delta-v (local): (%.2f, %.2f, %.2f) m/s\n", |
|
||||||
delta_v_local.x, delta_v_local.y, delta_v_local.z); |
|
||||||
printf(" Delta-v magnitude: %.2f m/s (%.3f km/s)\n", |
|
||||||
vec3_magnitude(delta_v_helio), vec3_magnitude(delta_v_helio) / 1000.0); |
|
||||||
} |
|
||||||
|
|
||||||
double calculate_phase_angle(SimulationState* sim, int departure_idx, int arrival_idx) { |
|
||||||
CelestialBody* departure = &sim->bodies[departure_idx]; |
|
||||||
CelestialBody* arrival = &sim->bodies[arrival_idx]; |
|
||||||
CelestialBody* sun = &sim->bodies[0]; |
|
||||||
|
|
||||||
double theta_depart = calculate_angular_position(departure, sun); |
|
||||||
double theta_arrival = calculate_angular_position(arrival, sun); |
|
||||||
|
|
||||||
double phase_rad = theta_arrival - theta_depart; |
|
||||||
|
|
||||||
while (phase_rad < 0.0) { |
|
||||||
phase_rad += 2.0 * M_PI; |
|
||||||
} |
|
||||||
while (phase_rad >= 2.0 * M_PI) { |
|
||||||
phase_rad -= 2.0 * M_PI; |
|
||||||
} |
|
||||||
|
|
||||||
return phase_rad * 180.0 / M_PI; |
|
||||||
} |
|
||||||
@ -1,44 +0,0 @@ |
|||||||
#ifndef MISSION_PLANNING_H |
|
||||||
#define MISSION_PLANNING_H |
|
||||||
|
|
||||||
#include "simulation.h" |
|
||||||
|
|
||||||
struct TransferParameters { |
|
||||||
double semi_major_axis; |
|
||||||
double eccentricity; |
|
||||||
double periapsis; |
|
||||||
double apoapsis; |
|
||||||
double transfer_time; |
|
||||||
double departure_velocity; |
|
||||||
double arrival_velocity; |
|
||||||
double phase_angle_deg; |
|
||||||
double delta_v_injection; |
|
||||||
double delta_v_capture; |
|
||||||
}; |
|
||||||
|
|
||||||
TransferParameters calculate_hohmann_transfer(double r_departure, double r_arrival, |
|
||||||
double central_mass); |
|
||||||
|
|
||||||
double calculate_angular_position(CelestialBody* body, CelestialBody* center); |
|
||||||
|
|
||||||
double calculate_required_phase_angle(double transfer_time, double arrival_period); |
|
||||||
|
|
||||||
bool check_launch_window(SimulationState* sim, int departure_idx, int arrival_idx, |
|
||||||
double required_phase_angle_deg, double tolerance_deg); |
|
||||||
|
|
||||||
void wait_for_launch_window(SimulationState* sim, int departure_idx, int arrival_idx, |
|
||||||
double required_phase_angle_deg, double tolerance_deg); |
|
||||||
|
|
||||||
void initialize_spacecraft_leo(CelestialBody* spacecraft, CelestialBody* parent, |
|
||||||
double altitude_m); |
|
||||||
|
|
||||||
void apply_transfer_burn(SimulationState* sim, int spacecraft_idx, |
|
||||||
int departure_idx, TransferParameters* params); |
|
||||||
|
|
||||||
double calculate_phase_angle(SimulationState* sim, int departure_idx, int arrival_idx); |
|
||||||
|
|
||||||
#endif |
|
||||||
|
|
||||||
// DEPRECATED: initialize_spacecraft_leo() is no longer needed. Spacecraft positions
|
|
||||||
// and velocities are now set via TOML config files with semi_major_axis parameter.
|
|
||||||
// This function is kept for reference only and will be removed in a future cleanup.
|
|
||||||
@ -1,130 +0,0 @@ |
|||||||
#include <catch2/catch_test_macros.hpp> |
|
||||||
#include "../src/physics.h" |
|
||||||
#include "../src/mission_planning.h" |
|
||||||
#include "../src/simulation.h" |
|
||||||
#include "../src/config_loader.h" |
|
||||||
#include "../src/test_utilities.h" |
|
||||||
#include <cmath> |
|
||||||
|
|
||||||
TEST_CASE("Earth → Mars Hohmann Transfer with LEO Spacecraft", "[mission][hohmann][config][integration]") { |
|
||||||
const double TIME_STEP = 1.0; |
|
||||||
const double SECONDS_PER_DAY = 86400.0; |
|
||||||
|
|
||||||
SimulationState* sim = create_simulation(4, TIME_STEP); |
|
||||||
REQUIRE(load_system_config(sim, "tests/configs/earth_mars_simple.toml")); |
|
||||||
|
|
||||||
const int SUN_IDX = 0; |
|
||||||
const int EARTH_IDX = 1; |
|
||||||
const int MARS_IDX = 2; |
|
||||||
const int CRAFT_IDX = 3; |
|
||||||
|
|
||||||
REQUIRE(sim->body_count == 4); |
|
||||||
REQUIRE(strcmp(sim->bodies[CRAFT_IDX].name, "Spacecraft") == 0); |
|
||||||
|
|
||||||
INFO("INITIAL Earth velocity: (" << sim->bodies[EARTH_IDX].velocity.x << ", " |
|
||||||
<< sim->bodies[EARTH_IDX].velocity.y << ", " |
|
||||||
<< sim->bodies[EARTH_IDX].velocity.z << ") m/s"); |
|
||||||
|
|
||||||
REQUIRE(sim->bodies[CRAFT_IDX].parent_index == EARTH_IDX); |
|
||||||
|
|
||||||
double dist_to_earth = vec3_distance(sim->bodies[CRAFT_IDX].position, |
|
||||||
sim->bodies[EARTH_IDX].position); |
|
||||||
double leo_altitude_m = dist_to_earth - sim->bodies[EARTH_IDX].radius; |
|
||||||
|
|
||||||
INFO("Spacecraft altitude: " << leo_altitude_m / 1000.0 << " km"); |
|
||||||
INFO("Spacecraft parent: " << sim->bodies[CRAFT_IDX].parent_index << " (Earth)"); |
|
||||||
INFO("Distance to Earth: " << dist_to_earth / 1000.0 << " km"); |
|
||||||
|
|
||||||
double expected_radius = sim->bodies[EARTH_IDX].radius + leo_altitude_m; |
|
||||||
REQUIRE(fabs(dist_to_earth - expected_radius) < 1000.0); |
|
||||||
|
|
||||||
double leo_velocity_mag = sqrt(G * sim->bodies[EARTH_IDX].mass / dist_to_earth); |
|
||||||
double v_leo_relative = vec3_magnitude(sim->bodies[CRAFT_IDX].local_velocity); |
|
||||||
INFO("Expected LEO velocity: " << leo_velocity_mag << " m/s"); |
|
||||||
INFO("Actual LEO velocity: " << v_leo_relative << " m/s"); |
|
||||||
REQUIRE(fabs(v_leo_relative - leo_velocity_mag) < 10.0); |
|
||||||
|
|
||||||
double v_squared = v_leo_relative * v_leo_relative; |
|
||||||
double kinetic_energy = 0.5 * sim->bodies[CRAFT_IDX].mass * v_squared; |
|
||||||
double potential_energy = -G * sim->bodies[CRAFT_IDX].mass * sim->bodies[EARTH_IDX].mass / dist_to_earth; |
|
||||||
double leo_total_energy = kinetic_energy + potential_energy; |
|
||||||
INFO("LEO total energy: " << leo_total_energy << " J"); |
|
||||||
REQUIRE(leo_total_energy < 0.0); |
|
||||||
|
|
||||||
double r_earth = vec3_distance(sim->bodies[EARTH_IDX].position, |
|
||||||
sim->bodies[SUN_IDX].position); |
|
||||||
double r_mars = vec3_distance(sim->bodies[MARS_IDX].position, |
|
||||||
sim->bodies[SUN_IDX].position); |
|
||||||
|
|
||||||
double earth_orbital_speed = sqrt(G * sim->bodies[SUN_IDX].mass / r_earth); |
|
||||||
Vec3 sun_to_earth_norm = vec3_normalize(vec3_sub(sim->bodies[EARTH_IDX].position, sim->bodies[SUN_IDX].position)); |
|
||||||
Vec3 earth_prograde = (Vec3){-sun_to_earth_norm.y, sun_to_earth_norm.x, 0.0}; |
|
||||||
Vec3 v_earth_helio = vec3_scale(earth_prograde, earth_orbital_speed); |
|
||||||
|
|
||||||
TransferParameters params = calculate_hohmann_transfer(r_earth, r_mars, |
|
||||||
sim->bodies[SUN_IDX].mass); |
|
||||||
|
|
||||||
INFO("Transfer time: " << params.transfer_time / SECONDS_PER_DAY << " days"); |
|
||||||
INFO("Required phase angle: " << params.phase_angle_deg << " degrees"); |
|
||||||
INFO("Delta-v injection: " << params.delta_v_injection / 1000.0 << " km/s"); |
|
||||||
|
|
||||||
INFO("Bypassing wait_for_launch_window - applying burn at initial configuration"); |
|
||||||
INFO("This tests core Hohmann transfer formulas without timing complications"); |
|
||||||
|
|
||||||
double wait_duration = 0.0; |
|
||||||
|
|
||||||
INFO("Earth velocity: (" << sim->bodies[EARTH_IDX].velocity.x << ", " |
|
||||||
<< sim->bodies[EARTH_IDX].velocity.y << ", " |
|
||||||
<< sim->bodies[EARTH_IDX].velocity.z << ") m/s"); |
|
||||||
INFO("Craft velocity: (" << sim->bodies[CRAFT_IDX].velocity.x << ", " |
|
||||||
<< sim->bodies[CRAFT_IDX].velocity.y << ", " |
|
||||||
<< sim->bodies[CRAFT_IDX].velocity.z << ") m/s"); |
|
||||||
INFO("Craft local position: (" << sim->bodies[CRAFT_IDX].local_position.x << ", " |
|
||||||
<< sim->bodies[CRAFT_IDX].local_position.y << ", " |
|
||||||
<< sim->bodies[CRAFT_IDX].local_position.z << ") m"); |
|
||||||
INFO("Craft local velocity: (" << sim->bodies[CRAFT_IDX].local_velocity.x << ", " |
|
||||||
<< sim->bodies[CRAFT_IDX].local_velocity.y << ", " |
|
||||||
<< sim->bodies[CRAFT_IDX].local_velocity.z << ") m/s"); |
|
||||||
|
|
||||||
double dot_product = sim->bodies[CRAFT_IDX].local_position.x * sim->bodies[CRAFT_IDX].local_velocity.x + |
|
||||||
sim->bodies[CRAFT_IDX].local_position.y * sim->bodies[CRAFT_IDX].local_velocity.y; |
|
||||||
INFO("Dot product (pos · vel): " << dot_product << " (should be ~0 for circular orbit)"); |
|
||||||
INFO("Earth prograde direction: (" << earth_prograde.x << ", " << earth_prograde.y << ", " << earth_prograde.z << ")"); |
|
||||||
|
|
||||||
OrbitalMetrics leo_metrics = calculate_orbital_metrics(&sim->bodies[CRAFT_IDX], |
|
||||||
&sim->bodies[EARTH_IDX]); |
|
||||||
INFO("LEO heliocentric energy: " << leo_metrics.total_energy << " J"); |
|
||||||
|
|
||||||
INFO("Bypassing wait_for_launch_window - applying burn at initial configuration"); |
|
||||||
INFO("This tests the core Hohmann transfer formulas without timing complications"); |
|
||||||
|
|
||||||
apply_transfer_burn(sim, CRAFT_IDX, EARTH_IDX, ¶ms); |
|
||||||
|
|
||||||
OrbitalMetrics post_burn_metrics = calculate_orbital_metrics(&sim->bodies[CRAFT_IDX], |
|
||||||
&sim->bodies[SUN_IDX]); |
|
||||||
|
|
||||||
INFO("Pre-burn heliocentric energy: " << leo_metrics.total_energy << " J"); |
|
||||||
INFO("Post-burn heliocentric energy: " << post_burn_metrics.total_energy << " J"); |
|
||||||
INFO("Energy added: " << (post_burn_metrics.total_energy - leo_metrics.total_energy) << " J"); |
|
||||||
|
|
||||||
double specific_energy_helio = 0.5 * pow(vec3_magnitude(sim->bodies[CRAFT_IDX].velocity), 2) - |
|
||||||
G * sim->bodies[SUN_IDX].mass / vec3_distance(sim->bodies[CRAFT_IDX].position, sim->bodies[SUN_IDX].position); |
|
||||||
INFO("Specific heliocentric energy: " << specific_energy_helio << " J/kg"); |
|
||||||
|
|
||||||
double expected_specific_energy = -G * sim->bodies[SUN_IDX].mass / (2.0 * params.semi_major_axis); |
|
||||||
INFO("Expected specific transfer orbit energy: " << expected_specific_energy << " J/kg"); |
|
||||||
|
|
||||||
double energy_error = fabs(specific_energy_helio - expected_specific_energy); |
|
||||||
if (expected_specific_energy != 0.0) { |
|
||||||
energy_error /= fabs(expected_specific_energy); |
|
||||||
} |
|
||||||
INFO("Energy error: " << (energy_error * 100.0) << "%"); |
|
||||||
|
|
||||||
REQUIRE(energy_error < 0.05); |
|
||||||
|
|
||||||
INFO("Test complete - burn successfully applied for Hohmann transfer"); |
|
||||||
INFO("Spacecraft now on transfer orbit from Earth to Mars"); |
|
||||||
INFO("Skipping long-duration simulation to avoid numerical instability"); |
|
||||||
|
|
||||||
destroy_simulation(sim); |
|
||||||
} |
|
||||||
@ -1,148 +0,0 @@ |
|||||||
#include <catch2/catch_test_macros.hpp> |
|
||||||
#include "../src/physics.h" |
|
||||||
#include "../src/mission_planning.h" |
|
||||||
#include "../src/simulation.h" |
|
||||||
#include "../src/config_loader.h" |
|
||||||
#include <cmath> |
|
||||||
|
|
||||||
const double AU = 1.496e11; |
|
||||||
const double M_SUN = 1.989e30; |
|
||||||
const double M_EARTH = 5.972e24; |
|
||||||
const double M_MARS = 6.39e23; |
|
||||||
const double R_EARTH = 1.496e11; |
|
||||||
const double R_MARS = 2.279e11; |
|
||||||
const double SECONDS_PER_DAY = 86400.0; |
|
||||||
|
|
||||||
TEST_CASE("Hohmann transfer - Earth to Mars", "[mission][hohmann]") { |
|
||||||
TransferParameters params = calculate_hohmann_transfer(R_EARTH, R_MARS, M_SUN); |
|
||||||
|
|
||||||
INFO("Semi-major axis: " << params.semi_major_axis / AU << " AU"); |
|
||||||
INFO("Eccentricity: " << params.eccentricity); |
|
||||||
INFO("Transfer time: " << params.transfer_time / SECONDS_PER_DAY << " days"); |
|
||||||
INFO("Departure velocity: " << params.departure_velocity / 1000.0 << " km/s"); |
|
||||||
INFO("Arrival velocity: " << params.arrival_velocity / 1000.0 << " km/s"); |
|
||||||
INFO("Phase angle: " << params.phase_angle_deg << " degrees"); |
|
||||||
INFO("Delta-v injection: " << params.delta_v_injection / 1000.0 << " km/s"); |
|
||||||
|
|
||||||
double expected_transfer_time = 259.0 * SECONDS_PER_DAY; |
|
||||||
double transfer_time_error = fabs(params.transfer_time - expected_transfer_time) / expected_transfer_time; |
|
||||||
REQUIRE(transfer_time_error < 0.05); |
|
||||||
|
|
||||||
double expected_phase_angle = 44.3; |
|
||||||
double phase_angle_error = fabs(params.phase_angle_deg - expected_phase_angle); |
|
||||||
REQUIRE(phase_angle_error < 1.0); |
|
||||||
|
|
||||||
double expected_delta_v = 2940.0; |
|
||||||
double delta_v_error = fabs(params.delta_v_injection - expected_delta_v) / expected_delta_v; |
|
||||||
REQUIRE(delta_v_error < 0.05); |
|
||||||
} |
|
||||||
|
|
||||||
TEST_CASE("Hohmann transfer - Mars to Earth", "[mission][hohmann][reverse]") { |
|
||||||
TransferParameters params = calculate_hohmann_transfer(R_MARS, R_EARTH, M_SUN); |
|
||||||
|
|
||||||
INFO("Transfer time (return): " << params.transfer_time / SECONDS_PER_DAY << " days"); |
|
||||||
INFO("Phase angle (return): " << params.phase_angle_deg << " degrees"); |
|
||||||
INFO("Delta-v injection (return): " << params.delta_v_injection / 1000.0 << " km/s"); |
|
||||||
|
|
||||||
REQUIRE(params.transfer_time > 0); |
|
||||||
double expected_sma = (R_EARTH + R_MARS) / 2.0; |
|
||||||
double sma_error = fabs(params.semi_major_axis - expected_sma) / expected_sma; |
|
||||||
REQUIRE(sma_error < 0.01); |
|
||||||
} |
|
||||||
|
|
||||||
TEST_CASE("Angular position - circular orbit", "[mission][angular]") { |
|
||||||
SimulationState* sim = create_simulation(10, 60.0); |
|
||||||
|
|
||||||
REQUIRE(load_system_config(sim, "tests/configs/earth_circular.toml")); |
|
||||||
|
|
||||||
CelestialBody* earth = &sim->bodies[1]; |
|
||||||
CelestialBody* sun = &sim->bodies[0]; |
|
||||||
|
|
||||||
double angle = calculate_angular_position(earth, sun); |
|
||||||
|
|
||||||
INFO("Earth angular position: " << angle << " rad (" << angle * 180.0 / M_PI << " deg)"); |
|
||||||
|
|
||||||
REQUIRE(angle >= 0.0); |
|
||||||
REQUIRE(angle < 2.0 * M_PI); |
|
||||||
|
|
||||||
update_simulation(sim); |
|
||||||
|
|
||||||
double angle_after = calculate_angular_position(earth, sun); |
|
||||||
INFO("Earth angular position after update: " << angle_after << " rad (" |
|
||||||
<< angle_after * 180.0 / M_PI << " deg)"); |
|
||||||
|
|
||||||
REQUIRE(angle_after >= 0.0); |
|
||||||
REQUIRE(angle_after < 2.0 * M_PI); |
|
||||||
|
|
||||||
destroy_simulation(sim); |
|
||||||
} |
|
||||||
|
|
||||||
TEST_CASE("Phase angle calculation", "[mission][phase]") { |
|
||||||
double mars_period = 687.0 * SECONDS_PER_DAY; |
|
||||||
double transfer_time = 259.0 * SECONDS_PER_DAY; |
|
||||||
|
|
||||||
double phase_angle = calculate_required_phase_angle(transfer_time, mars_period); |
|
||||||
|
|
||||||
INFO("Required phase angle: " << phase_angle << " degrees"); |
|
||||||
|
|
||||||
REQUIRE(phase_angle >= 0.0); |
|
||||||
REQUIRE(phase_angle < 360.0); |
|
||||||
|
|
||||||
double expected_phase_angle = 44.3; |
|
||||||
double phase_error = fabs(phase_angle - expected_phase_angle); |
|
||||||
REQUIRE(phase_error < 1.0); |
|
||||||
} |
|
||||||
|
|
||||||
TEST_CASE("Launch window detection", "[mission][window]") { |
|
||||||
const double TIME_STEP = 60.0; |
|
||||||
|
|
||||||
SimulationState* sim = create_simulation(10, TIME_STEP); |
|
||||||
|
|
||||||
REQUIRE(load_system_config(sim, "tests/configs/earth_mars_simple.toml")); |
|
||||||
|
|
||||||
const int EARTH_IDX = 1; |
|
||||||
const int MARS_IDX = 2; |
|
||||||
|
|
||||||
double r_earth = vec3_distance(sim->bodies[EARTH_IDX].position, sim->bodies[0].position); |
|
||||||
double r_mars = vec3_distance(sim->bodies[MARS_IDX].position, sim->bodies[0].position); |
|
||||||
TransferParameters params = calculate_hohmann_transfer(r_earth, r_mars, sim->bodies[0].mass); |
|
||||||
|
|
||||||
bool window_open = check_launch_window(sim, EARTH_IDX, MARS_IDX, |
|
||||||
params.phase_angle_deg, 5.0); |
|
||||||
|
|
||||||
INFO("Phase angle required: " << params.phase_angle_deg << " degrees"); |
|
||||||
INFO("Launch window open: " << (window_open ? "YES" : "NO")); |
|
||||||
|
|
||||||
REQUIRE(!window_open); |
|
||||||
|
|
||||||
destroy_simulation(sim); |
|
||||||
} |
|
||||||
|
|
||||||
TEST_CASE("Wait for launch window", "[mission][wait]") { |
|
||||||
const double TIME_STEP = 60.0; |
|
||||||
|
|
||||||
SimulationState* sim = create_simulation(10, TIME_STEP); |
|
||||||
|
|
||||||
REQUIRE(load_system_config(sim, "tests/configs/earth_mars_simple.toml")); |
|
||||||
|
|
||||||
const int EARTH_IDX = 1; |
|
||||||
const int MARS_IDX = 2; |
|
||||||
|
|
||||||
double r_earth = vec3_distance(sim->bodies[EARTH_IDX].position, sim->bodies[0].position); |
|
||||||
double r_mars = vec3_distance(sim->bodies[MARS_IDX].position, sim->bodies[0].position); |
|
||||||
TransferParameters params = calculate_hohmann_transfer(r_earth, r_mars, sim->bodies[0].mass); |
|
||||||
|
|
||||||
double start_time = sim->time; |
|
||||||
wait_for_launch_window(sim, EARTH_IDX, MARS_IDX, params.phase_angle_deg, 1.0); |
|
||||||
double end_time = sim->time; |
|
||||||
|
|
||||||
double wait_time = (end_time - start_time) / SECONDS_PER_DAY; |
|
||||||
INFO("Waited " << wait_time << " days for launch window"); |
|
||||||
|
|
||||||
bool window_open = check_launch_window(sim, EARTH_IDX, MARS_IDX, |
|
||||||
params.phase_angle_deg, 1.0); |
|
||||||
|
|
||||||
REQUIRE(window_open); |
|
||||||
|
|
||||||
destroy_simulation(sim); |
|
||||||
} |
|
||||||
Loading…
Reference in new issue