1const VALLADO_MU: f64 = 398600.4415;
26const VALLADO_RE: f64 = 6378.1363;
29const VALLADO_TUSEC: f64 = 806.8109913067327;
32use crate::astro::math::linear::invert_3x3_adjugate;
35use crate::astro::math::vec3;
36use crate::constants::SECONDS_PER_DAY as DAY2SEC;
37const SMALL: f64 = 1e-10;
38const COPLANAR_TOL_RAD: f64 = 5.0 * std::f64::consts::PI / 180.0;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
45pub enum IodError {
46 #[error("line-of-sight determinant too small")]
49 DeterminantTooSmall,
50 #[error("orbit determination not possible from the given geometry")]
53 OrbitNotPossible,
54 #[error("position vector has near-zero magnitude")]
57 ZeroVector,
58 #[error("position vectors are collinear")]
61 CollinearVectors,
62 #[error("position vectors are not coplanar")]
65 NotCoplanar,
66 #[error("observation times are equal or near-equal")]
69 InvalidTimeGeometry,
70 #[error("no positive real root for the slant-range polynomial")]
73 NoPositiveRoot,
74 #[error("slant-range root solver did not converge")]
78 RootSolveFailed,
79 #[error("non-finite value encountered")]
81 NonFiniteValue,
82}
83
84fn all_finite(a: &[f64; 3]) -> bool {
86 a.iter().all(|x| x.is_finite())
87}
88
89fn cross(a: &[f64; 3], b: &[f64; 3]) -> [f64; 3] {
90 vec3::cross3_ref(a, b)
91}
92
93fn dot(a: &[f64; 3], b: &[f64; 3]) -> f64 {
94 vec3::dot3_ref(a, b)
95}
96
97fn mag(a: &[f64; 3]) -> f64 {
98 vec3::norm3_ref(a)
99}
100
101fn unit(a: &[f64; 3]) -> [f64; 3] {
102 vec3::unit3_ref_unchecked(a)
103}
104
105fn vadd(a: &[f64; 3], b: &[f64; 3]) -> [f64; 3] {
110 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
111}
112
113fn smul(s: f64, a: &[f64; 3]) -> [f64; 3] {
116 vec3::scale3(*a, s)
117}
118
119fn det3(m: &[[f64; 3]; 3]) -> f64 {
121 m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
122 - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
123 + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0])
124}
125
126fn mat3_vec3(m: &[[f64; 3]; 3], v: &[f64; 3]) -> [f64; 3] {
128 [
129 m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
130 m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
131 m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2],
132 ]
133}
134
135fn mat3_mat3(a: &[[f64; 3]; 3], b: &[[f64; 3]; 3]) -> [[f64; 3]; 3] {
140 let mut r = [[0.0; 3]; 3];
141 for i in 0..3 {
142 for j in 0..3 {
143 r[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j];
144 }
145 }
146 r
147}
148
149fn los(ra: f64, dec: f64) -> [f64; 3] {
151 [dec.cos() * ra.cos(), dec.cos() * ra.sin(), dec.sin()]
152}
153
154pub fn gibbs(
163 r1: &[f64; 3],
164 r2: &[f64; 3],
165 r3: &[f64; 3],
166) -> Result<([f64; 3], f64, f64, f64), IodError> {
167 if !all_finite(r1) || !all_finite(r2) || !all_finite(r3) {
170 return Err(IodError::NonFiniteValue);
171 }
172
173 let magr1 = mag(r1);
174 let magr2 = mag(r2);
175 let magr3 = mag(r3);
176
177 if !magr1.is_finite() || !magr2.is_finite() || !magr3.is_finite() {
181 return Err(IodError::NonFiniteValue);
182 }
183
184 if magr1 < SMALL || magr2 < SMALL || magr3 < SMALL {
185 return Err(IodError::ZeroVector);
186 }
187
188 let p = cross(r2, r3);
190 let q = cross(r3, r1);
191 let w = cross(r1, r2);
192
193 let magp = mag(&p);
198 if !magp.is_finite() {
199 return Err(IodError::NonFiniteValue);
200 }
201 if magp < SMALL {
202 return Err(IodError::CollinearVectors);
203 }
204
205 let copa = dot(&unit(&p), &unit(r1)).clamp(-1.0, 1.0).asin();
209 if copa.abs() > COPLANAR_TOL_RAD {
210 return Err(IodError::NotCoplanar);
211 }
212
213 let d = vadd(&vadd(&p, &q), &w);
215 let magd = mag(&d);
216
217 let n = vadd(&vadd(&smul(magr1, &p), &smul(magr2, &q)), &smul(magr3, &w));
219 let magn = mag(&n);
220
221 if !magd.is_finite() || !magn.is_finite() {
222 return Err(IodError::NonFiniteValue);
223 }
224 if magd < 1e-6 || magn < 1e-6 {
225 return Err(IodError::OrbitNotPossible);
226 }
227
228 let theta12 = (dot(r1, r2) / (magr1 * magr2)).clamp(-1.0, 1.0).acos();
230 let theta23 = (dot(r2, r3) / (magr2 * magr3)).clamp(-1.0, 1.0).acos();
231
232 let r1mr2 = magr1 - magr2;
234 let r3mr1 = magr3 - magr1;
235 let r2mr3 = magr2 - magr3;
236 let s = vadd(&vadd(&smul(r1mr2, r3), &smul(r3mr1, r2)), &smul(r2mr3, r1));
237
238 let b = cross(&d, r2);
240
241 let lg = (VALLADO_MU / (magd * magn)).sqrt();
243
244 let v2 = vadd(&smul(lg / magr2, &b), &smul(lg, &s));
246
247 if !all_finite(&v2) || !theta12.is_finite() || !theta23.is_finite() || !copa.is_finite() {
250 return Err(IodError::NonFiniteValue);
251 }
252
253 Ok((v2, theta12, theta23, copa))
254}
255
256pub fn hgibbs(
268 r1: &[f64; 3],
269 r2: &[f64; 3],
270 r3: &[f64; 3],
271 jd1: f64,
272 jd2: f64,
273 jd3: f64,
274) -> Result<([f64; 3], f64, f64, f64), IodError> {
275 if !all_finite(r1)
278 || !all_finite(r2)
279 || !all_finite(r3)
280 || !jd1.is_finite()
281 || !jd2.is_finite()
282 || !jd3.is_finite()
283 {
284 return Err(IodError::NonFiniteValue);
285 }
286
287 let magr1 = mag(r1);
288 let magr2 = mag(r2);
289 let magr3 = mag(r3);
290
291 if !magr1.is_finite() || !magr2.is_finite() || !magr3.is_finite() {
294 return Err(IodError::NonFiniteValue);
295 }
296
297 if magr1 < SMALL || magr2 < SMALL || magr3 < SMALL {
298 return Err(IodError::ZeroVector);
299 }
300
301 let dt21 = (jd2 - jd1) * DAY2SEC;
303 let dt31 = (jd3 - jd1) * DAY2SEC;
304 let dt32 = (jd3 - jd2) * DAY2SEC;
305
306 if dt21.abs() < SMALL || dt31.abs() < SMALL || dt32.abs() < SMALL {
308 return Err(IodError::InvalidTimeGeometry);
309 }
310
311 let p = cross(r2, r3);
314 let magp = mag(&p);
315 if !magp.is_finite() {
316 return Err(IodError::NonFiniteValue);
317 }
318 if magp < SMALL {
319 return Err(IodError::CollinearVectors);
320 }
321
322 let copa = dot(&unit(&p), &unit(r1)).clamp(-1.0, 1.0).asin();
326 if copa.abs() > COPLANAR_TOL_RAD {
327 return Err(IodError::NotCoplanar);
328 }
329
330 let theta12 = (dot(r1, r2) / (magr1 * magr2)).clamp(-1.0, 1.0).acos();
332 let theta23 = (dot(r2, r3) / (magr2 * magr3)).clamp(-1.0, 1.0).acos();
333
334 let term1 = smul(
336 -dt32 * (1.0 / (dt21 * dt31) + VALLADO_MU / (12.0 * magr1.powi(3))),
337 r1,
338 );
339 let term2 = smul(
340 (dt32 - dt21) * (1.0 / (dt21 * dt32) + VALLADO_MU / (12.0 * magr2.powi(3))),
341 r2,
342 );
343 let term3 = smul(
344 dt21 * (1.0 / (dt32 * dt31) + VALLADO_MU / (12.0 * magr3.powi(3))),
345 r3,
346 );
347
348 let v2 = vadd(&vadd(&term1, &term2), &term3);
349
350 if !all_finite(&v2) || !theta12.is_finite() || !theta23.is_finite() || !copa.is_finite() {
353 return Err(IodError::NonFiniteValue);
354 }
355
356 Ok((v2, theta12, theta23, copa))
357}
358
359fn halley_iteration(poly: &[f64; 9]) -> Option<f64> {
365 let mut bigr2c = 20000.0 / VALLADO_RE;
367 let mut bigr2 = 100.0;
368 let mut converged = false;
369
370 for _ in 0..15 {
371 if (bigr2 - bigr2c).abs() < 8e-5 {
372 converged = true;
373 break;
374 }
375 bigr2 = bigr2c;
376 let x = bigr2;
377 let f = x.powi(8) + poly[2] * x.powi(6) + poly[5] * x.powi(3) + poly[8];
378 let f1 = 8.0 * x.powi(7) + 6.0 * poly[2] * x.powi(5) + 3.0 * poly[5] * x.powi(2);
379 let f2 = 56.0 * x.powi(6) + 30.0 * poly[2] * x.powi(4) + 6.0 * poly[5] * x;
380 let denom = 2.0 * f1 * f1 - f * f2;
381 if denom.abs() < SMALL {
382 return None;
383 }
384 bigr2c = bigr2 - (2.0 * f * f1) / denom;
385 if !bigr2c.is_finite() {
386 return None;
387 }
388 }
389
390 if !converged {
395 converged = (bigr2 - bigr2c).abs() < 8e-5;
396 }
397
398 if !converged || !bigr2c.is_finite() {
399 return None;
400 }
401
402 let x = bigr2c;
406 let t0 = x.powi(8);
407 let t2 = poly[2] * x.powi(6);
408 let t5 = poly[5] * x.powi(3);
409 let t8 = poly[8];
410 let residual = t0 + t2 + t5 + t8;
411 let scale = t0.abs() + t2.abs() + t5.abs() + t8.abs();
412 if !residual.is_finite() || !scale.is_finite() || residual.abs() > 1e-9 * scale {
413 return None;
414 }
415
416 Some(bigr2c)
417}
418
419pub fn gauss_angles(
434 decl: &[f64; 3],
435 rtasc: &[f64; 3],
436 jd: &[f64; 3],
437 jdf: &[f64; 3],
438 rseci: &[[f64; 3]; 3],
439) -> Result<([f64; 3], [f64; 3]), IodError> {
440 if !decl.iter().all(|x| x.is_finite())
443 || !rtasc.iter().all(|x| x.is_finite())
444 || !jd.iter().all(|x| x.is_finite())
445 || !jdf.iter().all(|x| x.is_finite())
446 || !rseci.iter().all(all_finite)
447 {
448 return Err(IodError::NonFiniteValue);
449 }
450
451 let tau12 = ((jd[0] - jd[1]) + (jdf[0] - jdf[1])) * DAY2SEC;
453 let _tau13 = ((jd[0] - jd[2]) + (jdf[0] - jdf[2])) * DAY2SEC;
454 let tau32 = ((jd[2] - jd[1]) + (jdf[2] - jdf[1])) * DAY2SEC;
455
456 if tau12.abs() < SMALL || tau32.abs() < SMALL || (tau32 - tau12).abs() < SMALL {
459 return Err(IodError::InvalidTimeGeometry);
460 }
461
462 let l1 = los(rtasc[0], decl[0]);
464 let l2 = los(rtasc[1], decl[1]);
465 let l3 = los(rtasc[2], decl[2]);
466
467 let tau12c = tau12 / VALLADO_TUSEC;
469 let tau32c = tau32 / VALLADO_TUSEC;
470 let rseci1c = smul(1.0 / VALLADO_RE, &rseci[0]);
471 let rseci2c = smul(1.0 / VALLADO_RE, &rseci[1]);
472 let rseci3c = smul(1.0 / VALLADO_RE, &rseci[2]);
473
474 let lmat = [
476 [l1[0], l2[0], l3[0]],
477 [l1[1], l2[1], l3[1]],
478 [l1[2], l2[2], l3[2]],
479 ];
480
481 let d = det3(&lmat);
482 if d.abs() < SMALL {
483 return Err(IodError::DeterminantTooSmall);
484 }
485
486 let lmati = invert_3x3_adjugate(&lmat).ok_or(IodError::DeterminantTooSmall)?;
491
492 let rsmatc = [
494 [rseci1c[0], rseci2c[0], rseci3c[0]],
495 [rseci1c[1], rseci2c[1], rseci3c[1]],
496 [rseci1c[2], rseci2c[2], rseci3c[2]],
497 ];
498
499 let lir = mat3_mat3(&lmati, &rsmatc);
500
501 let a1 = tau32c / (tau32c - tau12c);
503 let a1u = (tau32c * ((tau32c - tau12c).powi(2) - tau32c.powi(2))) / (6.0 * (tau32c - tau12c));
504 let a3 = -tau12c / (tau32c - tau12c);
505 let a3u = -(tau12c * ((tau32c - tau12c).powi(2) - tau12c.powi(2))) / (6.0 * (tau32c - tau12c));
506
507 let d1c = lir[1][0] * a1 - lir[1][1] + lir[1][2] * a3;
508 let d2c = lir[1][0] * a1u + lir[1][2] * a3u;
509 let magrs2 = mag(&rseci2c);
510 let l2dotrs = dot(&l2, &rseci2c);
511
512 let mut poly = [0.0; 9];
514 poly[0] = 1.0;
515 poly[2] = -(d1c.powi(2) + 2.0 * d1c * l2dotrs + magrs2.powi(2));
516 poly[5] = -2.0 * (l2dotrs * d2c + d1c * d2c);
517 poly[8] = -(d2c.powi(2));
518
519 let bigr2c = match halley_iteration(&poly) {
524 Some(r) if r > 0.0 && r * VALLADO_RE <= 50000.0 => r,
525 Some(_) => return Err(IodError::NoPositiveRoot),
526 None => return Err(IodError::RootSolveFailed),
527 };
528
529 let bigr2 = bigr2c * VALLADO_RE;
530 let a1u_sec = a1u * VALLADO_TUSEC.powi(2);
531 let a3u_sec = a3u * VALLADO_TUSEC.powi(2);
532
533 let u = VALLADO_MU / bigr2.powi(3);
535 let c1 = a1 + a1u_sec * u;
536 let c2 = -1.0;
537 let c3 = a3 + a3u_sec * u;
538
539 if c1.abs() < SMALL || c3.abs() < SMALL {
542 return Err(IodError::OrbitNotPossible);
543 }
544
545 let rsmat = [
547 [rseci[0][0], rseci[1][0], rseci[2][0]],
548 [rseci[0][1], rseci[1][1], rseci[2][1]],
549 [rseci[0][2], rseci[1][2], rseci[2][2]],
550 ];
551 let lir_full = mat3_mat3(&lmati, &rsmat);
552 let cmat = [-c1, -c2, -c3];
553 let rhomat = mat3_vec3(&lir_full, &cmat);
554
555 let r1 = vadd(&smul(rhomat[0] / c1, &l1), &rseci[0]);
557 let r2 = vadd(&smul(rhomat[1] / c2, &l2), &rseci[1]);
558 let r3 = vadd(&smul(rhomat[2] / c3, &l3), &rseci[2]);
559
560 let (v2, _, _, _) = gibbs(&r1, &r2, &r3)?;
562
563 if !all_finite(&r2) || !all_finite(&v2) {
564 return Err(IodError::NonFiniteValue);
565 }
566
567 Ok((r2, v2))
568}
569
570#[cfg(test)]
571mod tests {
572 use super::*;
573 use std::f64::consts::PI;
574
575 fn assert_rel(actual: f64, expected: f64, label: &str) {
576 let rel = ((actual - expected) / expected).abs();
577 assert!(rel < 1e-12, "{label}: relative error {rel:e} exceeds 1e-12");
578 }
579
580 fn assert_ulp(actual: f64, expected: f64, max_ulps: i64, label: &str) {
583 if actual == expected {
584 return;
585 }
586 let a = actual.to_bits() as i64;
587 let b = expected.to_bits() as i64;
588 let ulps = (a - b).abs();
589 assert!(
590 ulps <= max_ulps,
591 "{label}: {ulps} ulps exceeds {max_ulps} (got {actual}, expected {expected})"
592 );
593 }
594
595 #[test]
596 fn gibbs_example_7_3() {
597 let r1 = [0.0, 0.0, 6378.1363];
598 let r2 = [0.0, -4464.696, -5102.509];
599 let r3 = [0.0, 5740.323, 3189.068];
600
601 let (v2, theta12, theta23, copa) = gibbs(&r1, &r2, &r3).unwrap();
602 assert_ulp(v2[0], 0.0, 0, "v2_x");
603 assert_ulp(v2[1], 5.5311472050176125, 0, "v2_y");
604 assert_ulp(v2[2], -5.191806413494606, 0, "v2_z");
605 assert_ulp(theta12 * 180.0 / PI, 138.81407085944375, 2, "theta12");
606 assert_ulp(theta23 * 180.0 / PI, 160.24053069723146, 2, "theta23");
607 assert_ulp(copa, 0.0, 0, "copa");
608 }
609
610 #[test]
611 fn hgibbs_example_7_4() {
612 let r1 = [3419.85564, 6019.82602, 2784.60022];
613 let r2 = [2935.91195, 6326.18324, 2660.59584];
614 let r3 = [2434.95202, 6597.38674, 2521.52311];
615 let jd1 = 0.0;
616 let jd2 = (60.0 + 16.48) / 86400.0;
617 let jd3 = (120.0 + 33.04) / 86400.0;
618
619 let (v2, theta12, theta23, _copa) = hgibbs(&r1, &r2, &r3, jd1, jd2, jd3).unwrap();
620 assert_ulp(v2[0], -6.441557227511062, 0, "v2_x");
621 assert_ulp(v2[1], 3.777559606719521, 0, "v2_y");
622 assert_ulp(v2[2], -1.7205675602414345, 0, "v2_z");
623 assert_ulp(theta12 * 180.0 / PI, 4.499996147374992, 2, "theta12");
624 assert_ulp(theta23 * 180.0 / PI, 4.499998402168982, 2, "theta23");
625 }
626
627 #[test]
628 fn gauss_example_7_2() {
629 let d2r = |d: f64| d * PI / 180.0;
630 let decl = [d2r(18.667717), d2r(35.664741), d2r(36.996583)];
631 let rtasc = [d2r(0.939913), d2r(45.025748), d2r(67.886655)];
632 let jd = [2_456_159.5, 2_456_159.5, 2_456_159.5];
633 let jdf = [0.4864351851851852, 0.49199074074074073, 0.4947685185185185];
634 let rseci = [
635 [4054.881, 2748.195, 4074.237],
636 [3956.224, 2888.232, 4074.364],
637 [3905.073, 2956.935, 4074.430],
638 ];
639
640 let (r2, v2) = gauss_angles(&decl, &rtasc, &jd, &jdf, &rseci).unwrap();
641 assert_rel(r2[0], 6313.378130210396, "r2_x");
642 assert_rel(r2[1], 5247.50563344895, "r2_y");
643 assert_rel(r2[2], 6467.707164431651, "r2_z");
644 assert_rel(v2[0], -4.185488280436629, "v2_x");
645 assert_rel(v2[1], 4.7884929168898145, "v2_y");
646 assert_rel(v2[2], 1.721714659663034, "v2_z");
647 }
648
649 #[test]
652 fn gibbs_rejects_zero_vector() {
653 let r2 = [0.0, -4464.696, -5102.509];
654 let r3 = [0.0, 5740.323, 3189.068];
655 assert_eq!(
656 gibbs(&[0.0, 0.0, 0.0], &r2, &r3).unwrap_err(),
657 IodError::ZeroVector
658 );
659 }
660
661 #[test]
662 fn gibbs_rejects_collinear_vectors() {
663 let r1 = [0.0, 0.0, 6378.1363];
665 let r2 = [0.0, 1000.0, 0.0];
666 let r3 = [0.0, 2000.0, 0.0];
667 assert_eq!(
668 gibbs(&r1, &r2, &r3).unwrap_err(),
669 IodError::CollinearVectors
670 );
671 }
672
673 #[test]
674 fn gibbs_rejects_noncoplanar_vectors() {
675 let r1 = [6378.1363, 6378.1363, 6378.1363];
677 let r2 = [0.0, -4464.696, -5102.509];
678 let r3 = [0.0, 5740.323, 3189.068];
679 assert_eq!(gibbs(&r1, &r2, &r3).unwrap_err(), IodError::NotCoplanar);
680 }
681
682 #[test]
683 fn gibbs_rejects_nonfinite_input() {
684 let r2 = [0.0, -4464.696, -5102.509];
685 let r3 = [0.0, 5740.323, 3189.068];
686 assert_eq!(
687 gibbs(&[f64::NAN, 0.0, 6378.1363], &r2, &r3).unwrap_err(),
688 IodError::NonFiniteValue
689 );
690 }
691
692 #[test]
693 fn gibbs_accepts_antiparallel_endpoints() {
694 let r1 = [7000.0, 0.0, 0.0];
699 let r2 = [0.0, 7000.0, 0.0];
700 let r3 = [-7000.0, 0.0, 0.0];
701 let (v2, _, _, copa) = gibbs(&r1, &r2, &r3).unwrap();
702 let vcirc = (VALLADO_MU / 7000.0).sqrt();
703 assert!((v2[0] + vcirc).abs() < 1e-9, "v2_x {} vs {}", v2[0], -vcirc);
704 assert!(v2[1].abs() < 1e-9 && v2[2].abs() < 1e-9);
705 assert!(copa.abs() < 1e-12);
706 }
707
708 #[test]
709 fn gibbs_rejects_overflowing_magnitude() {
710 let r1 = [1e160, 0.0, 0.0];
713 let r2 = [0.0, 1e160, 0.0];
714 let r3 = [0.0, 0.0, 1e160];
715 assert_eq!(gibbs(&r1, &r2, &r3).unwrap_err(), IodError::NonFiniteValue);
716 }
717
718 #[test]
719 fn halley_iteration_reports_stationary_nonroot() {
720 let x0 = 20000.0 / VALLADO_RE;
724 let mut poly = [0.0; 9];
725 poly[0] = 1.0;
726 poly[2] = -x0.powi(2);
727 poly[5] = -(2.0 / 3.0) * x0.powi(5);
728 poly[8] = -x0.powi(8) / 9.0;
729 assert_eq!(halley_iteration(&poly), None);
730 }
731
732 #[test]
733 fn halley_iteration_reports_nonconvergence() {
734 let poly = [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
739 assert_eq!(halley_iteration(&poly), None);
740 }
741
742 #[test]
743 fn hgibbs_rejects_equal_times() {
744 let r1 = [3419.85564, 6019.82602, 2784.60022];
745 let r2 = [2935.91195, 6326.18324, 2660.59584];
746 let r3 = [2434.95202, 6597.38674, 2521.52311];
747 let jd = 0.0;
749 assert_eq!(
750 hgibbs(&r1, &r2, &r3, jd, jd, (120.0 + 33.04) / 86400.0).unwrap_err(),
751 IodError::InvalidTimeGeometry
752 );
753 }
754
755 #[test]
756 fn hgibbs_rejects_zero_vector() {
757 let r2 = [2935.91195, 6326.18324, 2660.59584];
758 let r3 = [2434.95202, 6597.38674, 2521.52311];
759 let jd1 = 0.0;
760 let jd2 = (60.0 + 16.48) / 86400.0;
761 let jd3 = (120.0 + 33.04) / 86400.0;
762 assert_eq!(
763 hgibbs(&[0.0, 0.0, 0.0], &r2, &r3, jd1, jd2, jd3).unwrap_err(),
764 IodError::ZeroVector
765 );
766 }
767
768 #[test]
769 fn hgibbs_rejects_nonfinite_output() {
770 let r1 = [3419.85564, 6019.82602, 2784.60022];
774 let r2 = [2935.91195, 6326.18324, 2660.59584];
775 let r3 = [2434.95202, 6597.38674, 2521.52311];
776 let err = hgibbs(&r1, &r2, &r3, 0.0, 1e306, 2e306).unwrap_err();
777 assert_eq!(err, IodError::NonFiniteValue);
778 }
779
780 #[test]
781 fn gauss_rejects_equal_times() {
782 let d2r = |d: f64| d * PI / 180.0;
783 let decl = [d2r(18.667717), d2r(35.664741), d2r(36.996583)];
784 let rtasc = [d2r(0.939913), d2r(45.025748), d2r(67.886655)];
785 let jd = [2_456_159.5, 2_456_159.5, 2_456_159.5];
786 let jdf = [0.49199074074074073, 0.49199074074074073, 0.4947685185185185];
788 let rseci = [
789 [4054.881, 2748.195, 4074.237],
790 [3956.224, 2888.232, 4074.364],
791 [3905.073, 2956.935, 4074.430],
792 ];
793 assert_eq!(
794 gauss_angles(&decl, &rtasc, &jd, &jdf, &rseci).unwrap_err(),
795 IodError::InvalidTimeGeometry
796 );
797 }
798
799 #[test]
800 fn gauss_rejects_out_of_range_root() {
801 let d2r = |d: f64| d * PI / 180.0;
802 let decl = [d2r(18.667717), d2r(35.664741), d2r(36.996583)];
803 let rtasc = [d2r(0.939913), d2r(45.025748), d2r(67.886655)];
804 let jd = [2_456_159.5, 2_456_159.5, 2_456_159.5];
805 let rseci = [
806 [4054.881, 2748.195, 4074.237],
807 [3956.224, 2888.232, 4074.364],
808 [3905.073, 2956.935, 4074.430],
809 ];
810 let base = [0.4864351851851852, 0.49199074074074073, 0.4947685185185185];
813 let mid = base[1];
814 let jdf = [
815 mid + (base[0] - mid) * 100.0,
816 mid,
817 mid + (base[2] - mid) * 100.0,
818 ];
819 assert_eq!(
820 gauss_angles(&decl, &rtasc, &jd, &jdf, &rseci).unwrap_err(),
821 IodError::NoPositiveRoot
822 );
823 }
824
825 #[test]
826 fn gauss_rejects_nonfinite_input() {
827 let d2r = |d: f64| d * PI / 180.0;
828 let decl = [f64::NAN, d2r(35.664741), d2r(36.996583)];
829 let rtasc = [d2r(0.939913), d2r(45.025748), d2r(67.886655)];
830 let jd = [2_456_159.5, 2_456_159.5, 2_456_159.5];
831 let jdf = [0.4864351851851852, 0.49199074074074073, 0.4947685185185185];
832 let rseci = [
833 [4054.881, 2748.195, 4074.237],
834 [3956.224, 2888.232, 4074.364],
835 [3905.073, 2956.935, 4074.430],
836 ];
837 assert_eq!(
838 gauss_angles(&decl, &rtasc, &jd, &jdf, &rseci).unwrap_err(),
839 IodError::NonFiniteValue
840 );
841 }
842}