Skip to content

Commit e70dc2c

Browse files
committed
added : automatic model augmentation based on observability
1 parent 5b0b870 commit e70dc2c

File tree

9 files changed

+107
-56
lines changed

9 files changed

+107
-56
lines changed

docs/src/internals/state_estim.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
```@docs
66
ModelPredictiveControl.init_estimstoch
77
ModelPredictiveControl.augment_model
8+
ModelPredictiveControl.default_nint
89
ModelPredictiveControl.init_ukf
910
ModelPredictiveControl.init_internalmodel
1011
```

docs/src/manual/nonlinmpc.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ plot(sim!(model, 60, u), plotu=false)
5959
An [`UnscentedKalmanFilter`](@ref) estimates the plant state :
6060

6161
```@example 1
62-
estim = UnscentedKalmanFilter(model, σQ=[0.1, 0.5], σQ_int=[5.0], σR=[0.5])
62+
estim = UnscentedKalmanFilter(model, σQ=[0.1, 0.5], σR=[0.5], nint_ym=[1], σQ_int=[5.0])
6363
```
6464

6565
The standard deviation of the angular velocity ``ω`` is higher here (`σQ` second value)

example/juMPC.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ mpcluen = LinMPC(luenberger)
5959

6060

6161

62+
#=
6263
kalmanFilter1 = KalmanFilter(linModel1)
6364
kalmanFilter2 = KalmanFilter(linModel1,nint_ym=0)
6465
@@ -210,3 +211,4 @@ display(pu)
210211
display(py)
211212
212213
=#
214+
=#

src/controller/nonlinmpc.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ function NonLinMPC(
198198
end
199199

200200
"""
201-
getinfo(mpc::NonLinMPC)
201+
getinfo(mpc::NonLinMPC) -> sol_summary, info
202202
203203
Invoke [`getinfo(::PredictiveController)`](@ref) and add `:JE` the economic optimum ``J_E``.
204204
"""

src/estimator/internal_model.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ end
110110
validate_internalmodel(::SimModel) = nothing
111111

112112
@doc raw"""
113-
init_internalmodel(As, Bs, Cs, Ds)
113+
init_internalmodel(As, Bs, Cs, Ds) -> Âs, B̂s
114114
115115
Calc stochastic model update matrices `Âs` and `B̂s` for `InternalModel` estimator.
116116

src/estimator/kalman.jl

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ struct SteadyKalmanFilter <: StateEstimator
2727
nxs = size(Asm,1)
2828
nx̂ = nx + nxs
2929
validate_kfcov(nym, nx̂, Q̂, R̂)
30-
As, _ , Cs, _ = stoch_ym2y(model, i_ym, Asm, [], Csm, [])
31-
Â, B̂u, Ĉ, B̂d, D̂d = augment_model(model, As, Cs)
32-
Ĉm, D̂dm = Ĉ[i_ym, :], D̂d[i_ym, :] # measured outputs ym only
30+
As, _ , Cs = stoch_ym2y(model, i_ym, Asm, [], Csm, [])
31+
validate_obsv(model, As, Cs)
32+
 , B̂u, Ĉ, B̂d, D̂d = augment_model(model, As, Cs)
3333
K = try
34-
kalman(Discrete, Â, Ĉm, Matrix(Q̂), Matrix(R̂)) # Matrix() required for Julia 1.6
34+
Q̂_kalman = Matrix(Q̂) # Matrix() required for Julia 1.6
35+
R̂_kalman = zeros(eltype(R̂), ny, ny)
36+
R̂_kalman[i_ym, i_ym] =
37+
kalman(Discrete, Â, Ĉ, Q̂_kalman, R̂_kalman)[:, i_ym]
3538
catch my_error
3639
if isa(my_error, ErrorException)
3740
error("Cannot compute the optimal Kalman gain K for the "*
@@ -41,6 +44,7 @@ struct SteadyKalmanFilter <: StateEstimator
4144
rethrow()
4245
end
4346
end
47+
Ĉm, D̂dm = Ĉ[i_ym, :], D̂d[i_ym, :] # measured outputs ym only
4448
i_ym = collect(i_ym)
4549
lastu0 = zeros(nu)
4650
= [zeros(model.nx); zeros(nxs)]
@@ -92,8 +96,8 @@ ones, for ``\mathbf{Ĉ^u, D̂_d^u}``).
9296
``\mathbf{Q}`` of `model`, specified as a standard deviation vector.
9397
- `σR=fill(1,length(i_ym))` : main diagonal of the sensor noise covariance ``\mathbf{R}``
9498
of `model` measured outputs, specified as a standard deviation vector.
95-
- `nint_ym=fill(1,length(i_ym))` : integrator quantity per measured outputs (vector) for the
96-
stochastic model, use `nint_ym=0` for no integrator at all.
99+
- `nint_ym=default_nint(model,i_ym)` : integrator quantity per measured outputs (vector) for
100+
the stochastic model, use `nint_ym=0` for no integrator at all (see Extended Help).
97101
- `σQ_int=fill(1,sum(nint_ym))` : same than `σQ` but for the stochastic model covariance
98102
``\mathbf{Q_{int}}`` (composed of output integrators).
99103
@@ -111,25 +115,30 @@ SteadyKalmanFilter estimator with a sample time Ts = 0.5 s, LinModel and:
111115
```
112116
113117
# Extended Help
114-
The model augmentation with `nint_ym` vector produces the integral action when the estimator
115-
is used in a controller as state feedback. The default is 1 integrator per measured outputs,
116-
resulting in an offset-free tracking for "step-like" unmeasured output disturbances. Use 2
117-
integrators for "ramp-like" disturbances. See [`init_estimstoch`](@ref).
118+
The model augmentation with `nint_ym` vector adds integrators at model measured outputs
119+
``\mathbf{y^m}``, producing the integral action when the estimator is used in a controller
120+
as state feedback. The method [`default_nint`](@ref) computes the default value of
121+
`nint_ym`. It can also be tweaked by following these rules on each measured output:
122+
123+
- Use 0 integrator if the model output is integrating (else it will be unobservable)
124+
- Use 1 integrator if the disturbances on the output are typically "step-like"
125+
- Use 2 integrators if the disturbances on the output are typically "ramp-like"
126+
127+
The function [`init_estimstoch`](@ref) builds the stochastic model from `nint_ym`.
118128
119129
!!! tip
120130
Increasing `σQ_int` values increases the integral action "gain".
121131
122132
The constructor pre-compute the steady-state Kalman gain `K` with the [`kalman`](https://juliacontrol.github.io/ControlSystems.jl/stable/lib/synthesis/#ControlSystemsBase.kalman-Tuple{Any,%20Any,%20Any,%20Any,%20Any,%20Vararg{Any}})
123-
function. It can sometimes fail, for example when `model` is integrating. In such a case,
124-
you can use 0 integrator on `model` integrating outputs, or the alternative time-varying
125-
[`KalmanFilter`](@ref).
133+
function. It can sometimes fail, for example when `model` matrices are ill-conditioned. In
134+
such a case, you can try the alternative time-varying [`KalmanFilter`](@ref).
126135
"""
127136
function SteadyKalmanFilter(
128137
model::LinModel;
129138
i_ym::IntRangeOrVector = 1:model.ny,
130139
σQ::Vector = fill(1/model.nx, model.nx),
131140
σR::Vector = fill(1, length(i_ym)),
132-
nint_ym::IntVectorOrInt = fill(1, length(i_ym)),
141+
nint_ym::IntVectorOrInt = default_nint(model, i_ym),
133142
σQ_int::Vector = fill(1, max(sum(nint_ym), 0))
134143
)
135144
# estimated covariances matrices (variance = σ²) :
@@ -196,8 +205,9 @@ struct KalmanFilter <: StateEstimator
196205
nxs = size(Asm,1)
197206
nx̂ = nx + nxs
198207
validate_kfcov(nym, nx̂, Q̂, R̂, P̂0)
199-
As, _ , Cs, _ = stoch_ym2y(model, i_ym, Asm, [], Csm, [])
200-
Â, B̂u, Ĉ, B̂d, D̂d = augment_model(model, As, Cs)
208+
As, _ , Cs = stoch_ym2y(model, i_ym, Asm, [], Csm, [])
209+
 , B̂u, Ĉ, B̂d, D̂d = augment_model(model, As, Cs)
210+
validate_obsv(model, As, Cs)
201211
Ĉm, D̂dm = Ĉ[i_ym, :], D̂d[i_ym, :] # measured outputs ym only
202212
i_ym = collect(i_ym)
203213
lastu0 = zeros(nu)
@@ -255,7 +265,7 @@ function KalmanFilter(
255265
σP0::Vector = fill(1/model.nx, model.nx),
256266
σQ::Vector = fill(1/model.nx, model.nx),
257267
σR::Vector = fill(1, length(i_ym)),
258-
nint_ym::IntVectorOrInt = fill(1, length(i_ym)),
268+
nint_ym::IntVectorOrInt = default_nint(model, i_ym),
259269
σP0_int::Vector = fill(1, max(sum(nint_ym), 0)),
260270
σQ_int::Vector = fill(1, max(sum(nint_ym), 0))
261271
)
@@ -333,7 +343,8 @@ struct UnscentedKalmanFilter{M<:SimModel} <: StateEstimator
333343
nxs = size(Asm,1)
334344
nx̂ = nx + nxs
335345
validate_kfcov(nym, nx̂, Q̂, R̂, P̂0)
336-
As, _ , Cs, _ = stoch_ym2y(model, i_ym, Asm, [], Csm, [])
346+
As, _ , Cs = stoch_ym2y(model, i_ym, Asm, [], Csm, [])
347+
validate_obsv(model, As, Cs)
337348
nσ, γ, m̂, Ŝ = init_ukf(nx̂, α, β, κ)
338349
i_ym = collect(i_ym)
339350
lastu0 = zeros(nu)
@@ -402,7 +413,7 @@ function UnscentedKalmanFilter(
402413
σP0::Vector = fill(1/model.nx, model.nx),
403414
σQ::Vector = fill(1/model.nx, model.nx),
404415
σR::Vector = fill(1, length(i_ym)),
405-
nint_ym::IntVectorOrInt = fill(1, length(i_ym)),
416+
nint_ym::IntVectorOrInt = default_nint(model, i_ym),
406417
σP0_int::Vector = fill(1, max(sum(nint_ym), 0)),
407418
σQ_int::Vector = fill(1, max(sum(nint_ym), 0)),
408419
α::Real = 1e-3,
@@ -427,7 +438,7 @@ UnscentedKalmanFilter{M}(model::M, i_ym, nint_ym, P̂0, Q̂, R̂, α, β, κ) wh
427438

428439

429440
@doc raw"""
430-
init_ukf(nx̂, α, β, κ)
441+
init_ukf(nx̂, α, β, κ) -> nσ, γ, m̂, Ŝ
431442
432443
Compute the [`UnscentedKalmanFilter`](@ref) constants from ``α, β`` and ``κ``.
433444
@@ -553,6 +564,7 @@ struct ExtendedKalmanFilter{M<:SimModel} <: StateEstimator
553564
nx̂ = nx + nxs
554565
validate_kfcov(nym, nx̂, Q̂, R̂, P̂0)
555566
As, _ , Cs, _ = stoch_ym2y(model, i_ym, Asm, [], Csm, [])
567+
validate_obsv(model, As, Cs)
556568
i_ym = collect(i_ym)
557569
lastu0 = zeros(nu)
558570
= [zeros(model.nx); zeros(nxs)]
@@ -614,7 +626,7 @@ function ExtendedKalmanFilter(
614626
σP0::Vector = fill(1/model.nx, model.nx),
615627
σQ::Vector = fill(1/model.nx, model.nx),
616628
σR::Vector = fill(1, length(i_ym)),
617-
nint_ym::IntVectorOrInt = fill(1, length(i_ym)),
629+
nint_ym::IntVectorOrInt = default_nint(model, i_ym),
618630
σP0_int::Vector = fill(1, max(sum(nint_ym), 0)),
619631
σQ_int::Vector = fill(1, max(sum(nint_ym), 0))
620632
) where {M<:SimModel}
@@ -695,7 +707,7 @@ function validate_kfcov(nym, nx̂, Q̂, R̂, P̂0=nothing)
695707
end
696708

697709
"""
698-
update_estimate_kf!(estim, Â, Ĉm, u, ym, d)
710+
update_estimate_kf!(estim, Â, Ĉm, u, ym, d) -> x̂, P̂
699711
700712
Update time-varying/extended Kalman Filter estimates with augmented `Â` and `Ĉm` matrices.
701713

src/estimator/luenberger.jl

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@ struct Luenberger <: StateEstimator
2424
Asm, Csm, nint_ym = init_estimstoch(i_ym, nint_ym)
2525
nxs = size(Asm,1)
2626
nx̂ = nx + nxs
27-
As, _ , Cs, _ = stoch_ym2y(model, i_ym, Asm, [], Csm, [])
28-
Â, B̂u, Ĉ, B̂d, D̂d = augment_model(model, As, Cs)
29-
Ĉm, D̂dm = Ĉ[i_ym, :], D̂d[i_ym, :] # measured outputs ym only
27+
As, _ , Cs = stoch_ym2y(model, i_ym, Asm, [], Csm, [])
28+
, B̂u, Ĉ, B̂d, D̂d = augment_model(model, As, Cs)
29+
validate_obsv(model, As, Cs)
3030
K = try
31-
place(Â, Ĉ, p̂, :o)
31+
place(Â, Ĉ, p̂, :o)[:, i_ym]
3232
catch
33-
error("Cannot compute the Luenberger gain L with specified poles p̂.")
33+
error("Cannot compute the Luenberger gain L with specified poles p̂.")
3434
end
35+
Ĉm, D̂dm = Ĉ[i_ym, :], D̂d[i_ym, :] # measured outputs ym only
3536
i_ym = collect(i_ym)
3637
lastu0 = zeros(nu)
3738
= [zeros(model.nx); zeros(nxs)]
@@ -51,7 +52,7 @@ end
5152
Luenberger(
5253
model::LinModel;
5354
i_ym = 1:model.ny,
54-
nint_ym = fill(1, length(i_ym)),
55+
nint_ym = default_nint(model, i_ym),
5556
p̂ = 1e-3*(0:(model.nx + sum(nint_ym)-1)) .+ 0.5)
5657
)
5758
@@ -80,7 +81,7 @@ Luenberger estimator with a sample time Ts = 0.5 s, LinModel and:
8081
function Luenberger(
8182
model::LinModel;
8283
i_ym::IntRangeOrVector = 1:model.ny,
83-
nint_ym::IntVectorOrInt = fill(1, length(i_ym)),
84+
nint_ym::IntVectorOrInt = default_nint(model, i_ym),
8485
= 1e-3*(0:(model.nx + sum(nint_ym)-1)) .+ 0.5
8586
)
8687
nx = model.nx
@@ -101,5 +102,5 @@ function update_estimate!(estim::Luenberger, u, ym, d=Float64[])
101102
Â, B̂u, B̂d, Ĉm, D̂dm = estim.Â, estim.B̂u, estim.B̂d, estim.Ĉm, estim.D̂dm
102103
x̂, K = estim.x̂, estim.K
103104
x̂[:] =*+ B̂u*u + B̂d*d + K*(ym - Ĉm*- D̂dm*d)
104-
return
105+
return
105106
end

src/predictive_control.jl

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ function moveinput!(
270270
end
271271

272272
@doc raw"""
273-
getinfo(mpc::PredictiveController)
273+
getinfo(mpc::PredictiveController) -> sol_summary, info
274274
275275
Get additional information about `mpc` controller optimum to ease troubleshooting.
276276
@@ -355,7 +355,7 @@ function validate_setpointdist(mpc::PredictiveController, ry, d, R̂y, D̂)
355355
end
356356

357357
"""
358-
getestimates!(mpc::PredictiveController, estim::StateEstimator)
358+
getestimates!(mpc::PredictiveController, estim::StateEstimator) -> x̂d, x̂s, ŷ
359359
360360
Get estimator output and split `x̂` into the deterministic `x̂d` and stochastic `x̂s` states.
361361
"""
@@ -368,7 +368,7 @@ function getestimates!(mpc::PredictiveController, estim::StateEstimator, ym , d)
368368
end
369369

370370
"""
371-
getestimates!(mpc::PredictiveController, estim::InternalModel)
371+
getestimates!(mpc::PredictiveController, estim::InternalModel) -> x̂d, x̂s, ŷ
372372
373373
Get the internal model deterministic `estim.x̂d` and stochastic `estim.x̂s` states.
374374
"""
@@ -513,7 +513,7 @@ end
513513
"""
514514
optim_objective!(mpc::PredictiveController, b, p)
515515
516-
Optimize the objective function ``J`` of `mpc` controller.
516+
Optimize the objective function ``J`` of `mpc` controller and return the solution `ΔŨ`.
517517
"""
518518
function optim_objective!(mpc::PredictiveController)
519519
optim = mpc.optim
@@ -550,7 +550,7 @@ end
550550
set_objective_linear_coef!(::PredictiveController, _ ) = nothing
551551

552552
@doc raw"""
553-
init_ΔUtoU(nu, Hp, Hc, C, c_Umin, c_Umax)
553+
init_ΔUtoU(nu, Hp, Hc, C, c_Umin, c_Umax) -> S_Hp, T_Hp, S_Hc, T_Hc
554554
555555
Init manipulated input increments to inputs conversion matrices.
556556
@@ -574,7 +574,7 @@ end
574574

575575

576576
@doc raw"""
577-
init_deterpred(model::LinModel, Hp, Hc)
577+
init_deterpred(model::LinModel, Hp, Hc) -> E, G, J, Kd, Q
578578
579579
Construct deterministic prediction matrices for [`LinModel`](@ref) `model`.
580580
@@ -699,7 +699,7 @@ function init_deterpred(model::SimModel, Hp, Hc)
699699
end
700700

701701
@doc raw"""
702-
init_quadprog(model::LinModel, Ẽ, S_Hp, M_Hp, N_Hc, L_Hp)
702+
init_quadprog(model::LinModel, Ẽ, S_Hp, M_Hp, N_Hc, L_Hp) -> P̃, q̃, p
703703
704704
Init the quadratic programming optimization matrix `P̃` and `q̃`.
705705
@@ -730,7 +730,7 @@ end
730730
obj_quadprog(ΔŨ, P̃, q̃) = 0.5*ΔŨ'**ΔŨ +'*ΔŨ
731731

732732
"""
733-
init_defaultcon(model, C, S_Hp, S_Hc, N_Hc, E)
733+
init_defaultcon(model, C, S_Hp, S_Hc, N_Hc, E) -> con, S̃_Hp, Ñ_Hc, Ẽ
734734
735735
Init `ControllerConstraint` struct with default parameters.
736736
@@ -779,7 +779,7 @@ function repeat_constraints(Hp, Hc, umin, umax, Δumin, Δumax, ŷmin, ŷmax)
779779
end
780780

781781
@doc raw"""
782-
relaxU(C, c_Umin, c_Umax, S_Hp, S_Hc)
782+
relaxU(C, c_Umin, c_Umax, S_Hp, S_Hc) -> A_Umin, A_Umax, S̃_Hp
783783
784784
Augment manipulated inputs constraints with slack variable ϵ for softening.
785785
@@ -813,7 +813,7 @@ function relaxU(C, c_Umin, c_Umax, S_Hp, S_Hc)
813813
end
814814

815815
@doc raw"""
816-
relaxΔU(C, c_ΔUmin, c_ΔUmax, ΔUmin, ΔUmax, N_Hc)
816+
relaxΔU(C, c_ΔUmin, c_ΔUmax, ΔUmin, ΔUmax, N_Hc) -> A_ΔŨmin, A_ΔŨmax, ΔŨmin, ΔŨmax, Ñ_Hc
817817
818818
Augment input increments constraints with slack variable ϵ for softening.
819819
@@ -850,7 +850,7 @@ function relaxΔU(C, c_ΔUmin, c_ΔUmax, ΔUmin, ΔUmax, N_Hc)
850850
end
851851

852852
@doc raw"""
853-
relaxŶ(::LinModel, C, c_Ŷmin, c_Ŷmax, E)
853+
relaxŶ(::LinModel, C, c_Ŷmin, c_Ŷmax, E) -> A_Ŷmin, A_Ŷmax, Ẽ
854854
855855
Augment linear output prediction constraints with slack variable ϵ for softening.
856856
@@ -890,7 +890,7 @@ function relaxŶ(::SimModel, C, c_Ŷmin, c_Ŷmax, E)
890890
end
891891

892892
@doc raw"""
893-
init_stochpred(estim::StateEstimator, Hp)
893+
init_stochpred(estim::StateEstimator, Hp) -> Ks, Ps
894894
895895
Init the stochastic prediction matrix `Ks` from `estim` estimator for predictive control.
896896
@@ -919,7 +919,7 @@ function init_stochpred(estim::StateEstimator, Hp)
919919
end
920920

921921
@doc raw"""
922-
init_stochpred(estim::InternalModel, Hp)
922+
init_stochpred(estim::InternalModel, Hp) -> Ks, Ps
923923
924924
Init the stochastic prediction matrices for [`InternalModel`](@ref).
925925
@@ -955,7 +955,7 @@ end
955955
init_linconstraint(model::LinModel,
956956
A_Umin, A_Umax, A_ΔŨmin, A_ΔŨmax, A_Ŷmin, A_Ŷmax,
957957
i_Umin, i_Umax, i_ΔŨmin, i_ΔŨmax, i_Ŷmin, i_Ŷmax
958-
)
958+
) -> A, i_b, b
959959
960960
Init `A`, `b` and `i_b` for the linear inequality constraints (``\mathbf{A ΔŨ ≤ b}``).
961961

0 commit comments

Comments
 (0)