Skip to content

Commit 03cb818

Browse files
authored
Merge pull request #209 from JuliaControl/manual_estimator_test
Added tests and improve doc for `ManualEstimator`
2 parents 414c283 + 40bd66d commit 03cb818

File tree

7 files changed

+283
-158
lines changed

7 files changed

+283
-158
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 = "1.6.2"
4+
version = "1.7.0"
55

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

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ for more detailed examples.
129129
- extended Kalman filter
130130
- unscented Kalman filter
131131
- moving horizon estimator
132+
- disable built-in observer to manually provide your own state estimate
132133
- easily estimate unmeasured disturbances by adding one or more integrators at the:
133134
- manipulated inputs
134135
- measured outputs

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ links = InterLinks(
1212
"JuMP" => "https://jump.dev/JuMP.jl/stable/objects.inv",
1313
"DifferentiationInterface" => "https://juliadiff.org/DifferentiationInterface.jl/DifferentiationInterface/stable/objects.inv",
1414
"ForwardDiff" => "https://juliadiff.org/ForwardDiff.jl/stable/objects.inv",
15+
"LowLevelParticleFilters" => "https://baggepinnen.github.io/LowLevelParticleFilters.jl/stable/objects.inv",
1516
)
1617

1718
DocMeta.setdocmeta!(

src/estimator/manual.jl

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,13 @@ end
5555
Construct a manual state estimator for `model` ([`LinModel`](@ref) or [`NonLinModel`](@ref)).
5656
5757
This [`StateEstimator`](@ref) type allows the construction of [`PredictiveController`](@ref)
58-
objects but turns off the built-in state estimation. The user must manually provides the
58+
objects but turns off the built-in state estimation. The user must manually provides the
5959
estimate ``\mathbf{x̂}_{k}(k)`` or ``\mathbf{x̂}_{k-1}(k)`` through [`setstate!`](@ref) at
60-
each time step. Calling [`preparestate!`](@ref) and [`updatestate!`](@ref) will not modify
61-
the estimate. See Extended Help for usage examples.
60+
each time step ``k``. The states in the estimator must obviously represent the same thing as
61+
in the controller, both for the deterministic and the stochastic model of the unmeasured
62+
disturbances (`nint_u` and `nint_ym` arguments). Calling [`preparestate!`](@ref) and
63+
[`updatestate!`](@ref) on this object will do nothing at all. See Extended Help for usage
64+
examples.
6265
6366
# Arguments
6467
- `model::SimModel` : (deterministic) model for the estimations.
@@ -84,13 +87,51 @@ ManualEstimator estimator with a sample time Ts = 0.5 s, LinModel and:
8487
8588
# Extended Help
8689
!!! details "Extended Help"
87-
A first use case is a linear predictive controller based on nonlinear state estimation.
88-
The [`ManualEstimator`](@ref) serves as a wrapper to provide the minimal required
89-
information to construct a [`PredictiveController`](@ref). e.g.:
90+
A first use case is a linear predictive controller based on nonlinear state estimation,
91+
for example a nonlinear [`MovingHorizonEstimator`](@ref) (MHE). Note that the model
92+
augmentation scheme with `nint_u` and `nint_ym` options must be identical for both
93+
[`StateEstimator`](@ref) objects (see [`SteadyKalmanFilter`](@ref) extended help for
94+
details on augmentation). The [`ManualEstimator`](@ref) serves as a wrapper to provide
95+
the minimal required information to construct any [`PredictiveController`](@ref) object:
9096
91-
```julia
92-
a=1
97+
```jldoctest
98+
julia> function man_sim()
99+
f(x,u,_,_) = 0.5*sin.(x + u)
100+
h(x,_,_) = x
101+
model = NonLinModel(f, h, 10.0, 1, 1, 1, solver=nothing)
102+
linModel = linearize(model, x=[0], u=[0])
103+
man = ManualEstimator(linModel, nint_u=[1])
104+
mpc = LinMPC(man)
105+
estim = MovingHorizonEstimator(model, nint_u=[1], He=5)
106+
estim = setconstraint!(estim, v̂min=[-0.001], v̂max=[0.001])
107+
initstate!(estim, [0], [0])
108+
y_data, ŷ_data = zeros(5), zeros(5)
109+
for i=1:5
110+
y = model() # simulated measurement
111+
x̂ = preparestate!(estim, y) # correct nonlinear MHE state estimate
112+
ŷ = estim() # nonlinear MHE estimated output
113+
setstate!(mpc, x̂) # update MPC with the MHE corrected state
114+
u = moveinput!(mpc, [0])
115+
y_data[i], ŷ_data[i] = y[1], ŷ[1]
116+
updatestate!(estim, u, y) # update nonlinear MHE estimation
117+
updatestate!(model, u .+ 0.5) # update simulator with load disturbance
118+
end
119+
return collect([y_data ŷ_data]')
120+
end;
121+
122+
julia> YandŶ = round.(man_sim(), digits=6)
123+
2×5 Matrix{Float64}:
124+
0.0 0.239713 0.227556 0.157837 0.098629
125+
-0.0 0.238713 0.226556 0.156837 0.097629
93126
```
127+
128+
A second use case is to allow the user to manually provide the state estimate computed
129+
from an external source, e.g. an observer from [`LowLevelParticleFilters`](@extref LowLevelParticleFilters).
130+
A custom stochastic model for the unmeasured disturbances (other than integrated white
131+
noise) can be specified by constructing a [`SimModel`](@ref) object with the augmented
132+
state-space matrices/functions directly, and by setting `nint_u=0` and `nint_ym=0`. See
133+
[`Disturbance-gallery`](@extref LowLevelParticleFilters) for examples of other
134+
disturbance models.
94135
"""
95136
function ManualEstimator(
96137
model::SM;

src/estimator/mhe/execute.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ function init_estimate_cov!(estim::MovingHorizonEstimator, _ , d0, u0)
1515
estim.U0[1:estim.model.nu] .= u0
1616
estim.D0[1:estim.model.nd] .= d0
1717
end
18+
estim.lastu0 .= u0
1819
# estim.P̂_0 is in fact P̂(-1|-1) is estim.direct==false, else P̂(-1|0)
1920
invert_cov!(estim, estim.P̂_0)
2021
estim.P̂arr_old .= estim.P̂_0

src/precompile.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ mpc_mhe.estim()
5656
u = mpc_mhe([55, 30])
5757
sim!(mpc_mhe, 2, [55, 30])
5858

59+
mpc_man = setconstraint!(LinMPC(ManualEstimator(model)), ymin=[45, -Inf])
60+
initstate!(mpc_man, model.uop, model())
61+
setstate!(mpc_man, ones(4))
62+
u = mpc_man([55, 30])
63+
5964
nmpc_skf = setconstraint!(NonLinMPC(SteadyKalmanFilter(model), Cwt=Inf), ymin=[45, -Inf])
6065
initstate!(nmpc_skf, model.uop, model())
6166
preparestate!(nmpc_skf, [55, 30])

0 commit comments

Comments
 (0)