@neilberkman/sidereon 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,203 @@
1
+ //! Solid-Earth pole tide station displacement (IERS Conventions 2010, §7.1.4).
2
+ //!
3
+ //! [`solid_earth_pole_tide`] computes the displacement of an Earth-fixed (ITRF)
4
+ //! station caused by the centrifugal-potential change of polar motion (the
5
+ //! "rotational deformation due to polar motion"). It is the sibling of
6
+ //! [`super::solid_earth_tide`] and is wired into the PPP correction stack in the
7
+ //! identical way: a per-epoch station displacement vector projected onto the
8
+ //! line of sight.
9
+ //!
10
+ //! Physics (IERS Conventions 2010, Chapter 7, §7.1.4):
11
+ //!
12
+ //! * The wobble variables are formed from the polar motion `(xp, yp)` and the
13
+ //! IERS conventional mean pole `(x_bar, y_bar)`:
14
+ //!
15
+ //! ```text
16
+ //! m1 = xp - x_bar (arcsec)
17
+ //! m2 = -(yp - y_bar) (arcsec)
18
+ //! ```
19
+ //!
20
+ //! * The displacement, in the local radial / colatitude / longitude triad, is
21
+ //! IERS Conventions (2010) Eq. (7.24):
22
+ //!
23
+ //! ```text
24
+ //! S_r = -33 sin(2 theta) (m1 cos lambda + m2 sin lambda) mm
25
+ //! S_theta = -9 cos(2 theta) (m1 cos lambda + m2 sin lambda) mm
26
+ //! S_lambda = 9 cos(theta) (m1 sin lambda - m2 cos lambda) mm
27
+ //! ```
28
+ //!
29
+ //! where `theta` is the geocentric colatitude and `lambda` the longitude. The
30
+ //! integer mm coefficients embed the nominal degree-2 Love/Shida numbers
31
+ //! h2 = 0.6078 and l2 = 0.0847 (radial scales with h2; the horizontal terms
32
+ //! with l2). `S_theta` points to increasing colatitude (south), so the local
33
+ //! north component is `-S_theta`; `S_lambda` is east; `S_r` is up.
34
+ //!
35
+ //! Mean pole model. The IERS 2010 conventional mean pole (a cubic until 2010.0,
36
+ //! linear thereafter; IERS Conventions 2010 Table 7.7) was revised in 2018 to a
37
+ //! single **linear secular pole**, because the original post-2010 linear
38
+ //! extrapolation diverges from the observed secular drift at the multi-cm level
39
+ //! within a decade. This module uses the **revised IERS (2018) linear secular
40
+ //! pole** (updated Chapter 7), which is the conventional model for the modern
41
+ //! (post-2010) epochs Sidereon targets and the one adopted for ITRF2014/2020:
42
+ //!
43
+ //! ```text
44
+ //! x_bar(t) = 55.0 + 1.677 (t - 2000.0) mas
45
+ //! y_bar(t) = 320.5 + 3.460 (t - 2000.0) mas
46
+ //! ```
47
+ //!
48
+ //! with `t` the epoch in years. (The widely used RTKLIB still applies the
49
+ //! superseded 2010 cubic+linear model; at a 2026 epoch the two mean poles differ
50
+ //! by >0.1 arcsec in x, which is why the choice is documented here.)
51
+ //!
52
+ //! Polar motion `(xp, yp)` is **not** available from the engine's embedded EOP
53
+ //! table (it carries UT1-UTC only; see
54
+ //! `crate::astro::frames::transforms::PolarMotion`). The caller therefore
55
+ //! supplies `xp`/`yp` in arcseconds, sourced from IERS EOP exactly like the
56
+ //! other Earth-orientation inputs. They must not be fabricated.
57
+
58
+ #[cfg(test)]
59
+ mod tests;
60
+
61
+ use crate::astro::constants::time::{DAYS_PER_JULIAN_YEAR, J2000_JD};
62
+ use crate::astro::constants::units::MM_PER_M;
63
+ use crate::astro::math::vec3::norm3_ref as norm;
64
+ use crate::validate;
65
+
66
+ use super::{gregorian_to_two_part_julian_date, invalid_tide_input, TideError};
67
+
68
+ /// IERS (2018) linear secular pole coefficients, in milliarcseconds and
69
+ /// milliarcseconds per year (revised IERS Conventions 2010, updated Chapter 7).
70
+ const MEAN_POLE_X0_MAS: f64 = 55.0;
71
+ const MEAN_POLE_X_RATE_MAS_PER_YR: f64 = 1.677;
72
+ const MEAN_POLE_Y0_MAS: f64 = 320.5;
73
+ const MEAN_POLE_Y_RATE_MAS_PER_YR: f64 = 3.460;
74
+
75
+ /// Eq. (7.24) displacement coefficients in millimetres per arcsecond of wobble.
76
+ /// The radial coefficient carries Love number h2 = 0.6078; the horizontal
77
+ /// coefficients carry Shida number l2 = 0.0847.
78
+ const RADIAL_MM_PER_ARCSEC: f64 = -33.0;
79
+ const COLATITUDE_MM_PER_ARCSEC: f64 = -9.0;
80
+ const LONGITUDE_MM_PER_ARCSEC: f64 = 9.0;
81
+
82
+ /// Solid-Earth pole tide displacement of an ITRF station, in metres (ECEF).
83
+ ///
84
+ /// Arguments:
85
+ /// * `xsta` - geocentric station position (m, ITRF).
86
+ /// * `year`, `month`, `day` - UTC calendar date (selects the mean pole epoch).
87
+ /// * `fhr` - UTC fractional hour of the day (`hour + min/60 + sec/3600`).
88
+ /// * `xp_arcsec`, `yp_arcsec` - IERS polar motion of the date (arcsec). These
89
+ /// are a data dependency the caller supplies from IERS EOP; the engine does
90
+ /// not embed them.
91
+ ///
92
+ /// Returns the displacement vector (m, geocentric ITRF), to be projected onto
93
+ /// the line of sight identically to [`super::solid_earth_tide`].
94
+ ///
95
+ /// Returns [`TideError`] when inputs are non-finite, the date/hour is invalid,
96
+ /// or the station vector is degenerate (zero radius or on the polar axis).
97
+ pub fn solid_earth_pole_tide(
98
+ xsta: &[f64; 3],
99
+ year: i32,
100
+ month: i32,
101
+ day: i32,
102
+ fhr: f64,
103
+ xp_arcsec: f64,
104
+ yp_arcsec: f64,
105
+ ) -> Result<[f64; 3], TideError> {
106
+ validate_pole_tide_domain(xsta, year, month, day, fhr, xp_arcsec, yp_arcsec)?;
107
+ Ok(solid_earth_pole_tide_unchecked(
108
+ xsta, year, month, day, fhr, xp_arcsec, yp_arcsec,
109
+ ))
110
+ }
111
+
112
+ fn validate_pole_tide_domain(
113
+ xsta: &[f64; 3],
114
+ year: i32,
115
+ month: i32,
116
+ day: i32,
117
+ fhr: f64,
118
+ xp_arcsec: f64,
119
+ yp_arcsec: f64,
120
+ ) -> Result<(), TideError> {
121
+ validate::finite_vec3(*xsta, "station position").map_err(invalid_tide_input)?;
122
+ validate::civil_datetime_with_second_policy(
123
+ i64::from(year),
124
+ i64::from(month),
125
+ i64::from(day),
126
+ 0,
127
+ 0,
128
+ 0.0,
129
+ validate::CivilSecondPolicy::Continuous,
130
+ )
131
+ .map_err(invalid_tide_input)?;
132
+ validate::finite_in_range_exclusive_upper(fhr, 0.0, 24.0, "fractional hour")
133
+ .map_err(invalid_tide_input)?;
134
+ validate::finite(xp_arcsec, "polar motion xp").map_err(invalid_tide_input)?;
135
+ validate::finite(yp_arcsec, "polar motion yp").map_err(invalid_tide_input)?;
136
+
137
+ validate::finite_positive(norm(xsta), "station radius").map_err(invalid_tide_input)?;
138
+ let station_horizontal_radius = (xsta[0] * xsta[0] + xsta[1] * xsta[1]).sqrt();
139
+ validate::finite_positive(station_horizontal_radius, "station horizontal radius")
140
+ .map_err(invalid_tide_input)?;
141
+
142
+ Ok(())
143
+ }
144
+
145
+ fn solid_earth_pole_tide_unchecked(
146
+ xsta: &[f64; 3],
147
+ year: i32,
148
+ month: i32,
149
+ day: i32,
150
+ fhr: f64,
151
+ xp_arcsec: f64,
152
+ yp_arcsec: f64,
153
+ ) -> [f64; 3] {
154
+ let (x_bar_arcsec, y_bar_arcsec) = mean_pole_arcsec(year, month, day, fhr);
155
+
156
+ // Wobble variables (arcsec), IERS Conventions (2010) §7.1.4.
157
+ let m1 = xp_arcsec - x_bar_arcsec;
158
+ let m2 = -(yp_arcsec - y_bar_arcsec);
159
+
160
+ // Geocentric station triad: theta is colatitude, lambda longitude.
161
+ let rsta = norm(xsta);
162
+ let sinphi = xsta[2] / rsta; // cos(theta)
163
+ let cosphi = (xsta[0] * xsta[0] + xsta[1] * xsta[1]).sqrt() / rsta; // sin(theta)
164
+ let sinla = xsta[1] / cosphi / rsta;
165
+ let cosla = xsta[0] / cosphi / rsta;
166
+
167
+ let sin2theta = 2.0 * cosphi * sinphi;
168
+ let cos2theta = sinphi * sinphi - cosphi * cosphi;
169
+ let costheta = sinphi;
170
+
171
+ let radial_factor = m1 * cosla + m2 * sinla;
172
+ let longitude_factor = m1 * sinla - m2 * cosla;
173
+
174
+ // Eq. (7.24), in millimetres.
175
+ let s_r = RADIAL_MM_PER_ARCSEC * sin2theta * radial_factor;
176
+ let s_theta = COLATITUDE_MM_PER_ARCSEC * cos2theta * radial_factor;
177
+ let s_lambda = LONGITUDE_MM_PER_ARCSEC * costheta * longitude_factor;
178
+
179
+ // Local radial/north/east displacement in metres (north = -S_theta).
180
+ let up = s_r / MM_PER_M;
181
+ let north = -s_theta / MM_PER_M;
182
+ let east = s_lambda / MM_PER_M;
183
+
184
+ // Geocentric ENU -> ECEF, matching the solid-earth tide convention.
185
+ [
186
+ up * cosla * cosphi - east * sinla - north * sinphi * cosla,
187
+ up * sinla * cosphi + east * cosla - north * sinphi * sinla,
188
+ up * sinphi + north * cosphi,
189
+ ]
190
+ }
191
+
192
+ /// IERS (2018) linear secular mean pole at the given UTC epoch, in arcseconds.
193
+ fn mean_pole_arcsec(year: i32, month: i32, day: i32, fhr: f64) -> (f64, f64) {
194
+ // Epoch in years since J2000.0 (RTKLIB-compatible Julian-year measure).
195
+ let (jd0, jd1) = gregorian_to_two_part_julian_date(year, month, day);
196
+ let years = ((jd0 - J2000_JD) + jd1 + fhr / 24.0) / DAYS_PER_JULIAN_YEAR;
197
+
198
+ let x_bar_mas = MEAN_POLE_X0_MAS + MEAN_POLE_X_RATE_MAS_PER_YR * years;
199
+ let y_bar_mas = MEAN_POLE_Y0_MAS + MEAN_POLE_Y_RATE_MAS_PER_YR * years;
200
+
201
+ // Milliarcseconds -> arcseconds.
202
+ (x_bar_mas / 1000.0, y_bar_mas / 1000.0)
203
+ }
@@ -0,0 +1,2 @@
1
+ // Backward-compatible CommonJS runtime target for the legacy type-only subpath.
2
+ module.exports = {};
@@ -0,0 +1,2 @@
1
+ // Backward-compatible runtime target for the legacy type-only subpath.
2
+ export {};