erfa_rust/
G16_safe.rs

1// G16
2//   fk425.c  → eraFk425_safe
3//   fk45z.c  → eraFk45z_safe
4//   fk524.c  → eraFk524_safe
5//   fk52h.c  → eraFk52h_safe
6//   fk54z.c  → eraFk54z_safe
7//   fk5hz.c  → eraFk5hz_safe
8//   fw2m.c   → eraFw2m_safe
9//   fw2xy.c  → eraFw2xy_safe
10
11use crate::H1_safe::{ERFA_DJ00, ERFA_DJY, ERFA_DR2AS};
12
13use crate::G12_safe::{eraEpb2jd_safe, eraEpj_safe};
14use crate::G15_safe::eraFk5hip_safe;
15use crate::G1_safe::eraAnp_safe;
16use crate::G6_safe::eraBpn2xy_safe;
17use crate::G7_safe::eraC2s_safe;
18
19use crate::G19_safe::eraIr_safe;
20
21use crate::G24_safe::{eraPdp_safe, eraPm_safe};
22use crate::G25_safe::eraPmp_safe;
23use crate::G26_safe::eraPpp_safe;
24use crate::G27_safe::{
25    eraPv2s_safe, eraPvmpv_safe, eraPvppv_safe, eraPvstar_safe, eraPvu_safe, eraPxp_safe,
26};
27use crate::G28_safe::{eraRv2m_safe, eraRx_safe, eraRxp_safe, eraRz_safe};
28use crate::G29_safe::{eraS2c_safe, eraS2pv_safe};
29use crate::G30_safe::eraStarpv_safe;
30use crate::G33_safe::eraTrxp_safe;
31
32pub type ErfaResult<T> = Result<T, ()>;
33
34// G16/fk425.c → eraFk425_safe
35
36// FK4 B1950 → FK5 J2000 full conversion.
37pub fn eraFk425_safe(
38    r1950: f64,
39    d1950: f64,
40    dr1950: f64,
41    dd1950: f64,
42    p1950: f64,
43    v1950: f64,
44) -> ErfaResult<(f64, f64, f64, f64, f64, f64)> {
45    const PMF: f64 = 100.0 * ERFA_DR2AS; // rad/yr → "/cy
46    const TINY: f64 = 1.0e-30;
47    const VF: f64 = 21.095; // km/s → au/trop. century
48
49    // E-terms of aberration: A (position), Adot (velocity)
50    const A: [[f64; 3]; 2] = [
51        [-1.62557e-6, -0.31919e-6, -0.13843e-6],
52        [1.245e-3, -1.580e-3, -0.659e-3],
53    ];
54
55    // 3×2 transform EM (Seidelmann 3.591-4)
56    const EM: [[[[f64; 3]; 2]; 3]; 2] = [
57        [
58            [
59                [0.999_925_678_2, -0.011_182_061_1, -0.004_857_947_7],
60                [
61                    0.000_002_423_950_18,
62                    -0.000_000_027_106_63,
63                    -0.000_000_011_776_56,
64                ],
65            ],
66            [
67                [0.011_182_061_0, 0.999_937_478_4, -0.000_027_176_5],
68                [
69                    0.000_000_027_106_63,
70                    0.000_002_423_978_78,
71                    -0.000_000_000_065_87,
72                ],
73            ],
74            [
75                [0.004_857_947_9, -0.000_027_147_4, 0.999_988_199_7],
76                [
77                    0.000_000_011_776_56,
78                    -0.000_000_000_065_82,
79                    0.000_002_424_101_73,
80                ],
81            ],
82        ],
83        [
84            [
85                [-0.000_551, -0.238_565, 0.435_739],
86                [0.999_947_04, -0.011_182_51, -0.004_857_67],
87            ],
88            [
89                [0.238_514, -0.002_667, -0.008_541],
90                [0.011_182_51, 0.999_958_83, -0.000_027_18],
91            ],
92            [
93                [-0.435_623, 0.012_254, 0.002_117],
94                [0.004_857_67, -0.000_027_14, 1.000_009_56],
95            ],
96        ],
97    ];
98
99    // Catalog FK4 → pv
100    let ur = dr1950 * PMF;
101    let ud = dd1950 * PMF;
102    let mut px = p1950;
103    let mut rv = v1950;
104    let pxvf = px * VF;
105    let w_rv = rv * pxvf;
106
107    let r0 = eraS2pv_safe(r1950, d1950, 1.0, ur, ud, w_rv)?;
108
109    // Subtract E-terms
110    let mut pv1 = eraPvmpv_safe(&r0, &A)?;
111
112    // Add small corrections proportional to dot(r0.pos, A.pos/Adot)
113    let s_pos = eraPdp_safe(&r0[0], &A[0])?;
114    let v_pos = eraPdp_safe(&r0[0], &A[1])?;
115    let cpos = eraSxp_safe(s_pos, &r0[0])?;
116    let cvel = eraSxp_safe(v_pos, &r0[0])?;
117    let pv2 = [cpos, cvel];
118    pv1 = eraPvppv_safe(&pv1, &pv2)?;
119
120    // FK4 → FK5 frame
121    let mut pv2_out = [[0.0_f64; 3]; 2];
122    for i in 0..2 {
123        for j in 0..3 {
124            let mut sum = 0.0_f64;
125            for k in 0..2 {
126                for l in 0..3 {
127                    sum += EM[i][j][k][l] * pv1[k][l];
128                }
129            }
130            pv2_out[i][j] = sum;
131        }
132    }
133
134    // pv → catalog FK5
135    let (r, d, w_scale, ur_out, ud_out, rd) = eraPv2s_safe(&pv2_out)?;
136    if px > TINY {
137        rv = rd / pxvf;
138        px = px / w_scale;
139    }
140
141    let r2000 = eraAnp_safe(r)?;
142    let d2000 = d;
143    let dr2000 = ur_out / PMF;
144    let dd2000 = ud_out / PMF;
145    let p2000 = px;
146    let v2000 = rv;
147
148    Ok((r2000, d2000, dr2000, dd2000, p2000, v2000))
149}
150
151// G16/fk45z.c → eraFk45z_safe
152
153// FK4 position at B1950 and epoch → FK5 J2000 position.
154pub fn eraFk45z_safe(r1950: f64, d1950: f64, bepoch: f64) -> ErfaResult<(f64, f64)> {
155    const PMF: f64 = 100.0 * ERFA_DR2AS;
156
157    const A: [f64; 3] = [-1.62557e-6, -0.31919e-6, -0.13843e-6];
158    const AD: [f64; 3] = [1.245e-3, -1.580e-3, -0.659e-3];
159
160    const EM: [[[f64; 3]; 3]; 2] = [
161        [
162            [0.999_925_678_2, -0.011_182_061_1, -0.004_857_947_7],
163            [0.011_182_061_0, 0.999_937_478_4, -0.000_027_176_5],
164            [0.004_857_947_9, -0.000_027_147_4, 0.999_988_199_7],
165        ],
166        [
167            [-0.000_551, -0.238_565, 0.435_739],
168            [0.238_514, -0.002_667, -0.008_541],
169            [-0.435_623, 0.012_254, 0.002_117],
170        ],
171    ];
172
173    // Unit vector for FK4 coordinates
174    let r0 = eraS2c_safe(r1950, d1950)?;
175
176    // p = A + w_epoch * AD
177    let w_epoch = (bepoch - 1950.0) / PMF;
178    let mut p = eraPpsp_safe(&A, w_epoch, &AD)?;
179
180    // p = p - (r0p) r0
181    let dot = eraPdp_safe(&r0, &p)?;
182    p = eraPpsp_safe(&p, -dot, &r0)?;
183
184    // p = r0 - p
185    p = eraPmp_safe(&r0, &p)?;
186
187    // Transform with EM into position/velocity-like pair
188    let mut pv = [[0.0_f64; 3]; 2];
189    for i in 0..2 {
190        for j in 0..3 {
191            let mut sum = 0.0_f64;
192            for k in 0..3 {
193                sum += EM[i][j][k] * p[k];
194            }
195            pv[i][j] = sum;
196        }
197    }
198
199    // Advance from B epoch to J2000 using uniform motion
200    let (djm0, djm) = eraEpb2jd_safe(bepoch)?;
201    let w_years = (eraEpj_safe(djm0, djm)? - 2000.0) / PMF;
202    let pv = eraPvu_safe(w_years, &pv)?;
203
204    // Cartesian → spherical
205    let (ra, d2000) = eraC2s_safe(&pv[0])?;
206    let r2000 = eraAnp_safe(ra)?;
207    Ok((r2000, d2000))
208}
209
210// G16/fk524.c → eraFk524_safe
211
212// FK5 J2000 → FK4 B1950 full conversion.
213pub fn eraFk524_safe(
214    r2000: f64,
215    d2000: f64,
216    dr2000: f64,
217    dd2000: f64,
218    p2000: f64,
219    v2000: f64,
220) -> ErfaResult<(f64, f64, f64, f64, f64, f64)> {
221    const PMF: f64 = 100.0 * ERFA_DR2AS;
222    const TINY: f64 = 1.0e-30;
223    const VF: f64 = 21.095;
224
225    const A: [[f64; 3]; 2] = [
226        [-1.62557e-6, -0.31919e-6, -0.13843e-6],
227        [1.245e-3, -1.580e-3, -0.659e-3],
228    ];
229
230    const EM: [[[[f64; 3]; 2]; 3]; 2] = [
231        [
232            [
233                [0.999_925_679_5, 0.011_181_482_8, 0.004_859_003_9],
234                [
235                    -0.000_002_423_898_40,
236                    -0.000_000_027_105_44,
237                    -0.000_000_011_777_42,
238                ],
239            ],
240            [
241                [-0.011_181_482_8, 0.999_937_484_9, -0.000_027_177_1],
242                [
243                    0.000_000_027_105_44,
244                    -0.000_002_423_927_02,
245                    0.000_000_000_065_85,
246                ],
247            ],
248            [
249                [-0.004_859_004_0, -0.000_027_155_7, 0.999_988_194_6],
250                [
251                    0.000_000_011_777_42,
252                    0.000_000_000_065_85,
253                    -0.000_002_424_049_95,
254                ],
255            ],
256        ],
257        [
258            [
259                [-0.000_551, 0.238_509, -0.435_614],
260                [0.999_904_32, 0.011_181_45, 0.004_858_52],
261            ],
262            [
263                [-0.238_560, -0.002_667, 0.012_254],
264                [-0.011_181_45, 0.999_916_13, -0.000_027_17],
265            ],
266            [
267                [0.435_730, -0.008_541, 0.002_117],
268                [-0.004_858_52, -0.000_027_16, 0.999_966_84],
269            ],
270        ],
271    ];
272
273    // FK5 catalog → pv
274    let pxvf = p2000 * VF;
275    let w_rv = v2000 * pxvf;
276    let r0 = eraS2pv_safe(r2000, d2000, 1.0, dr2000 * PMF, dd2000 * PMF, w_rv)?;
277
278    // FK5 → FK4 frame
279    let mut r1 = [[0.0_f64; 3]; 2];
280    for i in 0..2 {
281        for j in 0..3 {
282            let mut sum = 0.0_f64;
283            for k in 0..2 {
284                for l in 0..3 {
285                    sum += EM[i][j][k][l] * r0[k][l];
286                }
287            }
288            r1[i][j] = sum;
289        }
290    }
291
292    // Apply one-step E-terms
293    // First for position using A[0]
294    let len_r1 = eraPm_safe(&r1[0])?;
295    let mut p1 = eraSxp_safe(eraPdp_safe(&r1[0], &A[0])?, &r1[0])?;
296    let mut p2 = eraSxp_safe(len_r1, &A[0])?;
297    p1 = eraPmp_safe(&p2, &p1)?;
298    p1 = eraPpp_safe(&r1[0], &p1)?;
299
300    // Recompute length and repeat to refine
301    let len_p1 = eraPm_safe(&p1)?;
302    p1 = eraSxp_safe(eraPdp_safe(&r1[0], &A[0])?, &r1[0])?;
303    p2 = eraSxp_safe(len_p1, &A[0])?;
304    p1 = eraPmp_safe(&p2, &p1)?;
305    let mut pv = [[0.0_f64; 3]; 2];
306    pv[0] = eraPpp_safe(&r1[0], &p1)?;
307
308    // Now velocity using A[1]
309    let q1 = eraSxp_safe(eraPdp_safe(&r1[0], &A[1])?, &pv[0])?;
310    let q2 = eraSxp_safe(len_p1, &A[1])?;
311    let q = eraPmp_safe(&q2, &q1)?;
312    pv[1] = eraPpp_safe(&r1[1], &q)?;
313
314    // pv → catalog FK4
315    let (ra, dec, w_scale, ur, ud, rd) = eraPv2s_safe(&pv)?;
316
317    let mut px = p2000;
318    let mut rv = v2000;
319    if px > TINY {
320        rv = rd / pxvf;
321        px = px / w_scale;
322    }
323
324    let r1950 = eraAnp_safe(ra)?;
325    let d1950 = dec;
326    let dr1950 = ur / PMF;
327    let dd1950 = ud / PMF;
328    let p1950 = px;
329    let v1950 = rv;
330
331    Ok((r1950, d1950, dr1950, dd1950, p1950, v1950))
332}
333
334// G16/fk52h.c → eraFk52h_safe
335
336// FK5 J2000 → Hipparcos catalog parameters.
337pub fn eraFk52h_safe(
338    r5: f64,
339    d5: f64,
340    dr5: f64,
341    dd5: f64,
342    px5: f64,
343    rv5: f64,
344) -> ErfaResult<(f64, f64, f64, f64, f64, f64)> {
345    let (pv5, _warn) = eraStarpv_safe(r5, d5, dr5, dd5, px5, rv5)?;
346
347    let (r5h_m, mut s5h) = eraFk5hip_safe()?;
348
349    // Spin in rad/yr → rad/day
350    for v in &mut s5h {
351        *v /= 365.25;
352    }
353
354    // Rotate position to Hipparcos
355    let pvh_pos = eraRxp_safe(&r5h_m, &pv5[0])?;
356
357    // Add spin × position to velocity, then rotate
358    let wxp = eraPxp_safe(&pv5[0], &s5h)?;
359    let vv = eraPpp_safe(&wxp, &pv5[1])?;
360    let pvh_vel = eraRxp_safe(&r5h_m, &vv)?;
361
362    let pvh = [pvh_pos, pvh_vel];
363    let ((rh, dh, drh, ddh, pxh, rvh), _j) = eraPvstar_safe(&pvh)?;
364    Ok((rh, dh, drh, ddh, pxh, rvh))
365}
366
367// G16/fk54z.c → eraFk54z_safe
368
369// FK5 J2000 position at epoch → FK4 B1950 position and proper motion.
370pub fn eraFk54z_safe(r2000: f64, d2000: f64, bepoch: f64) -> ErfaResult<(f64, f64, f64, f64)> {
371    let (r, d, pr, pd, _px, _rv) = eraFk524_safe(r2000, d2000, 0.0, 0.0, 0.0, 0.0)?;
372
373    // Position vector
374    let mut p = eraS2c_safe(r, d)?;
375
376    // Proper-motion vector (approximate Cartesian)
377    let v = [
378        -pr * p[1] - pd * r.cos() * d.sin(),
379        pr * p[0] - pd * r.sin() * d.sin(),
380        pd * d.cos(),
381    ];
382
383    // Advance by epoch offset
384    let dt = bepoch - 1950.0;
385    for i in 0..3 {
386        p[i] += dt * v[i];
387    }
388
389    // Back to spherical
390    let (ra, d1950) = eraC2s_safe(&p)?;
391    let r1950 = eraAnp_safe(ra)?;
392    let dr1950 = pr;
393    let dd1950 = pd;
394
395    Ok((r1950, d1950, dr1950, dd1950))
396}
397
398// G16/fk5hz.c → eraFk5hz_safe
399
400// FK5 J2000 position at date → Hipparcos position.
401pub fn eraFk5hz_safe(r5: f64, d5: f64, date1: f64, date2: f64) -> ErfaResult<(f64, f64)> {
402    // Time from date to J2000 in Julian centuries (negative for forward date)
403    let t = -((date1 - ERFA_DJ00) + date2) / ERFA_DJY;
404
405    let p5e = eraS2c_safe(r5, d5)?;
406
407    let (r5h, s5h) = eraFk5hip_safe()?;
408
409    // Build small rotation from spin×t
410    let vst = eraSxp_safe(t, &s5h)?;
411    let rst = eraRv2m_safe(&vst)?;
412
413    // Apply spin rotation (transpose product) and then rotate to Hipparcos
414    let p5 = eraTrxp_safe(&rst, &p5e)?;
415    let ph = eraRxp_safe(&r5h, &p5)?;
416
417    let (ra, dh) = eraC2s_safe(&ph)?;
418    let rh = eraAnp_safe(ra)?;
419    Ok((rh, dh))
420}
421
422// G16/fw2m.c → eraFw2m_safe
423
424// FukushimaWilliams angles → rotation matrix.
425pub fn eraFw2m_safe(gamb: f64, phib: f64, psi: f64, eps: f64) -> ErfaResult<[[f64; 3]; 3]> {
426    let mut r = [[0.0_f64; 3]; 3];
427    eraIr_safe(&mut r)?;
428    eraRz_safe(gamb, &mut r)?;
429    eraRx_safe(phib, &mut r)?;
430    eraRz_safe(-psi, &mut r)?;
431    eraRx_safe(-eps, &mut r)?;
432    Ok(r)
433}
434
435// G16/fw2xy.c → eraFw2xy_safe
436
437// FukushimaWilliams angles → (x, y) CIP coordinates.
438pub fn eraFw2xy_safe(gamb: f64, phib: f64, psi: f64, eps: f64) -> ErfaResult<(f64, f64)> {
439    let rmat = eraFw2m_safe(gamb, phib, psi, eps)?;
440    let (x, y) = eraBpn2xy_safe(&rmat)?;
441    Ok((x, y))
442}
443
444// Scalar×vector → vector.
445#[inline]
446fn eraSxp_safe(s: f64, p: &[f64; 3]) -> ErfaResult<[f64; 3]> {
447    Ok([s * p[0], s * p[1], s * p[2]])
448}
449
450// a + sb → vector.
451#[inline]
452fn eraPpsp_safe(a: &[f64; 3], s: f64, b: &[f64; 3]) -> ErfaResult<[f64; 3]> {
453    Ok([a[0] + s * b[0], a[1] + s * b[1], a[2] + s * b[2]])
454}