diff --git a/examples/MINLPs/milp.jl b/examples/MINLPs/milp.jl deleted file mode 100644 index 488fbd57..00000000 --- a/examples/MINLPs/milp.jl +++ /dev/null @@ -1,8 +0,0 @@ -function milp(; solver = nothing) - m = JuMP.Model(solver) - - @variable(m, 0 <= objvar <= 20, Int) - @objective(m, Min, objvar) - - return m -end diff --git a/src/MOI_wrapper/MOI_wrapper.jl b/src/MOI_wrapper/MOI_wrapper.jl index ed0103af..ef295726 100644 --- a/src/MOI_wrapper/MOI_wrapper.jl +++ b/src/MOI_wrapper/MOI_wrapper.jl @@ -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}, @@ -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 diff --git a/src/main_algorithm.jl b/src/main_algorithm.jl index 4a3cdda8..20d27595 100644 --- a/src/main_algorithm.jl +++ b/src/main_algorithm.jl @@ -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) diff --git a/test/test_algorithm.jl b/test/test_algorithm.jl index 58554d7d..6400438e 100644 --- a/test/test_algorithm.jl +++ b/test/test_algorithm.jl @@ -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