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::mat3::mul_vec3;
36use crate::astro::math::vec3;
37use crate::constants::SECONDS_PER_DAY as DAY2SEC;
38const SMALL: f64 = 1e-10;
39const COPLANAR_TOL_RAD: f64 = 5.0 * std::f64::consts::PI / 180.0;
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
46pub enum IodError {
47 #[error("line-of-sight determinant too small")]
50 DeterminantTooSmall,
51 #[error("orbit determination not possible from the given geometry")]
54 OrbitNotPossible,
55 #[error("position vector has near-zero magnitude")]
58 ZeroVector,
59 #[error("position vectors are collinear")]
62 CollinearVectors,
63 #[error("position vectors are not coplanar")]
66 NotCoplanar,
67 #[error("observation times are equal or near-equal")]
70 InvalidTimeGeometry,
71 #[error("no positive real root for the slant-range polynomial")]
74 NoPositiveRoot,
75 #[error("slant-range root solver did not converge")]
79 RootSolveFailed,
80 #[error("non-finite value encountered")]
82 NonFiniteValue,
83}
84
85fn all_finite(a: &[f64; 3]) -> bool {
87 a.iter().all(|x| x.is_finite())
88}
89
90fn cross(a: &[f64; 3], b: &[f64; 3]) -> [f64; 3] {
91 vec3::cross3_ref(a, b)
92}
93
94fn dot(a: &[f64; 3], b: &[f64; 3]) -> f64 {
95 vec3::dot3_ref(a, b)
96}
97
98fn mag(a: &[f64; 3]) -> f64 {
99 vec3::norm3_ref(a)
100}
101
102fn unit(a: &[f64; 3]) -> [f64; 3] {
103 vec3::unit3_ref_unchecked(a)
104}
105
106fn vadd(a: &[f64; 3], b: &[f64; 3]) -> [f64; 3] {
111 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
112}
113
114fn smul(s: f64, a: &[f64; 3]) -> [f64; 3] {
117 vec3::scale3(*a, s)
118}
119
120fn det3(m: &[[f64; 3]; 3]) -> f64 {
122 m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
123 - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
124 + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0])
125}
126
127fn mat3_mat3(a: &[[f64; 3]; 3], b: &[[f64; 3]; 3]) -> [[f64; 3]; 3] {
132 let mut r = [[0.0; 3]; 3];
133 for i in 0..3 {
134 for j in 0..3 {
135 r[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j];
136 }
137 }
138 r
139}
140
141fn los(ra: f64, dec: f64) -> [f64; 3] {
143 [
144 libm::cos(dec) * libm::cos(ra),
145 libm::cos(dec) * libm::sin(ra),
146 libm::sin(dec),
147 ]
148}
149
150pub fn gibbs(
159 r1: &[f64; 3],
160 r2: &[f64; 3],
161 r3: &[f64; 3],
162) -> Result<([f64; 3], f64, f64, f64), IodError> {
163 if !all_finite(r1) || !all_finite(r2) || !all_finite(r3) {
166 return Err(IodError::NonFiniteValue);
167 }
168
169 let magr1 = mag(r1);
170 let magr2 = mag(r2);
171 let magr3 = mag(r3);
172
173 if !magr1.is_finite() || !magr2.is_finite() || !magr3.is_finite() {
177 return Err(IodError::NonFiniteValue);
178 }
179
180 if magr1 < SMALL || magr2 < SMALL || magr3 < SMALL {
181 return Err(IodError::ZeroVector);
182 }
183
184 let p = cross(r2, r3);
186 let q = cross(r3, r1);
187 let w = cross(r1, r2);
188
189 let magp = mag(&p);
194 if !magp.is_finite() {
195 return Err(IodError::NonFiniteValue);
196 }
197 if magp < SMALL {
198 return Err(IodError::CollinearVectors);
199 }
200
201 let copa = libm::asin(dot(&unit(&p), &unit(r1)).clamp(-1.0, 1.0));
205 if copa.abs() > COPLANAR_TOL_RAD {
206 return Err(IodError::NotCoplanar);
207 }
208
209 let d = vadd(&vadd(&p, &q), &w);
211 let magd = mag(&d);
212
213 let n = vadd(&vadd(&smul(magr1, &p), &smul(magr2, &q)), &smul(magr3, &w));
215 let magn = mag(&n);
216
217 if !magd.is_finite() || !magn.is_finite() {
218 return Err(IodError::NonFiniteValue);
219 }
220 if magd < 1e-6 || magn < 1e-6 {
221 return Err(IodError::OrbitNotPossible);
222 }
223
224 let theta12 = libm::acos((dot(r1, r2) / (magr1 * magr2)).clamp(-1.0, 1.0));
226 let theta23 = libm::acos((dot(r2, r3) / (magr2 * magr3)).clamp(-1.0, 1.0));
227
228 let r1mr2 = magr1 - magr2;
230 let r3mr1 = magr3 - magr1;
231 let r2mr3 = magr2 - magr3;
232 let s = vadd(&vadd(&smul(r1mr2, r3), &smul(r3mr1, r2)), &smul(r2mr3, r1));
233
234 let b = cross(&d, r2);
236
237 let lg = (VALLADO_MU / (magd * magn)).sqrt();
239
240 let v2 = vadd(&smul(lg / magr2, &b), &smul(lg, &s));
242
243 if !all_finite(&v2) || !theta12.is_finite() || !theta23.is_finite() || !copa.is_finite() {
246 return Err(IodError::NonFiniteValue);
247 }
248
249 Ok((v2, theta12, theta23, copa))
250}
251
252pub fn hgibbs(
264 r1: &[f64; 3],
265 r2: &[f64; 3],
266 r3: &[f64; 3],
267 jd1: f64,
268 jd2: f64,
269 jd3: f64,
270) -> Result<([f64; 3], f64, f64, f64), IodError> {
271 if !all_finite(r1)
274 || !all_finite(r2)
275 || !all_finite(r3)
276 || !jd1.is_finite()
277 || !jd2.is_finite()
278 || !jd3.is_finite()
279 {
280 return Err(IodError::NonFiniteValue);
281 }
282
283 let magr1 = mag(r1);
284 let magr2 = mag(r2);
285 let magr3 = mag(r3);
286
287 if !magr1.is_finite() || !magr2.is_finite() || !magr3.is_finite() {
290 return Err(IodError::NonFiniteValue);
291 }
292
293 if magr1 < SMALL || magr2 < SMALL || magr3 < SMALL {
294 return Err(IodError::ZeroVector);
295 }
296
297 let dt21 = (jd2 - jd1) * DAY2SEC;
299 let dt31 = (jd3 - jd1) * DAY2SEC;
300 let dt32 = (jd3 - jd2) * DAY2SEC;
301
302 if dt21.abs() < SMALL || dt31.abs() < SMALL || dt32.abs() < SMALL {
304 return Err(IodError::InvalidTimeGeometry);
305 }
306
307 let p = cross(r2, r3);
310 let magp = mag(&p);
311 if !magp.is_finite() {
312 return Err(IodError::NonFiniteValue);
313 }
314 if magp < SMALL {
315 return Err(IodError::CollinearVectors);
316 }
317
318 let copa = libm::asin(dot(&unit(&p), &unit(r1)).clamp(-1.0, 1.0));
322 if copa.abs() > COPLANAR_TOL_RAD {
323 return Err(IodError::NotCoplanar);
324 }
325
326 let theta12 = libm::acos((dot(r1, r2) / (magr1 * magr2)).clamp(-1.0, 1.0));
328 let theta23 = libm::acos((dot(r2, r3) / (magr2 * magr3)).clamp(-1.0, 1.0));
329
330 let term1 = smul(
332 -dt32 * (1.0 / (dt21 * dt31) + VALLADO_MU / (12.0 * magr1.powi(3))),
333 r1,
334 );
335 let term2 = smul(
336 (dt32 - dt21) * (1.0 / (dt21 * dt32) + VALLADO_MU / (12.0 * magr2.powi(3))),
337 r2,
338 );
339 let term3 = smul(
340 dt21 * (1.0 / (dt32 * dt31) + VALLADO_MU / (12.0 * magr3.powi(3))),
341 r3,
342 );
343
344 let v2 = vadd(&vadd(&term1, &term2), &term3);
345
346 if !all_finite(&v2) || !theta12.is_finite() || !theta23.is_finite() || !copa.is_finite() {
349 return Err(IodError::NonFiniteValue);
350 }
351
352 Ok((v2, theta12, theta23, copa))
353}
354
355fn halley_iteration(poly: &[f64; 9]) -> Option<f64> {
361 let mut bigr2c = 20000.0 / VALLADO_RE;
363 let mut bigr2 = 100.0;
364 let mut converged = false;
365
366 for _ in 0..15 {
367 if (bigr2 - bigr2c).abs() < 8e-5 {
368 converged = true;
369 break;
370 }
371 bigr2 = bigr2c;
372 let x = bigr2;
373 let f = x.powi(8) + poly[2] * x.powi(6) + poly[5] * x.powi(3) + poly[8];
374 let f1 = 8.0 * x.powi(7) + 6.0 * poly[2] * x.powi(5) + 3.0 * poly[5] * x.powi(2);
375 let f2 = 56.0 * x.powi(6) + 30.0 * poly[2] * x.powi(4) + 6.0 * poly[5] * x;
376 let denom = 2.0 * f1 * f1 - f * f2;
377 if denom.abs() < SMALL {
378 return None;
379 }
380 bigr2c = bigr2 - (2.0 * f * f1) / denom;
381 if !bigr2c.is_finite() {
382 return None;
383 }
384 }
385
386 if !converged {
391 converged = (bigr2 - bigr2c).abs() < 8e-5;
392 }
393
394 if !converged || !bigr2c.is_finite() {
395 return None;
396 }
397
398 let x = bigr2c;
402 let t0 = x.powi(8);
403 let t2 = poly[2] * x.powi(6);
404 let t5 = poly[5] * x.powi(3);
405 let t8 = poly[8];
406 let residual = t0 + t2 + t5 + t8;
407 let scale = t0.abs() + t2.abs() + t5.abs() + t8.abs();
408 if !residual.is_finite() || !scale.is_finite() || residual.abs() > 1e-9 * scale {
409 return None;
410 }
411
412 Some(bigr2c)
413}
414
415pub fn gauss_angles(
430 decl: &[f64; 3],
431 rtasc: &[f64; 3],
432 jd: &[f64; 3],
433 jdf: &[f64; 3],
434 rseci: &[[f64; 3]; 3],
435) -> Result<([f64; 3], [f64; 3]), IodError> {
436 if !decl.iter().all(|x| x.is_finite())
439 || !rtasc.iter().all(|x| x.is_finite())
440 || !jd.iter().all(|x| x.is_finite())
441 || !jdf.iter().all(|x| x.is_finite())
442 || !rseci.iter().all(all_finite)
443 {
444 return Err(IodError::NonFiniteValue);
445 }
446
447 let tau12 = ((jd[0] - jd[1]) + (jdf[0] - jdf[1])) * DAY2SEC;
449 let _tau13 = ((jd[0] - jd[2]) + (jdf[0] - jdf[2])) * DAY2SEC;
450 let tau32 = ((jd[2] - jd[1]) + (jdf[2] - jdf[1])) * DAY2SEC;
451
452 if tau12.abs() < SMALL || tau32.abs() < SMALL || (tau32 - tau12).abs() < SMALL {
455 return Err(IodError::InvalidTimeGeometry);
456 }
457
458 let l1 = los(rtasc[0], decl[0]);
460 let l2 = los(rtasc[1], decl[1]);
461 let l3 = los(rtasc[2], decl[2]);
462
463 let tau12c = tau12 / VALLADO_TUSEC;
465 let tau32c = tau32 / VALLADO_TUSEC;
466 let rseci1c = smul(1.0 / VALLADO_RE, &rseci[0]);
467 let rseci2c = smul(1.0 / VALLADO_RE, &rseci[1]);
468 let rseci3c = smul(1.0 / VALLADO_RE, &rseci[2]);
469
470 let lmat = [
472 [l1[0], l2[0], l3[0]],
473 [l1[1], l2[1], l3[1]],
474 [l1[2], l2[2], l3[2]],
475 ];
476
477 let d = det3(&lmat);
478 if d.abs() < SMALL {
479 return Err(IodError::DeterminantTooSmall);
480 }
481
482 let lmati = invert_3x3_adjugate(&lmat).ok_or(IodError::DeterminantTooSmall)?;
487
488 let rsmatc = [
490 [rseci1c[0], rseci2c[0], rseci3c[0]],
491 [rseci1c[1], rseci2c[1], rseci3c[1]],
492 [rseci1c[2], rseci2c[2], rseci3c[2]],
493 ];
494
495 let lir = mat3_mat3(&lmati, &rsmatc);
496
497 let a1 = tau32c / (tau32c - tau12c);
499 let a1u = (tau32c * ((tau32c - tau12c).powi(2) - tau32c.powi(2))) / (6.0 * (tau32c - tau12c));
500 let a3 = -tau12c / (tau32c - tau12c);
501 let a3u = -(tau12c * ((tau32c - tau12c).powi(2) - tau12c.powi(2))) / (6.0 * (tau32c - tau12c));
502
503 let d1c = lir[1][0] * a1 - lir[1][1] + lir[1][2] * a3;
504 let d2c = lir[1][0] * a1u + lir[1][2] * a3u;
505 let magrs2 = mag(&rseci2c);
506 let l2dotrs = dot(&l2, &rseci2c);
507
508 let mut poly = [0.0; 9];
510 poly[0] = 1.0;
511 poly[2] = -(d1c.powi(2) + 2.0 * d1c * l2dotrs + magrs2.powi(2));
512 poly[5] = -2.0 * (l2dotrs * d2c + d1c * d2c);
513 poly[8] = -(d2c.powi(2));
514
515 let bigr2c = match halley_iteration(&poly) {
520 Some(r) if r > 0.0 && r * VALLADO_RE <= 50000.0 => r,
521 Some(_) => return Err(IodError::NoPositiveRoot),
522 None => return Err(IodError::RootSolveFailed),
523 };
524
525 let bigr2 = bigr2c * VALLADO_RE;
526 let a1u_sec = a1u * VALLADO_TUSEC.powi(2);
527 let a3u_sec = a3u * VALLADO_TUSEC.powi(2);
528
529 let u = VALLADO_MU / bigr2.powi(3);
531 let c1 = a1 + a1u_sec * u;
532 let c2 = -1.0;
533 let c3 = a3 + a3u_sec * u;
534
535 if c1.abs() < SMALL || c3.abs() < SMALL {
538 return Err(IodError::OrbitNotPossible);
539 }
540
541 let rsmat = [
543 [rseci[0][0], rseci[1][0], rseci[2][0]],
544 [rseci[0][1], rseci[1][1], rseci[2][1]],
545 [rseci[0][2], rseci[1][2], rseci[2][2]],
546 ];
547 let lir_full = mat3_mat3(&lmati, &rsmat);
548 let cmat = [-c1, -c2, -c3];
549 let rhomat = mul_vec3(&lir_full, cmat);
550
551 let r1 = vadd(&smul(rhomat[0] / c1, &l1), &rseci[0]);
553 let r2 = vadd(&smul(rhomat[1] / c2, &l2), &rseci[1]);
554 let r3 = vadd(&smul(rhomat[2] / c3, &l3), &rseci[2]);
555
556 let (v2, _, _, _) = gibbs(&r1, &r2, &r3)?;
558
559 if !all_finite(&r2) || !all_finite(&v2) {
560 return Err(IodError::NonFiniteValue);
561 }
562
563 Ok((r2, v2))
564}
565
566#[cfg(test)]
567mod tests {
568 use super::*;
569 use std::f64::consts::PI;
570
571 fn assert_rel(actual: f64, expected: f64, label: &str) {
572 let rel = ((actual - expected) / expected).abs();
573 assert!(rel < 1e-12, "{label}: relative error {rel:e} exceeds 1e-12");
574 }
575
576 fn assert_ulp(actual: f64, expected: f64, max_ulps: i64, label: &str) {
579 if actual == expected {
580 return;
581 }
582 let a = actual.to_bits() as i64;
583 let b = expected.to_bits() as i64;
584 let ulps = (a - b).abs();
585 assert!(
586 ulps <= max_ulps,
587 "{label}: {ulps} ulps exceeds {max_ulps} (got {actual}, expected {expected})"
588 );
589 }
590
591 #[test]
592 fn gibbs_example_7_3() {
593 let r1 = [0.0, 0.0, 6378.1363];
594 let r2 = [0.0, -4464.696, -5102.509];
595 let r3 = [0.0, 5740.323, 3189.068];
596
597 let (v2, theta12, theta23, copa) = gibbs(&r1, &r2, &r3).unwrap();
598 assert_ulp(v2[0], 0.0, 0, "v2_x");
599 assert_ulp(v2[1], 5.5311472050176125, 0, "v2_y");
600 assert_ulp(v2[2], -5.191806413494606, 0, "v2_z");
601 assert_ulp(theta12 * 180.0 / PI, 138.81407085944375, 2, "theta12");
602 assert_ulp(theta23 * 180.0 / PI, 160.24053069723146, 2, "theta23");
603 assert_ulp(copa, 0.0, 0, "copa");
604 }
605
606 #[test]
607 fn hgibbs_example_7_4() {
608 let r1 = [3419.85564, 6019.82602, 2784.60022];
609 let r2 = [2935.91195, 6326.18324, 2660.59584];
610 let r3 = [2434.95202, 6597.38674, 2521.52311];
611 let jd1 = 0.0;
612 let jd2 = (60.0 + 16.48) / crate::constants::SECONDS_PER_DAY;
613 let jd3 = (120.0 + 33.04) / crate::constants::SECONDS_PER_DAY;
614
615 let (v2, theta12, theta23, _copa) = hgibbs(&r1, &r2, &r3, jd1, jd2, jd3).unwrap();
616 assert_ulp(v2[0], -6.441557227511062, 0, "v2_x");
617 assert_ulp(v2[1], 3.777559606719521, 0, "v2_y");
618 assert_ulp(v2[2], -1.7205675602414345, 0, "v2_z");
619 assert_ulp(theta12 * 180.0 / PI, 4.499996147374992, 2, "theta12");
620 assert_ulp(theta23 * 180.0 / PI, 4.499998402168982, 2, "theta23");
621 }
622
623 #[test]
624 fn gauss_example_7_2() {
625 let d2r = |d: f64| d * PI / 180.0;
626 let decl = [d2r(18.667717), d2r(35.664741), d2r(36.996583)];
627 let rtasc = [d2r(0.939913), d2r(45.025748), d2r(67.886655)];
628 let jd = [2_456_159.5, 2_456_159.5, 2_456_159.5];
629 let jdf = [0.4864351851851852, 0.49199074074074073, 0.4947685185185185];
630 let rseci = [
631 [4054.881, 2748.195, 4074.237],
632 [3956.224, 2888.232, 4074.364],
633 [3905.073, 2956.935, 4074.430],
634 ];
635
636 let (r2, v2) = gauss_angles(&decl, &rtasc, &jd, &jdf, &rseci).unwrap();
637 assert_rel(r2[0], 6313.378130210396, "r2_x");
638 assert_rel(r2[1], 5247.50563344895, "r2_y");
639 assert_rel(r2[2], 6467.707164431651, "r2_z");
640 assert_rel(v2[0], -4.185488280436629, "v2_x");
641 assert_rel(v2[1], 4.7884929168898145, "v2_y");
642 assert_rel(v2[2], 1.721714659663034, "v2_z");
643 }
644
645 #[test]
648 fn gibbs_rejects_zero_vector() {
649 let r2 = [0.0, -4464.696, -5102.509];
650 let r3 = [0.0, 5740.323, 3189.068];
651 assert_eq!(
652 gibbs(&[0.0, 0.0, 0.0], &r2, &r3).unwrap_err(),
653 IodError::ZeroVector
654 );
655 }
656
657 #[test]
658 fn gibbs_rejects_collinear_vectors() {
659 let r1 = [0.0, 0.0, 6378.1363];
661 let r2 = [0.0, 1000.0, 0.0];
662 let r3 = [0.0, 2000.0, 0.0];
663 assert_eq!(
664 gibbs(&r1, &r2, &r3).unwrap_err(),
665 IodError::CollinearVectors
666 );
667 }
668
669 #[test]
670 fn gibbs_rejects_noncoplanar_vectors() {
671 let r1 = [6378.1363, 6378.1363, 6378.1363];
673 let r2 = [0.0, -4464.696, -5102.509];
674 let r3 = [0.0, 5740.323, 3189.068];
675 assert_eq!(gibbs(&r1, &r2, &r3).unwrap_err(), IodError::NotCoplanar);
676 }
677
678 #[test]
679 fn gibbs_rejects_nonfinite_input() {
680 let r2 = [0.0, -4464.696, -5102.509];
681 let r3 = [0.0, 5740.323, 3189.068];
682 assert_eq!(
683 gibbs(&[f64::NAN, 0.0, 6378.1363], &r2, &r3).unwrap_err(),
684 IodError::NonFiniteValue
685 );
686 }
687
688 #[test]
689 fn gibbs_accepts_antiparallel_endpoints() {
690 let r1 = [7000.0, 0.0, 0.0];
695 let r2 = [0.0, 7000.0, 0.0];
696 let r3 = [-7000.0, 0.0, 0.0];
697 let (v2, _, _, copa) = gibbs(&r1, &r2, &r3).unwrap();
698 let vcirc = (VALLADO_MU / 7000.0).sqrt();
699 assert!((v2[0] + vcirc).abs() < 1e-9, "v2_x {} vs {}", v2[0], -vcirc);
700 assert!(v2[1].abs() < 1e-9 && v2[2].abs() < 1e-9);
701 assert!(copa.abs() < 1e-12);
702 }
703
704 #[test]
705 fn gibbs_rejects_overflowing_magnitude() {
706 let r1 = [1e160, 0.0, 0.0];
709 let r2 = [0.0, 1e160, 0.0];
710 let r3 = [0.0, 0.0, 1e160];
711 assert_eq!(gibbs(&r1, &r2, &r3).unwrap_err(), IodError::NonFiniteValue);
712 }
713
714 #[test]
715 fn halley_iteration_reports_stationary_nonroot() {
716 let x0 = 20000.0 / VALLADO_RE;
720 let mut poly = [0.0; 9];
721 poly[0] = 1.0;
722 poly[2] = -x0.powi(2);
723 poly[5] = -(2.0 / 3.0) * x0.powi(5);
724 poly[8] = -x0.powi(8) / 9.0;
725 assert_eq!(halley_iteration(&poly), None);
726 }
727
728 #[test]
729 fn halley_iteration_reports_nonconvergence() {
730 let poly = [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
735 assert_eq!(halley_iteration(&poly), None);
736 }
737
738 #[test]
739 fn hgibbs_rejects_equal_times() {
740 let r1 = [3419.85564, 6019.82602, 2784.60022];
741 let r2 = [2935.91195, 6326.18324, 2660.59584];
742 let r3 = [2434.95202, 6597.38674, 2521.52311];
743 let jd = 0.0;
745 assert_eq!(
746 hgibbs(
747 &r1,
748 &r2,
749 &r3,
750 jd,
751 jd,
752 (120.0 + 33.04) / crate::constants::SECONDS_PER_DAY
753 )
754 .unwrap_err(),
755 IodError::InvalidTimeGeometry
756 );
757 }
758
759 #[test]
760 fn hgibbs_rejects_zero_vector() {
761 let r2 = [2935.91195, 6326.18324, 2660.59584];
762 let r3 = [2434.95202, 6597.38674, 2521.52311];
763 let jd1 = 0.0;
764 let jd2 = (60.0 + 16.48) / crate::constants::SECONDS_PER_DAY;
765 let jd3 = (120.0 + 33.04) / crate::constants::SECONDS_PER_DAY;
766 assert_eq!(
767 hgibbs(&[0.0, 0.0, 0.0], &r2, &r3, jd1, jd2, jd3).unwrap_err(),
768 IodError::ZeroVector
769 );
770 }
771
772 #[test]
773 fn hgibbs_rejects_nonfinite_output() {
774 let r1 = [3419.85564, 6019.82602, 2784.60022];
778 let r2 = [2935.91195, 6326.18324, 2660.59584];
779 let r3 = [2434.95202, 6597.38674, 2521.52311];
780 let err = hgibbs(&r1, &r2, &r3, 0.0, 1e306, 2e306).unwrap_err();
781 assert_eq!(err, IodError::NonFiniteValue);
782 }
783
784 #[test]
785 fn gauss_rejects_equal_times() {
786 let d2r = |d: f64| d * PI / 180.0;
787 let decl = [d2r(18.667717), d2r(35.664741), d2r(36.996583)];
788 let rtasc = [d2r(0.939913), d2r(45.025748), d2r(67.886655)];
789 let jd = [2_456_159.5, 2_456_159.5, 2_456_159.5];
790 let jdf = [0.49199074074074073, 0.49199074074074073, 0.4947685185185185];
792 let rseci = [
793 [4054.881, 2748.195, 4074.237],
794 [3956.224, 2888.232, 4074.364],
795 [3905.073, 2956.935, 4074.430],
796 ];
797 assert_eq!(
798 gauss_angles(&decl, &rtasc, &jd, &jdf, &rseci).unwrap_err(),
799 IodError::InvalidTimeGeometry
800 );
801 }
802
803 #[test]
804 fn gauss_rejects_out_of_range_root() {
805 let d2r = |d: f64| d * PI / 180.0;
806 let decl = [d2r(18.667717), d2r(35.664741), d2r(36.996583)];
807 let rtasc = [d2r(0.939913), d2r(45.025748), d2r(67.886655)];
808 let jd = [2_456_159.5, 2_456_159.5, 2_456_159.5];
809 let rseci = [
810 [4054.881, 2748.195, 4074.237],
811 [3956.224, 2888.232, 4074.364],
812 [3905.073, 2956.935, 4074.430],
813 ];
814 let base = [0.4864351851851852, 0.49199074074074073, 0.4947685185185185];
817 let mid = base[1];
818 let jdf = [
819 mid + (base[0] - mid) * 100.0,
820 mid,
821 mid + (base[2] - mid) * 100.0,
822 ];
823 assert_eq!(
824 gauss_angles(&decl, &rtasc, &jd, &jdf, &rseci).unwrap_err(),
825 IodError::NoPositiveRoot
826 );
827 }
828
829 #[test]
830 fn gauss_rejects_nonfinite_input() {
831 let d2r = |d: f64| d * PI / 180.0;
832 let decl = [f64::NAN, d2r(35.664741), d2r(36.996583)];
833 let rtasc = [d2r(0.939913), d2r(45.025748), d2r(67.886655)];
834 let jd = [2_456_159.5, 2_456_159.5, 2_456_159.5];
835 let jdf = [0.4864351851851852, 0.49199074074074073, 0.4947685185185185];
836 let rseci = [
837 [4054.881, 2748.195, 4074.237],
838 [3956.224, 2888.232, 4074.364],
839 [3905.073, 2956.935, 4074.430],
840 ];
841 assert_eq!(
842 gauss_angles(&decl, &rtasc, &jd, &jdf, &rseci).unwrap_err(),
843 IodError::NonFiniteValue
844 );
845 }
846}