use super::types::PrecessionResult;
use crate::constants::ARCSEC_TO_RAD;
use crate::matrix::RotationMatrix3;
pub struct PrecessionIAU2000;
impl PrecessionIAU2000 {
pub fn new() -> Self {
Self
}
pub fn compute(&self, tt_centuries: f64) -> crate::AstroResult<PrecessionResult> {
let bias_matrix = self.frame_bias_matrix_iau2000();
let precession_matrix = self.precession_matrix_iau2000(tt_centuries)?;
let bias_precession_matrix = precession_matrix.multiply(&bias_matrix);
Ok(PrecessionResult {
bias_matrix,
precession_matrix,
bias_precession_matrix,
})
}
fn frame_bias_matrix_iau2000(&self) -> RotationMatrix3 {
const EPS0: f64 = 84381.448 * ARCSEC_TO_RAD;
const FRAME_BIAS_LONGITUDE: f64 = -0.041775 / 1000.0 * ARCSEC_TO_RAD;
const FRAME_BIAS_OBLIQUITY: f64 = -0.0068192 / 1000.0 * ARCSEC_TO_RAD;
const FRAME_BIAS_RA_OFFSET: f64 = -0.0146 / 1000.0 * ARCSEC_TO_RAD;
let mut rb = RotationMatrix3::identity();
rb.rotate_z(FRAME_BIAS_RA_OFFSET);
rb.rotate_y(FRAME_BIAS_LONGITUDE * libm::sin(EPS0));
rb.rotate_x(-FRAME_BIAS_OBLIQUITY);
rb
}
fn precession_matrix_iau2000(&self, tt_centuries: f64) -> crate::AstroResult<RotationMatrix3> {
const EPS0: f64 = 84381.448 * ARCSEC_TO_RAD;
let t = tt_centuries;
let psia77 = (5038.7784 + (-1.07259 + (-0.001147) * t) * t) * t * ARCSEC_TO_RAD;
let oma77 = EPS0 + ((0.05127 + (-0.007726) * t) * t) * t * ARCSEC_TO_RAD;
let chia = (10.5526 + (-2.38064 + (-0.001125) * t) * t) * t * ARCSEC_TO_RAD;
let dpsipr = -0.29965 / 1000.0 * ARCSEC_TO_RAD * t;
let depspr = -0.02524 / 1000.0 * ARCSEC_TO_RAD * t;
let psia = psia77 + dpsipr;
let oma = oma77 + depspr;
let mut rp = RotationMatrix3::identity();
rp.rotate_x(EPS0); rp.rotate_z(-psia); rp.rotate_x(-oma); rp.rotate_z(chia);
Ok(rp)
}
}
impl Default for PrecessionIAU2000 {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_helpers::assert_ulp_le;
#[test]
fn test_new_and_default() {
let p1 = PrecessionIAU2000::new();
let p2 = PrecessionIAU2000::default();
let r1 = p1.compute(0.0).unwrap();
let r2 = p2.compute(0.0).unwrap();
assert_eq!(r1.bias_matrix, r2.bias_matrix);
}
#[test]
fn test_compute_returns_rotation_matrices() {
let p = PrecessionIAU2000::new();
let result = p.compute(0.5).unwrap();
assert!(result.bias_matrix.is_rotation_matrix(1e-14));
assert!(result.precession_matrix.is_rotation_matrix(1e-14));
assert!(result.bias_precession_matrix.is_rotation_matrix(1e-14));
}
#[test]
fn test_bias_matrix_is_constant() {
let p = PrecessionIAU2000::new();
let r1 = p.compute(0.0).unwrap();
let r2 = p.compute(1.0).unwrap();
assert_eq!(r1.bias_matrix, r2.bias_matrix);
}
#[test]
fn test_precession_at_j2000_is_identity() {
let p = PrecessionIAU2000::new();
let result = p.compute(0.0).unwrap();
for i in 0..3 {
for j in 0..3 {
let expected = if i == j { 1.0 } else { 0.0 };
assert_ulp_le(
result.precession_matrix.get(i, j),
expected,
1,
&format!("precession[{},{}] at t=0", i, j),
);
}
}
}
#[test]
fn test_precession_changes_with_time() {
let p = PrecessionIAU2000::new();
let r0 = p.compute(0.0).unwrap();
let r1 = p.compute(1.0).unwrap();
let mut differs = false;
for i in 0..3 {
for j in 0..3 {
let identity_val = if i == j { 1.0 } else { 0.0 };
if (r1.precession_matrix.get(i, j) - identity_val).abs() > 1e-4 {
differs = true;
}
}
}
assert!(
differs,
"precession matrix should differ from identity after 1 century"
);
assert_ne!(r0.precession_matrix, r1.precession_matrix);
}
#[test]
fn test_combined_matrix_consistency() {
let p = PrecessionIAU2000::new();
let result = p.compute(0.5).unwrap();
let expected = result.precession_matrix.multiply(&result.bias_matrix);
for i in 0..3 {
for j in 0..3 {
assert_ulp_le(
result.bias_precession_matrix.get(i, j),
expected.get(i, j),
1,
&format!("bias_precession[{},{}]", i, j),
);
}
}
}
}