vibe coding an orbital mechanics simulation to try out claude code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

242 lines
8.3 KiB

#include <catch2/catch_test_macros.hpp>
#include "../src/physics.h"
#include "../src/orbital_mechanics.h"
#include "../src/simulation.h"
#include "../src/config_loader.h"
#include <cmath>
#include <limits>
const double CONVERGENCE_TOLERANCE = 1.0e-10;
const int MAX_ITERATIONS = 50;
TEST_CASE("Newton-Raphson solver - very low eccentricity (e < 0.01)", "[newton][raphson][low_e]") {
const double eccentricities[] = {0.001, 0.01};
for (int i = 0; i < 2; i++) {
double e = eccentricities[i];
INFO("Testing eccentricity: " << e);
double mean_anomaly = M_PI / 2.0;
double eccentric_anomaly = solve_kepler_elliptical(mean_anomaly, e);
// Verify Kepler's equation is satisfied: E - e*sin(E) = M
// Note: E ≈ M only when e=0 exactly. For small e, E ≈ M + e*sin(M)
double kepler_residual = eccentric_anomaly - e * sin(eccentric_anomaly) - mean_anomaly;
double first_order_approx = mean_anomaly + e * sin(mean_anomaly);
double approximation_error = fabs(eccentric_anomaly - first_order_approx);
INFO("Eccentric anomaly: " << eccentric_anomaly << " rad");
INFO("Mean anomaly: " << mean_anomaly << " rad");
INFO("Kepler's equation residual |E - e·sin(E) - M|: " << kepler_residual);
INFO("First-order approx E ≈ M + e·sin(M): " << first_order_approx << " rad");
INFO("|E - approx|: " << approximation_error);
// Verify solver correctly solves Kepler's equation
REQUIRE(fabs(kepler_residual) < CONVERGENCE_TOLERANCE);
// Verify result matches first-order approximation (valid for small e)
REQUIRE(approximation_error < 0.01);
}
}
TEST_CASE("Newton-Raphson solver - moderate eccentricity (0.1 < e < 0.5)", "[newton][raphson][moderate_e]") {
const double eccentricities[] = {0.1, 0.3, 0.5};
for (int i = 0; i < 3; i++) {
double e = eccentricities[i];
INFO("Testing eccentricity: " << e);
double mean_anomaly = M_PI / 4.0;
double eccentric_anomaly = solve_kepler_elliptical(mean_anomaly, e);
double rhs = mean_anomaly + e * sin(eccentric_anomaly);
double residual = eccentric_anomaly - rhs;
INFO("Eccentric anomaly: " << eccentric_anomaly << " rad");
INFO("Residual E - (M + e*sin(E)): " << residual);
REQUIRE(fabs(residual) < CONVERGENCE_TOLERANCE);
}
}
TEST_CASE("Newton-Raphson solver - high eccentricity (0.9 < e < 0.99)", "[newton][raphson][high_e]") {
const double eccentricities[] = {0.9, 0.95, 0.99};
for (int i = 0; i < 3; i++) {
double e = eccentricities[i];
INFO("Testing eccentricity: " << e);
double mean_anomaly = M_PI / 2.0;
int iterations = 0;
double E = get_initial_trial_value(mean_anomaly, e);
double E_prev = E + 2.0 * CONVERGENCE_TOLERANCE;
while (fabs(E - E_prev) > CONVERGENCE_TOLERANCE && iterations < MAX_ITERATIONS) {
E_prev = E;
double sin_E = sin(E);
E = E - (E - e * sin_E - mean_anomaly) / (1.0 - e * cos(E));
iterations++;
}
INFO("Converged in " << iterations << " iterations");
INFO("Eccentric anomaly: " << E << " rad");
double rhs = mean_anomaly + e * sin(E);
double residual = E - rhs;
INFO("Residual E - (M + e*sin(E)): " << residual);
REQUIRE(iterations < MAX_ITERATIONS);
REQUIRE(fabs(residual) < CONVERGENCE_TOLERANCE);
}
}
TEST_CASE("Newton-Raphson solver - mean anomaly near π (worst case)", "[newton][raphson][near_pi]") {
const double eccentricity = 0.7;
const double mean_anomalies[] = {M_PI - 0.01, M_PI, M_PI + 0.01};
for (int i = 0; i < 3; i++) {
double M = mean_anomalies[i];
INFO("Testing mean anomaly: " << M << " rad (" << (M * 180.0 / M_PI) << "°)");
int iterations = 0;
double E = get_initial_trial_value(M, eccentricity);
double E_prev = E + 2.0 * CONVERGENCE_TOLERANCE;
while (fabs(E - E_prev) > CONVERGENCE_TOLERANCE && iterations < MAX_ITERATIONS) {
E_prev = E;
double sin_E = sin(E);
E = E - (E - eccentricity * sin_E - M) / (1.0 - eccentricity * cos(E));
iterations++;
}
INFO("Converged in " << iterations << " iterations");
double rhs = M + eccentricity * sin(E);
double residual = E - rhs;
INFO("Residual E - (M + e*sin(E)): " << residual);
REQUIRE(iterations < MAX_ITERATIONS);
REQUIRE(fabs(residual) < CONVERGENCE_TOLERANCE);
}
}
TEST_CASE("Newton-Raphson solver - large mean anomaly values (M > 1000)", "[newton][raphson][large_M]") {
const double eccentricity = 0.3;
const double mean_anomalies[] = {1000.0, 10000.0};
for (int i = 0; i < 2; i++) {
double M = mean_anomalies[i];
INFO("Testing mean anomaly: " << M << " rad");
int iterations = 0;
double E = get_initial_trial_value(M, eccentricity);
double E_prev = E + 2.0 * CONVERGENCE_TOLERANCE;
while (fabs(E - E_prev) > CONVERGENCE_TOLERANCE && iterations < MAX_ITERATIONS) {
E_prev = E;
double sin_E = sin(E);
E = E - (E - eccentricity * sin_E - M) / (1.0 - eccentricity * cos(E));
iterations++;
}
INFO("Converged in " << iterations << " iterations");
double rhs = M + eccentricity * sin(E);
double residual = E - rhs;
INFO("Residual E - (M + e*sin(E)): " << residual);
REQUIRE(iterations < MAX_ITERATIONS);
REQUIRE(fabs(residual) < CONVERGENCE_TOLERANCE);
double M_reduced = fmod(E - eccentricity * sin(E), 2.0 * M_PI);
double M_target = fmod(M, 2.0 * M_PI);
double angle_diff = fabs(M_reduced - M_target);
if (angle_diff > M_PI) {
angle_diff = 2.0 * M_PI - angle_diff;
}
INFO("Reduced mean anomaly: " << M_reduced << " rad");
INFO("Target reduced: " << M_target << " rad");
INFO("Angle difference: " << angle_diff << " rad");
REQUIRE(angle_diff < CONVERGENCE_TOLERANCE * 10.0);
}
}
TEST_CASE("Newton-Raphson solver - eccentricity at boundaries (e ≈ 1.0)", "[newton][raphson][boundary]") {
const double eccentricities[] = {0.9999, 1.0001};
for (int i = 0; i < 2; i++) {
double e = eccentricities[i];
INFO("Testing eccentricity: " << e);
double M = M_PI / 4.0;
int iterations = 0;
double E = get_initial_trial_value(M, e);
double E_prev = E + 2.0 * CONVERGENCE_TOLERANCE;
while (fabs(E - E_prev) > CONVERGENCE_TOLERANCE && iterations < MAX_ITERATIONS) {
E_prev = E;
double sin_E = sin(E);
E = E - (E - e * sin_E - M) / (1.0 - e * cos(E));
iterations++;
}
INFO("Converged in " << iterations << " iterations");
if (fabs(1.0 - e * cos(E)) > 1.0e-10) {
double rhs = M + e * sin(E);
double residual = E - rhs;
INFO("Residual E - (M + e*sin(E)): " << residual);
REQUIRE(fabs(residual) < CONVERGENCE_TOLERANCE);
}
}
}
TEST_CASE("Newton-Raphson solver convergence rate", "[newton][raphson][convergence_rate]") {
const double eccentricity = 0.8;
const double mean_anomaly = M_PI / 3.0;
double E = get_initial_trial_value(mean_anomaly, eccentricity);
INFO("Initial guess: " << E << " rad");
double previous_residual = std::numeric_limits<double>::max();
int iteration = 0;
int convergence_count = 0;
for (int i = 0; i < 10; i++) {
double sin_E = sin(E);
double rhs = mean_anomaly + eccentricity * sin_E;
double residual = fabs(E - rhs);
INFO("Iteration " << i << ": E = " << E << ", residual = " << residual);
if (residual < CONVERGENCE_TOLERANCE) {
INFO("Converged at iteration " << i);
break;
}
if (i > 0 && residual < previous_residual * 0.5) {
convergence_count++;
}
previous_residual = residual;
E = E - (E - eccentricity * sin_E - mean_anomaly) / (1.0 - eccentricity * cos(E));
iteration++;
}
double convergence_ratio = (double)convergence_count / (double)iteration;
INFO("Quadratic convergence ratio: " << convergence_ratio * 100.0 << "%");
REQUIRE(convergence_ratio > 0.6);
}