use crate::cosmic::Orbit;
use crate::linalg::{
DefaultAllocator, DimName, Matrix3, OVector, Vector3, Vector6, allocator::Allocator,
};
use nalgebra::Complex;
pub fn tilde_matrix(v: &Vector3<f64>) -> Matrix3<f64> {
Matrix3::new(0.0, -v.z, v.y, v.z, 0.0, -v.x, -v.y, v.x, 0.0)
}
pub fn is_diagonal(m: &Matrix3<f64>) -> bool {
for i in 0..3 {
for j in 0..3 {
if i != j && m[(i, j)].abs() > f64::EPSILON {
return false;
}
}
}
true
}
pub fn are_eigenvalues_stable<N: DimName>(eigenvalues: &OVector<Complex<f64>, N>) -> bool
where
DefaultAllocator: Allocator<N>,
{
eigenvalues.iter().all(|ev| ev.re <= 0.0)
}
pub fn between_0_360(angle: f64) -> f64 {
let mut bounded = angle % 360.0;
if bounded < 0.0 {
bounded += 360.0;
}
bounded
}
pub fn between_pm_180(angle: f64) -> f64 {
between_pm_x(angle, 180.0)
}
pub fn between_pm_x(angle: f64, x: f64) -> f64 {
let mut bounded = angle % (2.0 * x);
if bounded > x {
bounded -= 2.0 * x;
}
if bounded < -x {
bounded += 2.0 * x;
}
bounded
}
pub fn kronecker(a: f64, b: f64) -> f64 {
if (a - b).abs() <= f64::EPSILON {
1.0
} else {
0.0
}
}
pub fn r1(angle_rad: f64) -> Matrix3<f64> {
let (s, c) = angle_rad.sin_cos();
Matrix3::new(1.0, 0.0, 0.0, 0.0, c, s, 0.0, -s, c)
}
pub fn r2(angle_rad: f64) -> Matrix3<f64> {
let (s, c) = angle_rad.sin_cos();
Matrix3::new(c, 0.0, -s, 0.0, 1.0, 0.0, s, 0.0, c)
}
pub fn r3(angle_rad: f64) -> Matrix3<f64> {
let (s, c) = angle_rad.sin_cos();
Matrix3::new(c, s, 0.0, -s, c, 0.0, 0.0, 0.0, 1.0)
}
pub fn rotv(v: &Vector3<f64>, axis: &Vector3<f64>, theta: f64) -> Vector3<f64> {
let k_hat = axis.normalize();
v.scale(theta.cos())
+ k_hat.cross(v).scale(theta.sin())
+ k_hat.scale(k_hat.dot(v) * (1.0 - theta.cos()))
}
pub fn perpv(a: &Vector3<f64>, b: &Vector3<f64>) -> Vector3<f64> {
let big_a = a.amax();
let big_b = b.amax();
if big_a < f64::EPSILON {
Vector3::zeros()
} else if big_b < f64::EPSILON {
*a
} else {
let a_scl = a / big_a;
let b_scl = b / big_b;
let v = projv(&a_scl, &b_scl);
(a_scl - v) * big_a
}
}
pub fn projv(a: &Vector3<f64>, b: &Vector3<f64>) -> Vector3<f64> {
let b_norm_squared = b.norm_squared();
if b_norm_squared.abs() < f64::EPSILON {
Vector3::zeros()
} else {
b.scale(a.dot(b) / b_norm_squared)
}
}
pub fn rss_errors<N: DimName>(prop_err: &OVector<f64, N>, cur_state: &OVector<f64, N>) -> f64
where
DefaultAllocator: Allocator<N>,
{
prop_err
.iter()
.zip(cur_state.iter())
.map(|(&x, &y)| (x - y).powi(2))
.sum::<f64>()
.sqrt()
}
pub fn rss_orbit_errors(prop_err: &Orbit, cur_state: &Orbit) -> (f64, f64) {
(
rss_errors(&prop_err.radius_km, &cur_state.radius_km),
rss_errors(&prop_err.velocity_km_s, &cur_state.velocity_km_s),
)
}
pub fn rss_orbit_vec_errors(prop_err: &Vector6<f64>, cur_state: &Vector6<f64>) -> (f64, f64) {
let err_radius = (prop_err.fixed_rows::<3>(0) - cur_state.fixed_rows::<3>(0)).norm();
let err_velocity = (prop_err.fixed_rows::<3>(3) - cur_state.fixed_rows::<3>(3)).norm();
(err_radius, err_velocity)
}
pub fn normalize(x: f64, min_x: f64, max_x: f64) -> f64 {
2.0 * (x - min_x) / (max_x - min_x) - 1.0
}
pub fn denormalize(xp: f64, min_x: f64, max_x: f64) -> f64 {
(max_x - min_x) * (xp + 1.0) / 2.0 + min_x
}
pub fn capitalize(s: &str) -> String {
let mut c = s.chars();
match c.next() {
None => String::new(),
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
}
}
#[macro_export]
macro_rules! pseudo_inverse {
($mat:expr) => {{
use $crate::md::TargetingError;
let (rows, cols) = $mat.shape();
if rows < cols {
match ($mat * $mat.transpose()).try_inverse() {
Some(m1_inv) => Ok($mat.transpose() * m1_inv),
None => Err(TargetingError::SingularJacobian),
}
} else {
match ($mat.transpose() * $mat).try_inverse() {
Some(m2_inv) => Ok(m2_inv * $mat.transpose()),
None => Err(TargetingError::SingularJacobian),
}
}
}};
}
pub fn mag_order(value: f64) -> i32 {
value.abs().log10().floor() as i32
}
pub fn unitize(v: Vector3<f64>) -> Vector3<f64> {
if v.norm() < f64::EPSILON {
v
} else {
v / v.norm()
}
}
pub fn cartesian_to_spherical(v: &Vector3<f64>) -> (f64, f64, f64) {
if v.norm() < f64::EPSILON {
(0.0, 0.0, 0.0)
} else {
let range_ρ = v.norm();
let θ = v.y.atan2(v.x);
let φ = (v.z / range_ρ).acos();
(range_ρ, θ, φ)
}
}
#[allow(clippy::many_single_char_names)]
pub fn spherical_to_cartesian(range_ρ: f64, θ: f64, φ: f64) -> Vector3<f64> {
if range_ρ < f64::EPSILON {
Vector3::zeros()
} else {
let x = range_ρ * φ.sin() * θ.cos();
let y = range_ρ * φ.sin() * θ.sin();
let z = range_ρ * φ.cos();
Vector3::new(x, y, z)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use nalgebra::{Complex, OVector};
#[test]
fn test_stable_eigenvalues() {
let eigenvalues = OVector::<Complex<f64>, nalgebra::U2>::from_column_slice(&[
Complex::new(-1.0, 0.0),
Complex::new(0.0, 0.0),
]);
assert!(are_eigenvalues_stable(&eigenvalues));
}
#[test]
fn test_unstable_eigenvalues() {
let eigenvalues = OVector::<Complex<f64>, nalgebra::U2>::from_column_slice(&[
Complex::new(1.0, 0.0),
Complex::new(0.0, 0.0),
]);
assert!(!are_eigenvalues_stable(&eigenvalues));
}
#[test]
fn test_oscillatory_eigenvalues() {
let eigenvalues = OVector::<Complex<f64>, nalgebra::U2>::from_column_slice(&[
Complex::new(0.0, 1.0),
Complex::new(0.0, -1.0),
]);
assert!(are_eigenvalues_stable(&eigenvalues));
}
#[test]
fn test_invariant_eigenvalues() {
let eigenvalues =
OVector::<Complex<f64>, nalgebra::U1>::from_column_slice(&[Complex::new(0.0, 0.0)]);
assert!(are_eigenvalues_stable(&eigenvalues));
}
#[test]
fn test_angle_bounds() {
approx::assert_relative_eq!(between_pm_180(181.0), -179.0);
approx::assert_relative_eq!(between_0_360(-179.0), 181.0);
}
#[test]
fn test_positive_angle() {
approx::assert_relative_eq!(between_0_360(450.0), 90.0);
approx::assert_relative_eq!(between_pm_x(270.0, 180.0), -90.0);
}
#[test]
fn test_negative_angle() {
approx::assert_relative_eq!(between_0_360(-90.0), 270.0);
approx::assert_relative_eq!(between_pm_x(-270.0, 180.0), 90.0);
}
#[test]
fn test_angle_in_range() {
approx::assert_relative_eq!(between_0_360(180.0), 180.0);
approx::assert_relative_eq!(between_pm_x(90.0, 180.0), 90.0);
}
#[test]
fn test_zero_angle() {
approx::assert_relative_eq!(between_0_360(0.0), 0.0);
approx::assert_relative_eq!(between_pm_x(0.0, 180.0), 0.0);
}
#[test]
fn test_full_circle_angle() {
approx::assert_relative_eq!(between_0_360(360.0), 0.0);
approx::assert_relative_eq!(between_pm_x(360.0, 180.0), 0.0);
}
#[test]
fn test_pseudo_inv() {
use crate::linalg::{DMatrix, SMatrix};
let mut mat = DMatrix::from_element(1, 3, 0.0);
mat[(0, 0)] = -1407.273208782421;
mat[(0, 1)] = -2146.3100013104886;
mat[(0, 2)] = 84.05022886527551;
println!("{}", pseudo_inverse!(&mat).unwrap());
let mut mat = SMatrix::<f64, 1, 3>::zeros();
mat[(0, 0)] = -1407.273208782421;
mat[(0, 1)] = -2146.3100013104886;
mat[(0, 2)] = 84.05022886527551;
println!("{}", pseudo_inverse!(&mat).unwrap());
let mut mat = SMatrix::<f64, 3, 1>::zeros();
mat[(0, 0)] = -1407.273208782421;
mat[(1, 0)] = -2146.3100013104886;
mat[(2, 0)] = 84.05022886527551;
println!("{}", pseudo_inverse!(&mat).unwrap());
let mat = SMatrix::<f64, 2, 2>::new(3.0, 4.0, -2.0, 1.0);
println!("{}", mat.try_inverse().unwrap());
println!("{}", pseudo_inverse!(&mat).unwrap());
}
#[test]
fn spherical() {
for v in &[
Vector3::<f64>::x(),
Vector3::<f64>::y(),
Vector3::<f64>::z(),
Vector3::<f64>::zeros(),
Vector3::<f64>::new(159.1, 561.2, 756.3),
] {
let (range_ρ, θ, φ) = cartesian_to_spherical(v);
let v_prime = spherical_to_cartesian(range_ρ, θ, φ);
assert!(rss_errors(v, &v_prime) < 1e-12, "{} != {}", v, &v_prime);
}
}
#[rustfmt::skip]
#[test]
fn test_diagonality() {
assert!(!is_diagonal(&Matrix3::new(10.0, 0.0, 0.0,
1.0, 5.0, 0.0,
0.0, 0.0, 2.0)),
"lower triangular"
);
assert!(!is_diagonal(&Matrix3::new(10.0, 1.0, 0.0,
1.0, 5.0, 0.0,
0.0, 0.0, 2.0)),
"symmetric but not diag"
);
assert!(!is_diagonal(&Matrix3::new(10.0, 1.0, 0.0,
0.0, 5.0, 0.0,
0.0, 0.0, 2.0)),
"upper triangular"
);
assert!(is_diagonal(&Matrix3::new(10.0, 0.0, 0.0,
0.0, 0.0, 0.0,
0.0, 0.0, 2.0)),
"diagonal with zero diagonal element"
);
assert!(is_diagonal(&Matrix3::new(10.0, 0.0, 0.0,
0.0, 5.0, 0.0,
0.0, 0.0, 2.0)),
"diagonal"
);
}
#[test]
fn test_projv() {
assert_eq!(
projv(&Vector3::new(6.0, 6.0, 6.0), &Vector3::new(2.0, 0.0, 0.0)),
Vector3::new(6.0, 0.0, 0.0)
);
assert_eq!(
projv(&Vector3::new(6.0, 6.0, 6.0), &Vector3::new(-3.0, 0.0, 0.0)),
Vector3::new(6.0, 0.0, 0.0)
);
assert_eq!(
projv(&Vector3::new(6.0, 6.0, 0.0), &Vector3::new(0.0, 7.0, 0.0)),
Vector3::new(0.0, 6.0, 0.0)
);
assert_eq!(
projv(&Vector3::new(6.0, 0.0, 0.0), &Vector3::new(0.0, 0.0, 9.0)),
Vector3::new(0.0, 0.0, 0.0)
);
let a = Vector3::new(1.0, 1.0, 0.0);
let b = Vector3::new(1.0, 0.0, 0.0);
let result = projv(&a, &b);
assert_abs_diff_eq!(result, Vector3::new(1.0, 0.0, 0.0), epsilon = 1e-7);
}
#[test]
fn test_rss_errors() {
use nalgebra::U3;
let prop_err = OVector::<f64, U3>::from_iterator([1.0, 2.0, 3.0]);
let cur_state = OVector::<f64, U3>::from_iterator([1.0, 2.0, 3.0]);
approx::assert_relative_eq!(rss_errors(&prop_err, &cur_state), 0.0);
let prop_err = OVector::<f64, U3>::from_iterator([1.0, 2.0, 3.0]);
let cur_state = OVector::<f64, U3>::from_iterator([4.0, 5.0, 6.0]);
approx::assert_relative_eq!(rss_errors(&prop_err, &cur_state), 5.196152422706632);
}
#[test]
fn test_normalize() {
let x = 5.0;
let min_x = 0.0;
let max_x = 10.0;
let result = normalize(x, min_x, max_x);
approx::assert_relative_eq!(result, 0.0);
}
#[test]
fn test_denormalize() {
let xp = 0.0;
let min_x = 0.0;
let max_x = 10.0;
let result = denormalize(xp, min_x, max_x);
approx::assert_relative_eq!(result, 5.0);
}
#[test]
fn test_capitalize() {
let s = "hello";
let result = capitalize(s);
assert_eq!(result, "Hello");
}
#[test]
fn test_r2() {
let angle = 0.0;
let rotation_matrix = r2(angle);
assert!((rotation_matrix - Matrix3::identity()).abs().max() <= f64::EPSILON);
let angle = std::f64::consts::PI / 2.0;
let rotation_matrix = r2(angle);
let expected_matrix = Matrix3::new(0.0, 0.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0);
assert!((rotation_matrix - expected_matrix).abs().max() <= f64::EPSILON);
let v = Vector3::new(0.0, 1.0, 0.0);
let rotated_v = rotation_matrix * v;
assert!((rotated_v - v).norm() <= f64::EPSILON);
}
#[test]
fn test_r1() {
let angle = 0.0;
let rotation_matrix = r1(angle);
assert!((rotation_matrix - Matrix3::identity()).abs().max() <= f64::EPSILON);
let angle = std::f64::consts::PI / 2.0;
let rotation_matrix = r1(angle);
let expected_matrix = Matrix3::new(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -1.0, 0.0);
assert!((rotation_matrix - expected_matrix).abs().max() <= f64::EPSILON);
let v = Vector3::new(1.0, 0.0, 0.0);
let rotated_v = rotation_matrix * v;
assert!((rotated_v - v).norm() <= f64::EPSILON);
}
#[test]
fn test_tilde_matrix() {
let vec = Vector3::new(1.0, 2.0, 3.0);
let rslt = Matrix3::new(0.0, -3.0, 2.0, 3.0, 0.0, -1.0, -2.0, 1.0, 0.0);
assert_eq!(tilde_matrix(&vec), rslt);
let v = Vector3::new(1.0, 2.0, 3.0);
let m = tilde_matrix(&v);
approx::assert_relative_eq!(m[(0, 0)], 0.0);
approx::assert_relative_eq!(m[(0, 1)], -v.z);
approx::assert_relative_eq!(m[(0, 2)], v.y);
approx::assert_relative_eq!(m[(1, 0)], v.z);
approx::assert_relative_eq!(m[(1, 1)], 0.0);
approx::assert_relative_eq!(m[(1, 2)], -v.x);
approx::assert_relative_eq!(m[(2, 0)], -v.y);
approx::assert_relative_eq!(m[(2, 1)], v.x);
approx::assert_relative_eq!(m[(2, 2)], 0.0);
}
#[test]
fn test_perpv() {
assert_eq!(
perpv(&Vector3::new(6.0, 6.0, 6.0), &Vector3::new(2.0, 0.0, 0.0)),
Vector3::new(0.0, 6.0, 6.0)
);
assert_eq!(
perpv(&Vector3::new(6.0, 6.0, 6.0), &Vector3::new(-3.0, 0.0, 0.0)),
Vector3::new(0.0, 6.0, 6.0)
);
assert_eq!(
perpv(&Vector3::new(6.0, 6.0, 0.0), &Vector3::new(0.0, 7.0, 0.0)),
Vector3::new(6.0, 0.0, 0.0)
);
assert_eq!(
perpv(&Vector3::new(6.0, 0.0, 0.0), &Vector3::new(0.0, 0.0, 9.0)),
Vector3::new(6.0, 0.0, 0.0)
);
let a = Vector3::new(1.0, 1.0, 0.0);
let b = Vector3::new(1.0, 0.0, 0.0);
let result = perpv(&a, &b);
assert_abs_diff_eq!(result, Vector3::new(0.0, 1.0, 0.0), epsilon = 1e-7);
}
#[test]
fn test_r3() {
let angle = 0.0;
let rotation_matrix = r3(angle);
assert!((rotation_matrix - Matrix3::identity()).abs().max() <= f64::EPSILON);
let angle = std::f64::consts::PI / 2.0;
let rotation_matrix = r3(angle);
let expected_matrix = Matrix3::new(0.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0);
assert!((rotation_matrix - expected_matrix).abs().max() <= f64::EPSILON);
let v = Vector3::new(0.0, 0.0, 1.0);
let rotated_v = rotation_matrix * v;
assert!((rotated_v - v).norm() <= f64::EPSILON);
}
#[test]
fn test_rotv() {
use approx::assert_abs_diff_eq;
let v = Vector3::new(1.0, 0.0, 0.0);
let axis = Vector3::new(0.0, 0.0, 1.0);
let theta = std::f64::consts::PI / 2.0;
let result = rotv(&v, &axis, theta);
assert_abs_diff_eq!(result, Vector3::new(0.0, 1.0, 0.0), epsilon = 1e-7);
}
}