Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Use `MathOptComplements.jl` with JuMP as follows:
using JuMP
import Ipopt
import MathOptComplements
model = Model(() -> MathOptComplements.Optimizer(Ipopt.Optimizer()))
model = Model(() -> MathOptComplements.Optimizer(Ipopt.Optimizer))
set_attribute(
model,
MathOptComplements.DefaultComplementarityReformulation(),
Expand Down
32 changes: 29 additions & 3 deletions src/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,29 @@
# Use of this source code is governed by an MIT-style license that can be found
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.

"""
Optimizer(
factory;
coefficient_type::Type{T} = Float64,
kwargs...,
) where {T}

Create a new optimizer, where the inner optimizer is created by
`MOI.instantiate(factory; kwargs...)`.

## Examples

```julia
julia> using MathOptComplements, Ipopt

julia> model = MathOptComplements.Optimizer(Ipopt.Optimizer);

julia> model = MathOptComplements.Optimizer(
Ipopt.Optimizer;
with_cache_type = Float64,
);
```
"""
mutable struct Optimizer{T,O<:MOI.ModelLike} <:
MOI.Bridges.AbstractBridgeOptimizer
model::O # This need to be called `model` by convention of `AbstractBridgeOptimizer`
Expand All @@ -11,7 +34,12 @@ mutable struct Optimizer{T,O<:MOI.ModelLike} <:
con_to_name::Dict{MOI.ConstraintIndex,String}
name_to_con::Union{Dict{String,MOI.ConstraintIndex},Nothing}

function Optimizer{T}(model::MOI.ModelLike) where {T}
function Optimizer(
factory::Any;
coefficient_type::Type{T} = Float64,
kwargs...,
) where {T}
model = MOI.instantiate(factory; kwargs...)
return new{T,typeof(model)}(
model,
ScholtesRelaxation(zero(T)),
Expand All @@ -22,8 +50,6 @@ mutable struct Optimizer{T,O<:MOI.ModelLike} <:
end
end

Optimizer(model::MOI.ModelLike) = Optimizer{Float64}(model)

MOI.Bridges.Constraint.bridges(model::Optimizer) = model.constraint_map

"""
Expand Down
2 changes: 1 addition & 1 deletion test/instances.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function runtests(make_opt)
is_test(s) = !startswith("$s", "#") && endswith("$s", "_model")
@testset "$name" for name in filter(is_test, names(@__MODULE__; all = true))
model = getfield(@__MODULE__, name)()
set_optimizer(model, () -> make_opt(Ipopt.Optimizer()))
set_optimizer(model, () -> make_opt(Ipopt.Optimizer))
set_attribute(model, "bound_relax_factor", 0.0)
set_attribute(model, "mu_strategy", "adaptive")
set_attribute(model, "bound_push", 1e-1)
Expand Down
104 changes: 56 additions & 48 deletions test/test_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ function test_per_constraint_reformulation()
set_optimizer(
model,
() -> MathOptComplements.Optimizer(
MOI.instantiate(Ipopt.Optimizer; with_cache_type = Float64),
Ipopt.Optimizer;
with_cache_type = Float64,
);
with_cache_type = Float64,
)
Expand All @@ -46,10 +47,9 @@ function test_per_constraint_reformulation()
MathOptComplements.ScholtesRelaxation(0.0),
)
# Override c1 with FischerBurmeister
MOI.set(
model,
MathOptComplements.ComplementarityReformulation(),
set_attribute(
c1,
MathOptComplements.ComplementarityReformulation(),
MathOptComplements.FischerBurmeisterRelaxation(1e-8),
)
@test MOI.supports(
Expand Down Expand Up @@ -82,10 +82,9 @@ function test_per_constraint_reformulation()
MathOptComplements.ComplementarityReformulation(),
MOI.ConstraintIndex{MOI.VectorOfVariables,MOI.Complements},
)
@test MOI.get(
model,
MathOptComplements.ComplementarityReformulation(),
@test get_attribute(
c1,
MathOptComplements.ComplementarityReformulation(),
) isa MathOptComplements.FischerBurmeisterRelaxation
set_attribute(model, "bound_relax_factor", 0.0)
set_silent(model)
Expand Down Expand Up @@ -125,7 +124,8 @@ function test_per_constraint_reformulation_after_optimize!()
set_optimizer(
model,
() -> MathOptComplements.Optimizer(
MOI.instantiate(Ipopt.Optimizer; with_cache_type = Float64),
Ipopt.Optimizer;
with_cache_type = Float64,
);
with_cache_type = Float64,
)
Expand All @@ -139,7 +139,7 @@ function test_per_constraint_reformulation_after_optimize!()
optimize!(model)
@test is_solved_and_feasible(model)
# Scholtes produces a quadratic constraint
inner = backend(model).optimizer.model.optimizer.model
inner = unsafe_backend(unsafe_backend(model).model)
@test MOI.get(
inner,
MOI.NumberOfConstraints{
Expand All @@ -148,20 +148,18 @@ function test_per_constraint_reformulation_after_optimize!()
}(),
) == 1
# Change reformulation after first optimize! (bridge.constraints is populated)
MOI.set(
model,
MathOptComplements.ComplementarityReformulation(),
set_attribute(
c,
MathOptComplements.ComplementarityReformulation(),
MathOptComplements.FischerBurmeisterRelaxation(1e-8),
)
@test MOI.get(
model,
MathOptComplements.ComplementarityReformulation(),
@test get_attribute(
c,
MathOptComplements.ComplementarityReformulation(),
) isa MathOptComplements.FischerBurmeisterRelaxation
optimize!(model)
@test is_solved_and_feasible(model)
inner = backend(model).optimizer.model.optimizer.model
inner = unsafe_backend(unsafe_backend(model).model)
# FB produces a nonlinear constraint
@test MOI.get(
inner,
Expand All @@ -182,7 +180,8 @@ end
function test_Optimizer_bridge_dispatch()
# NLP path: VectorAffineFunction in ComplementsWithSetType → NonlinearBridge
opt_nlp = MathOptComplements.Optimizer(
MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()),
() ->
MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()),
)
S = MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}
@test MOI.Bridges.is_bridged(opt_nlp, S)
Expand All @@ -199,7 +198,8 @@ function test_Optimizer_bridge_dispatch()
) == MathOptComplements.Bridges.NonlinearBridge{Float64,MOI.Nonnegatives}
# SOS1 path: VectorOfVariables in ComplementsWithSetType{Zeros} → ToSOS1Bridge
opt_sos1 = MathOptComplements.Optimizer(
MOI.Bridges.full_bridge_optimizer(HiGHS.Optimizer(), Float64),
HiGHS.Optimizer;
with_bridge_type = Float64,
)
S_zeros = MathOptComplements.ComplementsWithSetType{MOI.Zeros}
@test MOI.Bridges.bridge_type(opt_sos1, MOI.VectorOfVariables, S_zeros) ==
Expand Down Expand Up @@ -354,17 +354,19 @@ function test_per_constraint_reformulation_with_VerticalBridge()
set_optimizer(
model,
() -> MathOptComplements.Optimizer(
MOI.instantiate(Ipopt.Optimizer; with_cache_type = Float64),
),
Ipopt.Optimizer;
with_cache_type = Float64,
);
with_cache_type = Float64,
)
attr = MathOptComplements.ComplementarityReformulation()
reformulation = MathOptComplements.FischerBurmeisterRelaxation(1e-8)
MOI.set(model, attr, c, reformulation)
set_attribute(c, attr, reformulation)
set_attribute(model, "bound_relax_factor", 0.0)
set_silent(model)
optimize!(model)
@test MOI.supports(backend(model), attr, typeof(index(c)))
@test MOI.get(model, attr, c) == reformulation
@test get_attribute(c, attr) == reformulation
@test is_solved_and_feasible(model)
return
end
Expand All @@ -374,7 +376,8 @@ function test_bridge_chain()
# HiGHS does not support ScalarNonlinearFunction, so the Optimizer
# uses the SOS1 path instead of NonlinearBridge.
opt = MathOptComplements.Optimizer(
MOI.Bridges.full_bridge_optimizer(HiGHS.Optimizer(), Float64),
HiGHS.Optimizer;
with_bridge_type = Float64,
)
# Create a model with an Interval complementarity constraint
x = MOI.add_variable(opt)
Expand Down Expand Up @@ -424,7 +427,7 @@ end
function test_opt_add_all_bridges()
is_test(f) = startswith("$f", "_test_opt_")
function make_opt(inner)
lazy = MOI.Bridges.full_bridge_optimizer(inner, Float64)
lazy = MOI.instantiate(inner; with_bridge_type = Float64)
MathOptComplements.Bridges.add_all_bridges(lazy)
return lazy
end
Expand All @@ -439,9 +442,9 @@ function _test_opt_lower_bound_nonnegatives(make_opt)
@variable(model, x)
@variable(model, 0.0 <= y)
@constraint(model, [x, y] ∈ MOI.Complements(2))
inner = MOI.Utilities.Model{Float64}()
set_optimizer(model, () -> make_opt(inner))
set_optimizer(model, () -> make_opt(MOI.Utilities.Model{Float64}))
MOI.Utilities.attach_optimizer(model)
inner = unsafe_backend(model)
# ComplementsWithSetType is bridged further to nonlinear constraints
S = MathOptComplements.ComplementsWithSetType{MOI.Nonnegatives}
@test MOI.get(inner, MOI.NumberOfConstraints{MOI.VectorOfVariables,S}()) ==
Expand All @@ -454,9 +457,9 @@ function _test_opt_lower_bound_greater_than(make_opt)
@variable(model, x)
@variable(model, 3.0 <= y)
@constraint(model, [x, y] ∈ MOI.Complements(2))
inner = MOI.Utilities.Model{Float64}()
set_optimizer(model, () -> make_opt(inner))
set_optimizer(model, () -> make_opt(MOI.Utilities.Model{Float64}))
MOI.Utilities.attach_optimizer(model)
inner = unsafe_backend(model)
S = MathOptComplements.ComplementsWithSetType{MOI.GreaterThan{Float64}}
@test MOI.get(inner, MOI.NumberOfConstraints{MOI.VectorOfVariables,S}()) ==
0
Expand All @@ -468,9 +471,9 @@ function _test_opt_upper_bound_less_than(make_opt)
@variable(model, x)
@variable(model, y <= 1.0)
@constraint(model, [x, y] ∈ MOI.Complements(2))
inner = MOI.Utilities.Model{Float64}()
set_optimizer(model, () -> make_opt(inner))
set_optimizer(model, () -> make_opt(MOI.Utilities.Model{Float64}))
MOI.Utilities.attach_optimizer(model)
inner = unsafe_backend(model)
S = MathOptComplements.ComplementsWithSetType{MOI.LessThan{Float64}}
@test MOI.get(inner, MOI.NumberOfConstraints{MOI.VectorOfVariables,S}()) ==
0
Expand All @@ -482,9 +485,9 @@ function _test_opt_range_case_x1_bounded_interval(make_opt)
@variable(model, x)
@variable(model, 0.0 <= y <= 1.0)
@constraint(model, [x, y] ∈ MOI.Complements(2))
inner = MOI.Utilities.Model{Float64}()
set_optimizer(model, () -> make_opt(inner))
set_optimizer(model, () -> make_opt(MOI.Utilities.Model{Float64}))
MOI.Utilities.attach_optimizer(model)
inner = unsafe_backend(model)
S = MathOptComplements.ComplementsWithSetType{MOI.Interval{Float64}}
@test MOI.get(inner, MOI.NumberOfConstraints{MOI.VectorOfVariables,S}()) ==
0
Expand All @@ -496,9 +499,9 @@ function _test_opt_range_case_x1_bounded_nonneg(make_opt)
@variable(model, 0.0 <= x <= 10.0)
@variable(model, 0.0 <= y <= 10.0)
@constraint(model, [x, y] ∈ MOI.Complements(2))
inner = MOI.Utilities.Model{Float64}()
set_optimizer(model, () -> make_opt(inner))
set_optimizer(model, () -> make_opt(MOI.Utilities.Model{Float64}))
MOI.Utilities.attach_optimizer(model)
inner = unsafe_backend(model)
S = MathOptComplements.ComplementsWithSetType{MOI.Interval{Float64}}
@test MOI.get(inner, MOI.NumberOfConstraints{MOI.VectorOfVariables,S}()) ==
0
Expand All @@ -519,7 +522,7 @@ function _test_opt_Vertical(make_opt)
@variable(model, x3)
@variable(model, 0.0 <= y3)
@constraint(model, [1.0*x3, y3] ∈ MOI.Complements(2))
set_optimizer(model, () -> make_opt(Ipopt.Optimizer()))
set_optimizer(model, () -> make_opt(Ipopt.Optimizer))
MOI.Utilities.attach_optimizer(model)
return
end
Expand All @@ -529,7 +532,7 @@ function _test_opt_Vertical_errors(make_opt)
@variable(model, 0.0 <= x)
@variable(model, 0.0 <= y)
@constraint(model, [x, 1.0*y + x] ∈ MOI.Complements(2))
set_optimizer(model, () -> make_opt(Ipopt.Optimizer()))
set_optimizer(model, () -> make_opt(Ipopt.Optimizer))
@test_throws Exception MOI.Utilities.attach_optimizer(model)
return
end
Expand All @@ -547,7 +550,8 @@ function _test_relaxation(relax)
set_optimizer(
model,
() -> MathOptComplements.Optimizer(
MOI.instantiate(Ipopt.Optimizer; with_cache_type = Float64),
Ipopt.Optimizer;
with_cache_type = Float64,
),
)
set_attribute(
Expand Down Expand Up @@ -591,7 +595,7 @@ function test_simple_ncp()
MathOptComplements.LiuFukushimaRelaxation(1e-8),
MathOptComplements.KanzowSchwarzRelaxation(1e-8),
]
model = Model(() -> MathOptComplements.Optimizer(Ipopt.Optimizer()))
model = Model(() -> MathOptComplements.Optimizer(Ipopt.Optimizer))
@variable(model, y <= 0, start = -1)
@constraint(model, y + 1 ⟂ y)
MOI.set(
Expand Down Expand Up @@ -626,7 +630,7 @@ function test_simple_lp_3()
@constraint(model, z[2] ⟂ x[2])
set_optimizer(
model,
() -> MathOptComplements.Optimizer(Ipopt.Optimizer()),
() -> MathOptComplements.Optimizer(Ipopt.Optimizer),
)
MOI.set(
model,
Expand All @@ -649,7 +653,7 @@ function test_fletcher_leyffer_ex1_model()
MathOptComplements.LiuFukushimaRelaxation(1e-8),
MathOptComplements.KanzowSchwarzRelaxation(1e-8),
]
model = Model(() -> MathOptComplements.Optimizer(Ipopt.Optimizer()))
model = Model(() -> MathOptComplements.Optimizer(Ipopt.Optimizer))
@variable(model, z[1:2])
set_lower_bound(z[2], 0)
@objective(model, Min, (z[1] - 1)^2 + z[2]^2)
Expand All @@ -676,7 +680,7 @@ function test_reformulation_doesnt_error()
MathOptComplements.LiuFukushimaRelaxation(1e-8),
MathOptComplements.KanzowSchwarzRelaxation(1e-8),
]
model = Model(() -> MathOptComplements.Optimizer(Ipopt.Optimizer()))
model = Model(() -> MathOptComplements.Optimizer(Ipopt.Optimizer))
# Case 1: Complementarity defined as lower-bound on RHS
@variable(model, x1)
@variable(model, 0.0 <= y1)
Expand All @@ -701,8 +705,10 @@ function test_nonlinear_reformulation()
@variable(model, y >= 0.0)
@objective(model, Min, x^2 + y^2 - 4*x*y)
@constraint(model, [sin(x), y] ∈ MOI.Complements(2))
inner = MOI.Utilities.Model{Float64}()
set_optimizer(model, () -> MathOptComplements.Optimizer(inner))
set_optimizer(
model,
() -> MathOptComplements.Optimizer(MOI.Utilities.Model{Float64}),
)
MOI.Utilities.attach_optimizer(model)
expected = Model()
@variable(expected, x >= 0.0)
Expand All @@ -725,8 +731,10 @@ function test_reformulation_fletcher_leyffer_ex1()
set_lower_bound(z[2], 0)
@objective(model, Min, (z[1] - 1)^2 + z[2]^2)
@constraint(model, [z[2] - z[1], z[2]] ∈ MOI.Complements(2))
inner = MOI.Utilities.Model{Float64}()
set_optimizer(model, () -> MathOptComplements.Optimizer(inner))
set_optimizer(
model,
() -> MathOptComplements.Optimizer(MOI.Utilities.Model{Float64}),
)
MOI.Utilities.attach_optimizer(model)
expected = Model()
@variable(expected, z[1:2])
Expand All @@ -751,7 +759,7 @@ function test_double_sided_bound_reformulations()
# MathOptComplements.LiuFukushimaRelaxation(1e-8),
MathOptComplements.KanzowSchwarzRelaxation(1e-8),
]
model = Model(() -> MathOptComplements.Optimizer(Ipopt.Optimizer()))
model = Model(() -> MathOptComplements.Optimizer(Ipopt.Optimizer))
set_attribute(
model,
MathOptComplements.DefaultComplementarityReformulation(),
Expand Down Expand Up @@ -795,7 +803,7 @@ function test_greater_than_reformulations()
MathOptComplements.LiuFukushimaRelaxation(1e-8),
MathOptComplements.KanzowSchwarzRelaxation(1e-8),
]
model = Model(() -> MathOptComplements.Optimizer(Ipopt.Optimizer()))
model = Model(() -> MathOptComplements.Optimizer(Ipopt.Optimizer))
set_attribute(
model,
MathOptComplements.DefaultComplementarityReformulation(),
Expand Down Expand Up @@ -832,7 +840,7 @@ function test_less_than_reformulations()
MathOptComplements.LiuFukushimaRelaxation(1e-8),
MathOptComplements.KanzowSchwarzRelaxation(1e-8),
]
model = Model(() -> MathOptComplements.Optimizer(Ipopt.Optimizer()))
model = Model(() -> MathOptComplements.Optimizer(Ipopt.Optimizer))
set_attribute(
model,
MathOptComplements.DefaultComplementarityReformulation(),
Expand Down
Loading