erfa_rust/
G4_safe.rs

1// G4
2//   atci13.c   → eraAtci13_safe
3//   atciq.c    → eraAtciq_safe
4//   atciqn.c   → eraAtciqn_safe
5//   atciqz.c   → eraAtciqz_safe
6//   atco13.c   → eraAtco13_safe
7//   atic13.c   → eraAtic13_safe
8//   aticq.c    → eraAticq_safe
9//   aticqn.c   → eraAticqn_safe
10//   atio13.c   → eraAtio13_safe
11//   atioq.c    → eraAtioq_safe
12
13use crate::G1_safe::{eraAb_safe, eraAnp_safe, eraApci13_safe};
14use crate::G20_safe::{eraLdn_safe, eraLdsun_safe};
15use crate::G25_safe::eraPmpx_safe;
16use crate::G28_safe::eraRxp_safe;
17use crate::G29_safe::eraS2c_safe;
18use crate::G2_safe::eraApco13_safe;
19use crate::G33_safe::eraTrxp_safe;
20use crate::G35_safe::eraZp_safe;
21use crate::G3_safe::eraApio13_safe;
22use crate::G7_safe::eraC2s_safe;
23use crate::H1_safe::{eraASTROM, eraLDBODY};
24
25pub type ErfaResult<T> = Result<T, ()>;
26
27/*----------------------------------------------------------------------
28 *  atci13.c  → eraAtci13_safe
29 *--------------------------------------------------------------------*/
30
31// ICRS catalog (J2000) to CIRS; returns (ri, di, eo).
32pub fn eraAtci13_safe(
33    rc: f64,
34    dc: f64,
35    pr: f64,
36    pd: f64,
37    px: f64,
38    rv: f64,
39    date1: f64,
40    date2: f64,
41) -> ErfaResult<(f64, f64, f64)> {
42    let mut astrom = eraASTROM::default();
43    let eo = eraApci13_safe(date1, date2, &mut astrom)?;
44    let (ri, di) = eraAtciq_safe(rc, dc, pr, pd, px, rv, &astrom)?;
45    Ok((ri, di, eo))
46}
47
48/*----------------------------------------------------------------------
49 *  atciq.c  → eraAtciq_safe
50 *--------------------------------------------------------------------*/
51
52// ICRS catalog to CIRS given precomputed astrom; returns (ri, di).
53pub fn eraAtciq_safe(
54    rc: f64,
55    dc: f64,
56    pr: f64,
57    pd: f64,
58    px: f64,
59    rv: f64,
60    astrom: &eraASTROM,
61) -> ErfaResult<(f64, f64)> {
62    let a = astrom;
63
64    // Proper motion & parallax to BCRS direction.
65    let pco = eraPmpx_safe(rc, dc, pr, pd, px, rv, a.pmt, &a.eb)?;
66
67    // Solar light deflection.
68    let pnat = eraLdsun_safe(&pco, &a.eh, a.em)?;
69
70    // Aberration.
71    let ppr = eraAb_safe(&pnat, &a.v, a.em, a.bm1)?;
72
73    // Rotate by BPN to CIRS.
74    let pi = eraRxp_safe(&a.bpn, &ppr)?;
75
76    // To spherical and wrap.
77    let (w, di) = eraC2s_safe(&pi)?;
78    let ri = eraAnp_safe(w)?;
79    Ok((ri, di))
80}
81
82/*----------------------------------------------------------------------
83 *  atciqn.c  → eraAtciqn_safe
84 *--------------------------------------------------------------------*/
85
86// ICRS catalog to CIRS including N-body deflection; returns (ri, di).
87pub fn eraAtciqn_safe(
88    rc: f64,
89    dc: f64,
90    pr: f64,
91    pd: f64,
92    px: f64,
93    rv: f64,
94    astrom: &eraASTROM,
95    bodies: &[eraLDBODY],
96) -> ErfaResult<(f64, f64)> {
97    let a = astrom;
98
99    // Proper motion & parallax to BCRS direction.
100    let pco = eraPmpx_safe(rc, dc, pr, pd, px, rv, a.pmt, &a.eb)?;
101
102    // N-body light deflection.
103    let pnat = eraLdn_safe(bodies, &a.eb, &pco)?;
104
105    // Aberration.
106    let ppr = eraAb_safe(&pnat, &a.v, a.em, a.bm1)?;
107
108    // Rotate by BPN to CIRS.
109    let pi = eraRxp_safe(&a.bpn, &ppr)?;
110
111    // To spherical and wrap.
112    let (w, di) = eraC2s_safe(&pi)?;
113    let ri = eraAnp_safe(w)?;
114    Ok((ri, di))
115}
116
117/*----------------------------------------------------------------------
118 *  atciqz.c  → eraAtciqz_safe
119 *--------------------------------------------------------------------*/
120
121// ICRS to CIRS for zero PM/parallax; returns (ri, di).
122pub fn eraAtciqz_safe(rc: f64, dc: f64, astrom: &eraASTROM) -> ErfaResult<(f64, f64)> {
123    let a = astrom;
124
125    // Spherical to Cartesian.
126    let pco = eraS2c_safe(rc, dc)?;
127
128    // Solar deflection then aberration.
129    let pnat = eraLdsun_safe(&pco, &a.eh, a.em)?;
130    let ppr = eraAb_safe(&pnat, &a.v, a.em, a.bm1)?;
131
132    // Rotate by BPN to CIRS.
133    let pi = eraRxp_safe(&a.bpn, &ppr)?;
134
135    // To spherical and wrap.
136    let (w, di) = eraC2s_safe(&pi)?;
137    let ri = eraAnp_safe(w)?;
138    Ok((ri, di))
139}
140
141/*----------------------------------------------------------------------
142 *  atco13.c  → eraAtco13_safe
143 *--------------------------------------------------------------------*/
144
145// ICRS catalog to observed place from UTC/site/weather; returns (aob, zob, hob, dob, rob, eo, j).
146pub fn eraAtco13_safe(
147    rc: f64,
148    dc: f64,
149    pr: f64,
150    pd: f64,
151    px: f64,
152    rv: f64,
153    utc1: f64,
154    utc2: f64,
155    dut1: f64,
156    elong: f64,
157    phi: f64,
158    hm: f64,
159    xp: f64,
160    yp: f64,
161    phpa: f64,
162    tc: f64,
163    rh: f64,
164    wl: f64,
165) -> ErfaResult<(f64, f64, f64, f64, f64, f64, i32)> {
166    let mut astrom = eraASTROM::default();
167
168    // Site-dependent astrometry params from UTC.
169    let (eo, j) = eraApco13_safe(
170        utc1,
171        utc2,
172        dut1,
173        elong,
174        phi,
175        hm,
176        xp,
177        yp,
178        phpa,
179        tc,
180        rh,
181        wl,
182        &mut astrom,
183    )?;
184
185    // ICRS -> CIRS.
186    let (ri, di) = eraAtciq_safe(rc, dc, pr, pd, px, rv, &astrom)?;
187
188    // CIRS -> observed.
189    let (aob, zob, hob, dob, rob) = eraAtioq_safe(ri, di, &astrom)?;
190    Ok((aob, zob, hob, dob, rob, eo, j))
191}
192
193/*----------------------------------------------------------------------
194 *  atic13.c  → eraAtic13_safe
195 *--------------------------------------------------------------------*/
196
197// CIRS astrometric to ICRS catalog using 2013 models; returns (rc, dc, eo).
198pub fn eraAtic13_safe(ri: f64, di: f64, date1: f64, date2: f64) -> ErfaResult<(f64, f64, f64)> {
199    let mut astrom = eraASTROM::default();
200    let eo = eraApci13_safe(date1, date2, &mut astrom)?;
201    let (rc, dc) = eraAticq_safe(ri, di, &astrom)?;
202    Ok((rc, dc, eo))
203}
204
205/*----------------------------------------------------------------------
206 *  aticq.c  → eraAticq_safe
207 *--------------------------------------------------------------------*/
208
209// CIRS astrometric to ICRS catalog given precomputed astrom; returns (rc, dc).
210pub fn eraAticq_safe(ri: f64, di: f64, astrom: &eraASTROM) -> ErfaResult<(f64, f64)> {
211    let a = astrom;
212
213    // CIRS to Cartesian.
214    let pi = eraS2c_safe(ri, di)?;
215
216    // Bias-precession-nutation transpose to GCRS proper.
217    let ppr = eraTrxp_safe(&a.bpn, &pi)?;
218
219    // Aberration iteration (2 passes).
220    let mut d = eraZp_safe();
221    let mut pnat = [0.0_f64; 3];
222    let mut before = [0.0_f64; 3];
223    let mut after = [0.0_f64; 3];
224    for _ in 0..2 {
225        // before = unit(ppr - d)
226        let mut r2 = 0.0;
227        for i in 0..3 {
228            let w = ppr[i] - d[i];
229            before[i] = w;
230            r2 += w * w;
231        }
232        let r = r2.sqrt();
233        for v in before.iter_mut() {
234            *v /= r;
235        }
236
237        // after = aberrated(before)
238        let tmp = eraAb_safe(&before, &a.v, a.em, a.bm1)?;
239        after.copy_from_slice(&tmp);
240
241        // d = after - before; pnat = unit(ppr - d)
242        let mut r2b = 0.0;
243        for i in 0..3 {
244            d[i] = after[i] - before[i];
245            let w = ppr[i] - d[i];
246            pnat[i] = w;
247            r2b += w * w;
248        }
249        let rb = r2b.sqrt();
250        for v in pnat.iter_mut() {
251            *v /= rb;
252        }
253    }
254
255    // Light deflection iteration (5 passes).
256    let mut pco = [0.0_f64; 3];
257    d = eraZp_safe();
258    for _ in 0..5 {
259        // before = unit(pnat - d)
260        let mut r2 = 0.0;
261        for i in 0..3 {
262            let w = pnat[i] - d[i];
263            before[i] = w;
264            r2 += w * w;
265        }
266        let r = r2.sqrt();
267        for v in before.iter_mut() {
268            *v /= r;
269        }
270
271        // after = ldsun(before)
272        let tmp = eraLdsun_safe(&before, &a.eh, a.em)?;
273        after.copy_from_slice(&tmp);
274
275        // d = after - before; pco = unit(pnat - d)
276        let mut r2b = 0.0;
277        for i in 0..3 {
278            d[i] = after[i] - before[i];
279            let w = pnat[i] - d[i];
280            pco[i] = w;
281            r2b += w * w;
282        }
283        let rb = r2b.sqrt();
284        for v in pco.iter_mut() {
285            *v /= rb;
286        }
287    }
288
289    // To spherical and wrap.
290    let (w, dc) = eraC2s_safe(&pco)?;
291    let rc = eraAnp_safe(w)?;
292    Ok((rc, dc))
293}
294
295/*----------------------------------------------------------------------
296 *  aticqn.c  → eraAticqn_safe
297 *--------------------------------------------------------------------*/
298
299// CIRS astrometric to ICRS with n-body deflection; returns (rc, dc).
300pub fn eraAticqn_safe(
301    ri: f64,
302    di: f64,
303    astrom: &eraASTROM,
304    bodies: &[eraLDBODY],
305) -> ErfaResult<(f64, f64)> {
306    let a = astrom;
307
308    // CIRS to Cartesian and remove BPN.
309    let pi = eraS2c_safe(ri, di)?;
310    let ppr = eraTrxp_safe(&a.bpn, &pi)?;
311
312    // Aberration iteration (2 passes).
313    let mut d = eraZp_safe();
314    let mut pnat = [0.0_f64; 3];
315    let mut before = [0.0_f64; 3];
316    let mut after = [0.0_f64; 3];
317    for _ in 0..2 {
318        let mut r2 = 0.0;
319        for i in 0..3 {
320            let w = ppr[i] - d[i];
321            before[i] = w;
322            r2 += w * w;
323        }
324        let r = r2.sqrt();
325        for v in before.iter_mut() {
326            *v /= r;
327        }
328        let tmp = eraAb_safe(&before, &a.v, a.em, a.bm1)?;
329        after.copy_from_slice(&tmp);
330        let mut r2b = 0.0;
331        for i in 0..3 {
332            d[i] = after[i] - before[i];
333            let w = ppr[i] - d[i];
334            pnat[i] = w;
335            r2b += w * w;
336        }
337        let rb = r2b.sqrt();
338        for v in pnat.iter_mut() {
339            *v /= rb;
340        }
341    }
342
343    // N-body deflection iteration (5 passes).
344    let mut pco = [0.0_f64; 3];
345    d = eraZp_safe();
346    for _ in 0..5 {
347        let mut r2 = 0.0;
348        for i in 0..3 {
349            let w = pnat[i] - d[i];
350            before[i] = w;
351            r2 += w * w;
352        }
353        let r = r2.sqrt();
354        for v in before.iter_mut() {
355            *v /= r;
356        }
357        let tmp = eraLdn_safe(bodies, &a.eb, &before)?;
358        after.copy_from_slice(&tmp);
359        let mut r2b = 0.0;
360        for i in 0..3 {
361            d[i] = after[i] - before[i];
362            let w = pnat[i] - d[i];
363            pco[i] = w;
364            r2b += w * w;
365        }
366        let rb = r2b.sqrt();
367        for v in pco.iter_mut() {
368            *v /= rb;
369        }
370    }
371
372    // To spherical and wrap.
373    let (w, dc) = eraC2s_safe(&pco)?;
374    let rc = eraAnp_safe(w)?;
375    Ok((rc, dc))
376}
377
378/*----------------------------------------------------------------------
379 *  atio13.c  → eraAtio13_safe
380 *--------------------------------------------------------------------*/
381
382// CIRS astrometric to observed place from UTC/site/weather; returns (aob, zob, hob, dob, rob, j).
383pub fn eraAtio13_safe(
384    ri: f64,
385    di: f64,
386    utc1: f64,
387    utc2: f64,
388    dut1: f64,
389    elong: f64,
390    phi: f64,
391    hm: f64,
392    xp: f64,
393    yp: f64,
394    phpa: f64,
395    tc: f64,
396    rh: f64,
397    wl: f64,
398) -> ErfaResult<(f64, f64, f64, f64, f64, i32)> {
399    let mut astrom = eraASTROM::default();
400    let j = eraApio13_safe(
401        utc1,
402        utc2,
403        dut1,
404        elong,
405        phi,
406        hm,
407        xp,
408        yp,
409        phpa,
410        tc,
411        rh,
412        wl,
413        &mut astrom,
414    )?;
415    let (aob, zob, hob, dob, rob) = eraAtioq_safe(ri, di, &astrom)?;
416    Ok((aob, zob, hob, dob, rob, j))
417}
418
419/*----------------------------------------------------------------------
420 *  atioq.c  → eraAtioq_safe
421 *--------------------------------------------------------------------*/
422
423// CIRS (ri,di) to observed (az,zd,HA,Dec,RA) given precomputed astrom; returns (aob, zob, hob, dob, rob).
424pub fn eraAtioq_safe(
425    ri: f64,
426    di: f64,
427    astrom: &eraASTROM,
428) -> ErfaResult<(f64, f64, f64, f64, f64)> {
429    const CELMIN: f64 = 1e-6;
430    const SELMIN: f64 = 0.05;
431
432    let a = astrom;
433
434    // CIRS -> HA,Dec unit vector in topocentric frame.
435    let v = eraS2c_safe(ri - a.eral, di)?;
436    let x = v[0];
437    let y = v[1];
438    let mut z = v[2];
439
440    // Polar motion rotation (X then Y).
441    let (sx, cx) = a.xpl.sin_cos();
442    let (sy, cy) = a.ypl.sin_cos();
443    let xhd = cx * x + sx * z;
444    let yhd = sx * sy * x + cy * y - cx * sy * z;
445    let zhd = -sx * cy * x + sy * y + cx * cy * z;
446
447    // Diurnal aberration.
448    let f = 1.0 - a.diurab * yhd;
449    let xhdt = f * xhd;
450    let yhdt = f * (yhd + a.diurab);
451    let zhdt = f * zhd;
452
453    // Latitude rotation.
454    let xaet = a.sphi * xhdt - a.cphi * zhdt;
455    let yaet = yhdt;
456    let zaet = a.cphi * xhdt + a.sphi * zhdt;
457
458    // Azimuth.
459    let azobs = if xaet != 0.0 || yaet != 0.0 {
460        yaet.atan2(-xaet)
461    } else {
462        0.0
463    };
464
465    // Elevation with floors to avoid extreme refraction.
466    let mut r = (xaet * xaet + yaet * yaet).sqrt();
467    r = if r > CELMIN { r } else { CELMIN };
468    z = if zaet > SELMIN { zaet } else { SELMIN };
469
470    // Refraction: apply to tangent of zenith distance.
471    let tz = r / z;
472    let w = a.refb * tz * tz;
473    let del = (a.refa + w) * tz / (1.0 + (a.refa + 3.0 * w) / (z * z));
474
475    // Undo to get observed az/el vector.
476    let cosdel = 1.0 - del * del / 2.0;
477    let f2 = cosdel - del * z / r;
478    let xaeo = xaet * f2;
479    let yaeo = yaet * f2;
480    let zaeo = cosdel * zaet + del * r;
481
482    // Zenith distance.
483    let zdobs = ((xaeo * xaeo + yaeo * yaeo).sqrt()).atan2(zaeo);
484
485    // Undo latitude rotation.
486    let vx = a.sphi * xaeo + a.cphi * zaeo;
487    let vy = yaeo;
488    let vz = -a.cphi * xaeo + a.sphi * zaeo;
489    let v_obs = [vx, vy, vz];
490
491    // To spherical: hour angle (negative sign) and declination.
492    let (hmobs, dcobs) = eraC2s_safe(&v_obs)?;
493
494    // Compute RA only after hmobs is known.
495    let raobs = a.eral + hmobs;
496
497    // Wrap azimuth and RA into canonical ranges.
498    let aob = eraAnp_safe(azobs)?;
499    let zob = zdobs;
500    let hob = -hmobs;
501    let dob = dcobs;
502    let rob = eraAnp_safe(raobs)?;
503    Ok((aob, zob, hob, dob, rob))
504}