pub fn nutation_in_longitude(jd: f64) -> f64 {
let jd1 = jd;
let jd2 = 0.0;
let (dpsi, _deps) = erfars::precnutpolar::Nut00a(jd1, jd2);
dpsi * (180.0 * 3600.0 / std::f64::consts::PI)
}
pub fn nutation_in_obliquity(jd: f64) -> f64 {
let jd1 = jd;
let jd2 = 0.0;
let (_dpsi, deps) = erfars::precnutpolar::Nut00a(jd1, jd2);
deps * (180.0 * 3600.0 / std::f64::consts::PI)
}
pub fn mean_obliquity(jd: f64) -> f64 {
let jd1 = jd;
let jd2 = 0.0;
let eps_rad = erfars::precnutpolar::Obl06(jd1, jd2);
eps_rad.to_degrees()
}
pub fn true_obliquity(jd: f64) -> f64 {
mean_obliquity(jd) + nutation_in_obliquity(jd) / 3600.0
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Nutation {
pub longitude: f64,
pub obliquity: f64,
}
pub fn nutation(jd: f64) -> Nutation {
let jd1 = jd;
let jd2 = 0.0;
let (dpsi, deps) = erfars::precnutpolar::Nut00a(jd1, jd2);
let rad_to_arcsec = 180.0 * 3600.0 / std::f64::consts::PI;
Nutation {
longitude: dpsi * rad_to_arcsec,
obliquity: deps * rad_to_arcsec,
}
}
#[doc(hidden)]
pub fn nutation_in_longitude_arcsec(jd: f64) -> f64 {
nutation_in_longitude(jd)
}
#[doc(hidden)]
pub fn mean_obliquity_arcsec(jd: f64) -> f64 {
mean_obliquity(jd) * 3600.0
}
#[cfg(test)]
mod tests {
use super::*;
use crate::time::julian_date;
use chrono::{DateTime, Utc, NaiveDateTime};
#[test]
fn test_nutation_precision_august_2025() {
let dt = NaiveDateTime::new(
chrono::NaiveDate::from_ymd_opt(2025, 8, 1).unwrap(),
chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap()
);
let utc_dt = DateTime::<Utc>::from_naive_utc_and_offset(dt, Utc);
let jd_utc = julian_date(utc_dt);
let tt_offset_days = (37.0 + 32.184) / 86400.0; let jd_tt = jd_utc + tt_offset_days;
let nut = nutation(jd_tt);
let expected_dpsi = 3.821318106868885;
let expected_deps = 8.91080363873388;
let dpsi_diff = (nut.longitude - expected_dpsi).abs();
let deps_diff = (nut.obliquity - expected_deps).abs();
println!("Current dpsi: {:.9}, expected: {:.9}, diff: {:.6} mas",
nut.longitude, expected_dpsi, dpsi_diff * 1000.0);
println!("Current deps: {:.9}, expected: {:.9}, diff: {:.6} mas",
nut.obliquity, expected_deps, deps_diff * 1000.0);
assert!(dpsi_diff < 0.001, "Nutation in longitude differs by {:.6} mas", dpsi_diff * 1000.0);
assert!(deps_diff < 0.001, "Nutation in obliquity differs by {:.6} mas", deps_diff * 1000.0);
}
#[test]
fn test_nutation_with_utc_shows_issue() {
let dt = NaiveDateTime::new(
chrono::NaiveDate::from_ymd_opt(2025, 8, 1).unwrap(),
chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap()
);
let utc_dt = DateTime::<Utc>::from_naive_utc_and_offset(dt, Utc);
let jd_utc = julian_date(utc_dt);
let nut_utc = nutation(jd_utc);
println!("With UTC JD: dpsi={:.6}, deps={:.6}", nut_utc.longitude, nut_utc.obliquity);
let expected_dpsi = 3.821318106868885;
let expected_deps = 8.91080363873388;
let dpsi_error = (nut_utc.longitude - expected_dpsi).abs();
let deps_error = (nut_utc.obliquity - expected_deps).abs();
println!("Error using UTC instead of TT: dpsi={:.3} mas, deps={:.3} mas",
dpsi_error * 1000.0, deps_error * 1000.0);
}
#[test]
fn test_conversion_factor_precision() {
let exact_factor = 180.0 * 3600.0 / std::f64::consts::PI;
let current_factor = 206264.80624709636;
let factor_diff = (exact_factor - current_factor).abs();
println!("Exact factor: {:.15}", exact_factor);
println!("Current factor: {:.15}", current_factor);
println!("Difference: {:.2e}", factor_diff);
assert!(factor_diff < 1e-10, "Conversion factor precision issue");
}
#[test]
fn test_mean_obliquity_j2000() {
let jd_j2000 = 2451545.0;
let eps0 = mean_obliquity(jd_j2000);
let expected = 23.4392911;
assert!((eps0 - expected).abs() < 0.0001,
"Mean obliquity at J2000: got {:.7}, expected {:.7}", eps0, expected);
}
}