Skip to content

Commit b7a81ab

Browse files
authored
Tidy src/MOI_wrapper/conflict.jl (#339)
1 parent adcbcc1 commit b7a81ab

File tree

2 files changed

+77
-55
lines changed

2 files changed

+77
-55
lines changed

src/MOI_wrapper/conflict.jl

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,26 @@
44
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.
55

66
"""
7-
Computes a set of constraints which could not be satisfied when trying to minimize the total violations.
7+
compute_minimum_unsatisfied_constraints!(o::Optimizer)
8+
9+
Computes a set of constraints which could not be satisfied when trying to
10+
minimize the total violations.
811
912
Given the problem:
1013
```
1114
(P) min_x c^⊤ x
12-
s.t. F(x) ∈ S_i ∀ i in 1…m
15+
s.t. F(x) ∈ S_i ∀ i in 1…m
1316
```
14-
15-
the analysis uses a feasibility relaxation based on slack variables and indicator constraints:
17+
the analysis uses a feasibility relaxation based on slack variables and
18+
indicator constraints:
1619
```
1720
(M) min_{x, z} ∑_i z_i
18-
s.t. z_i = 0 → F(x) ∈ S_i ∀ i in 1…m
19-
z ∈ {0,1}ᵐ
21+
s.t. z_i = 0 → F(x) ∈ S_i ∀ i in 1…m
22+
z ∈ {0,1}ᵐ
2023
```
2124
22-
If (P) is infeasible, (M) has an optimal value above 1.
23-
All constraints with `z = 1` had to be violated.
25+
If (P) is infeasible, (M) has an optimal value above 1. All constraints with
26+
`z = 1` had to be violated.
2427
"""
2528
function compute_minimum_unsatisfied_constraints!(o::Optimizer)
2629
if o.conflict_status != MOI.COMPUTE_CONFLICT_NOT_CALLED
@@ -34,21 +37,17 @@ function compute_minimum_unsatisfied_constraints!(o::Optimizer)
3437
end
3538
# first transform all variable bound constraints to constraint bounds
3639
for (F, S) in MOI.get(o, MOI.ListOfConstraintTypesPresent())
40+
if !(F == MOI.VariableIndex && S <: BOUNDS)
41+
continue
42+
end
3743
sname = replace(string(S), "MathOptInterface." => "", "{Float64}" => "")
38-
if Tuple{F,S} <: Tuple{MOI.VariableIndex,BOUNDS}
39-
for (idx, c_index) in
40-
enumerate(MOI.get(o, MOI.ListOfConstraintIndices{F,S}()))
41-
s = MOI.get(o, MOI.ConstraintSet(), c_index)
42-
MOI.delete(o, c_index)
43-
vi = MOI.VariableIndex(c_index.value)
44-
ci_new = MOI.add_constraint(o, 1.0 * vi, s)
45-
MOI.set(
46-
o,
47-
MOI.ConstraintName(),
48-
ci_new,
49-
"varcons_$(c_index.value)_$sname",
50-
)
51-
end
44+
for ci in MOI.get(o, MOI.ListOfConstraintIndices{F,S}())
45+
set = MOI.get(o, MOI.ConstraintSet(), ci)
46+
MOI.delete(o, ci)
47+
x = MOI.VariableIndex(ci.value)
48+
ci_new = MOI.add_constraint(o, 1.0 * x, set)
49+
name = "varcons_$(x.value)_$sname"
50+
MOI.set(o, MOI.ConstraintName(), ci_new, name)
5251
end
5352
end
5453
# we need names for all constraints
@@ -80,31 +79,38 @@ function compute_minimum_unsatisfied_constraints!(o::Optimizer)
8079
if st != MOI.OPTIMAL
8180
error("Unexpected status $st when computing conflicts")
8281
end
83-
o.conflict_status = if MOI.get(o, MOI.ObjectiveValue()) > 0
84-
MOI.CONFLICT_FOUND
82+
if MOI.get(o, MOI.ObjectiveValue()) > 0
83+
o.conflict_status = MOI.CONFLICT_FOUND
8584
else
86-
MOI.NO_CONFLICT_EXISTS
85+
o.conflict_status = MOI.NO_CONFLICT_EXISTS
8786
end
8887
return
8988
end
9089

9190
"""
92-
Model attribute representing whether why the Minimum Unsatisfiable Constraint analysis terminated.
91+
UnsatisfiableSystemStatus() <: MOI.AbstractModelAttribute
92+
93+
Model attribute representing whether why the Minimum Unsatisfiable Constraint
94+
analysis terminated.
9395
"""
9496
struct UnsatisfiableSystemStatus <: MOI.AbstractModelAttribute end
9597

96-
attribute_value_type(::UnsatisfiableSystemStatus) = MOI.ConflictStatusCode
98+
MOI.attribute_value_type(::UnsatisfiableSystemStatus) = MOI.ConflictStatusCode
9799

98100
MOI.get(o::Optimizer, ::UnsatisfiableSystemStatus) = o.conflict_status
99101

100102
"""
101-
Attribute representing whether the constraint could be satisfied in the Minimum Unsatisfiable Constraint analysis.
103+
ConstraintSatisfiabilityStatus() <: MOI.AbstractModelAttribute
102104
103-
Note that this is different from a constraint belonging to an Irreducible Infeasible Subsystem.
105+
Attribute representing whether the constraint could be satisfied in the Minimum
106+
Unsatisfiable Constraint analysis.
107+
108+
Note that this is different from a constraint belonging to an Irreducible
109+
Infeasible Subsystem.
104110
"""
105111
struct ConstraintSatisfiabilityStatus <: MOI.AbstractConstraintAttribute end
106112

107-
function attribute_value_type(::ConstraintSatisfiabilityStatus)
113+
function MOI.attribute_value_type(::ConstraintSatisfiabilityStatus)
108114
return MOI.ConflictParticipationStatusCode
109115
end
110116

@@ -113,8 +119,11 @@ function MOI.get(
113119
::ConstraintSatisfiabilityStatus,
114120
index::MOI.ConstraintIndex{MOI.VariableIndex},
115121
)
116-
o.conflict_status == MOI.CONFLICT_FOUND || error("no conflict")
117-
# we cannot determine whether variable constraint (integer, binary, variable bounds) participate
122+
if o.conflict_status != MOI.CONFLICT_FOUND
123+
return MOI.NOT_IN_CONFLICT
124+
end
125+
# We cannot determine whether variable constraint (integer, binary, variable
126+
# bounds) participate.
118127
return MOI.MAYBE_IN_CONFLICT
119128
end
120129

@@ -123,15 +132,13 @@ function MOI.get(
123132
::ConstraintSatisfiabilityStatus,
124133
index::MOI.ConstraintIndex,
125134
)
126-
o.conflict_status == MOI.CONFLICT_FOUND || error("no conflict")
127-
c_name = MOI.get(o, MOI.ConstraintName(), index)
128-
slack_name = "$(c_name)_master"
129-
ptr = SCIPfindVar(o, slack_name)
130-
if ptr == C_NULL
131-
error(
132-
"No constraint name corresponds to the index $index - name $c_name",
133-
)
135+
MOI.throw_if_not_valid(o, index)
136+
if o.conflict_status != MOI.CONFLICT_FOUND
137+
return MOI.NOT_IN_CONFLICT
134138
end
139+
c_name = MOI.get(o, MOI.ConstraintName(), index)
140+
ptr = SCIPfindVar(o, "$(c_name)_master")
141+
@assert ptr != C_NULL
135142
sol = SCIPgetBestSol(o)
136143
slack_value = SCIPgetSolVal(o, sol, ptr)
137144
return slack_value > 0.5 ? MOI.IN_CONFLICT : MOI.NOT_IN_CONFLICT

test/MOI_tests.jl

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ function _base_satisfiability_problem()
209209
optimizer = SCIP.Optimizer()
210210
MOI.set(optimizer, MOI.Silent(), true)
211211
x, y, z = MOI.add_variables(optimizer, 3)
212+
MOI.add_constraint(optimizer, x, MOI.ZeroOne())
212213
MOI.add_constraint(optimizer, x, MOI.LessThan(1.0))
213214
MOI.add_constraint(optimizer, y, MOI.LessThan(1.0))
214215
MOI.add_constraint(optimizer, z, MOI.LessThan(1.0))
@@ -221,22 +222,36 @@ function _base_satisfiability_problem()
221222
end
222223

223224
function test_minimum_unsatisfiable_system()
224-
optimizer, x, y, z, c = _base_satisfiability_problem()
225-
MOI.optimize!(optimizer)
226-
MOI.get(optimizer, MOI.TerminationStatus()) == MOI.OPTIMAL
227-
SCIP.compute_minimum_unsatisfied_constraints!(optimizer)
228-
@test MOI.get(optimizer, SCIP.UnsatisfiableSystemStatus()) ==
225+
attr = SCIP.ConstraintSatisfiabilityStatus()
226+
model, x, y, z, c = _base_satisfiability_problem()
227+
MOI.optimize!(model)
228+
MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL
229+
SCIP.compute_minimum_unsatisfied_constraints!(model)
230+
@test_throws(
231+
ErrorException(
232+
"Conflict computation is destructive for the model and cannot be called twice.",
233+
),
234+
SCIP.compute_minimum_unsatisfied_constraints!(model),
235+
)
236+
@test MOI.attribute_value_type(SCIP.UnsatisfiableSystemStatus()) ==
237+
MOI.ConflictStatusCode
238+
@test MOI.attribute_value_type(attr) == MOI.ConflictParticipationStatusCode
239+
@test MOI.get(model, SCIP.UnsatisfiableSystemStatus()) ==
229240
MOI.NO_CONFLICT_EXISTS
230-
optimizer, x, y, z, c = _base_satisfiability_problem()
231-
c2 = MOI.add_constraint(optimizer, 1.0 * x + y + z, MOI.GreaterThan(2.0))
232-
MOI.set(optimizer, MOI.ConstraintName(), c2, "lincons2")
233-
MOI.optimize!(optimizer)
234-
MOI.get(optimizer, MOI.TerminationStatus()) == MOI.INFEASIBLE
235-
SCIP.compute_minimum_unsatisfied_constraints!(optimizer)
236-
@test MOI.get(optimizer, SCIP.UnsatisfiableSystemStatus()) ==
237-
MOI.CONFLICT_FOUND
238-
@test Int(MOI.get(optimizer, SCIP.ConstraintSatisfiabilityStatus(), c)) +
239-
Int(MOI.get(optimizer, SCIP.ConstraintSatisfiabilityStatus(), c2)) 1
241+
c_zeroone = MOI.ConstraintIndex{MOI.VariableIndex,MOI.ZeroOne}(x.value)
242+
@test MOI.get(model, attr, c_zeroone) == MOI.NOT_IN_CONFLICT
243+
@test MOI.get(model, attr, c) == MOI.NOT_IN_CONFLICT
244+
model, x, y, z, c = _base_satisfiability_problem()
245+
c2 = MOI.add_constraint(model, 1.0 * x + y + z, MOI.GreaterThan(2.0))
246+
MOI.set(model, MOI.ConstraintName(), c2, "lincons2")
247+
MOI.optimize!(model)
248+
MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE
249+
SCIP.compute_minimum_unsatisfied_constraints!(model)
250+
@test MOI.get(model, SCIP.UnsatisfiableSystemStatus()) == MOI.CONFLICT_FOUND
251+
@test MOI.get(model, attr, c) == MOI.IN_CONFLICT ||
252+
MOI.get(model, attr, c2) == MOI.IN_CONFLICT
253+
c_zeroone = MOI.ConstraintIndex{MOI.VariableIndex,MOI.ZeroOne}(x.value)
254+
@test MOI.get(model, attr, c_zeroone) == MOI.MAYBE_IN_CONFLICT
240255
return
241256
end
242257

0 commit comments

Comments
 (0)