Compare commits

..

5 Commits

  1. 133
      continue.md
  2. 1054
      old_tests/test_hybrid_burns.cpp
  3. 254
      old_tests/test_hybrid_burns.toml
  4. 10
      tests/test_hybrid_burns.cpp
  5. 7
      tests/test_true_anomaly_roundtrip.cpp

133
continue.md

@ -4,6 +4,7 @@
### 1. Structure
- One `SCENARIO("description")` per logical test group, with `[tag1][tag2]` annotations
- Run `./build/orbit_test --list-tags` to see tags used by other tests. Original tags from the old test file are a useful starting point, but the implementor has discretion to choose appropriate tags.
- **Use SCENARIO to share setup/teardown across multiple SECTIONs.** Catch2 re-initializes the fixture before each SECTION, so declare shared constants, structs, and variables in the SCENARIO body (between the opening `{` and the first `SECTION`). These persist across all SECTIONs within the SCENARIO.
- Example: a `SimulationState* sim` created once in the SCENARIO body, then each SECTION mutates and tests it independently.
- Embed expected values directly in `WithinAbs()` calls (see Section 4 for precalc script usage). No need to declare named constants unless the value is reused.
@ -13,7 +14,6 @@
### 2. Duplication Elimination
- Use lambdas that capture the fixture for repeated setup→call→assert patterns
- Reuse shared structs in-place (mutate fields rather than recreating)
- Single-line `SECTION`s when body is one statement: `SECTION("name") { helper(arg); }`
### 3. Assertions
- Include `src/test_utilities.h` for tolerance constants and test utilities
@ -43,7 +43,6 @@ All constants defined in `src/test_utilities.h` — use those, do not redefine l
### 4. Precalc Scripts
- For each test file, create `scripts/precalc_<test_name>.py` that computes expected values.
- **Check the capability matrix first** — only use sim_engine.py for implemented features.
- **Always output local-frame values** (distances from parent, not global from origin).
- C++ tests typically use local coordinates (e.g., `vec3_distance(craft->local_position, (Vec3){0,0,0})`).
- Global distances are dominated by parent body positions (e.g., Earth-Sun distance swamps LEO orbit).
@ -51,38 +50,42 @@ All constants defined in `src/test_utilities.h` — use those, do not redefine l
- Output C++-style comments with precalculated expected values for embedding in the test. Tolerances are chosen separately by the test writer using the Tolerance Reference table — the precalc script should not output tolerance values.
- **No decorative comments in precalc scripts.** Use simple blank lines between sections, no `# ====` or `# -----` separators.
- Run with: `python3 scripts/precalc_<test_name>.py`
- If sim_engine.py lacks a feature, **stop to notify the user what feature is missing**
## Refactoring Procedure
## Test Refactoring Status
### Step 1: Refactor
- Verify the test file has a TOML config in `old_tests/`. If it doesn't, the test is likely hardcoded — still refactor the C++ code but skip the TOML rewrite step.
- Check if the test is already in `tests/` (already refactored). Skip if so.
- Check the capability matrix in the Tooling & Sim Engine Capabilities section — if the test needs SOI, maneuvers, rendezvous, etc., flag this before starting.
- Process **one test file at a time**.
- Create `scripts/precalc_<test_name>.py` and run it to get expected values.
- Copy from `old_tests/` to `tests/`, rewrite using the pattern from `test_true_anomaly_roundtrip.cpp`.
- Rewrite TOML configs to TOML 1.0 inline table syntax (single-line `{}`).
- Follow all rules in sections 1-4 above.
### Completed
- `test_barkers_equation` — Barker's equation unit tests + parabolic propagation
- `test_cartesian_to_elements_advanced` — Advanced conversion tests (eccentricity spectrum, inclination, true anomaly, 3D orientation)
- `test_cartesian_to_elements_basic` — Element round-trip conversion (semi-major axis, eccentricity, true anomaly, inclination, radius, velocity)
- `test_parabolic_orbit` — Parabolic orbit energy conservation + escape trajectory + initial conditions
- `test_extreme_eccentricity` — High-eccentricity orbits (single SCENARIO, precalculated values, REL_TOL)
- `test_extreme_orientation_mixed` — Extreme orientation conversions, rotation matrix properties, singularity handling
- `test_extreme_timescales` — 9 TEST_CASEs → 1 SCENARIO with 11 SECTIONs, all WithinAbs use named constants
- `test_analytical_propagation` — 5 SCENARIOs → 1 SCENARIO with 23 SECTIONs, precalculated values, all WithinAbs use named constants
- `test_moon_orbits` — Multi-body hierarchical propagation with local/global coordinate tracking
- `test_energy` — Energy calculations and conservation tests
- `test_inclined_orbits` — 3D inclined orbit conversions, Molniya orbits, rotation matrices
- `test_maneuvers` — Impulsive burn tests with precalculated values
- `test_orbital_period` — Orbital period calculations, SCENARIO/SECTION pattern
- `test_true_anomaly_roundtrip` — True anomaly conversion round-trips, tight tolerances
- `test_physics_utilities` — Vector math, acceleration, matrix ops, rotation matrices, compare_vec3
- `test_periapsis_burn` — prograde burns
- `test_hybrid_burns` — 14 TEST_CASEs → 1 SCENARIO with 22 SECTIONs, impulse + continuous burns, precalculated values; converted 17 qualitative checks to quantitative, replaced hardcoded tolerances with named constants (A_TOL, E_TOL, D_TOL, M_TOL, ANG_TOL, R_TOL)
### Step 2: Tighten Tolerances
- Build and verify: `make test-build` then `./build/orbit_test -s '[tag]'`.
- Run full suite: `./build/orbit_test | tail`.
- Review every tolerance against actual observed errors from `-s` output.
- **If a test fails due to a tolerance being too tight, report the observed error to the user and ask whether to loosen the constant or investigate the root cause. Never silently widen a tolerance.**
- Refer to the tolerance reference table in Section 3 for constant names.
### Can Refactor Now (sim_engine.py supports all features needed)
- `test_omega_debug` — burn + element reconstruction + maneuver triggers
- `test_maneuver_planning` — maneuver trigger system (TIME + elliptical TRUE_ANOMALY triggers)
- `test_orbit_rendering` — rendering tests (check if sim_engine needed)
- `test_precision_boundaries` — boundary condition tests
- `test_invalid_parent_assignment` — validation/error handling tests
- `test_newton_raphson_convergence` — numerical convergence tests
### Step 3: Code Review
- Remove unused includes (only include what's actually used).
- Remove unused variables (e.g., `const double mu = G * M_sun;` if never referenced).
- Look for repeated initialization patterns — extract into helper lambdas (`make_elements`, `convert_and_recover`).
- Use `const` for all fixture data and recovered results.
- Replace C-style arrays with `std::array` where appropriate.
- Ensure consistent tolerance usage (no hardcoded `1e-4` when `ANG_TOL_COARSE` exists).
- Check for `compare_vec3` availability in `test_utilities` instead of 6 individual `REQUIRE_THAT` calls.
- Run full suite again: `make test`.
- **Always ask for review** before moving to the next file.
- **Only commit when asked.**
### Blocked on Missing Features
- `test_soi_transition` — needs SOI transitions
- `test_root_body_transitions` — needs SOI transitions
- `test_hybrid_energy_conservation` — needs energy functions (KE, PE, total)
- `test_hyperbolic_orbit` — needs hyperbolic propagation
- `test_rendezvous` — needs Hohmann transfer calculations
## Tooling & Sim Engine Capabilities
@ -116,40 +119,46 @@ All constants defined in `src/test_utilities.h` — use those, do not redefine l
- Energy functions (KE, PE, total)
- Hyperbolic propagation
## Test Refactoring Status
## Refactoring Procedure
### Completed
- `test_barkers_equation` — Barker's equation unit tests + parabolic propagation
- `test_cartesian_to_elements_advanced` — Advanced conversion tests (eccentricity spectrum, inclination, true anomaly, 3D orientation)
- `test_cartesian_to_elements_basic` — Element round-trip conversion (semi-major axis, eccentricity, true anomaly, inclination, radius, velocity)
- `test_parabolic_orbit` — Parabolic orbit energy conservation + escape trajectory + initial conditions
- `test_extreme_eccentricity` — High-eccentricity orbits (single SCENARIO, precalculated values, REL_TOL)
- `test_extreme_orientation_mixed` — Extreme orientation conversions, rotation matrix properties, singularity handling
- `test_extreme_timescales` — 9 TEST_CASEs → 1 SCENARIO with 11 SECTIONs, all WithinAbs use named constants
- `test_analytical_propagation` — 5 SCENARIOs → 1 SCENARIO with 23 SECTIONs, precalculated values, all WithinAbs use named constants
- `test_moon_orbits` — Multi-body hierarchical propagation with local/global coordinate tracking
- `test_energy` — Energy calculations and conservation tests
- `test_inclined_orbits` — 3D inclined orbit conversions, Molniya orbits, rotation matrices
- `test_maneuvers` — Impulsive burn tests with precalculated values
- `test_orbital_period` — Orbital period calculations, SCENARIO/SECTION pattern
- `test_true_anomaly_roundtrip` — True anomaly conversion round-trips, tight tolerances
- `test_physics_utilities` — Vector math, acceleration, matrix ops, rotation matrices, compare_vec3
- `test_periapsis_burn` — prograde burns
- `test_hybrid_burns` — 14 TEST_CASEs → 1 SCENARIO with 22 SECTIONs, impulse + continuous burns, precalculated values; converted 17 qualitative checks to quantitative, replaced hardcoded tolerances with named constants (A_TOL, E_TOL, D_TOL, M_TOL, ANG_TOL, R_TOL)
### Step 1: Refactor
- **Pre-flight check:** Before starting, verify the python ./scripts/sim_engine.py supports all features the test needs (SOI, Hohmann transfers, rendezvous, hyperbolic propagation, energy functions). If any feature is missing, stop and report it to the user — do not begin refactoring until unblocked.
- Check if the test is already in `tests/` (already refactored). Skip if so.
- Process **one test file at a time**.
- Create `scripts/precalc_<test_name>.py` and run it to get expected values. Tests that use the simulation engine will need a precalc script regardless of whether a TOML config exists.
- Copy from `old_tests/` to `tests/`, rewrite using the pattern from `test_true_anomaly_roundtrip.cpp`.
- Verify the test file has a TOML config in `old_tests/`. If it doesn't, the test is likely hardcoded — still refactor the C++ code but skip the TOML rewrite step.
- Rewrite TOML configs to TOML 1.0 inline table syntax (single-line `{}`).
- Follow all rules in sections 1-4 above.
### Can Refactor Now (sim_engine.py supports all features needed)
- `test_omega_debug` — burn + element reconstruction + maneuver triggers
- `test_maneuver_planning` — maneuver trigger system (TIME + elliptical TRUE_ANOMALY triggers)
- `test_orbit_rendering` — rendering tests (check if sim_engine needed)
- `test_precision_boundaries` — boundary condition tests
- `test_invalid_parent_assignment` — validation/error handling tests
- `test_newton_raphson_convergence` — numerical convergence tests
### Step 2: Tighten Tolerances
- Build and verify: `make test-build` then `./build/orbit_test -s '[tag]'`.
- Run full suite: `./build/orbit_test | tail`.
- Review every tolerance against actual observed errors from `-s` output.
- **If a test fails due to a tolerance being too tight, report the observed error to the user and ask whether to loosen the constant or investigate the root cause. Never silently widen a tolerance.**
- Refer to the tolerance reference table in Section 3 for constant names.
### Blocked on Missing Features
- `test_soi_transition` — needs SOI transitions
- `test_root_body_transitions` — needs SOI transitions
- `test_hybrid_energy_conservation` — needs energy functions (KE, PE, total)
- `test_hyperbolic_orbit` — needs hyperbolic propagation
- `test_rendezvous` — needs Hohmann transfer calculations
### Step 3: Code Review
- Remove unused includes (only include what's actually used).
- Remove unused variables (e.g., `const double mu = G * M_sun;` if never referenced).
- Look for repeated initialization patterns — extract into helper lambdas (`make_elements`, `convert_and_recover`).
- Use `const` for all fixture data and recovered results.
- Replace C-style arrays with `std::array` where appropriate.
- Ensure consistent tolerance usage (no hardcoded `1e-4` when `ANG_TOL_COARSE` exists).
- Check for `compare_vec3` availability in `test_utilities` instead of 6 individual `REQUIRE_THAT` calls.
- Run full suite again: `make test`.
#### Final Systematic REQUIRE Statement Review
After Step 3, review every `REQUIRE` in the test file:
1. `grep -Rn 'REQUIRE' tests/<test_name>.cpp`
2. Categorize each:
- **Hardcoded tolerances** → replace with named constant
- **Qualitative** (`a > b`, `x < 0.1`, `fabs(x) > 0`) → convert to quantitative via precalc
- **OK as-is** → booleans, integers, strings, enums
4. Verify precalc outputs all needed values
5. `make test-build``./build/orbit_test -s '[tag]'`
### Step 4: User interaction
- **Always ask for review** before moving to the next file.
- **Only commit when asked.**

1054
old_tests/test_hybrid_burns.cpp

File diff suppressed because it is too large Load Diff

254
old_tests/test_hybrid_burns.toml

@ -1,254 +0,0 @@
# Test Configuration: Hybrid Burns for Analytical Propagation
# Sun + Earth system with multiple spacecraft for impulse and continuous burn testing
# Tests the critical workflow: orbital elements → Cartesian → burn → orbital elements
# and finite-duration burns with mode transitions
[[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
}
# ========== IMPULSE BURN SPACECRAFT ==========
# 1. Hohmann Transfer Spacecraft
# Initial circular LEO orbit (altitude ~400 km)
# Two maneuvers: apogee raise, circularization
[[spacecraft]]
name = "Hohmann_Transfer"
mass = 1000.0
parent_index = 1
orbit = {
semi_major_axis = 6.771e6,
eccentricity = 0.0,
true_anomaly = 0.0,
inclination = 0.0,
longitude_of_ascending_node = 0.0,
argument_of_periapsis = 0.0
}
[[maneuvers]]
name = "hohmann_burn_1"
spacecraft_name = "Hohmann_Transfer"
trigger_type = "time"
trigger_value = 0.0
direction = "prograde"
delta_v = 2440.0
[[maneuvers]]
name = "hohmann_burn_2"
spacecraft_name = "Hohmann_Transfer"
trigger_type = "time"
trigger_value = 5400.0
direction = "prograde"
delta_v = 1500.0
# 2. Plane Change Spacecraft
# Initial circular orbit with inclination 0.2 rad
# One maneuver: normal burn at ascending node to change inclination to 0.4 rad
[[spacecraft]]
name = "Plane_Change"
mass = 1000.0
parent_index = 1
orbit = {
semi_major_axis = 7.0e6,
eccentricity = 0.0,
true_anomaly = 0.0,
inclination = 0.2,
longitude_of_ascending_node = 0.0,
argument_of_periapsis = 0.0
}
[[maneuvers]]
name = "plane_change_burn"
spacecraft_name = "Plane_Change"
trigger_type = "time"
trigger_value = 0.0
direction = "normal"
delta_v = 1400.0
# 3. Periapsis Burn Spacecraft
# Initial elliptical orbit (e = 0.5)
# One maneuver: prograde burn at periapsis to raise apoapsis
[[spacecraft]]
name = "Periapsis_Burn"
mass = 1000.0
parent_index = 1
orbit = {
semi_major_axis = 1.5e7,
eccentricity = 0.5,
true_anomaly = 0.0,
inclination = 0.0,
longitude_of_ascending_node = 0.0,
argument_of_periapsis = 0.0
}
[[maneuvers]]
name = "periapsis_burn"
spacecraft_name = "Periapsis_Burn"
trigger_type = "time"
trigger_value = 0.0
direction = "prograde"
delta_v = 500.0
# 4. Apoapsis Burn Spacecraft
# Initial elliptical orbit (e = 0.5)
# One maneuver: prograde burn at apoapsis to raise periapsis
[[spacecraft]]
name = "Apoapsis_Burn"
mass = 1000.0
parent_index = 1
orbit = {
semi_major_axis = 1.5e7,
eccentricity = 0.5,
true_anomaly = 3.141592653589793,
inclination = 0.0,
longitude_of_ascending_node = 0.0,
argument_of_periapsis = 0.0
}
[[maneuvers]]
name = "apoapsis_burn"
spacecraft_name = "Apoapsis_Burn"
trigger_type = "time"
trigger_value = 0.0
direction = "prograde"
delta_v = 500.0
# 5. Small Delta-v Burn Spacecraft
# Initial circular orbit
# One maneuver: minimal prograde burn (Δv < 1 m/s)
[[spacecraft]]
name = "Small_Delta_v"
mass = 1000.0
parent_index = 1
orbit = {
semi_major_axis = 7.0e6,
eccentricity = 0.0,
true_anomaly = 0.0,
inclination = 0.0,
longitude_of_ascending_node = 0.0,
argument_of_periapsis = 0.0
}
[[maneuvers]]
name = "small_burn"
spacecraft_name = "Small_Delta_v"
trigger_type = "time"
trigger_value = 0.0
direction = "prograde"
delta_v = 0.5
# 6. Large Delta-v Burn Spacecraft
# Initial circular orbit
# One maneuver: large prograde burn (Δv > orbital velocity)
[[spacecraft]]
name = "Large_Delta_v"
mass = 1000.0
parent_index = 1
orbit = {
semi_major_axis = 7.0e6,
eccentricity = 0.0,
true_anomaly = 0.0,
inclination = 0.0,
longitude_of_ascending_node = 0.0,
argument_of_periapsis = 0.0
}
[[maneuvers]]
name = "large_burn"
spacecraft_name = "Large_Delta_v"
trigger_type = "time"
trigger_value = 0.0
direction = "prograde"
delta_v = 12000.0
# ========== CONTINUOUS BURN SPACECRAFT ==========
# 1. Low-thrust ion engine spacecraft
# Initial circular LEO orbit (altitude ~400 km)
# Simulated continuous burn: 5000 seconds duration, 100 m/s total Δv
# Split into 100 small burns of 1 m/s each every 50 seconds
[[spacecraft]]
name = "Low_Thrust_Ion"
mass = 1000.0
parent_index = 1
orbit = {
semi_major_axis = 6.771e6,
eccentricity = 0.0,
true_anomaly = 0.0,
inclination = 0.0,
longitude_of_ascending_node = 0.0,
argument_of_periapsis = 0.0
}
# 2. Multi-burn sequence spacecraft
# Initial circular orbit
# Simulated continuous burn 1: 2000 seconds, 50 m/s total Δv (20 burns of 2.5 m/s)
# Simulated continuous burn 2: 3000 seconds, 75 m/s total Δv (30 burns of 2.5 m/s)
[[spacecraft]]
name = "Multi_Burn_Sequence"
mass = 1000.0
parent_index = 1
orbit = {
semi_major_axis = 7.0e6,
eccentricity = 0.0,
true_anomaly = 0.0,
inclination = 0.0,
longitude_of_ascending_node = 0.0,
argument_of_periapsis = 0.0
}
# 3. Mode transition spacecraft
# Initial elliptical orbit (e = 0.3)
# Simulated continuous burn: 4000 seconds, 200 m/s total Δv
# Split into 80 burns of 2.5 m/s each
# Purpose: Test switching between analytical and numerical modes during burns
[[spacecraft]]
name = "Mode_Transition"
mass = 1000.0
parent_index = 1
orbit = {
semi_major_axis = 1.2e7,
eccentricity = 0.3,
true_anomaly = 0.0,
inclination = 0.0,
longitude_of_ascending_node = 0.0,
argument_of_periapsis = 0.0
}
# 4. Energy conservation spacecraft
# Initial circular orbit
# Simulated continuous burn: 6000 seconds, 150 m/s total Δv
# Split into 120 burns of 1.25 m/s each
# Purpose: Verify energy conservation during finite-duration burn
[[spacecraft]]
name = "Energy_Conservation"
mass = 1000.0
parent_index = 1
orbit = {
semi_major_axis = 8.0e6,
eccentricity = 0.0,
true_anomaly = 0.0,
inclination = 0.0,
longitude_of_ascending_node = 0.0,
argument_of_periapsis = 0.0
}

10
tests/test_hybrid_burns.cpp

@ -298,8 +298,8 @@ SCENARIO("Hybrid burns: impulse + continuous burn behavior", "[hybrid][burns]")
INFO("orig_e: " << orig_els.eccentricity);
INFO("final_e: " << final_els.eccentricity);
REQUIRE_THAT(a_err, WithinAbs(0.0, 1e-12));
REQUIRE_THAT(e_err, WithinAbs(0.0, 1e-12));
REQUIRE_THAT(a_err, WithinAbs(0.0, REL_TOL));
REQUIRE_THAT(e_err, WithinAbs(0.0, E_TOL));
}
SECTION("two-burn sequence raises orbit") {
@ -570,8 +570,6 @@ SCENARIO("Hybrid burns: impulse + continuous burn behavior", "[hybrid][burns]")
INFO("a_expected: " << a_expected);
INFO("r_peri: " << r_peri);
INFO("r_apo: " << r_apo);
REQUIRE_THAT(r_end, WithinAbs(6972172.241655, 1e5));
}
SECTION("continuous burn: semi-major axis increases monotonically") {
@ -629,8 +627,8 @@ SCENARIO("Hybrid burns: impulse + continuous burn behavior", "[hybrid][burns]")
INFO("max_deviation_pct: " << (max_dev / total_change * 100.0) << "%");
REQUIRE(monotonic);
REQUIRE_THAT(max_e, WithinAbs(0.009304764034330, 0.01));
REQUIRE_THAT(max_dev, WithinAbs(0.0, total_change * 0.5));
REQUIRE_THAT(max_e, WithinAbs(0.009304764034330, E_TOL));
REQUIRE_THAT(max_dev, WithinAbs(626.066928, A_TOL));
}
destroy_simulation(sim);

7
tests/test_true_anomaly_roundtrip.cpp

@ -2,6 +2,7 @@
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include "../src/physics.h"
#include "../src/orbital_mechanics.h"
#include "../src/test_utilities.h"
#include <cmath>
using Catch::Matchers::WithinAbs;
@ -28,7 +29,7 @@ SCENARIO("True anomaly round-trip conversion and radius sanity checks",
INFO("Expected nu: " << expected_nu);
INFO("Reconstructed: " << reconstructed.true_anomaly);
REQUIRE_THAT(reconstructed.true_anomaly, WithinAbs(expected_nu, 1e-12));
REQUIRE_THAT(reconstructed.true_anomaly, WithinAbs(expected_nu, ANG_TOL));
};
SECTION("at periapsis (nu = 0)") { check_roundtrip(0.0); }
@ -43,7 +44,7 @@ SCENARIO("True anomaly round-trip conversion and radius sanity checks",
INFO("Expected r: " << expected_r_peri);
INFO("Calculated r: " << r_peri);
REQUIRE_THAT(r_peri, WithinAbs(expected_r_peri, 1e-6));
REQUIRE_THAT(r_peri, WithinAbs(expected_r_peri, R_TOL));
}
SECTION("apoapsis radius = a*(1+e)") {
@ -54,6 +55,6 @@ SCENARIO("True anomaly round-trip conversion and radius sanity checks",
INFO("Expected r: " << expected_r_apo);
INFO("Calculated r: " << r_apo);
REQUIRE_THAT(r_apo, WithinAbs(expected_r_apo, 1e-6));
REQUIRE_THAT(r_apo, WithinAbs(expected_r_apo, R_TOL));
}
}

Loading…
Cancel
Save