celestial_time/transforms/nutation/
mod.rs1pub 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;