Skip to content

Commit

Permalink
Don't falsely claim that Integer variables are supported
Browse files Browse the repository at this point in the history
They really are not supported, and the claim is only there
to provide a nice diagnostic.

But that prevents MOI `IntegerToZeroOneBridge` from triggering,
and providing Alpine with transparent support for them.

Fixes #244
  • Loading branch information
LebedevRI committed Aug 14, 2024
1 parent 81bc900 commit d2d9d1b
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 27 deletions.
8 changes: 0 additions & 8 deletions examples/MINLPs/milp.jl

This file was deleted.

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
75 changes: 72 additions & 3 deletions test/test_algorithm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1044,13 +1044,82 @@ end
@test MOI.get(m, Alpine.NumberOfIterations()) == 0
end

@testset "Test integer variables" begin
@testset "Test integer variable support via IntegerToZeroOneBridge" begin
test_solver = optimizer_with_attributes(
Alpine.Optimizer,
"nlp_solver" => IPOPT,
"mip_solver" => HIGHS,
"minlp_solver" => JUNIPER,
)
m = milp(solver = test_solver)
@test_throws "Alpine does not support MINLPs with generic integer (non-binary) variables yet!" JuMP.optimize!(m)

m = JuMP.Model(test_solver)

@variable(m, -10 <= objvar <= 20, Int)

@objective(m, Min, objvar)

JuMP.optimize!(m)

@test termination_status(m) == MOI.OPTIMAL
@test isapprox(objective_value(m), -10; atol = 1e-4)
@test isapprox(value(objvar), -10, atol = 1E-6)
@test MOI.get(m, 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,
)

m = JuMP.Model(test_solver)

@variable(m, objvar, Int)
@constraint(m, -10 <= objvar)
@constraint(m, objvar <= 20)

@objective(m, Min, objvar)

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

m = JuMP.Model(test_solver)

@variable(m, -10 <= objvar, Int)
@constraint(m, objvar <= 20)

@objective(m, Min, objvar)

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

m = JuMP.Model(test_solver)

@variable(m, objvar <= 20, Int)
@constraint(m, -10 <= objvar)

@objective(m, Min, objvar)

@test_throws "Unable to use IntegerToZeroOneBridge because the variable MOI.VariableIndex(1) has a non-finite domain" JuMP.optimize!(
m,
)
end

0 comments on commit d2d9d1b

Please sign in to comment.