1use fast_polynomial::poly_array;
6use glam::DMat3;
7use lox_test_utils::ApproxEq;
8use lox_time::{Time, julian_dates::JulianDate, time_scales::Tt};
9use lox_units::Angle;
10
11use crate::iers::{ReferenceSystem, ecliptic::MeanObliquity};
12
13impl ReferenceSystem {
14 pub fn precession(&self, time: Time<Tt>) -> PrecessionAngles {
15 match self {
16 ReferenceSystem::Iers1996 => PrecessionAngles::iau1976(time),
17 ReferenceSystem::Iers2003(_) => PrecessionAngles::iau2000(time),
18 ReferenceSystem::Iers2010 => PrecessionAngles::iau2006(time),
19 }
20 }
21 pub fn bias_precession_matrix(&self, time: Time<Tt>) -> DMat3 {
23 self.precession(time).bias_precession_matrix()
24 }
25
26 pub fn precession_matrix(&self, time: Time<Tt>) -> DMat3 {
28 self.precession(time).precession_matrix()
29 }
30}
31
32pub enum PrecessionAngles {
33 Iau1976 { zeta: Angle, z: Angle, theta: Angle },
35 Iau2000 {
37 psia: Angle,
38 oma: Angle,
39 chia: Angle,
40 },
41 Iau2006 {
43 gamb: Angle,
44 phib: Angle,
45 psib: Angle,
46 epsa: Angle,
47 },
48}
49
50impl PrecessionAngles {
51 pub fn iau1976(time: Time<Tt>) -> Self {
52 let t = time.centuries_since_j2000();
53
54 let tas2r = Angle::arcseconds(t);
55 let w = 2306.2181;
56 let zeta = (w + ((0.30188) + 0.017998 * t) * t) * tas2r;
57 let z = (w + ((1.09468) + 0.018203 * t) * t) * tas2r;
58 let theta = (2004.3109 + ((-0.42665) - 0.041833 * t) * t) * tas2r;
59
60 Self::Iau1976 { zeta, z, theta }
61 }
62
63 pub fn iau2000(time: Time<Tt>) -> Self {
64 let t = time.centuries_since_j2000();
65
66 let psia77 = Angle::arcseconds(poly_array(t, &[0.0, 5038.7784, -1.07259, -0.001147]));
68 let oma77 = EPS0 + Angle::arcseconds(poly_array(t, &[0.0, 0.0, 0.05127, -0.007726]));
69 let chia = Angle::arcseconds(poly_array(t, &[0.0, 10.5526, -2.38064, -0.001125]));
70
71 let PrecessionCorrectionsIau2000 { dpsipr, depspr } =
73 PrecessionCorrectionsIau2000::new(time);
74 let psia = psia77 + dpsipr;
75 let oma = oma77 + depspr;
76
77 Self::Iau2000 { psia, oma, chia }
78 }
79
80 pub fn iau2006(time: Time<Tt>) -> Self {
81 let t = time.centuries_since_j2000();
82
83 let gamb = Angle::arcseconds(poly_array(
84 t,
85 &[
86 -0.052928,
87 10.556378,
88 0.4932044,
89 -0.00031238,
90 -0.000002788,
91 0.0000000260,
92 ],
93 ));
94 let phib = Angle::arcseconds(poly_array(
95 t,
96 &[
97 84381.412819,
98 -46.811016,
99 0.0511268,
100 0.00053289,
101 -0.000000440,
102 -0.0000000176,
103 ],
104 ));
105 let psib = Angle::arcseconds(poly_array(
106 t,
107 &[
108 -0.041775,
109 5038.481484,
110 1.5584175,
111 -0.00018522,
112 -0.000026452,
113 -0.0000000148,
114 ],
115 ));
116 let epsa = MeanObliquity::iau2006(time.with_scale(Tt)).0;
117
118 Self::Iau2006 {
119 gamb,
120 phib,
121 psib,
122 epsa,
123 }
124 }
125
126 pub fn bias_precession_matrix(&self) -> DMat3 {
127 match *self {
128 PrecessionAngles::Iau1976 { zeta, z, theta } => {
129 let rp = (-z).rotation_z() * theta.rotation_y() * (-zeta).rotation_z();
130 rp * frame_bias()
131 }
132 PrecessionAngles::Iau2000 { psia, oma, chia } => {
133 let rp = chia.rotation_z()
134 * (-oma).rotation_x()
135 * (-psia).rotation_z()
136 * EPS0.rotation_x();
137
138 rp * frame_bias()
139 }
140 PrecessionAngles::Iau2006 {
141 gamb,
142 phib,
143 psib,
144 epsa,
145 } => {
146 (-epsa).rotation_x() * (-psib).rotation_z() * phib.rotation_x() * gamb.rotation_z()
147 }
148 }
149 }
150
151 pub fn precession_matrix(&self) -> DMat3 {
154 match *self {
155 PrecessionAngles::Iau1976 { zeta, z, theta } => {
156 (-z).rotation_z() * theta.rotation_y() * (-zeta).rotation_z()
157 }
158 PrecessionAngles::Iau2000 { psia, oma, chia } => {
159 chia.rotation_z() * (-oma).rotation_x() * (-psia).rotation_z() * EPS0.rotation_x()
160 }
161 PrecessionAngles::Iau2006 {
162 gamb,
163 phib,
164 psib,
165 epsa,
166 } => {
167 let bp = (-epsa).rotation_x()
168 * (-psib).rotation_z()
169 * phib.rotation_x()
170 * gamb.rotation_z();
171 bp * frame_bias().transpose()
172 }
173 }
174 }
175}
176
177const D_PSI_BIAS: Angle = Angle::arcseconds(-0.041775);
178const D_EPS_BIAS: Angle = Angle::arcseconds(-0.0068192);
179const D_RA0: Angle = Angle::arcseconds(-0.0146);
180const EPS0: Angle = Angle::arcseconds(84381.448);
181
182pub fn frame_bias() -> DMat3 {
183 (-D_EPS_BIAS).rotation_x() * (EPS0.sin() * D_PSI_BIAS).rotation_y() * D_RA0.rotation_z()
184}
185
186const PRECOR: Angle = Angle::arcseconds(-0.29965);
187const OBLCOR: Angle = Angle::arcseconds(-0.02524);
188
189#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, ApproxEq)]
190pub struct PrecessionCorrectionsIau2000 {
191 pub dpsipr: Angle,
192 pub depspr: Angle,
193}
194
195impl PrecessionCorrectionsIau2000 {
196 pub fn new(time: Time<Tt>) -> Self {
197 let t = time.centuries_since_j2000();
198
199 Self {
200 dpsipr: t * PRECOR,
201 depspr: t * OBLCOR,
202 }
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use lox_test_utils::assert_approx_eq;
209 use lox_units::AngleUnits;
210
211 use crate::iers::Iau2000Model;
212
213 use super::*;
214
215 #[test]
216 fn test_precession_iers1996() {
217 let time = Time::from_two_part_julian_date(Tt, 2400000.5, 50123.9999);
218 let exp = DMat3::from_cols_array(&[
219 0.999_999_550_432_835_1,
220 8.696_632_209_480_961e-4,
221 3.779_153_474_959_888_4e-4,
222 -8.696_632_209_485_112e-4,
223 0.999_999_621_842_856_1,
224 -1.643_284_776_111_886_4e-7,
225 -3.779_153_474_950_335e-4,
226 -1.643_306_746_147_367e-7,
227 0.999_999_928_589_979,
228 ])
229 .transpose()
230 * frame_bias();
231 let act = ReferenceSystem::Iers1996.bias_precession_matrix(time);
232 assert_approx_eq!(act, exp, atol <= 1e-12);
233 }
234
235 #[test]
236 fn test_bias_precession_iau2000() {
237 let time = Time::from_two_part_julian_date(Tt, 2400000.5, 50123.9999);
238 let exp = DMat3::from_cols_array(&[
239 0.999_999_550_517_508_8,
240 8.695_405_883_617_885e-4,
241 3.779_734_722_239_007e-4,
242 -8.695_405_990_410_864e-4,
243 0.999_999_621_949_492_5,
244 -1.360_775_820_404_982e-7,
245 -3.779_734_476_558_185e-4,
246 -1.925_857_585_832_024e-7,
247 0.999_999_928_568_015_3,
248 ])
249 .transpose();
250 let act = ReferenceSystem::Iers2003(Iau2000Model::A).bias_precession_matrix(time);
251 assert_approx_eq!(act, exp, atol <= 1e-12);
252 }
253
254 #[test]
255 fn test_precession_iau2006() {
256 let time = Time::from_two_part_julian_date(Tt, 2400000.5, 50123.9999);
257 let exp = DMat3::from_cols_array(&[
258 0.999_999_550_517_600_7,
259 8.695_404_617_348_209e-4,
260 3.779_735_201_865_589e-4,
261 -8.695_404_723_772_031e-4,
262 0.999_999_621_949_602_7,
263 -1.361_752_497_080_270_2e-7,
264 -3.779_734_957_034_089_7e-4,
265 -1.924_880_847_894_457e-7,
266 0.999_999_928_567_997_2,
267 ])
268 .transpose();
269 let act = ReferenceSystem::Iers2010.bias_precession_matrix(time);
270 assert_approx_eq!(act, exp, atol <= 1e-12);
271 }
272
273 #[test]
274 fn test_frame_bias_iau2000() {
275 let exp = DMat3::from_cols_array(&[
276 0.999_999_999_999_994_2,
277 -7.078_279_744_199_197e-8,
278 8.056_217_146_976_134e-8,
279 7.078_279_477_857_338e-8,
280 0.999_999_999_999_997,
281 3.306_041_454_222_136_4e-8,
282 -8.056_217_380_986_972e-8,
283 -3.306_040_883_980_552_3e-8,
284 0.999_999_999_999_996_2,
285 ])
286 .transpose();
287 let act = frame_bias();
288 assert_approx_eq!(act, exp, atol <= 1e-12);
289 }
290
291 #[test]
292 fn test_precession_corrections_iau2000() {
293 let time = Time::from_two_part_julian_date(Tt, 2400000.5, 53736.0);
294 let exp = PrecessionCorrectionsIau2000 {
295 dpsipr: -8.716_465_172_668_348e-8.rad(),
296 depspr: -7.342_018_386_722_813e-9.rad(),
297 };
298 let act = PrecessionCorrectionsIau2000::new(time);
299 assert_approx_eq!(act, exp, atol <= 1e-22);
300 }
301
302 #[test]
303 fn test_decomposition_invariant_iers1996() {
304 let time = Time::from_two_part_julian_date(Tt, 2400000.5, 50123.9999);
305 let bp = ReferenceSystem::Iers1996.bias_precession_matrix(time);
306 let p = ReferenceSystem::Iers1996.precession_matrix(time);
307 let b = frame_bias();
308 assert_approx_eq!(bp, p * b, atol <= 1e-14);
309 }
310
311 #[test]
312 fn test_decomposition_invariant_iers2003() {
313 let time = Time::from_two_part_julian_date(Tt, 2400000.5, 50123.9999);
314 let bp = ReferenceSystem::Iers2003(Iau2000Model::A).bias_precession_matrix(time);
315 let p = ReferenceSystem::Iers2003(Iau2000Model::A).precession_matrix(time);
316 let b = frame_bias();
317 assert_approx_eq!(bp, p * b, atol <= 1e-14);
318 }
319
320 #[test]
321 fn test_decomposition_invariant_iers2010() {
322 let time = Time::from_two_part_julian_date(Tt, 2400000.5, 50123.9999);
323 let bp = ReferenceSystem::Iers2010.bias_precession_matrix(time);
324 let p = ReferenceSystem::Iers2010.precession_matrix(time);
325 let b = frame_bias();
326 assert_approx_eq!(bp, p * b, atol <= 1e-14);
327 }
328}