Skip to content

Commit

Permalink
Merge pull request #245 from LebedevRI/int
Browse files Browse the repository at this point in the history
Don't erroneously claim support for `Integer` variables
  • Loading branch information
harshangrjn authored Oct 7, 2024
2 parents b8b3746 + ee8673f commit 034cc69
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 16 deletions.
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

0 comments on commit 034cc69

Please sign in to comment.