Skip to content

Commit 4e6b0c2

Browse files
authored
Merge pull request #46 from JuliaControl/nlp_single_funcs
Changed: avoid `collect` in `NonLinMPC` and `MovingHorizonEstimator` objective and constraints
2 parents 56b1cf0 + 579b2ec commit 4e6b0c2

File tree

8 files changed

+74
-106
lines changed

8 files changed

+74
-106
lines changed

docs/src/manual/nonlinmpc.md

+7-7
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ The vectors `σQ` and σR `σR` are the standard deviations of the process and s
8787
respectively. The value for the velocity ``ω`` is higher here (`σQ` second value) since
8888
``\dot{ω}(t)`` equation includes an uncertain parameter: the friction coefficient ``K``.
8989
Also, the argument `nint_u` explicitly adds one integrating state at the model input, the
90-
motor torque ``τ`` , with an associated standard deviation `σQint_u` of 0.1 N m. The
90+
motor torque ``τ``, with an associated standard deviation `σQint_u` of 0.1 N m. The
9191
estimator tuning is tested on a plant with a 25 % larger friction coefficient ``K``:
9292

9393
```@example 1
@@ -108,12 +108,12 @@ As the motor torque is limited to -1.5 to 1.5 N m, we incorporate the input cons
108108
a [`NonLinMPC`](@ref):
109109

110110
```@example 1
111-
nmpc = NonLinMPC(estim, Hp=20, Hc=2, Mwt=[0.5], Nwt=[2.5])
111+
nmpc = NonLinMPC(estim, Hp=20, Hc=2, Mwt=[0.5], Nwt=[2.5], Cwt=Inf)
112112
nmpc = setconstraint!(nmpc, umin=[-1.5], umax=[+1.5])
113113
```
114114

115-
We test `mpc` performance on `plant` by imposing an angular setpoint of 180° (inverted
116-
position):
115+
The option `Cwt=Inf` disables constraint softening. We test `mpc` performance on `plant` by
116+
imposing an angular setpoint of 180° (inverted position):
117117

118118
```@example 1
119119
using Logging; disable_logging(Warn) # hide
@@ -185,7 +185,7 @@ function JE(UE, ŶE, _ )
185185
τ, ω = UE[1:end-1], ŶE[2:2:end-1]
186186
return Ts*sum(τ.*ω)
187187
end
188-
empc = NonLinMPC(estim2, Hp=20, Hc=2, Mwt=[0.5, 0], Nwt=[2.5], Ewt=3.5e3, JE=JE)
188+
empc = NonLinMPC(estim2, Hp=20, Hc=2, Mwt=[0.5, 0], Nwt=[2.5], Cwt=Inf, Ewt=3.5e3, JE=JE)
189189
empc = setconstraint!(empc, umin=[-1.5], umax=[+1.5])
190190
```
191191

@@ -250,7 +250,7 @@ A [`SteadyKalmanFilter`](@ref) and a [`LinMPC`](@ref) are designed from `linmode
250250

251251
```@example 1
252252
kf = SteadyKalmanFilter(linmodel; σQ, σR, nint_u, σQint_u)
253-
mpc = LinMPC(kf, Hp=20, Hc=2, Mwt=[0.5], Nwt=[2.5])
253+
mpc = LinMPC(kf, Hp=20, Hc=2, Mwt=[0.5], Nwt=[2.5], Cwt=Inf)
254254
mpc = setconstraint!(mpc, umin=[-1.5], umax=[+1.5])
255255
```
256256

@@ -288,7 +288,7 @@ Constructing a [`LinMPC`](@ref) with `DAQP`:
288288
```@example 1
289289
using JuMP, DAQP
290290
daqp = Model(DAQP.Optimizer, add_bridges=false)
291-
mpc2 = LinMPC(kf, Hp=20, Hc=2, Mwt=[0.5], Nwt=[2.5], optim=daqp)
291+
mpc2 = LinMPC(kf, Hp=20, Hc=2, Mwt=[0.5], Nwt=[2.5], Cwt=Inf, optim=daqp)
292292
mpc2 = setconstraint!(mpc2, umin=[-1.5], umax=[+1.5])
293293
```
294294

src/controller/execute.jl

+5-2
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,6 @@ function optim_objective!(mpc::PredictiveController{NT}) where {NT<:Real}
445445
rethrow(err)
446446
end
447447
end
448-
ΔŨcurr, ΔŨlast = value.(ΔŨvar), ΔŨ0
449448
if !issolved(optim)
450449
status = termination_status(optim)
451450
if iserror(optim)
@@ -457,7 +456,11 @@ function optim_objective!(mpc::PredictiveController{NT}) where {NT<:Real}
457456
end
458457
@debug solution_summary(optim, verbose=true)
459458
end
460-
mpc.ΔŨ .= iserror(optim) ? ΔŨlast : ΔŨcurr
459+
if iserror(optim)
460+
mpc.ΔŨ .= ΔŨ0
461+
else
462+
mpc.ΔŨ .= value.(ΔŨvar)
463+
end
461464
return mpc.ΔŨ
462465
end
463466

src/controller/nonlinmpc.jl

+24-44
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ function init_optimization!(mpc::NonLinMPC, optim::JuMP.GenericModel{JNT}) where
304304
Jfunc, gfunc = let mpc=mpc, model=model, ng=ng, nΔŨ=nΔŨ, nŶ=Hp*ny, nx̂=nx̂, nu=nu, nU=Hp*nu
305305
Nc = nΔŨ + 3
306306
last_ΔŨtup_float, last_ΔŨtup_dual = nothing, nothing
307+
ΔŨ_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nΔŨ), Nc)
307308
Ŷ_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nŶ), Nc)
308309
U_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nU), Nc)
309310
g_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, ng), Nc)
@@ -313,63 +314,42 @@ function init_optimization!(mpc::NonLinMPC, optim::JuMP.GenericModel{JNT}) where
313314
û_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nu), Nc)
314315
Ȳ_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nŶ), Nc)
315316
Ū_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nU), Nc)
316-
function Jfunc(ΔŨtup::JNT...)
317+
function Jfunc(ΔŨtup::T...)::T where {T <: Real}
317318
ΔŨ1 = ΔŨtup[begin]
318-
= get_tmp(Ŷ_cache, ΔŨ1)
319-
ΔŨ = collect(ΔŨtup)
320-
if ΔŨtup !== last_ΔŨtup_float
321-
x̂, x̂next = get_tmp(x̂_cache, ΔŨ1), get_tmp(x̂next_cache, ΔŨ1)
322-
u, û = get_tmp(u_cache, ΔŨ1), get_tmp(û_cache, ΔŨ1)
323-
Ŷ, x̂end = predict!(Ŷ, x̂, x̂next, u, û, mpc, model, ΔŨ)
324-
g = get_tmp(g_cache, ΔŨ1)
325-
g = con_nonlinprog!(g, mpc, model, x̂end, Ŷ, ΔŨ)
319+
if T == JNT
326320
last_ΔŨtup_float = ΔŨtup
321+
else
322+
last_ΔŨtup_dual = ΔŨtup
327323
end
324+
ΔŨ, Ŷ = get_tmp(ΔŨ_cache, ΔŨ1), get_tmp(Ŷ_cache, ΔŨ1)
325+
x̂, x̂next = get_tmp(x̂_cache, ΔŨ1), get_tmp(x̂next_cache, ΔŨ1)
326+
u, û = get_tmp(u_cache, ΔŨ1), get_tmp(û_cache, ΔŨ1)
327+
ΔŨ .= ΔŨtup
328+
Ŷ, x̂end = predict!(Ŷ, x̂, x̂next, u, û, mpc, model, ΔŨ)
329+
g = get_tmp(g_cache, ΔŨ1)
330+
g = con_nonlinprog!(g, mpc, model, x̂end, Ŷ, ΔŨ)
328331
U, Ȳ, Ū = get_tmp(U_cache, ΔŨ1), get_tmp(Ȳ_cache, ΔŨ1), get_tmp(Ū_cache, ΔŨ1)
329-
return obj_nonlinprog!(U, Ȳ, Ū, mpc, model, Ŷ, ΔŨ)
330-
end
331-
function Jfunc(ΔŨtup::ForwardDiff.Dual...)
332-
ΔŨ1 = ΔŨtup[begin]
333-
= get_tmp(Ŷ_cache, ΔŨ1)
334-
ΔŨ = collect(ΔŨtup)
335-
if ΔŨtup !== last_ΔŨtup_dual
336-
x̂, x̂next = get_tmp(x̂_cache, ΔŨ1), get_tmp(x̂next_cache, ΔŨ1)
337-
u, û = get_tmp(u_cache, ΔŨ1), get_tmp(û_cache, ΔŨ1)
338-
Ŷ, x̂end = predict!(Ŷ, x̂, x̂next, u, û, mpc, model, ΔŨ)
339-
g = get_tmp(g_cache, ΔŨ1)
340-
g = con_nonlinprog!(g, mpc, model, x̂end, Ŷ, ΔŨ)
341-
last_ΔŨtup_dual = ΔŨtup
342-
end
343-
U, Ȳ, Ū = get_tmp(U_cache, ΔŨ1), get_tmp(Ȳ_cache, ΔŨ1), get_tmp(Ū_cache, ΔŨ1)
344-
return obj_nonlinprog!(U, Ȳ, Ū, mpc, model, Ŷ, ΔŨ)
332+
return obj_nonlinprog!(U, Ȳ, Ū, mpc, model, Ŷ, ΔŨ)::T
345333
end
346-
function gfunc_i(i, ΔŨtup::NTuple{N, JNT}) where N
334+
function gfunc_i(i, ΔŨtup::NTuple{N, T})::T where {N, T <:Real}
347335
ΔŨ1 = ΔŨtup[begin]
348336
g = get_tmp(g_cache, ΔŨ1)
349-
if ΔŨtup !== last_ΔŨtup_float
350-
= get_tmp(Ŷ_cache, ΔŨ1)
351-
ΔŨ = collect(ΔŨtup)
352-
x̂, x̂next = get_tmp(x̂_cache, ΔŨ1), get_tmp(x̂next_cache, ΔŨ1)
353-
u, û = get_tmp(u_cache, ΔŨ1), get_tmp(û_cache, ΔŨ1)
354-
Ŷ, x̂end = predict!(Ŷ, x̂, x̂next, u, û, mpc, model, ΔŨ)
355-
g = con_nonlinprog!(g, mpc, model, x̂end, Ŷ, ΔŨ)
356-
last_ΔŨtup_float = ΔŨtup
337+
if T == JNT
338+
isnewvalue = (ΔŨtup !== last_ΔŨtup_float)
339+
isnewvalue && (last_ΔŨtup_float = ΔŨtup)
340+
else
341+
isnewvalue = (ΔŨtup !== last_ΔŨtup_dual)
342+
isnewvalue && (last_ΔŨtup_dual = ΔŨtup)
357343
end
358-
return g[i]
359-
end
360-
function gfunc_i(i, ΔŨtup::NTuple{N, ForwardDiff.Dual}) where N
361-
ΔŨ1 = ΔŨtup[begin]
362-
g = get_tmp(g_cache, ΔŨ1)
363-
if ΔŨtup !== last_ΔŨtup_dual
364-
= get_tmp(Ŷ_cache, ΔŨ1)
365-
ΔŨ = collect(ΔŨtup)
344+
if isnewvalue
345+
ΔŨ, Ŷ = get_tmp(ΔŨ_cache, ΔŨ1), get_tmp(Ŷ_cache, ΔŨ1)
366346
x̂, x̂next = get_tmp(x̂_cache, ΔŨ1), get_tmp(x̂next_cache, ΔŨ1)
367347
u, û = get_tmp(u_cache, ΔŨ1), get_tmp(û_cache, ΔŨ1)
348+
ΔŨ .= ΔŨtup
368349
Ŷ, x̂end = predict!(Ŷ, x̂, x̂next, u, û, mpc, model, ΔŨ)
369350
g = con_nonlinprog!(g, mpc, model, x̂end, Ŷ, ΔŨ)
370-
last_ΔŨtup_dual = ΔŨtup
371351
end
372-
return g[i]
352+
return g[i]::T
373353
end
374354
gfunc = [(ΔŨ...) -> gfunc_i(i, ΔŨ) for i in 1:ng]
375355
(Jfunc, gfunc)

src/estimator/mhe/construct.jl

+23-43
Original file line numberDiff line numberDiff line change
@@ -1008,72 +1008,52 @@ function init_optimization!(
10081008
Jfunc, gfunc = let estim=estim, model=model, nZ̃=nZ̃, nV̂=nV̂, nX̂=nX̂, ng=ng, nx̂=nx̂, nu=nu, nŷ=nŷ
10091009
Nc = nZ̃ + 3
10101010
last_Z̃tup_float, last_Z̃tup_dual = nothing, nothing
1011+
Z̃_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nZ̃), Nc)
10111012
V̂_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nV̂), Nc)
10121013
g_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, ng), Nc)
10131014
X̂_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nX̂), Nc)
10141015
x̄_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nx̂), Nc)
10151016
û_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nu), Nc)
10161017
ŷ_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nŷ), Nc)
1017-
function Jfunc(Z̃tup::JNT...)
1018+
function Jfunc(Z̃tup::T...)::T where {T <: Real}
10181019
Z̃1 = Z̃tup[begin]
1019-
= get_tmp(V̂_cache, Z̃1)
1020-
= collect(Z̃tup)
1021-
if Z̃tup !== last_Z̃tup_float
1022-
g = get_tmp(g_cache, Z̃1)
1023-
= get_tmp(X̂_cache, Z̃1)
1024-
û, ŷ = get_tmp(û_cache, Z̃1), get_tmp(ŷ_cache, Z̃1)
1025-
V̂, X̂ = predict!(V̂, X̂, û, ŷ, estim, model, Z̃)
1026-
g = con_nonlinprog!(g, estim, model, X̂, V̂, Z̃)
1020+
if T == JNT
10271021
last_Z̃tup_float = Z̃tup
1028-
end
1029-
= get_tmp(x̄_cache, Z̃1)
1030-
return obj_nonlinprog!(x̄, estim, model, V̂, Z̃)
1031-
end
1032-
function Jfunc(Z̃tup::ForwardDiff.Dual...)
1033-
Z̃1 = Z̃tup[begin]
1034-
= get_tmp(V̂_cache, Z̃1)
1035-
= collect(Z̃tup)
1036-
if Z̃tup !== last_Z̃tup_dual
1037-
g = get_tmp(g_cache, Z̃1)
1038-
= get_tmp(X̂_cache, Z̃1)
1039-
û, ŷ = get_tmp(û_cache, Z̃1), get_tmp(ŷ_cache, Z̃1)
1040-
V̂, X̂ = predict!(V̂, X̂, û, ŷ, estim, model, Z̃)
1041-
g = con_nonlinprog!(g, estim, model, X̂, V̂, Z̃)
1022+
else
10421023
last_Z̃tup_dual = Z̃tup
10431024
end
1025+
Z̃, V̂ = get_tmp(Z̃_cache, Z̃1), get_tmp(V̂_cache, Z̃1)
1026+
= get_tmp(X̂_cache, Z̃1)
1027+
û, ŷ = get_tmp(û_cache, Z̃1), get_tmp(ŷ_cache, Z̃1)
1028+
Z̃ .= Z̃tup
1029+
V̂, X̂ = predict!(V̂, X̂, û, ŷ, estim, model, Z̃)
1030+
g = get_tmp(g_cache, Z̃1)
1031+
g = con_nonlinprog!(g, estim, model, X̂, V̂, Z̃)
10441032
= get_tmp(x̄_cache, Z̃1)
1045-
return obj_nonlinprog!(x̄, estim, model, V̂, Z̃)
1033+
return obj_nonlinprog!(x̄, estim, model, V̂, Z̃)::T
10461034
end
1047-
function gfunc_i(i, Z̃tup::NTuple{N, JNT}) where N
1035+
function gfunc_i(i, Z̃tup::NTuple{N, T})::T where {N, T <:Real}
10481036
Z̃1 = Z̃tup[begin]
10491037
g = get_tmp(g_cache, Z̃1)
1050-
if Z̃tup !== last_Z̃tup_float
1051-
= collect(Z̃tup)
1052-
= get_tmp(V̂_cache, Z̃1)
1053-
= get_tmp(X̂_cache, Z̃1)
1054-
û, ŷ = get_tmp(û_cache, Z̃1), get_tmp(ŷ_cache, Z̃1)
1055-
V̂, X̂ = predict!(V̂, X̂, û, ŷ, estim, model, Z̃)
1056-
g = con_nonlinprog!(g, estim, model, X̂, V̂, Z̃)
1057-
last_Z̃tup_float = Z̃tup
1038+
if T == JNT
1039+
isnewvalue = (Z̃tup !== last_Z̃tup_float)
1040+
isnewvalue && (last_Z̃tup_float = Z̃tup)
1041+
else
1042+
isnewvalue = (Z̃tup !== last_Z̃tup_dual)
1043+
isnewvalue && (last_Z̃tup_dual = Z̃tup)
10581044
end
1059-
return g[i]
1060-
end
1061-
function gfunc_i(i, Z̃tup::NTuple{N, ForwardDiff.Dual}) where N
1062-
Z̃1 = Z̃tup[begin]
1063-
g = get_tmp(g_cache, Z̃1)
1064-
if Z̃tup !== last_Z̃tup_dual
1065-
= collect(Z̃tup)
1066-
= get_tmp(V̂_cache, Z̃1)
1045+
if isnewvalue
1046+
Z̃, V̂ = get_tmp(Z̃_cache, Z̃1), get_tmp(V̂_cache, Z̃1)
10671047
= get_tmp(X̂_cache, Z̃1)
10681048
û, ŷ = get_tmp(û_cache, Z̃1), get_tmp(ŷ_cache, Z̃1)
1049+
Z̃ .= Z̃tup
10691050
V̂, X̂ = predict!(V̂, X̂, û, ŷ, estim, model, Z̃)
10701051
g = con_nonlinprog!(g, estim, model, X̂, V̂, Z̃)
1071-
last_Z̃tup_dual = Z̃tup
10721052
end
10731053
return g[i]
10741054
end
10751055
gfunc = [(Z̃...) -> gfunc_i(i, Z̃) for i in 1:ng]
1076-
Jfunc, gfunc
1056+
(Jfunc, gfunc)
10771057
end
10781058
register(optim, :Jfunc, nZ̃, Jfunc, autodiff=true)
10791059
@NLobjective(optim, Min, Jfunc(Z̃var...))

src/estimator/mhe/execute.jl

+5-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ function update_estimate!(estim::MovingHorizonEstimator{NT}, u, ym, d) where NT<
6262
end
6363
end
6464
# -------- error handling -------------------------
65-
Z̃curr, Z̃last = value.(Z̃var), Z̃0
6665
if !issolved(optim)
6766
status = termination_status(optim)
6867
if iserror(optim)
@@ -74,7 +73,11 @@ function update_estimate!(estim::MovingHorizonEstimator{NT}, u, ym, d) where NT<
7473
end
7574
@debug solution_summary(optim, verbose=true)
7675
end
77-
estim.Z̃ .= iserror(optim) ? Z̃last : Z̃curr
76+
if iserror(optim)
77+
estim.Z̃ .= Z̃0
78+
else
79+
estim.Z̃ .= value.(Z̃var)
80+
end
7881
# --------- update estimate -----------------------
7982
estim.Ŵ[1:nŵ*Nk] .= @views estim.Z̃[nx̃+1:nx̃+nŵ*Nk] # update Ŵ with optimum for warm-start
8083
V̂, X̂ = predict!(V̂, X̂, û, ŷ, estim, model, estim.Z̃)

src/general.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ const DEFAULT_CWT = 1e5
77
const DEFAULT_EWT = 0.0
88

99
"Termination status that means 'no solution available'."
10-
const ERROR_STATUSES = [
10+
const ERROR_STATUSES = (
1111
INFEASIBLE, DUAL_INFEASIBLE, LOCALLY_INFEASIBLE, INFEASIBLE_OR_UNBOUNDED,
1212
NUMERICAL_ERROR, INVALID_MODEL, INVALID_OPTION, INTERRUPTED,
1313
OTHER_ERROR
14-
]
14+
)
1515

1616
"Verify that `optim` termination status is `OPTIMAL` or `LOCALLY_SOLVED`."
1717
function issolved(optim::JuMP.GenericModel)
@@ -22,7 +22,7 @@ end
2222
"Verify that `optim` termination status means 'no solution available'."
2323
function iserror(optim::JuMP.GenericModel)
2424
status = termination_status(optim)
25-
return any(status .== ERROR_STATUSES)
25+
return any(errstatus->isequal(status, errstatus), ERROR_STATUSES)
2626
end
2727

2828
"Evaluate the quadratic programming objective function `0.5x'*H*x + q'*x` at `x`."

src/plot_sim.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ function sim!(
129129
U_data[:, i] = u
130130
D_data[:, i] = d
131131
X_data[:, i] = plant.x
132-
x = updatestate!(plant, u, d)
132+
updatestate!(plant, u, d)
133133
end
134134
return SimResult(plant, T_data, Y_data, U_data, Y_data,
135135
U_data, U_data, U_data, D_data, X_data, X_data)

test/test_state_estim.jl

+6-4
Original file line numberDiff line numberDiff line change
@@ -682,10 +682,12 @@ end
682682

683683
mhe4 = setconstraint!(MovingHorizonEstimator(nonlinmodel, He=1, nint_ym=0), x̂max=[50,50,50,50])
684684
g_X̂max_end = mhe4.optim.nlp_model.operators.registered_multivariate_operators[end].f
685-
@test g_X̂max_end((1.0, 1.0, 1.0, 1.0)) 0.0 # test gfunc_i(i,::NTuple{N, Float64})
686-
# test gfunc_i(i,::NTuple{N, ForwardDiff.Dual}) :
687-
@test ForwardDiff.gradient(g_X̂max_end, [1.0, 1.0, 1.0, 1.0]) [0.0, 0.0, 0.0, 0.0]
688-
685+
# test gfunc_i(i,::NTuple{N, Float64}):
686+
@test g_X̂max_end(
687+
(1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0)) 0.0
688+
# test gfunc_i(i,::NTuple{N, ForwardDiff.Dual}):
689+
@test ForwardDiff.gradient(
690+
g_X̂max_end, [1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0]) [0, 0, 0, 0, 0, 0, 0, 0]
689691
= diagm([1/4, 1/4, 1/4, 1/4].^2)
690692
= diagm([1, 1].^2)
691693
optim = Model(Ipopt.Optimizer)

0 commit comments

Comments
 (0)