#![allow(clippy::excessive_precision)]
use crate::nutation_j2000::nutation;
use crate::precession_j2000::precession_matrix;
use astrodyn_math::matrix3x3_product_transpose_transpose;
use glam::{DMat3, DVec3};
use std::f64::consts::PI;
const DEG_TO_RAD: f64 = PI / 180.0;
pub fn gast_rotation_matrix(gmst_seconds: f64, equa_of_equi: f64) -> DMat3 {
let theta_gast = ((gmst_seconds + equa_of_equi) / 240.0) * DEG_TO_RAD;
let temp = theta_gast / (2.0 * PI);
let mut theta = (temp - temp.floor()) * 2.0 * PI;
if theta < 0.0 {
theta += 2.0 * PI;
}
let (sin_ang, cos_ang) = theta.sin_cos();
DMat3::from_cols(
DVec3::new(cos_ang, sin_ang, 0.0),
DVec3::new(-sin_ang, cos_ang, 0.0),
DVec3::new(0.0, 0.0, 1.0),
)
}
pub fn polar_motion_matrix(xp: f64, yp: f64) -> DMat3 {
let (sin_xp, cos_xp) = xp.sin_cos();
let (sin_yp, cos_yp) = yp.sin_cos();
DMat3::from_cols(
DVec3::new(cos_xp, sin_xp * sin_yp, sin_xp * cos_yp),
DVec3::new(0.0, cos_yp, -sin_yp),
DVec3::new(-sin_xp, cos_xp * sin_yp, cos_xp * cos_yp),
)
}
pub fn compute_t_parent_this(gmst_seconds: f64, tt_centuries: f64) -> DMat3 {
compute_t_parent_this_with_polar(gmst_seconds, tt_centuries, None)
}
pub fn compute_t_parent_this_with_polar(
gmst_seconds: f64,
tt_centuries: f64,
polar: Option<(f64, f64)>,
) -> DMat3 {
let prec = precession_matrix(tt_centuries);
let nut = nutation(tt_centuries);
let rot = gast_rotation_matrix(gmst_seconds, nut.equa_of_equi);
let np = matrix3x3_product_transpose_transpose(&nut.rotation, &prec);
let scratch = if let Some((xp, yp)) = polar {
let pm = polar_motion_matrix(xp, yp);
pm.transpose() * rot.transpose()
} else {
rot.transpose()
};
scratch * np
}
pub fn compute_t_parent_this_from_tjt(gmst_seconds: f64, tt_tjt: f64) -> DMat3 {
compute_t_parent_this_from_tjt_with_polar(gmst_seconds, tt_tjt, None)
}
pub fn compute_t_parent_this_from_tjt_with_polar(
gmst_seconds: f64,
tt_tjt: f64,
polar: Option<(f64, f64)>,
) -> DMat3 {
let tt_centuries = (tt_tjt - 11544.5) / 36525.0;
compute_t_parent_this_with_polar(gmst_seconds, tt_centuries, polar)
}
pub fn compute_t_parent_this_from_tjt_with_polar_typed<P: astrodyn_quantities::frame::Planet>(
gmst_seconds: f64,
tt_tjt: f64,
polar: Option<(f64, f64)>,
) -> astrodyn_quantities::frame_transform::FrameTransform<
astrodyn_quantities::frame::RootInertial,
astrodyn_quantities::frame::PlanetFixed<P>,
> {
let mat = compute_t_parent_this_from_tjt_with_polar(gmst_seconds, tt_tjt, polar);
astrodyn_quantities::frame_transform::FrameTransform::from_matrix(mat)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compute_t_parent_this_at_nonzero_epoch() {
let tt_centuries = 0.1;
let gmst_seconds = 1_000_000.0;
let t = compute_t_parent_this(gmst_seconds, tt_centuries);
let product = t * t.transpose();
for i in 0..3 {
for j in 0..3 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(product.col(j)[i] - expected).abs() < 1e-12,
"T * T^T [{},{}] = {}, expected {}",
i,
j,
product.col(j)[i],
expected
);
}
}
let diff_from_identity = (t - glam::DMat3::IDENTITY).col(0).length()
+ (t - glam::DMat3::IDENTITY).col(1).length()
+ (t - glam::DMat3::IDENTITY).col(2).length();
assert!(
diff_from_identity > 0.01,
"RNP at non-J2000 should differ from identity, diff = {}",
diff_from_identity
);
let det = t.determinant();
assert!(
(det - 1.0).abs() < 1e-12,
"determinant should be 1.0, got {}",
det
);
}
#[test]
fn polar_motion_identity_at_zero() {
let pm = polar_motion_matrix(0.0, 0.0);
for i in 0..3 {
for j in 0..3 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(pm.col(j)[i] - expected).abs() < 1e-15,
"PM(0,0) [{},{}] = {}, expected {}",
i,
j,
pm.col(j)[i],
expected
);
}
}
}
#[test]
fn polar_motion_is_orthogonal() {
let arcsec = 4.848_136_811_095_36e-6;
let xp = 0.3 * arcsec;
let yp = 0.2 * arcsec;
let pm = polar_motion_matrix(xp, yp);
let product = pm * pm.transpose();
for i in 0..3 {
for j in 0..3 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(product.col(j)[i] - expected).abs() < 1e-14,
"PM*PM^T [{},{}] = {}",
i,
j,
product.col(j)[i]
);
}
}
let det = pm.determinant();
assert!((det - 1.0).abs() < 1e-14, "det(PM) = {}", det);
let diff = (pm - DMat3::IDENTITY).col(0).length();
assert!(diff > 1e-10 && diff < 1e-4, "PM diff from I = {}", diff);
}
#[test]
fn rnp_with_polar_differs_from_without() {
let gmst = 1_000_000.0;
let tt_c = 0.1;
let arcsec = 4.848_136_811_095_36e-6;
let xp = 0.3 * arcsec;
let yp = 0.2 * arcsec;
let without = compute_t_parent_this_with_polar(gmst, tt_c, None);
let with = compute_t_parent_this_with_polar(gmst, tt_c, Some((xp, yp)));
let det_w = with.determinant();
assert!((det_w - 1.0).abs() < 1e-12);
let diff = (with - without).col(0).length()
+ (with - without).col(1).length()
+ (with - without).col(2).length();
assert!(diff > 1e-10 && diff < 1e-4, "polar motion diff = {}", diff);
let with_zero = compute_t_parent_this_with_polar(gmst, tt_c, Some((0.0, 0.0)));
let diff_zero = (without - with_zero).col(0).length()
+ (without - with_zero).col(1).length()
+ (without - with_zero).col(2).length();
assert!(diff_zero < 1e-15, "None != Some(0,0): diff = {}", diff_zero);
}
#[test]
fn gast_rotation_is_orthogonal() {
let r = gast_rotation_matrix(1000.0, 0.5);
let rrt = r * r.transpose();
for i in 0..3 {
for j in 0..3 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(
(rrt.col(j)[i] - expected).abs() < 1e-14,
"R*R^T [{}][{}] = {}",
i,
j,
rrt.col(j)[i]
);
}
}
}
}