1const VALLADO_MU: f64 = 398600.4415;
26const VALLADO_RE: f64 = 6378.1363;
29const VALLADO_TUSEC: f64 = 806.8109913067327;
32const DAY2SEC: f64 = 86400.0;
34const SMALL: f64 = 1e-10;
35const COPLANAR_TOL_RAD: f64 = 5.0 * std::f64::consts::PI / 180.0;
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
42pub enum IodError {
43 #[error("line-of-sight determinant too small")]
46 DeterminantTooSmall,
47 #[error("orbit determination not possible from the given geometry")]
50 OrbitNotPossible,
51 #[error("position vector has near-zero magnitude")]
54 ZeroVector,
55 #[error("position vectors are collinear")]
58 CollinearVectors,
59 #[error("position vectors are not coplanar")]
62 NotCoplanar,
63 #[error("observation times are equal or near-equal")]
66 InvalidTimeGeometry,
67 #[error("no positive real root for the slant-range polynomial")]
70 NoPositiveRoot,
71 #[error("slant-range root solver did not converge")]
75 RootSolveFailed,
76 #[error("non-finite value encountered")]
78 NonFiniteValue,
79}
80
81fn all_finite(a: &[f64; 3]) -> bool {
83 a.iter().all(|x| x.is_finite())
84}
85
86fn cross(a: &[f64; 3], b: &[f64; 3]) -> [f64; 3] {
87 [
88 a[1] * b[2] - a[2] * b[1],
89 a[2] * b[0] - a[0] * b[2],
90 a[0] * b[1] - a[1] * b[0],
91 ]
92}
93
94fn dot(a: &[f64; 3], b: &[f64; 3]) -> f64 {
95 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
96}
97
98fn mag(a: &[f64; 3]) -> f64 {
99 dot(a, a).sqrt()
100}
101
102fn unit(a: &[f64; 3]) -> [f64; 3] {
103 let m = mag(a);
104 [a[0] / m, a[1] / m, a[2] / m]
105}
106
107fn vadd(a: &[f64; 3], b: &[f64; 3]) -> [f64; 3] {
108 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
109}
110
111fn smul(s: f64, a: &[f64; 3]) -> [f64; 3] {
112 [s * a[0], s * a[1], s * a[2]]
113}
114
115fn det3(m: &[[f64; 3]; 3]) -> f64 {
117 m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
118 - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
119 + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0])
120}
121
122fn inv3(m: &[[f64; 3]; 3]) -> [[f64; 3]; 3] {
124 let d = det3(m);
125 let id = 1.0 / d;
126 [
127 [
128 id * (m[1][1] * m[2][2] - m[1][2] * m[2][1]),
129 id * (m[0][2] * m[2][1] - m[0][1] * m[2][2]),
130 id * (m[0][1] * m[1][2] - m[0][2] * m[1][1]),
131 ],
132 [
133 id * (m[1][2] * m[2][0] - m[1][0] * m[2][2]),
134 id * (m[0][0] * m[2][2] - m[0][2] * m[2][0]),
135 id * (m[0][2] * m[1][0] - m[0][0] * m[1][2]),
136 ],
137 [
138 id * (m[1][0] * m[2][1] - m[1][1] * m[2][0]),
139 id * (m[0][1] * m[2][0] - m[0][0] * m[2][1]),
140 id * (m[0][0] * m[1][1] - m[0][1] * m[1][0]),
141 ],
142 ]
143}
144
145fn mat3_vec3(m: &[[f64; 3]; 3], v: &[f64; 3]) -> [f64; 3] {
147 [
148 m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
149 m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
150 m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2],
151 ]
152}
153
154fn mat3_mat3(a: &[[f64; 3]; 3], b: &[[f64; 3]; 3]) -> [[f64; 3]; 3] {
156 let mut r = [[0.0; 3]; 3];
157 for i in 0..3 {
158 for j in 0..3 {
159 r[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j];
160 }
161 }
162 r
163}
164
165fn los(ra: f64, dec: f64) -> [f64; 3] {
167 [dec.cos() * ra.cos(), dec.cos() * ra.sin(), dec.sin()]
168}
169
170pub fn gibbs(
179 r1: &[f64; 3],
180 r2: &[f64; 3],
181 r3: &[f64; 3],
182) -> Result<([f64; 3], f64, f64, f64), IodError> {
183 if !all_finite(r1) || !all_finite(r2) || !all_finite(r3) {
186 return Err(IodError::NonFiniteValue);
187 }
188
189 let magr1 = mag(r1);
190 let magr2 = mag(r2);
191 let magr3 = mag(r3);
192
193 if !magr1.is_finite() || !magr2.is_finite() || !magr3.is_finite() {
197 return Err(IodError::NonFiniteValue);
198 }
199
200 if magr1 < SMALL || magr2 < SMALL || magr3 < SMALL {
201 return Err(IodError::ZeroVector);
202 }
203
204 let p = cross(r2, r3);
206 let q = cross(r3, r1);
207 let w = cross(r1, r2);
208
209 let magp = mag(&p);
214 if !magp.is_finite() {
215 return Err(IodError::NonFiniteValue);
216 }
217 if magp < SMALL {
218 return Err(IodError::CollinearVectors);
219 }
220
221 let copa = dot(&unit(&p), &unit(r1)).clamp(-1.0, 1.0).asin();
225 if copa.abs() > COPLANAR_TOL_RAD {
226 return Err(IodError::NotCoplanar);
227 }
228
229 let d = vadd(&vadd(&p, &q), &w);
231 let magd = mag(&d);
232
233 let n = vadd(&vadd(&smul(magr1, &p), &smul(magr2, &q)), &smul(magr3, &w));
235 let magn = mag(&n);
236
237 if !magd.is_finite() || !magn.is_finite() {
238 return Err(IodError::NonFiniteValue);
239 }
240 if magd < 1e-6 || magn < 1e-6 {
241 return Err(IodError::OrbitNotPossible);
242 }
243
244 let theta12 = (dot(r1, r2) / (magr1 * magr2)).clamp(-1.0, 1.0).acos();
246 let theta23 = (dot(r2, r3) / (magr2 * magr3)).clamp(-1.0, 1.0).acos();
247
248 let r1mr2 = magr1 - magr2;
250 let r3mr1 = magr3 - magr1;
251 let r2mr3 = magr2 - magr3;
252 let s = vadd(&vadd(&smul(r1mr2, r3), &smul(r3mr1, r2)), &smul(r2mr3, r1));
253
254 let b = cross(&d, r2);
256
257 let lg = (VALLADO_MU / (magd * magn)).sqrt();
259
260 let v2 = vadd(&smul(lg / magr2, &b), &smul(lg, &s));
262
263 if !all_finite(&v2) || !theta12.is_finite() || !theta23.is_finite() || !copa.is_finite() {
266 return Err(IodError::NonFiniteValue);
267 }
268
269 Ok((v2, theta12, theta23, copa))
270}
271
272pub fn hgibbs(
284 r1: &[f64; 3],
285 r2: &[f64; 3],
286 r3: &[f64; 3],
287 jd1: f64,
288 jd2: f64,
289 jd3: f64,
290) -> Result<([f64; 3], f64, f64, f64), IodError> {
291 if !all_finite(r1)
294 || !all_finite(r2)
295 || !all_finite(r3)
296 || !jd1.is_finite()
297 || !jd2.is_finite()
298 || !jd3.is_finite()
299 {
300 return Err(IodError::NonFiniteValue);
301 }
302
303 let magr1 = mag(r1);
304 let magr2 = mag(r2);
305 let magr3 = mag(r3);
306
307 if !magr1.is_finite() || !magr2.is_finite() || !magr3.is_finite() {
310 return Err(IodError::NonFiniteValue);
311 }
312
313 if magr1 < SMALL || magr2 < SMALL || magr3 < SMALL {
314 return Err(IodError::ZeroVector);
315 }
316
317 let dt21 = (jd2 - jd1) * DAY2SEC;
319 let dt31 = (jd3 - jd1) * DAY2SEC;
320 let dt32 = (jd3 - jd2) * DAY2SEC;
321
322 if dt21.abs() < SMALL || dt31.abs() < SMALL || dt32.abs() < SMALL {
324 return Err(IodError::InvalidTimeGeometry);
325 }
326
327 let p = cross(r2, r3);
330 let magp = mag(&p);
331 if !magp.is_finite() {
332 return Err(IodError::NonFiniteValue);
333 }
334 if magp < SMALL {
335 return Err(IodError::CollinearVectors);
336 }
337
338 let copa = dot(&unit(&p), &unit(r1)).clamp(-1.0, 1.0).asin();
342 if copa.abs() > COPLANAR_TOL_RAD {
343 return Err(IodError::NotCoplanar);
344 }
345
346 let theta12 = (dot(r1, r2) / (magr1 * magr2)).clamp(-1.0, 1.0).acos();
348 let theta23 = (dot(r2, r3) / (magr2 * magr3)).clamp(-1.0, 1.0).acos();
349
350 let term1 = smul(
352 -dt32 * (1.0 / (dt21 * dt31) + VALLADO_MU / (12.0 * magr1.powi(3))),
353 r1,
354 );
355 let term2 = smul(
356 (dt32 - dt21) * (1.0 / (dt21 * dt32) + VALLADO_MU / (12.0 * magr2.powi(3))),
357 r2,
358 );
359 let term3 = smul(
360 dt21 * (1.0 / (dt32 * dt31) + VALLADO_MU / (12.0 * magr3.powi(3))),
361 r3,
362 );
363
364 let v2 = vadd(&vadd(&term1, &term2), &term3);
365
366 if !all_finite(&v2) || !theta12.is_finite() || !theta23.is_finite() || !copa.is_finite() {
369 return Err(IodError::NonFiniteValue);
370 }
371
372 Ok((v2, theta12, theta23, copa))
373}
374
375fn halley_iteration(poly: &[f64; 9]) -> Option<f64> {
381 let mut bigr2c = 20000.0 / VALLADO_RE;
383 let mut bigr2 = 100.0;
384 let mut converged = false;
385
386 for _ in 0..15 {
387 if (bigr2 - bigr2c).abs() < 8e-5 {
388 converged = true;
389 break;
390 }
391 bigr2 = bigr2c;
392 let x = bigr2;
393 let f = x.powi(8) + poly[2] * x.powi(6) + poly[5] * x.powi(3) + poly[8];
394 let f1 = 8.0 * x.powi(7) + 6.0 * poly[2] * x.powi(5) + 3.0 * poly[5] * x.powi(2);
395 let f2 = 56.0 * x.powi(6) + 30.0 * poly[2] * x.powi(4) + 6.0 * poly[5] * x;
396 let denom = 2.0 * f1 * f1 - f * f2;
397 if denom.abs() < SMALL {
398 return None;
399 }
400 bigr2c = bigr2 - (2.0 * f * f1) / denom;
401 if !bigr2c.is_finite() {
402 return None;
403 }
404 }
405
406 if !converged {
411 converged = (bigr2 - bigr2c).abs() < 8e-5;
412 }
413
414 if !converged || !bigr2c.is_finite() {
415 return None;
416 }
417
418 let x = bigr2c;
422 let t0 = x.powi(8);
423 let t2 = poly[2] * x.powi(6);
424 let t5 = poly[5] * x.powi(3);
425 let t8 = poly[8];
426 let residual = t0 + t2 + t5 + t8;
427 let scale = t0.abs() + t2.abs() + t5.abs() + t8.abs();
428 if !residual.is_finite() || !scale.is_finite() || residual.abs() > 1e-9 * scale {
429 return None;
430 }
431
432 Some(bigr2c)
433}
434
435pub fn gauss_angles(
450 decl: &[f64; 3],
451 rtasc: &[f64; 3],
452 jd: &[f64; 3],
453 jdf: &[f64; 3],
454 rseci: &[[f64; 3]; 3],
455) -> Result<([f64; 3], [f64; 3]), IodError> {
456 if !decl.iter().all(|x| x.is_finite())
459 || !rtasc.iter().all(|x| x.is_finite())
460 || !jd.iter().all(|x| x.is_finite())
461 || !jdf.iter().all(|x| x.is_finite())
462 || !rseci.iter().all(all_finite)
463 {
464 return Err(IodError::NonFiniteValue);
465 }
466
467 let tau12 = ((jd[0] - jd[1]) + (jdf[0] - jdf[1])) * DAY2SEC;
469 let _tau13 = ((jd[0] - jd[2]) + (jdf[0] - jdf[2])) * DAY2SEC;
470 let tau32 = ((jd[2] - jd[1]) + (jdf[2] - jdf[1])) * DAY2SEC;
471
472 if tau12.abs() < SMALL || tau32.abs() < SMALL || (tau32 - tau12).abs() < SMALL {
475 return Err(IodError::InvalidTimeGeometry);
476 }
477
478 let l1 = los(rtasc[0], decl[0]);
480 let l2 = los(rtasc[1], decl[1]);
481 let l3 = los(rtasc[2], decl[2]);
482
483 let tau12c = tau12 / VALLADO_TUSEC;
485 let tau32c = tau32 / VALLADO_TUSEC;
486 let rseci1c = smul(1.0 / VALLADO_RE, &rseci[0]);
487 let rseci2c = smul(1.0 / VALLADO_RE, &rseci[1]);
488 let rseci3c = smul(1.0 / VALLADO_RE, &rseci[2]);
489
490 let lmat = [
492 [l1[0], l2[0], l3[0]],
493 [l1[1], l2[1], l3[1]],
494 [l1[2], l2[2], l3[2]],
495 ];
496
497 let d = det3(&lmat);
498 if d.abs() < SMALL {
499 return Err(IodError::DeterminantTooSmall);
500 }
501
502 let lmati = inv3(&lmat);
503
504 let rsmatc = [
506 [rseci1c[0], rseci2c[0], rseci3c[0]],
507 [rseci1c[1], rseci2c[1], rseci3c[1]],
508 [rseci1c[2], rseci2c[2], rseci3c[2]],
509 ];
510
511 let lir = mat3_mat3(&lmati, &rsmatc);
512
513 let a1 = tau32c / (tau32c - tau12c);
515 let a1u = (tau32c * ((tau32c - tau12c).powi(2) - tau32c.powi(2))) / (6.0 * (tau32c - tau12c));
516 let a3 = -tau12c / (tau32c - tau12c);
517 let a3u = -(tau12c * ((tau32c - tau12c).powi(2) - tau12c.powi(2))) / (6.0 * (tau32c - tau12c));
518
519 let d1c = lir[1][0] * a1 - lir[1][1] + lir[1][2] * a3;
520 let d2c = lir[1][0] * a1u + lir[1][2] * a3u;
521 let magrs2 = mag(&rseci2c);
522 let l2dotrs = dot(&l2, &rseci2c);
523
524 let mut poly = [0.0; 9];
526 poly[0] = 1.0;
527 poly[2] = -(d1c.powi(2) + 2.0 * d1c * l2dotrs + magrs2.powi(2));
528 poly[5] = -2.0 * (l2dotrs * d2c + d1c * d2c);
529 poly[8] = -(d2c.powi(2));
530
531 let bigr2c = match halley_iteration(&poly) {
536 Some(r) if r > 0.0 && r * VALLADO_RE <= 50000.0 => r,
537 Some(_) => return Err(IodError::NoPositiveRoot),
538 None => return Err(IodError::RootSolveFailed),
539 };
540
541 let bigr2 = bigr2c * VALLADO_RE;
542 let a1u_sec = a1u * VALLADO_TUSEC.powi(2);
543 let a3u_sec = a3u * VALLADO_TUSEC.powi(2);
544
545 let u = VALLADO_MU / bigr2.powi(3);
547 let c1 = a1 + a1u_sec * u;
548 let c2 = -1.0;
549 let c3 = a3 + a3u_sec * u;
550
551 if c1.abs() < SMALL || c3.abs() < SMALL {
554 return Err(IodError::OrbitNotPossible);
555 }
556
557 let rsmat = [
559 [rseci[0][0], rseci[1][0], rseci[2][0]],
560 [rseci[0][1], rseci[1][1], rseci[2][1]],
561 [rseci[0][2], rseci[1][2], rseci[2][2]],
562 ];
563 let lir_full = mat3_mat3(&lmati, &rsmat);
564 let cmat = [-c1, -c2, -c3];
565 let rhomat = mat3_vec3(&lir_full, &cmat);
566
567 let r1 = vadd(&smul(rhomat[0] / c1, &l1), &rseci[0]);
569 let r2 = vadd(&smul(rhomat[1] / c2, &l2), &rseci[1]);
570 let r3 = vadd(&smul(rhomat[2] / c3, &l3), &rseci[2]);
571
572 let (v2, _, _, _) = gibbs(&r1, &r2, &r3)?;
574
575 if !all_finite(&r2) || !all_finite(&v2) {
576 return Err(IodError::NonFiniteValue);
577 }
578
579 Ok((r2, v2))
580}
581
582#[cfg(test)]
583mod tests {
584 use super::*;
585 use std::f64::consts::PI;
586
587 fn assert_rel(actual: f64, expected: f64, label: &str) {
588 let rel = ((actual - expected) / expected).abs();
589 assert!(rel < 1e-12, "{label}: relative error {rel:e} exceeds 1e-12");
590 }
591
592 fn assert_ulp(actual: f64, expected: f64, max_ulps: i64, label: &str) {
595 if actual == expected {
596 return;
597 }
598 let a = actual.to_bits() as i64;
599 let b = expected.to_bits() as i64;
600 let ulps = (a - b).abs();
601 assert!(
602 ulps <= max_ulps,
603 "{label}: {ulps} ulps exceeds {max_ulps} (got {actual}, expected {expected})"
604 );
605 }
606
607 #[test]
608 fn gibbs_example_7_3() {
609 let r1 = [0.0, 0.0, 6378.1363];
610 let r2 = [0.0, -4464.696, -5102.509];
611 let r3 = [0.0, 5740.323, 3189.068];
612
613 let (v2, theta12, theta23, copa) = gibbs(&r1, &r2, &r3).unwrap();
614 assert_ulp(v2[0], 0.0, 0, "v2_x");
615 assert_ulp(v2[1], 5.5311472050176125, 0, "v2_y");
616 assert_ulp(v2[2], -5.191806413494606, 0, "v2_z");
617 assert_ulp(theta12 * 180.0 / PI, 138.81407085944375, 2, "theta12");
618 assert_ulp(theta23 * 180.0 / PI, 160.24053069723146, 2, "theta23");
619 assert_ulp(copa, 0.0, 0, "copa");
620 }
621
622 #[test]
623 fn hgibbs_example_7_4() {
624 let r1 = [3419.85564, 6019.82602, 2784.60022];
625 let r2 = [2935.91195, 6326.18324, 2660.59584];
626 let r3 = [2434.95202, 6597.38674, 2521.52311];
627 let jd1 = 0.0;
628 let jd2 = (60.0 + 16.48) / 86400.0;
629 let jd3 = (120.0 + 33.04) / 86400.0;
630
631 let (v2, theta12, theta23, _copa) = hgibbs(&r1, &r2, &r3, jd1, jd2, jd3).unwrap();
632 assert_ulp(v2[0], -6.441557227511062, 0, "v2_x");
633 assert_ulp(v2[1], 3.777559606719521, 0, "v2_y");
634 assert_ulp(v2[2], -1.7205675602414345, 0, "v2_z");
635 assert_ulp(theta12 * 180.0 / PI, 4.499996147374992, 2, "theta12");
636 assert_ulp(theta23 * 180.0 / PI, 4.499998402168982, 2, "theta23");
637 }
638
639 #[test]
640 fn gauss_example_7_2() {
641 let d2r = |d: f64| d * PI / 180.0;
642 let decl = [d2r(18.667717), d2r(35.664741), d2r(36.996583)];
643 let rtasc = [d2r(0.939913), d2r(45.025748), d2r(67.886655)];
644 let jd = [2_456_159.5, 2_456_159.5, 2_456_159.5];
645 let jdf = [0.4864351851851852, 0.49199074074074073, 0.4947685185185185];
646 let rseci = [
647 [4054.881, 2748.195, 4074.237],
648 [3956.224, 2888.232, 4074.364],
649 [3905.073, 2956.935, 4074.430],
650 ];
651
652 let (r2, v2) = gauss_angles(&decl, &rtasc, &jd, &jdf, &rseci).unwrap();
653 assert_rel(r2[0], 6313.378130210396, "r2_x");
654 assert_rel(r2[1], 5247.50563344895, "r2_y");
655 assert_rel(r2[2], 6467.707164431651, "r2_z");
656 assert_rel(v2[0], -4.185488280436629, "v2_x");
657 assert_rel(v2[1], 4.7884929168898145, "v2_y");
658 assert_rel(v2[2], 1.721714659663034, "v2_z");
659 }
660
661 #[test]
664 fn gibbs_rejects_zero_vector() {
665 let r2 = [0.0, -4464.696, -5102.509];
666 let r3 = [0.0, 5740.323, 3189.068];
667 assert_eq!(
668 gibbs(&[0.0, 0.0, 0.0], &r2, &r3).unwrap_err(),
669 IodError::ZeroVector
670 );
671 }
672
673 #[test]
674 fn gibbs_rejects_collinear_vectors() {
675 let r1 = [0.0, 0.0, 6378.1363];
677 let r2 = [0.0, 1000.0, 0.0];
678 let r3 = [0.0, 2000.0, 0.0];
679 assert_eq!(
680 gibbs(&r1, &r2, &r3).unwrap_err(),
681 IodError::CollinearVectors
682 );
683 }
684
685 #[test]
686 fn gibbs_rejects_noncoplanar_vectors() {
687 let r1 = [6378.1363, 6378.1363, 6378.1363];
689 let r2 = [0.0, -4464.696, -5102.509];
690 let r3 = [0.0, 5740.323, 3189.068];
691 assert_eq!(gibbs(&r1, &r2, &r3).unwrap_err(), IodError::NotCoplanar);
692 }
693
694 #[test]
695 fn gibbs_rejects_nonfinite_input() {
696 let r2 = [0.0, -4464.696, -5102.509];
697 let r3 = [0.0, 5740.323, 3189.068];
698 assert_eq!(
699 gibbs(&[f64::NAN, 0.0, 6378.1363], &r2, &r3).unwrap_err(),
700 IodError::NonFiniteValue
701 );
702 }
703
704 #[test]
705 fn gibbs_accepts_antiparallel_endpoints() {
706 let r1 = [7000.0, 0.0, 0.0];
711 let r2 = [0.0, 7000.0, 0.0];
712 let r3 = [-7000.0, 0.0, 0.0];
713 let (v2, _, _, copa) = gibbs(&r1, &r2, &r3).unwrap();
714 let vcirc = (VALLADO_MU / 7000.0).sqrt();
715 assert!((v2[0] + vcirc).abs() < 1e-9, "v2_x {} vs {}", v2[0], -vcirc);
716 assert!(v2[1].abs() < 1e-9 && v2[2].abs() < 1e-9);
717 assert!(copa.abs() < 1e-12);
718 }
719
720 #[test]
721 fn gibbs_rejects_overflowing_magnitude() {
722 let r1 = [1e160, 0.0, 0.0];
725 let r2 = [0.0, 1e160, 0.0];
726 let r3 = [0.0, 0.0, 1e160];
727 assert_eq!(gibbs(&r1, &r2, &r3).unwrap_err(), IodError::NonFiniteValue);
728 }
729
730 #[test]
731 fn halley_iteration_reports_stationary_nonroot() {
732 let x0 = 20000.0 / VALLADO_RE;
736 let mut poly = [0.0; 9];
737 poly[0] = 1.0;
738 poly[2] = -x0.powi(2);
739 poly[5] = -(2.0 / 3.0) * x0.powi(5);
740 poly[8] = -x0.powi(8) / 9.0;
741 assert_eq!(halley_iteration(&poly), None);
742 }
743
744 #[test]
745 fn halley_iteration_reports_nonconvergence() {
746 let poly = [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
751 assert_eq!(halley_iteration(&poly), None);
752 }
753
754 #[test]
755 fn hgibbs_rejects_equal_times() {
756 let r1 = [3419.85564, 6019.82602, 2784.60022];
757 let r2 = [2935.91195, 6326.18324, 2660.59584];
758 let r3 = [2434.95202, 6597.38674, 2521.52311];
759 let jd = 0.0;
761 assert_eq!(
762 hgibbs(&r1, &r2, &r3, jd, jd, (120.0 + 33.04) / 86400.0).unwrap_err(),
763 IodError::InvalidTimeGeometry
764 );
765 }
766
767 #[test]
768 fn hgibbs_rejects_zero_vector() {
769 let r2 = [2935.91195, 6326.18324, 2660.59584];
770 let r3 = [2434.95202, 6597.38674, 2521.52311];
771 let jd1 = 0.0;
772 let jd2 = (60.0 + 16.48) / 86400.0;
773 let jd3 = (120.0 + 33.04) / 86400.0;
774 assert_eq!(
775 hgibbs(&[0.0, 0.0, 0.0], &r2, &r3, jd1, jd2, jd3).unwrap_err(),
776 IodError::ZeroVector
777 );
778 }
779
780 #[test]
781 fn hgibbs_rejects_nonfinite_output() {
782 let r1 = [3419.85564, 6019.82602, 2784.60022];
786 let r2 = [2935.91195, 6326.18324, 2660.59584];
787 let r3 = [2434.95202, 6597.38674, 2521.52311];
788 let err = hgibbs(&r1, &r2, &r3, 0.0, 1e306, 2e306).unwrap_err();
789 assert_eq!(err, IodError::NonFiniteValue);
790 }
791
792 #[test]
793 fn gauss_rejects_equal_times() {
794 let d2r = |d: f64| d * PI / 180.0;
795 let decl = [d2r(18.667717), d2r(35.664741), d2r(36.996583)];
796 let rtasc = [d2r(0.939913), d2r(45.025748), d2r(67.886655)];
797 let jd = [2_456_159.5, 2_456_159.5, 2_456_159.5];
798 let jdf = [0.49199074074074073, 0.49199074074074073, 0.4947685185185185];
800 let rseci = [
801 [4054.881, 2748.195, 4074.237],
802 [3956.224, 2888.232, 4074.364],
803 [3905.073, 2956.935, 4074.430],
804 ];
805 assert_eq!(
806 gauss_angles(&decl, &rtasc, &jd, &jdf, &rseci).unwrap_err(),
807 IodError::InvalidTimeGeometry
808 );
809 }
810
811 #[test]
812 fn gauss_rejects_out_of_range_root() {
813 let d2r = |d: f64| d * PI / 180.0;
814 let decl = [d2r(18.667717), d2r(35.664741), d2r(36.996583)];
815 let rtasc = [d2r(0.939913), d2r(45.025748), d2r(67.886655)];
816 let jd = [2_456_159.5, 2_456_159.5, 2_456_159.5];
817 let rseci = [
818 [4054.881, 2748.195, 4074.237],
819 [3956.224, 2888.232, 4074.364],
820 [3905.073, 2956.935, 4074.430],
821 ];
822 let base = [0.4864351851851852, 0.49199074074074073, 0.4947685185185185];
825 let mid = base[1];
826 let jdf = [
827 mid + (base[0] - mid) * 100.0,
828 mid,
829 mid + (base[2] - mid) * 100.0,
830 ];
831 assert_eq!(
832 gauss_angles(&decl, &rtasc, &jd, &jdf, &rseci).unwrap_err(),
833 IodError::NoPositiveRoot
834 );
835 }
836
837 #[test]
838 fn gauss_rejects_nonfinite_input() {
839 let d2r = |d: f64| d * PI / 180.0;
840 let decl = [f64::NAN, d2r(35.664741), d2r(36.996583)];
841 let rtasc = [d2r(0.939913), d2r(45.025748), d2r(67.886655)];
842 let jd = [2_456_159.5, 2_456_159.5, 2_456_159.5];
843 let jdf = [0.4864351851851852, 0.49199074074074073, 0.4947685185185185];
844 let rseci = [
845 [4054.881, 2748.195, 4074.237],
846 [3956.224, 2888.232, 4074.364],
847 [3905.073, 2956.935, 4074.430],
848 ];
849 assert_eq!(
850 gauss_angles(&decl, &rtasc, &jd, &jdf, &rseci).unwrap_err(),
851 IodError::NonFiniteValue
852 );
853 }
854}