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