Skip to content

Commit 48e5fae

Browse files
authored
Use bridges when passing the optimizer to MathOptIIS (#65)
1 parent 538c2a5 commit 48e5fae

4 files changed

Lines changed: 112 additions & 55 deletions

File tree

src/Infeasibility/Infeasibility.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55

66
module Infeasibility
77

8-
import MathOptInterface as MOI
98
import MathOptAnalyzer
9+
import MathOptIIS as MOIIS
10+
import MathOptInterface as MOI
1011

1112
include("structs.jl")
1213
include("analyze.jl")

src/Infeasibility/analyze.jl

Lines changed: 69 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,71 +3,86 @@
33
# Use of this source code is governed by an MIT-style license that can be found
44
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.
55

6-
import MathOptIIS as MOIIS
6+
function _add_result(out::Data, model, iis, meta::MOIIS.BoundsData)
7+
@assert length(iis.constraints) == 2
8+
err = InfeasibleBounds{Float64}(
9+
MOI.get(model, MOI.ConstraintFunction(), iis.constraints[1]),
10+
meta.lower_bound,
11+
meta.upper_bound,
12+
)
13+
push!(out.infeasible_bounds, err)
14+
return
15+
end
16+
17+
function _add_result(out::Data, model, iis, meta::MOIIS.IntegralityData)
18+
@assert length(iis.constraints) >= 2
19+
err = InfeasibleIntegrality{Float64}(
20+
MOI.get(model, MOI.ConstraintFunction(), iis.constraints[1]),
21+
meta.lower_bound,
22+
meta.upper_bound,
23+
meta.set,
24+
)
25+
push!(out.infeasible_integrality, err)
26+
return
27+
end
28+
29+
function _add_result(out::Data, model, iis, meta::MOIIS.RangeData)
30+
@assert length(iis.constraints) >= 1
31+
for con in iis.constraints
32+
if con isa MOI.ConstraintIndex{MOI.VariableIndex}
33+
continue
34+
end
35+
err = InfeasibleConstraintRange{Float64}(
36+
con,
37+
meta.lower_bound,
38+
meta.upper_bound,
39+
meta.set,
40+
)
41+
push!(out.constraint_range, err)
42+
break
43+
end
44+
return
45+
end
46+
47+
function _add_result(out::Data, model, iis, meta)
48+
push!(out.iis, IrreducibleInfeasibleSubset(iis.constraints))
49+
return
50+
end
51+
52+
function _instantiate_with_modify(optimizer, ::Type{T}) where {T}
53+
model = MOI.instantiate(optimizer)
54+
if !MOI.supports_incremental_interface(model)
55+
# Don't use `default_cache` for the cache because, for example, SCS's
56+
# default cache doesn't support modifying coefficients of the constraint
57+
# matrix. JuMP uses the default cache with SCS because it has an outer
58+
# layer of caching; we don't have that here, so we can't use the
59+
# default.
60+
#
61+
# We could revert to using the default cache if we fix this in MOI.
62+
cache = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}())
63+
model = MOI.Utilities.CachingOptimizer(cache, model)
64+
end
65+
return MOI.Bridges.full_bridge_optimizer(model, T)
66+
end
767

868
function MathOptAnalyzer.analyze(
969
::Analyzer,
1070
model::MOI.ModelLike;
1171
optimizer = nothing,
1272
)
13-
out = Data()
14-
T = Float64
15-
1673
solver = MOIIS.Optimizer()
1774
MOI.set(solver, MOIIS.InfeasibleModel(), model)
18-
1975
if optimizer !== nothing
20-
MOI.set(solver, MOIIS.InnerOptimizer(), optimizer)
76+
MOI.set(
77+
solver,
78+
MOIIS.InnerOptimizer(),
79+
() -> _instantiate_with_modify(optimizer, Float64),
80+
)
2181
end
22-
2382
MOI.compute_conflict!(solver)
24-
25-
data = solver.results
26-
27-
for iis in data
28-
meta = iis.metadata
29-
if typeof(meta) <: MOIIS.BoundsData
30-
constraints = iis.constraints
31-
@assert length(constraints) == 2
32-
func = MOI.get(model, MOI.ConstraintFunction(), constraints[1])
33-
push!(
34-
out.infeasible_bounds,
35-
InfeasibleBounds{T}(func, meta.lower_bound, meta.upper_bound),
36-
)
37-
elseif typeof(meta) <: MOIIS.IntegralityData
38-
constraints = iis.constraints
39-
@assert length(constraints) >= 2
40-
func = MOI.get(model, MOI.ConstraintFunction(), constraints[1])
41-
push!(
42-
out.infeasible_integrality,
43-
InfeasibleIntegrality{T}(
44-
func,
45-
meta.lower_bound,
46-
meta.upper_bound,
47-
meta.set,
48-
),
49-
)
50-
elseif typeof(meta) <: MOIIS.RangeData
51-
constraints = iis.constraints
52-
@assert length(constraints) >= 1
53-
# main_con = nothing
54-
for con in constraints
55-
if !(typeof(con) <: MOI.ConstraintIndex{MOI.VariableIndex})
56-
push!(
57-
out.constraint_range,
58-
InfeasibleConstraintRange{T}(
59-
con,
60-
meta.lower_bound,
61-
meta.upper_bound,
62-
meta.set,
63-
),
64-
)
65-
break
66-
end
67-
end
68-
else
69-
push!(out.iis, IrreducibleInfeasibleSubset(iis.constraints))
70-
end
83+
out = Data()
84+
for iis in solver.results
85+
_add_result(out, model, iis, iis.metadata)
7186
end
7287
return out
7388
end

test/Project.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
[deps]
22
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
33
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
4+
MathOptAnalyzer = "d1179b25-476b-425c-b826-c7787f0fff83"
45
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
6+
SCS = "c946c3f1-0d1f-5ce8-9dea-7daa1f7e2d13"
57
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
8+
9+
[compat]
10+
HiGHS = "1"
11+
JuMP = "1"
12+
MathOptInterface = "1"
13+
SCS = "1"

test/test_Infeasibility.jl

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ module TestInfeasibility
77

88
using JuMP
99
using Test
10+
1011
import HiGHS
1112
import MathOptAnalyzer
13+
import SCS
1214

1315
function runtests()
1416
for name in names(@__MODULE__; all = true)
@@ -591,6 +593,37 @@ function test_iis_spare()
591593
return
592594
end
593595

596+
function test_iis_bridges()
597+
model = Model(SCS.Optimizer)
598+
set_silent(model)
599+
@variable(model, 0 <= x <= 10)
600+
@variable(model, 0 <= y <= 20)
601+
@variable(model, 0 <= z <= 20)
602+
@constraint(model, c0, 2z <= 1)
603+
@constraint(model, c00, 3z <= 1)
604+
@constraint(model, c1, x + y <= 1)
605+
@constraint(model, c2, x + y >= 2)
606+
@objective(model, Max, x + y)
607+
optimize!(model)
608+
@test termination_status(model) == INFEASIBLE
609+
data = MathOptAnalyzer.analyze(
610+
MathOptAnalyzer.Infeasibility.Analyzer(),
611+
model,
612+
optimizer = SCS.Optimizer,
613+
)
614+
list = MathOptAnalyzer.list_of_issue_types(data)
615+
@test length(list) == 1
616+
ret = MathOptAnalyzer.list_of_issues(data, list[1])
617+
@test length(ret) == 1
618+
@test length(ret[].constraint) == 2
619+
@test Set([ret[].constraint[1], ret[].constraint[2]]) ==
620+
Set(JuMP.index.([c2, c1]))
621+
iis = MathOptAnalyzer.constraints(ret[], model)
622+
@test length(iis) == 2
623+
@test Set(iis) == Set([c2, c1])
624+
return
625+
end
626+
594627
end # module TestInfeasibility
595628

596629
TestInfeasibility.runtests()

0 commit comments

Comments
 (0)