/
Izzo.cs
319 lines (280 loc) · 11.7 KB
/
Izzo.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
/*
* Copyright Lamont Granquist, Sebastien Gaggini and the MechJeb contributors
* SPDX-License-Identifier: LicenseRef-PD-hp OR Unlicense OR CC0-1.0 OR 0BSD OR MIT-0 OR MIT OR LGPL-2.1+
*/
using System;
using System.Runtime.CompilerServices;
using MechJebLib.Primitives;
using static System.Math;
using static MechJebLib.Statics;
#nullable enable
namespace MechJebLib.Core.Lambert
{
/// <summary>
/// This is Izzo's method, copied from:
/// https://github.com/esa/pykep/blob/8b0e9444d09b909d7d1d11e951c8efcfde0a2ffd/src/lambert_problem.cpp
/// I made some changes to the API, largely around making it conform to the Gooding method API that we had
/// previously and not using the clockwise/counterclockwise API so it doesn't have that weird singularity
/// for perfectly polar orbits.
/// This implementation isn't any faster than the Gooding implementation in this repo for nrev=0.
/// </summary>
public class Izzo
{
public (V3 Vi, V3 Vf) Solve(double mu, V3 r1, V3 v1, V3 r2, double tof, int nrev)
{
lambert_problem2(r1, v1, r2, tof, mu, nrev);
if (_nsol == 0)
return (_v1[0], _v2[0]);
double mindv = double.PositiveInfinity;
int index = 0;
for (int i = 0; i < _nsol; i++)
{
double dv = (_v1[i] - v1).sqrMagnitude;
if (dv >= mindv)
continue;
index = i;
mindv = dv;
}
return (_v1[index], _v2[index]);
}
private int _nsol;
private V3[] _v1 = new V3[5];
private V3[] _v2 = new V3[5];
private int[] _iters = new int[5];
private double[] _x = new double[5];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void lambert_problem2(V3 r1, V3 v1, V3 r2, double tof, double mu, int nrev)
{
V3 hhat = V3.Cross(r1, v1).normalized;
V3 zhat = V3.Cross(r1.normalized, r2.normalized).normalized;
/* if angle to orbit normal is greater than 90 degrees, then flip the orbit normal */
bool flip = V3.Dot(hhat, zhat) < 0;
lambert_problem(r1, r2, tof, mu, nrev, flip);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void lambert_problem(V3 r1, V3 r2, double tof, double mu, int nrev, bool flip = false)
{
// 0 - Sanity checks
if (tof <= 0)
throw new Exception("Time of flight is negative!");
if (mu <= 0)
throw new Exception("Gravity parameter is zero or negative!");
// 1 - Getting lambda and T
double c = (r2 - r1).magnitude;
double r1M = r1.magnitude;
double r2M = r2.magnitude;
double s = (c + r1M + r2M) / 2.0;
V3 ir1 = r1.normalized;
V3 ir2 = r2.normalized;
V3 ih = V3.Cross(ir1, ir2).normalized;
double lambda2 = 1.0 - c / s;
double lambda = Sqrt(lambda2);
var it1 = V3.Cross(ih, ir1);
var it2 = V3.Cross(ih, ir2);
it1 = it1.normalized;
it2 = it2.normalized;
if (flip)
{
// Retrograde motion
lambda = -lambda;
it1[0] = -it1[0];
it1[1] = -it1[1];
it1[2] = -it1[2];
it2[0] = -it2[0];
it2[1] = -it2[1];
it2[2] = -it2[2];
}
double lambda3 = lambda * lambda2;
double t = Sqrt(2.0 * mu / s / s / s) * tof;
// 2 - We now have lambda, T and we will find all x
// 2.1 - Let us first detect the maximum number of revolutions for which there exists a solution
int nmax = (int)(t / PI);
double t00 = Acos(lambda) + lambda * Sqrt(1.0 - lambda2);
double t0 = t00 + nmax * PI;
double t1 = 2.0 / 3.0 * (1.0 - lambda3);
if (nmax > 0)
{
if (t < t0)
{
// We use Halley iterations to find xM and TM
int it = 0;
double tMin = t0;
double xOld = 0.0, xNew = 0.0;
while (true)
{
(double dt, double ddt, double dddt) = DTdx(lambda, xOld, tMin);
if (dt != 0.0)
xNew = xOld - dt * ddt / (ddt * ddt - dt * dddt / 2.0);
double err = Abs(xOld - xNew);
if (err < 1e-13 || it > 12)
break;
tMin = X2Tof(lambda, xNew, nmax);
xOld = xNew;
it++;
}
if (tMin > t)
nmax -= 1;
}
}
// We exit this if clause with Nmax being the maximum number of revolutions
// for which there exists a solution. We crop it to m_multi_revs
nmax = Min(nrev, nmax);
_nsol = nmax * 2 + 1;
// 2.2 We now allocate the memory for the output variables
if (_nsol > _x.Length)
{
_v1 = new V3[nmax * 2 + 1];
_v2 = new V3[nmax * 2 + 1];
_iters = new int[nmax * 2 + 1];
_x = new double[nmax * 2 + 1];
}
// 3 - We may now find all solutions in x,y
// 3.1 0 rev solution
// 3.1.1 initial guess
if (t >= t00)
_x[0] = -(t - t00) / (t - t00 + 4);
else if (t <= t1)
_x[0] = t1 * (t1 - t) / (2.0 / 5.0 * (1 - lambda2 * lambda3) * t) + 1;
else
_x[0] = Pow(t / t00, 0.69314718055994529 / Log(t1 / t00)) - 1.0;
// 3.1.2 Householder iterations
_iters[0] = Householder(lambda, t, ref _x[0], 0, 1e-5, 15);
// 3.2 multi rev solutions
for (int i = 1; i < nmax + 1; ++i)
{
// 3.2.1 left Householder iterations
double tmp = Pow((i * PI + PI) / (8.0 * t), 2.0 / 3.0);
_x[2 * i - 1] = (tmp - 1) / (tmp + 1);
_iters[2 * i - 1] = Householder(lambda, t, ref _x[2 * i - 1], i, 1e-8, 15);
// 3.2.1 right Householder iterations
tmp = Pow(8.0 * t / (i * PI), 2.0 / 3.0);
_x[2 * i] = (tmp - 1) / (tmp + 1);
_iters[2 * i] = Householder(lambda, t, ref _x[2 * i], i, 1e-8, 15);
}
// 4 - For each found x value we reconstruct the terminal velocities
double gamma = Sqrt(mu * s / 2.0);
double rho = (r1M - r2M) / c;
double sigma = Sqrt(1 - rho * rho);
for (int i = 0; i < _nsol; ++i)
{
double y = Sqrt(1.0 - lambda2 + lambda2 * _x[i] * _x[i]);
double vr1 = gamma * (lambda * y - _x[i] - rho * (lambda * y + _x[i])) / r1M;
double vr2 = -gamma * (lambda * y - _x[i] + rho * (lambda * y + _x[i])) / r2M;
double vt = gamma * sigma * (y + lambda * _x[i]);
double vt1 = vt / r1M;
double vt2 = vt / r2M;
for (int j = 0; j < 3; ++j)
_v1[i][j] = vr1 * ir1[j] + vt1 * it1[j];
for (int j = 0; j < 3; ++j)
_v2[i][j] = vr2 * ir2[j] + vt2 * it2[j];
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Householder(double lambda, double t, ref double x0, int n, double eps, int maxIter)
{
int it = 0;
double err = 1.0;
while (err > eps && it < maxIter)
{
double tof = X2Tof(lambda, x0, n);
(double dt, double ddt, double dddt) = DTdx(lambda, x0, tof);
double delta = tof - t;
double dt2 = dt * dt;
double xnew = x0 - delta * (dt2 - delta * ddt / 2.0) / (dt * (dt2 - delta * ddt) + dddt * delta * delta / 6.0);
err = Abs(x0 - xnew);
x0 = xnew;
it++;
}
return it;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ( double DT, double DDT, double DDDT) DTdx(double lambda, double x, double t)
{
double l2 = lambda * lambda;
double l3 = l2 * lambda;
double umx2 = 1.0 - x * x;
double y = Sqrt(1.0 - l2 * umx2);
double y2 = y * y;
double y3 = y2 * y;
double dt = 1.0 / umx2 * (3.0 * t * x - 2.0 + 2.0 * l3 * x / y);
double ddt = 1.0 / umx2 * (3.0 * t + 5.0 * x * dt + 2.0 * (1.0 - l2) * l3 / y3);
double dddt = 1.0 / umx2 * (7.0 * x * ddt + 8.0 * dt - 6.0 * (1.0 - l2) * l2 * l3 * x / y3 / y2);
return (dt, ddt, dddt);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double X2Tof2(double lambda, double x, int n)
{
double a = 1.0 / (1.0 - x * x);
if (a > 0) // ellipse
{
double alfa = 2.0 * Acos(x);
double beta = 2.0 * Asin(Sqrt(lambda * lambda / a));
if (lambda < 0.0) beta = -beta;
return a * Sqrt(a) * (alfa - Sin(alfa) - (beta - Sin(beta)) + 2.0 * PI * n) / 2.0;
}
else
{
double alfa = 2.0 * Acosh(x);
double beta = 2.0 * Asinh(Sqrt(-lambda * lambda / a));
if (lambda < 0.0) beta = -beta;
return -a * Sqrt(-a) * (beta - Sinh(beta) - (alfa - Sinh(alfa))) / 2.0;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double X2Tof(double lambda, double x, int n)
{
const double BATTIN = 0.01;
const double LAGRANGE = 0.2;
double dist = Abs(x - 1);
if (dist < LAGRANGE && dist > BATTIN)
// We use Lagrange tof expression
return X2Tof2(lambda, x, n);
double k = lambda * lambda;
double e = x * x - 1.0;
double rho = Abs(e);
double z = Sqrt(1 + k * e);
if (dist < BATTIN)
{
// We use Battin series tof expression
double eta = z - lambda * x;
double s1 = 0.5 * (1.0 - lambda - x * eta);
double q = HypergeometricF(s1, 1e-11);
q = 4.0 / 3.0 * q;
return (eta * eta * eta * q + 4.0 * lambda * eta) / 2.0 + n * PI / Pow(rho, 1.5);
}
// We use Lancaster tof expresion
double y = Sqrt(rho);
double g = x * z - lambda * e;
double d;
if (e < 0)
{
double l = Acos(g);
d = n * PI + l;
}
else
{
double f = y * (z - lambda * x);
d = Log(f + g);
}
return (x - lambda * z - d / y) / e;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double HypergeometricF(double z, double tol)
{
double sj = 1.0;
double cj = 1.0;
double err = 1.0;
int j = 0;
while (err > tol)
{
double cj1 = cj * (3.0 + j) * (1.0 + j) / (2.5 + j) * z / (j + 1);
double sj1 = sj + cj1;
err = Abs(cj1);
sj = sj1;
cj = cj1;
j += 1;
}
return sj;
}
}
}