Zermelo problem, example 2

\[ \left\{ \begin{array}{l} \displaystyle \min - x_1(8), \\[0.5em] \dot{x}_1(t) = x_2(t) + \cos(u(t)), \; \text{for a.e. } t\in [0,8],\\[0.5em] \dot{x}_2(t) = \sin(u(t)), \; \text{for a.e. } t\in [0,8], \\[0.5em] u(t) \in [-\frac{\pi}{2}, \frac{\pi}{2}], \; \text{for a.e. } t\in [0,8], \\[0.5em] x(0) = 0_{\mathbb{R}^2}, \quad x_2(8) = 4,\\[0.5em] \{x \in \mathbb{R}^2 \mid 5 < x_1 < 10 \} \text{ and } \{x \in \mathbb{R}^2 \mid 20 < x_1 < 25 \} \text{ are loss control regions.} \end{array} \right.\]

Reformulation for the direct method

\[ \left\{ \begin{array}{l} \displaystyle \min - x_1(8) + \varepsilon \int_0^8 v^2(t)dt + \int_0^8 f_{NC}(x(t))u^2(t)dt, \\[0.5em] \dot{x}_1(t) = f_{C}(x(t)) (x_2(t) + \cos(u(t)))+f_{NC}(x(t)) (x_2(t) + \cos(\lambda(t))), \; \text{for a.e. } t\in [0,8],\\[0.5em] \dot{x}_2(t) = f_{C}(x(t)) \sin(u(t))+f_{NC}(x(t)) \sin(\lambda(t)), \; \text{for a.e. } t\in [0,8], \\[0.5em] \dot{\lambda}(t) = f_{C}(x(t))v(t), \; \text{for a.e. } t\in [0,8], \\[0.5em] u(t)\in [-\frac{\pi}{2}, \frac{\pi}{2}] , \; \text{for a.e. } t\in [0,8], \\[0.5em] x(0) = 0_{\mathbb{R}^2}, \quad x_2(8) = 4,\\[0.5em] \{x \in \mathbb{R}^2 \mid 5 < x_1 < 10 \} \text{ and } \{x \in \mathbb{R}^2 \mid 20 < x_1 < 25 \} \text{ are loss control regions.} \end{array} \right.\]

using Plots
using Plots.PlotMeasures
using OptimalControl
using NLPModelsIpopt
include("smooth.jl")
fNC(x) = fNC_bounded(x, [(5, 10), (20, 25)], 0.05)
plot(fNC, 0, 30, label="fNC")
Example block output
ε  = 1e-3
tf = 8

@def ocp begin

    t ∈ [ 0, tf ],           time
    q = [ x1, x2, λ ] ∈ R^3, state
    ω = [u, v] ∈ R^2,        control

    x1(0) == 0
    x2(0) == 0
    x2(tf) == 4

    -π/2 ≤ u(t) ≤ π/2
    -π/2 ≤ λ(t) ≤ π/2

    q̇(t) == [fNC(x1(t))*(x2(t) + cos(λ(t))) + (1-fNC(x1(t)))*(x2(t) + cos(u(t))),
             fNC(x1(t))*sin(λ(t)) + (1-fNC(x1(t)))*sin(u(t)),
             (1-fNC(x1(t)))*v(t)]

    -x1(tf) + ∫(ε*(v(t))^2+fNC(x1(t))*(u(t))^2)  → min

end
N = 400
sol = solve(ocp; grid_size=N, print_level=4)
Total number of variables............................:     2406
                     variables with only lower bounds:        0
                variables with lower and upper bounds:      802
                     variables with only upper bounds:        0
Total number of equality constraints.................:     1604
Total number of inequality constraints...............:        0
        inequality constraints with only lower bounds:        0
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:        0


Number of Iterations....: 47

                                   (scaled)                 (unscaled)
Objective...............:  -3.0398318236703240e+01   -3.0398318236703240e+01
Dual infeasibility......:   4.5887331018290567e-09    4.5887331018290567e-09
Constraint violation....:   6.2325167249355218e-10    6.2325167249355218e-10
Variable bound violation:   0.0000000000000000e+00    0.0000000000000000e+00
Complementarity.........:   8.7039233340776861e-10    8.7039233340776861e-10
Overall NLP error.......:   4.5887331018290567e-09    4.5887331018290567e-09


Number of objective function evaluations             = 50
Number of objective gradient evaluations             = 48
Number of equality constraint evaluations            = 50
Number of inequality constraint evaluations          = 0
Number of equality constraint Jacobian evaluations   = 48
Number of inequality constraint Jacobian evaluations = 0
Number of Lagrangian Hessian evaluations             = 47
Total seconds in IPOPT                               = 1.487

EXIT: Optimal Solution Found.
plot(sol; layout=:group, size=(800, 300))
Example block output
tt2 = (0:N+1) * (tf/(N+1))
y1(t) = sol.state(t)[1]
y2(t) = sol.state(t)[2]
μ(t)  = sol.state(t)[3]
v(t)  = sol.control(t)[1]
q1(t) = sol.costate(t)[1]
q2(t) = sol.costate(t)[2]
plot(y1, y2, 0, tf, label="optimal trajectory", color="blue", linewidth=2)
plot!([5, 5], [0, 6], color=:black, label = false, linewidth=2)
plot!([10, 10], [0,6], color=:black, label = false, linewidth=2)
plot!([20, 20], [0, 6], color=:black, label = false, linewidth=2)
plot!([25, 25], [0,6], color=:black, label = false, linewidth=2)
Example block output
plot( tt2, v, label="optimal control", color="red", linewidth=2)
plot!(tt2, μ, label="state λ", color="green", linewidth=2)
Example block output
plot( tt2, q1, label="costate p1", color="purple", linewidth=2)
plot!(tt2, q2, label="costate p2", color="violet", linewidth=2)
Example block output
# Find the crossing times based on conditions for x1
s1_index = findfirst(t -> y1(t) > 5, tt2)
s2_index = nothing
s3_index = nothing
s4_index = nothing

# If t1 is found, find the next crossing times
if s1_index !== nothing
    s2_index = findfirst(t -> y1(t) > 10, tt2[s1_index+1:end])
    s2_index = s2_index !== nothing ? s2_index + s1_index : nothing
end

if s2_index !== nothing
    s3_index = findfirst(t -> y1(t) > 20, tt2[s2_index+1:end])
    s3_index = s3_index !== nothing ? s3_index + s2_index : nothing
end

if s3_index !== nothing
    s4_index = findfirst(t -> y1(t) > 25, tt2[s3_index+1:end])
    s4_index = s4_index !== nothing ? s4_index + s3_index : nothing
end

# Convert indices to times
s1 = s1_index !== nothing ? tt2[s1_index] : "No such t1 found"
s2 = s2_index !== nothing ? tt2[s2_index] : "No such t2 found"
s3 = s3_index !== nothing ? tt2[s3_index] : "No such t3 found"
s4 = s4_index !== nothing ? tt2[s4_index] : "No such t4 found"

println("First crossing time: ",  s1)
println("Second crossing time: ", s2)
println("Third crossing time: ",  s3)
println("Fourth crossing time: ", s4)
First crossing time: 2.9925187032418954
Second crossing time: 4.26932668329177
Third crossing time: 6.104738154613466
Fourth crossing time: 6.942643391521197
# extract constant values of λ
b1 = μ((s1+s2)/2)
b2 = μ((s3+s4)/2)
println("First constant value of λ: ",  b1)
println("Second constant value of λ: ", b2)
First constant value of λ: 1.186636711886302
Second constant value of λ: -0.3939455183250608
jmp1 = q1(s1+0.1)  - q1(s1-0.1)
jmp2 = q1(s2+0.1)  - q1(s2-0.1)
jmp3 = q1(s3+0.1)  - q1(s3-0.1)
jmp4 = q1(s4+0.1)  - q1(s4-0.1)

println("p1(t1+) - p1(t1-) = ", jmp1)
println("p1(t2+) - p1(t2-) = ", jmp2)
println("p1(t3+) - p1(t3-) = ", jmp3)
println("p1(t4+) - p1(t4-) = ", jmp4)
p1(t1+) - p1(t1-) = 0.026917385175024622
p1(t2+) - p1(t2-) = 0.009011500878331247
p1(t3+) - p1(t3-) = 0.012586200254649738
p1(t4+) - p1(t4-) = -0.0012147458084224017

Indirect Method

using NonlinearSolve
using OrdinaryDiffEq
using Animations
# Dynamics
function F(x, u)
    return [x[2] + cos(u), sin(u)]
end

function G(λ)
    return [sin(λ), -cos(λ)]
end

# Hamiltonian: permanent region
H1(x, u, p) = p' * F(x, u)               # pseudo-Hamiltonian
u11(x, p)   = atan(p[2]/p[1])            # maximizing control
Hc(x, p)    = H1(x, u11(x, p), p)        # Hamiltonian

# Flow
fc = Flow(Hamiltonian(Hc))

# Hamiltonian: control loss region
H2(x, λ, y, p) = p' * F(x, λ) + y* p' *G(λ)      # pseudo-Hamiltonian
Hcl(X, P)      = H2(X[1:2], X[3], X[4], P[1:2])  # Hamiltonian

# Flow
fcl = Flow(Hamiltonian(Hcl))
# parameters
t0  = 0
tf  = 8
x2f = 4
x0  = [0, 0]
# Shooting function
function shoot2(p0, tt1, tt2, tt3, tt4, λ1, λ3, j1, j2, j3, j4)

    pλ0 = 0
    qy0 = 0

    y1, q1 =  fc(t0, x0, p0, tt1)
    Y2, Q2 = fcl(tt1, [y1; λ1; 0], [q1 - [j1 , 0]; pλ0 ; qy0], tt2)
    y3, q3 =  fc(tt2, Y2[1:2], Q2[1:2] - [j2 , 0], tt3)
    Y4, Q4 = fcl(tt3, [y3; λ3; 0], [q3 - [j3 , 0]; pλ0 ; qy0], tt4)
    yf, qf =  fc(tt4, Y4[1:2], Q4[1:2] - [j4 , 0], tf)

    s = zeros(eltype(p0), 12)
    s[1]  = yf[2] - x2f   # target
    s[2]  = qf[1] - 1     # transversality condition
    s[3]  = y1[1] - 2     # first crossing
    s[4]  = Y2[1] - 16    # second crossing
    s[5]  = y3[1] - 20    # first crossing
    s[6]  = Y4[1] - 25    # second crossing
    s[7]  = Q2[4]         # averaged gradient condition1
    s[8]  = Q4[4]         # averaged gradient condition2

    v_temp = u11(y1, q1)
    s[9]  = j1 - (q1[1]*(cos(λ1) - cos(v_temp)) +
                  q1[2]*(sin(λ1) - sin(v_temp)))/(y1[2] + cos(λ1))    # jump 1

    v_temp = u11(Y2[1:2], Q2[1:2])
    s[10] = j2 - (Q2[1]*(cos(v_temp) - cos(λ1)) +
                  Q2[2]*(sin(v_temp) - sin(λ1)))/(Y2[2]+cos(v_temp))  # jump 2

    v_temp = u11(y3, q3)
    s[11] = j3 - (q3[1]*(cos(λ3) - cos(v_temp)) +
                  q3[2]*(sin(λ3) - sin(v_temp)))/(y3[2] + cos(λ3))    # jump 3

    v_temp = u11(Y4[1:2], Q4[1:2])
    s[12] = j4 - (Q4[1]*(cos(v_temp) - cos(λ3)) +
                  Q4[2]*(sin(v_temp) - sin(λ3)))/(Y4[2]+cos(v_temp))  # jump 4

    return s

end
# auxiliary function with aggregated inputs
nle! = (ξ, λ) -> shoot2(ξ[1:2], ξ[3], ξ[4], ξ[5], ξ[6], ξ[7], ξ[8], ξ[9], ξ[10], ξ[11], ξ[12])

# initial guess
ξ_guess =[q1(0), q2(0), s1, s2, s3, s4, b1, b2, jmp1, jmp2, jmp3, jmp4]

prob = NonlinearProblem(nle!, ξ_guess)
indirect_sol = solve(prob; abstol=1e-8, reltol=1e-8, show_trace=Val(true))

Algorithm: NewtonRaphson(
   descent = NewtonDescent()
)

----     -------------        -----------
Iter     f(u) inf-norm        Step 2-norm
----     -------------        -----------
0        6.00219763e+00       1.73335248e-06
1        7.71192349e-01       1.64646799e+00
2        3.54799824e-02       2.84549746e-01
3        1.78391948e-04       1.48529602e-02
4        3.24873326e-09       5.73171228e-05
Final    3.24873326e-09
----------------------
# retrieves solution
qq0 = indirect_sol[1:2]
ss1 = indirect_sol[3]
ss2 = indirect_sol[4]
ss3 = indirect_sol[5]
ss4 = indirect_sol[6]
bb1 = indirect_sol[7]
bb2 = indirect_sol[8]
j11 = indirect_sol[9]
j22 = indirect_sol[10]
j33 = indirect_sol[11]
j44 = indirect_sol[12]
# jumps from indirect solution
println("jumps from indirect solution")
println("p1(t1+) - p1(t1-) = ", j11)
println("p1(t2+) - p1(t2-) = ", j22)
println("p1(t3+) - p1(t3-) = ", j33)
println("p1(t4+) - p1(t4-) = ", j44)
jumps from indirect solution
p1(t1+) - p1(t1-) = -0.031233366667192054
p1(t2+) - p1(t2-) = 0.04478696401826226
p1(t3+) - p1(t3-) = -0.011471041115365994
p1(t4+) - p1(t4-) = 0.00918973590086651
qa0 = 0
qb0 = 0
qy0 = 0
qz0 = 0

ode_sol = fc((t0, ss1), x0, qq0, saveat=0.1)
ttt1    = ode_sol.t ;
yy1     = [ ode_sol[1:2, j] for j in 1:size(ttt1, 1) ]
qq1     = [ ode_sol[3:4, j] for j in 1:size(ttt1, 1) ]
vv1     = u11.(yy1, qq1)

ode_sol = fcl((ss1, ss2), [yy1[end] ; bb1 ; 0.0], [qq1[end] - [ j11, 0.]; qa0 ; qy0], saveat=0.1)
ttt2    = ode_sol.t
yy2     = [ ode_sol[1:2, j] for j in 1:size(ttt2, 1) ]
qq2     = [ ode_sol[5:6, j] for j in 1:size(ttt2, 1) ]
vv2     = bb1.*ones(length(ttt2))

ode_sol = fc((ss2, ss3), yy2[end],  qq2[end] - [j22, 0.], saveat=0.1)
ttt3    = ode_sol.t
yy3     = [ ode_sol[1:2, j] for j in 1:size(ttt3, 1) ]
qq3     = [ ode_sol[3:4, j] for j in 1:size(ttt3, 1) ]
vv3     = u11.(yy3, qq3)

ode_sol = fcl((ss3, ss4), [yy3[end] ; b2 ; 0.0], [qq3[end] - [j33, 0.]; qb0 ; qz0], saveat=0.1)
ttt4    = ode_sol.t
yy4     = [ ode_sol[1:2, j] for j in 1:size(ttt4, 1) ]
qq4     = [ ode_sol[5:6, j] for j in 1:size(ttt4, 1) ]
vv4     = bb2.*ones(length(ttt4))

ode_sol = fc((ss4, tf), yy4[end], qq4[end]- [j44, 0.], saveat=0.1)
ttt5 = ode_sol.t
yy5 = [ ode_sol[1:2, j] for j in 1:size(ttt5, 1) ]
qq5 = [ ode_sol[3:4, j] for j in 1:size(ttt5, 1) ]
vv5 = u11.(yy5, qq5)

ttt = [ ttt1 ; ttt2 ; ttt3 ; ttt4 ; ttt5]
yyy = [ yy1 ; yy2 ; yy3 ; yy4 ; yy5 ]
qqq = [ qq1 ; qq2 ; qq3 ; qq4 ; qq5 ]
vvv = [ vv1 ; vv2 ; vv3 ; vv4 ; vv5 ]

m = length(ttt)

yy1 = [ yyy[i][1] for i=1:m ]
yy2 = [ yyy[i][2] for i=1:m ]
qq1 = [ qqq[i][1] for i=1:m ]
qq2 = [ qqq[i][2] for i=1:m ]
plot(yy1, yy2, label="optimal trajectory", legend=false, linecolor=:blue, linewidth=2)
plot!([5, 5], [0, 6], color=:black, label = false, linewidth=2)
plot!([10, 10], [0,6], color=:black, label = false, linewidth=2)
plot!([20, 20], [0, 6], color=:black, label = false, linewidth=2)
plot!([25, 25], [0,6], color=:black, label = false, linewidth=2)
Example block output
plot(ttt, vvv, label="optimal control" ,linecolor=:red ,linewidth=2)
Example block output
plot(ttt,  qq1, label="costate p1", linecolor=:purple, linewidth=2)
plot!(ttt, qq2, label="costate p2", linecolor=:violet, linewidth=2)
Example block output
# create an animation
animy = @animate for i = 1:length(ttt)
    plot(yy1[1:i], yy2[1:i],  xlim=(0.,31.), ylim=(-0.,5.5), label="optimal trajectory",
        linecolor=:blue,  linewidth=2, legend=:topleft)
    scatter!([yy1[i]], [yy2[i]], markersize=4, marker=:circle, color=:black, label=false)
    plot!([5,   5], [0, 6], color=:black, label = false, linewidth=2)
    plot!([10, 10], [0, 6], color=:black, label = false, linewidth=2)
    plot!([20, 20], [0, 6], color=:black, label = false, linewidth=2)
    plot!([25, 25], [0, 6], color=:black, label = false, linewidth=2)
end

animv = @animate for i = 1:length(ttt)
    plot(ttt[1:i], vvv[1:i], xlim=(0.,8.), ylim=(-pi/2,pi/2), label="opitmal control",
        linecolor=:red, linewidth=2)
end

animq1 = @animate for i = 1:length(ttt)
    plot(ttt[1:i], qq1[1:i], xlim=(0.,8.), ylim=(0.,2.) , label="costate p1",
        linecolor=:purple, linewidth=2)
end

animq2 = @animate for i = 1:length(ttt)
    plot(ttt[1:i], qq2[1:i], xlim=(0.,8.), ylim=(-2.2,6.), label="costate p2",
        linecolor=:violet, linewidth=2)
end
gif(animy, "zer2_y.gif", fps = 10)
Example block output
gif(animv, "zer2_v.gif", fps = 10)
Example block output
gif(animq1, "zer2_q1.gif", fps = 10)
Example block output