Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Infeasibility/Infeasibility.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

module Infeasibility

import MathOptInterface as MOI
import MathOptAnalyzer
import MathOptIIS as MOIIS
import MathOptInterface as MOI

include("structs.jl")
include("analyze.jl")
Expand Down
123 changes: 69 additions & 54 deletions src/Infeasibility/analyze.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,71 +3,86 @@
# 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.

import MathOptIIS as MOIIS
function _add_result(out::Data, model, iis, meta::MOIIS.BoundsData)
@assert length(iis.constraints) == 2
err = InfeasibleBounds{Float64}(
MOI.get(model, MOI.ConstraintFunction(), iis.constraints[1]),
meta.lower_bound,
meta.upper_bound,
)
push!(out.infeasible_bounds, err)
return
end

function _add_result(out::Data, model, iis, meta::MOIIS.IntegralityData)
@assert length(iis.constraints) >= 2
err = InfeasibleIntegrality{Float64}(
MOI.get(model, MOI.ConstraintFunction(), iis.constraints[1]),
meta.lower_bound,
meta.upper_bound,
meta.set,
)
push!(out.infeasible_integrality, err)
return
end

function _add_result(out::Data, model, iis, meta::MOIIS.RangeData)
@assert length(iis.constraints) >= 1
for con in iis.constraints
if con isa MOI.ConstraintIndex{MOI.VariableIndex}
continue
end
err = InfeasibleConstraintRange{Float64}(
con,
meta.lower_bound,
meta.upper_bound,
meta.set,
)
push!(out.constraint_range, err)
break
end
return
end

function _add_result(out::Data, model, iis, meta)
push!(out.iis, IrreducibleInfeasibleSubset(iis.constraints))
return
end

function _instantiate_with_modify(optimizer, ::Type{T}) where {T}
model = MOI.instantiate(optimizer)
if !MOI.supports_incremental_interface(model)
# Don't use `default_cache` for the cache because, for example, SCS's
# default cache doesn't support modifying coefficients of the constraint
# matrix. JuMP uses the default cache with SCS because it has an outer
# layer of caching; we don't have that here, so we can't use the
# default.
#
# We could revert to using the default cache if we fix this in MOI.
cache = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}())
model = MOI.Utilities.CachingOptimizer(cache, model)
end
return MOI.Bridges.full_bridge_optimizer(model, T)
end

function MathOptAnalyzer.analyze(
::Analyzer,
model::MOI.ModelLike;
optimizer = nothing,
)
out = Data()
T = Float64

solver = MOIIS.Optimizer()
MOI.set(solver, MOIIS.InfeasibleModel(), model)

if optimizer !== nothing
MOI.set(solver, MOIIS.InnerOptimizer(), optimizer)
MOI.set(
solver,
MOIIS.InnerOptimizer(),
() -> _instantiate_with_modify(optimizer, Float64),
)
end

MOI.compute_conflict!(solver)

data = solver.results

for iis in data
meta = iis.metadata
if typeof(meta) <: MOIIS.BoundsData
constraints = iis.constraints
@assert length(constraints) == 2
func = MOI.get(model, MOI.ConstraintFunction(), constraints[1])
push!(
out.infeasible_bounds,
InfeasibleBounds{T}(func, meta.lower_bound, meta.upper_bound),
)
elseif typeof(meta) <: MOIIS.IntegralityData
constraints = iis.constraints
@assert length(constraints) >= 2
func = MOI.get(model, MOI.ConstraintFunction(), constraints[1])
push!(
out.infeasible_integrality,
InfeasibleIntegrality{T}(
func,
meta.lower_bound,
meta.upper_bound,
meta.set,
),
)
elseif typeof(meta) <: MOIIS.RangeData
constraints = iis.constraints
@assert length(constraints) >= 1
# main_con = nothing
for con in constraints
if !(typeof(con) <: MOI.ConstraintIndex{MOI.VariableIndex})
push!(
out.constraint_range,
InfeasibleConstraintRange{T}(
con,
meta.lower_bound,
meta.upper_bound,
meta.set,
),
)
break
end
end
else
push!(out.iis, IrreducibleInfeasibleSubset(iis.constraints))
end
out = Data()
for iis in solver.results
_add_result(out, model, iis, iis.metadata)
end
return out
end
8 changes: 8 additions & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
[deps]
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
MathOptAnalyzer = "d1179b25-476b-425c-b826-c7787f0fff83"
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
SCS = "c946c3f1-0d1f-5ce8-9dea-7daa1f7e2d13"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
HiGHS = "1"
JuMP = "1"
MathOptInterface = "1"
SCS = "1"
33 changes: 33 additions & 0 deletions test/test_Infeasibility.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ module TestInfeasibility

using JuMP
using Test

import HiGHS
import MathOptAnalyzer
import SCS

function runtests()
for name in names(@__MODULE__; all = true)
Expand Down Expand Up @@ -591,6 +593,37 @@ function test_iis_spare()
return
end

function test_iis_bridges()
model = Model(SCS.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)
@test termination_status(model) == INFEASIBLE
data = MathOptAnalyzer.analyze(
MathOptAnalyzer.Infeasibility.Analyzer(),
model,
optimizer = SCS.Optimizer,
)
list = MathOptAnalyzer.list_of_issue_types(data)
@test length(list) == 1
ret = MathOptAnalyzer.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 = MathOptAnalyzer.constraints(ret[], model)
@test length(iis) == 2
@test Set(iis) == Set([c2, c1])
return
end

end # module TestInfeasibility

TestInfeasibility.runtests()
Loading