use crate::astro::precession;
use crate::time::JulianDate;
use affn::Rotation3;
use qtty::*;
#[derive(Debug, Clone, Copy)]
pub struct CipCio {
pub x: f64,
pub y: f64,
pub s: Radians,
}
pub fn cip_xy(jd: JulianDate, dpsi: Radians, deps: Radians) -> (f64, f64) {
let npb = precession::precession_nutation_matrix(jd, dpsi, deps);
let m = npb.as_matrix();
(m[2][0], m[2][1])
}
pub fn cio_locator_s(jd: JulianDate, x: f64, y: f64) -> Radians {
let t = jd.julian_centuries().value();
let poly_uas = 94.0 + 3808.65 * t - 122.68 * t.powi(2) - 72574.11 * t.powi(3);
let s_rad = -0.5 * x * y + MicroArcseconds::new(poly_uas).to::<Radian>().value();
Radians::new(s_rad)
}
pub fn cip_cio(jd: JulianDate, dpsi: Radians, deps: Radians) -> CipCio {
let (x, y) = cip_xy(jd, dpsi, deps);
let s = cio_locator_s(jd, x, y);
CipCio { x, y, s }
}
pub fn gcrs_to_cirs_matrix(x: f64, y: f64, s: Radians) -> Rotation3 {
let r2 = x * x + y * y;
let e = y.atan2(x);
let d = r2.sqrt().atan2((1.0 - r2).max(0.0).sqrt());
let (se, ce) = e.sin_cos();
let (sd, cd) = d.sin_cos();
let es = e + s.value();
let (ses, ces) = es.sin_cos();
#[rustfmt::skip]
let m = [
[
ces * cd * ce + ses * se,
ces * cd * se - ses * ce,
ces * sd,
],
[
ses * cd * ce - ces * se,
ses * cd * se + ces * ce,
ses * sd,
],
[
-sd * ce,
-sd * se,
cd,
],
];
Rotation3::from_matrix(m)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cip_xy_at_j2000() {
let (x, y) = cip_xy(JulianDate::J2000, Radians::new(0.0), Radians::new(0.0));
assert!(x.abs() < 1e-4, "X at J2000 should be ~0, got {}", x);
assert!(y.abs() < 1e-4, "Y at J2000 should be ~0, got {}", y);
}
#[test]
fn cio_locator_small() {
let s = cio_locator_s(JulianDate::J2000, 0.0, 0.0);
let s_mas = s.value() * 206_264_806.0; assert!(
s_mas.abs() < 100.0,
"s at J2000 should be < 100 mas, got {} mas",
s_mas
);
}
#[test]
fn gcrs_to_cirs_near_identity_at_j2000() {
let q = gcrs_to_cirs_matrix(0.0, 0.0, Radians::new(0.0));
let m = q.as_matrix();
for (i, row) in m.iter().enumerate().take(3) {
assert!(
(row[i] - 1.0).abs() < 1e-6,
"Q[{}][{}] = {}, expected ≈ 1",
i,
i,
row[i]
);
}
}
#[test]
fn gcrs_to_cirs_is_proper_rotation() {
let jd = JulianDate::new(2_460_000.5);
let nut = crate::astro::nutation::nutation_iau2000b(jd);
let cip = cip_cio(jd, nut.dpsi, nut.deps);
let q = gcrs_to_cirs_matrix(cip.x, cip.y, cip.s);
let m = q.as_matrix();
let det = m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
- m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
+ m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
assert!((det - 1.0).abs() < 1e-12, "det(Q) = {}, expected ≈ 1", det);
}
}