@@ -1255,7 +1255,7 @@ function init_optimization!(
1255
1255
JuMP. set_attribute (optim, " nlp_scaling_max_gradient" , 10.0 / C)
1256
1256
end
1257
1257
end
1258
- Jfunc, gfuncs = get_optim_functions (estim, optim)
1258
+ Jfunc, ∇Jfunc!, gfuncs, ∇gfuncs! = get_optim_functions (estim, optim)
1259
1259
@operator (optim, J, nZ̃, Jfunc)
1260
1260
@objective (optim, Min, J (Z̃var... ))
1261
1261
nV̂, nX̂ = estim. He* estim. nym, estim. He* estim. nx̂
@@ -1293,10 +1293,26 @@ end
1293
1293
1294
1294
1295
1295
"""
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!
1297
1299
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.
1300
1316
1301
1317
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)
1302
1318
"""
@@ -1306,51 +1322,100 @@ function get_optim_functions(
1306
1322
model, con = estim. model, estim. con
1307
1323
nx̂, nym, nŷ, nu, nϵ, He = estim. nx̂, estim. nym, model. ny, model. nu, estim. nϵ, estim. He
1308
1324
nV̂, nX̂, ng, nZ̃ = He* nym, He* nx̂, length (con. i_g), length (estim. Z̃)
1309
- Nc = nZ̃ + 3
1325
+ Ncache = nZ̃ + 3
1310
1326
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)
1318
1335
# --------------------- 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]
1324
1343
end
1344
+ Z̃ = Z̃cache
1345
+ ϵ = (nϵ ≠ 0 ) ? Z̃[begin ] : zero (T) # ϵ = 0 if Cwt=Inf (meaning: no relaxation)
1325
1346
V̂, X̂0 = get_tmp (V̂_cache, Z̃1), get_tmp (X̂0_cache, Z̃1)
1326
1347
û0, ŷ0 = get_tmp (û0_cache, Z̃1), get_tmp (ŷ0_cache, Z̃1)
1327
1348
g = get_tmp (g_cache, Z̃1)
1328
1349
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)
1330
1350
g = con_nonlinprog! (g, estim, model, X̂0, V̂, ϵ)
1331
1351
end
1332
1352
return nothing
1333
1353
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)
1339
1358
return obj_nonlinprog! (x̄, estim, model, V̂, Z̃):: T
1340
1359
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
1347
1364
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 -------------------------------
1348
1382
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
1353
1418
end
1354
1419
end
1355
- return Jfunc, gfuncs
1420
+ return Jfunc, ∇Jfunc!, gfuncs, ∇gfuncs!
1356
1421
end
0 commit comments