Skip to content

Commit 9c65c72

Browse files
authored
Make MathOptInterface.jl a weak dependency (#1081)
* Create weak extension * Adapt tests * Add Requires * Add missing import * Revert formatting * fix compilation extension * fix references and pass MOIWrapper tests locally * Use PackageExtensionCompat instead * remove Requires * Solve issue with struct assignment * readapt tests * Use more common solution for any julia version * Add MOI to test
1 parent 78ab1f4 commit 9c65c72

File tree

3 files changed

+89
-30
lines changed

3 files changed

+89
-30
lines changed

Project.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,21 @@ FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
88
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
99
LineSearches = "d3d80556-e9d4-5f37-9878-2ab0fcc64255"
1010
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
11-
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
1211
NLSolversBase = "d41bc354-129a-5804-8e4c-c37616107c6c"
1312
NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
13+
PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930"
1414
Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a"
1515
PositiveFactorizations = "85a6dd25-e78a-55b7-8502-1745935b8125"
1616
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
1717
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
1818
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
1919

20+
[weakdeps]
21+
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
22+
23+
[extensions]
24+
OptimMOIExt = "MathOptInterface"
25+
2026
[compat]
2127
Compat = "3.2.0, 3.3.0, 3.4.0, 3.5.0, 3.6.0, 4"
2228
FillArrays = "0.6.2, 0.7, 0.8, 0.9, 0.10, 0.11, 0.12, 0.13, 1"
@@ -27,6 +33,7 @@ MathOptInterface = "1.17"
2733
NLSolversBase = "7.8.0"
2834
NaNMath = "0.3.2, 1"
2935
OptimTestProblems = "2.0.3"
36+
PackageExtensionCompat = "1"
3037
Parameters = "0.10, 0.11, 0.12"
3138
PositiveFactorizations = "0.2.2"
3239
Printf = "<0.0.1, 1.6"
@@ -39,6 +46,7 @@ julia = "1.6"
3946
[extras]
4047
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
4148
LineSearches = "d3d80556-e9d4-5f37-9878-2ab0fcc64255"
49+
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
4250
Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7"
4351
NLSolversBase = "d41bc354-129a-5804-8e4c-c37616107c6c"
4452
OptimTestProblems = "cec144fc-5a64-5bc6-99fb-dde8f63e154c"
@@ -49,4 +57,4 @@ StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
4957
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
5058

5159
[targets]
52-
test = ["Test", "Distributions", "Measurements", "OptimTestProblems", "Random", "RecursiveArrayTools", "StableRNGs", "LineSearches", "NLSolversBase", "PositiveFactorizations"]
60+
test = ["Test", "Distributions", "MathOptInterface", "Measurements", "OptimTestProblems", "Random", "RecursiveArrayTools", "StableRNGs", "LineSearches", "NLSolversBase", "PositiveFactorizations"]

src/MOI_wrapper.jl renamed to ext/OptimMOIExt.jl

Lines changed: 75 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
1+
module OptimMOIExt
2+
3+
using Optim
4+
using LinearAlgebra
15
import MathOptInterface as MOI
26

7+
function __init__()
8+
@static if VERSION >= v"1.9"
9+
@eval Optim begin
10+
OptimMOIExt = Base.get_extension(@__MODULE__, :OptimMOIExt)
11+
const Optimizer = OptimMOIExt.Optimizer
12+
end
13+
# setglobal!(Optim, :Optimizer, Optimizer)
14+
else
15+
@eval Optim begin
16+
using .OptimMOIExt
17+
const Optimizer = OptimMOIExt.Optimizer
18+
end
19+
end
20+
end
21+
322
mutable struct Optimizer{T} <: MOI.AbstractOptimizer
423
# Problem data.
524
variables::MOI.Utilities.VariablesContainer{T}
@@ -8,12 +27,12 @@ mutable struct Optimizer{T} <: MOI.AbstractOptimizer
827
sense::MOI.OptimizationSense
928

1029
# Parameters.
11-
method::Union{AbstractOptimizer,Nothing}
30+
method::Union{Optim.AbstractOptimizer,Nothing}
1231
silent::Bool
1332
options::Dict{Symbol,Any}
1433

1534
# Solution attributes.
16-
results::Union{Nothing,MultivariateOptimizationResults}
35+
results::Union{Nothing,Optim.MultivariateOptimizationResults}
1736
end
1837

1938
function Optimizer{T}() where {T}
@@ -37,7 +56,7 @@ function MOI.supports(::Optimizer, ::Union{MOI.ObjectiveSense,MOI.ObjectiveFunct
3756
end
3857
MOI.supports(::Optimizer, ::MOI.Silent) = true
3958
function MOI.supports(::Optimizer, p::MOI.RawOptimizerAttribute)
40-
return p.name == "method" || hasfield(Options, Symbol(p.name))
59+
return p.name == "method" || hasfield(Optim.Options, Symbol(p.name))
4160
end
4261

4362
function MOI.supports(::Optimizer, ::MOI.VariablePrimalStart, ::Type{MOI.VariableIndex})
@@ -103,7 +122,7 @@ function MOI.get(model::Optimizer, ::MOI.TimeLimitSec)
103122
return get(model.options, Symbol(TIME_LIMIT), nothing)
104123
end
105124

106-
MOI.Utilities.map_indices(::Function, opt::AbstractOptimizer) = opt
125+
MOI.Utilities.map_indices(::Function, opt::Optim.AbstractOptimizer) = opt
107126

108127
function MOI.set(model::Optimizer, p::MOI.RawOptimizerAttribute, value)
109128
if p.name == "method"
@@ -151,7 +170,11 @@ function MOI.is_valid(model::Optimizer, index::Union{MOI.VariableIndex,MOI.Const
151170
return MOI.is_valid(model.variables, index)
152171
end
153172

154-
function MOI.add_constraint(model::Optimizer{T}, vi::MOI.VariableIndex, set::BOUNDS{T}) where {T}
173+
function MOI.add_constraint(
174+
model::Optimizer{T},
175+
vi::MOI.VariableIndex,
176+
set::BOUNDS{T},
177+
) where {T}
155178
return MOI.add_constraint(model.variables, vi, set)
156179
end
157180

@@ -187,17 +210,17 @@ function MOI.set(
187210
return
188211
end
189212

190-
function requested_features(::ZerothOrderOptimizer, has_constraints)
213+
function requested_features(::Optim.ZerothOrderOptimizer, has_constraints)
191214
return Symbol[]
192215
end
193-
function requested_features(::FirstOrderOptimizer, has_constraints)
216+
function requested_features(::Optim.FirstOrderOptimizer, has_constraints)
194217
features = [:Grad]
195218
if has_constraints
196219
push!(features, :Jac)
197220
end
198221
return features
199222
end
200-
function requested_features(::Union{IPNewton,SecondOrderOptimizer}, has_constraints)
223+
function requested_features(::Union{IPNewton,Optim.SecondOrderOptimizer}, has_constraints)
201224
features = [:Grad, :Hess]
202225
if has_constraints
203226
push!(features, :Jac)
@@ -255,7 +278,12 @@ function MOI.optimize!(model::Optimizer{T}) where {T}
255278
method = model.method
256279
nl_constrained = !isempty(nlp_data.constraint_bounds)
257280
features = MOI.features_available(evaluator)
258-
has_bounds = any(vi -> isfinite(model.variables.lower[vi.value]) || isfinite(model.variables.upper[vi.value]), vars)
281+
has_bounds = any(
282+
vi ->
283+
isfinite(model.variables.lower[vi.value]) ||
284+
isfinite(model.variables.upper[vi.value]),
285+
vars,
286+
)
259287
if method === nothing
260288
if nl_constrained
261289
method = IPNewton()
@@ -264,12 +292,12 @@ function MOI.optimize!(model::Optimizer{T}) where {T}
264292
# are variable bounds, `Newton` is not supported. On the other hand,
265293
# `fallback_method(f, g!)` returns `LBFGS` which is supported if `has_bounds`.
266294
if :Hess in features && !has_bounds
267-
method = fallback_method(f, g!, h!)
295+
method = Optim.fallback_method(f, g!, h!)
268296
else
269-
method = fallback_method(f, g!)
297+
method = Optim.fallback_method(f, g!)
270298
end
271299
else
272-
method = fallback_method(f)
300+
method = Optim.fallback_method(f)
273301
end
274302
end
275303
used_features = requested_features(method, nl_constrained)
@@ -283,22 +311,35 @@ function MOI.optimize!(model::Optimizer{T}) where {T}
283311
initial_x = starting_value.(model, eachindex(model.starting_values))
284312
options = copy(model.options)
285313
if !nl_constrained && has_bounds && !(method isa IPNewton)
286-
options = Options(; options...)
287-
model.results = optimize(f, g!, model.variables.lower, model.variables.upper, initial_x, Fminbox(method), options; inplace = true)
314+
options = Optim.Options(; options...)
315+
model.results = optimize(
316+
f,
317+
g!,
318+
model.variables.lower,
319+
model.variables.upper,
320+
initial_x,
321+
Fminbox(method),
322+
options;
323+
inplace = true,
324+
)
288325
else
289-
d = promote_objtype(method, initial_x, :finite, true, f, g!, h!)
290-
add_default_opts!(options, method)
291-
options = Options(; options...)
326+
d = Optim.promote_objtype(method, initial_x, :finite, true, f, g!, h!)
327+
Optim.add_default_opts!(options, method)
328+
options = Optim.Options(; options...)
292329
if nl_constrained || has_bounds
293330
if nl_constrained
294331
lc = [b.lower for b in nlp_data.constraint_bounds]
295332
uc = [b.upper for b in nlp_data.constraint_bounds]
296333
c!(c, x) = MOI.eval_constraint(evaluator, c, x)
297334
if !(:Jac in features)
298-
error("Nonlinear constraints should be differentiable to be used with Optim.")
335+
error(
336+
"Nonlinear constraints should be differentiable to be used with Optim.",
337+
)
299338
end
300339
if !(:Hess in features)
301-
error("Nonlinear constraints should be twice differentiable to be used with Optim.")
340+
error(
341+
"Nonlinear constraints should be twice differentiable to be used with Optim.",
342+
)
302343
end
303344
jacobian_structure = MOI.jacobian_structure(evaluator)
304345
J_nzval = zeros(T, length(jacobian_structure))
@@ -315,13 +356,20 @@ function MOI.optimize!(model::Optimizer{T}) where {T}
315356
return H
316357
end
317358
c = TwiceDifferentiableConstraints(
318-
c!, jacobian!, con_hessian!,
319-
model.variables.lower, model.variables.upper, lc, uc,
359+
c!,
360+
jacobian!,
361+
con_hessian!,
362+
model.variables.lower,
363+
model.variables.upper,
364+
lc,
365+
uc,
320366
)
321367
else
322368
@assert has_bounds
323369
c = TwiceDifferentiableConstraints(
324-
model.variables.lower, model.variables.upper)
370+
model.variables.lower,
371+
model.variables.upper,
372+
)
325373
end
326374
model.results = optimize(d, c, initial_x, method, options)
327375
else
@@ -334,7 +382,7 @@ end
334382
function MOI.get(model::Optimizer, ::MOI.TerminationStatus)
335383
if model.results === nothing
336384
return MOI.OPTIMIZE_NOT_CALLED
337-
elseif converged(model.results)
385+
elseif Optim.converged(model.results)
338386
return MOI.LOCALLY_SOLVED
339387
else
340388
return MOI.OTHER_ERROR
@@ -354,7 +402,7 @@ function MOI.get(model::Optimizer, attr::MOI.PrimalStatus)
354402
if !(1 <= attr.result_index <= MOI.get(model, MOI.ResultCount()))
355403
return MOI.NO_SOLUTION
356404
end
357-
if converged(model.results)
405+
if Optim.converged(model.results)
358406
return MOI.FEASIBLE_POINT
359407
else
360408
return MOI.UNKNOWN_RESULT_STATUS
@@ -374,7 +422,7 @@ end
374422
function MOI.get(model::Optimizer, attr::MOI.VariablePrimal, vi::MOI.VariableIndex)
375423
MOI.check_result_index_bounds(model, attr)
376424
MOI.throw_if_not_valid(model, vi)
377-
return minimizer(model.results)[vi.value]
425+
return Optim.minimizer(model.results)[vi.value]
378426
end
379427

380428
function MOI.get(
@@ -384,5 +432,6 @@ function MOI.get(
384432
) where {T}
385433
MOI.check_result_index_bounds(model, attr)
386434
MOI.throw_if_not_valid(model, ci)
387-
return minimizer(model.results)[ci.value]
435+
return Optim.minimizer(model.results)[ci.value]
388436
end
437+
end # module

src/Optim.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ using Printf # For printing, maybe look into other options
3434
using FillArrays # For handling scalar bounds in Fminbox
3535

3636
#using Compat # for compatibility across multiple julia versions
37+
using PackageExtensionCompat # For retrocompatibility on package extensions
3738

3839
# for extensions of functions defined in Base.
3940
import Base: length, push!, show, getindex, setindex!, maximum, minimum
@@ -220,7 +221,8 @@ include("multivariate/solvers/constrained/ipnewton/utilities/trace.jl")
220221
# Maximization convenience wrapper
221222
include("maximize.jl")
222223

223-
# MathOptInterface wrapper
224-
include("MOI_wrapper.jl")
224+
function __init__()
225+
@require_extensions
226+
end
225227

226228
end

0 commit comments

Comments
 (0)