OptControl
What is OptControl.jl?
OptControl.jl is a interface that use symbols to build optimal control problem based on Symbolics.jl, and then transforms optimal control problem to:
- Differential Equations Problems(DEP)
- Optimzation Problems(OP)
DEP can be solved by DifferentialEquations.jl and OP can be solved by JuMP.jl
Features of OptControl.jl:
Symbolics.jl
makes problem building very easy and user-friendly- Script generation provides a template of
JuMP.jl
solution code orDifferentialEquations.jl
solution code, and you can design your code based on template. - Get results directly if you want.
Citation
If you use OptControl in your work, please cite the this paper:
@article{yang2022optcontrol,
title={OptControl.jl: An Interpreter for Optimal Control Problem},
author={Jingyi Yang, Yuebao Yang, Mingtao Li},
journal={arXiv preprint arXiv:2207.13229},
year={2022},
primaryClass={math.OC}
}
Quick Start
Install
using Pkg
Pkg.add("OptControl")
Solve Problem
To solve optimal control problem:
\[min \int_{0}^{2} u^2dt \\s.t. \hspace{0.4cm} \dot{\boldsymbol{x}} =\begin{bmatrix}0&1 \\0&0\end{bmatrix}\boldsymbol{x}+ \begin{bmatrix}0\\1\end{bmatrix}u\\\boldsymbol{x}(0)=\begin{bmatrix}1\\1\end{bmatrix},\boldsymbol{x}(2)=\begin{bmatrix}0\\0\end{bmatrix}\]
Copy and run:
using Symbolics,OptControl
@variables t u x[1:2]
f = [0 1; 0 0] * x + [0, 1] * u
L = 0.5 * u^2
t0 = [1.0, 1.0]
tf = [0.0, 0.0]
tspan = (0.0, 2.0)
N = 100
sol = generateJuMPcodes(L, f, x, u, tspan, t0, tf; N=N,
writeFilePath="JuMPscript.jl")
In Current folder, you will see
CurrentFolder/
└──JuMPscript.jl
And you will see contents of JuMPscript.jl
begin
using Pkg
pkgNeeds = ["JuMP", "Ipopt", "Symbolics"]
alreadyGet = keys(Pkg.project().dependencies)
toAdd = [package for package in pkgNeeds if package ∉ alreadyGet]
isempty(toAdd) ? nothing : Pkg.add(toAdd)
end
using Symbolics,Ipopt,JuMP
#============Function Parts================#
discretization_trapezoidal = function (yi, h, index, args...)
return yi + h / 2 * (args[1] + args[2])
end
L_objectiveFunc = function (ˍ₋arg1,)
#= C:\Users\DELL\.julia\packages\SymbolicUtils\v2ZkM\src\code.jl:349 =#
#= C:\Users\DELL\.julia\packages\SymbolicUtils\v2ZkM\src\code.jl:350 =#
#= C:\Users\DELL\.julia\packages\SymbolicUtils\v2ZkM\src\code.jl:351 =#
begin
(*)(0.5, (^)(ˍ₋arg1[3], 2))
end
end
F_statesFunc = function (ˍ₋arg1,)
#= C:\Users\DELL\.julia\packages\SymbolicUtils\v2ZkM\src\code.jl:349 =#
#= C:\Users\DELL\.julia\packages\SymbolicUtils\v2ZkM\src\code.jl:350 =#
#= C:\Users\DELL\.julia\packages\SymbolicUtils\v2ZkM\src\code.jl:351 =#
begin
begin
#= C:\Users\DELL\.julia\packages\SymbolicUtils\v2ZkM\src\code.jl:444 =#
(SymbolicUtils.Code.create_array)(typeof(ˍ₋arg1), nothing, Val{1}(), Val{(2,)}(), (getindex)(ˍ₋arg1, 2), ˍ₋arg1[3])
end
end
end
#========== define model =============#
model = Model(Ipopt.Optimizer)
set_optimizer_attribute(model, "print_level", 0)
#========== define variables =============#
JuMP.@variable(model, x[1:100, j=1:2] )
JuMP.@variable(model, u[1:100, j=1:1] )
#========== boundary constraint =============#
JuMP.@NLconstraint(model, [j = 1:2],x[1, j] == [1.0, 1.0][j])
JuMP.@NLconstraint(model,
[j = [i for i in 1:2 if !isequal([0.0, 0.0][i],nothing)]],
x[end, j] == [0.0, 0.0][j])
#========== state constraint =============#
JuMP.@NLconstraint(model, [i = 1:99, j = 1:2],
x[i+1,j] == discretization_trapezoidal(x[i,:], 0.02, i,
F_statesFunc(append!([],x[i+1,:],u[i+1,:])),
F_statesFunc(append!([],x[i+0,:],u[i+0,:])),
)[j])
#========== objective =============#
_sum_ = [L_objectiveFunc(append!([],x[i,:],u[i,:])) for i in 1:100]
JuMP.@NLobjective(model, Min, sum(_sum_[i] for i in 1:100))
#================ solve ==================#
JuMP.optimize!(model)
(x,u) = (JuMP.value.(x)[:,:],JuMP.value.(u)[:,:])
Actually, OptControl.jl just run the generated script automatically. If you are familiar with JuMP.jl or DifferentialEquations.jl and want to do more things, just give writeFilePath
a value(name of Julia script).
Solution Handle
If you don't know JuMP.jl or DifferentialEquations.jl. Don't worry, just run and get the results. Do what you want with the results.
Get state:
x = sol[1]
100×2 Matrix{Float64}:
1.0 1.0
1.01947 0.946775
1.0377 0.876642
1.05455 0.807758
1.07002 0.740124
1.08416 0.673739
1.09699 0.608604
1.10852 0.544719
1.11879 0.482083
1.12782 0.420697
1.13563 0.36056
1.14225 0.301673
1.14771 0.244036
1.15202 0.187648
1.15523 0.13251
1.15734 0.0786212
1.15838 0.0259822
1.15839 -0.0254071
1.15738 -0.0755468
⋮
0.13861 -0.715141
0.124606 -0.685306
0.11121 -0.654221
0.0984493 -0.621886
0.0863474 -0.588302
0.0749297 -0.553469
0.0642211 -0.517386
0.0542468 -0.480053
0.0450315 -0.44147
0.0366004 -0.401638
0.0289785 -0.360557
0.0221907 -0.318226
0.0162619 -0.274645
0.0112174 -0.229814
0.00708187 -0.183734
0.00388048 -0.136405
0.00163818 -0.0878253
0.000379965 -0.0379965
0.0 0.0
Get u:
u = sol[2]
100×1 Matrix{Float64}:
-1.7845763373682157
-3.537912486353386
-3.475432109587295
-3.412951732821204
-3.3504713560551127
-3.2879909792890216
-3.22551060252293
-3.163030225756839
-3.1005498489907475
-3.038069472224657
-2.975589095458566
-2.913108718692475
-2.850628341926384
-2.788147965160293
-2.725667588394202
-2.663187211628111
-2.6007068348620197
-2.5382264580959286
-2.4757460813298375
⋮
1.4605176549338985
1.5229980316999896
1.5854784084660805
1.6479587852321715
1.7104391619982624
1.7729195387643535
1.8353999155304446
1.8978802922965357
1.9603606690626267
2.022841045828718
2.085321422594809
2.1478017993609
2.210282176126991
2.272762552893082
2.3352429296591732
2.3977233064252643
2.4602036831913554
2.5226840599574465
1.276962124170246