Build State Equations with ModelingToolkit.jl
OptControl.jl
support ODESystem from ModelingToolkit.jl. ModelingToolkit.jl is very powerful and using ModelingToolkit.jl is strongly recommended. generateMTKcodes
is the same as generateJuMPcodes
, but f
is form of ODESystem from ModelingToolkit.jl.
OptControl.generateMTKcodes
— FunctiongenerateMTKcodes(
L,
F::ModelingToolkit.AbstractSystem,
state,
u,
tspan,
t0,
tf
) -> Any
generateMTKcodes(
L,
F::ModelingToolkit.AbstractSystem,
state,
u,
tspan,
t0,
tf,
Φ
) -> Any
generateMTKcodes(
L,
F::ModelingToolkit.AbstractSystem,
state,
u,
tspan,
t0,
tf,
Φ,
tf_constraint;
state_ub,
state_lb,
u_ub,
u_lb,
N,
discretization,
model_name,
writeFilePath,
tolerance
) -> Any
generateMTKcodes
will transform odesystem to control problem and generate the solution code that using JuMP.jl
to solve problem
A general form of optimal control problem:
\[min (\Phi(\boldsymbol{x}(t_f),t_f)+\int_{t_0}^{t_f} L[\boldsymbol{x}(t),\boldsymbol{u}(t),t]dt) \\ s.t. \hspace{0.4cm} \dot{\boldsymbol{x}} =f[\boldsymbol{x}(t),\boldsymbol{u}(t),t]\]
args:
L
: above L, function of state and u.
\[L[\boldsymbol{x}(t),\boldsymbol{u}(t),t]\]
F
: above f, in the form ofModelingToolkit.AbstractSystem
,
\[f[\boldsymbol{x}(t),\boldsymbol{u}(t),t]\]
state
: states(variable) inF
, from states ofModelingToolkit.ODESystem
u
: control variable u inF
, from parameters ofModelingToolkit.ODESystem
tspan
: time fieldt0
: initial value attspan[1]
, length oft0
must be equal to length of statetf
: final(end) value attspan[2]
, length oftf
must be equal to length of stateΦ
: above Φ, default: nothing
\[\Phi(\boldsymbol{x}(t_f),t_f)\]
tf_constraint
: special requirements for end time, default: nothing. Example:
\[x_{1}+x_{2}=0\]
state_ub
: state's upper limit, length ofstate_ub
must be equal to length of state, default: nothingstate_lb
: state's lower limit, length ofstate_lb
must be equal to length of state, default: nothingu_ub
: u's upper limit, length ofstate_ub
must be equal to length of u, default: nothingu_lb
: u's lower limit, length ofstate_lb
must be equal to length of u, default: nothingN
: Number of discrete, default: 1000
\[dt = (endTime - startTime) / N\]
discretization
: discretization methods, default: "trapezoidal"model_name
: name of JuMP model, default: "model"writeFilePath
: path of generated code, default: nothingtolerance
: acceptable_tol of Ipopt, default: 1.0e-6
Example:
using OptControl,ModelingToolkit
@parameters t
ps = @parameters σ = 1.0 ρ = 1.0 β = 1.0
st = @variables x(t) y(t) z(t)
D = Differential(t)
# Define a differential equation
eqs = [D(x) ~ σ * (y - x),
D(y) ~ x * (ρ - z) - y,
D(z) ~ x * y - β * z]
@named sys = ODESystem(eqs, t, st, ps)
# toexpr(eqs)
sys = structural_simplify(sys)
L = 0.5 * (x^2 + y^2 + β^2)
t0 = [1.0, 1.0, 1.0]
tf = [0.0, 0.0, 0.0]
tspan = (0.0, 2.0)
N = 100
sol = OptControl.generateMTKcodes(L, sys, states(sys), [β], tspan, t0, tf;
N=N, writeFilePath="test.jl")
Example 1: MTK Equations
A example from ModelingToolkit.jl document,
If we want to transform it to control problem, we treat $σ,ρ,β$ as control variables and $x,y,z$ as state variables( It's a math experiment, and it's meaningless in physics). We make $x,y,z$ change as we want by $σ,ρ,β$ with minimun cost $0.5 * (β^2 + σ^2 + ρ^2)$.
Control $x,y,z$ from $[1.0,1.0,1.0]$ to $[0.0,0.0,0.0]$ in 2 seconds.
using OptControl,ModelingToolkit, Test
@parameters t
ps = @parameters σ = 1.0 ρ = 1.0 β = 1.0
st = ModelingToolkit.@variables x(t) y(t) z(t)
D = Differential(t)
eqs = [D(x) ~ σ * (y - x),
D(y) ~ x * (ρ - z) - y,
D(z) ~ x * y - β * z]
@named sys = ODESystem(eqs, t, st, ps)
L = 0.5 * (β^2 + σ^2 + ρ^2)
t0 = [1.0, 1.0, 1.0]
tf = [0.0, 0.0, 0.0]
tspan = (0.0, 2.0)
N = 100
sol = OptControl.generateMTKcodes(L, sys, states(sys), [σ, ρ, β], tspan, t0, tf;
N=N)
@test isapprox.(0.0, sol[1][end, :], atol=1.0e-10) == [true, true, true]
Test Passed
Example 2: RC model
This is Acausal Component-Based Modeling the RC Circuit from ModelingToolkit.jl document
After defining components, solve the give the initial value and problem.
sys = structural_simplify(rc_model)
u0 = [
capacitor.v => 0.0
]
prob = ODAEProblem(sys, u0, (0, 10.0))
sol = solve(prob, Tsit5())
plot(sol)
To be a control problem that make $capacitor_+v$ change as we want by control variable $source_+V$, we assume parameter $source_+V$ is changeable.
using OptControl, ModelingToolkit, Test
@variables t
@connector function Pin(; name)
sts = @variables v(t) = 1.0 i(t) = 1.0 [connect = Flow]
ODESystem(Equation[], t, sts, []; name=name)
end
function Ground(; name)
@named g = Pin()
eqs = [g.v ~ 0]
compose(ODESystem(eqs, t, [], []; name=name), g)
end
function OnePort(; name)
@named p = Pin()
@named n = Pin()
sts = @variables v(t) = 1.0 i(t) = 1.0
eqs = [
v ~ p.v - n.v
0 ~ p.i + n.i
i ~ p.i
]
compose(ODESystem(eqs, t, sts, []; name=name), p, n)
end
function Resistor(; name, R=1.0)
@named oneport = OnePort()
@unpack v, i = oneport
ps = @parameters R = R
eqs = [
v ~ i * R
]
extend(ODESystem(eqs, t, [], ps; name=name), oneport)
end
function Capacitor(; name, C=1.0)
@named oneport = OnePort()
@unpack v, i = oneport
ps = @parameters C = C
D = Differential(t)
eqs = [
D(v) ~ i / C
]
extend(ODESystem(eqs, t, [], ps; name=name), oneport)
end
function ConstantVoltage(; name, V=1.0)
@named oneport = OnePort()
@unpack v = oneport
ps = @parameters V = V
eqs = [
V ~ v
]
extend(ODESystem(eqs, t, [], ps; name=name), oneport)
end
R = 1.0
C = 1.0
V = 1.0
@named resistor = Resistor(R=R)
@named capacitor = Capacitor(C=C)
@named source = ConstantVoltage(V=V)
@named ground = Ground()
rc_eqs = [
connect(source.p, resistor.p)
connect(resistor.n, capacitor.p)
connect(capacitor.n, source.n)
connect(capacitor.n, ground.g)
]
@named _rc_model = ODESystem(rc_eqs, t)
@named rc_model = compose(_rc_model,
[resistor, capacitor, source, ground])
sys = structural_simplify(rc_model)
L = 0.5 * (source.V^2)
t0 = [1.0]
tf = [3.0]
tspan = (0.0, 1.0)
N = 100
sol = OptControl.generateMTKcodes(L, sys, states(sys), [source.V], tspan, t0, tf;N=N)
@test isapprox.(3.0, sol[1][end, :], atol=1.0e-10) == [true]
Test Passed
To solve the control problem, don't need to solve ODEs or DAEs but define some control arguments after structure simplied.
In control problem, state
s are from state of ODESystem, and u
s are from parameters of ODESystem. States in ODESystem must be all passed to generateMTKcodes
, and some of parameters can be used as control variables just like example above. You can use accessor functions states(sys)
and parameters(sys)
to see them.