Skip to content

Commit ac38d81

Browse files
committed
wip: multiple shooting transcription
1 parent affb5db commit ac38d81

File tree

11 files changed

+248
-173
lines changed

11 files changed

+248
-173
lines changed

docs/src/internals/predictive_control.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@ ModelPredictiveControl.linconstraint!(::PredictiveController, ::LinModel)
3636

3737
```@docs
3838
ModelPredictiveControl.optim_objective!(::PredictiveController)
39+
ModelPredictiveControl.set_warmstart!
3940
ModelPredictiveControl.getinput
4041
```

src/controller/construct.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,37 @@
1+
struct PredictiveControllerBuffer{NT<:Real}
2+
u::Vector{NT}
3+
::Vector{NT}
4+
::Vector{NT}
5+
::Vector{NT}
6+
U::Vector{NT}
7+
::Matrix{NT}
8+
::Matrix{NT}
9+
empty::Vector{NT}
10+
end
11+
12+
@doc raw"""
13+
PredictiveControllerBuffer(estim, transcription, Hp, Hc, nϵ)
14+
15+
Create a buffer for `PredictiveController` objects.
16+
17+
The buffer is used to store intermediate results during computation without allocating.
18+
"""
19+
function PredictiveControllerBuffer(
20+
estim::StateEstimator{NT}, transcription::TranscriptionMethod, Hp::Int, Hc::Int, nϵ::Int
21+
) where NT <: Real
22+
nu, ny, nd, nx̂ = estim.model.nu, estim.model.ny, estim.model.nd, estim.nx̂
23+
nZ̃ = get_nZ̃(estim, transcription, Hp, Hc, nϵ)
24+
u = Vector{NT}(undef, nu)
25+
= Vector{NT}(undef, nZ̃)
26+
= Vector{NT}(undef, nd*Hp)
27+
= Vector{NT}(undef, ny*Hp)
28+
U = Vector{NT}(undef, nu*Hp)
29+
= Matrix{NT}(undef, ny*Hp, nZ̃)
30+
= Matrix{NT}(undef, nu*Hp, nZ̃)
31+
empty = Vector{NT}(undef, 0)
32+
return PredictiveControllerBuffer{NT}(u, Z̃, D̂, Ŷ, U, Ẽ, S̃, empty)
33+
end
34+
135
"Include all the objective function weights of [`PredictiveController`](@ref)"
236
struct ControllerWeights{NT<:Real}
337
M_Hp::Hermitian{NT, Matrix{NT}}

src/controller/execute.jl

Lines changed: 40 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
@doc raw"""
22
initstate!(mpc::PredictiveController, u, ym, d=[]) -> x̂
33
4-
Init the states of `mpc.estim` [`StateEstimator`](@ref) and warm start `mpc.ΔŨ` at zero.
4+
Init the states of `mpc.estim` [`StateEstimator`](@ref) and warm start `mpc.` at zero.
55
"""
66
function initstate!(mpc::PredictiveController, u, ym, d=mpc.estim.buffer.empty)
7-
mpc.ΔŨ .= 0
7+
mpc. .= 0
88
return initstate!(mpc.estim, u, ym, d)
99
end
1010

@@ -67,8 +67,9 @@ function moveinput!(
6767
validate_args(mpc, ry, d, D̂, R̂y, R̂u)
6868
initpred!(mpc, mpc.estim.model, d, D̂, R̂y, R̂u)
6969
linconstraint!(mpc, mpc.estim.model)
70-
ΔŨ = optim_objective!(mpc)
71-
return getinput(mpc, ΔŨ)
70+
linconstrainteq!(mpc, mpc.transcription)
71+
= optim_objective!(mpc)
72+
return getinput(mpc, Z̃)
7273
end
7374

7475
@doc raw"""
@@ -323,6 +324,22 @@ function linconstraint!(mpc::PredictiveController, ::SimModel)
323324
return nothing
324325
end
325326

327+
linconstrainteq!(mpc::PredictiveController, transcription::SingleShooting) = nothing
328+
function linconstrainteq!(mpc::PredictiveController, transcription::MultipleShooting)
329+
nx̂, Fŝ = mpc.estim.nx̂, mpc.con.Fŝ
330+
Fŝ .= mpc.con.Bŝ
331+
mul!(Fŝ, mpc.con.Kŝ, mpc.estim.x̂0, 1, 1)
332+
mul!(Fŝ, mpc.con.Vŝ, mpc.estim.lastu0, 1, 1)
333+
if mpc.estim.model.nd 0
334+
mul!(Fŝ, mpc.con.Gŝ, mpc.d0, 1, 1)
335+
mul!(Fŝ, mpc.con.Jŝ, mpc.D̂0, 1, 1)
336+
end
337+
mpc.con.beq .= @. -Fŝ
338+
linconeq = mpc.optim[:linconstrainteq]
339+
JuMP.set_normalized_rhs(linconeq, mpc.con.beq)
340+
return nothing
341+
end
342+
326343
@doc raw"""
327344
predict!(Ŷ0, x̂0, _, _, _, mpc::PredictiveController, model::LinModel, ΔŨ) -> Ŷ0, x̂0end
328345
@@ -467,39 +484,21 @@ function obj_econ(::PredictiveController, ::SimModel, _ , ::AbstractVector{NT})
467484
end
468485

469486
@doc raw"""
470-
optim_objective!(mpc::PredictiveController) -> ΔŨ
487+
optim_objective!(mpc::PredictiveController) ->
471488
472-
Optimize the objective function of `mpc` [`PredictiveController`](@ref) and return the solution `ΔŨ`.
489+
Optimize the objective function of `mpc` [`PredictiveController`](@ref) and return the solution ``.
473490
474-
If supported by `mpc.optim`, it warm-starts the solver at:
475-
```math
476-
\mathbf{ΔŨ} =
477-
\begin{bmatrix}
478-
\mathbf{Δu}_{k-1}(k+0) \\
479-
\mathbf{Δu}_{k-1}(k+1) \\
480-
\vdots \\
481-
\mathbf{Δu}_{k-1}(k+H_c-2) \\
482-
\mathbf{0} \\
483-
ϵ_{k-1}
484-
\end{bmatrix}
485-
```
486-
where ``\mathbf{Δu}_{k-1}(k+j)`` is the input increment for time ``k+j`` computed at the
487-
last control period ``k-1``. It then calls `JuMP.optimize!(mpc.optim)` and extract the
488-
solution. A failed optimization prints an `@error` log in the REPL and returns the
489-
warm-start value. A failed optimization also prints [`getinfo`](@ref) results in
490-
the debug log [if activated](https://docs.julialang.org/en/v1/stdlib/Logging/#Example:-Enable-debug-level-messages).
491+
If first warm-starts the solver with [`set_warm_start!`](@ref). It then calls
492+
`JuMP.optimize!(mpc.optim)` and extract the solution. A failed optimization prints an
493+
`@error` log in the REPL and returns the warm-start value. A failed optimization also prints
494+
[`getinfo`](@ref) results in the debug log [if activated](https://docs.julialang.org/en/v1/stdlib/Logging/#Example:-Enable-debug-level-messages).
491495
"""
492496
function optim_objective!(mpc::PredictiveController{NT}) where {NT<:Real}
493497
model, optim = mpc.estim.model, mpc.optim
494-
nu, Hc = model.nu, mpc.Hc
495-
ΔŨvar::Vector{JuMP.VariableRef} = optim[:ΔŨvar]
496-
# initial ΔŨ (warm-start): [Δu_{k-1}(k); Δu_{k-1}(k+1); ... ; 0_{nu × 1}; ϵ_{k-1}]
497-
ΔŨ0 = mpc.buffer.ΔŨ
498-
ΔŨ0[1:(Hc*nu-nu)] .= @views mpc.ΔŨ[nu+1:Hc*nu]
499-
ΔŨ0[(Hc*nu-nu+1):(Hc*nu)] .= 0
500-
mpc.== 1 && (ΔŨ0[end] = mpc.ΔŨ[end])
501-
JuMP.set_start_value.(ΔŨvar, ΔŨ0)
502-
set_objective_linear_coef!(mpc, ΔŨvar)
498+
nu, Hc = model.nu, mpc.Hc
499+
Z̃var::Vector{JuMP.VariableRef} = optim[:Z̃var]
500+
Z̃0 = set_warmstart!(mpc, mpc.transcription, Z̃var)
501+
set_objective_linear_coef!(mpc, Z̃var)
503502
try
504503
JuMP.optimize!(optim)
505504
catch err
@@ -529,11 +528,11 @@ function optim_objective!(mpc::PredictiveController{NT}) where {NT<:Real}
529528
@debug info2debugstr(getinfo(mpc))
530529
end
531530
if iserror(optim)
532-
mpc.ΔŨ .= ΔŨ0
531+
mpc. .= Z̃0
533532
else
534-
mpc.ΔŨ .= JuMP.value.(ΔŨvar)
533+
mpc. .= JuMP.value.(Z̃var)
535534
end
536-
return mpc.ΔŨ
535+
return mpc.
537536
end
538537

539538
"By default, no need to modify the objective function."
@@ -549,17 +548,17 @@ function preparestate!(mpc::PredictiveController, ym, d=mpc.estim.buffer.empty)
549548
end
550549

551550
@doc raw"""
552-
getinput(mpc::PredictiveController, ΔŨ) -> u
551+
getinput(mpc::PredictiveController, ) -> u
553552
554-
Get current manipulated input `u` from a [`PredictiveController`](@ref) solution `ΔŨ`.
553+
Get current manipulated input `u` from a [`PredictiveController`](@ref) solution ``.
555554
556-
The first manipulated input ``\mathbf{u}(k)`` is extracted from the input increments vector
557-
``\mathbf{ΔŨ}`` and applied on the plant (from the receding horizon principle).
555+
The first manipulated input ``\mathbf{u}(k)`` is extracted from the decision vector
556+
``\mathbf{}`` and applied on the plant (from the receding horizon principle).
558557
"""
559-
function getinput(mpc, ΔŨ)
558+
function getinput(mpc, )
560559
Δu = mpc.buffer.u
561560
for i in 1:mpc.estim.model.nu
562-
Δu[i] = ΔŨ[i]
561+
Δu[i] = [i]
563562
end
564563
u = Δu
565564
u .+= mpc.estim.lastu0 .+ mpc.estim.model.uop

src/controller/explicitmpc.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
struct ExplicitMPC{NT<:Real, SE<:StateEstimator} <: PredictiveController{NT}
22
estim::SE
33
transcription::SingleShooting
4-
ΔŨ::Vector{NT}
5-
::Vector{NT}
4+
::Vector{NT}
5+
::Vector{NT}
66
Hp::Int
77
Hc::Int
88
::Int
@@ -62,13 +62,13 @@ struct ExplicitMPC{NT<:Real, SE<:StateEstimator} <: PredictiveController{NT}
6262
# dummy vals (updated just before optimization):
6363
d0, D̂0, D̂e = zeros(NT, nd), zeros(NT, nd*Hp), zeros(NT, nd + nd*Hp)
6464
Uop, Yop, Dop = repeat(model.uop, Hp), repeat(model.yop, Hp), repeat(model.dop, Hp)
65-
nΔŨ = size(Ẽ, 2)
66-
ΔŨ = zeros(NT, nΔŨ)
67-
buffer = PredictiveControllerBuffer{NT}(nu, ny, nd, Hp, Hc, nϵ)
65+
nZ̃ = get_nZ̃(estim, transcription, Hp, Hc, nϵ)
66+
= zeros(NT, nZ̃)
67+
buffer = PredictiveControllerBuffer(estim, transcription, Hp, Hc, nϵ)
6868
mpc = new{NT, SE}(
6969
estim,
7070
transcription,
71-
ΔŨ, ŷ,
71+
, ŷ,
7272
Hp, Hc, nϵ,
7373
weights,
7474
R̂u, R̂y,

src/controller/linmpc.jl

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ struct LinMPC{
1313
# different since solvers that support non-Float64 are scarce.
1414
optim::JM
1515
con::ControllerConstraint{NT, Nothing}
16-
ΔŨ::Vector{NT}
17-
::Vector{NT}
16+
::Vector{NT}
17+
::Vector{NT}
1818
Hp::Int
1919
Hc::Int
2020
::Int
@@ -75,12 +75,12 @@ struct LinMPC{
7575
# dummy vals (updated just before optimization):
7676
d0, D̂0, D̂e = zeros(NT, nd), zeros(NT, nd*Hp), zeros(NT, nd + nd*Hp)
7777
Uop, Yop, Dop = repeat(model.uop, Hp), repeat(model.yop, Hp), repeat(model.dop, Hp)
78-
nΔŨ = size(Ẽ, 2)
79-
ΔŨ = zeros(NT, nΔŨ)
80-
buffer = PredictiveControllerBuffer{NT}(nu, ny, nd, Hp, Hc, nϵ)
78+
nZ̃ = get_nZ̃(estim, transcription, Hp, Hc, nϵ)
79+
= zeros(NT, nZ̃)
80+
buffer = PredictiveControllerBuffer(estim, transcription, Hp, Hc, nϵ)
8181
mpc = new{NT, SE, TM, JM}(
8282
estim, transcription, optim, con,
83-
ΔŨ, ŷ,
83+
, ŷ,
8484
Hp, Hc, nϵ,
8585
weights,
8686
R̂u, R̂y,
@@ -266,29 +266,29 @@ Init the quadratic optimization for [`LinMPC`](@ref) controllers.
266266
function init_optimization!(mpc::LinMPC, model::LinModel, optim)
267267
# --- variables and linear constraints ---
268268
con = mpc.con
269-
nΔŨ = length(mpc.ΔŨ)
269+
nZ̃ = length(mpc.)
270270
JuMP.num_variables(optim) == 0 || JuMP.empty!(optim)
271271
JuMP.set_silent(optim)
272272
limit_solve_time(mpc.optim, model.Ts)
273-
@variable(optim, ΔŨvar[1:nΔŨ])
273+
@variable(optim, Z̃var[1:nZ̃])
274274
A = con.A[con.i_b, :]
275275
b = con.b[con.i_b]
276-
@constraint(optim, linconstraint, A*ΔŨvar .≤ b)
277-
Aeq = con.A
278-
beq = con.b
279-
@constraint(optim, lineqconstraint, Aeq*ΔŨvar .== beq)
280-
set_objective_hessian!(mpc, ΔŨvar)
276+
@constraint(optim, linconstraint, A*Z̃var .≤ b)
277+
Aeq = con.Aeq
278+
beq = con.beq
279+
@constraint(optim, linconstrainteq, Aeq*Z̃var .== beq)
280+
set_objective_hessian!(mpc, Z̃var)
281281
return nothing
282282
end
283283

284284
"For [`LinMPC`](@ref), set the QP linear coefficient `q̃` just before optimization."
285-
function set_objective_linear_coef!(mpc::LinMPC, ΔŨvar)
286-
JuMP.set_objective_coefficient(mpc.optim, ΔŨvar, mpc.q̃)
285+
function set_objective_linear_coef!(mpc::LinMPC, Z̃var)
286+
JuMP.set_objective_coefficient(mpc.optim, Z̃var, mpc.q̃)
287287
return nothing
288288
end
289289

290290
"Update the quadratic objective function for [`LinMPC`](@ref) controllers."
291-
function set_objective_hessian!(mpc::LinMPC, ΔŨvar)
292-
@objective(mpc.optim, Min, obj_quadprog(ΔŨvar, mpc.H̃, mpc.q̃))
291+
function set_objective_hessian!(mpc::LinMPC, Z̃var)
292+
@objective(mpc.optim, Min, obj_quadprog(Z̃var, mpc.H̃, mpc.q̃))
293293
return nothing
294294
end

src/controller/nonlinmpc.jl

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ struct NonLinMPC{
1616
# different since solvers that support non-Float64 are scarce.
1717
optim::JM
1818
con::ControllerConstraint{NT, GCfunc}
19-
ΔŨ::Vector{NT}
20-
::Vector{NT}
19+
::Vector{NT}
20+
::Vector{NT}
2121
Hp::Int
2222
Hc::Int
2323
::Int
@@ -91,12 +91,12 @@ struct NonLinMPC{
9191
d0, D̂0, D̂e = zeros(NT, nd), zeros(NT, nd*Hp), zeros(NT, nd + nd*Hp)
9292
Uop, Yop, Dop = repeat(model.uop, Hp), repeat(model.yop, Hp), repeat(model.dop, Hp)
9393
test_custom_functions(NT, model, JE, gc!, nc, Uop, Yop, Dop, p)
94-
nΔŨ = size(Ẽ, 2)
95-
ΔŨ = zeros(NT, nΔŨ)
96-
buffer = PredictiveControllerBuffer{NT}(nu, ny, nd, Hp, Hc, nϵ)
94+
nZ̃ = get_nZ̃(estim, transcription, Hp, Hc, nϵ)
95+
= zeros(NT, nZ̃)
96+
buffer = PredictiveControllerBuffer(estim, transcription, Hp, Hc, nϵ)
9797
mpc = new{NT, SE, TM, JM, PT, JEfunc, GCfunc}(
9898
estim, transcription, optim, con,
99-
ΔŨ, ŷ,
99+
, ŷ,
100100
Hp, Hc, nϵ,
101101
weights,
102102
JE, p,
@@ -484,17 +484,17 @@ Init the nonlinear optimization for [`NonLinMPC`](@ref) controllers.
484484
function init_optimization!(mpc::NonLinMPC, model::SimModel, optim)
485485
# --- variables and linear constraints ---
486486
con = mpc.con
487-
nΔŨ = length(mpc.ΔŨ)
487+
nZ̃ = length(mpc.)
488488
JuMP.num_variables(optim) == 0 || JuMP.empty!(optim)
489489
JuMP.set_silent(optim)
490490
limit_solve_time(mpc.optim, mpc.estim.model.Ts)
491-
@variable(optim, ΔŨvar[1:nΔŨ])
491+
@variable(optim, Z̃var[1:nZ̃])
492492
A = con.A[con.i_b, :]
493493
b = con.b[con.i_b]
494-
@constraint(optim, linconstraint, A*ΔŨvar .≤ b)
494+
@constraint(optim, linconstraint, A*Z̃var .≤ b)
495495
Aeq = con.A
496496
beq = con.b
497-
@constraint(optim, lineqconstraint, Aeq*ΔŨvar .== beq)
497+
@constraint(optim, lineqconstraint, Aeq*Z̃var .== beq)
498498
# --- nonlinear optimization init ---
499499
if mpc.== 1 && JuMP.solver_name(optim) == "Ipopt"
500500
C = mpc.weights.Ñ_Hc[end]
@@ -506,8 +506,8 @@ function init_optimization!(mpc::NonLinMPC, model::SimModel, optim)
506506
end
507507
end
508508
Jfunc, gfuncs = get_optim_functions(mpc, mpc.optim)
509-
@operator(optim, J, nΔŨ, Jfunc)
510-
@objective(optim, Min, J(ΔŨvar...))
509+
@operator(optim, J, nZ̃, Jfunc)
510+
@objective(optim, Min, J(Z̃var...))
511511
init_nonlincon!(mpc, model, gfuncs)
512512
set_nonlincon!(mpc, model, mpc.optim)
513513
return nothing

0 commit comments

Comments
 (0)