1use std::f64::consts::PI;
22
23const VALLADO_MU: f64 = 398600.4415;
26const TWOPI: f64 = 2.0 * PI;
27const SMALL: f64 = 1e-10;
28
29fn cross(a: &[f64; 3], b: &[f64; 3]) -> [f64; 3] {
30 [
31 a[1] * b[2] - a[2] * b[1],
32 a[2] * b[0] - a[0] * b[2],
33 a[0] * b[1] - a[1] * b[0],
34 ]
35}
36
37fn dot(a: &[f64; 3], b: &[f64; 3]) -> f64 {
38 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
39}
40
41fn mag(a: &[f64; 3]) -> f64 {
42 dot(a, a).sqrt()
43}
44
45fn smul(s: f64, a: &[f64; 3]) -> [f64; 3] {
46 [s * a[0], s * a[1], s * a[2]]
47}
48
49fn vadd(a: &[f64; 3], b: &[f64; 3]) -> [f64; 3] {
50 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
51}
52
53fn seebatt(v: f64) -> f64 {
55 let c = [
56 9.0 / 7.0,
57 16.0 / 63.0,
58 25.0 / 99.0,
59 36.0 / 143.0,
60 49.0 / 195.0,
61 64.0 / 255.0,
62 81.0 / 323.0,
63 100.0 / 399.0,
64 121.0 / 483.0,
65 144.0 / 575.0,
66 169.0 / 675.0,
67 196.0 / 783.0,
68 225.0 / 899.0,
69 256.0 / 1023.0,
70 289.0 / 1155.0,
71 324.0 / 1295.0,
72 361.0 / 1443.0,
73 400.0 / 1599.0,
74 441.0 / 1763.0,
75 484.0 / 1935.0,
76 ];
77
78 let sqrtopv = (1.0 + v).sqrt();
79 let eta = v / (1.0 + sqrtopv).powi(2);
80
81 let mut term2 = 1.0 + c[19] * eta;
82 for j in (0..19).rev() {
83 term2 = 1.0 + c[j] * eta / term2;
84 }
85
86 8.0 * (1.0 + sqrtopv) / (3.0 + 1.0 / (5.0 + eta + (9.0 / 7.0) * eta / term2))
87}
88
89fn kbatt(v: f64) -> f64 {
91 let d = [
92 1.0 / 3.0,
93 4.0 / 27.0,
94 8.0 / 27.0,
95 2.0 / 9.0,
96 22.0 / 81.0,
97 208.0 / 891.0,
98 340.0 / 1287.0,
99 418.0 / 1755.0,
100 598.0 / 2295.0,
101 700.0 / 2907.0,
102 928.0 / 3591.0,
103 1054.0 / 4347.0,
104 1330.0 / 5175.0,
105 1480.0 / 6075.0,
106 1804.0 / 7047.0,
107 1978.0 / 8091.0,
108 2350.0 / 9207.0,
109 2548.0 / 10395.0,
110 2968.0 / 11655.0,
111 3190.0 / 12987.0,
112 3658.0 / 14391.0,
113 ];
114
115 let mut sum1: f64 = d[0];
117 let mut delold: f64 = 1.0;
118 let mut termold: f64 = d[0];
119 let ktr = 21;
120
121 for di in d.iter().take(ktr).skip(1) {
122 if termold.abs() <= 1e-8 {
123 break;
124 }
125 let del = 1.0 / (1.0 + di * v * delold);
126 let term = termold * (del - 1.0);
127 sum1 += term;
128 delold = del;
129 termold = term;
130 }
131 let _ = sum1; let mut term2 = 1.0 + d[ktr - 1] * v;
135 for i in 0..(ktr - 2) {
136 let sum2 = d[ktr - i - 2] * v / term2;
137 term2 = 1.0 + sum2;
138 }
139
140 d[0] / term2
141}
142
143fn hodograph(
145 r1: &[f64; 3],
146 r2: &[f64; 3],
147 v1: &[f64; 3],
148 p: f64,
149 ecc: f64,
150 dnu: f64,
151 dtsec: f64,
152) -> ([f64; 3], [f64; 3]) {
153 let magr1 = mag(r1);
154 let magr2 = mag(r2);
155
156 let a = VALLADO_MU * (1.0 / magr1 - 1.0 / p);
157 let b = (VALLADO_MU * ecc / p).powi(2) - a * a;
158 let x1_abs = if b <= 0.0 { 0.0 } else { b.sqrt() };
159 let mut x1 = -x1_abs;
160
161 let nvec;
162 if dnu.sin().abs() < SMALL {
163 let cp = cross(r1, v1);
165 let ncp = mag(&cp);
166 nvec = smul(1.0 / ncp, &cp);
167
168 if ecc < 1.0 {
169 let ptx = TWOPI * (p.powi(3) / (VALLADO_MU * (1.0 - ecc * ecc).powi(3))).sqrt();
170 if dtsec % ptx > ptx * 0.5 {
171 x1 = x1_abs;
172 }
173 }
174 } else {
175 let y2a = VALLADO_MU / p - x1 * dnu.sin() + a * dnu.cos();
177 let y2b = VALLADO_MU / p + x1 * dnu.sin() + a * dnu.cos();
178 if (VALLADO_MU / magr2 - y2b).abs() < (VALLADO_MU / magr2 - y2a).abs() {
179 x1 = x1_abs;
180 }
181
182 let cp = cross(r1, r2);
183 let ncp = mag(&cp);
184 nvec = if dnu % TWOPI > PI {
185 smul(-1.0 / ncp, &cp)
186 } else {
187 smul(1.0 / ncp, &cp)
188 };
189 }
190
191 let sqrtmup = (VALLADO_MU * p).sqrt();
192 let nr1 = cross(&nvec, r1);
193 let v1t = smul(
194 sqrtmup / magr1,
195 &vadd(&smul(x1 / VALLADO_MU, r1), &smul(1.0 / magr1, &nr1)),
196 );
197
198 let x2 = x1 * dnu.cos() + a * dnu.sin();
199 let nr2 = cross(&nvec, r2);
200 let v2t = smul(
201 sqrtmup / magr2,
202 &vadd(&smul(x2 / VALLADO_MU, r2), &smul(1.0 / magr2, &nr2)),
203 );
204
205 (v1t, v2t)
206}
207
208#[derive(Clone, Copy, PartialEq, Eq, Debug)]
210pub enum DirectionOfMotion {
211 Short,
213 Long,
215}
216
217#[derive(Clone, Copy, PartialEq, Eq, Debug)]
219pub enum DirectionOfEnergy {
220 Low,
222 High,
224}
225
226#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
228pub enum LambertError {
229 #[error("Lambert solver did not converge")]
231 NoConvergence,
232 #[error("position vector has near-zero magnitude")]
234 ZeroVector,
235 #[error("time of flight must be strictly positive")]
237 NonPositiveTimeOfFlight,
238 #[error("transfer-plane geometry is degenerate")]
244 DegenerateGeometry,
245 #[error("non-finite value encountered")]
247 NonFiniteValue,
248}
249
250fn all_finite(a: &[f64; 3]) -> bool {
252 a.iter().all(|x| x.is_finite())
253}
254
255pub fn battin(
264 r1: &[f64; 3],
265 r2: &[f64; 3],
266 v1: &[f64; 3],
267 dm: DirectionOfMotion,
268 de: DirectionOfEnergy,
269 nrev: i32,
270 dtsec: f64,
271) -> Result<([f64; 3], [f64; 3]), LambertError> {
272 if !all_finite(r1) || !all_finite(r2) || !all_finite(v1) {
275 return Err(LambertError::NonFiniteValue);
276 }
277
278 let magr1 = mag(r1);
279 let magr2 = mag(r2);
280 if !magr1.is_finite() || !magr2.is_finite() {
284 return Err(LambertError::NonFiniteValue);
285 }
286 if magr1 < SMALL || magr2 < SMALL {
287 return Err(LambertError::ZeroVector);
288 }
289
290 if !dtsec.is_finite() || dtsec <= 0.0 {
291 return Err(LambertError::NonPositiveTimeOfFlight);
292 }
293
294 let cosdeltanu = dot(r1, r2) / (magr1 * magr2);
296 let magrcrossr = mag(&cross(r1, r2));
297 if !magrcrossr.is_finite() {
300 return Err(LambertError::NonFiniteValue);
301 }
302 let sign = if dm == DirectionOfMotion::Short {
303 1.0
304 } else {
305 -1.0
306 };
307 let sindeltanu = sign * magrcrossr / (magr1 * magr2);
308 let mut dnu = sindeltanu.atan2(cosdeltanu);
309 if dnu < 0.0 {
310 dnu += TWOPI;
311 }
312
313 if dnu.sin().abs() < SMALL {
319 let n_v1 = mag(&cross(r1, v1));
320 let plane_from_v1 = cosdeltanu < 0.0 && n_v1.is_finite() && n_v1 >= SMALL;
321 if !plane_from_v1 {
322 return Err(LambertError::DegenerateGeometry);
323 }
324 }
325
326 let chord = (magr1 * magr1 + magr2 * magr2 - 2.0 * magr1 * magr2 * cosdeltanu).sqrt();
328 let s = (magr1 + magr2 + chord) * 0.5;
329 let eps = magr2 / magr1 - 1.0;
330
331 let lam = (magr1 * magr2).sqrt() / s * (dnu * 0.5).cos();
333 let l_ = ((1.0 - lam) / (1.0 + lam)).powi(2);
334 let m = 8.0 * VALLADO_MU * dtsec * dtsec / (s.powi(3) * (1.0 + lam).powi(6));
335
336 let mut xn = if nrev > 0 { 1.0 + 4.0 * l_ } else { l_ };
338
339 if de == DirectionOfEnergy::High && nrev > 0 {
340 xn = 1e-20;
342 let mut x = 10.0;
343 for _ in 0..20 {
344 if (xn - x).abs() < SMALL {
345 break;
346 }
347 x = xn;
348 let temp = 1.0 / (2.0 * (l_ - x * x));
349 let temp1 = x.sqrt();
350 let temp2 = (nrev as f64 * PI * 0.5 + temp1.atan()) / temp1;
351 let h1 = temp * (l_ + x) * (1.0 + 2.0 * x + l_);
352 let h2 = temp * m * temp1 * ((l_ - x * x) * temp2 - (l_ + x));
353
354 let b = 0.25 * 27.0 * h2 / (temp1 * (1.0 + h1)).powi(3);
355 let f = if b < 0.0 {
356 2.0 * ((b + 1.0).sqrt().acos() / 3.0).cos()
357 } else {
358 let a_ = (b.sqrt() + (b + 1.0).sqrt()).powf(1.0 / 3.0);
359 a_ + 1.0 / a_
360 };
361
362 let y = 2.0 / 3.0 * temp1 * (1.0 + h1) * ((b + 1.0).sqrt() / f + 1.0);
363 xn = 0.5
364 * ((m / (y * y) - (1.0 + l_))
365 - ((m / (y * y) - (1.0 + l_)).powi(2) - 4.0 * l_).sqrt());
366 }
367
368 let converged = (xn - x).abs() < SMALL;
373 if !converged {
374 return Err(LambertError::NoConvergence);
375 }
376
377 let a_orbit = s * (1.0 + lam).powi(2) * (1.0 + xn) * (l_ + xn) / (8.0 * xn);
378 let p = 2.0 * magr1 * magr2 * (1.0 + xn) * (dnu * 0.5).sin().powi(2)
379 / (s * (1.0 + lam).powi(2) * (l_ + xn));
380 let ecc = (1.0 - p / a_orbit).abs().sqrt();
381 finite_or_err(hodograph(r1, r2, v1, p, ecc, dnu, dtsec))
382 } else {
383 let mut x = 10.0;
385 let max_loops = 30;
386 let mut loops = 0;
387 let mut y = 0.0;
388
389 while (xn - x).abs() >= SMALL && loops < max_loops {
390 x = xn;
391 loops += 1;
392
393 let (h1, h2) = if nrev > 0 {
394 let temp = 1.0 / ((1.0 + 2.0 * x + l_) * (4.0 * x * x));
395 let temp1 = (nrev as f64 * PI * 0.5 + x.sqrt().atan()) / x.sqrt();
396 let h1 =
397 temp * (l_ + x).powi(2) * (3.0 * (1.0 + x).powi(2) * temp1 - (3.0 + 5.0 * x));
398 let h2 = temp * m * ((x * x - x * (1.0 + l_) - 3.0 * l_) * temp1 + (3.0 * l_ + x));
399 (h1, h2)
400 } else {
401 let tempx = seebatt(x);
402 let denom = 1.0 / ((1.0 + 2.0 * x + l_) * (4.0 * x + tempx * (3.0 + x)));
403 let h1 = (l_ + x).powi(2) * (1.0 + 3.0 * x + tempx) * denom;
404 let h2 = m * (x - l_ + tempx) * denom;
405 (h1, h2)
406 };
407
408 let b = 0.25 * 27.0 * h2 / (1.0 + h1).powi(3);
409 let u = 0.5 * b / (1.0 + (1.0 + b).sqrt());
410 let k2 = kbatt(u);
411 y = (1.0 + h1) / 3.0 * (2.0 + (1.0 + b).sqrt() / (1.0 + 2.0 * u * k2 * k2));
412 xn = (((1.0 - l_) * 0.5).powi(2) + m / (y * y)).sqrt() - (1.0 + l_) * 0.5;
413 }
414
415 let converged = (xn - x).abs() < SMALL;
419 if !converged {
420 return Err(LambertError::NoConvergence);
421 }
422
423 let p = 2.0 * magr1 * magr2 * y * y * (1.0 + x).powi(2) * (dnu * 0.5).sin().powi(2)
424 / (m * s * (1.0 + lam).powi(2));
425 let ecc = (eps * eps
426 + 4.0 * magr2 / magr1 * (dnu * 0.5).sin().powi(2) * ((l_ - x) / (l_ + x)).powi(2))
427 / (eps * eps + 4.0 * magr2 / magr1 * (dnu * 0.5).sin().powi(2));
428 let ecc = ecc.sqrt();
429
430 finite_or_err(hodograph(r1, r2, v1, p, ecc, dnu, dtsec))
431 }
432}
433
434fn finite_or_err(vels: ([f64; 3], [f64; 3])) -> Result<([f64; 3], [f64; 3]), LambertError> {
437 let (v1t, v2t) = vels;
438 if all_finite(&v1t) && all_finite(&v2t) {
439 Ok((v1t, v2t))
440 } else {
441 Err(LambertError::NonFiniteValue)
442 }
443}
444
445#[cfg(test)]
446mod tests {
447 use super::*;
448
449 const RE: f64 = 6378.1363;
451 const DTSEC: f64 = 92854.234;
452 const NREV: i32 = 1;
453
454 fn inputs() -> ([f64; 3], [f64; 3], [f64; 3]) {
455 let r1 = [2.5 * RE, 0.0, 0.0];
456 let r2 = [1.9151111 * RE, 1.6069690 * RE, 0.0];
457 let v1 = [0.0, 4.999792554221911, 0.0];
458 (r1, r2, v1)
459 }
460
461 fn assert_close(actual: f64, expected: f64, label: &str) {
462 if expected == 0.0 {
463 assert!(actual.abs() < 1e-10, "{label}: expected ~0, got {actual}");
464 } else {
465 let rel = ((actual - expected) / expected).abs();
466 assert!(rel < 1e-12, "{label}: relative error {rel:e} exceeds 1e-12");
467 }
468 }
469
470 #[test]
471 fn battin_short_high() {
472 let (r1, r2, v1) = inputs();
473 let (v1t, v2t) = battin(
474 &r1,
475 &r2,
476 &v1,
477 DirectionOfMotion::Short,
478 DirectionOfEnergy::High,
479 NREV,
480 DTSEC,
481 )
482 .unwrap();
483 assert_close(v1t[0], -0.8696153795282852, "v1t_x");
484 assert_close(v1t[1], 6.3351545812502374, "v1t_y");
485 assert_close(v1t[2], 0.0, "v1t_z");
486 assert_close(v2t[0], -3.405994961791248, "v2t_x");
487 assert_close(v2t[1], 5.41198791828363, "v2t_y");
488 assert_close(v2t[2], 0.0, "v2t_z");
489 }
490
491 #[test]
492 fn battin_short_low() {
493 let (r1, r2, v1) = inputs();
494 let (v1t, v2t) = battin(
495 &r1,
496 &r2,
497 &v1,
498 DirectionOfMotion::Short,
499 DirectionOfEnergy::Low,
500 NREV,
501 DTSEC,
502 )
503 .unwrap();
504 assert_close(v1t[0], 5.832522716212579, "v1t_x");
505 assert_close(v1t[1], 1.4319944881331306, "v1t_y");
506 assert_close(v2t[0], -5.388439978490882, "v2t_x");
507 assert_close(v2t[1], -2.652101898141935, "v2t_y");
508 }
509
510 #[test]
511 fn battin_long_high() {
512 let (r1, r2, v1) = inputs();
513 let (v1t, v2t) = battin(
514 &r1,
515 &r2,
516 &v1,
517 DirectionOfMotion::Long,
518 DirectionOfEnergy::High,
519 NREV,
520 DTSEC,
521 )
522 .unwrap();
523 assert_close(v1t[0], -6.241103309400493, "v1t_x");
524 assert_close(v1t[1], -1.351339299630816, "v1t_y");
525 assert_close(v2t[0], 5.649586715490154, "v2t_x");
526 assert_close(v2t[1], 2.976517897853268, "v2t_y");
527 }
528
529 #[test]
530 fn battin_long_low() {
531 let (r1, r2, v1) = inputs();
532 let (v1t, v2t) = battin(
533 &r1,
534 &r2,
535 &v1,
536 DirectionOfMotion::Long,
537 DirectionOfEnergy::Low,
538 NREV,
539 DTSEC,
540 )
541 .unwrap();
542 assert_close(v1t[0], 0.641119158614630, "v1t_x");
543 assert_close(v1t[1], -5.957501823796459, "v1t_y");
544 assert_close(v2t[0], 3.33828270226307, "v2t_x");
545 assert_close(v2t[1], -4.975814585231199, "v2t_y");
546 }
547
548 #[test]
549 fn battin_rejects_zero_position() {
550 let (_, r2, v1) = inputs();
551 let r1 = [0.0, 0.0, 0.0];
552 let err = battin(
553 &r1,
554 &r2,
555 &v1,
556 DirectionOfMotion::Short,
557 DirectionOfEnergy::Low,
558 NREV,
559 DTSEC,
560 )
561 .unwrap_err();
562 assert_eq!(err, LambertError::ZeroVector);
563 }
564
565 #[test]
566 fn battin_rejects_nonpositive_dtsec() {
567 let (r1, r2, v1) = inputs();
568 for bad in [0.0, -1.0] {
569 let err = battin(
570 &r1,
571 &r2,
572 &v1,
573 DirectionOfMotion::Short,
574 DirectionOfEnergy::Low,
575 NREV,
576 bad,
577 )
578 .unwrap_err();
579 assert_eq!(err, LambertError::NonPositiveTimeOfFlight);
580 }
581 }
582
583 #[test]
584 fn battin_rejects_overflowing_magnitude() {
585 let r1 = [1e160, 0.0, 0.0];
588 let r2 = [0.0, 1e160, 0.0];
589 let v1 = [0.0, 5.0, 0.0];
590 let err = battin(
591 &r1,
592 &r2,
593 &v1,
594 DirectionOfMotion::Short,
595 DirectionOfEnergy::Low,
596 NREV,
597 DTSEC,
598 )
599 .unwrap_err();
600 assert_eq!(err, LambertError::NonFiniteValue);
601 }
602
603 #[test]
604 fn battin_rejects_collinear_endpoints() {
605 let r1 = [2.5 * RE, 0.0, 0.0];
607 let r2 = [5.0 * RE, 0.0, 0.0];
608 let v1 = [0.0, 4.999792554221911, 0.0];
609 let err = battin(
610 &r1,
611 &r2,
612 &v1,
613 DirectionOfMotion::Short,
614 DirectionOfEnergy::Low,
615 NREV,
616 DTSEC,
617 )
618 .unwrap_err();
619 assert_eq!(err, LambertError::DegenerateGeometry);
620 }
621
622 #[test]
623 fn battin_rejects_bad_180_transfer() {
624 let r1 = [2.5 * RE, 0.0, 0.0];
627 let r2 = [-5.0 * RE, 0.0, 0.0];
628 let v1 = [3.0, 0.0, 0.0];
629 let err = battin(
630 &r1,
631 &r2,
632 &v1,
633 DirectionOfMotion::Short,
634 DirectionOfEnergy::Low,
635 NREV,
636 DTSEC,
637 )
638 .unwrap_err();
639 assert_eq!(err, LambertError::DegenerateGeometry);
640 }
641
642 #[test]
643 fn battin_rejects_nonfinite_input() {
644 let (_, r2, v1) = inputs();
645 let r1 = [f64::NAN, 0.0, 0.0];
646 let err = battin(
647 &r1,
648 &r2,
649 &v1,
650 DirectionOfMotion::Short,
651 DirectionOfEnergy::Low,
652 NREV,
653 DTSEC,
654 )
655 .unwrap_err();
656 assert_eq!(err, LambertError::NonFiniteValue);
657 }
658
659 #[test]
660 fn battin_high_energy_reports_nonconvergence() {
661 let (r1, r2, v1) = inputs();
665 let err = battin(
666 &r1,
667 &r2,
668 &v1,
669 DirectionOfMotion::Short,
670 DirectionOfEnergy::High,
671 5,
672 1.0,
673 )
674 .unwrap_err();
675 assert_eq!(err, LambertError::NoConvergence);
676 }
677}