Skip to content

Instantly share code, notes, and snippets.

@rikusalminen
Last active August 29, 2015 14:02
Show Gist options
  • Select an option

  • Save rikusalminen/16d46477e0e9e976d757 to your computer and use it in GitHub Desktop.

Select an option

Save rikusalminen/16d46477e0e9e976d757 to your computer and use it in GitHub Desktop.
Kepler problem in C
struct kepler_elements {
double semi_latus_rectum;
double eccentricity;
double mean_motion;
double inclination;
double longitude_of_ascending_node;
double argument_of_periapsis;
double periapsis_time;
};
#include <stdbool.h>
#include <math.h>
#include <float.h>
void cross(const double *a, const double *b, double *c) {
c[0] = a[1]*b[2] - a[2]*b[1];
c[1] = -(a[0]*b[2] - a[2]*b[0]);
c[2] = a[0]*b[1] - a[1]*b[0];
}
double dot(const double *a, const double *b) {
return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
}
double mag(const double *a) {
return sqrt(dot(a, a));
}
double clamp(double min, double max, double x){
return (x < min ? min : (x > max ? max : x));
}
bool zero(double x) {
return x*x < DBL_EPSILON;
//return fabs(x) < DBL_EPSILON;
}
double sign(double x) { return x < 0 ? -1.0 : 1.0; }
double square(double x) { return x*x; }
double cube(double x) { return x*x*x; }
static void transpose3x3(double *m)
{
for(int i = 0; i < 2; ++i) {
for(int j = i; j < 3; ++j) {
double temp = m[i * 3 + j];
m[i * 3 + j] = m[j * 3 + i];
m[j * 3 + i] = temp;
}
}
}
static void matrix_vector_product(const double *m, const double *v, double *x) {
for(int i = 0; i < 3; ++i) {
x[i] = 0.0;
for(int j = 0; j < 3; ++j) {
x[i] += m[3 * i + j] * v[j];
//x[i] += m[3 * j + i] * v[j];
}
}
}
static inline double kepler_iter1(double e, double M, double x) {
return M + e * sin(x);
}
static inline double kepler_iter2(double e, double M, double x) {
return x + (M + e * sin(x) - x) / (1.0 - e * cos(x));
}
static inline double kepler_iter3(double e, double M, double x) {
double s = e * sin(x);
double c = e * cos(x);
double f0 = x - s - M;
double f1 = 1.0 - c;
double f2 = s;
return x + (-5.0) * f0 / (f1 + sign(f1) * sqrt(fabs(16.0 * f1 * f1 - 20.0 * f0 * f2)));
}
static inline double kepler_iter4(double e, double M, double x) {
double s = e * sinh(x);
double c = e * cosh(x);
double f0 = s - x - M;
double f1 = c - 1.0;
double f2 = s;
return x + (-5.0) * f0 / (f1 + sign(f1) * sqrt(fabs(16.0 * f1 * f1 - 20.0 * f0 * f2)));
}
double kepler_anomaly_mean_to_eccentric(double e, double M) {
if(zero(e - 1.0)) {
// parabolic anomaly
double x = pow(sqrt(9.0*M*M + 1.0) + 3.0*M, 1.0/3.0);
return x - 1.0/x;
}
typedef double (*iter_func)(double, double, double);
iter_func iter = 0;
if(e < 0.3) iter = kepler_iter1;
else if(e < 0.9) iter = kepler_iter2;
else if(e < 1.0) iter = kepler_iter3;
else if(e > 1.0) iter = kepler_iter4;
int num_steps = 1;
if(e > 0.0 && e < 0.3) num_steps = 10;
else if(e < 0.9) num_steps = 20;
else if(e < 1.0) num_steps = 20;
else if(e > 1.0) num_steps = 30;
double threshold = DBL_EPSILON;
double x = M, x0 = x;
do {
x0 = x;
x = iter(e, M, x);
} while(--num_steps && (x0-x)*(x0-x) > threshold);
return x;
}
double kepler_anomaly_eccentric_to_mean(double e, double E) {
if(zero(e - 1.0))
// parabolic anomaly
return E*E*E/6.0 + E/2.0;
if(e > 1.0)
return e * sinh(E) - E;
return E - e * sin(E);
}
double kepler_anomaly_eccentric_to_true(double e, double E) {
if(zero(e - 1.0))
return 2.0 * atan(E);
if(e > 1.0)
return 2.0 * atan(sqrt((e+1.0) / (e-1.0)) * tanh(E/2.0));
return atan2(sqrt(1.0-e*e) * sin(E), cos(E) - e);
}
double kepler_anomaly_true_to_eccentric(double e, double f) {
if(zero(e - 1.0))
return tan(f / 2.0);
if(e > 1.0)
return sign(f) * acosh((e + cos(f)) / (1.0 + e * cos(f)));
return atan2(sqrt(1.0-e*e) * sin(f), cos(f) + e);
}
double kepler_anomaly_true_to_mean(double e, double f) {
return kepler_anomaly_eccentric_to_mean(e, kepler_anomaly_true_to_eccentric(e, f));
}
double kepler_anomaly_mean_to_true(double e, double M) {
return kepler_anomaly_eccentric_to_true(e, kepler_anomaly_mean_to_eccentric(e, M));
}
double kepler_anomaly_dEdM(double e, double E) {
if(zero(e - 1.0))
return 2.0 / (E*E + 1.0);
if(e > 1.0)
return 1.0 / (e*cosh(E) - 1.0);
return 1.0 / (1.0 - e * cos(E));
}
bool kepler_orbit_parabolic(const struct kepler_elements *elements) {
return zero(elements->eccentricity - 1.0);
}
bool kepler_orbit_hyperbolic(const struct kepler_elements *elements) {
return elements->eccentricity > 1.0 && !kepler_orbit_parabolic(elements);
}
bool kepler_orbit_closed(const struct kepler_elements *elements) {
return elements->eccentricity < 1.0 && !kepler_orbit_parabolic(elements);
}
bool kepler_orbit_circular(const struct kepler_elements *elements) {
return zero(elements->eccentricity);
}
double kepler_orbit_semi_major_axis(const struct kepler_elements *elements) {
if(kepler_orbit_parabolic(elements))
return INFINITY;
return elements->semi_latus_rectum / (1.0 - square(elements->eccentricity));
}
double kepler_orbit_semi_minor_axis(const struct kepler_elements *elements) {
if(kepler_orbit_parabolic(elements))
return INFINITY;
if(elements->eccentricity > 1.0)
return -elements->semi_latus_rectum / sqrt(square(elements->eccentricity) - 1.0);
return elements->semi_latus_rectum / sqrt(1.0 - square(elements->eccentricity));
}
double kepler_orbit_gravity_parameter(const struct kepler_elements *elements) {
if(kepler_orbit_parabolic(elements))
return square(elements->mean_motion) * cube(elements->semi_latus_rectum);
return square(elements->mean_motion) *
cube(fabs(kepler_orbit_semi_major_axis(elements)));
}
double kepler_orbit_specific_orbital_energy(const struct kepler_elements *elements) {
if(kepler_orbit_parabolic(elements))
return 0.0;
return -kepler_orbit_gravity_parameter(elements) /
(2.0 * kepler_orbit_semi_major_axis(elements));
}
double kepler_orbit_specific_angular_momentum(const struct kepler_elements *elements) {
return sqrt(elements->semi_latus_rectum * kepler_orbit_gravity_parameter(elements));
}
double kepler_orbit_apoapsis(const struct kepler_elements *elements) {
if(elements->eccentricity >= 1.0)
return INFINITY;
return elements->semi_latus_rectum / (1 - elements->eccentricity);
}
double kepler_orbit_periapsis(const struct kepler_elements *elements) {
return elements->semi_latus_rectum / (1 + elements->eccentricity);
}
double kepler_orbit_apoapsis_vel(const struct kepler_elements *elements) {
if(elements->eccentricity >= 1.0)
return INFINITY;
double mu = kepler_orbit_gravity_parameter(elements);
return sqrt((mu / elements->semi_latus_rectum) * square(1.0 - elements->eccentricity));
}
double kepler_orbit_periapsis_vel(const struct kepler_elements *elements) {
double mu = kepler_orbit_gravity_parameter(elements);
return sqrt((mu / elements->semi_latus_rectum) * square(1.0 + elements->eccentricity));
}
double kepler_orbit_period(const struct kepler_elements *elements) {
if(!kepler_orbit_closed(elements))
return INFINITY;
return 2.0 * M_PI / elements->mean_motion;
}
double kepler_orbit_mean_anomaly_at_time(const struct kepler_elements *elements, double t) {
double M = elements->mean_motion * (t - elements->periapsis_time);
if(kepler_orbit_closed(elements) && fabs(M) >= M_PI) {
double x = (M+M_PI)/(2.0*M_PI);
M = -M_PI + 2.0*M_PI * (x - floor(x));
}
return M;
}
void kepler_orientation_normal(double i, double an, double arg, double *dir) {
(void)arg;
dir[0] = sin(an) * sin(i);
dir[1] = -cos(an) * sin(i);
dir[2] = cos(i);
}
void kepler_orientation_tangent(double i, double an, double arg, double *dir) {
dir[0] = (cos(arg) * cos(an)) - (sin(arg) * sin(an) * cos(i));
dir[1] = (sin(arg) * cos(an) * cos(i)) + (cos(arg) * sin(an));
dir[2] = sin(arg) * sin(i);
}
void kepler_orientation_bitangent(double i, double an, double arg, double *dir) {
dir[0] = -(cos(arg) * sin(an) * cos(i)) - (sin(arg) * cos(an));
dir[1] = (cos(arg) * cos(an) * cos(i)) - (sin(arg) * sin(an));
dir[2] = cos(arg) * sin(i);
}
void kepler_orientation_matrix(double i, double an, double arg, double *mat) {
kepler_orientation_tangent(i, an, arg, mat+0);
kepler_orientation_bitangent(i, an, arg, mat+3);
kepler_orientation_normal(i, an, arg, mat+6);
transpose3x3(mat);
}
void kepler_orbit_normal(const struct kepler_elements *elements, double *dir) {
kepler_orientation_normal(
elements->inclination,
elements->longitude_of_ascending_node,
elements->argument_of_periapsis,
dir);
}
void kepler_orbit_tangent(const struct kepler_elements *elements, double *dir) {
kepler_orientation_tangent(
elements->inclination,
elements->longitude_of_ascending_node,
elements->argument_of_periapsis,
dir);
}
void kepler_orbit_bitangent(const struct kepler_elements *elements, double *dir) {
kepler_orientation_bitangent(
elements->inclination,
elements->longitude_of_ascending_node,
elements->argument_of_periapsis,
dir);
}
void kepler_orbit_matrix(const struct kepler_elements *elements, double *mat) {
kepler_orientation_matrix(
elements->inclination,
elements->longitude_of_ascending_node,
elements->argument_of_periapsis,
mat);
}
void kepler_elements_from_state(
double mu,
const double *pos,
const double *vel,
double epoch,
struct kepler_elements *elements) {
double r = mag(pos);
double v2 = dot(vel, vel);
// specific angular momentum
double h[3];
cross(pos, vel, h);
// TODO: check for radial trajectory
// eccentricity vector, direction: to periapsis, magnitude: eccentricity
// e = 1/mu * (v^2 - mu/r) * r - dot(r, v) * v;
double ecc[3];
for(int i = 0; i < 3; ++i)
ecc[i] = (1.0 / mu) * (pos[i]*(v2 - mu/r) - vel[i]*dot(pos, vel));
double e = mag(ecc);
bool circular = zero(e);
bool parabolic = zero(e - 1.0);
// line of nodes, pointing to ascending node, equatorial -> zero
double nodes[3] = { -h[1], h[0], 0.0 };
double N = mag(nodes);
bool equatorial = zero(dot(nodes, nodes));
// semi-latus rectum
double p = dot(h, h) / mu;
// inclination
double i = acos(clamp(-1.0, 1.0, h[2] / mag(h)));
// longitude of ascending node
double an = equatorial ? 0.0 : atan2(nodes[1], nodes[0]);
// argument of periapsis
double arg = 0.0 / 0.0; // NaN
if(circular)
// circular, zero
arg = 0.0;
else if(equatorial)
// equatorial, measure from X-axis, negative for retrograde
arg = sign(h[2]) * atan2(ecc[1], ecc[0]);
else
// angle between eccentricity vector and line of nodes (ascending node)
arg = sign(ecc[2]) * acos(clamp(-1.0, 1.0, dot(nodes, ecc) / (N * e)));
// true anomaly
double f = 0.0 / 0.0; // NaN
if(circular && equatorial)
// circular, equatorial -> measure from X-axis, negative for retrograde
f = sign(h[2]) * atan2(pos[1], pos[0]);
else if(circular)
// circular orbit -> measure from ascending node
f = -sign(dot(vel, nodes)) *
acos(clamp(-1.0, 1.0, dot(nodes, pos) / (N * r)));
else
// measure true anomaly from periapsis (eccentricity vector)
f = -sign(dot(vel, ecc)) *
acos(clamp(-1.0, 1.0, dot(ecc, pos) / (e * r)));
// mean anomaly at epoch
double M0 = kepler_anomaly_true_to_mean(e, f);
// mean motion
double a = p / (1.0 - e*e);
double n = parabolic ?
sqrt(mu / (p*p*p)) :
sqrt(mu / fabs(a*a*a));
// time at periapsis
double periapsis_time = epoch - M0 / n;
elements->semi_latus_rectum = p;
elements->eccentricity = e;
elements->mean_motion = n;
elements->inclination = i;
elements->longitude_of_ascending_node = an;
elements->argument_of_periapsis = arg;
elements->periapsis_time = periapsis_time;
}
void kepler_elements_to_state_f(
const struct kepler_elements *elements,
double f,
double *pos,
double *vel) {
// generic conic trajectory with true anomaly and vis-viva equation
double p = elements->semi_latus_rectum;
double e = elements->eccentricity;
double a = kepler_orbit_semi_major_axis(elements);
double mu = kepler_orbit_gravity_parameter(elements);
double r = p / (1.0 + e*cos(f));
double v = kepler_orbit_parabolic(elements) ?
sqrt(mu * (2.0 / r)) :
sqrt(mu * (2.0 / r - 1.0 / a));
double x = r * cos(f);
double y = r * sin(f);
double dx = -p * sin(f) / square(1.0 + e*cos(f));
double dy = p * (e + cos(f)) / square(1.0 + e*cos(f));
double d = sqrt(dx*dx + dy*dy);
double vx = v * dx / d;
double vy = v * dy / d;
pos[0] = x; pos[1] = y; pos[2] = 0.0;
vel[0] = vx; vel[1] = vy; vel[2] = 0.0;
}
void kepler_elements_to_state_E(
const struct kepler_elements *elements,
double E,
double *pos,
double *vel) {
double e = elements->eccentricity;
double p = elements->semi_latus_rectum;
double a = kepler_orbit_semi_major_axis(elements);
double b = kepler_orbit_semi_minor_axis(elements);
double n = elements->mean_motion;
double Edot = n * kepler_anomaly_dEdM(e, E);
double x, y, vx, vy;
if(kepler_orbit_parabolic(elements)) {
// parabolic trajectory
x = p/2.0 * (1.0 - E*E);
y = p * E;
vx = -p * E * Edot;
vy = p * Edot;
} else if(kepler_orbit_closed(elements)) {
// elliptic trajectory
x = a * (cos(E) - e);
y = b * sin(E);
vx = -a * sin(E) * Edot;
vy = b * cos(E) * Edot;
} else {
// hyperbolic trajectory
x = a * (cosh(E) - e);
y = -b * sinh(E);
vx = a * sinh(E) * Edot;
vy = -b * cosh(E) * Edot;
}
pos[0] = x; pos[1] = y; pos[2] = 0.0;
vel[0] = vx; vel[1] = vy; vel[2] = 0.0;
}
void kepler_elements_to_state_t(
const struct kepler_elements *elements,
double t,
double *pos,
double *vel) {
// mean anomaly
double M = kepler_orbit_mean_anomaly_at_time(elements, t);
// eccentric anomaly
double E = kepler_anomaly_mean_to_eccentric(elements->eccentricity, M);
kepler_elements_to_state_E(elements, E, pos, vel);
}
#include "../numtest.c"
static bool eqv3(double *a, double *b) {
double diff[3] = { a[0]-b[0], a[1]-b[1], a[2]-b[2] };
return (ZEROF(dot(a,a)) && ZEROF(dot(b,b))) ||
ZEROF(dot(diff, diff)/(dot(a, a) + dot(b, b)));
}
static void rotation_matrix_euler(double rx, double ry, double rz, double *m)
{
double sn[3] = { sin(rx), sin(ry), sin(rz) };
double cs[3] = { cos(rx), cos(ry), cos(rz) };
m[0] = cs[1]*cs[2];
m[1] = cs[1]*sn[2];
m[2] = -sn[1];
m[3] = sn[0]*sn[1]*cs[2] - cs[0]*sn[2];
m[4] = sn[0]*sn[1]*sn[2] + cs[0]*cs[2];
m[5] = sn[0]*cs[1];
m[6] = cs[0]*sn[1]*cs[2] + sn[0]*sn[2];
m[7] = cs[0]*sn[1]*sn[2] - sn[0]*cs[2];
m[8] = cs[0]*cs[1];
}
static void anomaly_test(double *params, int num_params, void *extra_args, struct numtest_ctx *test_ctx) {
(void)extra_args;
ASSERT(num_params == 2, "num_params");
double e = params[0] * 5.0;
double t = -1.0 + params[1] * 2.0;
double maxf = e > 1.0 ?
acos(1.0/e) : // hyperbolic
(zero(e-1.0) ?
4*M_PI/5 : // parabolic
M_PI); // closed orbit
double maxE = kepler_anomaly_true_to_eccentric(e, maxf);
double maxM = kepler_anomaly_eccentric_to_mean(e, maxE);
double M = t * maxM;
double E = t * maxE;
double f = t * maxf;
double f2 = kepler_anomaly_eccentric_to_true(e, E);
ASSERT_RANGEF(f2, -maxf, maxf, "True anomaly within range");
ASSERT_EQF(E, kepler_anomaly_true_to_eccentric(e, f2), "Eccentric -> True");
double E2 = kepler_anomaly_true_to_eccentric(e, f);
ASSERT_RANGEF(E2, -maxE, maxE, "Eccentric anomaly within range");
ASSERT_EQF(f, kepler_anomaly_eccentric_to_true(e, E2), "True -> Eccentric");
double E3 = kepler_anomaly_mean_to_eccentric(e, M);
ASSERT_RANGEF(E3, -maxE, maxE, "Eccentric anomaly within range");
ASSERT_EQF(M, kepler_anomaly_eccentric_to_mean(e, E3), "True -> Eccentric");
double M2 = kepler_anomaly_eccentric_to_mean(e, E);
ASSERT_RANGEF(M2, -maxM, maxM, "Mean anomaly within range");
ASSERT_EQF(E, kepler_anomaly_mean_to_eccentric(e, M2), "Eccentric -> Mean");
double M3 = kepler_anomaly_true_to_mean(e, f);
ASSERT_RANGEF(M3, -maxM, maxM, "Mean anomaly within range");
ASSERT_EQF(f, kepler_anomaly_mean_to_true(e, M3), "True -> Mean");
double f3 = kepler_anomaly_mean_to_true(e, M);
ASSERT_RANGEF(f3, -maxf, maxf, "True anomaly within range");
ASSERT_EQF(M, kepler_anomaly_true_to_mean(e, f3), "Mean -> True");
double dEdM = kepler_anomaly_dEdM(e, E);
double MM = kepler_anomaly_eccentric_to_mean(e, E);
double dM = 1.0e-10 * maxM;
double Eplus = kepler_anomaly_mean_to_eccentric(e, MM+dM);
double Eminus = kepler_anomaly_mean_to_eccentric(e, MM-dM);
ASSERT_EQF(dEdM * 2.0 * dM, (Eplus-Eminus), "dE/dM");
}
static void orbit_from_state_test(double *params, int num_params, void *extra_args, struct numtest_ctx *test_ctx) {
(void)extra_args;
ASSERT(num_params == 6, "");
struct kepler_elements elements = { 0, 0, 0, 0, 0, 0, 0};
double mu = 1.0 + params[0] * 1.0e10;
double r0 = 1.0 + params[1] * 1.0e10;
double v_circ = sqrt(mu / r0), v_esc = sqrt(2.0 * mu / r0);
double v0 = v_circ + (v_esc - v_circ) * 2.0 * params[2];
double r = r0, v = v0;
double mat[9];
double rot[3];
for(int i = 0; i < 3; ++i)
rot[i] = -M_PI + 2.0*M_PI * params[3+i];
rotation_matrix_euler(rot[0], rot[1], rot[2], mat);
// TODO: start above periapsis
double pos[3] = { r * mat[0], r * mat[1], r * mat[2] };
double vel[3] = { v * mat[3], v * mat[4], v * mat[5] };
double t0 = 0.0;
kepler_elements_from_state(mu, pos, vel, t0, &elements);
ASSERT(
isfinite(elements.semi_latus_rectum) &&
isfinite(elements.eccentricity) &&
isfinite(elements.mean_motion) &&
isfinite(elements.inclination) &&
isfinite(elements.longitude_of_ascending_node) &&
isfinite(elements.argument_of_periapsis) &&
isfinite(elements.periapsis_time),
"All elements not NaN or Inf");
ASSERT_EQF(kepler_orbit_gravity_parameter(&elements), mu,
"Gravity parameter matches");
ASSERT_RANGEF(elements.inclination, 0.0, M_PI,
"Inclination within range 0..pi");
ASSERT_RANGEF(elements.longitude_of_ascending_node, -M_PI, M_PI,
"Longitude of ascending node with range -pi..pi");
ASSERT_RANGEF(elements.argument_of_periapsis, -M_PI, M_PI,
"Argument of periapsis with range -pi..pi");
ASSERT(elements.eccentricity >= 0.0,
"Eccentricity is greater than zero");
ASSERT(v >= v_esc || kepler_orbit_closed(&elements),
"Closed orbit if less than escape velocity");
ASSERT_LTF(kepler_orbit_periapsis(&elements), r,
"Distance is greater than periapsis");
ASSERT_LTF(v, kepler_orbit_periapsis_vel(&elements),
"Velocity is smaller than periapsis velocity");
ASSERT(kepler_orbit_closed(&elements) ==
!(kepler_orbit_parabolic(&elements) || kepler_orbit_hyperbolic(&elements)),
"Closed orbits are not parabolic or hyperbolic");
ASSERT(kepler_orbit_closed(&elements) ||
kepler_orbit_parabolic(&elements) != kepler_orbit_hyperbolic(&elements),
"Parabolic orbits are not hyperbolic");
if(!kepler_orbit_parabolic(&elements)) {
double visviva = v*v/2.0 - mu/r;
ASSERT_EQF(visviva, kepler_orbit_specific_orbital_energy(&elements),
"Specific orbital energy (vis-viva)");
} else {
ASSERT(ZEROF(kepler_orbit_specific_orbital_energy(&elements)),
"Parabolic orbit specific orbital energy is zero (vis-viva)");
}
double h[3];
cross(pos, vel, h);
ASSERT_EQF(mag(h), kepler_orbit_specific_angular_momentum(&elements),
"Specific relative angular momentum");
if(kepler_orbit_closed(&elements)) {
ASSERT_LTF(kepler_orbit_periapsis(&elements), kepler_orbit_apoapsis(&elements),
"Apoapsis is greater than periapsis");
ASSERT_LTF(kepler_orbit_apoapsis_vel(&elements), kepler_orbit_periapsis_vel(&elements),
"Periapsis velocity is greater than apoapsis velocity");
ASSERT(kepler_orbit_semi_major_axis(&elements) > 0 && kepler_orbit_semi_minor_axis(&elements) > 0,
"Closed orbit semi-major and semi-minor axes are positive");
ASSERT_LTF(kepler_orbit_semi_minor_axis(&elements), kepler_orbit_semi_major_axis(&elements),
"Semi-minor axis is less than semi_major axis");
ASSERT(isfinite(kepler_orbit_period(&elements)),
"Closed orbits have finite period");
ASSERT_RANGEF(r, kepler_orbit_periapsis(&elements), kepler_orbit_apoapsis(&elements),
"Distance is between apoapsis and periapsis");
ASSERT_RANGEF(v, kepler_orbit_apoapsis_vel(&elements), kepler_orbit_periapsis_vel(&elements),
"Velocity is between apoapsis and periapsis velocity");
ASSERT(0.0 > kepler_orbit_specific_orbital_energy(&elements),
"Closed orbits have negative specific orbital energy");
}
if(kepler_orbit_circular(&elements)) {
ASSERT_EQF(kepler_orbit_apoapsis(&elements), kepler_orbit_periapsis(&elements),
"Circular orbit periapsis == apoapsis");
ASSERT_EQF(kepler_orbit_apoapsis_vel(&elements), kepler_orbit_periapsis_vel(&elements),
"Circular orbit periapsis velocity == apoapsis velocity");
}
if(kepler_orbit_parabolic(&elements)) {
// TODO: test parabolic orbits
}
if(kepler_orbit_hyperbolic(&elements)) {
ASSERT(kepler_orbit_semi_major_axis(&elements) < 0 && kepler_orbit_semi_minor_axis(&elements) < 0,
"Hyperbolic orbit semi-major and semi-minor axes are positive");
ASSERT(0.0 < kepler_orbit_specific_orbital_energy(&elements),
"Hyperbolic orbits have positive specific orbital energy");
}
double t[3], n[3], b[3];
kepler_orbit_tangent(&elements, t);
kepler_orbit_normal(&elements, n);
kepler_orbit_bitangent(&elements, b);
ASSERT(zero(dot(t, n)) && zero(dot(t, b)) && zero(dot(n, b)),
"Orbit normal, tangent, binormal are orthogonal");
ASSERT(ZEROF(dot(pos, n)/r) && ZEROF(dot(vel, n)/v),
"Orbit motion is planar");
ASSERT_EQF(dot(n, h), mag(h),
"Orbit normal and angular momentum");
double matrix[9];
kepler_orbit_matrix(&elements, matrix);
for(int pass = 0; pass < 2; ++pass)
{
double pos2[3], vel2[3];
double M = kepler_orbit_mean_anomaly_at_time(&elements, t0);
double E = kepler_anomaly_mean_to_eccentric(elements.eccentricity, M);
double f = kepler_anomaly_eccentric_to_true(elements.eccentricity, E);
if(pass == 0)
kepler_elements_to_state_E(&elements, E, pos2, vel2);
else
kepler_elements_to_state_f(&elements, f, pos2, vel2);
double pos3[3], vel3[3];
matrix_vector_product(matrix, pos2, pos3);
matrix_vector_product(matrix, vel2, vel3);
ASSERT(eqv3(pos, pos3), "Position identity");
ASSERT(eqv3(vel, vel3), "Velocity identity");
}
}
static void orbit_from_elements_test(double *params, int num_params, void *extra_args, struct numtest_ctx *test_ctx) {
(void)extra_args;
ASSERT(num_params == 4, "");
double p = 1.0 + params[0] * 1.0e10;
double e = params[1] * 5.0;
double mu = 1.0 + params[2] * 1.0e10;
double a = p / (1.0 - square(e));
double n = zero(e - 1.0) ?
sqrt(mu / cube(p)) : // parabolic
sqrt(mu / cube(fabs(a)));
double t0 = 0.0;
struct kepler_elements elements = { p, e, n, 0.0, 0.0, 0.0, t0 };
ASSERT_EQF(mu, kepler_orbit_gravity_parameter(&elements),
"Gravitational constant is correct");
double maxf = e > 1.0 ?
acos(1.0/e) : // hyperbolic
(zero(e-1.0) ?
4*M_PI/5 : // parabolic
M_PI); // closed orbit
double x1 = -1.0 + params[3] * 2.0;
double f1 = maxf * x1;
double E1 = kepler_anomaly_true_to_eccentric(e, f1);
const int states_size = 2 * 2 * 3; // 2 x position,velocity x vec3
double state_data[states_size];
kepler_elements_to_state_f(&elements, f1, &state_data[6*0 + 0], &state_data[6*0 + 3]);
kepler_elements_to_state_E(&elements, E1, &state_data[6*1 + 0], &state_data[6*1 + 3]);
for(double *ptr = state_data+0; ptr != state_data + states_size; ++ptr)
ASSERT(isfinite(*ptr), "Position and velocity not NaN");
ASSERT(eqv3(state_data + 6*0 + 0, state_data + 6*1 + 0),
"Conic and eccentric positions match");
ASSERT(eqv3(state_data + 6*0 + 3, state_data + 6*1 + 3),
"Conic and eccentric velocities match");
for(int pass = 0; pass < 2; ++pass) {
double *ptr = state_data + pass*2*3;
double *pos = ptr + 0, *vel = ptr + 3;
double r = mag(pos), v2 = dot(vel, vel); //, v = sqrt(v2);
ASSERT(dot(pos, pos) > 0 && dot(vel, vel) > 0,
"Position and Velocity non zero");
double h[3];
cross(pos, vel, h);
ASSERT_EQF(mag(h), kepler_orbit_specific_angular_momentum(&elements),
"Specific relative angular momentum");
if(!kepler_orbit_parabolic(&elements)) {
double visviva = v2/2 - mu/r;
ASSERT_EQF(visviva, kepler_orbit_specific_orbital_energy(&elements),
"Specific orbital energy (vis-viva)");
} else {
ASSERT_EQF(v2/2, mu/r,
"Specific orbital energy (vis-viva)");
}
if(kepler_orbit_parabolic(&elements)) {
ASSERT_EQF(
pos[0] - p/2.0,
-1.0/(2.0 * p) * square(pos[1]),
"Orbit is a parabola");
} else if(kepler_orbit_hyperbolic(&elements)) {
ASSERT_EQF(1.0,
square(pos[0] + a*e)/square(kepler_orbit_semi_major_axis(&elements)) -
square(pos[1])/square(kepler_orbit_semi_minor_axis(&elements)),
"Orbit is a hyperbola");
} else {
ASSERT_EQF(1.0,
square(pos[0] + a*e)/square(kepler_orbit_semi_major_axis(&elements)) +
square(pos[1])/square(kepler_orbit_semi_minor_axis(&elements)),
"Orbit is an ellipse");
}
}
}
const struct numtest_case numtest_cases[] = {
{ "anomaly_test", anomaly_test, 2, NULL },
{ "orbit_from_state_test", orbit_from_state_test, 6, NULL },
{ "orbit_from_elements_test", orbit_from_elements_test, 4, NULL },
{ 0, 0, 0, 0 }
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment