lambert_bate/
lib.rs

1#![allow(non_snake_case)]
2
3#[cfg(test)]
4mod tests;
5
6mod newton;
7use newton::LambertError;
8
9const Z_SERIES_THRESHOLD: f64 = 1.0e-3;
10
11const fn factorial(num: u64) -> u64 {
12    match num {
13        0  => 1,
14        1.. => num * factorial(num-1),
15    }
16}
17fn dot(v1: [f64; 3], v2: [f64; 3]) -> f64 {
18    v1[0]*v2[0] + v1[1] * v2[1] + v1[2] * v2[2]
19}
20
21/// A lightweight function to solve Lambert's problem, given two points in an orbit and the time of
22/// flight between them.
23/// 
24/// The first element of the return value is the velocity associated with the position `r1` while
25/// the second is associated with `r2`. `tof` indicates the time of flight between the two
26/// points. `mu` indicates the gravitational parameter of the system: 
27/// the product of Newton's Gravitational constant and the mass of the central body. It is assumed
28/// that the mass of the orbiting object is negligible.
29/// 
30/// If the velocities are desired for trajectories that take the long path between `r1` and 
31/// `r2`, then `short` should be set to `false`.
32/// 
33/// If `r1` or `r2` are nearly colinear, then `NaN` results are possible. In this case, a
34/// small offset should be added to one or both of the vectors. If the root-finding method encounters
35/// NaN or fails to complete for another reason, then `Err` will be returned.
36/// 
37/// # Examples
38/// 
39/// The below line computes the time velocity required to leave Earth from the x axis and arrive at
40/// Mars on the -x axis (a Hohmann transfer) in meters per second after 8 months. We chose a time of
41/// flight (22372602 seconds) which will lead to velocities aligned with the y axis, but other times
42/// of flight may also be chosen.
43/// 
44/// ```
45/// get_velocities([1.496e11, 0.0, 0.0], [-2.28e11, 1.0e6, 0.0], 22372602, 1.327e20,
46///     true, 1e-7, 100);
47/// ```
48/// 
49/// A small offset was added to the positions to prevent them from being co-linear.
50/// 
51/// If the same function is run with `short` changed to `false`, the resulting velocities will 
52/// have reversed y coordinate, except for small errors induced by the added offset.
53pub fn get_velocities(r1: [f64; 3], r2: [f64; 3], tof: f64, mu: f64, short: bool,
54    eps: f64, max_iter: usize) -> Result<([f64; 3], [f64; 3]), LambertError> {
55    let r1_mag = dot(r1, r1).sqrt();
56    let r2_mag = dot(r2, r2).sqrt();
57    let A = f64::sqrt(r1_mag * r2_mag + dot(r1, r2)) * (if short {1.0} else {-1.0});
58    
59    let best_z = newton::find_root(|z| -> f64 {
60        let S = get_S(z);
61        let C = get_C(z);
62        let y = r1_mag + r2_mag - A * (1.0 - z * S) / C.sqrt(); // Equation 5.3-9
63        let x = f64::sqrt(y / C); // Equation 5.3-10
64        let t = (x * x * x * S + A * y.sqrt()) / mu.sqrt();// Equation 5-3.12
65        t / tof - 1.0
66    }, |z| -> f64 {
67        let S = get_S(z);
68        let dS_dz = get_dS_dz(z);
69        let C = get_C(z);
70        let dC_dz = get_dC_dz(z);
71        let y = r1_mag + r2_mag - A * (1.0 - z * S) / C.sqrt();
72        let x = f64::sqrt(y / C);
73        (x * x * x * (dS_dz - 3.0 * S * dC_dz / (2.0 * C)) 
74            + A / 8.0 * (3.0 * S * y.sqrt() / C + A / x)) / mu.sqrt() / tof
75    }, eps, max_iter)?;
76
77    let S = get_S(best_z);
78    let C = get_C(best_z);
79    let y = r1_mag + r2_mag - A * (1.0 - best_z * S) / C.sqrt();
80    let f = 1.0 - y / r1_mag; // Equation 5.3-13
81    let g = A * f64::sqrt(y / mu);  // Equation 5.3-14
82    let g_dot = 1.0 - y / r2_mag; // Equation 5.3-15
83
84    let v1 = [
85        (r2[0] - f * r1[0]) / g,
86        (r2[1] - f * r1[1]) / g,
87        (r2[2] - f * r1[2]) / g,
88    ]; // Equation 5.3-16
89    let v2 = [
90        (g_dot * r2[0] - r1[0]) / g,
91        (g_dot * r2[1] - r1[1]) / g,
92        (g_dot * r2[2] - r1[2]) / g,
93    ]; // Equation 5.3-17
94
95    Ok((v1, v2))
96}
97
98/// Compute the S value defined by Bate
99fn get_S(z: f64) -> f64 { // Equation 4.4-10
100    if z.abs() < Z_SERIES_THRESHOLD {
101          1.0 / factorial(3) as f64
102        - z / factorial(5) as f64
103        + z * z / factorial(7) as f64
104        - z * z * z / factorial(9) as f64
105        + z * z * z * z / factorial(11) as f64
106    } else {
107        if z > 0.0 {
108            let z_sqrt = z.sqrt();
109            (z_sqrt - z_sqrt.sin()) / (z * z_sqrt)
110        } else {
111            let z_sqrt = (-z).sqrt();
112            (z_sqrt.sinh() - z_sqrt) / (-z * z_sqrt)
113        }
114    }
115}
116
117/// Compute the C value defined by Bate
118fn get_C(z: f64) -> f64 { // Equation 4.4-11
119    if z.abs() < Z_SERIES_THRESHOLD {
120        1.0 / factorial(2) as f64
121        - z / factorial(4) as f64
122        + z * z / factorial(6) as f64
123        - z * z * z / factorial(8) as f64
124        + z * z * z * z / factorial(10) as f64
125    } else {
126        if z > 0.0 {
127            (1.0 - z.sqrt().cos()) / z
128        } else {
129            (1.0 - (-z).sqrt().cosh()) / z
130        }
131    }
132}
133
134/// Compute the derivative of S
135fn get_dS_dz(z: f64) -> f64 { // Equation 5.3-13
136    if z.abs() < Z_SERIES_THRESHOLD {
137        - 1.0 / factorial(5) as f64
138        + 2.0 * z / factorial(7) as f64
139        - 3.0 * z * z / factorial(9) as f64
140        + 4.0 * z * z * z / factorial(11) as f64
141        - 5.0 * z * z * z * z / factorial(13) as f64
142    } else {
143        if z > 0.0 {
144            let sqrt_z = z.sqrt();
145            - 0.5  / (z * z) * (2.0 + sqrt_z.cos()
146                - 3.0 * sqrt_z.sin() / sqrt_z)
147        } else {
148            let sqrt_z = (-z).sqrt();
149            - 0.5 / (z * z) * (2.0 + sqrt_z.cosh()
150                - 3.0 * sqrt_z.sinh() / sqrt_z)
151        }
152    }
153}
154
155/// Compute the derivative of G
156fn get_dC_dz(z: f64) -> f64 { // Equation 5.3-14
157    if z.abs() < Z_SERIES_THRESHOLD {
158        - 1.0 / factorial(4) as f64
159        + 2.0 * z / factorial(6) as f64
160        - 3.0 * z * z / factorial(8) as f64
161        + 4.0 * z * z * z / factorial(10) as f64
162        - 5.0 * z * z * z * z / factorial(10) as f64
163    } else {
164        if z > 0.0 {
165            let sqrt_z = z.sqrt();
166            - 0.5 / (z * z) * (2.0 - 2.0 * sqrt_z.cos()
167                - sqrt_z.sin() * sqrt_z)
168        } else {
169            let sqrt_z = (-z).sqrt();
170            - 0.5 / (z * z) * (2.0 - 2.0 * sqrt_z.cosh()
171                + sqrt_z.sinh() * sqrt_z)
172        }
173    }
174}