Skip to content

Commit 833c822

Browse files
Merge pull request #265 from SciML/mtk_sparsity
Finish up AutoModelingToolkit sparsity handling 2
2 parents 158e468 + ea0c2b6 commit 833c822

File tree

10 files changed

+61
-68
lines changed

10 files changed

+61
-68
lines changed

Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ ProgressLogging = "33c8b6b6-d38a-422a-b730-caa89a2f386c"
1515
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
1616
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
1717
SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
18+
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
1819
TerminalLoggers = "5d786b92-1e48-4d6f-9151-6b4477ca9bed"
1920

2021
[compat]

lib/OptimizationMOI/Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ Optimization = "3"
1414

1515
[extras]
1616
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
17+
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
1718
NLopt = "76087f3c-5699-56af-9a33-bf431cd00edd"
1819
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1920
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
2021

2122
[targets]
22-
test = ["Ipopt", "NLopt", "Test", "Zygote"]
23+
test = ["Ipopt", "ModelingToolkit", "NLopt", "Test", "Zygote"]

lib/OptimizationMOI/src/OptimizationMOI.jl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
module OptimizationMOI
22

3-
using MathOptInterface, Optimization, Optimization.SciMLBase
3+
using MathOptInterface, Optimization, Optimization.SciMLBase, Optimization.SparseArrays
44
const MOI = MathOptInterface
55

66
struct MOIOptimizationProblem{T,F<:OptimizationFunction,uType,P} <: MOI.AbstractNLPEvaluator
77
f::F
88
u0::uType
99
p::P
10-
J::Matrix{T}
11-
H::Matrix{T}
12-
cons_H::Vector{Matrix{T}}
10+
J::Union{Matrix{T},SparseMatrixCSC{T}}
11+
H::Union{Matrix{T},SparseMatrixCSC{T}}
12+
cons_H::Vector{<:Union{Matrix{T},SparseMatrixCSC{T}}}
1313
end
1414

1515
function MOIOptimizationProblem(prob::OptimizationProblem)
@@ -21,9 +21,9 @@ function MOIOptimizationProblem(prob::OptimizationProblem)
2121
f,
2222
prob.u0,
2323
prob.p,
24-
zeros(T, num_cons, n),
25-
zeros(T, n, n),
26-
Matrix{T}[zeros(T, n, n) for i in 1:num_cons],
24+
isnothing(f.cons_jac_prototype) ? zeros(T, num_cons, n) : convert.(T, f.cons_jac_prototype),
25+
isnothing(f.hess_prototype) ? zeros(T, n, n) : convert.(T, f.hess_prototype),
26+
isnothing(f.cons_hess_prototype) ? Matrix{T}[zeros(T, n, n) for i in 1:num_cons] : [convert.(T, f.cons_hess_prototype[i]) for i in 1:num_cons],
2727
)
2828
end
2929

lib/OptimizationMOI/test/runtests.jl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using OptimizationMOI, Optimization, Ipopt, NLopt, Zygote
1+
using OptimizationMOI, Optimization, Ipopt, NLopt, Zygote, ModelingToolkit
22
using Test
33

44
@testset "OptimizationMOI.jl" begin
@@ -30,4 +30,14 @@ using Test
3030

3131
sol = solve(prob, OptimizationMOI.MOI.OptimizerWithAttributes(NLopt.Optimizer, "algorithm" => :LD_LBFGS))
3232
@test 10 * sol.minimum < l1
33+
34+
cons_circ = (x, p) -> [x[1]^2 + x[2]^2]
35+
optprob = OptimizationFunction(rosenbrock, Optimization.AutoModelingToolkit(true, true); cons=cons_circ)
36+
prob = OptimizationProblem(optprob, x0, _p, ucons=[Inf], lcons=[0.0])
37+
38+
sol = solve(prob, Ipopt.Optimizer())
39+
@test 10 * sol.minimum < l1
40+
41+
sol = solve(prob, OptimizationMOI.MOI.OptimizerWithAttributes(Ipopt.Optimizer, "max_cpu_time" => 60.0))
42+
@test 10 * sol.minimum < l1
3343
end

lib/OptimizationOptimJL/src/OptimizationOptimJL.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module OptimizationOptimJL
22

3-
using Reexport, Optimization, Optimization.SciMLBase
3+
using Reexport, Optimization, Optimization.SciMLBase, Optimization.SparseArrays
44
@reexport using Optim
55
decompose_trace(trace::Optim.OptimizationTrace) = last(trace)
66
decompose_trace(trace::Optim.OptimizationState) = trace
@@ -133,7 +133,8 @@ function ___solve(prob::OptimizationProblem, opt::Optim.AbstractOptimizer,
133133
H .*= false
134134
end
135135
end
136-
optim_f = Optim.TwiceDifferentiable(_loss, gg, fg!, hh, prob.u0)
136+
F = eltype(prob.u0)
137+
optim_f = Optim.TwiceDifferentiable(_loss, gg, fg!, hh, prob.u0, real(zero(F)), Optim.NLSolversBase.alloc_DF(prob.u0, real(zero(F))), isnothing(f.hess_prototype) ? Optim.NLSolversBase.alloc_H(prob.u0, real(zero(F))) : convert.(F, f.hess_prototype))
137138
end
138139

139140
opt_args = __map_optimizer_args(prob, opt, callback=_cb, maxiters=maxiters, maxtime=maxtime, abstol=abstol, reltol=reltol; kwargs...)
@@ -285,7 +286,8 @@ function ___solve(prob::OptimizationProblem, opt::Optim.ConstrainedOptimizer,
285286
H .*= false
286287
end
287288
end
288-
optim_f = Optim.TwiceDifferentiable(_loss, gg, fg!, hh, prob.u0)
289+
F = eltype(prob.u0)
290+
optim_f = Optim.TwiceDifferentiable(_loss, gg, fg!, hh, prob.u0, real(zero(F)), Optim.NLSolversBase.alloc_DF(prob.u0, real(zero(F))), isnothing(f.hess_prototype) ? Optim.NLSolversBase.alloc_H(prob.u0, real(zero(F))) : convert.(F, f.hess_prototype))
289291

290292
cons! = (res, θ) -> res .= f.cons(θ)
291293

lib/OptimizationOptimJL/test/runtests.jl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ using Test
1818
@test 10 * sol.minimum < l1
1919

2020
prob = OptimizationProblem(rosenbrock, x0, _p)
21-
sol = solve(prob, Optim.NelderMead(;initial_simplex=Optim.AffineSimplexer(;a = 0.025, b = 0.5)))
21+
sol = solve(prob, Optim.NelderMead(; initial_simplex=Optim.AffineSimplexer(; a=0.025, b=0.5)))
2222
@test 10 * sol.minimum < l1
2323

2424
cons = (x, p) -> [x[1]^2 + x[2]^2]
@@ -96,4 +96,12 @@ using Test
9696
prob = OptimizationProblem(optprob, x0, _p)
9797
sol = solve(prob, Optim.BFGS())
9898
@test 10 * sol.minimum < l1
99+
100+
optprob = OptimizationFunction(rosenbrock, Optimization.AutoModelingToolkit(true, false))
101+
prob = OptimizationProblem(optprob, x0, _p)
102+
sol = solve(prob, Optim.Newton())
103+
@test 10 * sol.minimum < l1
104+
105+
sol = solve(prob, Optim.KrylovTrustRegion())
106+
@test 10 * sol.minimum < l1
99107
end

src/Optimization.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ using Reexport
99
using Requires
1010
using DiffResults
1111
using Logging, ProgressLogging, ConsoleProgressMonitor, TerminalLoggers, LoggingExtras
12-
using ArrayInterfaceCore, Base.Iterators
12+
using ArrayInterfaceCore, Base.Iterators, SparseArrays
1313
using Pkg
1414

1515
import SciMLBase: OptimizationProblem, OptimizationFunction, AbstractADType

src/function/function.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This function is used internally by Optimization.jl to construct
55
the necessary extra functions (gradients, Hessians, etc.) before
66
optimization. Each of the ADType dispatches use the supplied automatic
77
differentiation type in order to specify how the construction process
8-
occurs.
8+
occurs.
99
1010
If no ADType is given, then the default `NoAD` dispatch simply
1111
defines closures on any supplied gradient function to enclose the
@@ -31,7 +31,7 @@ function instantiate_function(f, x, ::AbstractADType, p, num_cons = 0)
3131
cons_j = f.cons_j === nothing ? nothing : (res,x)->f.cons_j(res,x,p)
3232
cons_h = f.cons_h === nothing ? nothing : (res,x)->f.cons_h(res,x,p)
3333

34-
return OptimizationFunction{true}(f.f, SciMLBase.NoAD(); grad=grad, hess=hess, hv=hv,
34+
return OptimizationFunction{true}(f.f, SciMLBase.NoAD(); grad=grad, hess=hess, hv=hv,
3535
cons=cons, cons_j=cons_j, cons_h=cons_h,
36-
hess_prototype=nothing, cons_jac_prototype=nothing, cons_hess_prototype=nothing)
36+
hess_prototype=f.hess_prototype, cons_jac_prototype=f.cons_jac_prototype, cons_hess_prototype=f.cons_hess_prototype)
3737
end

src/function/mtk.jl

Lines changed: 17 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,3 @@
1-
"""
2-
AutoModelingToolkit <: AbstractADType
3-
4-
An AbstractADType choice for use in OptimizationFunction for automatically
5-
generating the unspecified derivative functions. Usage:
6-
7-
```julia
8-
OptimizationFunction(f,AutoModelingToolkit();kwargs...)
9-
```
10-
11-
This uses the [ModelingToolkit.jl](https://github.com/SciML/ModelingToolkit.jl)
12-
symbolic system for automatically converting the `f` function into
13-
a symbolic equation and uses symbolic differentiation in order to generate
14-
a fast derivative code. Note that this will also compile a new version
15-
of your `f` function that is automatically optimized. Because of the
16-
required symbolic analysis, the state and parameters are required in
17-
the function definition, i.e.:
18-
19-
Summary:
20-
21-
- Not compatible with GPUs
22-
- Compatible with Hessian-based optimization
23-
- Not compatible with Hv-based optimization
24-
- Not compatible with constraint functions
25-
26-
## Constructor
27-
28-
```julia
29-
OptimizationFunction(f,AutoModelingToolkit(),x0,p,
30-
grad = false, hess = false, sparse = false,
31-
checkbounds = false,
32-
linenumbers = true,
33-
parallel=SerialForm(),
34-
kwargs...)
35-
```
36-
37-
The special keyword arguments are as follows:
38-
39-
- `grad`: whether to symbolically generate the gradient function.
40-
- `hess`: whether to symbolically generate the Hessian function.
41-
- `sparse`: whether to use sparsity detection in the Hessian.
42-
- `checkbounds`: whether to perform bounds checks in the generated code.
43-
- `linenumbers`: whether to include line numbers in the generated code.
44-
- `parallel`: whether to automatically parallelize the calculations.
45-
46-
For more information, see the [ModelingToolkit.jl `OptimizationSystem` documentation](https://mtk.sciml.ai/dev/systems/OptimizationSystem/)
47-
"""
481
struct AutoModelingToolkit <: AbstractADType
492
obj_sparse::Bool
503
cons_sparse::Bool
@@ -56,6 +9,8 @@ function instantiate_function(f, x, adtype::AutoModelingToolkit, p, num_cons=0)
569
p = isnothing(p) ? SciMLBase.NullParameters() : p
5710
sys = ModelingToolkit.modelingtoolkitize(OptimizationProblem(f, x, p))
5811

12+
hess_prototype, cons_jac_prototype, cons_hess_prototype = nothing, nothing, nothing
13+
5914
if f.grad === nothing
6015
grad_oop, grad_iip = ModelingToolkit.generate_gradient(sys, expression=Val{false})
6116
grad(J, u) = (grad_iip(J, u, p); J)
@@ -72,7 +27,7 @@ function instantiate_function(f, x, adtype::AutoModelingToolkit, p, num_cons=0)
7227

7328
if f.hv === nothing
7429
hv = function (H, θ, v, args...)
75-
res = ArrayInterfaceCore.zeromatrix(θ)
30+
res = adtype.obj_sparse ? hess_prototype : ArrayInterfaceCore.zeromatrix(θ)
7631
hess(res, θ, args...)
7732
H .= res * v
7833
end
@@ -105,7 +60,19 @@ function instantiate_function(f, x, adtype::AutoModelingToolkit, p, num_cons=0)
10560
cons_h = f.cons_h
10661
end
10762

108-
return OptimizationFunction{true}(f.f, adtype; grad=grad, hess=hess, hv=hv,
63+
if adtype.obj_sparse
64+
_hess_prototype = ModelingToolkit.hessian_sparsity(sys)
65+
hess_prototype = convert.(eltype(x), _hess_prototype)
66+
end
67+
68+
if adtype.cons_sparse
69+
_cons_jac_prototype = ModelingToolkit.jacobian_sparsity(cons_sys)
70+
cons_jac_prototype = convert.(eltype(x), _cons_jac_prototype)
71+
_cons_hess_prototype = ModelingToolkit.hessian_sparsity(cons_sys)
72+
cons_hess_prototype = [convert.(eltype(x), _cons_hess_prototype[i]) for i in 1:num_cons]
73+
end
74+
75+
return OptimizationFunction{true}(f.f, adtype; grad=grad, hess=hess, hv=hv,
10976
cons=cons, cons_j=cons_j, cons_h=cons_h,
110-
hess_prototype=nothing, cons_jac_prototype=nothing, cons_hess_prototype=nothing)
77+
hess_prototype=hess_prototype, cons_jac_prototype=cons_jac_prototype, cons_hess_prototype=cons_hess_prototype)
11178
end

test/ADtests.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Optimization, OptimizationOptimJL, OptimizationOptimisers, Test
22
using ForwardDiff, Zygote, ReverseDiff, FiniteDiff, Tracker
33
using ModelingToolkit
4+
45
x0 = zeros(2)
56
rosenbrock(x, p=nothing) = (1 - x[1])^2 + 100 * (x[2] - x[1]^2)^2
67
l1 = rosenbrock(x0)
@@ -61,13 +62,16 @@ optf = OptimizationFunction(rosenbrock, Optimization.AutoModelingToolkit(true, t
6162
optprob = Optimization.instantiate_function(optf, x0, Optimization.AutoModelingToolkit(true, true), nothing, 2)
6263
using SparseArrays
6364
sH = sparse([1, 1, 2, 2], [1, 2, 1, 2], zeros(4))
65+
@test findnz(sH)[1:2] == findnz(optprob.hess_prototype)[1:2]
6466
optprob.hess(sH, x0)
6567
@test sH == H2
6668
@test optprob.cons(x0) == [0.0, 0.0]
6769
sJ = sparse([1, 1, 2, 2], [1, 2, 1, 2], zeros(4))
70+
@test findnz(sJ)[1:2] == findnz(optprob.cons_jac_prototype)[1:2]
6871
optprob.cons_j(sJ, [5.0, 3.0])
6972
@test all(isapprox(sJ, [10.0 6.0; -0.149013 -0.958924]; rtol=1e-3))
70-
sH3 = [sparse([1,2], [1, 2], zeros(2)), sparse([1, 1, 2], [1, 2, 1], zeros(3))]
73+
sH3 = [sparse([1, 2], [1, 2], zeros(2)), sparse([1, 1, 2], [1, 2, 1], zeros(3))]
74+
@test getindex.(findnz.(sH3), Ref([1,2])) == getindex.(findnz.(optprob.cons_hess_prototype), Ref([1,2]))
7175
optprob.cons_h(sH3, x0)
7276
@test Array.(sH3) == [[2.0 0.0; 0.0 2.0], [-0.0 1.0; 1.0 0.0]]
7377

0 commit comments

Comments
 (0)