Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added: MovingHorizonEstimator support for direct=true, initialized with P̂(-1|-1) #96

Merged
merged 51 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
4ddeb45
added: `InternalModel` now produces 0 allocation
franckgaga Aug 14, 2024
a51a087
minor doc correction
franckgaga Aug 14, 2024
12c1a2a
doc: update plots figure with new direct forms
franckgaga Aug 15, 2024
2647c73
doc: internal correction in MHE prediction equations
franckgaga Aug 15, 2024
90e5e61
doc: introducing constant K in docstring for the current/predictor fo…
franckgaga Aug 15, 2024
c50f1a0
doc: mistake correction MHE
franckgaga Aug 15, 2024
46163dc
doc: bookmark
franckgaga Aug 15, 2024
1a50b81
doc: continuation MHE `direct=true`
franckgaga Aug 16, 2024
8f497a8
Merge branch 'main' into mhe_direct
franckgaga Aug 19, 2024
4c4bdbb
doc : minor correction
franckgaga Aug 19, 2024
4409980
doc: minor correction in `KalmanFilter`
franckgaga Aug 20, 2024
a89e2fa
doc: added prediction matrices details for K=0 and 1
franckgaga Aug 21, 2024
c164d8b
doc: minor corrections
franckgaga Aug 21, 2024
3409b4d
doc: minor corrections
franckgaga Aug 21, 2024
11b1dfc
doc: ok now my notation for the MHE works! (phew that was hard)
franckgaga Aug 23, 2024
594c688
doc: comment on MHE arrival covariance with `direct=true`
franckgaga Aug 23, 2024
a3a88b4
doc: minor correction
franckgaga Aug 23, 2024
9ba5a83
doc: minor correction
franckgaga Aug 23, 2024
1c94cbc
doc: add source
franckgaga Aug 24, 2024
6d1a9a5
MHE: WIP
franckgaga Aug 30, 2024
29fa66a
MHE direct starting to work! 🥳
franckgaga Aug 30, 2024
5be7f27
debug: clamp `estim.Nk` correctly
franckgaga Aug 31, 2024
d7a19ba
debug
franckgaga Aug 31, 2024
17ad181
debug: MHE test
franckgaga Aug 31, 2024
54f0152
move fille windows checkup inside `correct_cov!` and `update_cov!`
franckgaga Sep 1, 2024
8a02643
comment
franckgaga Sep 1, 2024
0ff5640
debug: prediction matrix G for MHE now ok
franckgaga Sep 1, 2024
39edab7
debug: MHE correctly add new u value to window
franckgaga Sep 1, 2024
9208b30
debug: MHE prediction matrix J now okay
franckgaga Sep 1, 2024
794e8f0
debug: MHE adding u at the right position in its window
franckgaga Sep 1, 2024
35b6696
debug: MHE tests
franckgaga Sep 1, 2024
fd5e14a
doc: remove comment on MHE current version in manual
franckgaga Sep 1, 2024
c1a9147
added: views in MHE code
franckgaga Sep 1, 2024
f7623f9
debug: MHE d0 position in D0 is different if `direct==true`
franckgaga Sep 3, 2024
ca6bb97
debug: MHE correct U position in its window
franckgaga Sep 3, 2024
1816ab8
debug: MHE do not add d0 is `nd == 0`
franckgaga Sep 3, 2024
9d66a91
added: additional test for MHE with `direct=true`
franckgaga Sep 3, 2024
7f87fd9
doc: comment on MHE with `direct=true` for `updatestate!`
franckgaga Sep 3, 2024
dfee1df
doc: some details in MHE `update_estimate!`method
franckgaga Sep 3, 2024
75c7337
doc: minor correction
franckgaga Sep 3, 2024
6d8b194
doc: replace `M_k` by `N_k` in `setconstraint!`
franckgaga Sep 4, 2024
d1a06db
bump version
franckgaga Sep 4, 2024
e91008e
test: also compare MHE to `ExtendedKalmanFilter` results
franckgaga Sep 4, 2024
6aa7079
test: removed useless plots
franckgaga Sep 4, 2024
0fa367e
test: adding `LinModel` v.s. `NonLinModel` integration tests for MHE …
franckgaga Sep 4, 2024
065634e
doc: define predictions matrices of MPC and MHE
franckgaga Sep 5, 2024
a15f0f1
doc: adding all the kwargs to the Kalman filters and MHE
franckgaga Sep 5, 2024
3c5b01f
doc: added some details on the initial state estimate in the estimato…
franckgaga Sep 5, 2024
16a4217
doc: minor corrections
franckgaga Sep 5, 2024
3c1f59f
doc: some details on Kalman filter tuning in `SteadyKalmanFilter` doc…
franckgaga Sep 5, 2024
0f161ba
doc: minor modification
franckgaga Sep 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "ModelPredictiveControl"
uuid = "61f9bdb8-6ae4-484a-811f-bbf86720c31c"
authors = ["Francis Gagnon"]
version = "0.23.1"
version = "0.24.0"

[deps]
ControlSystemsBase = "aaaaaaaa-a6ca-5380-bf3e-84a91bcd477e"
Expand Down
178 changes: 92 additions & 86 deletions docs/src/assets/plot_controller.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
178 changes: 87 additions & 91 deletions docs/src/assets/plot_estimator.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/src/internals/predictive_control.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ ModelPredictiveControl.linconstraint!(::PredictiveController, ::LinModel)
## Solve Optimization Problem

```@docs
ModelPredictiveControl.optim_objective!
ModelPredictiveControl.optim_objective!(::PredictiveController)
```
6 changes: 6 additions & 0 deletions docs/src/internals/state_estim.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ ModelPredictiveControl.initpred!(::MovingHorizonEstimator, ::LinModel)
ModelPredictiveControl.linconstraint!(::MovingHorizonEstimator, ::LinModel)
```

## Solve Optimization Problem

```@docs
ModelPredictiveControl.optim_objective!(::MovingHorizonEstimator)
```

## Evaluate Estimated Output

```@docs
Expand Down
6 changes: 1 addition & 5 deletions docs/src/manual/linmpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ mpc_mhe = LinMPC(estim, Hp=10, Hc=2, Mwt=[1, 1], Nwt=[0.1, 0.1])
mpc_mhe = setconstraint!(mpc_mhe, ymin=[45, -Inf])
```

The rejection is not improved here:
The rejection is indeed improved:

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

![plot3_LinMPC](plot3_LinMPC.svg)

This is because the more performant `direct=true` version of the [`MovingHorizonEstimator`](@ref)
is not not implemented yet. The rejection will be improved with the `direct=true` version
(coming soon).

## Adding Feedforward Compensation

Suppose that the load disturbance ``u_l`` of the last section is in fact caused by a
Expand Down
11 changes: 6 additions & 5 deletions src/controller/construct.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The predictive controllers support both soft and hard constraints, defined by:
\mathbf{u_{min} - c_{u_{min}}} ϵ ≤&&\ \mathbf{u}(k+j) &≤ \mathbf{u_{max} + c_{u_{max}}} ϵ &&\qquad j = 0, 1 ,..., H_p - 1 \\
\mathbf{Δu_{min} - c_{Δu_{min}}} ϵ ≤&&\ \mathbf{Δu}(k+j) &≤ \mathbf{Δu_{max} + c_{Δu_{max}}} ϵ &&\qquad j = 0, 1 ,..., H_c - 1 \\
\mathbf{y_{min} - c_{y_{min}}} ϵ ≤&&\ \mathbf{ŷ}(k+j) &≤ \mathbf{y_{max} + c_{y_{max}}} ϵ &&\qquad j = 1, 2 ,..., H_p \\
\mathbf{x̂_{min} - c_{x̂_{min}}} ϵ ≤&&\ \mathbf{x̂}_{i}(k+j) &≤ \mathbf{x̂_{max} + c_{x̂_{max}}} ϵ &&\qquad j = H_p
\mathbf{x̂_{min} - c_{x̂_{min}}} ϵ ≤&&\ \mathbf{x̂}_i(k+j) &≤ \mathbf{x̂_{max} + c_{x̂_{max}}} ϵ &&\qquad j = H_p
\end{alignat*}
```
and also ``ϵ ≥ 0``. The last line is the terminal constraints applied on the states at the
Expand Down Expand Up @@ -435,7 +435,7 @@ The model predictions are evaluated from the deviation vectors (see [`setop!`](@
&= \mathbf{E ΔU} + \mathbf{F}
\end{aligned}
```
in which ``\mathbf{x̂_0}(k) = \mathbf{x̂}_{i}(k) - \mathbf{x̂_{op}}``, with ``i = k`` if
in which ``\mathbf{x̂_0}(k) = \mathbf{x̂}_i(k) - \mathbf{x̂_{op}}``, with ``i = k`` if
`estim.direct==true`, otherwise ``i = k - 1``. The predicted outputs ``\mathbf{Ŷ_0}`` and
measured disturbances ``\mathbf{D̂_0}`` respectively include ``\mathbf{ŷ_0}(k+j)`` and
``\mathbf{d̂_0}(k+j)`` values with ``j=1`` to ``H_p``, and input increments ``\mathbf{ΔU}``,
Expand All @@ -453,8 +453,9 @@ terminal states at ``k+H_p``:
&= \mathbf{e_x̂ ΔU} + \mathbf{f_x̂}
\end{aligned}
```
The ``\mathbf{F}`` and ``\mathbf{f_x̂}`` vectors are recalculated at each control period
``k``, see [`initpred!`](@ref) and [`linconstraint!`](@ref).
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
Extended Help section. The ``\mathbf{F}`` and ``\mathbf{f_x̂}`` vectors are recalculated at
each control period ``k``, see [`initpred!`](@ref) and [`linconstraint!`](@ref).

# Extended Help
!!! details "Extended Help"
Expand Down Expand Up @@ -529,7 +530,7 @@ function init_predmat(estim::StateEstimator{NT}, model::LinModel, Hp, Hc) where
# Apow_csum 3D array : Apow_csum[:,:,1] = A^0, Apow_csum[:,:,2] = A^1 + A^0, ...
Âpow_csum = cumsum(Âpow, dims=3)
# helper function to improve code clarity and be similar to eqs. in docstring:
getpower(array3D, power) = array3D[:,:, power+1]
getpower(array3D, power) = @views array3D[:,:, power+1]
# --- state estimates x̂ ---
kx̂ = getpower(Âpow, Hp)
K = Matrix{NT}(undef, Hp*ny, nx̂)
Expand Down
3 changes: 2 additions & 1 deletion src/controller/execute.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ Solve the optimization problem of `mpc` [`PredictiveController`](@ref) and retur
results ``\mathbf{u}(k)``. Following the receding horizon principle, the algorithm discards
the optimal future manipulated inputs ``\mathbf{u}(k+1), \mathbf{u}(k+2), ...`` Note that
the method mutates `mpc` internal data but it does not modifies `mpc.estim` states. Call
[`updatestate!(mpc, u, ym, d)`](@ref) to update `mpc` state estimates.
[`preparestate!(mpc, ym, d)`](@ref) before `moveinput!`, and [`updatestate!(mpc, u, ym, d)`](@ref)
after, to update `mpc` state estimates.

Calling a [`PredictiveController`](@ref) object calls this method.

Expand Down
5 changes: 3 additions & 2 deletions src/estimator/construct.jl
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,9 @@ end

One integrator on each measured output by default if `model` is not a [`LinModel`](@ref).

If the integrator quantity per manipulated input `nint_u ≠ 0`, the method returns zero
integrator on each measured output.
Theres is no verification the augmented model remains observable. If the integrator quantity
per manipulated input `nint_u ≠ 0`, the method returns zero integrator on each measured
output.
"""
function default_nint(model::SimModel, i_ym=1:model.ny, nint_u=0)
validate_ym(model, i_ym)
Expand Down
8 changes: 5 additions & 3 deletions src/estimator/execute.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ end

Init `estim.x̂0` states from current inputs `u`, measured outputs `ym` and disturbances `d`.

The method tries to find a good steady-state for the initial estimate ``\mathbf{x̂}(0)``. It
The method tries to find a good steady-state for the initial estimate ``\mathbf{x̂}``. It
removes the operating points with [`remove_op!`](@ref) and call [`init_estimate!`](@ref):

- If `estim.model` is a [`LinModel`](@ref), it finds the steady-state of the augmented model
Expand Down Expand Up @@ -203,7 +203,7 @@ Prepare `estim.x̂0` estimate with meas. outputs `ym` and dist. `d` for the curr

This function should be called at the beginning of each discrete time step. Its behavior
depends if `estim` is a [`StateEstimator`](@ref) in the current/filter (1.) or
delayed/predictor (2.) form:
delayed/predictor (2.) formulation:

1. If `estim.direct` is `true`, it removes the operating points with [`remove_op!`](@ref),
calls [`correct_estimate!`](@ref), and returns the corrected state estimate
Expand Down Expand Up @@ -246,7 +246,9 @@ This function should be called at the end of each discrete time step. It removes
operating points with [`remove_op!`](@ref), calls [`update_estimate!`](@ref) and returns the
state estimate for the next time step ``\mathbf{x̂}_k(k+1)``. The method [`preparestate!`](@ref)
should be called prior to this one to correct the estimate when applicable (if
`estim.direct == true`).
`estim.direct == true`). Note that the [`MovingHorizonEstimator`](@ref) with the default
`direct=true` option is not able to estimate ``\mathbf{x̂}_k(k+1)``, the returned value
is therefore the current corrected state ``\mathbf{x̂}_k(k)``.

# Examples
```jldoctest
Expand Down
18 changes: 12 additions & 6 deletions src/estimator/internal_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ struct InternalModel{NT<:Real, SM<:SimModel} <: StateEstimator{NT}
x̂d::Vector{NT}
x̂s::Vector{NT}
ŷs::Vector{NT}
x̂snext::Vector{NT}
i_ym::Vector{Int}
nx̂::Int
nym::Int
Expand Down Expand Up @@ -43,14 +44,14 @@ struct InternalModel{NT<:Real, SM<:SimModel} <: StateEstimator{NT}
lastu0 = zeros(NT, nu)
# x̂0 and x̂d are same object (updating x̂d will update x̂0):
x̂d = x̂0 = zeros(NT, model.nx)
x̂s = zeros(NT, nxs)
x̂s, x̂snext = zeros(NT, nxs), zeros(NT, nxs)
ŷs = zeros(NT, ny)
direct = true # InternalModel always uses direct transmission from ym
corrected = [false]
buffer = StateEstimatorBuffer{NT}(nu, nx̂, nym, ny, nd)
return new{NT, SM}(
model,
lastu0, x̂op, f̂op, x̂0, x̂d, x̂s, ŷs,
lastu0, x̂op, f̂op, x̂0, x̂d, x̂s, ŷs, x̂snext,
i_ym, nx̂, nym, nyu, nxs,
As, Bs, Cs, Ds,
Â, B̂u, Ĉ, B̂d, D̂d, Ĉm, D̂dm,
Expand Down Expand Up @@ -247,9 +248,14 @@ function correct_estimate!(estim::InternalModel, y0m, d0)
ŷ0d = estim.buffer.ŷ
h!(ŷ0d, estim.model, estim.x̂d, d0)
ŷs = estim.ŷs
ŷs[estim.i_ym] .= @views y0m .- ŷ0d[estim.i_ym]
# ŷs=0 for unmeasured outputs :
map(i -> ŷs[i] = (i in estim.i_ym) ? ŷs[i] : 0, eachindex(ŷs))
for j in eachindex(ŷs) # broadcasting was allocating unexpectedly, so we use a loop
if j in estim.i_ym
i = estim.i_ym[j]
ŷs[j] = y0m[i] - ŷ0d[j]
else
ŷs[j] = 0
end
end
return nothing
end

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