Skip to content

Commit aefc4ff

Browse files
committed
added: validate hessian backend
1 parent fd52b78 commit aefc4ff

File tree

3 files changed

+52
-18
lines changed

3 files changed

+52
-18
lines changed

src/ModelPredictiveControl.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ using Random: randn
66

77
using RecipesBase
88

9-
using DifferentiationInterface: ADTypes.AbstractADType, AutoForwardDiff, AutoSparse
9+
using DifferentiationInterface: ADTypes.AbstractADType, AutoForwardDiff
10+
using DifferentiationInterface: AutoSparse, SecondOrder
1011
using DifferentiationInterface: gradient!, value_and_gradient!, prepare_gradient
1112
using DifferentiationInterface: jacobian!, value_and_jacobian!, prepare_jacobian
1213
using DifferentiationInterface: hessian!, value_gradient_and_hessian!, prepare_hessian

src/controller/nonlinmpc.jl

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@ This controller allocates memory at each time step for the optimization.
223223
- `jacobian=default_jacobian(transcription)` : an `AbstractADType` backend for the Jacobian
224224
of the nonlinear constraints, see `gradient` above for the options (default in Extended Help).
225225
- `hessian=false` : an `AbstractADType` backend for the Hessian of the Lagrangian, see
226-
`gradient` above for the options (`false` to skip it and use `optim` approximation).
226+
`gradient` above for the options. The default `false` skip it and use the quasi-Newton
227+
method of `optim`, which is always the case if `oracle=false` (see Extended Help).
227228
- `oracle=JuMP.solver_name(optim)=="Ipopt"`: use the efficient [`VectorNonlinearOracle`](@extref MathOptInterface MathOptInterface.VectorNonlinearOracle)
228229
for the nonlinear constraints (not supported by most optimizers for now).
229230
- additional keyword arguments are passed to [`UnscentedKalmanFilter`](@ref) constructor
@@ -282,16 +283,20 @@ NonLinMPC controller with a sample time Ts = 10.0 s:
282283
the `gc` argument (both `gc` and `gc!` accepts non-mutating and mutating functions).
283284
284285
By default, the optimization relies on dense [`ForwardDiff`](@extref ForwardDiff)
285-
automatic differentiation (AD) to compute the objective and constraint derivatives. One
286-
exception: if `transcription` is not a [`SingleShooting`](@ref), the `jacobian` argument
287-
defaults to this [sparse backend](@extref DifferentiationInterface AutoSparse-object):
286+
automatic differentiation (AD) to compute the objective and constraint derivatives. Two
287+
exceptions: if `transcription` is not a [`SingleShooting`](@ref), the `jacobian`
288+
argument defaults to this [sparse backend](@extref DifferentiationInterface AutoSparse-object):
288289
```julia
289290
AutoSparse(
290291
AutoForwardDiff();
291292
sparsity_detector = TracerSparsityDetector(),
292293
coloring_algorithm = GreedyColoringAlgorithm()
293294
)
294295
```
296+
This is also the sparse backend selected for the Hessian of the Lagrangian function if
297+
`oracle=true` and `hessian=true`, which is the second exception. Second order
298+
derivatives are only supported with `oracle=true` option.
299+
295300
Optimizers generally benefit from exact derivatives like AD. However, the [`NonLinModel`](@ref)
296301
state-space functions must be compatible with this feature. See [`JuMP` documentation](@extref JuMP Common-mistakes-when-writing-a-user-defined-operator)
297302
for common mistakes when writing these functions.
@@ -402,7 +407,7 @@ function NonLinMPC(
402407
validate_JE(NT, JE)
403408
gc! = get_mutating_gc(NT, gc)
404409
weights = ControllerWeights(estim.model, Hp, Hc, M_Hp, N_Hc, L_Hp, Cwt, Ewt)
405-
hessian = default_hessian(gradient, jacobian, hessian)
410+
hessian = validate_hessian(hessian, gradient, oracle)
406411
return NonLinMPC{NT}(
407412
estim, Hp, Hc, nb, weights, JE, gc!, nc, p,
408413
transcription, optim, gradient, jacobian, hessian, oracle
@@ -412,14 +417,6 @@ end
412417
default_jacobian(::SingleShooting) = DEFAULT_NONLINMPC_JACDENSE
413418
default_jacobian(::TranscriptionMethod) = DEFAULT_NONLINMPC_JACSPARSE
414419

415-
function default_hessian(gradient, jacobian, hessian::Bool)
416-
if hessian
417-
return DEFAULT_NONLINMPC_HESSIAN
418-
else
419-
return nothing
420-
end
421-
end
422-
423420
"""
424421
validate_JE(NT, JE) -> nothing
425422
@@ -513,6 +510,34 @@ function test_custom_functions(NT, model::SimModel, JE, gc!, nc, Uop, Yop, Dop,
513510
return nothing
514511
end
515512

513+
"""
514+
validate_hessian(hessian, gradient, oracle) -> backend
515+
516+
Validate `hessian` argument and return the differentiation backend.
517+
"""
518+
function validate_hessian(hessian, gradient, oracle)
519+
if hessian == true
520+
backend = DEFAULT_NONLINMPC_HESSIAN
521+
elseif hessian == false || isnothing(hessian)
522+
backend = nothing
523+
else
524+
backend = hessian
525+
end
526+
if oracle == false && !isnothing(backend)
527+
error("Second order derivatives are only supported with oracle=true.")
528+
end
529+
if oracle == true && !isnothing(backend)
530+
hess = dense_backend(backend)
531+
grad = dense_backend(gradient)
532+
if hess != grad
533+
@info "The objective function gradient will be computed with the hessian "*
534+
"backend ($(backend_str(hess)))\n instead of the one in gradient "*
535+
"argument ($(backend_str(grad))) for efficiency."
536+
end
537+
end
538+
return backend
539+
end
540+
516541
"""
517542
addinfo!(info, mpc::NonLinMPC) -> info
518543

src/general.jl

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,11 @@ function init_diffstructure(A::AbstractSparseMatrix)
6969
I, J = findnz(A)
7070
return collect(zip(I, J))
7171
end
72-
init_diffstructure(A::AbstractMatrix)= Tuple.(CartesianIndices(A))[:]
72+
init_diffstructure(A::AbstractMatrix) = Tuple.(CartesianIndices(A))[:]
7373

74-
"Get the lower-triangular indices from the differentiation matrix structure."
75-
function lowertriangle_indices(diffmat_struct::Vector{Tuple{Int, Int}})
76-
return [(i,j) for (i,j) in diffmat_struct if i j]
74+
"Get the lower-triangular indices from the differentiation matrix structure `i_vec`."
75+
function lowertriangle_indices(i_vec::Vector{Tuple{Int, Int}})
76+
return [(i,j) for (i,j) in i_vec if i j]
7777
end
7878

7979
"Fill the lower triangular part of A in-place with the corresponding part in B."
@@ -100,6 +100,14 @@ function backend_str(backend::AutoSparse)
100100
" $(nameof(typeof(backend.coloring_algorithm))))"
101101
return str
102102
end
103+
function backend_str(backend::SecondOrder)
104+
str = "SecondOrder ($(nameof(typeof(backend.outer))),"*
105+
" $(nameof(typeof(backend.inner))))"
106+
return str
107+
end
108+
dense_backend(backend::AbstractADType) = backend
109+
dense_backend(backend::AutoSparse) = backend.dense_ad
110+
dense_backend(backend::SecondOrder) = backend.inner
103111

104112
"Verify that x and y elements are different using `!==`."
105113
isdifferent(x, y) = any(xi !== yi for (xi, yi) in zip(x, y))

0 commit comments

Comments
 (0)