1use std::{fmt::Display, ops::Mul};
6
7use glam::{DMat3, DVec3};
8use lox_bodies::{TryRotationalElements, UndefinedOriginPropertyError};
9use lox_core::{coords::Cartesian, f64::consts::ROTATION_RATE_EARTH};
10use lox_test_utils::ApproxEq;
11use lox_time::{
12 Time,
13 julian_dates::JulianDate,
14 offsets::{OffsetProvider, TryOffset},
15 time_scales::{Tdb, TimeScale, Tt, Ut1},
16};
17use thiserror::Error;
18
19use crate::{
20 Iau, ReferenceFrame,
21 iau::icrf_to_iau,
22 iers::{
23 Corrections, ReferenceSystem,
24 cio::CioLocator,
25 cip::CipCoords,
26 earth_rotation::{EarthRotationAngle, EquationOfTheEquinoxes},
27 polar_motion::PoleCoords,
28 precession::frame_bias,
29 },
30};
31
32mod impls;
33
34pub trait TryRotation<Origin, Target, T>
35where
36 Origin: ReferenceFrame,
37 Target: ReferenceFrame,
38 T: TimeScale,
39{
40 type Error: std::error::Error + Send + Sync + 'static;
41
42 fn try_rotation(
43 &self,
44 origin: Origin,
45 target: Target,
46 time: Time<T>,
47 ) -> Result<Rotation, Self::Error>;
48}
49
50pub trait TryComposedRotation<T, P>
51where
52 T: TimeScale + Copy,
53{
54 fn try_composed_rotation(&self, provider: &P, time: Time<T>)
55 -> Result<Rotation, RotationError>;
56}
57
58impl<T, P, R1, R2, R3> TryComposedRotation<T, P> for (R1, R2, R3)
59where
60 T: TimeScale + Copy,
61 R1: ReferenceFrame + Copy,
62 R2: ReferenceFrame + Copy,
63 R3: ReferenceFrame + Copy,
64 P: RotationProvider<T>
65 + TryRotation<R1, R2, T, Error = RotationError>
66 + TryRotation<R2, R3, T, Error = RotationError>,
67{
68 fn try_composed_rotation(
69 &self,
70 provider: &P,
71 time: Time<T>,
72 ) -> Result<Rotation, RotationError> {
73 Ok(provider
74 .try_rotation(self.0, self.1, time)?
75 .compose(provider.try_rotation(self.1, self.2, time)?))
76 }
77}
78
79impl<T, P, R1, R2, R3, R4> TryComposedRotation<T, P> for (R1, R2, R3, R4)
80where
81 T: TimeScale + Copy,
82 R1: ReferenceFrame + Copy,
83 R2: ReferenceFrame + Copy,
84 R3: ReferenceFrame + Copy,
85 R4: ReferenceFrame + Copy,
86 P: RotationProvider<T>
87 + TryRotation<R1, R2, T, Error = RotationError>
88 + TryRotation<R2, R3, T, Error = RotationError>
89 + TryRotation<R3, R4, T, Error = RotationError>,
90{
91 fn try_composed_rotation(
92 &self,
93 provider: &P,
94 time: Time<T>,
95 ) -> Result<Rotation, RotationError> {
96 Ok(provider
97 .try_rotation(self.0, self.1, time)?
98 .compose(provider.try_rotation(self.1, self.2, time)?)
99 .compose(provider.try_rotation(self.2, self.3, time)?))
100 }
101}
102
103impl<T, P, R1, R2, R3, R4, R5> TryComposedRotation<T, P> for (R1, R2, R3, R4, R5)
104where
105 T: TimeScale + Copy,
106 R1: ReferenceFrame + Copy,
107 R2: ReferenceFrame + Copy,
108 R3: ReferenceFrame + Copy,
109 R4: ReferenceFrame + Copy,
110 R5: ReferenceFrame + Copy,
111 P: RotationProvider<T>
112 + TryRotation<R1, R2, T, Error = RotationError>
113 + TryRotation<R2, R3, T, Error = RotationError>
114 + TryRotation<R3, R4, T, Error = RotationError>
115 + TryRotation<R4, R5, T, Error = RotationError>,
116{
117 fn try_composed_rotation(
118 &self,
119 provider: &P,
120 time: Time<T>,
121 ) -> Result<Rotation, RotationError> {
122 Ok(provider
123 .try_rotation(self.0, self.1, time)?
124 .compose(provider.try_rotation(self.1, self.2, time)?)
125 .compose(provider.try_rotation(self.2, self.3, time)?)
126 .compose(provider.try_rotation(self.3, self.4, time)?))
127 }
128}
129
130impl<T, P, R1, R2, R3, R4, R5, R6> TryComposedRotation<T, P> for (R1, R2, R3, R4, R5, R6)
131where
132 T: TimeScale + Copy,
133 R1: ReferenceFrame + Copy,
134 R2: ReferenceFrame + Copy,
135 R3: ReferenceFrame + Copy,
136 R4: ReferenceFrame + Copy,
137 R5: ReferenceFrame + Copy,
138 R6: ReferenceFrame + Copy,
139 P: RotationProvider<T>
140 + TryRotation<R1, R2, T, Error = RotationError>
141 + TryRotation<R2, R3, T, Error = RotationError>
142 + TryRotation<R3, R4, T, Error = RotationError>
143 + TryRotation<R4, R5, T, Error = RotationError>
144 + TryRotation<R5, R6, T, Error = RotationError>,
145{
146 fn try_composed_rotation(
147 &self,
148 provider: &P,
149 time: Time<T>,
150 ) -> Result<Rotation, RotationError> {
151 Ok(provider
152 .try_rotation(self.0, self.1, time)?
153 .compose(provider.try_rotation(self.1, self.2, time)?)
154 .compose(provider.try_rotation(self.2, self.3, time)?)
155 .compose(provider.try_rotation(self.3, self.4, time)?)
156 .compose(provider.try_rotation(self.4, self.5, time)?))
157 }
158}
159
160#[derive(Debug)]
161pub enum RotationErrorKind {
162 Offset,
163 Eop,
164}
165
166impl Display for RotationErrorKind {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 match self {
169 RotationErrorKind::Offset => "offset error".fmt(f),
170 RotationErrorKind::Eop => "EOP error".fmt(f),
171 }
172 }
173}
174
175#[derive(Debug, Error)]
176#[error("{kind}: {error}")]
177pub struct RotationError {
178 kind: RotationErrorKind,
179 error: Box<dyn std::error::Error + Send + Sync + 'static>,
180}
181
182impl RotationError {
183 pub fn offset(err: impl std::error::Error + Send + Sync + 'static) -> Self {
184 RotationError {
185 kind: RotationErrorKind::Offset,
186 error: Box::new(err),
187 }
188 }
189
190 pub fn eop(err: impl std::error::Error + Send + Sync + 'static) -> Self {
191 RotationError {
192 kind: RotationErrorKind::Eop,
193 error: Box::new(err),
194 }
195 }
196}
197
198#[derive(Debug, Error)]
199pub enum DynRotationError {
200 #[error(transparent)]
201 Offset(#[from] RotationError),
202 #[error("incompatible reference systems")]
203 IncompatibleReferenceSystems,
204 #[error(transparent)]
205 UndefinedProperty(#[from] UndefinedOriginPropertyError),
206}
207
208pub trait RotationProvider<T: TimeScale>: OffsetProvider {
209 type EopError: std::error::Error + Send + Sync + 'static;
210
211 fn corrections(
212 &self,
213 time: Time<T>,
214 sys: ReferenceSystem,
215 ) -> Result<Corrections, Self::EopError>;
216 fn pole_coords(&self, time: Time<T>) -> Result<PoleCoords, Self::EopError>;
217
218 fn icrf_to_iau<R>(&self, time: Time<T>, frame: Iau<R>) -> Result<Rotation, RotationError>
219 where
220 T: TimeScale + Copy,
221 R: TryRotationalElements,
222 Self: TryOffset<T, Tdb>,
223 {
224 let seconds = time
225 .try_to_scale(Tdb, self)
226 .map_err(RotationError::offset)?
227 .seconds_since_j2000();
228 let angles = frame.rotational_elements(seconds);
229 let rates = frame.rotational_element_rates(seconds);
230
231 Ok(icrf_to_iau(angles, rates))
232 }
233 fn iau_to_icrf<R>(&self, time: Time<T>, frame: Iau<R>) -> Result<Rotation, RotationError>
234 where
235 T: TimeScale + Copy,
236 R: TryRotationalElements,
237 Self: TryOffset<T, Tdb>,
238 {
239 Ok(self.icrf_to_iau(time, frame)?.transpose())
240 }
241
242 fn icrf_to_itrf(&self, time: Time<T>) -> Result<Rotation, RotationError>
244 where
245 T: TimeScale + Copy,
246 Self: TryOffset<T, Tdb> + TryOffset<T, Tt> + TryOffset<T, Ut1>,
247 {
248 Ok(self
249 .icrf_to_cirf(time)?
250 .compose(self.cirf_to_tirf(time)?)
251 .compose(self.tirf_to_itrf(time)?))
252 }
253 fn itrf_to_icrf(&self, time: Time<T>) -> Result<Rotation, RotationError>
254 where
255 T: TimeScale + Copy,
256 Self: TryOffset<T, Tdb> + TryOffset<T, Tt> + TryOffset<T, Ut1>,
257 {
258 Ok(self.icrf_to_itrf(time)?.transpose())
259 }
260
261 fn icrf_to_j2000(&self) -> Rotation {
262 Rotation::new(frame_bias())
263 }
264
265 fn j2000_to_icrf(&self) -> Rotation {
266 Rotation::new(frame_bias().transpose())
267 }
268
269 fn j2000_to_mod(&self, time: Time<T>, sys: ReferenceSystem) -> Result<Rotation, RotationError>
270 where
271 T: TimeScale + Copy,
272 Self: TryOffset<T, Tt>,
273 {
274 let time = time.try_to_scale(Tt, self).map_err(RotationError::offset)?;
275 Ok(sys.precession_matrix(time).into())
276 }
277
278 fn mod_to_j2000(&self, time: Time<T>, sys: ReferenceSystem) -> Result<Rotation, RotationError>
279 where
280 T: TimeScale + Copy,
281 Self: TryOffset<T, Tt>,
282 {
283 Ok(self.j2000_to_mod(time, sys)?.transpose())
284 }
285
286 fn icrf_to_mod(&self, time: Time<T>, sys: ReferenceSystem) -> Result<Rotation, RotationError>
287 where
288 T: TimeScale + Copy,
289 Self: TryOffset<T, Tt>,
290 {
291 let time = time.try_to_scale(Tt, self).map_err(RotationError::offset)?;
292 Ok(sys.bias_precession_matrix(time).into())
293 }
294 fn mod_to_icrf(&self, time: Time<T>, sys: ReferenceSystem) -> Result<Rotation, RotationError>
295 where
296 T: TimeScale + Copy,
297 Self: TryOffset<T, Tt>,
298 {
299 Ok(self.icrf_to_mod(time, sys)?.transpose())
300 }
301
302 fn mod_to_tod(&self, time: Time<T>, sys: ReferenceSystem) -> Result<Rotation, RotationError>
303 where
304 T: TimeScale + Copy,
305 Self: TryOffset<T, Tdb>,
306 {
307 let tdb = time
308 .try_to_scale(Tdb, self)
309 .map_err(RotationError::offset)?;
310 let corr = self.corrections(time, sys).map_err(RotationError::eop)?;
311 Ok(sys.nutation_matrix(tdb, corr).into())
312 }
313 fn tod_to_mod(&self, time: Time<T>, sys: ReferenceSystem) -> Result<Rotation, RotationError>
314 where
315 T: TimeScale + Copy,
316 Self: TryOffset<T, Tdb>,
317 {
318 Ok(self.mod_to_tod(time, sys)?.transpose())
319 }
320
321 fn tod_to_pef(&self, time: Time<T>, sys: ReferenceSystem) -> Result<Rotation, RotationError>
322 where
323 T: TimeScale + Copy,
324 Self: TryOffset<T, Tt> + TryOffset<T, Ut1>,
325 {
326 let tt = time.try_to_scale(Tt, self).map_err(RotationError::offset)?;
327 let ut1 = time
328 .try_to_scale(Ut1, self)
329 .map_err(RotationError::offset)?;
330 let corr = self.corrections(time, sys).map_err(RotationError::eop)?;
331 Ok(
332 Rotation::new(sys.earth_rotation(tt, ut1, corr)).with_angular_velocity(DVec3::new(
333 0.0,
334 0.0,
335 ROTATION_RATE_EARTH,
336 )),
337 )
338 }
339 fn pef_to_tod(&self, time: Time<T>, sys: ReferenceSystem) -> Result<Rotation, RotationError>
340 where
341 T: TimeScale + Copy,
342 Self: TryOffset<T, Tt> + TryOffset<T, Ut1>,
343 {
344 Ok(self.tod_to_pef(time, sys)?.transpose())
345 }
346
347 fn pef_to_itrf(&self, time: Time<T>, sys: ReferenceSystem) -> Result<Rotation, RotationError>
348 where
349 T: TimeScale + Copy,
350 Self: TryOffset<T, Tt>,
351 {
352 let tt = time.try_to_scale(Tt, self).map_err(RotationError::offset)?;
353 let pole_coords = self.pole_coords(time).map_err(RotationError::eop)?;
354 Ok(sys.polar_motion_matrix(tt, pole_coords).into())
355 }
356
357 fn itrf_to_pef(&self, time: Time<T>, sys: ReferenceSystem) -> Result<Rotation, RotationError>
358 where
359 T: TimeScale + Copy,
360 Self: TryOffset<T, Tt>,
361 {
362 Ok(self.pef_to_itrf(time, sys)?.transpose())
363 }
364
365 fn tod_to_teme(&self, time: Time<T>) -> Result<Rotation, RotationError>
366 where
367 T: TimeScale + Copy,
368 Self: TryOffset<T, Tdb>,
369 {
370 let tdb = time
371 .try_to_scale(Tdb, self)
372 .map_err(RotationError::offset)?;
373
374 let eoe = EquationOfTheEquinoxes::iau1994(tdb);
375
376 Ok(Rotation::new(eoe.0.rotation_z()))
378 }
379
380 fn teme_to_tod(&self, time: Time<T>) -> Result<Rotation, RotationError>
381 where
382 T: TimeScale + Copy,
383 Self: TryOffset<T, Tdb>,
384 {
385 Ok(self.tod_to_teme(time)?.transpose())
386 }
387
388 fn icrf_to_cirf(&self, time: Time<T>) -> Result<Rotation, RotationError>
389 where
390 T: TimeScale + Copy,
391 Self: TryOffset<T, Tdb>,
392 {
393 let tdb = time
394 .try_to_scale(Tdb, self)
395 .map_err(RotationError::offset)?;
396 let mut xy = CipCoords::iau2006(tdb);
397 let s = CioLocator::iau2006(tdb, xy);
398
399 xy += self
400 .corrections(time, ReferenceSystem::Iers2010)
401 .unwrap_or_default();
402
403 Ok(Rotation::new(xy.celestial_to_intermediate_matrix(s)))
404 }
405 fn cirf_to_icrf(&self, time: Time<T>) -> Result<Rotation, RotationError>
406 where
407 T: TimeScale + Copy,
408 Self: TryOffset<T, Tdb>,
409 {
410 Ok(self.icrf_to_cirf(time)?.transpose())
411 }
412
413 fn cirf_to_tirf(&self, time: Time<T>) -> Result<Rotation, RotationError>
414 where
415 T: TimeScale + Copy,
416 Self: TryOffset<T, Ut1>,
417 {
418 let time = time
419 .try_to_scale(Ut1, self)
420 .map_err(RotationError::offset)?;
421 let era = EarthRotationAngle::iau2000(time);
422 Ok(
423 Rotation::new(era.0.rotation_z()).with_angular_velocity(DVec3::new(
424 0.0,
425 0.0,
426 ROTATION_RATE_EARTH,
427 )),
428 )
429 }
430 fn tirf_to_cirf(&self, time: Time<T>) -> Result<Rotation, RotationError>
431 where
432 T: TimeScale + Copy,
433 Self: TryOffset<T, Ut1>,
434 {
435 Ok(self.cirf_to_tirf(time)?.transpose())
436 }
437
438 fn tirf_to_itrf(&self, time: Time<T>) -> Result<Rotation, RotationError>
439 where
440 T: TimeScale + Copy,
441 Self: TryOffset<T, Tt>,
442 {
443 let tt = time.try_to_scale(Tt, self).map_err(RotationError::offset)?;
444 let pole_coords = self.pole_coords(time).map_err(RotationError::eop)?;
445 Ok(ReferenceSystem::Iers2010
446 .polar_motion_matrix(tt, pole_coords)
447 .into())
448 }
449
450 fn itrf_to_tirf(&self, time: Time<T>) -> Result<Rotation, RotationError>
451 where
452 T: TimeScale + Copy,
453 Self: TryOffset<T, Tt>,
454 {
455 Ok(self.tirf_to_itrf(time)?.transpose())
456 }
457}
458
459fn rotation_matrix_derivative(m: DMat3, v: DVec3) -> DMat3 {
460 let sx = DVec3::new(0.0, v.z, v.y);
461 let sy = DVec3::new(-v.z, 0.0, v.x);
462 let sz = DVec3::new(v.y, -v.x, 0.0);
463 let s = DMat3::from_cols(sx, sy, sz);
464 -s * m
465}
466
467#[derive(Debug, Clone, Copy, PartialEq, ApproxEq)]
468#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
469pub struct Rotation {
470 pub m: DMat3,
472 pub dm: DMat3,
474}
475
476impl Rotation {
477 pub const IDENTITY: Self = Self {
478 m: DMat3::IDENTITY,
479 dm: DMat3::ZERO,
480 };
481
482 pub fn new(m: DMat3) -> Self {
483 Self { m, dm: DMat3::ZERO }
484 }
485
486 pub fn with_derivative(mut self, dm: DMat3) -> Self {
487 self.dm = dm;
488 self
489 }
490
491 pub fn with_angular_velocity(mut self, v: DVec3) -> Self {
492 self.dm = rotation_matrix_derivative(self.m, v);
493 self
494 }
495
496 pub fn position_matrix(&self) -> DMat3 {
497 self.m
498 }
499
500 pub fn velocity_matrix(&self) -> DMat3 {
501 self.dm
502 }
503
504 pub fn compose(self, other: Self) -> Self {
505 Self {
506 m: other.m * self.m,
507 dm: other.dm * self.m + other.m * self.dm,
508 }
509 }
510
511 pub fn transpose(&self) -> Self {
512 let m = self.m.transpose();
513 let dm = self.dm.transpose();
514 Self { m, dm }
515 }
516
517 pub fn rotate_position(&self, pos: DVec3) -> DVec3 {
518 self.m * pos
519 }
520
521 pub fn rotate_velocity(&self, pos: DVec3, vel: DVec3) -> DVec3 {
522 self.dm * pos + self.m * vel
523 }
524
525 pub fn rotate_state(&self, pos: DVec3, vel: DVec3) -> (DVec3, DVec3) {
526 (self.rotate_position(pos), self.rotate_velocity(pos, vel))
527 }
528}
529
530impl Default for Rotation {
531 fn default() -> Self {
532 Self {
533 m: DMat3::IDENTITY,
534 dm: DMat3::ZERO,
535 }
536 }
537}
538
539impl Mul<DVec3> for Rotation {
540 type Output = DVec3;
541
542 fn mul(self, rhs: DVec3) -> Self::Output {
543 self.m * rhs
544 }
545}
546
547impl Mul<Cartesian> for Rotation {
548 type Output = Cartesian;
549
550 fn mul(self, rhs: Cartesian) -> Self::Output {
551 let pos = self.m * rhs.position();
552 let vel = self.dm * rhs.position() + self.m * rhs.velocity();
553 Cartesian::from_vecs(pos, vel)
554 }
555}
556
557impl From<DMat3> for Rotation {
558 fn from(matrix: DMat3) -> Self {
559 Rotation::new(matrix)
560 }
561}
562
563#[cfg(test)]
564mod tests {
565 use std::convert::Infallible;
566
567 use lox_test_utils::assert_approx_eq;
568 use lox_time::{deltas::TimeDelta, offsets::OffsetProvider};
569 use lox_units::AngleUnits;
570
571 use crate::iers::Iau2000Model;
572
573 use super::*;
574
575 #[derive(Debug)]
576 struct TestRotationProvider;
577
578 impl OffsetProvider for TestRotationProvider {
579 type Error = Infallible;
580
581 fn tai_to_ut1(&self, _delta: TimeDelta) -> Result<TimeDelta, Self::Error> {
582 Ok(TimeDelta::from_seconds_f64(-33.072073684954375))
583 }
584
585 fn ut1_to_tai(&self, _delta: TimeDelta) -> Result<TimeDelta, Self::Error> {
586 unreachable!()
587 }
588 }
589
590 impl<T> RotationProvider<T> for TestRotationProvider
591 where
592 T: TimeScale,
593 {
594 type EopError = Infallible;
595
596 fn corrections(
597 &self,
598 _time: Time<T>,
599 sys: ReferenceSystem,
600 ) -> Result<Corrections, Infallible> {
601 match sys {
602 ReferenceSystem::Iers1996 => {
603 Ok(Corrections(-55.0655e-3.arcsec(), -6.3580e-3.arcsec()))
604 }
605 ReferenceSystem::Iers2003(_) => {
606 Ok(Corrections(0.1725e-3.arcsec(), -0.2650e-3.arcsec()))
607 }
608 ReferenceSystem::Iers2010 => {
609 Ok(Corrections(0.1750e-3.arcsec(), -0.2259e-3.arcsec()))
610 }
611 }
612 }
613
614 fn pole_coords(&self, _time: Time<T>) -> Result<PoleCoords, Infallible> {
615 Ok(PoleCoords {
616 xp: 0.0349282.arcsec(),
617 yp: 0.4833163.arcsec(),
618 })
619 }
620 }
621
622 #[test]
623 fn test_celestial_to_terrestrial_iers1996() {
624 let tt = Time::from_two_part_julian_date(Tt, 2454195.5, 0.500754444444444);
625 let sys = ReferenceSystem::Iers1996;
626
627 let npb_exp = DMat3::from_cols_array(&[
628 0.999998403176203,
629 -0.001639032970562,
630 -0.000712190961847,
631 0.001639000942243,
632 0.999998655799521,
633 -0.000045552846624,
634 0.000712264667137,
635 0.000044385492226,
636 0.999999745354454,
637 ])
638 .transpose();
639 let c2t_exp = DMat3::from_cols_array(&[
640 0.973104317592265,
641 0.230363826166883,
642 -0.000703332813776,
643 -0.230363798723533,
644 0.973104570754697,
645 0.000120888299841,
646 0.000712264667137,
647 0.000044385492226,
648 0.999999745354454,
649 ])
650 .transpose();
651 let c2t_pm_exp = DMat3::from_cols_array(&[
652 0.973104317712772,
653 0.230363826174782,
654 -0.000703163477127,
655 -0.230363800391868,
656 0.973104570648022,
657 0.000118545116892,
658 0.000711560100206,
659 0.000046626645796,
660 0.999999745754058,
661 ])
662 .transpose();
663
664 let npb_act = TestRotationProvider
665 .j2000_to_mod(tt.with_scale(Tdb), sys)
666 .unwrap()
667 .compose(
668 TestRotationProvider
669 .mod_to_tod(tt.with_scale(Tdb), sys)
670 .unwrap(),
671 );
672 assert_approx_eq!(npb_act.m, npb_exp, atol <= 1e-12);
673
674 let c2t_act = npb_act.compose(TestRotationProvider.tod_to_pef(tt, sys).unwrap());
675 assert_approx_eq!(c2t_act.m, c2t_exp, atol <= 1e-12);
676
677 let c2t_pm_act = c2t_act.compose(TestRotationProvider.pef_to_itrf(tt, sys).unwrap());
678 assert_approx_eq!(c2t_pm_act.m, c2t_pm_exp, atol <= 1e-12);
679 }
680
681 #[test]
682 fn test_celestial_to_terrestrial_iers2003() {
683 let tt = Time::from_two_part_julian_date(Tt, 2454195.5, 0.500754444444444);
684 let sys = ReferenceSystem::Iers2003(Iau2000Model::A);
685
686 let npb_exp = DMat3::from_cols_array(&[
687 0.999998402755640,
688 -0.001639289519579,
689 -0.000712191013215,
690 0.001639257491365,
691 0.999998655379006,
692 -0.000045552787478,
693 0.000712264729795,
694 0.000044385250265,
695 0.999999745354420,
696 ])
697 .transpose();
698 let c2t_exp = DMat3::from_cols_array(&[
699 0.973104317573209,
700 0.230363826247361,
701 -0.000703332818999,
702 -0.230363798803834,
703 0.973104570735656,
704 0.000120888549787,
705 0.000712264729795,
706 0.000044385250265,
707 0.999999745354420,
708 ])
709 .transpose();
710 let c2t_pm_exp = DMat3::from_cols_array(&[
711 0.973104317697618,
712 0.230363826238780,
713 -0.000703163482352,
714 -0.230363800455689,
715 0.973104570632883,
716 0.000118545366826,
717 0.000711560162864,
718 0.000046626403835,
719 0.999999745754024,
720 ])
721 .transpose();
722
723 let npb_act = TestRotationProvider
724 .icrf_to_mod(tt, sys)
725 .unwrap()
726 .compose(TestRotationProvider.mod_to_tod(tt, sys).unwrap());
727 assert_approx_eq!(npb_act.m, npb_exp, atol <= 1e-12);
728
729 let c2t_act = npb_act.compose(TestRotationProvider.tod_to_pef(tt, sys).unwrap());
730 assert_approx_eq!(c2t_act.m, c2t_exp, atol <= 1e-12);
731
732 let c2t_pm_act = c2t_act.compose(TestRotationProvider.pef_to_itrf(tt, sys).unwrap());
733 assert_approx_eq!(c2t_pm_act.m, c2t_pm_exp, atol <= 1e-12);
734 }
735
736 #[test]
737 fn test_celestial_to_terrestrial_iau2006() {
738 let tt = Time::from_two_part_julian_date(Tt, 2454195.5, 0.500754444444444);
739
740 let npb_exp = DMat3::from_cols_array(&[
741 0.999999746339445,
742 -0.000000005138822,
743 -0.000712264730072,
744 -0.000000026475227,
745 0.999999999014975,
746 -0.000044385242827,
747 0.000712264729599,
748 0.000044385250426,
749 0.999999745354420,
750 ])
751 .transpose();
752 let c2t_exp = DMat3::from_cols_array(&[
753 0.973104317573127,
754 0.230363826247709,
755 -0.000703332818845,
756 -0.230363798804182,
757 0.973104570735574,
758 0.000120888549586,
759 0.000712264729599,
760 0.000044385250426,
761 0.999999745354420,
762 ])
763 .transpose();
764 let c2t_pm_exp = DMat3::from_cols_array(&[
765 0.973104317697535,
766 0.230363826239128,
767 -0.000703163482198,
768 -0.230363800456037,
769 0.973104570632801,
770 0.000118545366625,
771 0.000711560162668,
772 0.000046626403995,
773 0.999999745754024,
774 ])
775 .transpose();
776
777 let npb_act = TestRotationProvider.icrf_to_cirf(tt).unwrap();
778 assert_approx_eq!(npb_act.m, npb_exp, atol <= 1e-11);
779
780 let c2t_act = npb_act.compose(TestRotationProvider.cirf_to_tirf(tt).unwrap());
781 assert_approx_eq!(c2t_act.m, c2t_exp, atol <= 1e-11);
782
783 let c2t_pm_act = c2t_act.compose(TestRotationProvider.tirf_to_itrf(tt).unwrap());
784 assert_approx_eq!(c2t_pm_act.m, c2t_pm_exp, atol <= 1e-11);
785 }
786
787 #[test]
788 fn test_tod_to_teme() {
789 let tdb = Time::from_two_part_julian_date(Tdb, 2400000.5, 41234.0);
792 let eoe: f64 = 5.357_758_254_609_257e-5; let rotation = TestRotationProvider.tod_to_teme(tdb).unwrap();
795
796 let (sin_eoe, cos_eoe) = eoe.sin_cos();
801 let expected =
802 DMat3::from_cols_array(&[cos_eoe, sin_eoe, 0.0, -sin_eoe, cos_eoe, 0.0, 0.0, 0.0, 1.0])
803 .transpose();
804
805 assert_approx_eq!(rotation.m, expected, atol <= 1e-15);
806
807 let roundtrip = rotation.compose(TestRotationProvider.teme_to_tod(tdb).unwrap());
809 assert_approx_eq!(roundtrip.m, DMat3::IDENTITY, atol <= 1e-15);
810 }
811
812 #[test]
813 fn test_teme_icrf_roundtrip() {
814 let tt = Time::from_two_part_julian_date(Tt, 2454195.5, 0.500754444444444);
817 let sys = ReferenceSystem::Iers1996;
818
819 let icrf_to_mod = TestRotationProvider.icrf_to_mod(tt, sys).unwrap();
821 let mod_to_tod = TestRotationProvider.mod_to_tod(tt, sys).unwrap();
822 let tod_to_teme = TestRotationProvider.tod_to_teme(tt).unwrap();
823
824 let icrf_to_teme = icrf_to_mod.compose(mod_to_tod).compose(tod_to_teme);
825
826 let teme_to_tod = TestRotationProvider.teme_to_tod(tt).unwrap();
828 let tod_to_mod = TestRotationProvider.tod_to_mod(tt, sys).unwrap();
829 let mod_to_icrf = TestRotationProvider.mod_to_icrf(tt, sys).unwrap();
830
831 let teme_to_icrf = teme_to_tod.compose(tod_to_mod).compose(mod_to_icrf);
832
833 let roundtrip = icrf_to_teme.compose(teme_to_icrf);
835 assert_approx_eq!(roundtrip.m, DMat3::IDENTITY, atol <= 1e-14);
836 }
837
838 #[test]
839 fn test_icrf_j2000_roundtrip() {
840 let fwd =
841 <TestRotationProvider as RotationProvider<Tt>>::icrf_to_j2000(&TestRotationProvider);
842 let rev =
843 <TestRotationProvider as RotationProvider<Tt>>::j2000_to_icrf(&TestRotationProvider);
844 let roundtrip = fwd.compose(rev);
845 assert_approx_eq!(roundtrip.m, DMat3::IDENTITY, atol <= 1e-15);
846 }
847
848 #[test]
849 fn test_j2000_mod_equivalence_iers1996() {
850 let tt = Time::from_two_part_julian_date(Tt, 2454195.5, 0.500754444444444);
851 let sys = ReferenceSystem::Iers1996;
852
853 let via_j2000 =
855 <TestRotationProvider as RotationProvider<Tt>>::icrf_to_j2000(&TestRotationProvider)
856 .compose(TestRotationProvider.j2000_to_mod(tt, sys).unwrap());
857 let direct = TestRotationProvider.icrf_to_mod(tt, sys).unwrap();
858
859 assert_approx_eq!(via_j2000.m, direct.m, atol <= 1e-14);
860 }
861
862 #[test]
863 fn test_j2000_mod_equivalence_iers2003() {
864 let tt = Time::from_two_part_julian_date(Tt, 2454195.5, 0.500754444444444);
865 let sys = ReferenceSystem::Iers2003(Iau2000Model::A);
866
867 let via_j2000 =
868 <TestRotationProvider as RotationProvider<Tt>>::icrf_to_j2000(&TestRotationProvider)
869 .compose(TestRotationProvider.j2000_to_mod(tt, sys).unwrap());
870 let direct = TestRotationProvider.icrf_to_mod(tt, sys).unwrap();
871
872 assert_approx_eq!(via_j2000.m, direct.m, atol <= 1e-14);
873 }
874
875 #[test]
876 fn test_j2000_mod_equivalence_iers2010() {
877 let tt = Time::from_two_part_julian_date(Tt, 2454195.5, 0.500754444444444);
878 let sys = ReferenceSystem::Iers2010;
879
880 let via_j2000 =
881 <TestRotationProvider as RotationProvider<Tt>>::icrf_to_j2000(&TestRotationProvider)
882 .compose(TestRotationProvider.j2000_to_mod(tt, sys).unwrap());
883 let direct = TestRotationProvider.icrf_to_mod(tt, sys).unwrap();
884
885 assert_approx_eq!(via_j2000.m, direct.m, atol <= 1e-14);
886 }
887
888 #[test]
889 fn test_j2000_full_chain_equivalence() {
890 let tt = Time::from_two_part_julian_date(Tt, 2454195.5, 0.500754444444444);
892 let sys = ReferenceSystem::Iers2003(Iau2000Model::A);
893
894 let via_j2000 =
895 <TestRotationProvider as RotationProvider<Tt>>::icrf_to_j2000(&TestRotationProvider)
896 .compose(TestRotationProvider.j2000_to_mod(tt, sys).unwrap())
897 .compose(TestRotationProvider.mod_to_tod(tt, sys).unwrap())
898 .compose(TestRotationProvider.tod_to_pef(tt, sys).unwrap())
899 .compose(TestRotationProvider.pef_to_itrf(tt, sys).unwrap());
900
901 let direct = TestRotationProvider
902 .icrf_to_mod(tt, sys)
903 .unwrap()
904 .compose(TestRotationProvider.mod_to_tod(tt, sys).unwrap())
905 .compose(TestRotationProvider.tod_to_pef(tt, sys).unwrap())
906 .compose(TestRotationProvider.pef_to_itrf(tt, sys).unwrap());
907
908 assert_approx_eq!(via_j2000.m, direct.m, atol <= 1e-14);
909 }
910}