#![allow(clippy::inconsistent_digit_grouping)]
#![allow(clippy::excessive_precision)]
use super::{Arcsec, Rad};
pub const EPSILON_0: Arcsec = Arcsec::new(84_381.406);
#[derive(Debug)]
pub struct FukushimaWilliamsAngles {
pub gamma_bar: Rad,
pub phi_bar: Rad,
pub psi_bar: Rad,
pub eps_a: Rad,
}
pub fn fukushima_williams(t: f64) -> FukushimaWilliamsAngles {
FukushimaWilliamsAngles {
gamma_bar: gamma_bar_arcsec(t).to_radians(),
phi_bar: phi_bar_arcsec(t).to_radians(),
psi_bar: psi_bar_arcsec(t).to_radians(),
eps_a: eps_a_arcsec(t).to_radians(),
}
}
#[derive(Debug)]
pub struct EclipticPrecessionAngles {
pub psi_a: Rad,
pub omega_a: Rad,
pub chi_a: Rad,
pub eps_a: Rad,
}
pub fn ecliptic_precession_angles(t: f64) -> EclipticPrecessionAngles {
EclipticPrecessionAngles {
psi_a: psi_a_arcsec(t).to_radians(),
omega_a: omega_a_arcsec(t).to_radians(),
chi_a: chi_a_arcsec(t).to_radians(),
eps_a: eps_a_arcsec(t).to_radians(),
}
}
#[inline]
fn horner6(t: f64, c0: f64, c1: f64, c2: f64, c3: f64, c4: f64, c5: f64) -> f64 {
c0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * c5))))
}
fn psi_a_arcsec(t: f64) -> Arcsec {
Arcsec::new(horner6(
t,
0.0,
5038.481507,
-1.0790069,
-0.00114045,
0.000132851,
-0.0000000951,
))
}
fn omega_a_arcsec(t: f64) -> Arcsec {
Arcsec::new(horner6(
t,
EPSILON_0.raw(),
-0.025754,
0.0512623,
-0.00772503,
-0.000000467,
0.0000003337,
))
}
fn chi_a_arcsec(t: f64) -> Arcsec {
Arcsec::new(horner6(
t,
0.0,
10.556403,
-2.3814292,
-0.00121197,
0.000170663,
-0.0000000560,
))
}
fn eps_a_arcsec(t: f64) -> Arcsec {
Arcsec::new(horner6(
t,
EPSILON_0.raw(),
-46.836769,
-0.0001831,
0.00200340,
-0.000000576,
-0.0000000434,
))
}
fn gamma_bar_arcsec(t: f64) -> Arcsec {
Arcsec::new(horner6(
t,
-0.052928,
10.556378,
0.4932044,
-0.00031238,
-0.000002788,
0.0000000260,
))
}
fn phi_bar_arcsec(t: f64) -> Arcsec {
Arcsec::new(horner6(
t,
84381.412819,
-46.811016,
0.0511268,
0.00053289,
-0.000000440,
-0.0000000176,
))
}
fn psi_bar_arcsec(t: f64) -> Arcsec {
Arcsec::new(horner6(
t,
-0.041775,
5038.481484,
1.5584175,
-0.00018522,
-0.000026452,
-0.0000000148,
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn phi_bar_at_j2000_differs_from_epsilon_zero_by_6819_mas() {
let fw = fukushima_williams(0.0);
let phi_bar_arcsec = fw.phi_bar.raw() / super::super::DAS2R;
let eps_0_arcsec = EPSILON_0.raw();
let delta_mas = (phi_bar_arcsec - eps_0_arcsec) * 1e3;
assert!(
(delta_mas - 6.819).abs() < 1e-6,
"phi_bar − ε₀ = {delta_mas} mas, expected 6.819 mas"
);
}
#[test]
fn eps_a_at_j2000_equals_epsilon_zero() {
let fw = fukushima_williams(0.0);
let le = ecliptic_precession_angles(0.0);
assert_eq!(fw.eps_a.raw(), EPSILON_0.to_radians().raw());
assert_eq!(le.eps_a.raw(), EPSILON_0.to_radians().raw());
}
#[test]
fn zero_constant_term_angles_at_j2000() {
let le = ecliptic_precession_angles(0.0);
assert_eq!(le.psi_a.raw(), 0.0);
assert_eq!(le.chi_a.raw(), 0.0);
let fw = fukushima_williams(0.0);
let gamma_bar_arcsec = fw.gamma_bar.raw() / super::super::DAS2R;
assert!((gamma_bar_arcsec - (-0.052928)).abs() < 1e-12);
let psi_bar_arcsec = fw.psi_bar.raw() / super::super::DAS2R;
assert!((psi_bar_arcsec - (-0.041775)).abs() < 1e-12);
}
#[test]
fn precession_polynomials_are_finite_over_wide_t_range() {
for &t in &[-10.0, -1.0, -0.5, -0.1, 0.0, 0.1, 0.5, 1.0, 10.0] {
let fw = fukushima_williams(t);
assert!(fw.gamma_bar.is_finite());
assert!(fw.phi_bar.is_finite());
assert!(fw.psi_bar.is_finite());
assert!(fw.eps_a.is_finite());
let le = ecliptic_precession_angles(t);
assert!(le.psi_a.is_finite());
assert!(le.omega_a.is_finite());
assert!(le.chi_a.is_finite());
assert!(le.eps_a.is_finite());
}
}
#[test]
fn omega_a_at_j2000_equals_epsilon_zero() {
let le = ecliptic_precession_angles(0.0);
assert_eq!(le.omega_a.raw(), EPSILON_0.to_radians().raw());
}
}