Skip to content

Commit 58e355e

Browse files
authored
Merge pull request #52 from JuliaControl/adapt_linmpc
Added: adaptatation of `LinMPC` model through `setmodel!`
2 parents 3228f29 + 3353368 commit 58e355e

18 files changed

+178
-97
lines changed

docs/src/public/generic_func.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ initstate!
3737
setstate!
3838
```
3939

40+
## Set Plant Model
41+
42+
```@docs
43+
setmodel!
44+
```
45+
4046
## Quick Simulation
4147

4248
### Simulate

src/ModelPredictiveControl.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import OSQP, Ipopt
2323

2424
export SimModel, LinModel, NonLinModel
2525
export DiffSolver, RungeKutta
26-
export setop!, setstate!, updatestate!, evaloutput, linearize
26+
export setop!, setstate!, setmodel!, updatestate!, evaloutput, linearize
2727
export StateEstimator, InternalModel, Luenberger
2828
export SteadyKalmanFilter, KalmanFilter, UnscentedKalmanFilter, ExtendedKalmanFilter
2929
export MovingHorizonEstimator

src/controller/construct.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ function init_quadprog(::LinModel{NT}, Ẽ, S̃, M_Hp, Ñ_Hc, L_Hp) where {NT<:
548548
end
549549
"Return empty matrices if `model` is not a [`LinModel`](@ref)."
550550
function init_quadprog(::SimModel{NT}, Ẽ, S̃, M_Hp, Ñ_Hc, L_Hp) where {NT<:Real}
551-
= Hermitian(zeros(NT, 0, 0))
551+
= Hermitian(zeros(NT, 0, 0), :L)
552552
= zeros(NT, 0)
553553
p = zeros(NT, 1) # dummy value (updated just before optimization)
554554
return H̃, q̃, p
@@ -679,7 +679,7 @@ function relaxΔU(::SimModel{NT}, C, C_Δumin, C_Δumax, ΔUmin, ΔUmax, N_Hc) w
679679
ΔŨmin, ΔŨmax = [ΔUmin; NT[0.0]], [ΔUmax; NT[Inf]]
680680
A_ϵ = [zeros(NT, 1, length(ΔUmin)) NT[1.0]]
681681
A_ΔŨmin, A_ΔŨmax = -[I C_Δumin; A_ϵ], [I -C_Δumax; A_ϵ]
682-
Ñ_Hc = Hermitian([N_Hc zeros(NT, nΔU, 1);zeros(NT, 1, nΔU) C])
682+
Ñ_Hc = Hermitian([N_Hc zeros(NT, nΔU, 1);zeros(NT, 1, nΔU) C], :L)
683683
else # ΔŨ = ΔU (only hard constraints)
684684
ΔŨmin, ΔŨmax = ΔUmin, ΔUmax
685685
I_Hc = Matrix{NT}(I, nΔU, nΔU)

src/controller/execute.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,3 +474,10 @@ Call [`updatestate!`](@ref) on `mpc.estim` [`StateEstimator`](@ref).
474474
"""
475475
updatestate!(mpc::PredictiveController, u, ym, d=empty(mpc.estim.x̂)) = updatestate!(mpc.estim,u,ym,d)
476476
updatestate!(::PredictiveController, _ ) = throw(ArgumentError("missing measured outputs ym"))
477+
478+
"""
479+
setstate!(mpc::PredictiveController, x̂)
480+
481+
Set the estimate at `mpc.estim.x̂`.
482+
"""
483+
setstate!(mpc::PredictiveController, x̂) = (setstate!(mpc.estim, x̂); return mpc)

src/controller/explicitmpc.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ struct ExplicitMPC{NT<:Real, SE<:StateEstimator} <: PredictiveController{NT}
4242
Ewt = 0 # economic costs not supported for ExplicitMPC
4343
validate_weights(model, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt)
4444
# Matrix() call is needed to convert `Diagonal` to normal `Matrix`
45-
M_Hp, N_Hc, L_Hp = Hermitian(Matrix(M_Hp)), Hermitian(Matrix(N_Hc)), Hermitian(Matrix(L_Hp))
45+
M_Hp = Hermitian(Matrix(M_Hp), :L)
46+
N_Hc = Hermitian(Matrix(N_Hc), :L)
47+
L_Hp = Hermitian(Matrix(L_Hp), :L)
4648
# dummy vals (updated just before optimization):
4749
R̂y, R̂u, T_lastu = zeros(NT, ny*Hp), zeros(NT, nu*Hp), zeros(NT, nu*Hp)
4850
noR̂u = iszero(L_Hp)

src/controller/linmpc.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ struct LinMPC{
5050
Ewt = 0 # economic costs not supported for LinMPC
5151
validate_weights(model, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt)
5252
# Matrix() call is needed to convert `Diagonal` to normal `Matrix`
53-
M_Hp, N_Hc, L_Hp = Hermitian(Matrix(M_Hp)), Hermitian(Matrix(N_Hc)), Hermitian(Matrix(L_Hp))
53+
M_Hp = Hermitian(Matrix(M_Hp), :L)
54+
N_Hc = Hermitian(Matrix(N_Hc), :L)
55+
L_Hp = Hermitian(Matrix(L_Hp), :L)
5456
# dummy vals (updated just before optimization):
5557
R̂y, R̂u, T_lastu = zeros(NT, ny*Hp), zeros(NT, nu*Hp), zeros(NT, nu*Hp)
5658
noR̂u = iszero(L_Hp)

src/controller/nonlinmpc.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ struct NonLinMPC{
5151
= copy(model.yop) # dummy vals (updated just before optimization)
5252
validate_weights(model, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt)
5353
# Matrix() call is needed to convert `Diagonal` to normal `Matrix`
54-
M_Hp, N_Hc, L_Hp = Hermitian(Matrix(M_Hp)), Hermitian(Matrix(N_Hc)), Hermitian(Matrix(L_Hp))
54+
M_Hp = Hermitian(Matrix(M_Hp), :L)
55+
N_Hc = Hermitian(Matrix(N_Hc), :L)
56+
L_Hp = Hermitian(Matrix(L_Hp), :L)
5557
# dummy vals (updated just before optimization):
5658
R̂y, R̂u, T_lastu = zeros(NT, ny*Hp), zeros(NT, nu*Hp), zeros(NT, nu*Hp)
5759
noR̂u = iszero(L_Hp)

src/estimator/execute.jl

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,3 +221,60 @@ function validate_args(estim::StateEstimator, u, ym, d)
221221
nym = estim.nym
222222
size(ym) (nym,) && throw(DimensionMismatch("ym size $(size(ym)) ≠ meas. output size ($nym,)"))
223223
end
224+
225+
"""
226+
setstate!(estim::StateEstimator, x̂)
227+
228+
Set `estim.x̂` states to values specified by `x̂`.
229+
"""
230+
function setstate!(estim::StateEstimator, x̂)
231+
size(x̂) == (estim.nx̂,) || error("x̂ size must be $((estim.nx̂,))")
232+
estim.x̂[:] =
233+
return estim
234+
end
235+
236+
"""
237+
setmodel!(estim::StateEstimator, model::LinModel) -> estim
238+
239+
Set `estim.model` state-space matrices and operating points to `model` values.
240+
241+
Not supported by [`Luenberger`](@ref) and [`SteadyKalmanFilter`](@ref) estimators, use the
242+
time-varying [`KalmanFilter`](@ref) instead. The matrix dimensions and sample time must stay
243+
the same. The observability and controllability of the new augmented model is not verified.
244+
"""
245+
function setmodel!(estim::StateEstimator, model::LinModel)
246+
validate_model(estim, model)
247+
estim.model.A .= model.A
248+
estim.model.Bu .= model.Bu
249+
estim.model.C .= model.C
250+
estim.model.Bd .= model.Bd
251+
estim.model.Dd .= model.Dd
252+
estim.model.uop .= model.uop
253+
estim.model.yop .= model.yop
254+
estim.model.dop .= model.dop
255+
setmodel_estimator!(estim, model)
256+
return estim
257+
end
258+
259+
"Validate the dimensions and sample time of `model` against `estim.model`."
260+
function validate_model(estim::StateEstimator, model::LinModel)
261+
model.Ts == estim.model.Ts || throw(ArgumentError("model.Ts must be $(estim.model.Ts) s"))
262+
model.nu == estim.model.nu || throw(ArgumentError("model.nu must be $(estim.model.nu)"))
263+
model.nx == estim.model.nx || throw(ArgumentError("model.nx must be $(estim.model.nx)"))
264+
model.ny == estim.model.ny || throw(ArgumentError("model.ny must be $(estim.model.ny)"))
265+
model.nd == estim.model.nd || throw(ArgumentError("model.nd must be $(estim.model.nd)"))
266+
end
267+
268+
"Update the augmented model matrices of `estim` by default."
269+
function setmodel_estimator!(estim::StateEstimator, model::LinModel)
270+
As, Cs_u, Cs_y = estim.As, estim.Cs_u, estim.Cs_y
271+
Â, B̂u, Ĉ, B̂d, D̂d = augment_model(model, As, Cs_u, Cs_y, verify_obsv=false)
272+
estim. .=
273+
estim.B̂u .= B̂u
274+
estim.Ĉ .=
275+
estim.B̂d .= B̂d
276+
estim.D̂d .= D̂d
277+
estim.Ĉm .= @views Ĉ[estim.i_ym, :]
278+
estim.D̂dm .= @views D̂d[estim.i_ym, :]
279+
return nothing
280+
end

src/estimator/internal_model.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ function init_internalmodel(As, Bs, Cs, Ds)
197197
return Âs, B̂s
198198
end
199199

200+
"Do nothing else for `InternalModel` estimator."
201+
setmodel_estimator!(estim::InternalModel, ::LinModel) = nothing
202+
200203
@doc raw"""
201204
update_estimate!(estim::InternalModel, u, ym, d=empty(estim.x̂)) -> x̂d
202205

src/estimator/kalman.jl

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ function SteadyKalmanFilter(model::SM, i_ym, nint_u, nint_ym, Q̂, R̂) where {N
166166
return SteadyKalmanFilter{NT, SM}(model, i_ym, nint_u, nint_ym, Q̂, R̂)
167167
end
168168

169+
"Throw an error if `setmodel!` is called on a SteadyKalmanFilter"
170+
function setmodel_estimator!(estim::SteadyKalmanFilter, _ )
171+
error("SteadyKalmanFilter does not support setmodel! (use KalmanFilter instead)")
172+
end
169173

170174

171175
@doc raw"""
@@ -192,7 +196,7 @@ function update_estimate!(estim::SteadyKalmanFilter, u, ym, d=empty(estim.x̂))
192196
mul!(x̂next, B̂u, u, 1, 1)
193197
mul!(x̂next, B̂d, d, 1, 1)
194198
mul!(x̂next, K̂, v̂, 1, 1)
195-
x̂ .= x̂next
199+
estim.x̂ .= x̂next
196200
return nothing
197201
end
198202

@@ -347,6 +351,7 @@ function update_estimate!(estim::KalmanFilter, u, ym, d)
347351
return update_estimate_kf!(estim, u, ym, d, estim.Â, estim.Ĉm, estim.P̂, estim.x̂)
348352
end
349353

354+
350355
struct UnscentedKalmanFilter{NT<:Real, SM<:SimModel} <: StateEstimator{NT}
351356
model::SM
352357
lastu0::Vector{NT}
@@ -368,12 +373,13 @@ struct UnscentedKalmanFilter{NT<:Real, SM<:SimModel} <: StateEstimator{NT}
368373
B̂d::Matrix{NT}
369374
D̂d::Matrix{NT}
370375
P̂0::Hermitian{NT, Matrix{NT}}
371-
::Hermitian{NT, Matrix{NT}}
372-
::Hermitian{NT, Matrix{NT}}
376+
::Hermitian{NT, Matrix{NT}}
377+
::Hermitian{NT, Matrix{NT}}
373378
::Matrix{NT}
374379
::Hermitian{NT, Matrix{NT}}
375-
::Matrix{NT}
380+
::Matrix{NT}
376381
Ŷm::Matrix{NT}
382+
sqrtP̂::LowerTriangular{NT, Matrix{NT}}
377383
::Int
378384
γ::NT
379385
::Vector{NT}
@@ -396,14 +402,15 @@ struct UnscentedKalmanFilter{NT<:Real, SM<:SimModel} <: StateEstimator{NT}
396402
= zeros(NT, nx̂, nym)
397403
= Hermitian(zeros(NT, nym, nym), :L)
398404
X̂, Ŷm = zeros(NT, nx̂, nσ), zeros(NT, nym, nσ)
405+
sqrtP̂ = LowerTriangular(zeros(NT, nx̂, nx̂))
399406
return new{NT, SM}(
400407
model,
401408
lastu0, x̂, P̂,
402409
i_ym, nx̂, nym, nyu, nxs,
403410
As, Cs_u, Cs_y, nint_u, nint_ym,
404411
Â, B̂u, Ĉ, B̂d, D̂d,
405412
P̂0, Q̂, R̂,
406-
K̂, M̂, X̂, Ŷm,
413+
K̂, M̂, X̂, Ŷm, sqrtP̂,
407414
nσ, γ, m̂, Ŝ
408415
)
409416
end
@@ -571,21 +578,23 @@ noise, respectively.
571578
"""
572579
function update_estimate!(estim::UnscentedKalmanFilter{NT}, u, ym, d) where NT<:Real
573580
x̂, P̂, Q̂, R̂, K̂, M̂ = estim.x̂, estim.P̂, estim.Q̂, estim.R̂, estim.K̂, estim.
574-
X̂, Ŷm = estim.X̂, estim.Ŷm
575581
nym, nx̂, nσ = estim.nym, estim.nx̂, estim.
576582
γ, m̂, Ŝ = estim.γ, estim.m̂, estim.
583+
X̂, Ŷm = estim.X̂, estim.Ŷm
584+
sqrtP̂ = estim.sqrtP̂
577585
# --- initialize matrices ---
578-
X̂_next = Matrix{NT}(undef, nx̂, nσ)
586+
x̂next = Vector{NT}(undef, nx̂)
579587
= Vector{NT}(undef, estim.model.nu)
580588
ŷm = Vector{NT}(undef, nym)
581589
= Vector{NT}(undef, estim.model.ny)
582-
sqrt_P̂ = LowerTriangular{NT, Matrix{NT}}(Matrix{NT}(undef, nx̂, nx̂))
583590
# --- correction step ---
584-
sqrt_P̂ .= cholesky(P̂).L
585-
γ_sqrt_P̂ = lmul!(γ, sqrt_P̂)
591+
P̂_chol = sqrtP̂.data
592+
P̂_chol .=
593+
cholesky!(Hermitian(P̂_chol, :L)) # also modifies sqrtP̂
594+
γ_sqrtP̂ = lmul!(γ, sqrtP̂)
586595
X̂ .=
587-
X̂[:, 2:nx̂+1] .+= γ_sqrt_P̂
588-
X̂[:, nx̂+2:end] .-= γ_sqrt_P̂
596+
X̂[:, 2:nx̂+1] .+= γ_sqrtP̂
597+
X̂[:, nx̂+2:end] .-= γ_sqrtP̂
589598
for j in axes(Ŷm, 2)
590599
@views ĥ!(ŷ, estim, estim.model, X̂[:, j], d)
591600
@views Ŷm[:, j] .= ŷ[estim.i_ym]
@@ -594,27 +603,36 @@ function update_estimate!(estim::UnscentedKalmanFilter{NT}, u, ym, d) where NT<:
594603
X̄, Ȳm = X̂, Ŷm
595604
X̄ .=.-
596605
Ȳm .= Ŷm .- ŷm
597-
M̂ .= Hermitian(Ȳm ** Ȳm' .+)
606+
.data .= Ȳm ** Ȳm' .+
598607
mul!(K̂, X̄, lmul!(Ŝ, Ȳm'))
599608
rdiv!(K̂, cholesky(M̂))
600609
= ŷm
601610
v̂ .= ym .- ŷm
602-
x̂_cor =+*
603-
P̂_cor =- Hermitian(K̂ **', :L)
611+
x̂cor = x̂next
612+
x̂cor .=
613+
mul!(x̂cor, K̂, v̂, 1, 1)
614+
P̂cor = Hermitian(P̂ .-**', :L)
604615
# --- prediction step ---
605-
X̂_cor, sqrt_P̂_cor = X̂, sqrt_P̂
606-
sqrt_P̂_cor .= cholesky(P̂_cor).L
607-
γ_sqrt_P̂_cor = lmul!(γ, sqrt_P̂_cor)
608-
X̂_cor .= x̂_cor
609-
X̂_cor[:, 2:nx̂+1] .+= γ_sqrt_P̂_cor
610-
X̂_cor[:, nx̂+2:end] .-= γ_sqrt_P̂_cor
611-
for j in axes(X̂_next, 2)
612-
@views f̂!(X̂_next[:, j], û, estim, estim.model, X̂_cor[:, j], u, d)
616+
X̂cor, sqrtP̂cor = X̂, sqrtP̂
617+
P̂cor_chol = sqrtP̂cor.data
618+
P̂cor_chol .= P̂cor
619+
cholesky!(Hermitian(P̂cor_chol, :L)) # also modifies sqrtP̂cor
620+
γ_sqrtP̂cor = lmul!(γ, sqrtP̂cor)
621+
X̂cor .= x̂cor
622+
X̂cor[:, 2:nx̂+1] .+= γ_sqrtP̂cor
623+
X̂cor[:, nx̂+2:end] .-= γ_sqrtP̂cor
624+
X̂next = X̂cor
625+
for j in axes(X̂next, 2)
626+
@views x̂cor .= X̂cor[:, j]
627+
@views f̂!(X̂next[:, j], û, estim, estim.model, x̂cor, u, d)
613628
end
614-
x̂_next = mul!(x̂, X̂_next, m̂)
615-
X̄_next = X̂_next
616-
X̄_next .= X̂_next .- x̂_next
617-
P̂ .= Hermitian(X̄_next ** X̄_next' .+ Q̂)
629+
x̂next .= mul!(x̂, X̂next, m̂)
630+
X̄next = X̂next
631+
X̄next .= X̂next .- x̂next
632+
P̂next = P̂cor
633+
P̂next.data .= X̄next ** X̄next' .+
634+
estim.x̂ .= x̂next
635+
estim.P̂ .= P̂next
618636
return nothing
619637
end
620638

@@ -822,17 +840,17 @@ function update_estimate_kf!(estim::StateEstimator{NT}, u, ym, d, Â, Ĉm, P̂
822840
Q̂, R̂, M̂, K̂ = estim.Q̂, estim.R̂, estim.M̂, estim.
823841
nx̂, nu, ny = estim.nx̂, estim.model.nu, estim.model.ny
824842
x̂next, û, ŷ = Vector{NT}(undef, nx̂), Vector{NT}(undef, nu), Vector{NT}(undef, ny)
825-
P̂next = similar(P̂)
826843
mul!(M̂, P̂.data, Ĉm') # the ".data" weirdly removes a type instability in mul!
827-
rdiv!(M̂, cholesky!(Hermitian(Ĉm ** Ĉm' .+ R̂)))
844+
rdiv!(M̂, cholesky!(Hermitian(Ĉm ** Ĉm' .+, :L)))
828845
mul!(K̂, Â, M̂)
829846
ĥ!(ŷ, estim, estim.model, x̂, d)
830847
ŷm = @views ŷ[estim.i_ym]
831848
= ŷm
832849
v̂ .= ym .- ŷm
833850
f̂!(x̂next, û, estim, estim.model, x̂, u, d)
834851
mul!(x̂next, K̂, v̂, 1, 1)
852+
P̂next = Hermitian(Â * (P̂ .-* Ĉm * P̂) *' .+ Q̂, :L)
835853
estim.x̂ .= x̂next
836-
P̂ .= Hermitian(Â * (P̂ .-* Ĉm * P̂) *' .+ Q̂)
854+
estim.P̂ .= P̂next
837855
return nothing
838856
end

0 commit comments

Comments
 (0)