@@ -13,9 +13,9 @@ struct NonLinMPC{
13
13
SE<: StateEstimator ,
14
14
TM<: TranscriptionMethod ,
15
15
JM<: JuMP.GenericModel ,
16
- GB<: AbstractADType ,
17
- JB<: AbstractADType ,
16
+ GB<: Union{Nothing, AbstractADType} ,
18
17
HB<: Union{Nothing, AbstractADType} ,
18
+ JB<: AbstractADType ,
19
19
PT<: Any ,
20
20
JEfunc<: Function ,
21
21
GCfunc<: Function
@@ -27,8 +27,8 @@ struct NonLinMPC{
27
27
optim:: JM
28
28
con:: ControllerConstraint{NT, GCfunc}
29
29
gradient:: GB
30
+ hessian :: HB
30
31
jacobian:: JB
31
- hessian:: HB
32
32
Z̃:: Vector{NT}
33
33
ŷ:: Vector{NT}
34
34
Hp:: Int
@@ -65,15 +65,15 @@ struct NonLinMPC{
65
65
function NonLinMPC {NT} (
66
66
estim:: SE ,
67
67
Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt, JE:: JEfunc , gc!:: GCfunc , nc, p:: PT ,
68
- transcription:: TM , optim:: JM , gradient:: GB , jacobian :: JB , hessian :: HB
68
+ transcription:: TM , optim:: JM , gradient:: GB , hessian :: HB , jacobian :: JB ,
69
69
) where {
70
70
NT<: Real ,
71
71
SE<: StateEstimator ,
72
72
TM<: TranscriptionMethod ,
73
73
JM<: JuMP.GenericModel ,
74
- GB<: AbstractADType ,
75
- JB<: AbstractADType ,
74
+ GB<: Union{Nothing, AbstractADType} ,
76
75
HB<: Union{Nothing, AbstractADType} ,
76
+ JB<: AbstractADType ,
77
77
PT<: Any ,
78
78
JEfunc<: Function ,
79
79
GCfunc<: Function ,
@@ -110,9 +110,9 @@ struct NonLinMPC{
110
110
nZ̃ = get_nZ (estim, transcription, Hp, Hc) + nϵ
111
111
Z̃ = zeros (NT, nZ̃)
112
112
buffer = PredictiveControllerBuffer (estim, transcription, Hp, Hc, nϵ)
113
- mpc = new {NT, SE, TM, JM, GB, JB, HB , PT, JEfunc, GCfunc} (
113
+ mpc = new {NT, SE, TM, JM, GB, HB, JB , PT, JEfunc, GCfunc} (
114
114
estim, transcription, optim, con,
115
- gradient, jacobian, hessian ,
115
+ gradient, hessian, jacobian ,
116
116
Z̃, ŷ,
117
117
Hp, Hc, nϵ,
118
118
weights,
@@ -205,12 +205,14 @@ This controller allocates memory at each time step for the optimization.
205
205
- `transcription=SingleShooting()` : a [`TranscriptionMethod`](@ref) for the optimization.
206
206
- `optim=JuMP.Model(Ipopt.Optimizer)` : nonlinear optimizer used in the predictive
207
207
controller, provided as a [`JuMP.Model`](@extref) object (default to [`Ipopt`](https://github.com/jump-dev/Ipopt.jl) optimizer).
208
- - `gradient=AutoForwardDiff()` : an `AbstractADType` backend for the gradient of the objective
209
- function, see [`DifferentiationInterface` doc](@extref DifferentiationInterface List).
208
+ - `hessian=nothing` : an `AbstractADType` backend for the Hessian of the objective function
209
+ (see [`DifferentiationInterface` doc](@extref DifferentiationInterface List)), or
210
+ `nothing` for the LBFGS approximation provided by `optim` (details in Extended Help).
211
+ - `gradient=isnothing(hessian) ? AutoForwardDiff() : nothing` : an `AbstractADType` backend
212
+ for the gradient of the objective function (see `hessian` for the options), or `nothing`
213
+ to retrieve lower-order derivatives from `hessian`.
210
214
- `jacobian=default_jacobian(transcription)` : an `AbstractADType` backend for the Jacobian
211
- of the nonlinear constraints, see `gradient` above for the options (default in Extended Help).
212
- - `hessian=nothing` : an `AbstractADType` backend for the Hessian of the objective function,
213
- see `gradient` above for the options, use `nothing` for the LBFGS approximation of `optim`.
215
+ of the nonlinear constraints (see `hessian` for the options, defaults in Extended Help).
214
216
- additional keyword arguments are passed to [`UnscentedKalmanFilter`](@ref) constructor
215
217
(or [`SteadyKalmanFilter`](@ref), for [`LinModel`](@ref)).
216
218
@@ -264,16 +266,16 @@ NonLinMPC controller with a sample time Ts = 10.0 s, Ipopt optimizer, UnscentedK
264
266
exception: if `transcription` is not a [`SingleShooting`](@ref), the `jacobian` argument
265
267
defaults to this [sparse backend](@extref DifferentiationInterface AutoSparse-object):
266
268
```julia
267
- AutoSparse(
269
+ sparseAD = AutoSparse(
268
270
AutoForwardDiff();
269
271
sparsity_detector = TracerSparsityDetector(),
270
272
coloring_algorithm = GreedyColoringAlgorithm()
271
273
)
272
274
```
273
- Also, the `hessian` argument defaults to `nothing` meaning the built-in second-order
274
- approximation of `solver `. Otherwise, a sparse backend like above is recommended to test
275
- different `hessian` methods. Optimizers generally benefit from exact derivatives like AD.
276
- However, the [`NonLinModel`](@ref) state-space functions must be compatible with this
275
+ Also, the `hessian` argument defaults to `nothing` meaning the LBFGS approximation of
276
+ `optim `. Otherwise, a sparse backend like above is recommended to test a different
277
+ `hessian` method. In general, optimizers benefit from exact derivatives like AD.
278
+ However, the [`NonLinModel`](@ref) state-space functions must be compatible with this
277
279
feature. See [`JuMP` documentation](@extref JuMP Common-mistakes-when-writing-a-user-defined-operator)
278
280
for common mistakes when writing these functions.
279
281
@@ -299,16 +301,16 @@ function NonLinMPC(
299
301
p = model. p,
300
302
transcription:: TranscriptionMethod = DEFAULT_NONLINMPC_TRANSCRIPTION,
301
303
optim:: JuMP.GenericModel = JuMP. Model (DEFAULT_NONLINMPC_OPTIMIZER, add_bridges= false ),
302
- gradient:: AbstractADType = DEFAULT_NONLINMPC_GRADIENT,
304
+ hessian :: Union{Nothing, AbstractADType} = nothing ,
305
+ gradient:: Union{Nothing, AbstractADType} = isnothing (hessian) ? DEFAULT_NONLINMPC_GRADIENT : nothing ,
303
306
jacobian:: AbstractADType = default_jacobian (transcription),
304
- hessian:: Union{Nothing, AbstractADType} = nothing ,
305
307
kwargs...
306
308
)
307
309
estim = UnscentedKalmanFilter (model; kwargs... )
308
310
return NonLinMPC (
309
311
estim;
310
312
Hp, Hc, Mwt, Nwt, Lwt, Cwt, Ewt, JE, gc, nc, p, M_Hp, N_Hc, L_Hp,
311
- transcription, optim, gradient, jacobian, hessian
313
+ transcription, optim, gradient, hessian, jacobian
312
314
)
313
315
end
314
316
@@ -331,16 +333,16 @@ function NonLinMPC(
331
333
p = model. p,
332
334
transcription:: TranscriptionMethod = DEFAULT_NONLINMPC_TRANSCRIPTION,
333
335
optim:: JuMP.GenericModel = JuMP. Model (DEFAULT_NONLINMPC_OPTIMIZER, add_bridges= false ),
334
- gradient:: AbstractADType = DEFAULT_NONLINMPC_GRADIENT,
336
+ hessian :: Union{Nothing, AbstractADType} = nothing ,
337
+ gradient:: Union{Nothing, AbstractADType} = isnothing (hessian) ? DEFAULT_NONLINMPC_GRADIENT : nothing ,
335
338
jacobian:: AbstractADType = default_jacobian (transcription),
336
- hessian:: Union{Nothing, AbstractADType} = nothing ,
337
339
kwargs...
338
340
)
339
341
estim = SteadyKalmanFilter (model; kwargs... )
340
342
return NonLinMPC (
341
343
estim;
342
344
Hp, Hc, Mwt, Nwt, Lwt, Cwt, Ewt, JE, gc, nc, p, M_Hp, N_Hc, L_Hp,
343
- transcription, optim, gradient, jacobian, hessian
345
+ transcription, optim, gradient, hessian, jacobian
344
346
)
345
347
end
346
348
@@ -387,9 +389,9 @@ function NonLinMPC(
387
389
p = estim. model. p,
388
390
transcription:: TranscriptionMethod = DEFAULT_NONLINMPC_TRANSCRIPTION,
389
391
optim:: JuMP.GenericModel = JuMP. Model (DEFAULT_NONLINMPC_OPTIMIZER, add_bridges= false ),
390
- gradient:: AbstractADType = DEFAULT_NONLINMPC_GRADIENT,
392
+ hessian :: Union{Nothing, AbstractADType} = nothing ,
393
+ gradient:: Union{Nothing, AbstractADType} = isnothing (hessian) ? DEFAULT_NONLINMPC_GRADIENT : nothing ,
391
394
jacobian:: AbstractADType = default_jacobian (transcription),
392
- hessian:: Union{Nothing, AbstractADType} = nothing ,
393
395
) where {
394
396
NT<: Real ,
395
397
SE<: StateEstimator{NT}
@@ -403,7 +405,7 @@ function NonLinMPC(
403
405
gc! = get_mutating_gc (NT, gc)
404
406
return NonLinMPC {NT} (
405
407
estim, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt, JE, gc!, nc, p,
406
- transcription, optim, gradient, jacobian, hessian
408
+ transcription, optim, gradient, hessian, jacobian
407
409
)
408
410
end
409
411
@@ -551,10 +553,12 @@ function init_optimization!(mpc::NonLinMPC, model::SimModel, optim::JuMP.Generic
551
553
JuMP. set_attribute (optim, " nlp_scaling_max_gradient" , 10.0 / C)
552
554
end
553
555
end
556
+ validate_backends (mpc. gradient, mpc. hessian)
554
557
Jfunc, ∇Jfunc!, ∇²Jfunc!, gfuncs, ∇gfuncs!, geqfuncs, ∇geqfuncs! = get_optim_functions (
555
558
mpc, optim
556
559
)
557
- @operator (optim, J, nZ̃, Jfunc, ∇Jfunc!)
560
+ Jargs = isnothing (∇²Jfunc!) ? (Jfunc, ∇Jfunc!) : (Jfunc, ∇Jfunc!, ∇²Jfunc!)
561
+ @operator (optim, J, nZ̃, Jargs... )
558
562
@objective (optim, Min, J (Z̃var... ))
559
563
init_nonlincon! (mpc, model, transcription, gfuncs, ∇gfuncs!, geqfuncs, ∇geqfuncs!)
560
564
set_nonlincon! (mpc, model, transcription, optim)
@@ -591,7 +595,7 @@ Inspired from: [User-defined operators with vector outputs](@extref JuMP User-de
591
595
function get_optim_functions (mpc:: NonLinMPC , :: JuMP.GenericModel{JNT} ) where JNT<: Real
592
596
# ----------- common cache for Jfunc, gfuncs and geqfuncs ----------------------------
593
597
model = mpc. estim. model
594
- grad, jac = mpc. gradient, mpc. jacobian
598
+ grad, hess, jac = mpc. gradient, mpc . hessian , mpc. jacobian
595
599
nu, ny, nx̂, nϵ, nk = model. nu, model. ny, mpc. estim. nx̂, mpc. nϵ, model. nk
596
600
Hp, Hc = mpc. Hp, mpc. Hc
597
601
ng, nc, neq = length (mpc. con. i_g), mpc. con. nc, mpc. con. neq
@@ -613,32 +617,58 @@ function get_optim_functions(mpc::NonLinMPC, ::JuMP.GenericModel{JNT}) where JNT
613
617
update_predictions! (ΔŨ, x̂0end, Ue, Ŷe, U0, Ŷ0, Û0, K0, X̂0, gc, g, geq, mpc, Z̃)
614
618
return obj_nonlinprog! (Ŷ0, U0, mpc, model, Ue, Ŷe, ΔŨ)
615
619
end
616
- Z̃_∇J = fill (myNaN, nZ̃) # NaN to force update_predictions! at first call
617
- ∇J_context = (
620
+ Z̃_J = fill (myNaN, nZ̃) # NaN to force update_predictions! at first call
621
+ context_J = (
618
622
Cache (ΔŨ), Cache (x̂0end), Cache (Ue), Cache (Ŷe), Cache (U0), Cache (Ŷ0),
619
623
Cache (Û0), Cache (K0), Cache (X̂0),
620
624
Cache (gc), Cache (g), Cache (geq),
621
625
)
622
- ∇J_prep = prepare_gradient (Jfunc!, grad, Z̃_∇J, ∇J_context... ; strict)
623
- ∇J = Vector {JNT} (undef, nZ̃)
624
- function update_objective! (J, ∇J, Z̃, Z̃arg)
626
+ if ! isnothing (grad)
627
+ prep_∇J = prepare_gradient (Jfunc!, grad, Z̃_J, context_J... ; strict)
628
+ else
629
+ prep_∇J = nothing
630
+ end
631
+ if ! isnothing (hess)
632
+ prep_∇²J = prepare_hessian (Jfunc!, hess, Z̃_J, context_J... ; strict)
633
+ display (sparsity_pattern (prep_∇²J))
634
+ else
635
+ prep_∇²J = nothing
636
+ end
637
+ ∇J = Vector {JNT} (undef, nZ̃)
638
+ ∇²J = init_diffmat (JNT, hess, prep_∇²J, nZ̃, nZ̃)
639
+
640
+
641
+
642
+ function update_objective! (J, ∇J, Z̃, Z̃arg, hess:: Nothing , grad:: AbstractADType )
643
+ if isdifferent (Z̃arg, Z̃)
644
+ Z̃ .= Z̃arg
645
+ J[], _ = value_and_gradient! (Jfunc!, ∇J, prep_∇J, grad, Z̃_J, context_J... )
646
+ end
647
+ end
648
+ function update_objective! (J, ∇J, Z̃, Z̃arg, hess:: AbstractADType , grad:: Nothing )
625
649
if isdifferent (Z̃arg, Z̃)
626
650
Z̃ .= Z̃arg
627
- J[], _ = value_and_gradient! (Jfunc!, ∇J, ∇J_prep, grad, Z̃_∇J, ∇J_context... )
651
+ J[], _ = value_gradient_and_hessian! (
652
+ Jfunc!, ∇J, ∇²J, prep_∇²J, hess, Z̃, context_J...
653
+ )
654
+ # display(∇J)
655
+ # display(∇²J)
656
+ # println(∇²J)
628
657
end
629
- end
658
+ end
659
+
630
660
function Jfunc (Z̃arg:: Vararg{T, N} ) where {N, T<: Real }
631
- update_objective! (J, ∇J, Z̃_∇J , Z̃arg)
661
+ update_objective! (J, ∇J, Z̃_J , Z̃arg, hess, grad )
632
662
return J[]:: T
633
663
end
634
664
∇Jfunc! = if nZ̃ == 1 # univariate syntax (see JuMP.@operator doc):
635
665
function (Z̃arg)
636
- update_objective! (J, ∇J, Z̃_∇J , Z̃arg)
666
+ update_objective! (J, ∇J, Z̃_J , Z̃arg, hess, grad )
637
667
return ∇J[begin ]
638
668
end
639
669
else # multivariate syntax (see JuMP.@operator doc):
640
670
function (∇Jarg:: AbstractVector{T} , Z̃arg:: Vararg{T, N} ) where {N, T<: Real }
641
- update_objective! (J, ∇J, Z̃_∇J , Z̃arg)
671
+ update_objective! (J, ∇J, Z̃_J , Z̃arg, hess, grad )
642
672
return ∇Jarg .= ∇J
643
673
end
644
674
end
@@ -648,27 +678,27 @@ function get_optim_functions(mpc::NonLinMPC, ::JuMP.GenericModel{JNT}) where JNT
648
678
update_predictions! (ΔŨ, x̂0end, Ue, Ŷe, U0, Ŷ0, Û0, K0, X̂0, gc, g, geq, mpc, Z̃)
649
679
return g
650
680
end
651
- Z̃_∇g = fill (myNaN, nZ̃) # NaN to force update_predictions! at first call
652
- ∇g_context = (
681
+ Z̃_g = fill (myNaN, nZ̃) # NaN to force update_predictions! at first call
682
+ context_g = (
653
683
Cache (ΔŨ), Cache (x̂0end), Cache (Ue), Cache (Ŷe), Cache (U0), Cache (Ŷ0),
654
684
Cache (Û0), Cache (K0), Cache (X̂0),
655
685
Cache (gc), Cache (geq),
656
686
)
657
687
# temporarily enable all the inequality constraints for sparsity detection:
658
688
mpc. con. i_g[1 : end - nc] .= true
659
- ∇g_prep = prepare_jacobian (gfunc!, g, jac, Z̃_∇g, ∇g_context ... ; strict)
689
+ ∇g_prep = prepare_jacobian (gfunc!, g, jac, Z̃_g, context_g ... ; strict)
660
690
mpc. con. i_g[1 : end - nc] .= false
661
691
∇g = init_diffmat (JNT, jac, ∇g_prep, nZ̃, ng)
662
692
function update_con! (g, ∇g, Z̃, Z̃arg)
663
693
if isdifferent (Z̃arg, Z̃)
664
694
Z̃ .= Z̃arg
665
- value_and_jacobian! (gfunc!, g, ∇g, ∇g_prep, jac, Z̃, ∇g_context ... )
695
+ value_and_jacobian! (gfunc!, g, ∇g, ∇g_prep, jac, Z̃, context_g ... )
666
696
end
667
697
end
668
698
gfuncs = Vector {Function} (undef, ng)
669
699
for i in eachindex (gfuncs)
670
700
gfunc_i = function (Z̃arg:: Vararg{T, N} ) where {N, T<: Real }
671
- update_con! (g, ∇g, Z̃_∇g , Z̃arg)
701
+ update_con! (g, ∇g, Z̃_g , Z̃arg)
672
702
return g[i]:: T
673
703
end
674
704
gfuncs[i] = gfunc_i
@@ -677,12 +707,12 @@ function get_optim_functions(mpc::NonLinMPC, ::JuMP.GenericModel{JNT}) where JNT
677
707
for i in eachindex (∇gfuncs!)
678
708
∇gfuncs_i! = if nZ̃ == 1 # univariate syntax (see JuMP.@operator doc):
679
709
function (Z̃arg:: T ) where T<: Real
680
- update_con! (g, ∇g, Z̃_∇g , Z̃arg)
710
+ update_con! (g, ∇g, Z̃_g , Z̃arg)
681
711
return ∇g[i, begin ]
682
712
end
683
713
else # multivariate syntax (see JuMP.@operator doc):
684
714
function (∇g_i, Z̃arg:: Vararg{T, N} ) where {N, T<: Real }
685
- update_con! (g, ∇g, Z̃_∇g , Z̃arg)
715
+ update_con! (g, ∇g, Z̃_g , Z̃arg)
686
716
return ∇g_i .= @views ∇g[i, :]
687
717
end
688
718
end
@@ -693,24 +723,24 @@ function get_optim_functions(mpc::NonLinMPC, ::JuMP.GenericModel{JNT}) where JNT
693
723
update_predictions! (ΔŨ, x̂0end, Ue, Ŷe, U0, Ŷ0, Û0, K0, X̂0, gc, g, geq, mpc, Z̃)
694
724
return geq
695
725
end
696
- Z̃_∇geq = fill (myNaN, nZ̃) # NaN to force update_predictions! at first call
697
- ∇geq_context = (
726
+ Z̃_geq = fill (myNaN, nZ̃) # NaN to force update_predictions! at first call
727
+ context_geq = (
698
728
Cache (ΔŨ), Cache (x̂0end), Cache (Ue), Cache (Ŷe), Cache (U0), Cache (Ŷ0),
699
729
Cache (Û0), Cache (K0), Cache (X̂0),
700
730
Cache (gc), Cache (g)
701
731
)
702
- ∇geq_prep = prepare_jacobian (geqfunc!, geq, jac, Z̃_∇geq, ∇geq_context ... ; strict)
732
+ ∇geq_prep = prepare_jacobian (geqfunc!, geq, jac, Z̃_geq, context_geq ... ; strict)
703
733
∇geq = init_diffmat (JNT, jac, ∇geq_prep, nZ̃, neq)
704
734
function update_con_eq! (geq, ∇geq, Z̃, Z̃arg)
705
735
if isdifferent (Z̃arg, Z̃)
706
736
Z̃ .= Z̃arg
707
- value_and_jacobian! (geqfunc!, geq, ∇geq, ∇geq_prep, jac, Z̃, ∇geq_context ... )
737
+ value_and_jacobian! (geqfunc!, geq, ∇geq, ∇geq_prep, jac, Z̃, context_geq ... )
708
738
end
709
739
end
710
740
geqfuncs = Vector {Function} (undef, neq)
711
741
for i in eachindex (geqfuncs)
712
742
geqfunc_i = function (Z̃arg:: Vararg{T, N} ) where {N, T<: Real }
713
- update_con_eq! (geq, ∇geq, Z̃_∇geq , Z̃arg)
743
+ update_con_eq! (geq, ∇geq, Z̃_geq , Z̃arg)
714
744
return geq[i]:: T
715
745
end
716
746
geqfuncs[i] = geqfunc_i
@@ -721,7 +751,7 @@ function get_optim_functions(mpc::NonLinMPC, ::JuMP.GenericModel{JNT}) where JNT
721
751
# constraints imply MultipleShooting, thus input increment ΔU and state X̂0 in Z̃:
722
752
∇geqfuncs_i! =
723
753
function (∇geq_i, Z̃arg:: Vararg{T, N} ) where {N, T<: Real }
724
- update_con_eq! (geq, ∇geq, Z̃_∇geq , Z̃arg)
754
+ update_con_eq! (geq, ∇geq, Z̃_geq , Z̃arg)
725
755
return ∇geq_i .= @views ∇geq[i, :]
726
756
end
727
757
∇geqfuncs![i] = ∇geqfuncs_i!
0 commit comments