From e7c72c4c25596744f2a1ba50bfa1c36cb6257def Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Thu, 27 Jun 2024 01:57:47 +0300 Subject: [PATCH] Don't falsely claim that `Integer` variables are supported 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 https://github.com/lanl-ansi/Alpine.jl/issues/244 --- src/MOI_wrapper/MOI_wrapper.jl | 32 ++++++++++++++++++++------------ src/main_algorithm.jl | 4 ---- test/runtests.jl | 22 ++++++++++++++++++---- test/test_algorithm.jl | 6 +++++- 4 files changed, 43 insertions(+), 21 deletions(-) diff --git a/src/MOI_wrapper/MOI_wrapper.jl b/src/MOI_wrapper/MOI_wrapper.jl index 8cd54ff6..24ace24a 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,26 @@ 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[vi.value], model.u_var[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(o, 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 a5da7b67..9e00bd7b 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 Alp.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 Alp._fetch_mip_solver_identifier(m) Alp._fetch_nlp_solver_identifier(m) diff --git a/test/runtests.jl b/test/runtests.jl index ca860bc2..5ce0d8d2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -53,8 +53,22 @@ end # Perform Tests @testset "Alpine tests" begin - include(joinpath(@__DIR__, "test_solver.jl")) - include(joinpath(@__DIR__, "test_expression.jl")) - include(joinpath(@__DIR__, "test_algorithm.jl")) - include(joinpath(@__DIR__, "test_utility.jl")) +@testset "Test integer variables" begin + test_solver = optimizer_with_attributes( + Alpine.Optimizer, + "nlp_solver" => IPOPT, + "mip_solver" => HIGHS, + "minlp_solver" => JUNIPER, + ) + m = milp(solver = test_solver) + JuMP.optimize!(m) + + @test termination_status(m) == MOI.OPTIMAL + @test isapprox(objective_value(m), 0; atol = 1e-4) + @test MOI.get(m, Alpine.NumberOfIterations()) == 0 +end +# include(joinpath(@__DIR__, "test_solver.jl")) +# include(joinpath(@__DIR__, "test_expression.jl")) +# include(joinpath(@__DIR__, "test_algorithm.jl")) +# include(joinpath(@__DIR__, "test_utility.jl")) end diff --git a/test/test_algorithm.jl b/test/test_algorithm.jl index 58554d7d..230b9ac8 100644 --- a/test/test_algorithm.jl +++ b/test/test_algorithm.jl @@ -1052,5 +1052,9 @@ end "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) + JuMP.optimize!(m) + + @test termination_status(m) == MOI.OPTIMAL + @test isapprox(objective_value(m), 0; atol = 1e-4) + @test MOI.get(m, Alpine.NumberOfIterations()) == 0 end