Skip to content

Commit b320658

Browse files
authored
Merge pull request #96 from JuliaControl/mhe_direct_initial_posteriori
added: MovingHorizonEstimator support for `direct=true`, initialized with `P̂(-1|-1)`
2 parents 1e4d3f8 + 0f161ba commit b320658

18 files changed

+967
-528
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
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.23.1"
4+
version = "0.24.0"
55

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

docs/src/assets/plot_controller.svg

Lines changed: 92 additions & 86 deletions
Loading

docs/src/assets/plot_estimator.svg

Lines changed: 87 additions & 91 deletions
Loading

docs/src/internals/predictive_control.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ ModelPredictiveControl.linconstraint!(::PredictiveController, ::LinModel)
3333
## Solve Optimization Problem
3434

3535
```@docs
36-
ModelPredictiveControl.optim_objective!
36+
ModelPredictiveControl.optim_objective!(::PredictiveController)
3737
```

docs/src/internals/state_estim.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ ModelPredictiveControl.initpred!(::MovingHorizonEstimator, ::LinModel)
3434
ModelPredictiveControl.linconstraint!(::MovingHorizonEstimator, ::LinModel)
3535
```
3636

37+
## Solve Optimization Problem
38+
39+
```@docs
40+
ModelPredictiveControl.optim_objective!(::MovingHorizonEstimator)
41+
```
42+
3743
## Evaluate Estimated Output
3844

3945
```@docs

docs/src/manual/linmpc.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ mpc_mhe = LinMPC(estim, Hp=10, Hc=2, Mwt=[1, 1], Nwt=[0.1, 0.1])
203203
mpc_mhe = setconstraint!(mpc_mhe, ymin=[45, -Inf])
204204
```
205205

206-
The rejection is not improved here:
206+
The rejection is indeed improved:
207207

208208
```@example 1
209209
setstate!(model, zeros(model.nx))
@@ -216,10 +216,6 @@ savefig("plot3_LinMPC.svg"); nothing # hide
216216

217217
![plot3_LinMPC](plot3_LinMPC.svg)
218218

219-
This is because the more performant `direct=true` version of the [`MovingHorizonEstimator`](@ref)
220-
is not not implemented yet. The rejection will be improved with the `direct=true` version
221-
(coming soon).
222-
223219
## Adding Feedforward Compensation
224220

225221
Suppose that the load disturbance ``u_l`` of the last section is in fact caused by a

src/controller/construct.jl

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ The predictive controllers support both soft and hard constraints, defined by:
4444
\mathbf{u_{min} - c_{u_{min}}} ϵ ≤&&\ \mathbf{u}(k+j) &≤ \mathbf{u_{max} + c_{u_{max}}} ϵ &&\qquad j = 0, 1 ,..., H_p - 1 \\
4545
\mathbf{Δu_{min} - c_{Δu_{min}}} ϵ ≤&&\ \mathbf{Δu}(k+j) &≤ \mathbf{Δu_{max} + c_{Δu_{max}}} ϵ &&\qquad j = 0, 1 ,..., H_c - 1 \\
4646
\mathbf{y_{min} - c_{y_{min}}} ϵ ≤&&\ \mathbf{ŷ}(k+j) &≤ \mathbf{y_{max} + c_{y_{max}}} ϵ &&\qquad j = 1, 2 ,..., H_p \\
47-
\mathbf{x̂_{min} - c_{x̂_{min}}} ϵ ≤&&\ \mathbf{x̂}_{i}(k+j) &≤ \mathbf{x̂_{max} + c_{x̂_{max}}} ϵ &&\qquad j = H_p
47+
\mathbf{x̂_{min} - c_{x̂_{min}}} ϵ ≤&&\ \mathbf{x̂}_i(k+j) &≤ \mathbf{x̂_{max} + c_{x̂_{max}}} ϵ &&\qquad j = H_p
4848
\end{alignat*}
4949
```
5050
and also ``ϵ ≥ 0``. The last line is the terminal constraints applied on the states at the
@@ -435,7 +435,7 @@ The model predictions are evaluated from the deviation vectors (see [`setop!`](@
435435
&= \mathbf{E ΔU} + \mathbf{F}
436436
\end{aligned}
437437
```
438-
in which ``\mathbf{x̂_0}(k) = \mathbf{x̂}_{i}(k) - \mathbf{x̂_{op}}``, with ``i = k`` if
438+
in which ``\mathbf{x̂_0}(k) = \mathbf{x̂}_i(k) - \mathbf{x̂_{op}}``, with ``i = k`` if
439439
`estim.direct==true`, otherwise ``i = k - 1``. The predicted outputs ``\mathbf{Ŷ_0}`` and
440440
measured disturbances ``\mathbf{D̂_0}`` respectively include ``\mathbf{ŷ_0}(k+j)`` and
441441
``\mathbf{d̂_0}(k+j)`` values with ``j=1`` to ``H_p``, and input increments ``\mathbf{ΔU}``,
@@ -453,8 +453,9 @@ terminal states at ``k+H_p``:
453453
&= \mathbf{e_x̂ ΔU} + \mathbf{f_x̂}
454454
\end{aligned}
455455
```
456-
The ``\mathbf{F}`` and ``\mathbf{f_x̂}`` vectors are recalculated at each control period
457-
``k``, see [`initpred!`](@ref) and [`linconstraint!`](@ref).
456+
The matrices ``\mathbf{E, G, J, K, V, B, e_x̂, g_x̂, j_x̂, k_x̂, v_x̂, b_x̂}`` are defined in the
457+
Extended Help section. The ``\mathbf{F}`` and ``\mathbf{f_x̂}`` vectors are recalculated at
458+
each control period ``k``, see [`initpred!`](@ref) and [`linconstraint!`](@ref).
458459
459460
# Extended Help
460461
!!! details "Extended Help"
@@ -529,7 +530,7 @@ function init_predmat(estim::StateEstimator{NT}, model::LinModel, Hp, Hc) where
529530
# Apow_csum 3D array : Apow_csum[:,:,1] = A^0, Apow_csum[:,:,2] = A^1 + A^0, ...
530531
Âpow_csum = cumsum(Âpow, dims=3)
531532
# helper function to improve code clarity and be similar to eqs. in docstring:
532-
getpower(array3D, power) = array3D[:,:, power+1]
533+
getpower(array3D, power) = @views array3D[:,:, power+1]
533534
# --- state estimates x̂ ---
534535
kx̂ = getpower(Âpow, Hp)
535536
K = Matrix{NT}(undef, Hp*ny, nx̂)

src/controller/execute.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ Solve the optimization problem of `mpc` [`PredictiveController`](@ref) and retur
1717
results ``\mathbf{u}(k)``. Following the receding horizon principle, the algorithm discards
1818
the optimal future manipulated inputs ``\mathbf{u}(k+1), \mathbf{u}(k+2), ...`` Note that
1919
the method mutates `mpc` internal data but it does not modifies `mpc.estim` states. Call
20-
[`updatestate!(mpc, u, ym, d)`](@ref) to update `mpc` state estimates.
20+
[`preparestate!(mpc, ym, d)`](@ref) before `moveinput!`, and [`updatestate!(mpc, u, ym, d)`](@ref)
21+
after, to update `mpc` state estimates.
2122
2223
Calling a [`PredictiveController`](@ref) object calls this method.
2324

src/estimator/construct.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,9 @@ end
197197
198198
One integrator on each measured output by default if `model` is not a [`LinModel`](@ref).
199199
200-
If the integrator quantity per manipulated input `nint_u ≠ 0`, the method returns zero
201-
integrator on each measured output.
200+
Theres is no verification the augmented model remains observable. If the integrator quantity
201+
per manipulated input `nint_u ≠ 0`, the method returns zero integrator on each measured
202+
output.
202203
"""
203204
function default_nint(model::SimModel, i_ym=1:model.ny, nint_u=0)
204205
validate_ym(model, i_ym)

src/estimator/execute.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ end
8686
8787
Init `estim.x̂0` states from current inputs `u`, measured outputs `ym` and disturbances `d`.
8888
89-
The method tries to find a good steady-state for the initial estimate ``\mathbf{x̂}(0)``. It
89+
The method tries to find a good steady-state for the initial estimate ``\mathbf{x̂}``. It
9090
removes the operating points with [`remove_op!`](@ref) and call [`init_estimate!`](@ref):
9191
9292
- If `estim.model` is a [`LinModel`](@ref), it finds the steady-state of the augmented model
@@ -203,7 +203,7 @@ Prepare `estim.x̂0` estimate with meas. outputs `ym` and dist. `d` for the curr
203203
204204
This function should be called at the beginning of each discrete time step. Its behavior
205205
depends if `estim` is a [`StateEstimator`](@ref) in the current/filter (1.) or
206-
delayed/predictor (2.) form:
206+
delayed/predictor (2.) formulation:
207207
208208
1. If `estim.direct` is `true`, it removes the operating points with [`remove_op!`](@ref),
209209
calls [`correct_estimate!`](@ref), and returns the corrected state estimate
@@ -246,7 +246,9 @@ This function should be called at the end of each discrete time step. It removes
246246
operating points with [`remove_op!`](@ref), calls [`update_estimate!`](@ref) and returns the
247247
state estimate for the next time step ``\mathbf{x̂}_k(k+1)``. The method [`preparestate!`](@ref)
248248
should be called prior to this one to correct the estimate when applicable (if
249-
`estim.direct == true`).
249+
`estim.direct == true`). Note that the [`MovingHorizonEstimator`](@ref) with the default
250+
`direct=true` option is not able to estimate ``\mathbf{x̂}_k(k+1)``, the returned value
251+
is therefore the current corrected state ``\mathbf{x̂}_k(k)``.
250252
251253
# Examples
252254
```jldoctest

src/estimator/internal_model.jl

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ struct InternalModel{NT<:Real, SM<:SimModel} <: StateEstimator{NT}
77
x̂d::Vector{NT}
88
x̂s::Vector{NT}
99
ŷs::Vector{NT}
10+
x̂snext::Vector{NT}
1011
i_ym::Vector{Int}
1112
nx̂::Int
1213
nym::Int
@@ -43,14 +44,14 @@ struct InternalModel{NT<:Real, SM<:SimModel} <: StateEstimator{NT}
4344
lastu0 = zeros(NT, nu)
4445
# x̂0 and x̂d are same object (updating x̂d will update x̂0):
4546
x̂d = x̂0 = zeros(NT, model.nx)
46-
x̂s = zeros(NT, nxs)
47+
x̂s, x̂snext = zeros(NT, nxs), zeros(NT, nxs)
4748
ŷs = zeros(NT, ny)
4849
direct = true # InternalModel always uses direct transmission from ym
4950
corrected = [false]
5051
buffer = StateEstimatorBuffer{NT}(nu, nx̂, nym, ny, nd)
5152
return new{NT, SM}(
5253
model,
53-
lastu0, x̂op, f̂op, x̂0, x̂d, x̂s, ŷs,
54+
lastu0, x̂op, f̂op, x̂0, x̂d, x̂s, ŷs, x̂snext,
5455
i_ym, nx̂, nym, nyu, nxs,
5556
As, Bs, Cs, Ds,
5657
Â, B̂u, Ĉ, B̂d, D̂d, Ĉm, D̂dm,
@@ -247,9 +248,14 @@ function correct_estimate!(estim::InternalModel, y0m, d0)
247248
ŷ0d = estim.buffer.
248249
h!(ŷ0d, estim.model, estim.x̂d, d0)
249250
ŷs = estim.ŷs
250-
ŷs[estim.i_ym] .= @views y0m .- ŷ0d[estim.i_ym]
251-
# ŷs=0 for unmeasured outputs :
252-
map(i -> ŷs[i] = (i in estim.i_ym) ? ŷs[i] : 0, eachindex(ŷs))
251+
for j in eachindex(ŷs) # broadcasting was allocating unexpectedly, so we use a loop
252+
if j in estim.i_ym
253+
i = estim.i_ym[j]
254+
ŷs[j] = y0m[i] - ŷ0d[j]
255+
else
256+
ŷs[j] = 0
257+
end
258+
end
253259
return nothing
254260
end
255261

@@ -276,7 +282,7 @@ function update_estimate!(estim::InternalModel, _ , d0, u0)
276282
f!(x̂dnext, model, x̂d, u0, d0)
277283
x̂d .= x̂dnext # this also updates estim.x̂0 (they are the same object)
278284
# --------------- stochastic model -----------------------
279-
x̂snext = similar(x̂s) # TODO: remove this allocation with a new buffer?
285+
x̂snext = estim.x̂snext
280286
mul!(x̂snext, estim.Âs, x̂s)
281287
mul!(x̂snext, estim.B̂s, ŷs, 1, 1)
282288
estim.x̂s .= x̂snext

0 commit comments

Comments
 (0)