In this tutorial we present the indirect simple shooting method on a simple example.
using Suppressor # to suppress warnings
Let us start by importing the necessary packages.
using OptimalControl
using MINPACK # NLE solver
Let us consider the following optimal control problem:
with
t0 = 0
tf = 1
x0 = -1
xf = 0
α = 1.5
@def ocp begin
t ∈ [ t0, tf ], time
x ∈ R, state
u ∈ R, control
x(t0) == x0
x(tf) == xf
ẋ(t) == -x(t) + α * x(t)^2 + u(t)
∫( 0.5u(t)^2 ) → min
end;
nothing # hide
The pseudo-Hamiltonian of this problem is
where
since
where
!!! note "Our goal"
Our goal is to solve this (BVP).
To achive our goal, let us first introduce the pseudo-Hamiltonian vector field
and then denote by
To compute OptimalControl
package, we define the flow of the associated Hamiltonian vector field by:
u(x, p) = p
f = Flow(ocp, u)
nothing # hide
!!! note "Nota bene"
Actually, $z(\cdot, t_0, x_0, p_0)$ is also solution of
```math
\dot{z}(t) = \vec{\mathbf{H}}(z(t)), \quad z(t_0) = (x_0, p_0),
```
where $\mathbf{H}(z) = H(z, u(z))$ and $\vec{\mathbf{H}} = (\nabla_p \mathbf{H}, -\nabla_x \mathbf{H})$. This is what is actually computed by `Flow`.
We define also an auxiliary exponential map for clarity.
exp(p0; saveat=[]) = f((t0, tf), x0, p0, saveat=saveat).ode_sol
nothing # hide
Now, to solve the (BVP) we introduce the shooting function.
where
This is what we call the indirect simple shooting method.
S(p0) = exp(p0)(tf)[1] - xf; # shooting function
nle = (s, ξ) -> s[1] = S(ξ[1]) # auxiliary function
ξ = [ 0.0 ] # initial guess
global indirect_sol = # hide
@suppress_err begin # hide
fsolve(nle, ξ) # hide
indirect_sol = fsolve(nle, ξ) # resolution of S(p0) = 0
end # hide
p0_sol = indirect_sol.x[1] # costate solution
println("costate: p0 = ", p0_sol)
@suppress_err begin # hide
println("shoot: |S(p0)| = ", abs(S(p0_sol)), "\n")
end # hide
nothing # hide
We get:
times = range(t0, tf, length=2) # hide
p0min = -0.5 # hide
p0max = 2 # hide
plt_flow = plot() # hide
p0s = range(p0min, p0max, length=20) # hide
for i ∈ 1:length(p0s) # hide
sol = exp(p0s[i]) # hide
x = [sol(t)[1] for t ∈ sol.t] # hide
p = [sol(t)[2] for t ∈ sol.t] # hide
label = i==1 ? "extremals" : false # hide
plot!(plt_flow, x, p, color=:blue, label=label) # hide
end # hide
p0s = range(p0min, p0max, length=200) # hide
xs = zeros(length(p0s), length(times)) # hide
ps = zeros(length(p0s), length(times)) # hide
for i ∈ 1:length(p0s) # hide
sol = exp(p0s[i], saveat=times) # hide
xs[i, :] = [z[1] for z ∈ sol.(times)] # hide
ps[i, :] = [z[2] for z ∈ sol.(times)] # hide
end # hide
for j ∈ 1:length(times) # hide
label = j==1 ? "flow at times" : false # hide
plot!(plt_flow, xs[:, j], ps[:, j], color=:green, linewidth=2, label=label) # hide
end # hide
plot!(plt_flow, xlims = (-1.1, 1), ylims = (p0min, p0max)) # hide
plot!(plt_flow, [0, 0], [p0min, p0max], color=:black, xlabel="x", ylabel="p", label="x=xf") # hide
sol = exp(p0_sol) # hide
x = [sol(t)[1] for t ∈ sol.t] # hide
p = [sol(t)[2] for t ∈ sol.t] # hide
plot!(plt_flow, x, p, color=:red, linewidth=2, label="extremal solution") # hide
plot!(plt_flow, [x[end]], [p[end]], seriestype=:scatter, color=:green, label=false) # hide
plt_shoot = plot(xlims=(p0min, p0max), ylims=(-2, 4), xlabel="p₀", ylabel="y") # hide
plot!(plt_shoot, p0s, S, linewidth=2, label="S(p₀)", color=:green) # hide
plot!(plt_shoot, [p0min, p0max], [0, 0], color=:black, label="y=0") # hide
plot!(plt_shoot, [p0_sol, p0_sol], [-2, 0], color=:black, label="p₀ solution", linestyle=:dash) # hide
plot!(plt_shoot, [p0_sol], [0], seriestype=:scatter, color=:green, label=false) # hide
plot(plt_flow, plt_shoot, layout=(1,2), size=(800, 450)) # hide