Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make MathOptInterface.jl a weak dependency #1081

Merged
merged 14 commits into from
Mar 21, 2024
12 changes: 10 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,21 @@ FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
LineSearches = "d3d80556-e9d4-5f37-9878-2ab0fcc64255"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
NLSolversBase = "d41bc354-129a-5804-8e4c-c37616107c6c"
NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
PackageExtensionCompat = "65ce6f38-6b18-4e1d-a461-8949797d7930"
Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a"
PositiveFactorizations = "85a6dd25-e78a-55b7-8502-1745935b8125"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"

[weakdeps]
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"

[extensions]
OptimMOIExt = "MathOptInterface"

[compat]
Compat = "3.2.0, 3.3.0, 3.4.0, 3.5.0, 3.6.0, 4"
FillArrays = "0.6.2, 0.7, 0.8, 0.9, 0.10, 0.11, 0.12, 0.13, 1"
Expand All @@ -27,6 +33,7 @@ MathOptInterface = "1.17"
NLSolversBase = "7.8.0"
NaNMath = "0.3.2, 1"
OptimTestProblems = "2.0.3"
PackageExtensionCompat = "1"
Parameters = "0.10, 0.11, 0.12"
PositiveFactorizations = "0.2.2"
Printf = "<0.0.1, 1.6"
Expand All @@ -39,6 +46,7 @@ julia = "1.6"
[extras]
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
LineSearches = "d3d80556-e9d4-5f37-9878-2ab0fcc64255"
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7"
NLSolversBase = "d41bc354-129a-5804-8e4c-c37616107c6c"
OptimTestProblems = "cec144fc-5a64-5bc6-99fb-dde8f63e154c"
Expand All @@ -49,4 +57,4 @@ StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test", "Distributions", "Measurements", "OptimTestProblems", "Random", "RecursiveArrayTools", "StableRNGs", "LineSearches", "NLSolversBase", "PositiveFactorizations"]
test = ["Test", "Distributions", "MathOptInterface", "Measurements", "OptimTestProblems", "Random", "RecursiveArrayTools", "StableRNGs", "LineSearches", "NLSolversBase", "PositiveFactorizations"]
101 changes: 75 additions & 26 deletions src/MOI_wrapper.jl → ext/OptimMOIExt.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
module OptimMOIExt

using Optim
using LinearAlgebra
import MathOptInterface as MOI

function __init__()
@static if VERSION >= v"1.9"
@eval Optim begin
OptimMOIExt = Base.get_extension(@__MODULE__, :OptimMOIExt)
const Optimizer = OptimMOIExt.Optimizer
end
# setglobal!(Optim, :Optimizer, Optimizer)
else
@eval Optim begin
using .OptimMOIExt
const Optimizer = OptimMOIExt.Optimizer
end
end
end

mutable struct Optimizer{T} <: MOI.AbstractOptimizer
# Problem data.
variables::MOI.Utilities.VariablesContainer{T}
Expand All @@ -8,12 +27,12 @@ mutable struct Optimizer{T} <: MOI.AbstractOptimizer
sense::MOI.OptimizationSense

# Parameters.
method::Union{AbstractOptimizer,Nothing}
method::Union{Optim.AbstractOptimizer,Nothing}
silent::Bool
options::Dict{Symbol,Any}

# Solution attributes.
results::Union{Nothing,MultivariateOptimizationResults}
results::Union{Nothing,Optim.MultivariateOptimizationResults}
end

function Optimizer{T}() where {T}
Expand All @@ -37,7 +56,7 @@ function MOI.supports(::Optimizer, ::Union{MOI.ObjectiveSense,MOI.ObjectiveFunct
end
MOI.supports(::Optimizer, ::MOI.Silent) = true
function MOI.supports(::Optimizer, p::MOI.RawOptimizerAttribute)
return p.name == "method" || hasfield(Options, Symbol(p.name))
return p.name == "method" || hasfield(Optim.Options, Symbol(p.name))
end

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

MOI.Utilities.map_indices(::Function, opt::AbstractOptimizer) = opt
MOI.Utilities.map_indices(::Function, opt::Optim.AbstractOptimizer) = opt

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

function MOI.add_constraint(model::Optimizer{T}, vi::MOI.VariableIndex, set::BOUNDS{T}) where {T}
function MOI.add_constraint(
model::Optimizer{T},
vi::MOI.VariableIndex,
set::BOUNDS{T},
) where {T}
return MOI.add_constraint(model.variables, vi, set)
end

Expand Down Expand Up @@ -187,17 +210,17 @@ function MOI.set(
return
end

function requested_features(::ZerothOrderOptimizer, has_constraints)
function requested_features(::Optim.ZerothOrderOptimizer, has_constraints)
return Symbol[]
end
function requested_features(::FirstOrderOptimizer, has_constraints)
function requested_features(::Optim.FirstOrderOptimizer, has_constraints)
features = [:Grad]
if has_constraints
push!(features, :Jac)
end
return features
end
function requested_features(::Union{IPNewton,SecondOrderOptimizer}, has_constraints)
function requested_features(::Union{IPNewton,Optim.SecondOrderOptimizer}, has_constraints)
features = [:Grad, :Hess]
if has_constraints
push!(features, :Jac)
Expand Down Expand Up @@ -255,7 +278,12 @@ function MOI.optimize!(model::Optimizer{T}) where {T}
method = model.method
nl_constrained = !isempty(nlp_data.constraint_bounds)
features = MOI.features_available(evaluator)
has_bounds = any(vi -> isfinite(model.variables.lower[vi.value]) || isfinite(model.variables.upper[vi.value]), vars)
has_bounds = any(
vi ->
isfinite(model.variables.lower[vi.value]) ||
isfinite(model.variables.upper[vi.value]),
vars,
)
if method === nothing
if nl_constrained
method = IPNewton()
Expand All @@ -264,12 +292,12 @@ function MOI.optimize!(model::Optimizer{T}) where {T}
# are variable bounds, `Newton` is not supported. On the other hand,
# `fallback_method(f, g!)` returns `LBFGS` which is supported if `has_bounds`.
if :Hess in features && !has_bounds
method = fallback_method(f, g!, h!)
method = Optim.fallback_method(f, g!, h!)
else
method = fallback_method(f, g!)
method = Optim.fallback_method(f, g!)
end
else
method = fallback_method(f)
method = Optim.fallback_method(f)
end
end
used_features = requested_features(method, nl_constrained)
Expand All @@ -283,22 +311,35 @@ function MOI.optimize!(model::Optimizer{T}) where {T}
initial_x = starting_value.(model, eachindex(model.starting_values))
options = copy(model.options)
if !nl_constrained && has_bounds && !(method isa IPNewton)
options = Options(; options...)
model.results = optimize(f, g!, model.variables.lower, model.variables.upper, initial_x, Fminbox(method), options; inplace = true)
options = Optim.Options(; options...)
model.results = optimize(
f,
g!,
model.variables.lower,
model.variables.upper,
initial_x,
Fminbox(method),
options;
inplace = true,
)
else
d = promote_objtype(method, initial_x, :finite, true, f, g!, h!)
add_default_opts!(options, method)
options = Options(; options...)
d = Optim.promote_objtype(method, initial_x, :finite, true, f, g!, h!)
Optim.add_default_opts!(options, method)
options = Optim.Options(; options...)
if nl_constrained || has_bounds
if nl_constrained
lc = [b.lower for b in nlp_data.constraint_bounds]
uc = [b.upper for b in nlp_data.constraint_bounds]
c!(c, x) = MOI.eval_constraint(evaluator, c, x)
if !(:Jac in features)
error("Nonlinear constraints should be differentiable to be used with Optim.")
error(
"Nonlinear constraints should be differentiable to be used with Optim.",
)
end
if !(:Hess in features)
error("Nonlinear constraints should be twice differentiable to be used with Optim.")
error(
"Nonlinear constraints should be twice differentiable to be used with Optim.",
)
end
jacobian_structure = MOI.jacobian_structure(evaluator)
J_nzval = zeros(T, length(jacobian_structure))
Expand All @@ -315,13 +356,20 @@ function MOI.optimize!(model::Optimizer{T}) where {T}
return H
end
c = TwiceDifferentiableConstraints(
c!, jacobian!, con_hessian!,
model.variables.lower, model.variables.upper, lc, uc,
c!,
jacobian!,
con_hessian!,
model.variables.lower,
model.variables.upper,
lc,
uc,
)
else
@assert has_bounds
c = TwiceDifferentiableConstraints(
model.variables.lower, model.variables.upper)
model.variables.lower,
model.variables.upper,
)
end
model.results = optimize(d, c, initial_x, method, options)
else
Expand All @@ -334,7 +382,7 @@ end
function MOI.get(model::Optimizer, ::MOI.TerminationStatus)
if model.results === nothing
return MOI.OPTIMIZE_NOT_CALLED
elseif converged(model.results)
elseif Optim.converged(model.results)
return MOI.LOCALLY_SOLVED
else
return MOI.OTHER_ERROR
Expand All @@ -354,7 +402,7 @@ function MOI.get(model::Optimizer, attr::MOI.PrimalStatus)
if !(1 <= attr.result_index <= MOI.get(model, MOI.ResultCount()))
return MOI.NO_SOLUTION
end
if converged(model.results)
if Optim.converged(model.results)
return MOI.FEASIBLE_POINT
else
return MOI.UNKNOWN_RESULT_STATUS
Expand All @@ -374,7 +422,7 @@ end
function MOI.get(model::Optimizer, attr::MOI.VariablePrimal, vi::MOI.VariableIndex)
MOI.check_result_index_bounds(model, attr)
MOI.throw_if_not_valid(model, vi)
return minimizer(model.results)[vi.value]
return Optim.minimizer(model.results)[vi.value]
end

function MOI.get(
Expand All @@ -384,5 +432,6 @@ function MOI.get(
) where {T}
MOI.check_result_index_bounds(model, attr)
MOI.throw_if_not_valid(model, ci)
return minimizer(model.results)[ci.value]
return Optim.minimizer(model.results)[ci.value]
end
end # module
6 changes: 4 additions & 2 deletions src/Optim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ using Printf # For printing, maybe look into other options
using FillArrays # For handling scalar bounds in Fminbox

#using Compat # for compatibility across multiple julia versions
using PackageExtensionCompat # For retrocompatibility on package extensions

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

# MathOptInterface wrapper
include("MOI_wrapper.jl")
function __init__()
@require_extensions
end

end
Loading