Skip to content

Commit

Permalink
Merge pull request #55 from JuliaControl/adapt_mhe
Browse files Browse the repository at this point in the history
Added: `setmodel!` for `MovingHorizonEstimator`
  • Loading branch information
franckgaga authored Apr 26, 2024
2 parents d3e0a2f + 76b0a12 commit dc8681a
Show file tree
Hide file tree
Showing 10 changed files with 406 additions and 239 deletions.
2 changes: 1 addition & 1 deletion docs/src/public/sim_model.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The [`SimModel`](@ref) types represents discrete state-space models that can be
construct [`StateEstimator`](@ref)s and [`PredictiveController`](@ref)s, or as plant
simulators by calling [`evaloutput`](@ref) and [`updatestate!`](@ref) methods on
[`SimModel`](@ref) instances (to test estimator/controller designs). For time simulations,
the states `x` are stored inside [`SimModel`](@ref) instances. Use [`setstate!`](@ref)
the states ``\mathbf{x}`` are stored inside [`SimModel`](@ref) instances. Use [`setstate!`](@ref)
method to manually modify them.

## SimModel
Expand Down
14 changes: 7 additions & 7 deletions src/controller/construct.jl
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ The ``\mathbf{F}`` and ``\mathbf{f_x̂}`` vectors are recalculated at each contr
# Extended Help
!!! details "Extended Help"
Using the augmented matrices ``\mathbf{Â, B̂_u, Ĉ, B̂_d, D̂_d}`` in `estim` (see
[`augment_model`](@ref)) and the function ``\mathbf{W}(j) = ∑_{i=0}^j \mathbf{Â}^i``,
[`augment_model`](@ref)), and the function ``\mathbf{W}(j) = ∑_{i=0}^j \mathbf{Â}^i``,
the prediction matrices are computed by :
```math
\begin{aligned}
Expand Down Expand Up @@ -447,8 +447,7 @@ The ``\mathbf{F}`` and ``\mathbf{f_x̂}`` vectors are recalculated at each contr
\mathbf{Ĉ W}(0) \\
\mathbf{Ĉ W}(1) \\
\vdots \\
\mathbf{Ĉ W}(H_p-1) \end{bmatrix}
\mathbf{\big(x̂_{op} + f̂_{op}\big)}
\mathbf{Ĉ W}(H_p-1) \end{bmatrix} \mathbf{\big(x̂_{op} + f̂_{op}\big)}
\end{aligned}
```
For the terminal constraints, the matrices are computed with:
Expand All @@ -468,7 +467,7 @@ The ``\mathbf{F}`` and ``\mathbf{f_x̂}`` vectors are recalculated at each contr
\end{bmatrix} \\
\mathbf{k_x̂} &= \mathbf{Â}^{H_p} \\
\mathbf{v_x̂} &= \mathbf{W}(H_p-1)\mathbf{B̂_u} \\
\mathbf{b_x̂} &= \mathbf{W}(H_p-1) \mathbf{\big(x̂_{op} + f̂_{op}\big)}
\mathbf{b_x̂} &= \mathbf{W}(H_p-1) \mathbf{\big(f̂_{op} - x̂_{op}\big)}
\end{aligned}
```
"""
Expand Down Expand Up @@ -525,15 +524,16 @@ function init_predmat(estim::StateEstimator{NT}, model::LinModel, Hp, Hc) where
jx̂[: , iCol] = j < Hp ? getpower(Âpow, Hp-j-1)*B̂d : zeros(NT, nx̂, nd)
end
end
# --- state and state update f̂op operating points ---
# --- state x̂op and state update f̂op operating points ---
coef_bx̂ = getpower(Âpow_csum, Hp-1)
coef_B = Matrix{NT}(undef, ny*Hp, nx̂)
for j=1:Hp
iRow = (1:ny) .+ ny*(j-1)
coef_B[iRow,:] =*getpower(Âpow_csum, j-1)
end
bx̂ = coef_bx̂ * (estim.f̂op - estim.x̂op)
B = coef_B * (estim.f̂op - estim.x̂op)
f̂op_n_x̂op = estim.f̂op - estim.x̂op
bx̂ = coef_bx̂ * f̂op_n_x̂op
B = coef_B * f̂op_n_x̂op
return E, G, J, K, V, B, ex̂, gx̂, jx̂, kx̂, vx̂, bx̂
end

Expand Down
12 changes: 6 additions & 6 deletions src/controller/execute.jl
Original file line number Diff line number Diff line change
Expand Up @@ -564,12 +564,12 @@ function setmodel_controller!(mpc::PredictiveController, model::LinModel, x̂op_
mpc.Yop[(1+ny*i):(ny+ny*i)] .= model.yop
mpc.Dop[(1+nd*i):(nd+nd*i)] .= model.dop
end
con.U0min .-= mpc.Uop # convert U0 to U with the new operating point
con.U0max .-= mpc.Uop # convert U0 to U with the new operating point
con.Y0min .-= mpc.Yop # convert Y0 to Y with the new operating point
con.Y0max .-= mpc.Yop # convert Y0 to Y with the new operating point
con.x̂0min .-= estim.x̂op # convert x̂0 to with the new operating point
con.x̂0max .-= estim.x̂op # convert x̂0 to with the new operating point
con.U0min .-= mpc.Uop # convert U to U0 with the new operating point
con.U0max .-= mpc.Uop # convert U to U0 with the new operating point
con.Y0min .-= mpc.Yop # convert Y to Y0 with the new operating point
con.Y0max .-= mpc.Yop # convert Y to Y0 with the new operating point
con.x̂0min .-= estim.x̂op # convert to x̂0 with the new operating point
con.x̂0max .-= estim.x̂op # convert to x̂0 with the new operating point
con.A_Ymin .= A_Ymin
con.A_Ymax .= A_Ymax
con.A_x̂min .= A_x̂min
Expand Down
10 changes: 7 additions & 3 deletions src/estimator/execute.jl
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,8 @@ Set model and operating points of `estim` [`StateEstimator`](@ref) to `model` va
Allows model adaptation of estimators based on [`LinModel`](@ref) at runtime ([`NonLinModel`](@ref)
is not supported). Not supported by [`Luenberger`](@ref) and [`SteadyKalmanFilter`](@ref),
use the time-varying [`KalmanFilter`](@ref) instead. The matrix dimensions and sample time
use the time-varying [`KalmanFilter`](@ref) instead. The [`MovingHorizonEstimator`] model
is kept constant over the estimation horizon ``H_e``. The matrix dimensions and sample time
must stay the same. Note that the observability and controllability of the new augmented
model is not verified (see Extended Help for details).
Expand All @@ -272,6 +273,9 @@ julia> setmodel!(kf, LinModel(ss(0.42, 0.5, 1, 0, 4.0))); kf.model.A
"""
function setmodel!(estim::StateEstimator, model::LinModel)
validate_model(estim.model, model)
uop_old = copy(estim.model.uop)
yop_old = copy(estim.model.yop)
dop_old = copy(estim.model.dop)
# --- update model matrices and its operating points ---
estim.model.A .= model.A
estim.model.Bu .= model.Bu
Expand All @@ -285,7 +289,7 @@ function setmodel!(estim::StateEstimator, model::LinModel)
estim.model.dop .= model.dop
estim.model.xop .= model.xop
estim.model.fop .= model.fop
setmodel_estimator!(estim, model)
setmodel_estimator!(estim, model, uop_old, yop_old, dop_old)
return estim
end

Expand All @@ -300,7 +304,7 @@ end
validate_model(::SimModel, ::SimModel) = error("Only LinModel is supported for setmodel!")

"Update the augmented model matrices of `estim` by default."
function setmodel_estimator!(estim::StateEstimator, model::LinModel)
function setmodel_estimator!(estim::StateEstimator, model::LinModel, _ , _ , _)
As, Cs_u, Cs_y = estim.As, estim.Cs_u, estim.Cs_y
Â, B̂u, Ĉ, B̂d, D̂d, x̂op, f̂op = augment_model(model, As, Cs_u, Cs_y, verify_obsv=false)
# --- update augmented state-space matrices ---
Expand Down
4 changes: 2 additions & 2 deletions src/estimator/internal_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ function init_internalmodel(As, Bs, Cs, Ds)
end

"Update similar values for [`InternalModel`](@ref) estimator."
function setmodel_estimator!(estim::InternalModel, model::LinModel)
function setmodel_estimator!(estim::InternalModel, model::LinModel, _ , _ , _)
Â, B̂u, Ĉ, B̂d, D̂d, x̂op, f̂op = matrices_internalmodel(model)
# --- update augmented state-space matrices ---
estim. .=
Expand All @@ -215,7 +215,7 @@ function setmodel_estimator!(estim::InternalModel, model::LinModel)
estim.x̂0 .+= estim.x̂op # convert x̂0 to x̂ with the old operating point
estim.x̂op .= x̂op
estim.f̂op .= f̂op
estim.x̂0 .-= estim.x̂op # convert x̂0 to with the new operating point
estim.x̂0 .-= estim.x̂op # convert to x̂0 with the new operating point
end

@doc raw"""
Expand Down
2 changes: 1 addition & 1 deletion src/estimator/kalman.jl
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ function SteadyKalmanFilter(model::SM, i_ym, nint_u, nint_ym, Q̂, R̂) where {N
end

"Throw an error if `setmodel!` is called on a SteadyKalmanFilter"
function setmodel_estimator!(estim::SteadyKalmanFilter, model::LinModel)
function setmodel_estimator!(::SteadyKalmanFilter, ::LinModel, _ , _ , _)
error("SteadyKalmanFilter does not support setmodel! (use KalmanFilter instead)")
end

Expand Down
2 changes: 1 addition & 1 deletion src/estimator/luenberger.jl
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,4 @@ function update_estimate!(estim::Luenberger, u, ym, d=empty(estim.x̂0))
end

"Throw an error if `setmodel!` is called on `Luenberger` observer."
setmodel_estimator!(estim::Luenberger, ::LinModel) = error("Luenberger does not support setmodel!")
setmodel_estimator!(::Luenberger,::LinModel,_,_,_) = error("Luenberger does not support setmodel!")
Loading

2 comments on commit dc8681a

@franckgaga
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register

Release notes:

BREAKING CHANGE
All the keyword arguments related to initial values e.g. σP0, x0 and x̂0 now require an underscore e.g. σP_0, x_0, x̂_0 (to differentiate from operating point deviation vectors)

  • Added: setmodel! for runtime model adaptation of controller/estimator based on LinModel
  • Added: linearize and setop! now support non-equilibrium points
  • Added: successive linearization MPC with the new setmodel! and linearize functions
  • Added: successive linearization MHE with the new setmodel! and linearize functions
  • Added: linearize! method for in-place model linearization (to reduce allocations)
  • Added: 6 args. LinModel constructor now support scalars (similarly to ss function)
  • Added: ExtendedKalmanFilter now compute the Jacobians in-place (to reduce allocations)
  • Changed: struct state data x and state estimate renamed to x0 and x̂0
  • Debug: ExplicitMPC with non-Float64 now works
  • Debug: accept integers in linearize arguments
  • Debug: call empty! on JuMP.Model to support re-construction of MPC instances
  • Doc: new setmodel!, setop! and linearize function documentation
  • Doc: example of model adaptation with successive linearization on the pendulum (very efficient!)

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/105638

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.21.0 -m "<description of version>" dc8681a582fd459fa51699f1a8b894ade41ea512
git push origin v0.21.0

Please sign in to comment.