use crate::nutation::*;
use crate::time::julian_date;
use chrono::{TimeZone, Utc};
#[test]
fn test_nutation_at_j2000() {
let jd = 2451545.0;
let dpsi = nutation_in_longitude(jd);
let deps = nutation_in_obliquity(jd);
assert!(dpsi.abs() < 20.0, "Nutation in longitude should be < 20\", got {}\"", dpsi);
assert!(deps.abs() < 10.0, "Nutation in obliquity should be < 10\", got {}\"", deps);
}
#[test]
fn test_mean_obliquity() {
let jd = 2451545.0;
let eps0 = mean_obliquity(jd);
assert!((eps0 - 23.43927944).abs() < 0.00000001,
"Mean obliquity at J2000.0 should be 23.43927944° (IAU 2006), got {}°", eps0);
let jd_1900 = 2415020.0;
let eps0_1900 = mean_obliquity(jd_1900);
assert!((eps0_1900 - 23.4522).abs() < 0.0001,
"Mean obliquity at J1900.0 should be ~23.4522°, got {}°", eps0_1900);
}
#[test]
fn test_true_obliquity() {
let jd = 2451545.0;
let true_eps = true_obliquity(jd);
let mean_eps = mean_obliquity(jd);
let diff = (true_eps - mean_eps) * 3600.0; let deps = nutation_in_obliquity(jd);
assert!((diff - deps).abs() < 0.001,
"True obliquity should differ from mean by nutation amount");
}
#[test]
fn test_nutation_periodicity() {
let jd_start = 2451545.0; let jd_half_period = jd_start + 18.6 * 365.25 / 2.0; let jd_full_period = jd_start + 18.6 * 365.25;
let dpsi_start = nutation_in_longitude(jd_start);
let dpsi_half = nutation_in_longitude(jd_half_period);
let dpsi_full = nutation_in_longitude(jd_full_period);
assert!(dpsi_start * dpsi_half < 0.0,
"Nutation should have opposite sign at half period");
assert!((dpsi_full - dpsi_start).abs() < 2.0,
"Nutation should repeat after full period (~18.6 years)");
}
#[test]
fn test_nutation_magnitude() {
let start_date = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
let mut max_dpsi: f64 = 0.0;
let mut max_deps: f64 = 0.0;
for days in 0..7000 {
let dt = start_date + chrono::Duration::days(days);
let jd = julian_date(dt);
let dpsi = nutation_in_longitude(jd);
let deps = nutation_in_obliquity(jd);
if dpsi.abs() > max_dpsi.abs() {
max_dpsi = dpsi;
}
if deps.abs() > max_deps.abs() {
max_deps = deps;
}
assert!(dpsi.abs() <= 20.0,
"Nutation in longitude out of bounds: {}\" on day {}", dpsi, days);
assert!(deps.abs() <= 10.0,
"Nutation in obliquity out of bounds: {}\" on day {}", deps, days);
}
assert!(max_dpsi.abs() > 17.0, "Should see large nutation in longitude");
assert!(max_deps.abs() > 9.0, "Should see large nutation in obliquity");
}
#[test]
fn test_nutation_struct() {
let jd = 2451545.0;
let nut = nutation(jd);
let dpsi = nutation_in_longitude(jd);
let deps = nutation_in_obliquity(jd);
assert!((nut.longitude - dpsi).abs() < 0.001,
"Nutation struct longitude should match individual calculation");
assert!((nut.obliquity - deps).abs() < 0.001,
"Nutation struct obliquity should match individual calculation");
}
#[test]
fn test_nutation_known_values() {
let dt = Utc.with_ymd_and_hms(1987, 4, 10, 0, 0, 0).unwrap();
let jd = julian_date(dt);
let dpsi = nutation_in_longitude(jd);
let deps = nutation_in_obliquity(jd);
assert!((dpsi - (-3.788)).abs() < 0.5,
"Nutation in longitude should be ~-3.788\", got {}\"", dpsi);
assert!((deps - 9.443).abs() < 0.5,
"Nutation in obliquity should be ~9.443\", got {}\"", deps);
}
#[test]
fn test_mean_obliquity_change_over_time() {
let jd_2000 = 2451545.0;
let jd_2100 = jd_2000 + 100.0 * 365.25;
let eps_2000 = mean_obliquity(jd_2000);
let eps_2100 = mean_obliquity(jd_2100);
assert!(eps_2100 < eps_2000,
"Mean obliquity should decrease over time");
let change_arcsec = (eps_2000 - eps_2100) * 3600.0;
assert!(change_arcsec > 45.0 && change_arcsec < 49.0,
"Mean obliquity should decrease by ~47\" per century, got {}\"", change_arcsec);
}
#[test]
fn test_nutation_symmetry() {
let jd = 2451545.0;
let nut1 = nutation(jd);
let dpsi = nutation_in_longitude(jd);
let deps = nutation_in_obliquity(jd);
assert_eq!(nut1.longitude, dpsi);
assert_eq!(nut1.obliquity, deps);
}
#[test]
fn test_nutation_backwards_compatibility() {
let jd = 2451545.0;
let dpsi_old = nutation_in_longitude_arcsec(jd);
let dpsi_new = nutation_in_longitude(jd);
assert_eq!(dpsi_old, dpsi_new);
let eps0_old = mean_obliquity_arcsec(jd);
let eps0_new = mean_obliquity(jd) * 3600.0;
assert!((eps0_old - eps0_new).abs() < 0.001);
}
#[test]
fn test_iau2000a_precision() {
let test_cases = [
(2451545.0, -20000.0..0.0, -10000.0..10000.0), (2458849.5, -20000.0..0.0, -10000.0..10000.0), ];
for (jd, dpsi_range, deps_range) in test_cases {
let dpsi = nutation_in_longitude(jd);
let deps = nutation_in_obliquity(jd);
let dpsi_mas = dpsi * 1000.0;
let deps_mas = deps * 1000.0;
assert!(dpsi_range.contains(&dpsi_mas),
"dpsi at JD {}: {} mas should be in range {:?}", jd, dpsi_mas, dpsi_range);
assert!(deps_range.contains(&deps_mas),
"deps at JD {}: {} mas should be in range {:?}", jd, deps_mas, deps_range);
}
}
#[test]
fn test_nutation_principal_terms() {
let jd_start = 2451545.0; let period_days = 6798.4;
let mut values = Vec::new();
for i in 0..100 {
let jd = jd_start + (i as f64) * period_days / 100.0;
let dpsi = nutation_in_longitude(jd);
values.push(dpsi);
}
let max_val = values.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let min_val = values.iter().cloned().fold(f64::INFINITY, f64::min);
let amplitude = (max_val - min_val) / 2.0;
assert!(amplitude > 16.0 && amplitude < 20.0,
"Principal nutation amplitude should be ~17-19\", got {}\"", amplitude);
}
#[test]
fn test_nutation_short_period_terms() {
let jd_start = 2451545.0;
let mut prev_dpsi = nutation_in_longitude(jd_start);
let mut changes = Vec::new();
for day in 1..30 {
let jd = jd_start + day as f64;
let dpsi = nutation_in_longitude(jd);
changes.push((dpsi - prev_dpsi).abs());
prev_dpsi = dpsi;
}
let max_daily_change = changes.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
assert!(max_daily_change > 0.1,
"Should see measurable daily changes from short-period terms");
}
#[test]
fn test_equation_of_equinoxes() {
let jd = 2451545.0;
let dpsi = nutation_in_longitude(jd); let eps = mean_obliquity(jd);
let ee_approx = dpsi * eps.to_radians().cos() / 15.0;
assert!(ee_approx.abs() < 1.5,
"Equation of equinoxes should be < 1.5 seconds of time, got {}", ee_approx);
}
#[test]
fn test_nutation_at_extremes() {
let start_date = Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap();
let mut max_dpsi = -1000.0;
let mut min_dpsi = 1000.0;
for days in (0..7000).step_by(30) {
let dt = start_date + chrono::Duration::days(days);
let jd = julian_date(dt);
let dpsi = nutation_in_longitude(jd);
if dpsi > max_dpsi {
max_dpsi = dpsi;
}
if dpsi < min_dpsi {
min_dpsi = dpsi;
}
}
assert!(max_dpsi > 15.0,
"Maximum nutation in longitude should be > 15\", got {}\"", max_dpsi);
assert!(min_dpsi < -15.0,
"Minimum nutation in longitude should be < -15\", got {}\"", min_dpsi);
}
#[test]
fn test_nutation_matrix_consistency() {
let jd = 2451545.0;
let dpsi = nutation_in_longitude(jd);
let deps = nutation_in_obliquity(jd);
let eps = mean_obliquity(jd);
let dpsi_rad = dpsi / 206264.80624709636;
let deps_rad = deps / 206264.80624709636;
let eps_rad = eps.to_radians();
let cos_dpsi = dpsi_rad.cos();
let sin_dpsi = dpsi_rad.sin();
let _cos_eps = eps_rad.cos();
let _sin_eps = eps_rad.sin();
let _cos_deps_eps = (eps_rad + deps_rad).cos();
let _sin_deps_eps = (eps_rad + deps_rad).sin();
assert!((cos_dpsi - 1.0).abs() < 0.001, "cos(dpsi) should be ~1 for small angles");
assert!(sin_dpsi.abs() < 0.0001, "sin(dpsi) should be small");
}