Skip to content

Commit f43ac3c

Browse files
committed
added: same improvements in MovingHorizonEstimator with NonLinModel
1 parent 12269ee commit f43ac3c

File tree

2 files changed

+102
-37
lines changed

2 files changed

+102
-37
lines changed

src/controller/nonlinmpc.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ end
515515

516516
"""
517517
get_optim_functions(
518-
mpc::NonLinMPC, ::JuMP.GenericModel
518+
mpc::NonLinMPC, optim::JuMP.GenericModel
519519
) -> Jfunc, ∇Jfunc!, gfuncs, ∇gfuncs!, geqfuncs, ∇geqfuncs!
520520
521521
Return the functions for the nonlinear optimization of `mpc` [`NonLinMPC`](@ref) controller.
@@ -563,7 +563,7 @@ function get_optim_functions(mpc::NonLinMPC, ::JuMP.GenericModel{JNT}) where JNT
563563
function update_simulations!(
564564
Z̃arg::Union{NTuple{N, T}, AbstractVector{T}}, Z̃cache
565565
) where {N, T<:Real}
566-
if isdifferent(Z̃cache, Z̃arg) # new Z̃, update:
566+
if isdifferent(Z̃cache, Z̃arg)
567567
for i in eachindex(Z̃cache)
568568
# Z̃cache .= Z̃arg is type unstable with Z̃arg::NTuple{N, FowardDiff.Dual}
569569
Z̃cache[i] = Z̃arg[i]
@@ -600,7 +600,7 @@ function get_optim_functions(mpc::NonLinMPC, ::JuMP.GenericModel{JNT}) where JNT
600600
ΔŨ = get_tmp(ΔŨ_cache, T)
601601
Ue, Ŷe = get_tmp(Ue_cache, T), get_tmp(Ŷe_cache, T)
602602
U0, Ŷ0 = get_tmp(U0_cache, T), get_tmp(Ŷ0_cache, T)
603-
return obj_nonlinprog!(Ŷ0, U0, mpc, model, Ue, Ŷe, ΔŨ)
603+
return obj_nonlinprog!(Ŷ0, U0, mpc, model, Ue, Ŷe, ΔŨ)::T
604604
end
605605
Z̃_∇J = fill(myNaN, nZ̃)
606606
∇J = Vector{JNT}(undef, nZ̃) # gradient of objective J

src/estimator/mhe/construct.jl

Lines changed: 99 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1255,7 +1255,7 @@ function init_optimization!(
12551255
JuMP.set_attribute(optim, "nlp_scaling_max_gradient", 10.0/C)
12561256
end
12571257
end
1258-
Jfunc, gfuncs = get_optim_functions(estim, optim)
1258+
Jfunc, ∇Jfunc!, gfuncs, ∇gfuncs! = get_optim_functions(estim, optim)
12591259
@operator(optim, J, nZ̃, Jfunc)
12601260
@objective(optim, Min, J(Z̃var...))
12611261
nV̂, nX̂ = estim.He*estim.nym, estim.He*estim.nx̂
@@ -1293,10 +1293,26 @@ end
12931293

12941294

12951295
"""
1296-
get_optim_functions(estim::MovingHorizonEstimator, ::JuMP.GenericModel) -> Jfunc, gfuncs
1296+
get_optim_functions(
1297+
estim::MovingHorizonEstimator, optim::JuMP.GenericModel
1298+
) -> Jfunc, ∇Jfunc!, gfuncs, ∇gfuncs!
12971299
1298-
Get the objective `Jfunc` function and constraint `gfuncs` function vector for
1299-
[`MovingHorizonEstimator`](@ref).
1300+
Return the functions for the nonlinear optimization of [`MovingHorizonEstimator`](@ref).
1301+
1302+
Return the nonlinear objective `Jfunc` function, and `∇Jfunc!`, to compute its gradient.
1303+
Also return vectors with the nonlinear inequality constraint functions `gfuncs`, and
1304+
`∇gfuncs!`, for the associated gradients.
1305+
1306+
This method is really indicated and I'm not proud of it. That's because of 3 elements:
1307+
1308+
- These functions are used inside the nonlinear optimization, so they must be type-stable
1309+
and as efficient as possible.
1310+
- The `JuMP` NLP syntax forces splatting for the decision variable, which implies use
1311+
of `Vararg{T,N}` (see the [performance tip](https://docs.julialang.org/en/v1/manual/performance-tips/#Be-aware-of-when-Julia-avoids-specializing))
1312+
and memoization to avoid redundant computations. This is already complex, but it's even
1313+
worse knowing that most automatic differentiation tools do not support splatting.
1314+
- The signature of gradient and hessian functions is not the same for univariate (`nZ̃ == 1`)
1315+
and multivariate (`nZ̃ > 1`) operators in `JuMP`. Both must be defined.
13001316
13011317
Inspired from: [User-defined operators with vector outputs](https://jump.dev/JuMP.jl/stable/tutorials/nonlinear/tips_and_tricks/#User-defined-operators-with-vector-outputs)
13021318
"""
@@ -1306,51 +1322,100 @@ function get_optim_functions(
13061322
model, con = estim.model, estim.con
13071323
nx̂, nym, nŷ, nu, nϵ, He = estim.nx̂, estim.nym, model.ny, model.nu, estim.nϵ, estim.He
13081324
nV̂, nX̂, ng, nZ̃ = He*nym, He*nx̂, length(con.i_g), length(estim.Z̃)
1309-
Nc = nZ̃ + 3
1325+
Ncache = nZ̃ + 3
13101326
myNaN = convert(JNT, NaN) # fill Z̃ with NaNs to force update_simulations! at 1st call:
1311-
Z̃_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(fill(myNaN, nZ̃), Nc)
1312-
V̂_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nV̂), Nc)
1313-
g_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, ng), Nc)
1314-
X̂0_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nX̂), Nc)
1315-
x̄_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nx̂), Nc)
1316-
û0_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nu), Nc)
1317-
ŷ0_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nŷ), Nc)
1327+
# ---------------------- differentiation cache ---------------------------------------
1328+
Z̃_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(fill(myNaN, nZ̃), Ncache)
1329+
V̂_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nV̂), Ncache)
1330+
g_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, ng), Ncache)
1331+
X̂0_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nX̂), Ncache)
1332+
x̄_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nx̂), Ncache)
1333+
û0_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nu), Ncache)
1334+
ŷ0_cache::DiffCache{Vector{JNT}, Vector{JNT}} = DiffCache(zeros(JNT, nŷ), Ncache)
13181335
# --------------------- update simulation function ------------------------------------
1319-
function update_simulations!(Z̃, Z̃tup::NTuple{N, T}) where {N, T <:Real}
1320-
if any(new !== old for (new, old) in zip(Z̃tup, Z̃)) # new Z̃tup, update predictions:
1321-
Z̃1 = Z̃tup[begin]
1322-
for i in eachindex(Z̃tup)
1323-
Z̃[i] = Z̃tup[i] # Z̃ .= Z̃tup seems to produce a type instability
1336+
function update_simulations!(
1337+
Z̃arg::Union{NTuple{N, T}, AbstractVector{T}}, Z̃cache
1338+
) where {N, T <:Real}
1339+
if isdifferent(Z̃cache, Z̃arg)
1340+
for i in eachindex(Z̃cache)
1341+
# Z̃cache .= Z̃arg is type unstable with Z̃arg::NTuple{N, FowardDiff.Dual}
1342+
Z̃cache[i] = Z̃arg[i]
13241343
end
1344+
= Z̃cache
1345+
ϵ = (nϵ 0) ? Z̃[begin] : zero(T) # ϵ = 0 if Cwt=Inf (meaning: no relaxation)
13251346
V̂, X̂0 = get_tmp(V̂_cache, Z̃1), get_tmp(X̂0_cache, Z̃1)
13261347
û0, ŷ0 = get_tmp(û0_cache, Z̃1), get_tmp(ŷ0_cache, Z̃1)
13271348
g = get_tmp(g_cache, Z̃1)
13281349
V̂, X̂0 = predict!(V̂, X̂0, û0, ŷ0, estim, model, Z̃)
1329-
ϵ = (nϵ 0) ? Z̃[begin] : zero(T) # ϵ = 0 if Cwt=Inf (meaning: no relaxation)
13301350
g = con_nonlinprog!(g, estim, model, X̂0, V̂, ϵ)
13311351
end
13321352
return nothing
13331353
end
1334-
function Jfunc(Z̃tup::Vararg{T, N}) where {N, T<:Real}
1335-
Z̃1 = Z̃tup[begin]
1336-
= get_tmp(Z̃_cache, Z̃1)
1337-
update_simulations!(Z̃, Z̃tup)
1338-
x̄, V̂ = get_tmp(x̄_cache, Z̃1), get_tmp(V̂_cache, Z̃1)
1354+
# --------------------- objective functions -------------------------------------------
1355+
function Jfunc(Z̃arg::Vararg{T, N}) where {N, T<:Real}
1356+
update_simulations!(Z̃arg, get_tmp(Z̃_cache, T))
1357+
x̄, V̂ = get_tmp(x̄_cache, T), get_tmp(V̂_cache, T)
13391358
return obj_nonlinprog!(x̄, estim, model, V̂, Z̃)::T
13401359
end
1341-
function gfunc_i(i, Z̃tup::NTuple{N, T})::T where {N, T <:Real}
1342-
Z̃1 = Z̃tup[begin]
1343-
= get_tmp(Z̃_cache, Z̃1)
1344-
update_simulations!(Z̃, Z̃tup)
1345-
g = get_tmp(g_cache, Z̃1)
1346-
return g[i]
1360+
function Jfunc_vec(Z̃arg::AbstractVector{T}) where T<:Real
1361+
update_simulations!(Z̃arg, get_tmp(Z̃_cache, T))
1362+
x̄, V̂ = get_tmp(x̄_cache, T), get_tmp(V̂_cache, T)
1363+
return obj_nonlinprog!(x̄, estim, model, V̂, Z̃)::T
13471364
end
1365+
Z̃_∇J = fill(myNaN, nZ̃)
1366+
∇J = Vector{JNT}(undef, nZ̃) # gradient of objective J
1367+
∇J_buffer = GradientBuffer(Jfunc_vec, Z̃_∇J)
1368+
∇Jfunc! = if nZ̃ == 1
1369+
function (Z̃arg::T) where T<:Real
1370+
Z̃_∇J .= Z̃arg
1371+
gradient!(∇J, ∇J_buffer, Z̃_∇J)
1372+
return ∇J[begin] # univariate syntax, see JuMP.@operator doc
1373+
end
1374+
else
1375+
function (∇J::AbstractVector{T}, Z̃arg::Vararg{T, N}) where {N, T<:Real}
1376+
Z̃_∇J .= Z̃arg
1377+
gradient!(∇J, ∇J_buffer, Z̃_∇J)
1378+
return ∇J # multivariate syntax, see JuMP.@operator doc
1379+
end
1380+
end
1381+
# --------------------- inequality constraint functions -------------------------------
13481382
gfuncs = Vector{Function}(undef, ng)
1349-
for i in 1:ng
1350-
# this is another syntax for anonymous function, allowing parameters T and N:
1351-
gfuncs[i] = function (ΔŨtup::Vararg{T, N}) where {N, T<:Real}
1352-
return gfunc_i(i, ΔŨtup)
1383+
for i in eachindex(gfuncs)
1384+
func_i = function (Z̃arg::Vararg{T, N}) where {N, T<:Real}
1385+
update_simulations!(Z̃arg, get_tmp(Z̃_cache, T))
1386+
g = get_tmp(g_cache, T)
1387+
return g[i]::T
1388+
end
1389+
gfuncs[i] = func_i
1390+
end
1391+
function gfunc_vec!(g, Z̃vec::AbstractVector{T}) where T<:Real
1392+
update_simulations!(Z̃vec, get_tmp(Z̃_cache, T))
1393+
g .= get_tmp(g_cache, T)
1394+
return g
1395+
end
1396+
Z̃_∇g = fill(myNaN, nZ̃)
1397+
g_vec = Vector{JNT}(undef, ng)
1398+
∇g = Matrix{JNT}(undef, ng, nZ̃) # Jacobian of inequality constraints g
1399+
∇g_buffer = JacobianBuffer(gfunc_vec!, g_vec, Z̃_∇g)
1400+
∇gfuncs! = Vector{Function}(undef, ng)
1401+
for i in eachindex(∇gfuncs!)
1402+
∇gfuncs![i] = if nZ̃ == 1
1403+
function (Z̃arg::T) where T<:Real
1404+
if isdifferent(Z̃arg, Z̃_∇g)
1405+
Z̃_∇g .= Z̃arg
1406+
jacobian!(∇g, ∇g_buffer, g_vec, Z̃_∇g)
1407+
end
1408+
return ∇g[i, begin] # univariate syntax, see JuMP.@operator doc
1409+
end
1410+
else
1411+
function (∇g_i, Z̃arg::Vararg{T, N}) where {N, T<:Real}
1412+
if isdifferent(Z̃arg, Z̃_∇g)
1413+
Z̃_∇g .= Z̃arg
1414+
jacobian!(∇g, ∇g_buffer, g_vec, Z̃_∇g)
1415+
end
1416+
return ∇g_i .= @views ∇g[i, :] # multivariate syntax, see JuMP.@operator doc
1417+
end
13531418
end
13541419
end
1355-
return Jfunc, gfuncs
1420+
return Jfunc, ∇Jfunc!, gfuncs, ∇gfuncs!
13561421
end

0 commit comments

Comments
 (0)