use crate::core::error::PoliastroResult;
use crate::core::linalg::Vector3;
use nalgebra as na;
pub fn jacobian_two_body(r: &Vector3, mu: f64) -> na::Matrix6<f64> {
let r_mag = r.norm();
let r_mag3 = r_mag * r_mag * r_mag;
let r_mag5 = r_mag3 * r_mag * r_mag;
let coeff1 = -mu / r_mag3;
let coeff2 = 3.0 * mu / r_mag5;
let rx2 = r.x * r.x;
let ry2 = r.y * r.y;
let rz2 = r.z * r.z;
let rxy = r.x * r.y;
let rxz = r.x * r.z;
let ryz = r.y * r.z;
#[rustfmt::skip]
let da_dr = na::Matrix3::new(
coeff1 + coeff2 * rx2, coeff2 * rxy, coeff2 * rxz,
coeff2 * rxy, coeff1 + coeff2 * ry2, coeff2 * ryz,
coeff2 * rxz, coeff2 * ryz, coeff1 + coeff2 * rz2,
);
#[rustfmt::skip]
let jacobian = na::Matrix6::new(
0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
da_dr[(0,0)], da_dr[(0,1)], da_dr[(0,2)], 0.0, 0.0, 0.0,
da_dr[(1,0)], da_dr[(1,1)], da_dr[(1,2)], 0.0, 0.0, 0.0,
da_dr[(2,0)], da_dr[(2,1)], da_dr[(2,2)], 0.0, 0.0, 0.0,
);
jacobian
}
pub fn jacobian_j2(r: &Vector3, mu: f64, j2: f64, R: f64) -> na::Matrix6<f64> {
let mut jacobian = jacobian_two_body(r, mu);
let x = r.x;
let y = r.y;
let z = r.z;
let r_mag = r.norm();
let r2 = r_mag * r_mag;
let r4 = r2 * r2;
let r6 = r4 * r2;
let r8 = r6 * r2;
let z2 = z * z;
let k = 1.5 * j2 * mu * R * R;
let z2_r2 = z2 / r2;
let factor_xy = 5.0 * z2_r2 - 1.0;
let factor_z = 5.0 * z2_r2 - 3.0;
let dax_dx = k * (
(factor_xy / r4) * (1.0/r_mag - x*x/r_mag/r2)
+ (x / r_mag / r4) * (10.0*z2*(-x)/r4 - x*factor_xy*4.0/r2/r_mag)
);
let dax_dy = k * (
(factor_xy / r4) * (-x*y/r_mag/r2)
+ (x / r_mag / r4) * (10.0*z2*(-y)/r4 - y*factor_xy*4.0/r2/r_mag)
);
let dax_dz = k * (
(factor_xy / r4) * (-x*z/r_mag/r2)
+ (x / r_mag / r4) * (10.0*z2*(-z)/r4 + 10.0*z/r2 - z*factor_xy*4.0/r2/r_mag)
);
let day_dx = k * (
(factor_xy / r4) * (-y*x/r_mag/r2)
+ (y / r_mag / r4) * (10.0*z2*(-x)/r4 - x*factor_xy*4.0/r2/r_mag)
);
let day_dy = k * (
(factor_xy / r4) * (1.0/r_mag - y*y/r_mag/r2)
+ (y / r_mag / r4) * (10.0*z2*(-y)/r4 - y*factor_xy*4.0/r2/r_mag)
);
let day_dz = k * (
(factor_xy / r4) * (-y*z/r_mag/r2)
+ (y / r_mag / r4) * (10.0*z2*(-z)/r4 + 10.0*z/r2 - z*factor_xy*4.0/r2/r_mag)
);
let daz_dx = k * (
(factor_z / r4) * (-z*x/r_mag/r2)
+ (z / r_mag / r4) * (10.0*z2*(-x)/r4 - x*factor_z*4.0/r2/r_mag)
);
let daz_dy = k * (
(factor_z / r4) * (-z*y/r_mag/r2)
+ (z / r_mag / r4) * (10.0*z2*(-y)/r4 - y*factor_z*4.0/r2/r_mag)
);
let daz_dz = k * (
(factor_z / r4) * (1.0/r_mag - z*z/r_mag/r2)
+ (z / r_mag / r4) * (10.0*z2*(-z)/r4 + 10.0*z/r2 - z*factor_z*4.0/r2/r_mag)
);
jacobian[(3, 0)] += dax_dx;
jacobian[(3, 1)] += dax_dy;
jacobian[(3, 2)] += dax_dz;
jacobian[(4, 0)] += day_dx;
jacobian[(4, 1)] += day_dy;
jacobian[(4, 2)] += day_dz;
jacobian[(5, 0)] += daz_dx;
jacobian[(5, 1)] += daz_dy;
jacobian[(5, 2)] += daz_dz;
jacobian
}
pub fn propagate_stm_rk4(
r0: &Vector3,
v0: &Vector3,
dt: f64,
mu: f64,
n_steps: usize,
) -> PoliastroResult<(Vector3, Vector3, na::Matrix6<f64>)> {
use crate::core::numerical::rk4_step;
let h = dt / n_steps as f64;
let dynamics = |_t: f64, augmented: &na::DVector<f64>| -> na::DVector<f64> {
let r = Vector3::new(augmented[0], augmented[1], augmented[2]);
let v = Vector3::new(augmented[3], augmented[4], augmented[5]);
let mut stm = na::Matrix6::<f64>::zeros();
for i in 0..6 {
for j in 0..6 {
stm[(i, j)] = augmented[6 + i * 6 + j];
}
}
let r_mag = r.norm();
let a = -mu / (r_mag * r_mag * r_mag) * r;
let A = jacobian_two_body(&r, mu);
let dstm_dt = A * stm;
let mut result = na::DVector::zeros(42);
result[0] = v.x;
result[1] = v.y;
result[2] = v.z;
result[3] = a.x;
result[4] = a.y;
result[5] = a.z;
for i in 0..6 {
for j in 0..6 {
result[6 + i * 6 + j] = dstm_dt[(i, j)];
}
}
result
};
let mut augmented = na::DVector::zeros(42);
augmented[0] = r0.x;
augmented[1] = r0.y;
augmented[2] = r0.z;
augmented[3] = v0.x;
augmented[4] = v0.y;
augmented[5] = v0.z;
for i in 0..6 {
augmented[6 + i * 6 + i] = 1.0;
}
let mut t = 0.0;
for _ in 0..n_steps {
augmented = rk4_step(dynamics, t, &augmented, h);
t += h;
}
let r_final = Vector3::new(augmented[0], augmented[1], augmented[2]);
let v_final = Vector3::new(augmented[3], augmented[4], augmented[5]);
let mut stm_final = na::Matrix6::<f64>::zeros();
for i in 0..6 {
for j in 0..6 {
stm_final[(i, j)] = augmented[6 + i * 6 + j];
}
}
Ok((r_final, v_final, stm_final))
}
pub fn propagate_stm_dopri5(
r0: &Vector3,
v0: &Vector3,
dt: f64,
mu: f64,
tol: Option<f64>,
) -> PoliastroResult<(Vector3, Vector3, na::Matrix6<f64>)> {
use crate::core::numerical::dopri5_integrate;
let tol = tol.unwrap_or(1e-10);
let dynamics = |_t: f64, augmented: &na::DVector<f64>| -> na::DVector<f64> {
let r = Vector3::new(augmented[0], augmented[1], augmented[2]);
let v = Vector3::new(augmented[3], augmented[4], augmented[5]);
let mut stm = na::Matrix6::<f64>::zeros();
for i in 0..6 {
for j in 0..6 {
stm[(i, j)] = augmented[6 + i * 6 + j];
}
}
let r_mag = r.norm();
let a = -mu / (r_mag * r_mag * r_mag) * r;
let A = jacobian_two_body(&r, mu);
let dstm_dt = A * stm;
let mut result = na::DVector::zeros(42);
result[0] = v.x;
result[1] = v.y;
result[2] = v.z;
result[3] = a.x;
result[4] = a.y;
result[5] = a.z;
for i in 0..6 {
for j in 0..6 {
result[6 + i * 6 + j] = dstm_dt[(i, j)];
}
}
result
};
let mut augmented0 = na::DVector::zeros(42);
augmented0[0] = r0.x;
augmented0[1] = r0.y;
augmented0[2] = r0.z;
augmented0[3] = v0.x;
augmented0[4] = v0.y;
augmented0[5] = v0.z;
for i in 0..6 {
augmented0[6 + i * 6 + i] = 1.0;
}
let augmented_final = dopri5_integrate(
dynamics,
0.0,
&augmented0,
dt,
dt.abs() / 10.0,
tol,
None,
)?;
let r_final = Vector3::new(augmented_final[0], augmented_final[1], augmented_final[2]);
let v_final = Vector3::new(augmented_final[3], augmented_final[4], augmented_final[5]);
let mut stm_final = na::Matrix6::<f64>::zeros();
for i in 0..6 {
for j in 0..6 {
stm_final[(i, j)] = augmented_final[6 + i * 6 + j];
}
}
Ok((r_final, v_final, stm_final))
}
pub fn propagate_stm_j2_rk4(
r0: &Vector3,
v0: &Vector3,
dt: f64,
mu: f64,
j2: f64,
R: f64,
n_steps: usize,
) -> PoliastroResult<(Vector3, Vector3, na::Matrix6<f64>)> {
use crate::core::numerical::rk4_step;
use crate::propagators::perturbations::j2_perturbation;
let h = dt / n_steps as f64;
let dynamics = |_t: f64, augmented: &na::DVector<f64>| -> na::DVector<f64> {
let r = Vector3::new(augmented[0], augmented[1], augmented[2]);
let v = Vector3::new(augmented[3], augmented[4], augmented[5]);
let mut stm = na::Matrix6::<f64>::zeros();
for i in 0..6 {
for j in 0..6 {
stm[(i, j)] = augmented[6 + i * 6 + j];
}
}
let r_mag = r.norm();
let a_twobody = -mu / (r_mag * r_mag * r_mag) * r;
let a_j2 = j2_perturbation(&r, mu, j2, R);
let a_total = a_twobody + a_j2;
let A = jacobian_j2(&r, mu, j2, R);
let dstm_dt = A * stm;
let mut result = na::DVector::zeros(42);
result[0] = v.x;
result[1] = v.y;
result[2] = v.z;
result[3] = a_total.x;
result[4] = a_total.y;
result[5] = a_total.z;
for i in 0..6 {
for j in 0..6 {
result[6 + i * 6 + j] = dstm_dt[(i, j)];
}
}
result
};
let mut augmented0 = na::DVector::zeros(42);
augmented0[0] = r0.x;
augmented0[1] = r0.y;
augmented0[2] = r0.z;
augmented0[3] = v0.x;
augmented0[4] = v0.y;
augmented0[5] = v0.z;
for i in 0..6 {
augmented0[6 + i * 6 + i] = 1.0;
}
let mut augmented = augmented0;
let mut t = 0.0;
for _ in 0..n_steps {
augmented = rk4_step(dynamics, t, &augmented, h);
t += h;
}
let r_final = Vector3::new(augmented[0], augmented[1], augmented[2]);
let v_final = Vector3::new(augmented[3], augmented[4], augmented[5]);
let mut stm_final = na::Matrix6::<f64>::zeros();
for i in 0..6 {
for j in 0..6 {
stm_final[(i, j)] = augmented[6 + i * 6 + j];
}
}
Ok((r_final, v_final, stm_final))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::constants::GM_EARTH;
use approx::assert_relative_eq;
#[test]
fn test_jacobian_two_body_structure() {
let r = Vector3::new(7000e3, 0.0, 0.0);
let A = jacobian_two_body(&r, GM_EARTH);
for i in 0..3 {
for j in 0..3 {
assert_relative_eq!(A[(i, j)], 0.0, epsilon = 1e-20);
}
}
assert_relative_eq!(A[(0, 3)], 1.0);
assert_relative_eq!(A[(1, 4)], 1.0);
assert_relative_eq!(A[(2, 5)], 1.0);
assert_relative_eq!(A[(0, 4)], 0.0, epsilon = 1e-20);
for i in 3..6 {
for j in 3..6 {
assert_relative_eq!(A[(i, j)], 0.0, epsilon = 1e-20);
}
}
assert!(A[(3, 0)].abs() > 1e-10);
}
#[test]
fn test_jacobian_symmetry() {
let r = Vector3::new(7000e3, 0.0, 0.0);
let A = jacobian_two_body(&r, GM_EARTH);
assert_relative_eq!(A[(4, 1)], A[(5, 2)], epsilon = 1e-10);
}
#[test]
fn test_stm_identity_at_zero_time() {
let r0 = Vector3::new(7000e3, 0.0, 0.0);
let v0 = Vector3::new(0.0, 7546.0, 0.0);
let (_, _, stm) = propagate_stm_rk4(&r0, &v0, 1e-10, GM_EARTH, 1).unwrap();
for i in 0..6 {
for j in 0..6 {
let expected = if i == j { 1.0 } else { 0.0 };
assert_relative_eq!(stm[(i, j)], expected, epsilon = 1e-6);
}
}
}
#[test]
fn test_stm_propagation_basic() {
let r0 = Vector3::new(7000e3, 0.0, 0.0);
let v0 = Vector3::new(0.0, 7546.0, 0.0);
let (r, v, stm) = propagate_stm_rk4(&r0, &v0, 600.0, GM_EARTH, 100).unwrap();
assert!(r.norm() > 6000e3);
assert!(v.norm() > 1000.0);
let identity_diff = (stm[(0, 0)] - 1.0).abs() + (stm[(0, 1)]).abs();
assert!(identity_diff > 0.01);
let det = stm.determinant();
assert_relative_eq!(det.abs(), 1.0, epsilon = 0.1);
}
#[test]
fn test_stm_linearity() {
let r0 = Vector3::new(7000e3, 0.0, 0.0);
let v0 = Vector3::new(0.0, 7546.0, 0.0);
let dt = 600.0;
let (r_nom, v_nom, stm) = propagate_stm_rk4(&r0, &v0, dt, GM_EARTH, 100).unwrap();
let dr0 = 1000.0; let r0_pert = Vector3::new(r0.x + dr0, r0.y, r0.z);
let (r_pert, v_pert, _) = propagate_stm_rk4(&r0_pert, &v0, dt, GM_EARTH, 100).unwrap();
let dr_actual = r_pert - r_nom;
let dv_actual = v_pert - v_nom;
let delta_x0 = na::Vector6::new(dr0, 0.0, 0.0, 0.0, 0.0, 0.0);
let delta_x = stm * delta_x0;
let r_error = (dr_actual.x - delta_x[0]).abs();
let v_error = (dv_actual.x - delta_x[3]).abs();
assert!(r_error < dr0 * 0.01); assert!(v_error < 1.0); }
#[test]
fn test_stm_dopri5_vs_rk4() {
let r0 = Vector3::new(7000e3, 0.0, 0.0);
let v0 = Vector3::new(0.0, 7546.0, 0.0);
let dt = 600.0;
let (r_rk4, v_rk4, stm_rk4) = propagate_stm_rk4(&r0, &v0, dt, GM_EARTH, 100).unwrap();
let (r_dopri5, v_dopri5, stm_dopri5) =
propagate_stm_dopri5(&r0, &v0, dt, GM_EARTH, Some(1e-10)).unwrap();
assert!((r_rk4 - r_dopri5).norm() < 100.0); assert!((v_rk4 - v_dopri5).norm() < 0.1);
let stm_diff = (stm_rk4 - stm_dopri5).norm();
assert!(stm_diff < 0.01);
}
#[test]
fn test_jacobian_j2_vs_two_body() {
let r = Vector3::new(7000e3, 0.0, 0.0);
let A_twobody = jacobian_two_body(&r, GM_EARTH);
let A_j2_zero = jacobian_j2(&r, GM_EARTH, 0.0, 6378e3);
for i in 0..6 {
for j in 0..6 {
assert_relative_eq!(A_twobody[(i, j)], A_j2_zero[(i, j)], epsilon = 1e-15);
}
}
}
#[test]
fn test_stm_j2_propagation() {
use crate::core::constants::{J2_EARTH, R_EARTH};
let r0 = Vector3::new(7000e3, 0.0, 1000e3); let v0 = Vector3::new(0.0, 7546.0, 100.0);
let result = propagate_stm_j2_rk4(&r0, &v0, 600.0, GM_EARTH, J2_EARTH, R_EARTH, 100);
assert!(result.is_ok());
let (r, v, stm) = result.unwrap();
assert!(r.norm() > 6000e3);
assert!(v.norm() > 1000.0);
let det = stm.determinant();
assert_relative_eq!(det.abs(), 1.0, epsilon = 0.2);
}
}