diff --git a/README.md b/README.md index 3315820..fe9349c 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ system, `$SCIPOPTDIR/lib/libscip.so`, `$SCIPOPTDIR/lib/libscip.dylib`, or `$SCIPOPTDIR/bin/libscip.dll` must exist). Then, install `SCIP.jl` using `Pkg.add` and `Pkg.build` from the Julia command line: ```julia -julia> ENV["SCIPOPTDIR"] = raw"C:\Program Files\SCIPOptSuite 9.1.1" # for Windows +julia> ENV["SCIPOPTDIR"] = raw"C:\Program Files\SCIPOptSuite 10.0.0" # for Windows julia> import Pkg diff --git a/src/MOI_wrapper/conflict.jl b/src/MOI_wrapper/conflict.jl index c91e03c..ec641af 100644 --- a/src/MOI_wrapper/conflict.jl +++ b/src/MOI_wrapper/conflict.jl @@ -143,3 +143,163 @@ function MOI.get( slack_value = SCIPgetSolVal(o, sol, ptr) return slack_value > 0.5 ? MOI.IN_CONFLICT : MOI.NOT_IN_CONFLICT end + +## IIS & MOI conflicts + +function has_all_names(o::Optimizer) + for vidx in MOI.get(o, MOI.ListOfVariableIndices()) + if MOI.get(o, MOI.VariableName(), vidx) == "" + return false + end + end + for (F, S) in MOI.get(o, MOI.ListOfConstraintTypesPresent()) + if F === MOI.VariableIndex + continue + end + for cidx in MOI.get(o, MOI.ListOfConstraintIndices{F,S}()) + if MOI.get(o, MOI.ConstraintName(), cidx) === nothing || + MOI.get(o, MOI.ConstraintName(), cidx) == "" + return false + end + end + end + return true +end + +function set_default_names!(o::Optimizer) + for vidx in MOI.get(o, MOI.ListOfVariableIndices()) + if MOI.get(o, MOI.VariableName(), vidx) == "" + MOI.set(o, MOI.VariableName(), vidx, "x$(vidx.value)") + end + end + for (F, S) in MOI.get(o, MOI.ListOfConstraintTypesPresent()) + if F === MOI.VariableIndex + continue + end + for cidx in MOI.get(o, MOI.ListOfConstraintIndices{F,S}()) + if MOI.get(o, MOI.ConstraintName(), cidx) === nothing || + MOI.get(o, MOI.ConstraintName(), cidx) == "" + MOI.set(o, MOI.ConstraintName(), cidx, "$(string(cidx))") + end + end + end + return +end + +function MOI.compute_conflict!(o::Optimizer) + if !has_all_names(o::Optimizer) + error( + "SCIP requires all variables and constraints to have a name set for the IIS to work.", + ) + end + @SCIP_CALL SCIP.LibSCIP.SCIPgenerateIIS(o) + return +end + +function MOI.get(o::Optimizer, ::MOI.ConflictStatus) + iis = SCIP.LibSCIP.SCIPgetIIS(o) + is_infeasible = SCIP.LibSCIP.SCIPiisIsSubscipInfeasible(iis) + st = MOI.get(o, MOI.TerminationStatus()) + # we ignore MOI.NO_CONFLICT_FOUND for now + # this is not supposed to happen with a greedy method + if st != MOI.INFEASIBLE + return MOI.NO_CONFLICT_EXISTS + elseif is_infeasible == 0 + return MOI.COMPUTE_CONFLICT_NOT_CALLED + else + return MOI.CONFLICT_FOUND + end +end + +function MOI.get( + o::Optimizer, + ::MOI.ConstraintConflictStatus, + index::MOI.ConstraintIndex{MOI.VariableIndex,S}, +) where {S<:Union{MOI.LessThan,MOI.EqualTo,MOI.GreaterThan}} + if MOI.get(o, MOI.ConflictStatus()) != MOI.CONFLICT_FOUND + return MOI.NOT_IN_CONFLICT + end + set = MOI.get(o, MOI.ConstraintSet(), index) + bound_values = bounds(o, set) + index_var_name = + MOI.get(o, MOI.VariableName(), MOI.VariableIndex(index.value)) + nvars = SCIP.LibSCIP.SCIPgetNOrigVars(iis_subscip) + vars_ptr = SCIP.LibSCIP.SCIPgetOrigVars(iis_subscip) + raw_vars_vec = unsafe_wrap(Array, vars_ptr, nvars) + for var in raw_vars_vec + var_name = unsafe_string(SCIP.LibSCIP.SCIPvarGetName(var)) + if var_name == index_var_name + lb = SCIP.LibSCIP.SCIPvarGetUbOriginal(var) + ub = SCIP.LibSCIP.SCIPvarGetUbOriginal(var) + if bound_values[1] ≈ lb && bound_values[2] ≈ ub + return MOI.IN_CONFLICT + end + return MOI.NOT_IN_CONFLICT + end + end + # variable not present in the IIS subproblem + return MOI.NOT_IN_CONFLICT +end + +function MOI.get( + o::Optimizer, + ::MOI.ConstraintConflictStatus, + index::MOI.ConstraintIndex{F,S}, +) where { + F<:Union{ + MOI.ScalarAffineFunction, + MOI.VectorOfVariables, + MOI.ScalarQuadraticFunction, + }, + S, +} + if MOI.get(o, MOI.ConflictStatus()) != MOI.CONFLICT_FOUND + return MOI.NOT_IN_CONFLICT + end + c_name = MOI.get(o, MOI.ConstraintName(), index) + iis_subscip = SCIP.LibSCIP.SCIPiisGetSubscip(SCIP.LibSCIP.SCIPgetIIS(o)) + n_cons = SCIP.LibSCIP.SCIPgetNOrigConss(iis_subscip) + cons_ptr = SCIP.LibSCIP.SCIPgetOrigConss(iis_subscip) + raw_cons_vec = unsafe_wrap(Array, cons_ptr, n_cons) + for cons in raw_cons_vec + cons_name = unsafe_string(SCIP.LibSCIP.SCIPconsGetName(cons)) + if cons_name == c_name + return MOI.IN_CONFLICT + end + end + return MOI.NOT_IN_CONFLICT +end + +function MOI.get( + o::Optimizer, + ::MOI.ConstraintConflictStatus, + index::MOI.ConstraintIndex{ + MOI.VariableIndex, + <:Union{MOI.Integer,MOI.ZeroOne}, + }, +) + if MOI.get(o, MOI.ConflictStatus()) != MOI.CONFLICT_FOUND + return MOI.NOT_IN_CONFLICT + end + if !MOI.is_valid(model, index) + throw(MOI.InvalidIndex(index)) + end + index_var_name = + MOI.get(o, MOI.VariableName(), MOI.VariableIndex(index.value)) + nvars = SCIP.LibSCIP.SCIPgetNOrigVars(iis_subscip) + vars_ptr = SCIP.LibSCIP.SCIPgetOrigVars(iis_subscip) + raw_vars_vec = unsafe_wrap(Array, vars_ptr, nvars) + for var in raw_vars_vec + var_name = unsafe_string(SCIP.LibSCIP.SCIPvarGetName(var)) + if var_name == index_var_name + if SCIP.LibSCIP.SCIPvarIsBinary(var) || + SCIP.LibSCIP.SCIPvarIsIntegral(var) + # we cannot ensure the integrality constraint is in the conflict + return MOI.MAYBE_IN_CONFLICT + end + return MOI.NOT_IN_CONFLICT + end + end + + return MOI.MAYBE_IN_CONFLICT +end diff --git a/test/MOI_tests.jl b/test/MOI_tests.jl index 79c28c7..7922926 100644 --- a/test/MOI_tests.jl +++ b/test/MOI_tests.jl @@ -1692,6 +1692,55 @@ function test_fix_binary_variable() return end +function test_SOS1_no_conflict() + model = SCIP.Optimizer() + MOI.set(model, MOI.Silent(), true) + x = MOI.add_variables(model, 2) + set = MOI.SOS1([1.0, 2.0]) + c1 = MOI.add_constraint(model, MOI.VectorOfVariables(x), set) + SCIP.set_default_names!(model) + MOI.optimize!(model) + MOI.compute_conflict!(model) + @test MOI.get(model, MOI.ConstraintConflictStatus(), c1) == + MOI.NOT_IN_CONFLICT + return +end + +function test_SOS1_in_conflict() + model = SCIP.Optimizer() + MOI.set(model, MOI.Silent(), true) + x = MOI.add_variables(model, 2) + MOI.add_constraint.(model, x, MOI.GreaterThan(0.0)) + MOI.add_constraint.(model, x, MOI.LessThan(1.0)) + set = MOI.SOS1([1.0, 2.0]) + c1 = MOI.add_constraint(model, MOI.VectorOfVariables(x), set) + c2 = MOI.add_constraint(model, 1.0 * x[1] + 1.0 * x[2], MOI.EqualTo(2.0)) + SCIP.set_default_names!(model) + MOI.optimize!(model) + MOI.compute_conflict!(model) + @test MOI.get(model, MOI.ConstraintConflictStatus(), c1) == MOI.IN_CONFLICT + @test MOI.get(model, MOI.ConstraintConflictStatus(), c2) == MOI.IN_CONFLICT + return +end + +function test_SOS2_not_in_conflict() + model = SCIP.Optimizer() + MOI.set(model, MOI.Silent(), true) + x = MOI.add_variables(model, 3) + MOI.add_constraint.(model, x, MOI.GreaterThan(0.0)) + set = MOI.SOS2([1.0, 2.0, 3.0]) + c1 = MOI.add_constraint(model, MOI.VectorOfVariables(x), set) + f = sum(1.0 * x[i] for i in 1:3) + c2 = MOI.add_constraint(model, f, MOI.EqualTo(-1.0)) + SCIP.set_default_names!(model) + MOI.optimize!(model) + MOI.compute_conflict!(model) + @test MOI.get(model, MOI.ConstraintConflictStatus(), c1) == + MOI.NOT_IN_CONFLICT + @test MOI.get(model, MOI.ConstraintConflictStatus(), c2) == MOI.IN_CONFLICT + return +end + end # module TestMOIWrapper TestMOIWrapper.runtests() diff --git a/test/iis.jl b/test/iis.jl new file mode 100644 index 0000000..dd9803e --- /dev/null +++ b/test/iis.jl @@ -0,0 +1,53 @@ +using SCIP +using Test +import MathOptInterface as MOI + +@testset "Basic conflict with raw SCIP API" begin + o = SCIP.Optimizer() + + x, _ = MOI.add_constrained_variable(o, MOI.ZeroOne()) + y, _ = MOI.add_constrained_variable(o, MOI.ZeroOne()) + z, _ = MOI.add_constrained_variable(o, MOI.ZeroOne()) + w, _ = MOI.add_constrained_variable(o, MOI.ZeroOne()) + + MOI.set(o, MOI.VariableName(), x, "x") + MOI.set(o, MOI.VariableName(), y, "y") + MOI.set(o, MOI.VariableName(), z, "z") + MOI.set(o, MOI.VariableName(), w, "w") + + MOI.add_constraint(o, 1.0x + y, MOI.LessThan(1.5)) + MOI.add_constraint(o, 1.0z + 1.2y, MOI.LessThan(1.3)) + MOI.add_constraint(o, 1.0z + 1.2y, MOI.LessThan(1.3)) + MOI.add_constraint(o, 1.0w + 1.2x, MOI.LessThan(1.1)) + + MOI.add_constraint(o, 1.0z + 0.9x + 1.2y, MOI.GreaterThan(2.0)) + + for S in (MOI.LessThan{Float64}, MOI.GreaterThan{Float64}) + for cidx in MOI.get( + o, + MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{Float64},S}(), + ) + MOI.set( + o, + MOI.ConstraintName(), + cidx, + "c$(cidx.value)_$(length(string(S)))", + ) + end + end + + @test SCIP.has_all_names(o) + + f_obj = 1.0 * x + MOI.set(o, MOI.ObjectiveFunction{typeof(f_obj)}(), f_obj) + MOI.set(o, MOI.ObjectiveSense(), MOI.MIN_SENSE) + + MOI.optimize!(o) + @test MOI.get(o, MOI.TerminationStatus()) == MOI.INFEASIBLE + + iis = SCIP.LibSCIP.SCIPgetIIS(o) + @test SCIP.LibSCIP.SCIPiisIsSubscipInfeasible(iis) == 0 + SCIP.LibSCIP.SCIPgenerateIIS(o) + @test SCIP.LibSCIP.SCIPiisIsSubscipInfeasible(iis) == 1 + @test SCIP.LibSCIP.SCIPiisIsSubscipIrreducible(iis) == 1 +end diff --git a/test/runtests.jl b/test/runtests.jl index 1506409..ed04c70 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -21,6 +21,7 @@ include("sepa_support.jl") @testset "SCIP" begin @testset "$file" for file in [ + "iis.jl", "conshdlr.jl", "cutsel.jl", "direct_library_calls.jl",