From 9c455ccea583b8a190e78cae09a9696bb528f0d1 Mon Sep 17 00:00:00 2001 From: cinnaboot Date: Mon, 27 Apr 2026 13:59:43 -0400 Subject: [PATCH] refactor: true_anomaly_roundtrip - SCENARIO/SECTION pattern, tight tolerances --- tests/test_true_anomaly_roundtrip.cpp | 258 +++++++++++++------------- 1 file changed, 134 insertions(+), 124 deletions(-) diff --git a/tests/test_true_anomaly_roundtrip.cpp b/tests/test_true_anomaly_roundtrip.cpp index 26f282e..7d8a908 100644 --- a/tests/test_true_anomaly_roundtrip.cpp +++ b/tests/test_true_anomaly_roundtrip.cpp @@ -4,130 +4,140 @@ #include "../src/orbital_mechanics.h" #include -TEST_CASE("True anomaly round-trip conversion at periapsis", "[orbital_elements][true_anomaly]") { - double parent_mass = 5.972e24; - - OrbitalElements elements = {0}; - elements.semi_major_axis = 7000e3; - elements.eccentricity = 0.3; - elements.true_anomaly = 0.0; - elements.inclination = 0.0; - elements.longitude_of_ascending_node = 0.0; - elements.argument_of_periapsis = 0.0; - - Vec3 pos, vel; - orbital_elements_to_cartesian(elements, parent_mass, &pos, &vel); - OrbitalElements reconstructed = cartesian_to_orbital_elements(pos, vel, parent_mass); - - INFO("Original true_anomaly: " << elements.true_anomaly); - INFO("Reconstructed true_anomaly: " << reconstructed.true_anomaly); - - REQUIRE_THAT(reconstructed.true_anomaly, - Catch::Matchers::WithinAbs(elements.true_anomaly, 0.01)); +SCENARIO("True anomaly round-trip conversion preserves values", + "[orbital_elements][true_anomaly]") { + const double parent_mass = 5.972e24; + const double a = 7000e3; + const double e = 0.3; + + SECTION("at periapsis (nu = 0)") { + const double expected_nu = 0.0; + + OrbitalElements elements = {0}; + elements.semi_major_axis = a; + elements.eccentricity = e; + elements.true_anomaly = expected_nu; + elements.inclination = 0.0; + elements.longitude_of_ascending_node = 0.0; + elements.argument_of_periapsis = 0.0; + + Vec3 pos, vel; + orbital_elements_to_cartesian(elements, parent_mass, &pos, &vel); + OrbitalElements reconstructed = cartesian_to_orbital_elements(pos, vel, parent_mass); + + INFO("Expected nu: " << expected_nu); + INFO("Reconstructed: " << reconstructed.true_anomaly); + + REQUIRE_THAT(reconstructed.true_anomaly, + Catch::Matchers::WithinAbs(expected_nu, 1e-12)); + } + + SECTION("at apoapsis (nu = pi)") { + const double expected_nu = M_PI; + + OrbitalElements elements = {0}; + elements.semi_major_axis = a; + elements.eccentricity = e; + elements.true_anomaly = expected_nu; + elements.inclination = 0.0; + elements.longitude_of_ascending_node = 0.0; + elements.argument_of_periapsis = 0.0; + + Vec3 pos, vel; + orbital_elements_to_cartesian(elements, parent_mass, &pos, &vel); + OrbitalElements reconstructed = cartesian_to_orbital_elements(pos, vel, parent_mass); + + INFO("Expected nu: " << expected_nu); + INFO("Reconstructed: " << reconstructed.true_anomaly); + + REQUIRE_THAT(reconstructed.true_anomaly, + Catch::Matchers::WithinAbs(expected_nu, 1e-12)); + } + + SECTION("at 90 degrees (nu = pi/2)") { + const double expected_nu = M_PI / 2.0; + + OrbitalElements elements = {0}; + elements.semi_major_axis = a; + elements.eccentricity = e; + elements.true_anomaly = expected_nu; + elements.inclination = 0.0; + elements.longitude_of_ascending_node = 0.0; + elements.argument_of_periapsis = 0.0; + + Vec3 pos, vel; + orbital_elements_to_cartesian(elements, parent_mass, &pos, &vel); + OrbitalElements reconstructed = cartesian_to_orbital_elements(pos, vel, parent_mass); + + INFO("Expected nu: " << expected_nu); + INFO("Reconstructed: " << reconstructed.true_anomaly); + + REQUIRE_THAT(reconstructed.true_anomaly, + Catch::Matchers::WithinAbs(expected_nu, 1e-12)); + } + + SECTION("at 270 degrees (nu = 3*pi/2)") { + const double expected_nu = 3.0 * M_PI / 2.0; + + OrbitalElements elements = {0}; + elements.semi_major_axis = a; + elements.eccentricity = e; + elements.true_anomaly = expected_nu; + elements.inclination = 0.0; + elements.longitude_of_ascending_node = 0.0; + elements.argument_of_periapsis = 0.0; + + Vec3 pos, vel; + orbital_elements_to_cartesian(elements, parent_mass, &pos, &vel); + OrbitalElements reconstructed = cartesian_to_orbital_elements(pos, vel, parent_mass); + + INFO("Expected nu: " << expected_nu); + INFO("Reconstructed: " << reconstructed.true_anomaly); + + REQUIRE_THAT(reconstructed.true_anomaly, + Catch::Matchers::WithinAbs(expected_nu, 1e-12)); + } } -TEST_CASE("True anomaly round-trip conversion at apoapsis", "[orbital_elements][true_anomaly]") { - double parent_mass = 5.972e24; - - OrbitalElements elements = {0}; - elements.semi_major_axis = 7000e3; - elements.eccentricity = 0.3; - elements.true_anomaly = M_PI; - elements.inclination = 0.0; - elements.longitude_of_ascending_node = 0.0; - elements.argument_of_periapsis = 0.0; - - Vec3 pos, vel; - orbital_elements_to_cartesian(elements, parent_mass, &pos, &vel); - OrbitalElements reconstructed = cartesian_to_orbital_elements(pos, vel, parent_mass); - - INFO("Original true_anomaly: " << elements.true_anomaly); - INFO("Reconstructed true_anomaly: " << reconstructed.true_anomaly); - - REQUIRE_THAT(reconstructed.true_anomaly, - Catch::Matchers::WithinAbs(elements.true_anomaly, 0.01)); -} - -TEST_CASE("True anomaly round-trip conversion at 90 degrees", "[orbital_elements][true_anomaly]") { - double parent_mass = 5.972e24; - - OrbitalElements elements = {0}; - elements.semi_major_axis = 7000e3; - elements.eccentricity = 0.3; - elements.true_anomaly = M_PI / 2.0; - elements.inclination = 0.0; - elements.longitude_of_ascending_node = 0.0; - elements.argument_of_periapsis = 0.0; - - Vec3 pos, vel; - orbital_elements_to_cartesian(elements, parent_mass, &pos, &vel); - OrbitalElements reconstructed = cartesian_to_orbital_elements(pos, vel, parent_mass); - - INFO("Original true_anomaly: " << elements.true_anomaly); - INFO("Reconstructed true_anomaly: " << reconstructed.true_anomaly); - - REQUIRE_THAT(reconstructed.true_anomaly, - Catch::Matchers::WithinAbs(elements.true_anomaly, 0.01)); -} - -TEST_CASE("True anomaly round-trip conversion at 270 degrees", "[orbital_elements][true_anomaly]") { - double parent_mass = 5.972e24; - - OrbitalElements elements = {0}; - elements.semi_major_axis = 7000e3; - elements.eccentricity = 0.3; - elements.true_anomaly = 3.0 * M_PI / 2.0; - elements.inclination = 0.0; - elements.longitude_of_ascending_node = 0.0; - elements.argument_of_periapsis = 0.0; - - Vec3 pos, vel; - orbital_elements_to_cartesian(elements, parent_mass, &pos, &vel); - OrbitalElements reconstructed = cartesian_to_orbital_elements(pos, vel, parent_mass); - - INFO("Original true_anomaly: " << elements.true_anomaly); - INFO("Reconstructed true_anomaly: " << reconstructed.true_anomaly); - - REQUIRE_THAT(reconstructed.true_anomaly, - Catch::Matchers::WithinAbs(elements.true_anomaly, 0.01)); -} - -TEST_CASE("Radius at periapsis matches expected value", "[orbital_elements][sanity]") { - double parent_mass = 5.972e24; - - OrbitalElements peri = {0}; - peri.semi_major_axis = 7000e3; - peri.eccentricity = 0.3; - peri.true_anomaly = 0.0; - - Vec3 pos, vel; - orbital_elements_to_cartesian(peri, parent_mass, &pos, &vel); - double r_peri = vec3_magnitude(pos); - double expected_peri = peri.semi_major_axis * (1.0 - peri.eccentricity); - - INFO("At true_anomaly=0:"); - INFO(" Calculated radius: " << r_peri); - INFO(" Expected: " << expected_peri); - - REQUIRE_THAT(r_peri, Catch::Matchers::WithinAbs(expected_peri, 1.0)); -} - -TEST_CASE("Radius at apoapsis matches expected value", "[orbital_elements][sanity]") { - double parent_mass = 5.972e24; - - OrbitalElements apo = {0}; - apo.semi_major_axis = 7000e3; - apo.eccentricity = 0.3; - apo.true_anomaly = M_PI; - - Vec3 pos, vel; - orbital_elements_to_cartesian(apo, parent_mass, &pos, &vel); - double r_apo = vec3_magnitude(pos); - double expected_apo = apo.semi_major_axis * (1.0 + apo.eccentricity); - - INFO("At true_anomaly=pi:"); - INFO(" Calculated radius: " << r_apo); - INFO(" Expected: " << expected_apo); - - REQUIRE_THAT(r_apo, Catch::Matchers::WithinAbs(expected_apo, 1.0)); +SCENARIO("Radius at periapsis and apoapsis matches analytical formula", + "[orbital_elements][sanity]") { + const double parent_mass = 5.972e24; + const double a = 7000e3; + const double e = 0.3; + const double expected_r_peri = a * (1.0 - e); // 4900000.0 + const double expected_r_apo = a * (1.0 + e); // 9100000.0 + + SECTION("periapsis radius = a*(1-e)") { + OrbitalElements peri = {0}; + peri.semi_major_axis = a; + peri.eccentricity = e; + peri.true_anomaly = 0.0; + + Vec3 pos, vel; + orbital_elements_to_cartesian(peri, parent_mass, &pos, &vel); + const double r_peri = vec3_magnitude(pos); + + INFO("Expected r: " << expected_r_peri); + INFO("Calculated r: " << r_peri); + + REQUIRE_THAT(r_peri, + Catch::Matchers::WithinAbs(expected_r_peri, 1e-6)); + } + + SECTION("apoapsis radius = a*(1+e)") { + OrbitalElements apo = {0}; + apo.semi_major_axis = a; + apo.eccentricity = e; + apo.true_anomaly = M_PI; + + Vec3 pos, vel; + orbital_elements_to_cartesian(apo, parent_mass, &pos, &vel); + const double r_apo = vec3_magnitude(pos); + + INFO("Expected r: " << expected_r_apo); + INFO("Calculated r: " << r_apo); + + REQUIRE_THAT(r_apo, + Catch::Matchers::WithinAbs(expected_r_apo, 1e-6)); + } }