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

Don't erroneously claim support for Integer variables #245

Merged
merged 3 commits into from
Oct 7, 2024
Merged
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
38 changes: 26 additions & 12 deletions src/MOI_wrapper/MOI_wrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -322,18 +322,6 @@ function MOI.add_constraint(model::Optimizer, vi::MOI.VariableIndex, set::SCALAR
return MOI.ConstraintIndex{typeof(vi),typeof(set)}(vi.value)
end

function MOI.supports_constraint(
::Optimizer,
::Type{MOI.VariableIndex},
::Type{MOI.Integer},
)
return true
end

function MOI.add_constraint(model::Optimizer, f::MOI.VariableIndex, set::MOI.Integer)
model.var_type_orig[f.value] = :Int
return MOI.ConstraintIndex{typeof(f),typeof(set)}(f.value)
end
function MOI.supports_constraint(
::Optimizer,
::Type{MOI.VariableIndex},
Expand Down Expand Up @@ -457,6 +445,32 @@ function MOI.is_valid(model::Alpine.Optimizer, vi::MOI.VariableIndex)
return 1 <= vi.value <= model.num_var_orig
end

function _get_bound_set(model::Alpine.Optimizer, vi::MOI.VariableIndex)
if !MOI.is_valid(model, vi)
throw(MOI.InvalidIndex(vi))
end
return _bound_set(model.l_var_orig[vi.value], model.u_var_orig[vi.value])
end

function MOI.is_valid(
model::Alpine.Optimizer,
ci::MOI.ConstraintIndex{MOI.VariableIndex,S},
) where {S<:SCALAR_SET}
set = _get_bound_set(model, MOI.VariableIndex(ci.value))
return set isa S
end

function MOI.get(
model::Alpine.Optimizer,
::MOI.ConstraintSet,
ci::MOI.ConstraintIndex{MOI.VariableIndex,S},
) where {S<:SCALAR_SET}
if !MOI.is_valid(model, ci)
throw(MOI.InvalidIndex(ci))
end
return _get_bound_set(model, MOI.VariableIndex(ci.value))
end

# Taken from MatrixOptInterface.jl
@enum ConstraintSense EQUAL_TO GREATER_THAN LESS_THAN INTERVAL
_sense(::Type{<:MOI.EqualTo}) = EQUAL_TO
Expand Down
4 changes: 0 additions & 4 deletions src/main_algorithm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,6 @@ function load!(m::Optimizer)
# Populate data to create the bounding MIP model
recategorize_var(m) # Initial round of variable re-categorization

:Int in m.var_type_orig && error(
"Alpine does not support MINLPs with generic integer (non-binary) variables yet!",
)

# Solver-dependent detection
_fetch_mip_solver_identifier(m)
_fetch_nlp_solver_identifier(m)
Expand Down
71 changes: 71 additions & 0 deletions test/test_algorithm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1043,3 +1043,74 @@ end
@test isapprox(objective_value(m), 13; atol = 1e-4)
@test MOI.get(m, Alpine.NumberOfIterations()) == 0
end

@testset "Test integer variable support via IntegerToZeroOneBridge" begin
test_solver = optimizer_with_attributes(
Alpine.Optimizer,
"nlp_solver" => IPOPT,
"mip_solver" => HIGHS,
"minlp_solver" => JUNIPER,
)
model = JuMP.Model(test_solver)
@variable(model, -10 <= x <= 20, Int)
@objective(model, Min, x)
JuMP.optimize!(model)
@test termination_status(model) == MOI.OPTIMAL
@test isapprox(objective_value(model), -10; atol = 1e-4)
@test isapprox(value(x), -10, atol = 1e-6)
@test MOI.get(model, Alpine.NumberOfIterations()) == 0
end

@testset "Test integer variable support 0" begin
test_solver = optimizer_with_attributes(
Alpine.Optimizer,
"nlp_solver" => IPOPT,
"mip_solver" => HIGHS,
"minlp_solver" => JUNIPER,
)
model = JuMP.Model(test_solver)
@variable(model, x, Int)
@objective(model, Min, x)
@test_throws(
ErrorException(
"Unable to use IntegerToZeroOneBridge because the variable MOI.VariableIndex(1) has a non-finite domain",
),
JuMP.optimize!(model),
)
end

@testset "Test integer variable support 1" begin
test_solver = optimizer_with_attributes(
Alpine.Optimizer,
"nlp_solver" => IPOPT,
"mip_solver" => HIGHS,
"minlp_solver" => JUNIPER,
)
model = JuMP.Model(test_solver)
@variable(model, -10 <= x, Int)
@objective(model, Min, x)
@test_throws(
ErrorException(
"Unable to use IntegerToZeroOneBridge because the variable MOI.VariableIndex(1) has a non-finite domain",
),
JuMP.optimize!(model),
)
end

@testset "Test integer variable support 2" begin
test_solver = optimizer_with_attributes(
Alpine.Optimizer,
"nlp_solver" => IPOPT,
"mip_solver" => HIGHS,
"minlp_solver" => JUNIPER,
)
model = JuMP.Model(test_solver)
@variable(model, x <= 20, Int)
@objective(model, Min, x)
@test_throws(
ErrorException(
"Unable to use IntegerToZeroOneBridge because the variable MOI.VariableIndex(1) has a non-finite domain",
),
JuMP.optimize!(model),
)
end
Loading