Skip to content

Commit 3228f29

Browse files
authoredApr 11, 2024··
Merge pull request #51 from JuliaControl/cleanup
Namespace, `NonLinMPC` and `MovingHorizonEsitimator` cleanup
2 parents 701d29b + c6fd512 commit 3228f29

File tree

6 files changed

+160
-151
lines changed

6 files changed

+160
-151
lines changed
 

‎Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ModelPredictiveControl"
22
uuid = "61f9bdb8-6ae4-484a-811f-bbf86720c31c"
33
authors = ["Francis Gagnon"]
4-
version = "0.20.1"
4+
version = "0.20.2"
55

66
[deps]
77
ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e"

‎src/controller/linmpc.jl

+4-4
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ struct LinMPC{
7676
d0, D̂0, D̂E,
7777
Ŷop, Dop,
7878
)
79-
init_optimization!(mpc, optim)
79+
init_optimization!(mpc, model, optim)
8080
return mpc
8181
end
8282
end
@@ -235,16 +235,16 @@ function LinMPC(
235235
end
236236

237237
"""
238-
init_optimization!(mpc::LinMPC, optim::JuMP.GenericModel)
238+
init_optimization!(mpc::LinMPC, model::LinModel, optim)
239239
240240
Init the quadratic optimization for [`LinMPC`](@ref) controllers.
241241
"""
242-
function init_optimization!(mpc::LinMPC, optim::JuMP.GenericModel)
242+
function init_optimization!(mpc::LinMPC, model::LinModel, optim)
243243
# --- variables and linear constraints ---
244244
con = mpc.con
245245
nΔŨ = length(mpc.ΔŨ)
246246
JuMP.set_silent(optim)
247-
limit_solve_time(mpc.optim, mpc.estim.model.Ts)
247+
limit_solve_time(mpc.optim, model.Ts)
248248
@variable(optim, ΔŨvar[1:nΔŨ])
249249
A = con.A[con.i_b, :]
250250
b = con.b[con.i_b]

‎src/controller/nonlinmpc.jl

+62-60
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ struct NonLinMPC{
7777
d0, D̂0, D̂E,
7878
Ŷop, Dop,
7979
)
80-
init_optimization!(mpc, optim)
80+
init_optimization!(mpc, model, optim)
8181
return mpc
8282
end
8383
end
@@ -277,11 +277,11 @@ function addinfo!(info, mpc::NonLinMPC)
277277
end
278278

279279
"""
280-
init_optimization!(mpc::NonLinMPC, optim::JuMP.GenericModel)
280+
init_optimization!(mpc::NonLinMPC, model::SimModel, optim)
281281
282282
Init the nonlinear optimization for [`NonLinMPC`](@ref) controllers.
283283
"""
284-
function init_optimization!(mpc::NonLinMPC, optim::JuMP.GenericModel{JNT}) where JNT<:Real
284+
function init_optimization!(mpc::NonLinMPC, model::SimModel, optim)
285285
# --- variables and linear constraints ---
286286
C, con = mpc.C, mpc.con
287287
nΔŨ = length(mpc.ΔŨ)
@@ -300,65 +300,11 @@ function init_optimization!(mpc::NonLinMPC, optim::JuMP.GenericModel{JNT}) where
300300
JuMP.set_attribute(optim, "nlp_scaling_max_gradient", 10.0/C)
301301
end
302302
end
303-
model = mpc.estim.model
304-
nu, ny, nx̂, Hp, ng = model.nu, model.ny, mpc.estim.nx̂, mpc.Hp, length(con.i_g)
305-
# inspired from https://jump.dev/JuMP.jl/stable/tutorials/nonlinear/tips_and_tricks/#User-defined-operators-with-vector-outputs
306-
Jfunc, gfunc = let mpc=mpc, model=model, ng=ng, nΔŨ=nΔŨ, nŶ=Hp*ny, nx̂=nx̂, nu=nu, nU=Hp*nu
307-
Nc = nΔŨ + 3
308-
last_ΔŨtup_float, last_ΔŨtup_dual = nothing, nothing
309-
ΔŨ_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nΔŨ), Nc)
310-
Ŷ_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nŶ), Nc)
311-
U_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nU), Nc)
312-
g_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, ng), Nc)
313-
x̂_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nx̂), Nc)
314-
x̂next_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nx̂), Nc)
315-
u_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nu), Nc)
316-
û_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nu), Nc)
317-
Ȳ_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nŶ), Nc)
318-
Ū_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nU), Nc)
319-
function Jfunc(ΔŨtup::T...)::T where {T <: Real}
320-
ΔŨ1 = ΔŨtup[begin]
321-
if T == JNT
322-
last_ΔŨtup_float = ΔŨtup
323-
else
324-
last_ΔŨtup_dual = ΔŨtup
325-
end
326-
ΔŨ, Ŷ = get_tmp(ΔŨ_cache, ΔŨ1), get_tmp(Ŷ_cache, ΔŨ1)
327-
x̂, x̂next = get_tmp(x̂_cache, ΔŨ1), get_tmp(x̂next_cache, ΔŨ1)
328-
u, û = get_tmp(u_cache, ΔŨ1), get_tmp(û_cache, ΔŨ1)
329-
ΔŨ .= ΔŨtup
330-
Ŷ, x̂end = predict!(Ŷ, x̂, x̂next, u, û, mpc, model, ΔŨ)
331-
g = get_tmp(g_cache, ΔŨ1)
332-
g = con_nonlinprog!(g, mpc, model, x̂end, Ŷ, ΔŨ)
333-
U, Ȳ, Ū = get_tmp(U_cache, ΔŨ1), get_tmp(Ȳ_cache, ΔŨ1), get_tmp(Ū_cache, ΔŨ1)
334-
return obj_nonlinprog!(U, Ȳ, Ū, mpc, model, Ŷ, ΔŨ)::T
335-
end
336-
function gfunc_i(i, ΔŨtup::NTuple{N, T})::T where {N, T <:Real}
337-
ΔŨ1 = ΔŨtup[begin]
338-
g = get_tmp(g_cache, ΔŨ1)
339-
if T == JNT
340-
isnewvalue = (ΔŨtup !== last_ΔŨtup_float)
341-
isnewvalue && (last_ΔŨtup_float = ΔŨtup)
342-
else
343-
isnewvalue = (ΔŨtup !== last_ΔŨtup_dual)
344-
isnewvalue && (last_ΔŨtup_dual = ΔŨtup)
345-
end
346-
if isnewvalue
347-
ΔŨ, Ŷ = get_tmp(ΔŨ_cache, ΔŨ1), get_tmp(Ŷ_cache, ΔŨ1)
348-
x̂, x̂next = get_tmp(x̂_cache, ΔŨ1), get_tmp(x̂next_cache, ΔŨ1)
349-
u, û = get_tmp(u_cache, ΔŨ1), get_tmp(û_cache, ΔŨ1)
350-
ΔŨ .= ΔŨtup
351-
Ŷ, x̂end = predict!(Ŷ, x̂, x̂next, u, û, mpc, model, ΔŨ)
352-
g = con_nonlinprog!(g, mpc, model, x̂end, Ŷ, ΔŨ)
353-
end
354-
return g[i]::T
355-
end
356-
gfunc = [(ΔŨ...) -> gfunc_i(i, ΔŨ) for i in 1:ng]
357-
(Jfunc, gfunc)
358-
end
303+
Jfunc, gfunc = get_optim_functions(mpc, mpc.optim)
359304
register(optim, :Jfunc, nΔŨ, Jfunc, autodiff=true)
360305
@NLobjective(optim, Min, Jfunc(ΔŨvar...))
361-
if ng 0
306+
ny, nx̂, Hp = model.ny, mpc.estim.nx̂, mpc.Hp
307+
if length(con.i_g) 0
362308
for i in eachindex(con.Ymin)
363309
sym = Symbol("g_Ymin_$i")
364310
register(optim, sym, nΔŨ, gfunc[i], autodiff=true)
@@ -382,6 +328,62 @@ function init_optimization!(mpc::NonLinMPC, optim::JuMP.GenericModel{JNT}) where
382328
return nothing
383329
end
384330

331+
"""
332+
get_optim_functions(mpc::NonLinMPC, ::JuMP.GenericModel) -> Jfunc, gfunc
333+
334+
Get the objective `Jfunc` and constraints `gfunc` functions for [`NonLinMPC`](@ref).
335+
336+
Inspired from: [User-defined operators with vector outputs](https://jump.dev/JuMP.jl/stable/tutorials/nonlinear/tips_and_tricks/#User-defined-operators-with-vector-outputs)
337+
"""
338+
function get_optim_functions(mpc::NonLinMPC, ::JuMP.GenericModel{JNT}) where JNT<:Real
339+
model = mpc.estim.model
340+
nu, ny, nx̂, Hp = model.nu, model.ny, mpc.estim.nx̂, mpc.Hp
341+
ng, nΔŨ, nU, nŶ = length(mpc.con.i_g), length(mpc.ΔŨ), Hp*nu, Hp*ny
342+
Nc = nΔŨ + 3
343+
ΔŨ_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nΔŨ), Nc)
344+
Ŷ_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nŶ), Nc)
345+
U_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nU), Nc)
346+
g_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, ng), Nc)
347+
x̂_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nx̂), Nc)
348+
x̂next_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nx̂), Nc)
349+
u_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nu), Nc)
350+
û_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nu), Nc)
351+
Ȳ_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nŶ), Nc)
352+
Ū_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nU), Nc)
353+
function Jfunc(ΔŨtup::T...) where T<:Real
354+
ΔŨ1 = ΔŨtup[begin]
355+
ΔŨ, g = get_tmp(ΔŨ_cache, ΔŨ1), get_tmp(g_cache, ΔŨ1)
356+
for i in eachindex(ΔŨtup)
357+
ΔŨ[i] = ΔŨtup[i] # ΔŨ .= ΔŨtup seems to produce a type instability
358+
end
359+
= get_tmp(Ŷ_cache, ΔŨ1)
360+
x̂, x̂next = get_tmp(x̂_cache, ΔŨ1), get_tmp(x̂next_cache, ΔŨ1)
361+
u, û = get_tmp(u_cache, ΔŨ1), get_tmp(û_cache, ΔŨ1)
362+
Ŷ, x̂end = predict!(Ŷ, x̂, x̂next, u, û, mpc, model, ΔŨ)
363+
g = get_tmp(g_cache, ΔŨ1)
364+
g = con_nonlinprog!(g, mpc, model, x̂end, Ŷ, ΔŨ)
365+
U, Ȳ, Ū = get_tmp(U_cache, ΔŨ1), get_tmp(Ȳ_cache, ΔŨ1), get_tmp(Ū_cache, ΔŨ1)
366+
return obj_nonlinprog!(U, Ȳ, Ū, mpc, model, Ŷ, ΔŨ)::T
367+
end
368+
function gfunc_i(i, ΔŨtup::NTuple{N, T}) where {N, T<:Real}
369+
ΔŨ1 = ΔŨtup[begin]
370+
ΔŨ, g = get_tmp(ΔŨ_cache, ΔŨ1), get_tmp(g_cache, ΔŨ1)
371+
if any(new !== old for (new, old) in zip(ΔŨtup, ΔŨ)) # new ΔŨtup, update predictions:
372+
for i in eachindex(ΔŨtup)
373+
ΔŨ[i] = ΔŨtup[i] # ΔŨ .= ΔŨtup seems to produce a type instability
374+
end
375+
= get_tmp(Ŷ_cache, ΔŨ1)
376+
x̂, x̂next = get_tmp(x̂_cache, ΔŨ1), get_tmp(x̂next_cache, ΔŨ1)
377+
u, û = get_tmp(u_cache, ΔŨ1), get_tmp(û_cache, ΔŨ1)
378+
Ŷ, x̂end = predict!(Ŷ, x̂, x̂next, u, û, mpc, model, ΔŨ)
379+
g = con_nonlinprog!(g, mpc, model, x̂end, Ŷ, ΔŨ)
380+
end
381+
return g[i]::T
382+
end
383+
gfunc = [(ΔŨ...) -> gfunc_i(i, ΔŨ) for i in 1:ng]
384+
return Jfunc, gfunc
385+
end
386+
385387
"Set the nonlinear constraints on the output predictions `Ŷ` and terminal states `x̂end`."
386388
function setnonlincon!(mpc::NonLinMPC, ::NonLinModel)
387389
optim = mpc.optim

‎src/estimator/kalman.jl

+18-13
Original file line numberDiff line numberDiff line change
@@ -368,9 +368,12 @@ struct UnscentedKalmanFilter{NT<:Real, SM<:SimModel} <: StateEstimator{NT}
368368
B̂d::Matrix{NT}
369369
D̂d::Matrix{NT}
370370
P̂0::Hermitian{NT, Matrix{NT}}
371-
::Hermitian{NT, Matrix{NT}}
372-
::Hermitian{NT, Matrix{NT}}
371+
::Hermitian{NT, Matrix{NT}}
372+
::Hermitian{NT, Matrix{NT}}
373373
::Matrix{NT}
374+
::Hermitian{NT, Matrix{NT}}
375+
::Matrix{NT}
376+
Ŷm::Matrix{NT}
374377
::Int
375378
γ::NT
376379
::Vector{NT}
@@ -391,14 +394,16 @@ struct UnscentedKalmanFilter{NT<:Real, SM<:SimModel} <: StateEstimator{NT}
391394
P̂0 = Hermitian(P̂0, :L)
392395
= copy(P̂0)
393396
= zeros(NT, nx̂, nym)
397+
= Hermitian(zeros(NT, nym, nym), :L)
398+
X̂, Ŷm = zeros(NT, nx̂, nσ), zeros(NT, nym, nσ)
394399
return new{NT, SM}(
395400
model,
396401
lastu0, x̂, P̂,
397402
i_ym, nx̂, nym, nyu, nxs,
398403
As, Cs_u, Cs_y, nint_u, nint_ym,
399404
Â, B̂u, Ĉ, B̂d, D̂d,
400405
P̂0, Q̂, R̂,
401-
K̂,
406+
K̂, M̂, X̂, Ŷm,
402407
nσ, γ, m̂, Ŝ
403408
)
404409
end
@@ -565,18 +570,18 @@ noise, respectively.
565570
ISBN9780470045343.
566571
"""
567572
function update_estimate!(estim::UnscentedKalmanFilter{NT}, u, ym, d) where NT<:Real
568-
x̂, P̂, Q̂, R̂, K̂ = estim.x̂, estim.P̂, estim.Q̂, estim.R̂, estim.
573+
x̂, P̂, Q̂, R̂, K̂, M̂ = estim.x̂, estim.P̂, estim.Q̂, estim.R̂, estim.K̂, estim.
574+
X̂, Ŷm = estim.X̂, estim.Ŷm
569575
nym, nx̂, nσ = estim.nym, estim.nx̂, estim.
570576
γ, m̂, Ŝ = estim.γ, estim.m̂, estim.
571577
# --- initialize matrices ---
572-
X̂, X̂_next = Matrix{NT}(undef, nx̂, nσ), Matrix{NT}(undef, nx̂, nσ)
578+
X̂_next = Matrix{NT}(undef, nx̂, nσ)
573579
= Vector{NT}(undef, estim.model.nu)
574580
ŷm = Vector{NT}(undef, nym)
575581
= Vector{NT}(undef, estim.model.ny)
576-
Ŷm = Matrix{NT}(undef, nym, nσ)
577582
sqrt_P̂ = LowerTriangular{NT, Matrix{NT}}(Matrix{NT}(undef, nx̂, nx̂))
578583
# --- correction step ---
579-
sqrt_P̂.data .= cholesky(P̂).L
584+
sqrt_P̂ .= cholesky(P̂).L
580585
γ_sqrt_P̂ = lmul!(γ, sqrt_P̂)
581586
X̂ .=
582587
X̂[:, 2:nx̂+1] .+= γ_sqrt_P̂
@@ -589,7 +594,7 @@ function update_estimate!(estim::UnscentedKalmanFilter{NT}, u, ym, d) where NT<:
589594
X̄, Ȳm = X̂, Ŷm
590595
X̄ .=.-
591596
Ȳm .= Ŷm .- ŷm
592-
= Hermitian(Ȳm ** Ȳm' +, :L)
597+
.= Hermitian(Ȳm ** Ȳm' .+ R̂)
593598
mul!(K̂, X̄, lmul!(Ŝ, Ȳm'))
594599
rdiv!(K̂, cholesky(M̂))
595600
= ŷm
@@ -598,19 +603,18 @@ function update_estimate!(estim::UnscentedKalmanFilter{NT}, u, ym, d) where NT<:
598603
P̂_cor =- Hermitian(K̂ **', :L)
599604
# --- prediction step ---
600605
X̂_cor, sqrt_P̂_cor = X̂, sqrt_P̂
601-
sqrt_P̂_cor.data .= cholesky(P̂_cor).L
606+
sqrt_P̂_cor .= cholesky(P̂_cor).L
602607
γ_sqrt_P̂_cor = lmul!(γ, sqrt_P̂_cor)
603608
X̂_cor .= x̂_cor
604609
X̂_cor[:, 2:nx̂+1] .+= γ_sqrt_P̂_cor
605610
X̂_cor[:, nx̂+2:end] .-= γ_sqrt_P̂_cor
606-
X̂_next = similar(X̂_cor)
607611
for j in axes(X̂_next, 2)
608612
@views f̂!(X̂_next[:, j], û, estim, estim.model, X̂_cor[:, j], u, d)
609613
end
610614
x̂_next = mul!(x̂, X̂_next, m̂)
611615
X̄_next = X̂_next
612616
X̄_next .= X̂_next .- x̂_next
613-
.data .= X̄_next ** X̄_next' .+ # .data is necessary for Hermitians
617+
P̂ .= Hermitian(X̄_next ** X̄_next' .+)
614618
return nothing
615619
end
616620

@@ -781,7 +785,7 @@ end
781785
function init_estimate_cov!(
782786
estim::Union{KalmanFilter, UnscentedKalmanFilter, ExtendedKalmanFilter}, _ , _ , _
783787
)
784-
estim..data .= estim.P̂0 # .data is necessary for Hermitians
788+
estim.P̂ .= estim.P̂0
785789
return nothing
786790
end
787791

@@ -818,6 +822,7 @@ function update_estimate_kf!(estim::StateEstimator{NT}, u, ym, d, Â, Ĉm, P̂
818822
Q̂, R̂, M̂, K̂ = estim.Q̂, estim.R̂, estim.M̂, estim.
819823
nx̂, nu, ny = estim.nx̂, estim.model.nu, estim.model.ny
820824
x̂next, û, ŷ = Vector{NT}(undef, nx̂), Vector{NT}(undef, nu), Vector{NT}(undef, ny)
825+
P̂next = similar(P̂)
821826
mul!(M̂, P̂.data, Ĉm') # the ".data" weirdly removes a type instability in mul!
822827
rdiv!(M̂, cholesky!(Hermitian(Ĉm ** Ĉm' .+ R̂)))
823828
mul!(K̂, Â, M̂)
@@ -828,6 +833,6 @@ function update_estimate_kf!(estim::StateEstimator{NT}, u, ym, d, Â, Ĉm, P̂
828833
f̂!(x̂next, û, estim, estim.model, x̂, u, d)
829834
mul!(x̂next, K̂, v̂, 1, 1)
830835
estim.x̂ .= x̂next
831-
.data .=* (P̂ .-* Ĉm * P̂) *' .+ # .data is necessary for Hermitians
836+
P̂ .= Hermitian(* (P̂ .-* Ĉm * P̂) *' .+)
832837
return nothing
833838
end

0 commit comments

Comments
 (0)