4 changed files with 251 additions and 1 deletions
@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3 |
||||
""" |
||||
Precalculate expected values for test_maneuver_planning.cpp refactoring. |
||||
Computes velocities and energy after time-based and true anomaly-based |
||||
maneuver triggers. |
||||
""" |
||||
|
||||
import math |
||||
import sys |
||||
sys.path.insert(0, "/home/agent/dev/claudes_game") |
||||
from scripts.sim_engine import * |
||||
|
||||
|
||||
def main(): |
||||
G_const = G |
||||
earth_mass = 5.972e24 |
||||
mu = G_const * earth_mass |
||||
|
||||
# Initial circular orbit at r = 6.771e6 m |
||||
r0 = 6.771e6 |
||||
v_circular = math.sqrt(mu / r0) |
||||
|
||||
print("// Initial circular orbit") |
||||
print(f"// r = {r0} m") |
||||
print(f"// v_circular = {v_circular:.15e} m/s") |
||||
print() |
||||
|
||||
# Run the full simulation with the TOML config |
||||
sim = Simulator("tests/test_maneuver_planning.toml", dt=60.0) |
||||
|
||||
# Get initial craft velocity |
||||
craft = sim.get_craft("LEO_Satellite") |
||||
v_initial = vmag(craft.local_vel) |
||||
print(f"// Initial craft velocity from config: {v_initial:.15e} m/s") |
||||
print() |
||||
|
||||
# Run to just before first burn (t=3600) |
||||
steps_to_first_burn = int(3600.0 / 60.0) # 60 steps |
||||
sim.run(steps_to_first_burn) |
||||
|
||||
craft = sim.get_craft("LEO_Satellite") |
||||
v_before_burn1 = vmag(craft.local_vel) |
||||
t_before = sim.time |
||||
|
||||
# Check if maneuver[0] executed |
||||
man = sim.maneuvers[0] |
||||
print("// First burn (time trigger at 3600.0 s)") |
||||
print(f"// Time at step end: {t_before:.1f} s") |
||||
print(f"// Executed: {man.executed}") |
||||
print(f"// Executed time: {man.executed_time:.15e} s") |
||||
print(f"// Velocity before burn: {v_before_burn1:.15e} m/s") |
||||
print() |
||||
|
||||
# Continue a bit more to ensure burn fires |
||||
sim.run(1) |
||||
man = sim.maneuvers[0] |
||||
craft = sim.get_craft("LEO_Satellite") |
||||
v_after_burn1 = vmag(craft.local_vel) |
||||
a_after_burn1 = craft.orbit.a |
||||
e_after_burn1 = craft.orbit.e |
||||
r_after_burn1 = vmag(craft.local_pos) |
||||
|
||||
print(f"// After stepping past burn:") |
||||
print(f"// Executed: {man.executed}") |
||||
print(f"// Executed time: {man.executed_time:.15e} s") |
||||
print(f"// Velocity after burn: {v_after_burn1:.15e} m/s") |
||||
print(f"// Semi-major axis: {a_after_burn1:.15e} m") |
||||
print(f"// Eccentricity: {e_after_burn1:.15e}") |
||||
print(f"// Radius: {r_after_burn1:.15e} m") |
||||
print(f"// KE after first burn: {0.5 * 1000.0 * v_after_burn1 * v_after_burn1:.15e} J") |
||||
print() |
||||
|
||||
# Continue until second burn fires (true anomaly 0.0) |
||||
max_additional_steps = 2000 # should be enough |
||||
second_burn_fired = False |
||||
for step in range(max_additional_steps): |
||||
sim.run(1) |
||||
if sim.maneuvers[1].executed: |
||||
second_burn_fired = True |
||||
break |
||||
|
||||
craft = sim.get_craft("LEO_Satellite") |
||||
v_after_burn2 = vmag(craft.local_vel) |
||||
a_after_burn2 = craft.orbit.a |
||||
e_after_burn2 = craft.orbit.e |
||||
|
||||
man2 = sim.maneuvers[1] |
||||
print("// Second burn (true anomaly trigger at 0.0)") |
||||
print(f"// Fired: {second_burn_fired}") |
||||
print(f"// Executed time: {man2.executed_time:.15e} s") |
||||
print(f"// Sim time: {sim.time:.1f} s") |
||||
print(f"// Velocity after second burn: {v_after_burn2:.15e} m/s") |
||||
print(f"// Semi-major axis: {a_after_burn2:.15e} m") |
||||
print(f"// Eccentricity: {e_after_burn2:.15e}") |
||||
print(f"// KE after second burn: {0.5 * 1000.0 * v_after_burn2 * v_after_burn2:.15e} J") |
||||
print() |
||||
|
||||
# Also get the exact burn result for second burn |
||||
br = man2.burn_result |
||||
print(f"// Pre-burn state at second burn:") |
||||
print(f"// pos = {br.position}") |
||||
print(f"// vel = {br.velocity}") |
||||
print(f"// true_anomaly = {br.true_anomaly:.15e} rad") |
||||
print() |
||||
|
||||
# Run beyond well past to verify no extra executions |
||||
sim.run(500) |
||||
exec_count = sum(1 for m in sim.maneuvers if m.executed) |
||||
print("// After extra simulation") |
||||
print(f"// Total executed: {exec_count}") |
||||
for i, m in enumerate(sim.maneuvers): |
||||
print(f"// Maneuver[{i}] '{m.name}': executed={m.executed}, time={m.executed_time:.1f} s") |
||||
print() |
||||
|
||||
print("// For WithinAbs assertions:") |
||||
print(f"// v_initial := {v_circular:.15e}") |
||||
print(f"// v_after_burn1 := {v_after_burn1:.15e}") |
||||
print(f"// a_after_burn1 := {a_after_burn1:.15e}") |
||||
print(f"// e_after_burn1 := {e_after_burn1:.15e}") |
||||
print(f"// v_after_burn2 := {v_after_burn2:.15e}") |
||||
print(f"// a_after_burn2 := {a_after_burn2:.15e}") |
||||
print(f"// e_after_burn2 := {e_after_burn2:.15e}") |
||||
print(f"// executed_time_1 := {man.executed_time:.15e}") |
||||
print(f"// executed_time_2 := {man2.executed_time:.15e}") |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
main() |
||||
@ -0,0 +1,81 @@
|
||||
#include <catch2/catch_test_macros.hpp> |
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp> |
||||
#include "../src/physics.h" |
||||
#include "../src/simulation.h" |
||||
#include "../src/orbital_objects.h" |
||||
#include "../src/maneuver.h" |
||||
#include "../src/config_loader.h" |
||||
#include "../src/test_utilities.h" |
||||
using Catch::Matchers::WithinAbs; |
||||
|
||||
SCENARIO("Maneuver planning and execution", "[maneuver][planning][trigger][config]") { |
||||
const double TIME_STEP = 60.0; |
||||
const double BURN_TIME = 3600.0; |
||||
SimulationState* sim = create_simulation(10, 10, 100, TIME_STEP); |
||||
|
||||
REQUIRE(load_system_config(sim, "tests/test_maneuver_planning.toml")); |
||||
|
||||
auto run_until = [&](double target_time) { |
||||
while (sim->time < target_time) { |
||||
update_simulation(sim); |
||||
} |
||||
}; |
||||
|
||||
SECTION("maneuvers load from config with correct properties") { |
||||
REQUIRE(sim->maneuver_count == 2); |
||||
REQUIRE(std::string(sim->maneuvers[0].name) == "orbit_raise_1"); |
||||
REQUIRE(std::string(sim->maneuvers[1].name) == "orbit_raise_2"); |
||||
REQUIRE(sim->maneuvers[0].trigger_type == TRIGGER_TIME); |
||||
REQUIRE(sim->maneuvers[1].trigger_type == TRIGGER_TRUE_ANOMALY); |
||||
REQUIRE_THAT(sim->maneuvers[0].delta_v, WithinAbs(500.0, D_TOL)); |
||||
} |
||||
|
||||
SECTION("time-based trigger executes at 3600 s") { |
||||
REQUIRE(!sim->maneuvers[0].executed); |
||||
REQUIRE(!sim->maneuvers[1].executed); |
||||
|
||||
run_until(BURN_TIME + TIME_STEP); |
||||
|
||||
REQUIRE(sim->maneuvers[0].executed); |
||||
REQUIRE_THAT(sim->maneuvers[0].executed_time, WithinAbs(BURN_TIME, TIME_STEP)); |
||||
|
||||
const double after_velocity = vec3_magnitude(sim->spacecraft[0].local_velocity); |
||||
REQUIRE_THAT(after_velocity, WithinAbs(8.170251503359999e3, V_TOL)); |
||||
} |
||||
|
||||
SECTION("true anomaly trigger executes after first burn") { |
||||
REQUIRE(!sim->maneuvers[1].executed); |
||||
|
||||
run_until(BURN_TIME + TIME_STEP); |
||||
REQUIRE(sim->maneuvers[0].executed); |
||||
|
||||
const double max_sim_time = BURN_TIME + 72000.0; |
||||
while (sim->time < max_sim_time) { |
||||
update_simulation(sim); |
||||
if (sim->maneuvers[1].executed) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
REQUIRE(sim->maneuvers[1].executed); |
||||
REQUIRE_THAT(vec3_magnitude(sim->spacecraft[0].local_velocity), |
||||
WithinAbs(8.672299586435140e3, V_TOL)); |
||||
} |
||||
|
||||
SECTION("maneuvers execute only once") { |
||||
run_until(20000.0); |
||||
|
||||
REQUIRE(sim->maneuvers[0].executed); |
||||
REQUIRE(sim->maneuvers[1].executed); |
||||
|
||||
int execution_count = 0; |
||||
for (int i = 0; i < sim->maneuver_count; i++) { |
||||
if (sim->maneuvers[i].executed) { |
||||
execution_count++; |
||||
} |
||||
} |
||||
REQUIRE(execution_count == 2); |
||||
} |
||||
|
||||
destroy_simulation(sim); |
||||
} |
||||
@ -0,0 +1,41 @@
|
||||
# Test Configuration: Maneuver Sequence |
||||
# Sun + Earth + LEO Satellite with scheduled burns |
||||
# Tests time-based and true anomaly-based maneuver triggers |
||||
|
||||
[[bodies]] |
||||
name = "Sun" |
||||
mass = 1.989e30 |
||||
radius = 6.96e8 |
||||
parent_index = -1 |
||||
color = {r = 1.0, g = 1.0, b = 0.0} |
||||
orbit = {semi_major_axis = 0.0, eccentricity = 0.0, true_anomaly = 0.0} |
||||
|
||||
[[bodies]] |
||||
name = "Earth" |
||||
mass = 5.972e24 |
||||
radius = 6.371e6 |
||||
parent_index = 0 |
||||
color = {r = 0.0, g = 0.5, b = 1.0} |
||||
orbit = {semi_major_axis = 1.496e11, eccentricity = 0.0, true_anomaly = 0.0} |
||||
|
||||
[[spacecraft]] |
||||
name = "LEO_Satellite" |
||||
mass = 1000.0 |
||||
parent_index = 1 |
||||
orbit = {semi_major_axis = 6.771e6, eccentricity = 0.0, true_anomaly = 1.57} |
||||
|
||||
[[maneuvers]] |
||||
name = "orbit_raise_1" |
||||
spacecraft_name = "LEO_Satellite" |
||||
trigger_type = "time" |
||||
trigger_value = 3600.0 |
||||
direction = "prograde" |
||||
delta_v = 500.0 |
||||
|
||||
[[maneuvers]] |
||||
name = "orbit_raise_2" |
||||
spacecraft_name = "LEO_Satellite" |
||||
trigger_type = "true_anomaly" |
||||
trigger_value = 0.0 |
||||
direction = "prograde" |
||||
delta_v = 500.0 |
||||
Loading…
Reference in new issue