Skip to content

Commit 072f604

Browse files
committed
debug : sensor noise constraints now works
1 parent 9a7e2e9 commit 072f604

File tree

2 files changed

+59
-28
lines changed

2 files changed

+59
-28
lines changed

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,13 @@ for more detailed examples.
119119
- [x] easily estimate unmeasured disturbances by adding one or more integrators at the:
120120
- [x] manipulated inputs
121121
- [x] measured outputs
122-
- [x] bumpless manual to automatic transfer for control with a proper intial estimate
122+
- [x] bumpless manual to automatic transfer for control with a proper initial estimate
123123
- [x] observers in predictor form to ease control applications
124-
- [x] moving horizon estimator that supports:
125-
- [x] inequality state constraints
124+
- [x] moving horizon estimator in two formulations:
125+
- [x] linear plant models (quadratic optimization)
126+
- [x] nonlinear plant models (nonlinear optimization)
127+
- [x] moving horizon estimator constraints on:
128+
- [x] state estimates
129+
- [x] process noise estimates
130+
- [x] sensor noise estimates
126131
- [ ] zero process noise equality constraint to reduce the problem size

src/estimator/mhe.jl

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ model is identical to the one in [`UnscentedKalmanFilter`](@ref) documentation.
193193
- `He=nothing`: estimation horizon ``H_e``, must be specified.
194194
- `optim=default_optim_mhe(model)` : quadratic or nonlinear optimizer used in the moving
195195
horizon estimator, provided as a [`JuMP.Model`](https://jump.dev/JuMP.jl/stable/api/JuMP/#JuMP.Model)
196-
(default to [`Ipopt.jl`](https://github.com/jump-dev/Ipopt.jl), or [`OSQP.jl`](https://osqp.org/docs/parsers/jump.html)
196+
(default to [`Ipopt`](https://github.com/jump-dev/Ipopt.jl), or [`OSQP`](https://osqp.org/docs/parsers/jump.html)
197197
if `model` is a [`LinModel`](@ref)).
198198
- `<keyword arguments>` of [`SteadyKalmanFilter`](@ref) constructor.
199199
- `<keyword arguments>` of [`KalmanFilter`](@ref) constructor.
@@ -562,7 +562,7 @@ function init_optimization!(
562562
g = get_tmp(g_cache, Z̃tup[1])
563563
= get_tmp(X̂_cache, Z̃tup[1])
564564
V̂, X̂ = predict!(V̂, X̂, estim, model, Z̃)
565-
g = con_nonlinprog!(g, estim, model, X̂)
565+
g = con_nonlinprog!(g, estim, model, X̂, V̂)
566566
last_Z̃tup_float = Z̃tup
567567
end
568568
return obj_nonlinprog(estim, model, V̂, Z̃)
@@ -574,7 +574,7 @@ function init_optimization!(
574574
g = get_tmp(g_cache, Z̃tup[1])
575575
= get_tmp(X̂_cache, Z̃tup[1])
576576
V̂, X̂ = predict!(V̂, X̂, estim, model, Z̃)
577-
g = con_nonlinprog!(g, estim, model, X̂)
577+
g = con_nonlinprog!(g, estim, model, X̂, V̂)
578578
last_Z̃tup_dual = Z̃tup
579579
end
580580
return obj_nonlinprog(estim, model, V̂, Z̃)
@@ -586,7 +586,7 @@ function init_optimization!(
586586
= get_tmp(X̂_cache, Z̃tup[1])
587587
= collect(Z̃tup)
588588
V̂, X̂ = predict!(V̂, X̂, estim, model, Z̃)
589-
g = con_nonlinprog!(g, estim, model, X̂)
589+
g = con_nonlinprog!(g, estim, model, X̂, V̂)
590590
last_Z̃tup_float = Z̃tup
591591
end
592592
return g[i]
@@ -598,7 +598,7 @@ function init_optimization!(
598598
= get_tmp(X̂_cache, Z̃tup[1])
599599
= collect(Z̃tup)
600600
V̂, X̂ = predict!(V̂, X̂, estim, model, Z̃)
601-
g = con_nonlinprog!(g, estim, model, X̂)
601+
g = con_nonlinprog!(g, estim, model, X̂, V̂)
602602
last_Z̃tup_dual = Z̃tup
603603
end
604604
return g[i]
@@ -715,8 +715,8 @@ function setconstraint!(
715715
isnothing(X̂max) && !isnothing(x̂max) && (X̂max = repeat(x̂max, He+1))
716716
isnothing(Ŵmin) && !isnothing(ŵmin) && (Ŵmin = repeat(ŵmin, He))
717717
isnothing(Ŵmax) && !isnothing(ŵmax) && (Ŵmax = repeat(ŵmax, He))
718-
isnothing(V̂min) && !isnothing(V̂min) && (X̂min = repeat(v̂min, He))
719-
isnothing(V̂max) && !isnothing(V̂max) && (X̂max = repeat(v̂max, He))
718+
isnothing(V̂min) && !isnothing(v̂min) && (V̂min = repeat(v̂min, He))
719+
isnothing(V̂max) && !isnothing(v̂max) && (V̂max = repeat(v̂max, He))
720720
if !isnothing(X̂min)
721721
size(X̂min) == (nX̂con,) || throw(ArgumentError("X̂min size must be $((nX̂con,))"))
722722
con.x̂min[:] = X̂min[1:nx̂]
@@ -838,6 +838,14 @@ function setnonlincon!(estim::MovingHorizonEstimator, ::NonLinModel)
838838
f_sym = Symbol("g_X̂max_$(i)")
839839
add_nonlinear_constraint(optim, :($(f_sym)($(Z̃var...)) <= 0))
840840
end
841+
for i in findall(.!isinf.(con.V̂min))
842+
f_sym = Symbol("g_V̂min_$(i)")
843+
add_nonlinear_constraint(optim, :($(f_sym)($(Z̃var...)) <= 0))
844+
end
845+
for i in findall(.!isinf.(con.V̂max))
846+
f_sym = Symbol("g_V̂max_$(i)")
847+
add_nonlinear_constraint(optim, :($(f_sym)($(Z̃var...)) <= 0))
848+
end
841849
return nothing
842850
end
843851

@@ -922,11 +930,11 @@ function update_estimate!(estim::MovingHorizonEstimator{NT}, u, ym, d) where NT<
922930
Nk = estim.Nk[]
923931
nŴ, nYm, nX̂ = nx̂*Nk, nym*Nk, nx̂*Nk
924932
Z̃var::Vector{VariableRef} = optim[:Z̃var]
925-
x̄V̂ = Vector{NT}(undef, nx̂ + nYm)
933+
= Vector{NT}(undef, nYm)
926934
= Vector{NT}(undef, nX̂)
927935
Z̃0 = [estim.x̂arr_old; estim.Ŵ]
928-
x̄V̂, X̂ = predict!(x̄V̂, X̂, estim, model, Z̃0)
929-
J0 = obj_nonlinprog(estim, model, x̄V̂, Z̃0)
936+
, X̂ = predict!(, X̂, estim, model, Z̃0)
937+
J0 = obj_nonlinprog(estim, model, , Z̃0)
930938
# initial Z̃0 with Ŵ=0 if objective or constraint function not finite :
931939
isfinite(J0) || (Z̃0 = [estim.x̂arr_old; zeros(NT, nŴ)])
932940
set_start_value.(Z̃var, Z̃0)
@@ -958,7 +966,7 @@ function update_estimate!(estim::MovingHorizonEstimator{NT}, u, ym, d) where NT<
958966
estim.Z̃[:] = !isfatal(status) ? Z̃curr : Z̃last
959967
# --------- update estimate -----------------------
960968
estim.Ŵ[1:nŴ] = estim.Z̃[nx̂+1:nx̂+nŴ] # update Ŵ with optimum for next time step
961-
x̄V̂, X̂ = predict!(x̄V̂, X̂, estim, model, estim.Z̃)
969+
, X̂ = predict!(, X̂, estim, model, estim.Z̃)
962970
x̂[:] = X̂[(1 + nx̂*(Nk-1)):(nx̂*Nk)]
963971
if Nk == He
964972
uarr, ymarr, darr = estim.U[1:nu], estim.Ym[1:nym], estim.D[1:nd]
@@ -1006,31 +1014,49 @@ function linconstraint!(estim::MovingHorizonEstimator, model::LinModel)
10061014
if model.nd 0
10071015
estim.con.Fx̂[:] = estim.con.Fx̂ + estim.con.Jx̂*estim.D
10081016
end
1017+
X̂min, X̂max = trunc_bounds(estim, estim.con.X̂min, estim.con.X̂max, estim.nx̂)
1018+
Ŵmin, Ŵmax = trunc_bounds(estim, estim.con.Ŵmin, estim.con.Ŵmax, estim.nx̂)
1019+
V̂min, V̂max = trunc_bounds(estim, estim.con.V̂min, estim.con.V̂max, estim.nym)
10091020
estim.con.b[:] = [
10101021
-estim.con.x̂min
10111022
+estim.con.x̂max
1012-
-estim.con.X̂min + estim.con.Fx̂
1013-
+estim.con.X̂max - estim.con.Fx̂
1014-
-estim.con.Ŵmin
1015-
+estim.con.Ŵmax
1016-
-estim.con.V̂min + estim.F
1017-
+estim.con.V̂max - estim.F
1023+
-X̂min + estim.con.Fx̂
1024+
+X̂max - estim.con.Fx̂
1025+
-Ŵmin
1026+
+Ŵmax
1027+
-V̂min + estim.F
1028+
+V̂max - estim.F
10181029
]
10191030
lincon = estim.optim[:linconstraint]
10201031
set_normalized_rhs.(lincon, estim.con.b[estim.con.i_b])
10211032
end
10221033

10231034
function linconstraint!(estim::MovingHorizonEstimator, ::SimModel)
1035+
Ŵmin, Ŵmax = trunc_bounds(estim, estim.con.Ŵmin, estim.con.Ŵmax, estim.nx̂)
10241036
estim.con.b[:] = [
10251037
-estim.con.x̂min
10261038
+estim.con.x̂max
1027-
-estim.con.Ŵmin
1028-
+estim.con.Ŵmax
1039+
-Ŵmin
1040+
+Ŵmax
10291041
]
10301042
lincon = estim.optim[:linconstraint]
10311043
set_normalized_rhs.(lincon, estim.con.b[estim.con.i_b])
10321044
end
10331045

1046+
"Truncate the bounds `Bmin` and `Bmax` to the window size `Nk` if `Nk < He`."
1047+
function trunc_bounds(estim::MovingHorizonEstimator, Bmin, Bmax, n)
1048+
He, Nk = estim.He, estim.Nk[]
1049+
if Nk < He
1050+
nB = n*Nk
1051+
Bmin_t = @views [Bmin[end-nB+1:end]; fill(-Inf, He*n-nB)]
1052+
Bmax_t = @views [Bmax[end-nB+1:end]; fill(+Inf, He*n-nB)]
1053+
else
1054+
Bmin_t = Bmin
1055+
Bmax_t = Bmax
1056+
end
1057+
return Bmin_t, Bmax_t
1058+
end
1059+
10341060
"Update the covariance `P̂` with the `KalmanFilter` if `model` is a `LinModel`."
10351061
function update_cov!(P̂, estim::MovingHorizonEstimator, ::LinModel, u, ym, d)
10361062
return update_estimate_kf!(estim, u, ym, d, estim.Â, estim.Ĉ[estim.i_ym, :], P̂)
@@ -1105,27 +1131,27 @@ function predict!(
11051131
end
11061132

11071133
"""
1108-
con_nonlinprog!(g, estim::MovingHorizonEstimator, model::SimModel, X̂)
1134+
con_nonlinprog!(g, estim::MovingHorizonEstimator, model::SimModel, X̂, V̂)
11091135
11101136
Nonlinear constrains for [`MovingHorizonEstimator`](@ref).
11111137
"""
1112-
function con_nonlinprog!(g, estim::MovingHorizonEstimator, ::SimModel, X̂)
1138+
function con_nonlinprog!(g, estim::MovingHorizonEstimator, ::SimModel, X̂, V̂)
11131139
nX̂con, nX̂ = length(estim.con.X̂min), estim.nx̂ *estim.Nk[]
11141140
nV̂con, nV̂ = length(estim.con.V̂min), estim.nym*estim.Nk[]
11151141
for i in eachindex(g)
11161142
estim.con.i_g[i] || continue
11171143
if i nX̂con
11181144
j = i
1119-
(j nX̂) && (g[i] = estim.con.X̂min[j] - X̂[j])
1145+
g[i] = j > nX̂ ? 0 : estim.con.X̂min[nX̂con-nX̂+j] - X̂[j]
11201146
elseif i 2nX̂con
11211147
j = i - nX̂con
1122-
(j nX̂) && (g[i] = X̂[j] - estim.con.X̂max[j])
1148+
g[i] = j > nX̂ ? 0 : X̂[j] - estim.con.X̂max[nX̂con-nX̂+j]
11231149
elseif i 2nX̂con + nV̂con
11241150
j = i - 2nX̂con
1125-
(j nV̂) && (g[i] = estim.con.V̂min[j] - V̂[j])
1151+
g[i] = j > nV̂ ? 0 : estim.con.V̂min[nV̂con-nV̂+j] - V̂[j]
11261152
else
11271153
j = i - 2nX̂con - nV̂con
1128-
(j nV̂) && (g[i] = V̂[j] - estim.con.V̂max[j])
1154+
g[i] = j > nV̂ ? 0 : V̂[j] - estim.con.V̂max[nV̂con-nV̂+j]
11291155
end
11301156
end
11311157
return g

0 commit comments

Comments
 (0)