@@ -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
13011317Inspired 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̃ = 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- Z̃ = 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- Z̃ = 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!
13561421end
0 commit comments