From d6bd534247dda7af9043f8da629f78c33051ca9b Mon Sep 17 00:00:00 2001 From: joaquimg Date: Fri, 6 Jun 2025 00:47:55 -0300 Subject: [PATCH 1/2] Base code --- src/MathOptConflictSolver.jl | 2 +- src/iis.jl | 560 +++++++++++++++++++++++++++++++++++ test/iis.jl | 487 ++++++++++++++++++++++++++++++ test/runtests.jl | 2 +- 4 files changed, 1049 insertions(+), 2 deletions(-) create mode 100644 src/iis.jl create mode 100644 test/iis.jl diff --git a/src/MathOptConflictSolver.jl b/src/MathOptConflictSolver.jl index cc69378..7d822ad 100644 --- a/src/MathOptConflictSolver.jl +++ b/src/MathOptConflictSolver.jl @@ -7,6 +7,6 @@ module MathOptConflictSolver import MathOptInterface as MOI -greet() = print("Hello World!") +include("iis.jl") end # module MathOptConflictSolver diff --git a/src/iis.jl b/src/iis.jl new file mode 100644 index 0000000..84e22e2 --- /dev/null +++ b/src/iis.jl @@ -0,0 +1,560 @@ +# Copyright (c) 2025: Joaquim Dias Garcia, Oscar Dowson and contributors +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +abstract type AbstractAdditionalData end + +struct InfeasibilityData + constraints::Vector{MOI.ConstraintIndex} + irreducible::Bool + metadata::AbstractAdditionalData +end + +struct BoundsData <: AbstractAdditionalData + lower_bound::Float64 + upper_bound::Float64 +end + +struct IntegralityData <: AbstractAdditionalData + lower_bound::Float64 + upper_bound::Float64 + set::Union{MOI.Integer,MOI.ZeroOne}#, MOI.Semicontinuous{T}, MOI.Semiinteger{T}} +end + +struct RangeData <: AbstractAdditionalData + lower_bound::Float64 + upper_bound::Float64 + set::Union{<:MOI.EqualTo,<:MOI.LessThan,<:MOI.GreaterThan} +end + +struct NoData <: AbstractAdditionalData end + +function compute_conflicts(model::MOI.ModelLike; optimizer = nothing) + out = InfeasibilityData[] + + T = Float64 + + variables = Dict{MOI.VariableIndex,Interval{T}}() + + variable_indices = MOI.get(model, MOI.ListOfVariableIndices()) + + lb = Dict{MOI.VariableIndex,T}() + lb_con = Dict{MOI.VariableIndex,MOI.ConstraintIndex}() + ub = Dict{MOI.VariableIndex,T}() + ub_con = Dict{MOI.VariableIndex,MOI.ConstraintIndex}() + + for con in MOI.get( + model, + MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.EqualTo{T}}(), + ) + set = MOI.get(model, MOI.ConstraintSet(), con) + func = MOI.get(model, MOI.ConstraintFunction(), con) + lb[func] = set.value + lb_con[func] = con + ub[func] = set.value + ub_con[func] = con + end + + for con in MOI.get( + model, + MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.LessThan{T}}(), + ) + set = MOI.get(model, MOI.ConstraintSet(), con) + func = MOI.get(model, MOI.ConstraintFunction(), con) + # lb[func] = -Inf + ub[func] = set.upper + ub_con[func] = con + end + + for con in MOI.get( + model, + MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.GreaterThan{T}}(), + ) + set = MOI.get(model, MOI.ConstraintSet(), con) + func = MOI.get(model, MOI.ConstraintFunction(), con) + lb[func] = set.lower + lb_con[func] = con + # ub[func] = Inf + end + + for con in MOI.get( + model, + MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.Interval{T}}(), + ) + set = MOI.get(model, MOI.ConstraintSet(), con) + func = MOI.get(model, MOI.ConstraintFunction(), con) + lb[func] = set.lower + lb_con[func] = con + ub[func] = set.upper + ub_con[func] = con + end + + # for con in MOI.get(model, MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.SemiContinuous{T}}()) + # set = MOI.get(model, MOI.ConstraintSet(), con) + # func = MOI.get(model, MOI.ConstraintFunction(), con) + # lb[func] = 0 # set.lower + # ub[func] = set.upper + # end + + # for con in MOI.get(model, MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.SemiInteger{T}}()) + # set = MOI.get(model, MOI.ConstraintSet(), con) + # func = MOI.get(model, MOI.ConstraintFunction(), con) + # lb[func] = 0 #set.lower + # ub[func] = set.upper + # end + + bounds_consistent = true + + for con in MOI.get( + model, + MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.Integer}(), + ) + func = MOI.get(model, MOI.ConstraintFunction(), con) + _lb = get(lb, func, -Inf) + _ub = get(ub, func, Inf) + if abs(_ub - _lb) < 1 && ceil(_ub) == ceil(_lb) + push!( + out, + InfeasibilityData( + [con, lb_con[func], ub_con[func]], + true, + IntegralityData(_lb, _ub, MOI.Integer()), + ), + ) + bounds_consistent = false + end + end + + for con in MOI.get( + model, + MOI.ListOfConstraintIndices{MOI.VariableIndex,MOI.ZeroOne}(), + ) + func = MOI.get(model, MOI.ConstraintFunction(), con) + _lb = get(lb, func, -Inf) + _ub = get(ub, func, Inf) + if _lb > 0 && _ub < 1 + push!( + out, + InfeasibilityData( + [con, lb_con[func], ub_con[func]], + true, + IntegralityData(_lb, _ub, MOI.ZeroOne()), + ), + ) + bounds_consistent = false + elseif _lb > 1 + push!( + out, + InfeasibilityData( + [con, lb_con[func]], + true, + IntegralityData(_lb, Inf, MOI.ZeroOne()), + ), + ) + bounds_consistent = false + elseif _ub < 0 + push!( + out, + InfeasibilityData( + [con, ub_con[func]], + true, + IntegralityData(-Inf, _ub, MOI.ZeroOne()), + ), + ) + bounds_consistent = false + end + end + + for var in variable_indices + _lb = get(lb, var, -Inf) + _ub = get(ub, var, Inf) + if _lb > _ub + push!( + out, + InfeasibilityData( + [lb_con[var], ub_con[var]], + true, + BoundsData(_lb, _ub), + ), + ) + bounds_consistent = false + else + variables[var] = Interval(_lb, _ub) + end + end + + # check PSD diagonal >= 0 ? + # other cones? + if !bounds_consistent + return out + end + + # second layer of infeasibility analysis is constraint range analysis + range_consistent = true + + affine_cons = vcat( + MOI.get( + model, + MOI.ListOfConstraintIndices{ + MOI.ScalarAffineFunction{T}, + MOI.EqualTo{T}, + }(), + ), + MOI.get( + model, + MOI.ListOfConstraintIndices{ + MOI.ScalarAffineFunction{T}, + MOI.LessThan{T}, + }(), + ), + MOI.get( + model, + MOI.ListOfConstraintIndices{ + MOI.ScalarAffineFunction{T}, + MOI.GreaterThan{T}, + }(), + ), + ) + + for con in affine_cons + func = MOI.get(model, MOI.ConstraintFunction(), con) + # failed = false + list_of_variables = MOI.VariableIndex[] + interval = _eval_variables(func) do var_idx + push!(list_of_variables, var_idx) + # this only fails if we allow continuing after bounds issues + # if !haskey(variables, var_idx) + # failed = true + # return Interval(-Inf, Inf) + # end + return variables[var_idx] + end + # if failed + # continue + # end + set = MOI.get(model, MOI.ConstraintSet(), con) + if _invalid_range(set, interval) + cons = Set{MOI.ConstraintIndex}() + push!(cons, con) + for var in list_of_variables + if haskey(lb_con, var) + push!(cons, lb_con[var]) + end + if haskey(ub_con, var) + push!(cons, ub_con[var]) + end + end + push!( + out, + InfeasibilityData( + collect(cons), + true, # strictly speaking, we might need the propor "sides" + RangeData(interval.lo, interval.hi, set), + ), + ) + range_consistent = false + end + end + + if !range_consistent + return out + end + + # check if there is a optimizer + # third layer is an IIS resolver + if optimizer === nothing + println("iis resolver cannot continue because no optimizer is provided") + return out + end + iis = iis_elastic_filter(model, optimizer) + # for now, only one iis is computed + if iis !== nothing + push!(out, InfeasibilityData(iis, true, NoData())) + end + + return out +end + +function _invalid_range(set::MOI.EqualTo, interval) + rhs = set.value + return interval.lo > rhs || interval.hi < rhs +end + +function _invalid_range(set::MOI.LessThan, interval) + rhs = set.upper + return interval.lo > rhs +end + +function _invalid_range(set::MOI.GreaterThan, interval) + rhs = set.lower + return interval.hi < rhs +end + +function _fix_to_zero(model, variable::MOI.VariableIndex, ::Type{T}) where {T} + ub_idx = + MOI.ConstraintIndex{MOI.VariableIndex,MOI.LessThan{T}}(variable.value) + lb_idx = MOI.ConstraintIndex{MOI.VariableIndex,MOI.GreaterThan{T}}( + variable.value, + ) + has_lower = false + if MOI.is_valid(model, lb_idx) + MOI.delete(model, lb_idx) + has_lower = true + # MOI.PenaltyRelaxation only creates variables with LB + # elseif MOI.is_valid(model, ub_idx) + # MOI.delete(model, ub_idx) + else + error("Variable is not bounded") + end + MOI.add_constraint(model, variable, MOI.EqualTo{T}(zero(T))) + return has_lower +end + +function _set_bound_zero( + model, + variable::MOI.VariableIndex, + has_lower::Bool, + ::Type{T}, +) where {T} + eq_idx = + MOI.ConstraintIndex{MOI.VariableIndex,MOI.EqualTo{T}}(variable.value) + @assert MOI.is_valid(model, eq_idx) + MOI.delete(model, eq_idx) + if has_lower + MOI.add_constraint(model, variable, MOI.GreaterThan{T}(zero(T))) + # MOI.PenaltyRelaxation only creates variables with LB + # else + # MOI.add_constraint(model, variable, MOI.LessThan{T}(zero(T))) + end + return +end + +function iis_elastic_filter(original_model::MOI.ModelLike, optimizer) + T = Float64 + + # handle optimize not called + status = MOI.get(original_model, MOI.TerminationStatus()) + if !( + status in + (MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE, MOI.ALMOST_INFEASIBLE) + ) + println( + "iis resolver cannot continue because model is found to be $(status) by the solver", + ) + return nothing + end + + model = MOI.instantiate(optimizer) + reference_map = MOI.copy_to(model, original_model) + MOI.set(model, MOI.Silent(), true) + + obj_sense = MOI.get(model, MOI.ObjectiveSense()) + base_obj_type = MOI.get(model, MOI.ObjectiveFunctionType()) + base_obj_func = MOI.get(model, MOI.ObjectiveFunction{base_obj_type}()) + + constraint_to_affine = + MOI.modify(model, MOI.Utilities.PenaltyRelaxation(default = 1.0)) + # might need to do something related to integers / binary + relaxed_obj_type = MOI.get(model, MOI.ObjectiveFunctionType()) + relaxed_obj_func = MOI.get(model, MOI.ObjectiveFunction{relaxed_obj_type}()) + + pure_relaxed_obj_func = relaxed_obj_func - base_obj_func + + max_iterations = length(constraint_to_affine) + + tolerance = 1e-5 + + de_elastisized = [] + + changed_obj = false + + for i in 1:max_iterations + MOI.optimize!(model) + status = MOI.get(model, MOI.TerminationStatus()) + if status in ( # possibily primal unbounded + MOI.INFEASIBLE_OR_UNBOUNDED, + MOI.DUAL_INFEASIBLE, + MOI.ALMOST_DUAL_INFEASIBLE, + ) + #try with a pure relaxation objective + MOI.set( + model, + MOI.ObjectiveFunction{relaxed_obj_type}(), + pure_relaxed_obj_func, + ) + changed_obj = true + MOI.optimize!(model) + end + if status in + (MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE, MOI.ALMOST_INFEASIBLE) + break + end + for (con, func) in constraint_to_affine + if length(func.terms) == 1 + var = func.terms[1].variable + value = MOI.get(model, MOI.VariablePrimal(), var) + if value > tolerance + has_lower = _fix_to_zero(model, var, T) + delete!(constraint_to_affine, con) + push!(de_elastisized, (con, var, has_lower)) + end + elseif length(func.terms) == 2 + var1 = func.terms[1].variable + coef1 = func.terms[1].coefficient + var2 = func.terms[2].variable + coef2 = func.terms[2].coefficient + value1 = MOI.get(model, MOI.VariablePrimal(), var1) + value2 = MOI.get(model, MOI.VariablePrimal(), var2) + if value1 > tolerance && value2 > tolerance + error("IIS failed due numerical instability") + elseif value1 > tolerance + # TODO: coef is alwayas 1.0 + has_lower = _fix_to_zero(model, var1, T) + delete!(constraint_to_affine, con) + constraint_to_affine[con] = coef2 * var2 + push!(de_elastisized, (con, var1, has_lower)) + elseif value2 > tolerance + has_lower = _fix_to_zero(model, var2, T) + delete!(constraint_to_affine, con) + constraint_to_affine[con] = coef1 * var1 + push!(de_elastisized, (con, var2, has_lower)) + end + else + println( + "$con and relaxing function with more than two terms: $func", + ) + end + end + end + + if changed_obj + MOI.set( + model, + MOI.ObjectiveFunction{relaxed_obj_type}(), + relaxed_obj_func, + ) + end + + # consider deleting all no iis constraints + # be careful with intervals + + obj_type = MOI.get(model, MOI.ObjectiveFunctionType()) + obj_func = MOI.get(model, MOI.ObjectiveFunction{obj_type}()) + obj_sense = MOI.get(model, MOI.ObjectiveSense()) + + # deletion filter + cadidates = MOI.ConstraintIndex[] + for (con, var, has_lower) in de_elastisized + _set_bound_zero(model, var, has_lower, T) + MOI.optimize!(model) + status = MOI.get(model, MOI.TerminationStatus()) + if status in + (MOI.INFEASIBLE, MOI.ALMOST_INFEASIBLE, MOI.ALMOST_INFEASIBLE) + # this constraint is not in IIS + elseif status in ( + MOI.OPTIMAL, + MOI.ALMOST_OPTIMAL, + MOI.LOCALLY_SOLVED, + MOI.ALMOST_LOCALLY_SOLVED, + ) + push!(cadidates, con) + _fix_to_zero(model, var, T) + elseif status in ( + MOI.INFEASIBLE_OR_UNBOUNDED, + MOI.DUAL_INFEASIBLE, + MOI.ALMOST_DUAL_INFEASIBLE, # possibily primal unbounded + ) + MOI.set(model, MOI.ObjectiveSense(), MOI.FEASIBILITY_SENSE) + MOI.optimize!(model) + primal_status = MOI.get(model, MOI.PrimalStatus()) + if primal_status in (MOI.FEASIBLE_POINT, MOI.NEARLY_FEASIBLE_POINT) + # this constraint is not in IIS + push!(cadidates, con) + _fix_to_zero(model, var, T) + MOI.set(model, MOI.ObjectiveSense(), obj_sense) + MOI.set(model, MOI.ObjectiveFunction{obj_type}(), obj_func) + else + error( + "IIS failed due numerical instability, got status $status,", + "then, for MOI.FEASIBILITY_SENSE objective, got primal status $primal_status", + ) + end + else + error("IIS failed due numerical instability, got status $status") + end + end + + pre_iis = Set(cadidates) + iis = MOI.ConstraintIndex[] + for (F, S) in MOI.get(original_model, MOI.ListOfConstraintTypesPresent()) + if F == MOI.VariableIndex + continue + end + for con in MOI.get(original_model, MOI.ListOfConstraintIndices{F,S}()) + new_con = reference_map[con] + if new_con in pre_iis + push!(iis, con) + end + end + end + + return iis +end + +#= + Helpers +=# + +# This type and the associated function were inspired by IntervalArithmetic.jl +# Copyright (c) 2014-2021: David P. Sanders & Luis Benet + +struct Interval{T<:Real} + lo::T + hi::T + + function Interval(lo::T, hi::T) where {T<:Real} + @assert lo <= hi + return new{T}(lo, hi) + end +end + +Base.convert(::Type{Interval{T}}, x::T) where {T<:Real} = Interval(x, x) + +Base.zero(::Type{Interval{T}}) where {T<:Real} = Interval(zero(T), zero(T)) + +Base.iszero(a::Interval) = iszero(a.hi) && iszero(a.lo) + +function Base.:+(a::Interval{T}, b::Interval{T}) where {T<:Real} + return Interval(a.lo + b.lo, a.hi + b.hi) +end + +function Base.:*(x::T, a::Interval{T}) where {T<:Real} + if iszero(a) || iszero(x) + return Interval(zero(T), zero(T)) + elseif x >= zero(T) + return Interval(a.lo * x, a.hi * x) + end + return Interval(a.hi * x, a.lo * x) +end + +# This type and the associated function were inspired by JuMP.jl and +# MathOptInterface.jl + +function _eval_variables(value_fn::Function, t::MOI.ScalarAffineTerm) + return t.coefficient * value_fn(t.variable) +end + +function _eval_variables( + value_fn::Function, + f::MOI.ScalarAffineFunction{T}, +) where {T} + # TODO: this conversion exists in JuMP, but not in MOI + S = Base.promote_op(value_fn, MOI.VariableIndex) + U = MOI.MA.promote_operation(*, T, S) + out = convert(U, f.constant) + for t in f.terms + out += _eval_variables(value_fn, t) + end + return out +end diff --git a/test/iis.jl b/test/iis.jl new file mode 100644 index 0000000..9aaf233 --- /dev/null +++ b/test/iis.jl @@ -0,0 +1,487 @@ +# Copyright (c) 2025: Joaquim Dias Garcia, Oscar Dowson and contributors +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +module TestIIS + +import HiGHS +using JuMP +import MathOptConflictSolver as MOCS +using Test + +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$name", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end + return +end + +function test_bounds() + model = Model() + @variable(model, 0 <= x <= 1) + @variable(model, 2 <= y <= 1) + @constraint(model, x + y <= 1) + @objective(model, Max, x + y) + data = MOCS.compute_conflicts(JuMP.backend(model)) + @test length(data) == 1 + @test data[].constraints == + [JuMP.index(LowerBoundRef(y)), JuMP.index(UpperBoundRef(y))] + @test data[].irreducible + @test data[].metadata == MOCS.BoundsData(2.0, 1.0) + return +end + +function test_integrality() + model = Model() + @variable(model, 0 <= x <= 1, Int) + @variable(model, 2.2 <= y <= 2.9, Int) + @constraint(model, x + y <= 1) + @objective(model, Max, x + y) + data = MOCS.compute_conflicts(JuMP.backend(model)) + @test length(data) == 1 + @test data[].constraints == [ + JuMP.index(IntegerRef(y)), + JuMP.index(LowerBoundRef(y)), + JuMP.index(UpperBoundRef(y)), + ] + @test data[].irreducible + @test data[].metadata == MOCS.IntegralityData(2.2, 2.9, MOI.Integer()) + return +end + +function test_binary_inner() + model = Model() + @variable(model, 0.5 <= x <= 0.8, Bin) + @variable(model, 0 <= y <= 1, Bin) + @constraint(model, x + y <= 1) + @objective(model, Max, x + y) + data = MOCS.compute_conflicts(JuMP.backend(model)) + @test length(data) == 1 + @test data[].constraints == [ + JuMP.index(BinaryRef(x)), + JuMP.index(LowerBoundRef(x)), + JuMP.index(UpperBoundRef(x)), + ] + @test data[].irreducible + @test data[].metadata == MOCS.IntegralityData(0.5, 0.8, MOI.ZeroOne()) + return +end + +function test_binary_lower() + model = Model() + @variable(model, 1.5 <= x <= 1.8, Bin) + @variable(model, 0 <= y <= 1, Bin) + @constraint(model, x + y <= 1) + @objective(model, Max, x + y) + data = MOCS.compute_conflicts(JuMP.backend(model)) + @test length(data) == 1 + @test data[].constraints == [ + JuMP.index(BinaryRef(x)), + JuMP.index(LowerBoundRef(x)), + ] + @test data[].irreducible + @test data[].metadata == MOCS.IntegralityData(1.5, Inf, MOI.ZeroOne()) + return +end + +function test_binary_upper() + model = Model() + @variable(model, -2.5 <= x <= -1.8, Bin) + @variable(model, 0 <= y <= 1, Bin) + @constraint(model, x + y <= 1) + @objective(model, Max, x + y) + data = MOCS.compute_conflicts(JuMP.backend(model)) + @test length(data) == 1 + @test data[].constraints == [ + JuMP.index(BinaryRef(x)), + JuMP.index(UpperBoundRef(x)), + ] + @test data[].irreducible + @test data[].metadata == MOCS.IntegralityData(-Inf, -1.8, MOI.ZeroOne()) + return +end + +function _isequal_unordered(v1::AbstractVector, v2::AbstractVector) + return length(v1) == length(v2) && Set(v1) == Set(v2) +end + +function test_range() + model = Model() + @variable(model, 10 <= x <= 11) + @variable(model, 1 <= y <= 11) + @constraint(model, c, x + y <= 1) + @objective(model, Max, x + y) + data = MOCS.compute_conflicts(JuMP.backend(model)) + @test length(data) == 1 + @test _isequal_unordered(data[].constraints, [ + JuMP.index(c), + JuMP.index(UpperBoundRef(x)), + JuMP.index(LowerBoundRef(x)), + JuMP.index(UpperBoundRef(y)), + JuMP.index(LowerBoundRef(y)), + ]) + @test data[].irreducible + @test data[].metadata == MOCS.RangeData(11.0, 22.0, MOI.LessThan{Float64}(1.0)) + return +end + +function test_range_neg() + model = Model() + @variable(model, 10 <= x <= 11) + @variable(model, -11 <= y <= -1) + @constraint(model, c, x - y <= 1) + @objective(model, Max, x + y) + # + data = MOCS.compute_conflicts(JuMP.backend(model)) + @test length(data) == 1 + @test _isequal_unordered(data[].constraints, [ + JuMP.index(c), + JuMP.index(UpperBoundRef(x)), + JuMP.index(LowerBoundRef(x)), + JuMP.index(UpperBoundRef(y)), + JuMP.index(LowerBoundRef(y)), + ]) + @test data[].irreducible + @test data[].metadata == MOCS.RangeData(11.0, 22.0, MOI.LessThan{Float64}(1.0)) + return +end + +function test_range_equalto() + model = Model() + @variable(model, x == 1) + @variable(model, y == 2) + @constraint(model, c, x + y == 1) + @objective(model, Max, x + y) + data = MOCS.compute_conflicts(JuMP.backend(model)) + @test length(data) == 1 + @test _isequal_unordered(data[].constraints, [ + JuMP.index(c), + JuMP.index(FixRef(x)), + JuMP.index(FixRef(y)), + ]) + @test data[].irreducible + @test data[].metadata == MOCS.RangeData(3.0, 3.0, MOI.EqualTo{Float64}(1.0)) + return +end + +function test_range_equalto_2() + model = Model() + @variable(model, x == 1) + @variable(model, y == 2) + @constraint(model, c, 3x + 2y == 1) + @objective(model, Max, x + y) + data = MOCS.compute_conflicts(JuMP.backend(model)) + @test length(data) == 1 + @test _isequal_unordered(data[].constraints, [ + JuMP.index(c), + JuMP.index(FixRef(x)), + JuMP.index(FixRef(y)), + ]) + @test data[].irreducible + @test data[].metadata == MOCS.RangeData(7.0, 7.0, MOI.EqualTo{Float64}(1.0)) + return +end + +function test_range_greaterthan() + model = Model() + @variable(model, 10 <= x <= 11) + @variable(model, 1 <= y <= 11) + @constraint(model, c, x + y >= 100) + @objective(model, Max, x + y) + data = MOCS.compute_conflicts(JuMP.backend(model)) + @test length(data) == 1 + @test _isequal_unordered(data[].constraints, [ + JuMP.index(c), + JuMP.index(UpperBoundRef(x)), + JuMP.index(LowerBoundRef(x)), + JuMP.index(UpperBoundRef(y)), + JuMP.index(LowerBoundRef(y)), + ]) + @test data[].irreducible + @test data[].metadata == MOCS.RangeData(11.0, 22.0, MOI.GreaterThan{Float64}(100.0)) + return +end + +function test_range_equalto_3() + model = Model() + @variable(model, 10 <= x <= 11) + @variable(model, 1 <= y <= 11) + @constraint(model, c, x + y == 100) + @objective(model, Max, x + y) + data = MOCS.compute_conflicts(JuMP.backend(model)) + @test length(data) == 1 + @test _isequal_unordered(data[].constraints, [ + JuMP.index(c), + JuMP.index(UpperBoundRef(x)), + JuMP.index(LowerBoundRef(x)), + JuMP.index(UpperBoundRef(y)), + JuMP.index(LowerBoundRef(y)), + ]) + @test data[].irreducible + @test data[].metadata == MOCS.RangeData(11.0, 22.0, MOI.EqualTo{Float64}(100.0)) + return +end + +function test_interval() + model = Model(HiGHS.Optimizer) + set_silent(model) + @variable(model, x in MOI.Interval(0, 10)) + @variable(model, 0 <= y <= 20) + @constraint(model, c1, x + y <= 1) + @objective(model, Max, x + y) + optimize!(model) + data = MOCS.compute_conflicts(JuMP.backend(model), optimizer = HiGHS.Optimizer) + @test length(data) == 0 + # TODO check status + return +end +#= + +function test_iis_feasible() + model = Model(HiGHS.Optimizer) + set_silent(model) + @variable(model, 0 <= x <= 10) + @variable(model, 0 <= y <= 20) + @constraint(model, c1, x + y <= 1) + @objective(model, Max, x + y) + optimize!(model) + data = ModelAnalyzer.analyze( + ModelAnalyzer.Infeasibility.Analyzer(), + model, + optimizer = HiGHS.Optimizer, + ) + list = ModelAnalyzer.list_of_issue_types(data) + @test length(list) == 0 +end + + + +function test_iis() + model = Model(HiGHS.Optimizer) + set_silent(model) + @variable(model, 0 <= x <= 10) + @variable(model, 0 <= y <= 20) + @constraint(model, c1, x + y <= 1) + @constraint(model, c2, x + y >= 2) + @objective(model, Max, x + y) + optimize!(model) + data = ModelAnalyzer.analyze(ModelAnalyzer.Infeasibility.Analyzer(), model) + list = ModelAnalyzer.list_of_issue_types(data) + @test length(list) == 0 + data = ModelAnalyzer.analyze( + ModelAnalyzer.Infeasibility.Analyzer(), + model, + optimizer = HiGHS.Optimizer, + ) + list = ModelAnalyzer.list_of_issue_types(data) + @test length(list) == 1 + ret = ModelAnalyzer.list_of_issues(data, list[1]) + @test length(ret) == 1 + @test length(ret[].constraint) == 2 + @test Set([ret[].constraint[1], ret[].constraint[2]]) == + Set(JuMP.index.([c2, c1])) + iis = ModelAnalyzer.constraints(ret[], model) + @test length(iis) == 2 + @test Set(iis) == Set([c2, c1]) + # + buf = IOBuffer() + ModelAnalyzer.summarize( + buf, + ModelAnalyzer.Infeasibility.IrreducibleInfeasibleSubset, + ) + str = String(take!(buf)) + @test startswith(str, "# `IrreducibleInfeasibleSubset`") + ModelAnalyzer.summarize( + buf, + ModelAnalyzer.Infeasibility.IrreducibleInfeasibleSubset, + verbose = false, + ) + str = String(take!(buf)) + @test str == "# IrreducibleInfeasibleSubset" + # + ModelAnalyzer.summarize(buf, ret[1], verbose = true) + str = String(take!(buf)) + @test startswith(str, "Irreducible Infeasible Subset: ") + @test contains(str, ", ") + ModelAnalyzer.summarize(buf, ret[1], verbose = false) + str = String(take!(buf)) + @test startswith(str, "IIS: ") + @test contains(str, ", ") + + buf = IOBuffer() + Base.show(buf, data) + str = String(take!(buf)) + @test startswith(str, "Infeasibility analysis found 1 issues") + + ModelAnalyzer.summarize(buf, data, verbose = true) + str = String(take!(buf)) + @test startswith(str, "## Infeasibility Analysis\n\n") + ModelAnalyzer.summarize(buf, data, verbose = false) + ModelAnalyzer.summarize(buf, data, verbose = true) + return +end + +function test_iis_free_var() + model = Model(HiGHS.Optimizer) + set_silent(model) + @variable(model, x) + @variable(model, y) + @constraint(model, c1, x + y <= 1) + @constraint(model, c2, x + y >= 2) + @objective(model, Max, -2x + y) + optimize!(model) + data = ModelAnalyzer.analyze( + ModelAnalyzer.Infeasibility.Analyzer(), + model, + optimizer = HiGHS.Optimizer, + ) + list = ModelAnalyzer.list_of_issue_types(data) + @test length(list) == 1 + ret = ModelAnalyzer.list_of_issues(data, list[1]) + @test length(ret) == 1 + @test length(ret[].constraint) == 2 + @test Set([ret[].constraint[1], ret[].constraint[2]]) == + Set(JuMP.index.([c2, c1])) + iis = ModelAnalyzer.constraints(ret[], model) + @test length(iis) == 2 + @test Set(iis) == Set([c2, c1]) + return +end + +function test_iis_multiple() + model = Model(HiGHS.Optimizer) + set_silent(model) + @variable(model, 0 <= x <= 10) + @variable(model, 0 <= y <= 20) + @constraint(model, c1, x + y <= 1) + @constraint(model, c3, x + y <= 1.5) + @constraint(model, c2, x + y >= 2) + @objective(model, Max, x + y) + optimize!(model) + data = ModelAnalyzer.analyze(ModelAnalyzer.Infeasibility.Analyzer(), model) + list = ModelAnalyzer.list_of_issue_types(data) + @test length(list) == 0 + data = ModelAnalyzer.analyze( + ModelAnalyzer.Infeasibility.Analyzer(), + model, + optimizer = HiGHS.Optimizer, + ) + list = ModelAnalyzer.list_of_issue_types(data) + @test length(list) == 1 + ret = ModelAnalyzer.list_of_issues(data, list[1]) + @test length(ret) == 1 + @test length(ret[].constraint) == 2 + @test JuMP.index(c2) in Set([ret[].constraint[1], ret[].constraint[2]]) + @test Set([ret[].constraint[1], ret[].constraint[2]]) ⊆ + Set(JuMP.index.([c3, c2, c1])) + iis = ModelAnalyzer.constraints(ret[], model) + @test length(iis) == 2 + @test Set(iis) ⊆ Set([c3, c2, c1]) + @test c2 in iis + return +end + +function test_iis_interval_right() + model = Model(HiGHS.Optimizer) + set_silent(model) + @variable(model, 0 <= x <= 10) + @variable(model, 0 <= y <= 20) + @constraint(model, c1, 0 <= x + y <= 1) + @constraint(model, c2, x + y >= 2) + @objective(model, Max, x + y) + optimize!(model) + data = ModelAnalyzer.analyze(ModelAnalyzer.Infeasibility.Analyzer(), model) + list = ModelAnalyzer.list_of_issue_types(data) + @test length(list) == 0 + data = ModelAnalyzer.analyze( + ModelAnalyzer.Infeasibility.Analyzer(), + model, + optimizer = HiGHS.Optimizer, + ) + list = ModelAnalyzer.list_of_issue_types(data) + @test length(list) == 1 + ret = ModelAnalyzer.list_of_issues(data, list[1]) + @test length(ret) == 1 + @test length(ret[].constraint) == 2 + @test Set([ret[].constraint[1], ret[].constraint[2]]) == + Set(JuMP.index.([c2, c1])) + iis = ModelAnalyzer.constraints(ret[], model) + @test length(iis) == 2 + @test Set(iis) == Set([c2, c1]) + return +end + +function test_iis_interval_left() + model = Model(HiGHS.Optimizer) + set_silent(model) + @variable(model, 0 <= x <= 10) + @variable(model, 0 <= y <= 20) + @constraint(model, c1, x + y <= 1) + @constraint(model, c2, 2 <= x + y <= 5) + @objective(model, Max, x + y) + optimize!(model) + data = ModelAnalyzer.analyze(ModelAnalyzer.Infeasibility.Analyzer(), model) + list = ModelAnalyzer.list_of_issue_types(data) + @test length(list) == 0 + data = ModelAnalyzer.analyze( + ModelAnalyzer.Infeasibility.Analyzer(), + model, + optimizer = HiGHS.Optimizer, + ) + list = ModelAnalyzer.list_of_issue_types(data) + @test length(list) == 1 + ret = ModelAnalyzer.list_of_issues(data, list[1]) + @test length(ret) == 1 + @test length(ret[].constraint) == 2 + @test Set([ret[].constraint[1], ret[].constraint[2]]) == + Set(JuMP.index.([c2, c1])) + iis = ModelAnalyzer.constraints(ret[], model) + @test length(iis) == 2 + @test Set(iis) == Set([c2, c1]) + iis = ModelAnalyzer.constraints(ret[], JuMP.backend(model)) + @test length(iis) == 2 + @test Set(iis) == Set(JuMP.index.([c2, c1])) + return +end + +function test_iis_spare() + model = Model(HiGHS.Optimizer) + set_silent(model) + @variable(model, 0 <= x <= 10) + @variable(model, 0 <= y <= 20) + @variable(model, 0 <= z <= 20) + @constraint(model, c0, 2z <= 1) + @constraint(model, c00, 3z <= 1) + @constraint(model, c1, x + y <= 1) + @constraint(model, c2, x + y >= 2) + @objective(model, Max, x + y) + optimize!(model) + data = ModelAnalyzer.analyze(ModelAnalyzer.Infeasibility.Analyzer(), model) + list = ModelAnalyzer.list_of_issue_types(data) + @test length(list) == 0 + data = ModelAnalyzer.analyze( + ModelAnalyzer.Infeasibility.Analyzer(), + model, + optimizer = HiGHS.Optimizer, + ) + list = ModelAnalyzer.list_of_issue_types(data) + @test length(list) == 1 + ret = ModelAnalyzer.list_of_issues(data, list[1]) + @test length(ret) == 1 + @test length(ret[].constraint) == 2 + @test Set([ret[].constraint[1], ret[].constraint[2]]) == + Set(JuMP.index.([c2, c1])) + iis = ModelAnalyzer.constraints(ret[], model) + @test length(iis) == 2 + @test Set(iis) == Set([c2, c1]) + return +end +=# +end # module + +TestIIS.runtests() diff --git a/test/runtests.jl b/test/runtests.jl index 12287cd..b2d3058 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ -# Copyright (c) 2025: Joaquim Garcia, Oscar Dowson and contributors +# Copyright (c) 2025: Joaquim Dias Garcia, Oscar Dowson and contributors # # Use of this source code is governed by an MIT-style license that can be found # in the LICENSE.md file or at https://opensource.org/licenses/MIT. From 31cccb7721f85cc2f1c7524a09dd199f9a4c1ace Mon Sep 17 00:00:00 2001 From: joaquimg Date: Sat, 7 Jun 2025 13:27:13 -0300 Subject: [PATCH 2/2] finish (original) tests --- test/iis.jl | 317 +++++++++++++++++++--------------------------------- 1 file changed, 116 insertions(+), 201 deletions(-) diff --git a/test/iis.jl b/test/iis.jl index 9aaf233..90b2dbd 100644 --- a/test/iis.jl +++ b/test/iis.jl @@ -80,10 +80,8 @@ function test_binary_lower() @objective(model, Max, x + y) data = MOCS.compute_conflicts(JuMP.backend(model)) @test length(data) == 1 - @test data[].constraints == [ - JuMP.index(BinaryRef(x)), - JuMP.index(LowerBoundRef(x)), - ] + @test data[].constraints == + [JuMP.index(BinaryRef(x)), JuMP.index(LowerBoundRef(x))] @test data[].irreducible @test data[].metadata == MOCS.IntegralityData(1.5, Inf, MOI.ZeroOne()) return @@ -97,10 +95,8 @@ function test_binary_upper() @objective(model, Max, x + y) data = MOCS.compute_conflicts(JuMP.backend(model)) @test length(data) == 1 - @test data[].constraints == [ - JuMP.index(BinaryRef(x)), - JuMP.index(UpperBoundRef(x)), - ] + @test data[].constraints == + [JuMP.index(BinaryRef(x)), JuMP.index(UpperBoundRef(x))] @test data[].irreducible @test data[].metadata == MOCS.IntegralityData(-Inf, -1.8, MOI.ZeroOne()) return @@ -118,15 +114,19 @@ function test_range() @objective(model, Max, x + y) data = MOCS.compute_conflicts(JuMP.backend(model)) @test length(data) == 1 - @test _isequal_unordered(data[].constraints, [ - JuMP.index(c), - JuMP.index(UpperBoundRef(x)), - JuMP.index(LowerBoundRef(x)), - JuMP.index(UpperBoundRef(y)), - JuMP.index(LowerBoundRef(y)), - ]) + @test _isequal_unordered( + data[].constraints, + [ + JuMP.index(c), + JuMP.index(UpperBoundRef(x)), + JuMP.index(LowerBoundRef(x)), + JuMP.index(UpperBoundRef(y)), + JuMP.index(LowerBoundRef(y)), + ], + ) @test data[].irreducible - @test data[].metadata == MOCS.RangeData(11.0, 22.0, MOI.LessThan{Float64}(1.0)) + @test data[].metadata == + MOCS.RangeData(11.0, 22.0, MOI.LessThan{Float64}(1.0)) return end @@ -139,15 +139,19 @@ function test_range_neg() # data = MOCS.compute_conflicts(JuMP.backend(model)) @test length(data) == 1 - @test _isequal_unordered(data[].constraints, [ - JuMP.index(c), - JuMP.index(UpperBoundRef(x)), - JuMP.index(LowerBoundRef(x)), - JuMP.index(UpperBoundRef(y)), - JuMP.index(LowerBoundRef(y)), - ]) + @test _isequal_unordered( + data[].constraints, + [ + JuMP.index(c), + JuMP.index(UpperBoundRef(x)), + JuMP.index(LowerBoundRef(x)), + JuMP.index(UpperBoundRef(y)), + JuMP.index(LowerBoundRef(y)), + ], + ) @test data[].irreducible - @test data[].metadata == MOCS.RangeData(11.0, 22.0, MOI.LessThan{Float64}(1.0)) + @test data[].metadata == + MOCS.RangeData(11.0, 22.0, MOI.LessThan{Float64}(1.0)) return end @@ -159,11 +163,10 @@ function test_range_equalto() @objective(model, Max, x + y) data = MOCS.compute_conflicts(JuMP.backend(model)) @test length(data) == 1 - @test _isequal_unordered(data[].constraints, [ - JuMP.index(c), - JuMP.index(FixRef(x)), - JuMP.index(FixRef(y)), - ]) + @test _isequal_unordered( + data[].constraints, + [JuMP.index(c), JuMP.index(FixRef(x)), JuMP.index(FixRef(y))], + ) @test data[].irreducible @test data[].metadata == MOCS.RangeData(3.0, 3.0, MOI.EqualTo{Float64}(1.0)) return @@ -177,11 +180,10 @@ function test_range_equalto_2() @objective(model, Max, x + y) data = MOCS.compute_conflicts(JuMP.backend(model)) @test length(data) == 1 - @test _isequal_unordered(data[].constraints, [ - JuMP.index(c), - JuMP.index(FixRef(x)), - JuMP.index(FixRef(y)), - ]) + @test _isequal_unordered( + data[].constraints, + [JuMP.index(c), JuMP.index(FixRef(x)), JuMP.index(FixRef(y))], + ) @test data[].irreducible @test data[].metadata == MOCS.RangeData(7.0, 7.0, MOI.EqualTo{Float64}(1.0)) return @@ -195,15 +197,19 @@ function test_range_greaterthan() @objective(model, Max, x + y) data = MOCS.compute_conflicts(JuMP.backend(model)) @test length(data) == 1 - @test _isequal_unordered(data[].constraints, [ - JuMP.index(c), - JuMP.index(UpperBoundRef(x)), - JuMP.index(LowerBoundRef(x)), - JuMP.index(UpperBoundRef(y)), - JuMP.index(LowerBoundRef(y)), - ]) + @test _isequal_unordered( + data[].constraints, + [ + JuMP.index(c), + JuMP.index(UpperBoundRef(x)), + JuMP.index(LowerBoundRef(x)), + JuMP.index(UpperBoundRef(y)), + JuMP.index(LowerBoundRef(y)), + ], + ) @test data[].irreducible - @test data[].metadata == MOCS.RangeData(11.0, 22.0, MOI.GreaterThan{Float64}(100.0)) + @test data[].metadata == + MOCS.RangeData(11.0, 22.0, MOI.GreaterThan{Float64}(100.0)) return end @@ -215,15 +221,19 @@ function test_range_equalto_3() @objective(model, Max, x + y) data = MOCS.compute_conflicts(JuMP.backend(model)) @test length(data) == 1 - @test _isequal_unordered(data[].constraints, [ - JuMP.index(c), - JuMP.index(UpperBoundRef(x)), - JuMP.index(LowerBoundRef(x)), - JuMP.index(UpperBoundRef(y)), - JuMP.index(LowerBoundRef(y)), - ]) + @test _isequal_unordered( + data[].constraints, + [ + JuMP.index(c), + JuMP.index(UpperBoundRef(x)), + JuMP.index(LowerBoundRef(x)), + JuMP.index(UpperBoundRef(y)), + JuMP.index(LowerBoundRef(y)), + ], + ) @test data[].irreducible - @test data[].metadata == MOCS.RangeData(11.0, 22.0, MOI.EqualTo{Float64}(100.0)) + @test data[].metadata == + MOCS.RangeData(11.0, 22.0, MOI.EqualTo{Float64}(100.0)) return end @@ -235,12 +245,12 @@ function test_interval() @constraint(model, c1, x + y <= 1) @objective(model, Max, x + y) optimize!(model) - data = MOCS.compute_conflicts(JuMP.backend(model), optimizer = HiGHS.Optimizer) + data = + MOCS.compute_conflicts(JuMP.backend(model), optimizer = HiGHS.Optimizer) @test length(data) == 0 # TODO check status return end -#= function test_iis_feasible() model = Model(HiGHS.Optimizer) @@ -250,17 +260,11 @@ function test_iis_feasible() @constraint(model, c1, x + y <= 1) @objective(model, Max, x + y) optimize!(model) - data = ModelAnalyzer.analyze( - ModelAnalyzer.Infeasibility.Analyzer(), - model, - optimizer = HiGHS.Optimizer, - ) - list = ModelAnalyzer.list_of_issue_types(data) - @test length(list) == 0 + data = MOCS.compute_conflicts(JuMP.backend(model)) + @test length(data) == 0 + return end - - function test_iis() model = Model(HiGHS.Optimizer) set_silent(model) @@ -270,59 +274,17 @@ function test_iis() @constraint(model, c2, x + y >= 2) @objective(model, Max, x + y) optimize!(model) - data = ModelAnalyzer.analyze(ModelAnalyzer.Infeasibility.Analyzer(), model) - list = ModelAnalyzer.list_of_issue_types(data) - @test length(list) == 0 - data = ModelAnalyzer.analyze( - ModelAnalyzer.Infeasibility.Analyzer(), - model, - optimizer = HiGHS.Optimizer, - ) - list = ModelAnalyzer.list_of_issue_types(data) - @test length(list) == 1 - ret = ModelAnalyzer.list_of_issues(data, list[1]) - @test length(ret) == 1 - @test length(ret[].constraint) == 2 - @test Set([ret[].constraint[1], ret[].constraint[2]]) == - Set(JuMP.index.([c2, c1])) - iis = ModelAnalyzer.constraints(ret[], model) - @test length(iis) == 2 - @test Set(iis) == Set([c2, c1]) - # - buf = IOBuffer() - ModelAnalyzer.summarize( - buf, - ModelAnalyzer.Infeasibility.IrreducibleInfeasibleSubset, - ) - str = String(take!(buf)) - @test startswith(str, "# `IrreducibleInfeasibleSubset`") - ModelAnalyzer.summarize( - buf, - ModelAnalyzer.Infeasibility.IrreducibleInfeasibleSubset, - verbose = false, + data = MOCS.compute_conflicts(JuMP.backend(model)) + @test length(data) == 0 + data = + MOCS.compute_conflicts(JuMP.backend(model), optimizer = HiGHS.Optimizer) + @test length(data) == 1 + @test data[].irreducible + @test data[].metadata == MOCS.NoData() + @test _isequal_unordered( + data[].constraints, + [JuMP.index(c2), JuMP.index(c1)], ) - str = String(take!(buf)) - @test str == "# IrreducibleInfeasibleSubset" - # - ModelAnalyzer.summarize(buf, ret[1], verbose = true) - str = String(take!(buf)) - @test startswith(str, "Irreducible Infeasible Subset: ") - @test contains(str, ", ") - ModelAnalyzer.summarize(buf, ret[1], verbose = false) - str = String(take!(buf)) - @test startswith(str, "IIS: ") - @test contains(str, ", ") - - buf = IOBuffer() - Base.show(buf, data) - str = String(take!(buf)) - @test startswith(str, "Infeasibility analysis found 1 issues") - - ModelAnalyzer.summarize(buf, data, verbose = true) - str = String(take!(buf)) - @test startswith(str, "## Infeasibility Analysis\n\n") - ModelAnalyzer.summarize(buf, data, verbose = false) - ModelAnalyzer.summarize(buf, data, verbose = true) return end @@ -335,21 +297,15 @@ function test_iis_free_var() @constraint(model, c2, x + y >= 2) @objective(model, Max, -2x + y) optimize!(model) - data = ModelAnalyzer.analyze( - ModelAnalyzer.Infeasibility.Analyzer(), - model, - optimizer = HiGHS.Optimizer, + data = + MOCS.compute_conflicts(JuMP.backend(model), optimizer = HiGHS.Optimizer) + @test length(data) == 1 + @test data[].irreducible + @test data[].metadata == MOCS.NoData() + @test _isequal_unordered( + data[].constraints, + [JuMP.index(c2), JuMP.index(c1)], ) - list = ModelAnalyzer.list_of_issue_types(data) - @test length(list) == 1 - ret = ModelAnalyzer.list_of_issues(data, list[1]) - @test length(ret) == 1 - @test length(ret[].constraint) == 2 - @test Set([ret[].constraint[1], ret[].constraint[2]]) == - Set(JuMP.index.([c2, c1])) - iis = ModelAnalyzer.constraints(ret[], model) - @test length(iis) == 2 - @test Set(iis) == Set([c2, c1]) return end @@ -363,26 +319,15 @@ function test_iis_multiple() @constraint(model, c2, x + y >= 2) @objective(model, Max, x + y) optimize!(model) - data = ModelAnalyzer.analyze(ModelAnalyzer.Infeasibility.Analyzer(), model) - list = ModelAnalyzer.list_of_issue_types(data) - @test length(list) == 0 - data = ModelAnalyzer.analyze( - ModelAnalyzer.Infeasibility.Analyzer(), - model, - optimizer = HiGHS.Optimizer, - ) - list = ModelAnalyzer.list_of_issue_types(data) - @test length(list) == 1 - ret = ModelAnalyzer.list_of_issues(data, list[1]) - @test length(ret) == 1 - @test length(ret[].constraint) == 2 - @test JuMP.index(c2) in Set([ret[].constraint[1], ret[].constraint[2]]) - @test Set([ret[].constraint[1], ret[].constraint[2]]) ⊆ - Set(JuMP.index.([c3, c2, c1])) - iis = ModelAnalyzer.constraints(ret[], model) + data = + MOCS.compute_conflicts(JuMP.backend(model), optimizer = HiGHS.Optimizer) + @test length(data) == 1 + @test data[].irreducible + @test data[].metadata == MOCS.NoData() + iis = data[].constraints @test length(iis) == 2 - @test Set(iis) ⊆ Set([c3, c2, c1]) - @test c2 in iis + @test Set(iis) ⊆ Set([JuMP.index(c3), JuMP.index(c2), JuMP.index(c1)]) + @test JuMP.index(c2) in Set(iis) return end @@ -395,24 +340,15 @@ function test_iis_interval_right() @constraint(model, c2, x + y >= 2) @objective(model, Max, x + y) optimize!(model) - data = ModelAnalyzer.analyze(ModelAnalyzer.Infeasibility.Analyzer(), model) - list = ModelAnalyzer.list_of_issue_types(data) - @test length(list) == 0 - data = ModelAnalyzer.analyze( - ModelAnalyzer.Infeasibility.Analyzer(), - model, - optimizer = HiGHS.Optimizer, + data = + MOCS.compute_conflicts(JuMP.backend(model), optimizer = HiGHS.Optimizer) + @test length(data) == 1 + @test data[].irreducible + @test data[].metadata == MOCS.NoData() + @test _isequal_unordered( + data[].constraints, + [JuMP.index(c2), JuMP.index(c1)], ) - list = ModelAnalyzer.list_of_issue_types(data) - @test length(list) == 1 - ret = ModelAnalyzer.list_of_issues(data, list[1]) - @test length(ret) == 1 - @test length(ret[].constraint) == 2 - @test Set([ret[].constraint[1], ret[].constraint[2]]) == - Set(JuMP.index.([c2, c1])) - iis = ModelAnalyzer.constraints(ret[], model) - @test length(iis) == 2 - @test Set(iis) == Set([c2, c1]) return end @@ -425,27 +361,15 @@ function test_iis_interval_left() @constraint(model, c2, 2 <= x + y <= 5) @objective(model, Max, x + y) optimize!(model) - data = ModelAnalyzer.analyze(ModelAnalyzer.Infeasibility.Analyzer(), model) - list = ModelAnalyzer.list_of_issue_types(data) - @test length(list) == 0 - data = ModelAnalyzer.analyze( - ModelAnalyzer.Infeasibility.Analyzer(), - model, - optimizer = HiGHS.Optimizer, + data = + MOCS.compute_conflicts(JuMP.backend(model), optimizer = HiGHS.Optimizer) + @test length(data) == 1 + @test data[].irreducible + @test data[].metadata == MOCS.NoData() + @test _isequal_unordered( + data[].constraints, + [JuMP.index(c2), JuMP.index(c1)], ) - list = ModelAnalyzer.list_of_issue_types(data) - @test length(list) == 1 - ret = ModelAnalyzer.list_of_issues(data, list[1]) - @test length(ret) == 1 - @test length(ret[].constraint) == 2 - @test Set([ret[].constraint[1], ret[].constraint[2]]) == - Set(JuMP.index.([c2, c1])) - iis = ModelAnalyzer.constraints(ret[], model) - @test length(iis) == 2 - @test Set(iis) == Set([c2, c1]) - iis = ModelAnalyzer.constraints(ret[], JuMP.backend(model)) - @test length(iis) == 2 - @test Set(iis) == Set(JuMP.index.([c2, c1])) return end @@ -461,27 +385,18 @@ function test_iis_spare() @constraint(model, c2, x + y >= 2) @objective(model, Max, x + y) optimize!(model) - data = ModelAnalyzer.analyze(ModelAnalyzer.Infeasibility.Analyzer(), model) - list = ModelAnalyzer.list_of_issue_types(data) - @test length(list) == 0 - data = ModelAnalyzer.analyze( - ModelAnalyzer.Infeasibility.Analyzer(), - model, - optimizer = HiGHS.Optimizer, + data = + MOCS.compute_conflicts(JuMP.backend(model), optimizer = HiGHS.Optimizer) + @test length(data) == 1 + @test data[].irreducible + @test data[].metadata == MOCS.NoData() + @test _isequal_unordered( + data[].constraints, + [JuMP.index(c2), JuMP.index(c1)], ) - list = ModelAnalyzer.list_of_issue_types(data) - @test length(list) == 1 - ret = ModelAnalyzer.list_of_issues(data, list[1]) - @test length(ret) == 1 - @test length(ret[].constraint) == 2 - @test Set([ret[].constraint[1], ret[].constraint[2]]) == - Set(JuMP.index.([c2, c1])) - iis = ModelAnalyzer.constraints(ret[], model) - @test length(iis) == 2 - @test Set(iis) == Set([c2, c1]) return end -=# + end # module TestIIS.runtests()