|
|
|
|
@ -8,12 +8,42 @@
|
|
|
|
|
#include "../src/config_loader.h" |
|
|
|
|
#include "../src/test_utilities.h" |
|
|
|
|
#include <cmath> |
|
|
|
|
#include <cstring> |
|
|
|
|
|
|
|
|
|
const double POSITION_TOLERANCE = 1e-3; |
|
|
|
|
const double VELOCITY_TOLERANCE = 1e-3; |
|
|
|
|
const double ELEMENT_TOLERANCE = 1e-6; |
|
|
|
|
const double ENERGY_TOLERANCE = 1e-6; |
|
|
|
|
|
|
|
|
|
int find_maneuver_by_name(SimulationState* sim, const char* name) { |
|
|
|
|
for (int i = 0; i < sim->maneuver_count; i++) { |
|
|
|
|
if (strcmp(sim->maneuvers[i].name, name) == 0) { |
|
|
|
|
return i; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return -1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void execute_maneuver_by_name(SimulationState* sim, const char* maneuver_name, Spacecraft* craft) { |
|
|
|
|
int maneuver_index = find_maneuver_by_name(sim, maneuver_name); |
|
|
|
|
REQUIRE(maneuver_index >= 0); |
|
|
|
|
|
|
|
|
|
Maneuver* maneuver = &sim->maneuvers[maneuver_index]; |
|
|
|
|
REQUIRE(!maneuver->executed); |
|
|
|
|
|
|
|
|
|
// Set simulation time to trigger (for time-based triggers)
|
|
|
|
|
if (maneuver->trigger_type == TRIGGER_TIME) { |
|
|
|
|
sim->time = maneuver->trigger_value; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Execute maneuver
|
|
|
|
|
execute_maneuver(maneuver, craft, sim->time); |
|
|
|
|
|
|
|
|
|
// Verify execution
|
|
|
|
|
REQUIRE(maneuver->executed); |
|
|
|
|
REQUIRE(maneuver->executed_time == sim->time); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TEST_CASE("Config loading for hybrid impulse burns", "[hybrid][impulse][config]") { |
|
|
|
|
const double TIME_STEP = 60.0; |
|
|
|
|
|
|
|
|
|
@ -63,13 +93,16 @@ SCENARIO("Hohmann transfer with two burns", "[hybrid][impulse][hohmann]") {
|
|
|
|
|
Vec3 initial_pos; |
|
|
|
|
Vec3 initial_vel; |
|
|
|
|
orbital_elements_to_cartesian(craft->orbit, earth->mass, &initial_pos, &initial_vel); |
|
|
|
|
craft->local_position = initial_pos; |
|
|
|
|
craft->local_velocity = initial_vel; |
|
|
|
|
|
|
|
|
|
SECTION("First burn at perigee raises apogee") { |
|
|
|
|
OrbitalElements initial_elements = craft->orbit; |
|
|
|
|
OrbitalElements initial_elements = craft->orbit; |
|
|
|
|
|
|
|
|
|
SECTION("First burn at perigee raises apogee") { |
|
|
|
|
double initial_velocity_mag = vec3_magnitude(initial_vel); |
|
|
|
|
|
|
|
|
|
apply_impulsive_burn(craft, BURN_PROGRADE, 2440.0); |
|
|
|
|
// Execute first maneuver via maneuver system
|
|
|
|
|
execute_maneuver_by_name(sim, "hohmann_burn_1", craft); |
|
|
|
|
|
|
|
|
|
double new_velocity_mag = vec3_magnitude(craft->local_velocity); |
|
|
|
|
|
|
|
|
|
@ -89,6 +122,40 @@ SCENARIO("Hohmann transfer with two burns", "[hybrid][impulse][hohmann]") {
|
|
|
|
|
REQUIRE(new_elements.eccentricity > initial_elements.eccentricity); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
SECTION("Second burn at apogee circularizes orbit") { |
|
|
|
|
// Execute first burn
|
|
|
|
|
execute_maneuver_by_name(sim, "hohmann_burn_1", craft); |
|
|
|
|
|
|
|
|
|
OrbitalElements after_first_burn = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); |
|
|
|
|
|
|
|
|
|
// Set up position at apogee (true_anomaly = PI)
|
|
|
|
|
OrbitalElements apogee_elements = after_first_burn; |
|
|
|
|
apogee_elements.true_anomaly = M_PI; |
|
|
|
|
|
|
|
|
|
Vec3 apogee_pos; |
|
|
|
|
Vec3 apogee_vel; |
|
|
|
|
orbital_elements_to_cartesian(apogee_elements, earth->mass, &apogee_pos, &apogee_vel); |
|
|
|
|
craft->local_position = apogee_pos; |
|
|
|
|
craft->local_velocity = apogee_vel; |
|
|
|
|
|
|
|
|
|
// Execute second maneuver via maneuver system
|
|
|
|
|
execute_maneuver_by_name(sim, "hohmann_burn_2", craft); |
|
|
|
|
|
|
|
|
|
Vec3 final_pos = craft->local_position; |
|
|
|
|
Vec3 final_vel = craft->local_velocity; |
|
|
|
|
|
|
|
|
|
OrbitalElements final_elements = cartesian_to_orbital_elements(final_pos, final_vel, earth->mass); |
|
|
|
|
|
|
|
|
|
INFO("After first burn a: " << after_first_burn.semi_major_axis); |
|
|
|
|
INFO("After first burn e: " << after_first_burn.eccentricity); |
|
|
|
|
INFO("Final a: " << final_elements.semi_major_axis); |
|
|
|
|
INFO("Final e: " << final_elements.eccentricity); |
|
|
|
|
|
|
|
|
|
REQUIRE(final_elements.semi_major_axis > after_first_burn.semi_major_axis); |
|
|
|
|
REQUIRE(final_elements.eccentricity < after_first_burn.eccentricity); |
|
|
|
|
REQUIRE(final_elements.eccentricity < 0.1); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
destroy_simulation(sim); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -105,6 +172,8 @@ SCENARIO("Large burns (Δv > orbital velocity)", "[hybrid][impulse][large_delta_
|
|
|
|
|
Vec3 initial_pos; |
|
|
|
|
Vec3 initial_vel; |
|
|
|
|
orbital_elements_to_cartesian(craft->orbit, earth->mass, &initial_pos, &initial_vel); |
|
|
|
|
craft->local_position = initial_pos; |
|
|
|
|
craft->local_velocity = initial_vel; |
|
|
|
|
|
|
|
|
|
OrbitalElements initial_elements = cartesian_to_orbital_elements(initial_pos, initial_vel, earth->mass); |
|
|
|
|
|
|
|
|
|
@ -115,8 +184,8 @@ SCENARIO("Large burns (Δv > orbital velocity)", "[hybrid][impulse][large_delta_
|
|
|
|
|
INFO("Initial velocity: " << initial_velocity_mag << " m/s"); |
|
|
|
|
INFO("Escape velocity: " << escape_velocity << " m/s"); |
|
|
|
|
|
|
|
|
|
double delta_v = 12000.0; |
|
|
|
|
apply_impulsive_burn(craft, BURN_PROGRADE, delta_v); |
|
|
|
|
// Execute large burn via maneuver system
|
|
|
|
|
execute_maneuver_by_name(sim, "large_burn", craft); |
|
|
|
|
|
|
|
|
|
double final_velocity_mag = vec3_magnitude(craft->local_velocity); |
|
|
|
|
INFO("Final velocity: " << final_velocity_mag << " m/s"); |
|
|
|
|
@ -133,7 +202,8 @@ SCENARIO("Large burns (Δv > orbital velocity)", "[hybrid][impulse][large_delta_
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
SECTION("Large burn produces correct hyperbolic trajectory") { |
|
|
|
|
apply_impulsive_burn(craft, BURN_PROGRADE, 12000.0); |
|
|
|
|
// Execute large burn via maneuver system
|
|
|
|
|
execute_maneuver_by_name(sim, "large_burn", craft); |
|
|
|
|
|
|
|
|
|
OrbitalElements new_elements = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); |
|
|
|
|
|
|
|
|
|
@ -176,7 +246,14 @@ SCENARIO("Energy conservation during burns", "[hybrid][impulse][energy]") {
|
|
|
|
|
double delta_v = 1000.0; |
|
|
|
|
Vec3 v_initial = craft->local_velocity; |
|
|
|
|
|
|
|
|
|
apply_impulsive_burn(craft, BURN_PROGRADE, delta_v); |
|
|
|
|
// Get maneuver delta_v from config
|
|
|
|
|
int maneuver_index = find_maneuver_by_name(sim, "hohmann_burn_1"); |
|
|
|
|
REQUIRE(maneuver_index >= 0); |
|
|
|
|
Maneuver* maneuver = &sim->maneuvers[maneuver_index]; |
|
|
|
|
delta_v = maneuver->delta_v; |
|
|
|
|
|
|
|
|
|
// Execute burn via maneuver system
|
|
|
|
|
execute_maneuver_by_name(sim, "hohmann_burn_1", craft); |
|
|
|
|
|
|
|
|
|
Vec3 v_final = craft->local_velocity; |
|
|
|
|
Vec3 dv = vec3_sub(v_final, v_initial); |
|
|
|
|
@ -204,7 +281,16 @@ SCENARIO("Energy conservation during burns", "[hybrid][impulse][energy]") {
|
|
|
|
|
double delta_v = 1000.0; |
|
|
|
|
Vec3 v_initial = craft->local_velocity; |
|
|
|
|
|
|
|
|
|
apply_impulsive_burn(craft, BURN_RETROGRADE, delta_v); |
|
|
|
|
// Reset spacecraft for second test
|
|
|
|
|
craft->local_position = initial_pos; |
|
|
|
|
craft->local_velocity = initial_vel; |
|
|
|
|
sim->time = 0.0; |
|
|
|
|
sim->maneuvers[find_maneuver_by_name(sim, "hohmann_burn_1")].executed = false; |
|
|
|
|
|
|
|
|
|
// Create a retrograde maneuver for this test
|
|
|
|
|
Vec3 retrograde_dir = calculate_retrograde_dir(v_initial); |
|
|
|
|
Vec3 dv_vec = vec3_scale(retrograde_dir, delta_v); |
|
|
|
|
apply_custom_burn(craft, dv_vec); |
|
|
|
|
|
|
|
|
|
Vec3 v_final = craft->local_velocity; |
|
|
|
|
Vec3 dv = vec3_sub(v_final, v_initial); |
|
|
|
|
@ -247,6 +333,8 @@ SCENARIO("Round-trip conversion with burns", "[hybrid][impulse][roundtrip]") {
|
|
|
|
|
Vec3 position_from_elements; |
|
|
|
|
Vec3 velocity_from_elements; |
|
|
|
|
orbital_elements_to_cartesian(original_elements, earth->mass, &position_from_elements, &velocity_from_elements); |
|
|
|
|
craft->local_position = position_from_elements; |
|
|
|
|
craft->local_velocity = velocity_from_elements; |
|
|
|
|
|
|
|
|
|
INFO("Original semi_major_axis: " << original_elements.semi_major_axis); |
|
|
|
|
INFO("Original eccentricity: " << original_elements.eccentricity); |
|
|
|
|
@ -259,13 +347,10 @@ SCENARIO("Round-trip conversion with burns", "[hybrid][impulse][roundtrip]") {
|
|
|
|
|
REQUIRE_THAT(recovered_elements.semi_major_axis, Catch::Matchers::WithinAbs(original_elements.semi_major_axis, ELEMENT_TOLERANCE)); |
|
|
|
|
REQUIRE_THAT(recovered_elements.eccentricity, Catch::Matchers::WithinAbs(original_elements.eccentricity, ELEMENT_TOLERANCE)); |
|
|
|
|
|
|
|
|
|
Vec3 burn_velocity = calculate_prograde_dir(velocity_from_elements); |
|
|
|
|
Vec3 new_velocity = velocity_from_elements; |
|
|
|
|
new_velocity.x += burn_velocity.x * 1000.0; |
|
|
|
|
new_velocity.y += burn_velocity.y * 1000.0; |
|
|
|
|
new_velocity.z += burn_velocity.z * 1000.0; |
|
|
|
|
// Execute maneuver via system
|
|
|
|
|
execute_maneuver_by_name(sim, "hohmann_burn_1", craft); |
|
|
|
|
|
|
|
|
|
OrbitalElements post_burn_elements = cartesian_to_orbital_elements(position_from_elements, new_velocity, earth->mass); |
|
|
|
|
OrbitalElements post_burn_elements = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); |
|
|
|
|
|
|
|
|
|
INFO("Post-burn semi_major_axis: " << post_burn_elements.semi_major_axis); |
|
|
|
|
INFO("Post-burn eccentricity: " << post_burn_elements.eccentricity); |
|
|
|
|
@ -280,6 +365,8 @@ SCENARIO("Round-trip conversion with burns", "[hybrid][impulse][roundtrip]") {
|
|
|
|
|
Vec3 position; |
|
|
|
|
Vec3 velocity; |
|
|
|
|
orbital_elements_to_cartesian(original_elements, earth->mass, &position, &velocity); |
|
|
|
|
craft->local_position = position; |
|
|
|
|
craft->local_velocity = velocity; |
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 5; i++) { |
|
|
|
|
OrbitalElements elements = cartesian_to_orbital_elements(position, velocity, earth->mass); |
|
|
|
|
@ -327,7 +414,9 @@ SCENARIO("Multiple burn sequences", "[hybrid][impulse][sequence]") {
|
|
|
|
|
INFO("Initial a: " << initial_elements.semi_major_axis); |
|
|
|
|
INFO("Initial e: " << initial_elements.eccentricity); |
|
|
|
|
|
|
|
|
|
apply_impulsive_burn(craft, BURN_PROGRADE, 500.0); |
|
|
|
|
// Execute first burn via maneuver system
|
|
|
|
|
execute_maneuver_by_name(sim, "hohmann_burn_1", craft); |
|
|
|
|
|
|
|
|
|
OrbitalElements after_first_burn = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); |
|
|
|
|
|
|
|
|
|
INFO("After first burn a: " << after_first_burn.semi_major_axis); |
|
|
|
|
@ -335,6 +424,7 @@ SCENARIO("Multiple burn sequences", "[hybrid][impulse][sequence]") {
|
|
|
|
|
|
|
|
|
|
REQUIRE(after_first_burn.semi_major_axis > initial_elements.semi_major_axis); |
|
|
|
|
|
|
|
|
|
// Propagate to apogee for second burn
|
|
|
|
|
OrbitalElements apogee_elements = after_first_burn; |
|
|
|
|
apogee_elements.true_anomaly = M_PI; |
|
|
|
|
|
|
|
|
|
@ -344,7 +434,9 @@ SCENARIO("Multiple burn sequences", "[hybrid][impulse][sequence]") {
|
|
|
|
|
craft->local_position = apogee_pos; |
|
|
|
|
craft->local_velocity = apogee_vel; |
|
|
|
|
|
|
|
|
|
apply_impulsive_burn(craft, BURN_PROGRADE, 300.0); |
|
|
|
|
// Execute second burn via maneuver system
|
|
|
|
|
execute_maneuver_by_name(sim, "hohmann_burn_2", craft); |
|
|
|
|
|
|
|
|
|
OrbitalElements after_second_burn = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); |
|
|
|
|
|
|
|
|
|
INFO("After second burn a: " << after_second_burn.semi_major_axis); |
|
|
|
|
@ -357,13 +449,33 @@ SCENARIO("Multiple burn sequences", "[hybrid][impulse][sequence]") {
|
|
|
|
|
SECTION("Three-burn sequence with plane change") { |
|
|
|
|
OrbitalElements initial_elements = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); |
|
|
|
|
|
|
|
|
|
apply_impulsive_burn(craft, BURN_PROGRADE, 500.0); |
|
|
|
|
// Reset spacecraft
|
|
|
|
|
craft->local_position = initial_pos; |
|
|
|
|
craft->local_velocity = initial_vel; |
|
|
|
|
sim->time = 0.0; |
|
|
|
|
for (int i = 0; i < sim->maneuver_count; i++) { |
|
|
|
|
sim->maneuvers[i].executed = false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Execute prograde burn manually (no config maneuver for this sequence)
|
|
|
|
|
Vec3 prograde_dir = calculate_prograde_dir(craft->local_velocity); |
|
|
|
|
Vec3 dv1 = vec3_scale(prograde_dir, 500.0); |
|
|
|
|
apply_custom_burn(craft, dv1); |
|
|
|
|
|
|
|
|
|
OrbitalElements after_burn1 = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); |
|
|
|
|
|
|
|
|
|
apply_impulsive_burn(craft, BURN_NORMAL, 300.0); |
|
|
|
|
// Execute normal burn
|
|
|
|
|
Vec3 normal_dir = calculate_normal_dir(craft->local_position, craft->local_velocity); |
|
|
|
|
Vec3 dv2 = vec3_scale(normal_dir, 300.0); |
|
|
|
|
apply_custom_burn(craft, dv2); |
|
|
|
|
|
|
|
|
|
OrbitalElements after_burn2 = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); |
|
|
|
|
|
|
|
|
|
apply_impulsive_burn(craft, BURN_PROGRADE, 200.0); |
|
|
|
|
// Execute second prograde burn
|
|
|
|
|
prograde_dir = calculate_prograde_dir(craft->local_velocity); |
|
|
|
|
Vec3 dv3 = vec3_scale(prograde_dir, 200.0); |
|
|
|
|
apply_custom_burn(craft, dv3); |
|
|
|
|
|
|
|
|
|
OrbitalElements after_burn3 = cartesian_to_orbital_elements(craft->local_position, craft->local_velocity, earth->mass); |
|
|
|
|
|
|
|
|
|
INFO("Initial a: " << initial_elements.semi_major_axis); |
|
|
|
|
|