erfa_rust/
G20_safe.rs

1// G20
2//   ld.c      → eraLd_safe
3//   ldn.c     → eraLdn_safe
4//   ldsun.c   → eraLdsun_safe
5//   lteceq.c  → eraLteceq_safe
6//   ltecm.c   → eraLtecm_safe
7//   lteqec.c  → eraLteqec_safe
8//   ltp.c     → eraLtp_safe
9//   ltpb.c    → eraLtpb_safe
10//   ltpecl.c  → eraLtpecl_safe
11//   ltpequ.c  → eraLtpequ_safe
12
13use crate::G1_safe::{eraAnp_safe, eraAnpm_safe};
14use crate::G24_safe::eraPdp_safe;
15use crate::G25_safe::{eraPmp_safe, eraPn_safe};
16use crate::G26_safe::eraPpsp_safe;
17use crate::G27_safe::eraPxp_safe;
18use crate::G28_safe::eraRxp_safe;
19use crate::G29_safe::eraS2c_safe;
20use crate::G33_safe::eraTrxp_safe;
21use crate::G7_safe::eraC2s_safe;
22use crate::H1_safe::{
23    eraLDBODY, ERFA_AULT, ERFA_D2PI, ERFA_DAS2R, ERFA_DAYSEC, ERFA_GMAX, ERFA_GMIN, ERFA_SRS,
24};
25
26pub type ErfaResult<T> = Result<T, ()>;
27
28//----------------------------------------------------------------------
29// G20/ld.c → eraLd_safe
30//----------------------------------------------------------------------
31// Single-body light deflection; returns deflected unit vector.
32pub fn eraLd_safe(
33    bm: f64,
34    p: &[f64; 3],
35    q: &[f64; 3],
36    e: &[f64; 3],
37    em: f64,
38    dlim: f64,
39) -> ErfaResult<[f64; 3]> {
40    let qpe = [q[0] + e[0], q[1] + e[1], q[2] + e[2]];
41    let qdqpe = eraPdp_safe(q, &qpe)?;
42    let w = bm * ERFA_SRS / em / ERFA_GMAX(qdqpe, dlim);
43
44    let eq = eraPxp_safe(e, q)?;
45    let peq = eraPxp_safe(p, &eq)?;
46
47    Ok([p[0] + w * peq[0], p[1] + w * peq[1], p[2] + w * peq[2]])
48}
49
50//----------------------------------------------------------------------
51// G20/ldn.c → eraLdn_safe
52//----------------------------------------------------------------------
53// N-body light deflection; returns deflected unit vector.
54pub fn eraLdn_safe(bodies: &[eraLDBODY], ob: &[f64; 3], sc: &[f64; 3]) -> ErfaResult<[f64; 3]> {
55    // Light time for 1 au (days).
56    const CR: f64 = ERFA_AULT / ERFA_DAYSEC;
57
58    let mut sn = *sc;
59
60    for body in bodies {
61        // Body→observer at observation epoch (au).
62        let v = eraPmp_safe(ob, &body.pv[0])?;
63
64        // Minus light-time since ray passed body (days).
65        let dt = ERFA_GMIN(eraPdp_safe(&sn, &v)? * CR, 0.0);
66
67        // Backtrack body to retarded epoch.
68        let ev = eraPpsp_safe(&v, -dt, &body.pv[1])?;
69
70        // Unit vector and distance body→observer.
71        let (em, e) = eraPn_safe(&ev)?;
72
73        // Apply deflection for this body (use sn as both p and q).
74        sn = eraLd_safe(body.bm, &sn, &sn, &e, em, body.dl)?;
75    }
76
77    Ok(sn)
78}
79
80//----------------------------------------------------------------------
81// G20/ldsun.c → eraLdsun_safe
82//----------------------------------------------------------------------
83// Solar light deflection; convenience wrapper around eraLd_safe.
84pub fn eraLdsun_safe(p: &[f64; 3], e: &[f64; 3], em: f64) -> ErfaResult<[f64; 3]> {
85    let em2 = em * em;
86    let dlim = 1.0e-6 / if em2 < 1.0 { 1.0 } else { em2 };
87    eraLd_safe(1.0, p, p, e, em, dlim)
88}
89
90//----------------------------------------------------------------------
91// G20/lteceq.c → eraLteceq_safe
92//----------------------------------------------------------------------
93// Long-term model: ecliptic (dl, db) at epoch epj → ICRS (dr, dd).
94pub fn eraLteceq_safe(epj: f64, dl: f64, db: f64) -> ErfaResult<(f64, f64)> {
95    let v1 = eraS2c_safe(dl, db)?;
96    let rm = eraLtecm_safe(epj)?;
97    let v2 = eraTrxp_safe(&rm, &v1)?;
98    let (a, b) = eraC2s_safe(&v2)?;
99    let dr = eraAnp_safe(a)?;
100    let dd = eraAnpm_safe(b)?;
101    Ok((dr, dd))
102}
103
104//----------------------------------------------------------------------
105// G20/ltecm.c → eraLtecm_safe
106//----------------------------------------------------------------------
107// Build rotation matrix ICRS←ecliptic at epoch epj (long-term).
108pub fn eraLtecm_safe(epj: f64) -> ErfaResult<[[f64; 3]; 3]> {
109    // Frame-bias constants (rad).
110    const DX: f64 = -0.016_617 * ERFA_DAS2R;
111    const DE: f64 = -0.006_819_2 * ERFA_DAS2R;
112    const DR: f64 = -0.014_6 * ERFA_DAS2R;
113
114    let p = eraLtpequ_safe(epj)?;
115    let z = eraLtpecl_safe(epj)?;
116
117    // Equinox vector x = unit(p × z).
118    let w = eraPxp_safe(&p, &z)?;
119    let (_s, x) = eraPn_safe(&w)?;
120
121    // y = z × x.
122    let y = eraPxp_safe(&z, &x)?;
123
124    // Matrix rows with small frame-bias terms.
125    let mut m = [[0.0_f64; 3]; 3];
126
127    m[0][0] = x[0] - x[1] * DR + x[2] * DX;
128    m[0][1] = x[0] * DR + x[1] + x[2] * DE;
129    m[0][2] = -x[0] * DX - x[1] * DE + x[2];
130
131    m[1][0] = y[0] - y[1] * DR + y[2] * DX;
132    m[1][1] = y[0] * DR + y[1] + y[2] * DE;
133    m[1][2] = -y[0] * DX - y[1] * DE + y[2];
134
135    m[2][0] = z[0] - z[1] * DR + z[2] * DX;
136    m[2][1] = z[0] * DR + z[1] + z[2] * DE;
137    m[2][2] = -z[0] * DX - z[1] * DE + z[2];
138
139    Ok(m)
140}
141
142//----------------------------------------------------------------------
143// G20/lteqec.c → eraLteqec_safe
144//----------------------------------------------------------------------
145// Long-term model: ICRS (dr, dd) → ecliptic (dl, db) at epoch epj.
146pub fn eraLteqec_safe(epj: f64, dr: f64, dd: f64) -> ErfaResult<(f64, f64)> {
147    let v1 = eraS2c_safe(dr, dd)?;
148    let rm = eraLtecm_safe(epj)?;
149    let v2 = eraRxp_safe(&rm, &v1)?;
150    let (a, b) = eraC2s_safe(&v2)?;
151    let dl = eraAnp_safe(a)?;
152    let db = eraAnpm_safe(b)?;
153    Ok((dl, db))
154}
155
156//----------------------------------------------------------------------
157// G20/ltp.c → eraLtp_safe
158//----------------------------------------------------------------------
159// Long-term precession matrix at epoch epj.
160pub fn eraLtp_safe(epj: f64) -> ErfaResult<[[f64; 3]; 3]> {
161    let peqr = eraLtpequ_safe(epj)?;
162    let pecl = eraLtpecl_safe(epj)?;
163
164    let v = eraPxp_safe(&peqr, &pecl)?;
165    let (_w, eqx) = eraPn_safe(&v)?;
166
167    let v2 = eraPxp_safe(&peqr, &eqx)?;
168
169    let mut m = [[0.0_f64; 3]; 3];
170    for i in 0..3 {
171        m[0][i] = eqx[i];
172        m[1][i] = v2[i];
173        m[2][i] = peqr[i];
174    }
175    Ok(m)
176}
177
178//----------------------------------------------------------------------
179// G20/ltpb.c → eraLtpb_safe
180//----------------------------------------------------------------------
181// Long-term precession+bias matrix at epoch epj.
182pub fn eraLtpb_safe(epj: f64) -> ErfaResult<[[f64; 3]; 3]> {
183    const DX: f64 = -0.016_617 * ERFA_DAS2R;
184    const DE: f64 = -0.006_819_2 * ERFA_DAS2R;
185    const DR: f64 = -0.014_6 * ERFA_DAS2R;
186
187    let rp = eraLtp_safe(epj)?;
188
189    let mut rpb = [[0.0_f64; 3]; 3];
190    for i in 0..3 {
191        rpb[i][0] = rp[i][0] - rp[i][1] * DR + rp[i][2] * DX;
192        rpb[i][1] = rp[i][0] * DR + rp[i][1] + rp[i][2] * DE;
193        rpb[i][2] = -rp[i][0] * DX - rp[i][1] * DE + rp[i][2];
194    }
195    Ok(rpb)
196}
197
198//----------------------------------------------------------------------
199// G20/ltpecl.c → eraLtpecl_safe
200//----------------------------------------------------------------------
201// Long-term ecliptic pole unit vector at epoch epj.
202pub fn eraLtpecl_safe(epj: f64) -> ErfaResult<[f64; 3]> {
203    // Obliquity at J2000.
204    const EPS0: f64 = 84_381.406 * ERFA_DAS2R;
205
206    // Polynomial coefficients.
207    const PQPOL: [[f64; 4]; 2] = [
208        [5_851.607_687, -0.118_900_0, -0.000_289_13, 0.000_000_101],
209        [-1_600.886_300, 1.168_981_8, -0.000_000_20, -0.000_000_437],
210    ];
211
212    // Periodic coefficients.
213    const PQPER: [[f64; 5]; 8] = [
214        [
215            708.15,
216            -5_486.751_211,
217            -684.661_560,
218            667.666_730,
219            -5_523.863_691,
220        ],
221        [
222            2_309.00,
223            -17.127_623,
224            2_446.283_880,
225            -2_354.886_252,
226            -549.747_450,
227        ],
228        [
229            1_620.00,
230            -617.517_403,
231            399.671_049,
232            -428.152_441,
233            -310.998_056,
234        ],
235        [492.20, 413.442_940, -356.652_376, 376.202_861, 421.535_876],
236        [1_183.00, 78.614_193, -186.387_003, 184.778_874, -36.776_172],
237        [
238            622.00,
239            -180.732_815,
240            -316.800_070,
241            335.321_713,
242            -145.278_396,
243        ],
244        [882.00, -87.676_083, 198.296_701, -185.138_669, -34.744_450],
245        [547.00, 46.140_315, 101.135_679, -120.972_830, 22.885_731],
246    ];
247
248    // Centuries since J2000.
249    let t = (epj - 2000.0) / 100.0;
250
251    // Periodic part.
252    let mut p = 0.0_f64;
253    let mut q = 0.0_f64;
254    let w = ERFA_D2PI * t;
255    for coeff in PQPER {
256        let a = w / coeff[0];
257        let (s, c) = a.sin_cos();
258        p += c * coeff[1] + s * coeff[3];
259        q += c * coeff[2] + s * coeff[4];
260    }
261
262    // Polynomial part.
263    let mut tt = 1.0_f64;
264    for i in 0..4 {
265        p += PQPOL[0][i] * tt;
266        q += PQPOL[1][i] * tt;
267        tt *= t;
268    }
269
270    // To radians.
271    let p = p * ERFA_DAS2R;
272    let q = q * ERFA_DAS2R;
273
274    // Form pole vector.
275    let mut wv = 1.0 - p * p - q * q;
276    wv = if wv < 0.0 { 0.0 } else { wv.sqrt() };
277    let (s0, c0) = EPS0.sin_cos();
278
279    Ok([p, -q * c0 - wv * s0, -q * s0 + wv * c0])
280}
281
282//----------------------------------------------------------------------
283// G20/ltpequ.c → eraLtpequ_safe
284//----------------------------------------------------------------------
285// Long-term equator pole unit vector at epoch epj.
286pub fn eraLtpequ_safe(epj: f64) -> ErfaResult<[f64; 3]> {
287    // Polynomial coefficients.
288    const XYPOL: [[f64; 4]; 2] = [
289        [5_453.282_155, 0.425_284_1, -0.000_371_73, -0.000_000_152],
290        [-73_750.930_350, -0.767_545_2, -0.000_187_25, 0.000_000_231],
291    ];
292
293    // Periodic coefficients.
294    const XYPER: [[f64; 5]; 14] = [
295        [
296            256.75,
297            -819.940_624,
298            75_004.344_875,
299            81_491.287_984,
300            1_558.515_853,
301        ],
302        [
303            708.15,
304            -8_444.676_815,
305            624.033_993,
306            787.163_481,
307            7_774.939_698,
308        ],
309        [
310            274.20,
311            2_600.009_459,
312            1_251.136_893,
313            1_251.296_102,
314            -2_219.534_038,
315        ],
316        [
317            241.45,
318            2_755.175_630,
319            -1_102.212_834,
320            -1_257.950_837,
321            -2_523.969_396,
322        ],
323        [
324            2_309.00,
325            -167.659_835,
326            -2_660.664_980,
327            -2_966.799_730,
328            247.850_422,
329        ],
330        [492.20, 871.855_056, 699.291_817, 639.744_522, -846.485_643],
331        [396.10, 44.769_698, 153.167_220, 131.600_209, -1_393.124_055],
332        [
333            288.90,
334            -512.313_065,
335            -950.865_637,
336            -445.040_117,
337            368.526_116,
338        ],
339        [231.10, -819.415_595, 499.754_645, 584.522_874, 749.045_012],
340        [
341            1_610.00,
342            -538.071_099,
343            -145.188_210,
344            -89.756_563,
345            444.704_518,
346        ],
347        [620.00, -189.793_622, 558.116_553, 524.429_630, 235.934_465],
348        [157.87, -402.922_932, -23.923_029, -13.549_067, 374.049_623],
349        [
350            220.30,
351            179.516_345,
352            -165.405_086,
353            -210.157_124,
354            -171.330_180,
355        ],
356        [1_200.00, -9.814_756, 9.344_131, -44.919_798, -22.899_655],
357    ];
358
359    let t = (epj - 2000.0) / 100.0;
360
361    // Periodic contribution.
362    let mut x = 0.0_f64;
363    let mut y = 0.0_f64;
364    let w = ERFA_D2PI * t;
365    for coeff in XYPER {
366        let a = w / coeff[0];
367        let (s, c) = a.sin_cos();
368        x += c * coeff[1] + s * coeff[3];
369        y += c * coeff[2] + s * coeff[4];
370    }
371
372    // Polynomial contribution.
373    let mut tt = 1.0_f64;
374    for i in 0..4 {
375        x += XYPOL[0][i] * tt;
376        y += XYPOL[1][i] * tt;
377        tt *= t;
378    }
379
380    // To radians and form vector.
381    let x = x * ERFA_DAS2R;
382    let y = y * ERFA_DAS2R;
383    let wv = 1.0 - x * x - y * y;
384    let z = if wv < 0.0 { 0.0 } else { wv.sqrt() };
385    Ok([x, y, z])
386}