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(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;