Minimum time orbit transfer

Introduction

Minimum time control of the Kepler equation (CNES / TAS / Inria / CNRS collaboration, check [1] and [2]):

\[\begin{cases} t_f \to \min,\\ \ddot{q} = -\mu\frac{q}{|q|^3} + \frac{u}{m}\,,\quad t \in [0,t_f],\\ \dot{m} = -\beta|u|,\quad |u| \leq T_{\mathrm{max}}. \end{cases}\]

Fixed initial and final Keplerian orbits (free final longitude).

using OptimalControl
using NLPModelsIpopt
using OrdinaryDiffEq
using Plots
using MINPACK
using ForwardDiff
using LinearAlgebra

Problem definition

Tmax = 60                                  # Maximum thrust in Newtons
cTmax = 3600^2 / 1e6; T = Tmax * cTmax     # Conversion from Newtons to kg x Mm / h²
mass0 = 1500                               # Initial mass of the spacecraft
β = 1.42e-02                               # Engine specific impulsion
μ = 5165.8620912                           # Earth gravitation constant
P0 = 11.625                                # Initial semilatus rectum
ex0, ey0 = 0.75, 0                         # Initial eccentricity
hx0, hy0 = 6.12e-2, 0                      # Initial ascending node and inclination
L0 = π                                     # Initial longitude
Pf = 42.165                                # Final semilatus rectum
exf, eyf = 0, 0                            # Final eccentricity
hxf, hyf = 0, 0                            # Final ascending node and inclination

asqrt(x; ε=1e-9) = sqrt(sqrt(x^2+ε^2))     # Avoid issues with AD

function F0(x)
    P, ex, ey, hx, hy, L = x
    pdm = asqrt(P / μ)
    cl = cos(L)
    sl = sin(L)
    w = 1 + ex * cl + ey * sl
    F = zeros(eltype(x), 6) # Use eltype to allow overloading for AD
    F[6] = w^2 / (P * pdm)
    return F
end

function F1(x)
    P, ex, ey, hx, hy, L = x
    pdm = asqrt(P / μ)
    cl = cos(L)
    sl = sin(L)
    F = zeros(eltype(x), 6)
    F[2] = pdm *   sl
    F[3] = pdm * (-cl)
    return F
end

function F2(x)
    P, ex, ey, hx, hy, L = x
    pdm = asqrt(P / μ)
    cl = cos(L)
    sl = sin(L)
    w = 1 + ex * cl + ey * sl
    F = zeros(eltype(x), 6)
    F[1] = pdm * 2 * P / w
    F[2] = pdm * (cl + (ex + cl) / w)
    F[3] = pdm * (sl + (ey + sl) / w)
    return F
end

function F3(x)
    P, ex, ey, hx, hy, L = x
    pdm = asqrt(P / μ)
    cl = cos(L)
    sl = sin(L)
    w = 1 + ex * cl + ey * sl
    pdmw = pdm / w
    zz = hx * sl - hy * cl
    uh = (1 + hx^2 + hy^2) / 2
    F = zeros(eltype(x), 6)
    F[2] = pdmw * (-zz * ey)
    F[3] = pdmw *   zz * ex
    F[4] = pdmw *   uh * cl
    F[5] = pdmw *   uh * sl
    F[6] = pdmw *   zz
    return F
end

Direct solve

tf = 15                                      # Estimation of final time
Lf = 3π                                      # Estimation of final longitude
x0 = [P0, ex0, ey0, hx0, hy0, L0]            # Initial state
xf = [Pf, exf, eyf, hxf, hyf, Lf]            # Final state
x(t) = x0 + (xf - x0) * t / tf               # Linear interpolation
u(t) = [0.1, 0.5, 0.]                        # Initial guess for the control
nlp_init = (state=x, control=u, variable=tf) # Initial guess for the NLP

ocp = @def begin
    tf ∈ R, variable
    t ∈ [0, tf], time
    x = (P, ex, ey, hx, hy, L) ∈ R⁶, state
    u ∈ R³, control
    x(0) == x0
    x[1:5](tf) == xf[1:5]
    mass = mass0 - β * T * t
    ẋ(t) == F0(x(t)) + T / mass * (u₁(t) * F1(x(t)) + u₂(t) * F2(x(t)) + u₃(t) * F3(x(t)))
    u₁(t)^2 + u₂(t)^2 + u₃(t)^2 ≤ 1
    tf → min
end
The (non autonomous) optimal control problem is given by:

    tf ∈ R, variable
    t ∈ [0, tf], time
    x = ((P, ex, ey, hx, hy, L) ∈ R⁶, state)
    u ∈ R³, control
    x(0) == x0
    (x[1:5])(tf) == xf[1:5]
    mass = mass0 - β * T * t
    ẋ(t) == F0(x(t)) + (T / mass) * (u₁(t) * F1(x(t)) + u₂(t) * F2(x(t)) + u₃(t) * F3(x(t)))
    u₁(t) ^ 2 + u₂(t) ^ 2 + u₃(t) ^ 2 ≤ 1
    tf → min

The (non autonomous) optimal control problem is of the form:

    minimize  J(x, u, tf) = g(x(0), x(tf), tf)

    subject to

        ẋ(t) = f(t, x(t), u(t), tf), t in [0, tf] a.e.,

        ξl ≤ ξ(t, u(t), tf) ≤ ξu, 
        ϕl ≤ ϕ(x(0), x(tf), tf) ≤ ϕu, 

    where x(t) = (P(t), ex(t), ey(t), hx(t), hy(t), L(t)) ∈ R⁶, u(t) ∈ R³ and tf ∈ R.

Declarations (* required):
╭────────┬────────┬──────────┬──────────┬───────────┬────────────┬─────────────╮
│ times* │ state* │ control* │ variable │ dynamics* │ objective* │ constraints │
├────────┼────────┼──────────┼──────────┼───────────┼────────────┼─────────────┤
│   V    │   V    │    V     │    V     │     V     │     V      │      V      │
╰────────┴────────┴──────────┴──────────┴───────────┴────────────┴─────────────╯
nlp_sol = OptimalControl.solve(ocp; init=nlp_init, grid_size=100)
This is Ipopt version 3.14.17, running with linear solver MUMPS 5.7.3.

Number of nonzeros in equality constraint Jacobian...:    11011
Number of nonzeros in inequality constraint Jacobian.:      303
Number of nonzeros in Lagrangian Hessian.............:     4445

Total number of variables............................:      910
                     variables with only lower bounds:        0
                variables with lower and upper bounds:        0
                     variables with only upper bounds:        0
Total number of equality constraints.................:      611
Total number of inequality constraints...............:      101
        inequality constraints with only lower bounds:        0
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:      101

iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
   0  1.5000000e+01 2.14e-01 2.46e-01   0.0 0.00e+00    -  0.00e+00 0.00e+00   0
   1  1.5632877e+01 1.73e-01 6.59e-01  -6.1 7.27e+00    -  5.95e-01 1.90e-01h  3
   2  1.6096872e+01 1.51e-01 1.15e+00  -6.4 3.71e+00   0.0 4.78e-01 1.25e-01h  4
   3  1.9018883e+01 6.12e-02 2.70e+00  -1.1 3.97e+00  -0.5 7.39e-01 6.03e-01H  1
   4  2.0358419e+01 1.56e-02 1.98e+00  -1.7 1.60e+00  -0.1 1.00e+00 8.38e-01h  1
   5  2.0617233e+01 2.68e-04 4.98e-01  -3.6 2.59e-01   0.4 1.00e+00 1.00e+00h  1
   6  2.0429778e+01 3.01e-04 1.53e-01  -3.7 1.87e-01  -0.1 1.00e+00 1.00e+00h  1
   7  1.9857419e+01 2.52e-03 1.52e-01  -4.6 5.72e-01  -0.6 1.00e+00 1.00e+00f  1
   8  1.9319499e+01 5.35e-03 1.22e-01  -2.7 1.03e+00  -1.1 1.00e+00 5.20e-01f  1
   9  1.8399038e+01 4.64e-02 7.26e-02  -2.4 1.40e+00  -1.5 1.00e+00 6.59e-01f  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  10  1.7193105e+01 3.05e+00 2.62e-01  -2.1 3.01e+00  -2.0 1.00e+00 8.31e-01f  1
  11  1.5106279e+01 1.10e+01 3.82e-01  -2.2 1.36e+01  -2.5 1.70e-01 3.23e-01h  1
  12  1.5675085e+01 2.15e+00 2.08e-01  -2.2 1.62e+00  -1.2 7.98e-01 1.00e+00h  1
  13  1.5767117e+01 1.59e-01 7.09e-02  -2.2 7.59e-01  -0.7 1.00e+00 1.00e+00h  1
  14  1.5167619e+01 5.39e-01 1.06e-01  -1.9 6.62e+00    -  1.00e+00 4.46e-01h  1
  15  1.5207976e+01 1.55e-01 2.60e-01  -2.2 1.87e+00    -  1.00e+00 7.88e-01h  1
  16  1.4958081e+01 2.78e-02 2.02e-02  -4.0 4.59e-01    -  1.00e+00 9.93e-01h  1
  17  1.5012097e+01 4.19e-01 7.47e-03  -2.5 6.61e-01    -  1.00e+00 9.34e-01h  1
  18  1.4993570e+01 2.41e-04 5.76e-04  -2.7 3.51e-01    -  1.00e+00 1.00e+00h  1
  19  1.4814517e+01 9.34e-04 3.26e-03  -8.7 2.05e-01    -  1.00e+00 1.00e+00h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  20  1.4796807e+01 5.17e-04 5.95e-05  -5.1 6.84e-02    -  1.00e+00 1.00e+00h  1
  21  1.4796405e+01 1.26e-07 1.37e-08 -11.0 1.93e-03    -  1.00e+00 1.00e+00h  1
  22  1.4796405e+01 7.11e-15 1.45e-14 -11.0 2.11e-06    -  1.00e+00 1.00e+00h  1

Number of Iterations....: 22

                                   (scaled)                 (unscaled)
Objective...............:   1.4796404502710013e+01    1.4796404502710013e+01
Dual infeasibility......:   1.4543921622589551e-14    1.4543921622589551e-14
Constraint violation....:   7.1054273576010019e-15    7.1054273576010019e-15
Variable bound violation:   0.0000000000000000e+00    0.0000000000000000e+00
Complementarity.........:   1.0004070416573181e-11    1.0004070416573181e-11
Overall NLP error.......:   1.0004070416573181e-11    1.0004070416573181e-11


Number of objective function evaluations             = 34
Number of objective gradient evaluations             = 23
Number of equality constraint evaluations            = 34
Number of inequality constraint evaluations          = 34
Number of equality constraint Jacobian evaluations   = 23
Number of inequality constraint Jacobian evaluations = 23
Number of Lagrangian Hessian evaluations             = 22
Total seconds in IPOPT                               = 6.747

EXIT: Optimal Solution Found.
tf = nlp_sol.variable
p0 = nlp_sol.costate(0)
plot(nlp_sol)
Example block output

Shooting (1/2), Tmax = 60 Newtons

function ur(t, x, p, tf) # Regular maximising control
    H1 = p' * F1(x)
    H2 = p' * F2(x)
    H3 = p' * F3(x)
    u = [H1, H2, H3]
    u = u / sqrt(u[1]^2 + u[2]^2 + u[3]^2)
    return u
end

fr = Flow(ocp, ur) # Regular flow (first version)

function shoot(ξ::Vector{T}) where T
    tf, p0 = ξ[1], ξ[2:end]
    xf_, pf = fr(0, x0, p0, tf)
    s = zeros(T, 7)
    s[1:5] = xf_[1:5] - xf[1:5]
    s[6] = pf[6]
    s[7] = p0[1]^2 + p0[2]^2 + p0[3]^2 + p0[4]^2 + p0[5]^2 + p0[6]^2 - 1
    return s
end

p0 = p0 / norm(p0) # Normalization |p0|=1 for free final time
ξ = [tf; p0]; # Initial guess
jshoot(ξ) = ForwardDiff.jacobian(shoot, ξ)
shoot!(s, ξ) = (s[:] = shoot(ξ); nothing)
jshoot!(js, ξ) = (js[:] = jshoot(ξ); nothing)
bvp_sol = fsolve(shoot!, jshoot!, ξ; show_trace=true); println(bvp_sol)
Iter     f(x) inf-norm    Step 2-norm      Step time
------   --------------   --------------   --------------
     1     1.441506e+00     0.000000e+00         6.803907
     2     7.828938e-02     5.822027e-05        14.395052
     3     1.805662e-02     1.793354e-05         0.025509
     4     4.039662e-03     6.128462e-07         0.007661
     5     1.137949e-04     1.107466e-08         0.008436
     6     6.538025e-05     1.358877e-10         0.030044
     7     6.246359e-06     4.488870e-12         0.007067
     8     3.534257e-08     5.344944e-14         0.007143
     9     3.604299e-10     1.853523e-18         0.018883
Results of Nonlinear Solver Algorithm
 * Algorithm: Modified Powell (User Jac, Expert)
 * Starting Point: [14.796404502710013, -0.016164435389650875, -0.905096220293085, -0.330678776871153, -0.09648290171205866, 0.03514811057836561, 0.24626560776262554]
 * Zero: [14.800364354664751, -0.01618971492162694, -0.9084889547974426, -0.3252598589830435, -0.09447217088704518, 0.03432296283542465, 0.2418443289106981]
 * Inf-norm of residuals: 0.000000
 * Convergence: true
 * Message: algorithm estimates that the relative error between x and the solution is at most tol
 * Total time: 21.303749 seconds
 * Function Calls: 9
 * Jacobian Calls (df/dx): 1

Shooting (2/2), Tmax = 0.7 Newtons

hr = (t, x, p) -> begin # Regular maximised Hamiltonian (more efficient)
    H0 = p' * F0(x)
    H1 = p' * F1(x)
    H2 = p' * F2(x)
    H3 = p' * F3(x)
    mass = mass0 - β*T*t
    h = H0 + T / mass * sqrt(H1^2 + H2^2 + H3^2)
    return h
end

hr = Hamiltonian(hr; autonomous=false)
fr = Flow(hr) # Regular flow (again)

Tmax = 0.7                                 # Maximum thrust (Newtons)
cTmax = 3600^2 / 1e6; T = Tmax * cTmax     # Conversion from Newtons to kg x Mm / h²
tf = 1.210e3; p0 =-[-2.215319700438820e+01, -4.347109477345140e+01, 9.613188807286992e-01, 3.181800985503019e+02, -2.307236094862410e+00, -5.797863110671591e-01] # Tmax = 0.7 Newtons
p0 = p0 / norm(p0) # Normalization |p0|=1 for free final time
ξ = [tf; p0]; # Initial guess

bvp_sol = fsolve(shoot!, jshoot!, ξ; show_trace=true); println(bvp_sol)
Iter     f(x) inf-norm    Step 2-norm      Step time
------   --------------   --------------   --------------
     1     1.073118e+00     0.000000e+00         3.222664
     2     1.431619e-01     1.222060e+01        10.768419
     3     1.090263e-01     1.104500e+01         0.321505
     4     1.847681e-02     2.971575e+00         0.317427
     5     1.996342e-02     4.468500e-01         0.305917
     6     8.607891e-03     2.181855e-02         0.305471
     7     2.826179e-03     2.618597e-03         0.296809
     8     1.241781e-03     7.994157e-05         0.290444
     9     1.554962e-03     6.601818e-05         0.300203
    10     7.600558e-05     2.808537e-05         0.289783
    11     4.207243e-05     1.773631e-07         0.290194
    12     7.674833e-06     3.846836e-07         0.290703
    13     5.808837e-07     7.112732e-09         0.300415
    14     1.038006e-07     5.664024e-11         0.297672
    15     1.078794e-08     9.239514e-13         0.295442
Results of Nonlinear Solver Algorithm
 * Algorithm: Modified Powell (User Jac, Expert)
 * Starting Point: [1210.0, 0.06881811251625697, 0.13504139789537645, -0.0029863026489984875, -0.9884150724670144, 0.007167346236588738, 0.001801085395597836]
 * Zero: [1214.592219656874, 0.0693340965649636, 0.5161919875828057, -0.00023166922238217568, -0.8536506963043183, 0.004363237669460584, -9.327823133008281e-5]
 * Inf-norm of residuals: 0.000000
 * Convergence: true
 * Message: algorithm estimates that the relative error between x and the solution is at most tol
 * Total time: 17.893155 seconds
 * Function Calls: 15
 * Jacobian Calls (df/dx): 1

Plots

tf = bvp_sol.x[1]
p0 = bvp_sol.x[2:end]
ode_sol = fr((0, tf), x0, p0)
t  = ode_sol.t; N = size(t, 1)
P  = ode_sol[1, :]
ex = ode_sol[2, :]
ey = ode_sol[3, :]
hx = ode_sol[4, :]
hy = ode_sol[5, :]
L  = ode_sol[6, :]
cL = cos.(L)
sL = sin.(L)
w  = @. 1 + ex * cL + ey * sL
Z  = @. hx * sL - hy * cL
C  = @. 1 + hx^2 + hy^2
q1 = @. P *((1 + hx^2 - hy^2) * cL + 2 * hx * hy * sL) / (C * w)
q2 = @. P *((1 - hx^2 + hy^2) * sL + 2 * hx * hy * cL) / (C * w)
q3 = @. 2 * P * Z / (C * w)

plt1 = plot3d(1; xlim = (-60, 60), ylim = (-60, 60), zlim = (-5, 5), title = "Orbit transfer", legend=false)
@gif for i = 1:N
    push!(plt1, q1[i], q2[i], q3[i])
end every N ÷ min(N, 100)
Example block output

Dependencies

All the numerical simulations to generate this documentation are performed with the following packages.

using Pkg
Pkg.status()
Status `~/work/Kepler.jl/Kepler.jl/docs/Project.toml`
  [e30172f5] Documenter v1.9.0
  [f6369f11] ForwardDiff v0.10.38
  [459d104a] Kepler v0.1.5 `~/work/Kepler.jl/Kepler.jl`
⌃ [4854310b] MINPACK v1.2.0
  [f4238b75] NLPModelsIpopt v0.10.3
⌅ [5f98b655] OptimalControl v0.12.3
  [1dea7af3] OrdinaryDiffEq v6.92.0
  [91a5bcdd] Plots v1.40.10
  [37e2e46d] LinearAlgebra v1.11.0
Info Packages marked with ⌃ and ⌅ have new versions available. Those with ⌃ may be upgradable, but those with ⌅ are restricted by compatibility constraints from upgrading. To see why use `status --outdated`

References

  • 1Bonnard, B.; Caillau, J.-B.; Trélat, E. Geometric optimal control of elliptic Keplerian orbits. Discrete Contin. Dyn. Syst. Ser. B 5 (2005), no. 4, 929-956.
  • 2Caillau, J.-B.; Gergaud, J.; Noailles, J. 3D Geosynchronous Transfer of a Satellite: continuation on the Thrust. J. Optim. Theory Appl. 118 (2003), no. 3, 541-565.