Skip to content

Commit a6b1462

Browse files
authored
Merge pull request #276 from scipopt/conflict
Conflict report
2 parents 68d1366 + 4682a7b commit a6b1462

File tree

3 files changed

+157
-0
lines changed

3 files changed

+157
-0
lines changed

src/MOI_wrapper.jl

+4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
3939
moi_heuristic::Any # ::Union{HeuristicCb, Nothing}
4040
objective_sense::Union{Nothing,MOI.OptimizationSense}
4141
objective_function_set::Bool
42+
conflict_status::MOI.ConflictStatusCode
4243

4344
function Optimizer(; kwargs...)
4445
scip = Ref{Ptr{SCIP_}}(C_NULL)
@@ -73,6 +74,7 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
7374
nothing,
7475
nothing,
7576
false,
77+
MOI.COMPUTE_CONFLICT_NOT_CALLED,
7678
)
7779
finalizer(free_scip, o)
7880

@@ -281,6 +283,7 @@ function MOI.empty!(o::Optimizer)
281283
end
282284
o.objective_sense = nothing
283285
o.objective_function_set = false
286+
o.conflict_status = MOI.COMPUTE_CONFLICT_NOT_CALLED
284287
o.moi_separator = nothing
285288
o.moi_heuristic = nothing
286289
return nothing
@@ -432,3 +435,4 @@ include(joinpath("MOI_wrapper", "results.jl"))
432435
include(joinpath("MOI_wrapper", "conshdlr.jl"))
433436
include(joinpath("MOI_wrapper", "sepa.jl"))
434437
include(joinpath("MOI_wrapper", "heuristic.jl"))
438+
include(joinpath("MOI_wrapper", "conflict.jl"))

src/MOI_wrapper/conflict.jl

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
2+
"""
3+
Computes a set of constraints which could not be satisfied when trying to minimize the total violations.
4+
5+
Given the problem:
6+
```
7+
(P) min_x c^⊤ x
8+
s.t. F(x) ∈ S_i ∀ i in 1…m
9+
```
10+
11+
the analysis uses a feasibility relaxation based on slack variables and indicator constraints:
12+
```
13+
(M) min_{x, z} ∑_i z_i
14+
s.t. z_i = 0 → F(x) ∈ S_i ∀ i in 1…m
15+
z ∈ {0,1}ᵐ
16+
```
17+
18+
If (P) is infeasible, (M) has an optimal value above 1.
19+
All constraints with `z = 1` had to be violated.
20+
"""
21+
function compute_minimum_unsatisfied_constraints!(o::Optimizer)
22+
if o.conflict_status != MOI.COMPUTE_CONFLICT_NOT_CALLED
23+
error("Conflict computation is destructive for the model and cannot be called twice.")
24+
end
25+
# free the transformed problem first
26+
if LibSCIP.SCIPgetStage(o) != LibSCIP.SCIP_STAGE_PROBLEM
27+
@SCIP_CALL LibSCIP.SCIPfreeTransform(o)
28+
end
29+
# first transform all variable bound constraints to constraint bounds
30+
for (F, S) in MOI.get(o, MOI.ListOfConstraintTypesPresent())
31+
sname = replace(string(S), "MathOptInterface." => "", "{Float64}" => "")
32+
if Tuple{F, S} <: Tuple{VI, BOUNDS}
33+
for (idx, c_index) in enumerate(MOI.get(o, MOI.ListOfConstraintIndices{F,S}()))
34+
s = MOI.get(o, MOI.ConstraintSet(), c_index)
35+
MOI.delete(o, c_index)
36+
vi = MOI.VariableIndex(c_index.value)
37+
ci_new = MOI.add_constraint(o, 1.0 * vi, s)
38+
MOI.set(o, MOI.ConstraintName(), ci_new, "varcons_$(c_index.value)_$sname")
39+
end
40+
end
41+
end
42+
# we need names for all constraints
43+
for (F, S) in MOI.get(o, MOI.ListOfConstraintTypesPresent())
44+
if F === VI
45+
continue
46+
end
47+
for (idx, c_index) in enumerate(MOI.get(o, MOI.ListOfConstraintIndices{F,S}()))
48+
if MOI.get(o, MOI.ConstraintName(), c_index) == ""
49+
cons_ptr = cons(o, c_index)
50+
handler_name = unsafe_string(SCIPconshdlrGetName(SCIPconsGetHdlr(cons_ptr)))
51+
cons_name = "$(handler_name)_moi_$(idx)"
52+
MOI.set(o, MOI.ConstraintName(), c_index, cons_name)
53+
end
54+
end
55+
end
56+
success = Ref{LibSCIP.SCIP_Bool}(SCIP.FALSE)
57+
@SCIP_CALL LibSCIP.SCIPtransformMinUC(o, success)
58+
if success[] != SCIP.TRUE
59+
error("Failed to compute the minimum unsatisfied constraints system.\nSome constraint types may not support the required transformations")
60+
end
61+
MOI.optimize!(o)
62+
st = MOI.get(o, MOI.TerminationStatus())
63+
if st != MOI.OPTIMAL
64+
error("Unexpected status $st when computing conflicts")
65+
end
66+
o.conflict_status = if MOI.get(o, MOI.ObjectiveValue()) > 0
67+
MOI.CONFLICT_FOUND
68+
else
69+
MOI.NO_CONFLICT_EXISTS
70+
end
71+
return
72+
end
73+
74+
"""
75+
Model attribute representing whether why the Minimum Unsatisfiable Constraint analysis terminated.
76+
"""
77+
struct UnsatisfiableSystemStatus <: MOI.AbstractModelAttribute end
78+
79+
attribute_value_type(::UnsatisfiableSystemStatus) = MOI.ConflictStatusCode
80+
81+
MOI.get(o::Optimizer, ::UnsatisfiableSystemStatus) = o.conflict_status
82+
83+
"""
84+
Attribute representing whether the constraint could be satisfied in the Minimum Unsatisfiable Constraint analysis.
85+
86+
Note that this is different from a constraint belonging to an Irreducible Infeasible Subsystem.
87+
"""
88+
struct ConstraintSatisfiabilityStatus <: MOI.AbstractConstraintAttribute end
89+
90+
function attribute_value_type(::ConstraintSatisfiabilityStatus)
91+
return MOI.ConflictParticipationStatusCode
92+
end
93+
94+
function MOI.get(o::Optimizer, ::ConstraintSatisfiabilityStatus, index::MOI.ConstraintIndex{MOI.VariableIndex})
95+
o.conflict_status == MOI.CONFLICT_FOUND || error("no conflict")
96+
# we cannot determine whether variable constraint (integer, binary, variable bounds) participate
97+
return MOI.MAYBE_IN_CONFLICT
98+
end
99+
100+
function MOI.get(o::Optimizer, ::ConstraintSatisfiabilityStatus, index::MOI.ConstraintIndex)
101+
o.conflict_status == MOI.CONFLICT_FOUND || error("no conflict")
102+
c_name = MOI.get(o, MOI.ConstraintName(), index)
103+
slack_name = "$(c_name)_master"
104+
ptr = SCIPfindVar(o, slack_name)
105+
if ptr == C_NULL
106+
error("No constraint name corresponds to the index $index - name $c_name")
107+
end
108+
sol = SCIPgetBestSol(o)
109+
slack_value = SCIPgetSolVal(o, sol, ptr)
110+
return slack_value > 0.5 ? MOI.IN_CONFLICT : MOI.NOT_IN_CONFLICT
111+
end

test/MOI_conflicts.jl

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using SCIP
2+
import MathOptInterface as MOI
3+
using Test
4+
5+
function base_satisfiability_problem()
6+
optimizer = SCIP.Optimizer()
7+
8+
x, y, z = MOI.add_variables(optimizer, 3)
9+
MOI.add_constraint(optimizer, x, MOI.LessThan(1.0))
10+
MOI.add_constraint(optimizer, y, MOI.LessThan(1.0))
11+
MOI.add_constraint(optimizer, z, MOI.LessThan(1.0))
12+
13+
c = MOI.add_constraint(
14+
optimizer,
15+
MOI.VectorOfVariables([x, y, z]),
16+
MOI.SOS1([1.0, 2.0, 3.0]),
17+
)
18+
return optimizer, x, y, z, c
19+
end
20+
21+
@testset "Minimum Unsatisfiable System" begin
22+
optimizer, x, y, z, c = base_satisfiability_problem()
23+
MOI.optimize!(optimizer)
24+
25+
MOI.get(optimizer, MOI.TerminationStatus()) == MOI.OPTIMAL
26+
SCIP.compute_minimum_unsatisfied_constraints!(optimizer)
27+
28+
@test MOI.get(optimizer, SCIP.UnsatisfiableSystemStatus()) == MOI.NO_CONFLICT_EXISTS
29+
30+
optimizer, x, y, z, c = base_satisfiability_problem()
31+
c2 = MOI.add_constraint(optimizer, 1.0 * x + y + z, MOI.GreaterThan(2.0))
32+
33+
MOI.set(optimizer, MOI.ConstraintName(), c2, "lincons2")
34+
35+
MOI.optimize!(optimizer)
36+
MOI.get(optimizer, MOI.TerminationStatus()) == MOI.INFEASIBLE
37+
38+
SCIP.compute_minimum_unsatisfied_constraints!(optimizer)
39+
@test MOI.get(optimizer, SCIP.UnsatisfiableSystemStatus()) == MOI.CONFLICT_FOUND
40+
41+
@test Int(MOI.get(optimizer, MOI.ConstraintSatisfiabilityStatus(), c)) + Int(MOI.get(optimizer, MOI.ConstraintSatisfiabilityStatus(), c2)) 1
42+
end

0 commit comments

Comments
 (0)