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