Skip to main content

celestial_time/transforms/nutation/
mod.rs

1pub mod iau2000a;
2pub mod iau2000b;
3pub mod iau2006a;
4
5use crate::{TimeResult, TT};
6
7#[derive(Debug)]
8pub struct NutationResult {
9    core_result: celestial_core::nutation::NutationResult,
10    model: NutationModel,
11}
12
13impl NutationResult {
14    pub fn new(core_result: celestial_core::nutation::NutationResult, model: NutationModel) -> Self {
15        Self { core_result, model }
16    }
17
18    pub fn nutation_longitude(&self) -> f64 {
19        self.core_result.delta_psi
20    }
21
22    pub fn nutation_obliquity(&self) -> f64 {
23        self.core_result.delta_eps
24    }
25
26    pub fn model(&self) -> NutationModel {
27        self.model
28    }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum NutationModel {
33    IAU2000A,
34    IAU2000B,
35    IAU2006A,
36}
37
38pub trait NutationCalculator {
39    fn nutation_iau2000a(&self) -> TimeResult<NutationResult>;
40
41    fn nutation_iau2000b(&self) -> TimeResult<NutationResult>;
42
43    fn nutation_iau2006a(&self) -> TimeResult<NutationResult>;
44
45    fn nutation(&self) -> TimeResult<NutationResult> {
46        self.nutation_iau2006a()
47    }
48}
49
50impl NutationCalculator for TT {
51    fn nutation_iau2000a(&self) -> TimeResult<NutationResult> {
52        iau2000a::calculate(self)
53    }
54
55    fn nutation_iau2000b(&self) -> TimeResult<NutationResult> {
56        iau2000b::calculate(self)
57    }
58
59    fn nutation_iau2006a(&self) -> TimeResult<NutationResult> {
60        iau2006a::calculate(self)
61    }
62}
63
64#[cfg(test)]
65mod integration_tests {
66    use super::*;
67    use crate::TT;
68    use celestial_core::constants::J2000_JD;
69
70    #[test]
71    fn test_all_nutation_models_at_j2000() {
72        let j2000_tt = TT::j2000();
73
74        let result_2000a = j2000_tt.nutation_iau2000a().unwrap();
75        let result_2000b = j2000_tt.nutation_iau2000b().unwrap();
76        let result_2006a = j2000_tt.nutation_iau2006a().unwrap();
77
78        assert!(
79            result_2000a.nutation_longitude().abs() < 1e-3,
80            "2000A nutation too large"
81        );
82        assert!(
83            result_2000b.nutation_longitude().abs() < 1e-3,
84            "2000B nutation too large"
85        );
86        assert!(
87            result_2006a.nutation_longitude().abs() < 1e-3,
88            "2006A nutation too large"
89        );
90
91        assert_eq!(result_2000a.model, NutationModel::IAU2000A);
92        assert_eq!(result_2000b.model, NutationModel::IAU2000B);
93        assert_eq!(result_2006a.model, NutationModel::IAU2006A);
94    }
95
96    #[test]
97    fn test_iau2000b_is_abbreviated_version() {
98        let j2000_tt = TT::j2000();
99
100        let result_2000a = j2000_tt.nutation_iau2000a().unwrap();
101        let result_2000b = j2000_tt.nutation_iau2000b().unwrap();
102
103        let diff_psi =
104            (result_2000a.nutation_longitude() - result_2000b.nutation_longitude()).abs();
105        let diff_eps =
106            (result_2000a.nutation_obliquity() - result_2000b.nutation_obliquity()).abs();
107
108        assert!(
109            diff_psi < 5e-9,
110            "2000B differs too much from 2000A in longitude: {:.3} mas",
111            diff_psi * 206264806.247
112        );
113        assert!(
114            diff_eps < 5e-9,
115            "2000B differs too much from 2000A in obliquity: {:.3} mas",
116            diff_eps * 206264806.247
117        );
118    }
119
120    #[test]
121    fn test_iau2006a_corrections_reasonable() {
122        let j2000_tt = TT::j2000();
123
124        let result_2000a = j2000_tt.nutation_iau2000a().unwrap();
125        let result_2006a = j2000_tt.nutation_iau2006a().unwrap();
126
127        let diff_psi =
128            (result_2000a.nutation_longitude() - result_2006a.nutation_longitude()).abs();
129        let diff_eps =
130            (result_2000a.nutation_obliquity() - result_2006a.nutation_obliquity()).abs();
131
132        assert!(
133            diff_psi < 1e-8,
134            "2006A corrections too large relative to 2000A"
135        );
136        assert!(
137            diff_eps < 1e-8,
138            "2006A corrections too large relative to 2000A"
139        );
140    }
141
142    #[test]
143    fn test_nutation_trait_methods() {
144        let j2000_tt = TT::j2000();
145
146        let default_nutation = j2000_tt.nutation().unwrap();
147
148        assert_eq!(default_nutation.model, NutationModel::IAU2006A);
149    }
150
151    #[test]
152    fn test_nutation_result_model_getter() {
153        let j2000_tt = TT::j2000();
154        let result_2000a = j2000_tt.nutation_iau2000a().unwrap();
155        let result_2000b = j2000_tt.nutation_iau2000b().unwrap();
156        let result_2006a = j2000_tt.nutation_iau2006a().unwrap();
157
158        assert_eq!(result_2000a.model(), NutationModel::IAU2000A);
159        assert_eq!(result_2000b.model(), NutationModel::IAU2000B);
160        assert_eq!(result_2006a.model(), NutationModel::IAU2006A);
161    }
162
163    #[test]
164    fn test_nutation_epoch_too_far_from_j2000() {
165        use crate::JulianDate;
166
167        let far_future_jd = J2000_JD + (25.0 * celestial_core::constants::DAYS_PER_JULIAN_CENTURY);
168        let far_future_tt = TT::from_julian_date(JulianDate::from_f64(far_future_jd));
169
170        let result = far_future_tt.nutation_iau2006a();
171        assert!(result.is_err());
172
173        if let Err(crate::TimeError::InvalidEpoch(msg)) = result {
174            assert!(msg.contains("Epoch too far from J2000.0"));
175        } else {
176            panic!("Expected InvalidEpoch error");
177        }
178    }
179}
180
181mod utils {
182    use crate::{TimeError, TimeResult, TT};
183
184    pub fn tt_to_centuries(tt: &TT) -> TimeResult<f64> {
185        let jd = tt.to_julian_date();
186        let centuries = celestial_core::utils::jd_to_centuries(jd.jd1(), jd.jd2());
187
188        if centuries.abs() > 20.0 {
189            return Err(TimeError::InvalidEpoch(format!(
190                "Epoch too far from J2000.0 for nutation model: {:.1} centuries",
191                centuries
192            )));
193        }
194
195        Ok(centuries)
196    }
197}
198
199pub(crate) use utils::tt_to_centuries;