Skip to content

Commit 41aa699

Browse files
authored
Fix LowerBoundAlreadySet and UpperBoundAlreadySet (#325)
1 parent 3da3dd5 commit 41aa699

File tree

6 files changed

+144
-100
lines changed

6 files changed

+144
-100
lines changed

src/MOI_wrapper.jl

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,21 @@ const BOUNDS = Union{
1717
_kSCIP_SOLVE_STATUS_FINISHED,
1818
)
1919

20+
@enum(
21+
_SCIP_BOUND_TYPE,
22+
_kSCIP_EQUAL_TO,
23+
_kSCIP_INTERVAL,
24+
_kSCIP_LESS_THAN,
25+
_kSCIP_GREATER_THAN,
26+
_kSCIP_LESS_AND_GREATER_THAN,
27+
)
28+
2029
mutable struct Optimizer <: MOI.AbstractOptimizer
2130
inner::SCIPData
2231
reference::Dict{Ptr{Cvoid},Union{VarRef,ConsRef}}
2332
constypes::Dict{Tuple{Type,Type},Set{ConsRef}}
24-
binbounds::Dict{MOI.VariableIndex,BOUNDS} # only for binary variables
33+
binbounds::Dict{MOI.VariableIndex,MOI.Interval{Float64}} # only for binary variables
34+
bound_types::Dict{MOI.VariableIndex,_SCIP_BOUND_TYPE}
2535
params::Dict{String,Any}
2636
start::Dict{MOI.VariableIndex,Float64} # can be partial
2737
moi_separator::Any # ::Union{CutCbSeparator, Nothing}
@@ -44,7 +54,8 @@ mutable struct Optimizer <: MOI.AbstractOptimizer
4454
SCIPData(),
4555
Dict{Ptr{Cvoid},Union{VarRef,ConsRef}}(),
4656
Dict{Tuple{Type,Type},Set{ConsRef}}(),
47-
Dict{MOI.VariableIndex,BOUNDS}(),
57+
Dict{MOI.VariableIndex,MOI.Interval{Float64}}(),
58+
Dict{MOI.VariableIndex,_SCIP_BOUND_TYPE}(),
4859
Dict{String,Any}(),
4960
Dict{MOI.VariableIndex,Float64}(),
5061
nothing,
@@ -86,6 +97,7 @@ function MOI.empty!(o::Optimizer)
8697
empty!(o.reference)
8798
empty!(o.constypes)
8899
empty!(o.binbounds)
100+
empty!(o.bound_types)
89101
empty!(o.start)
90102
o.inner = SCIPData()
91103
# reapply parameters
@@ -128,13 +140,17 @@ function cons(
128140
end
129141

130142
"Extract bounds from sets."
131-
bounds(set::MOI.EqualTo{Float64}) = (set.value, set.value)
143+
bounds(o, set::MOI.EqualTo{Float64}) = (set.value, set.value)
132144

133-
bounds(set::MOI.GreaterThan{Float64}) = (set.lower, nothing)
145+
function bounds(o, set::MOI.GreaterThan{Float64})
146+
return (set.lower, SCIPinfinity(o))
147+
end
134148

135-
bounds(set::MOI.LessThan{Float64}) = (nothing, set.upper)
149+
function bounds(o, set::MOI.LessThan{Float64})
150+
return (-SCIPinfinity(o), set.upper)
151+
end
136152

137-
bounds(set::MOI.Interval{Float64}) = (set.lower, set.upper)
153+
bounds(o, set::MOI.Interval{Float64}) = (set.lower, set.upper)
138154

139155
"Make set from bounds."
140156
function from_bounds(::Type{MOI.EqualTo{Float64}}, lower, upper)

src/MOI_wrapper/UserCutCallback.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,16 @@ function MOI.submit(
7676
func::MOI.ScalarAffineFunction{Float64},
7777
set::BOUNDS,
7878
)
79-
lhs, rhs = bounds(set)
80-
inf = SCIPinfinity(o)
79+
lhs, rhs = bounds(o, set)
8180
add_cut_sepa(
8281
o.inner.scip[],
8382
o.inner.vars,
8483
o.inner.sepas,
8584
cb_data.callback_data.sepa,
8685
[VarRef(t.variable.value) for t in func.terms],
8786
[t.coefficient for t in func.terms],
88-
something(lhs, -inf),
89-
something(rhs, inf),
87+
lhs,
88+
rhs,
9089
)
9190
cb_data.callback_data.submit_called = true
9291
return

src/MOI_wrapper/linear_constraints.jl

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ function MOI.add_constraint(
2222
allow_modification(o)
2323
varrefs = [VarRef(t.variable.value) for t in func.terms]
2424
coefs = [t.coefficient for t in func.terms]
25-
inf = SCIPinfinity(o)
26-
lhs, rhs = bounds(set)
27-
lhs, rhs = something(lhs, -inf), something(rhs, inf)
25+
lhs, rhs = bounds(o, set)
2826
cr = add_linear_constraint(o.inner, varrefs, coefs, lhs, rhs)
2927
ci = MOI.ConstraintIndex{F,S}(cr.val)
3028
register!(o, ci)
@@ -39,9 +37,7 @@ function MOI.set(
3937
set::S,
4038
) where {S<:BOUNDS}
4139
allow_modification(o)
42-
inf = SCIPinfinity(o)
43-
lhs, rhs = bounds(set)
44-
lhs, rhs = something(lhs, -inf), something(rhs, inf)
40+
lhs, rhs = bounds(o, set)
4541
@SCIP_CALL SCIPchgLhsLinear(o, cons(o, ci), lhs)
4642
@SCIP_CALL SCIPchgRhsLinear(o, cons(o, ci), rhs)
4743
return nothing

src/MOI_wrapper/quadratic_constraints.jl

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,7 @@ function MOI.add_constraint(
3333
factor = 1.0 .- 0.5 * (quadrefs1 .== quadrefs2)
3434
quadcoefs = factor .* [t.coefficient for t in func.quadratic_terms]
3535
# range
36-
inf = SCIPinfinity(o)
37-
lhs, rhs = bounds(set)
38-
lhs, rhs = something(lhs, -inf), something(rhs, inf)
36+
lhs, rhs = bounds(o, set)
3937
cr = add_quadratic_constraint(
4038
o.inner,
4139
linrefs,
@@ -59,9 +57,7 @@ function MOI.set(
5957
set::S,
6058
) where {S<:BOUNDS}
6159
allow_modification(o)
62-
inf = SCIPinfinity(o)
63-
lhs, rhs = bounds(set)
64-
lhs, rhs = something(lhs, -inf), something(rhs, inf)
60+
lhs, rhs = bounds(o, set)
6561
@SCIP_CALL SCIPchgLhsQuadratic(o, cons(o, ci), lhs)
6662
@SCIP_CALL SCIPchgRhsQuadratic(o, cons(o, ci), rhs)
6763
return nothing

src/MOI_wrapper/variable.jl

Lines changed: 102 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,11 @@ function MOI.delete(o::Optimizer, vi::MOI.VariableIndex)
115115
throw(MOI.InvalidIndex(vi))
116116
end
117117
delete!(o.binbounds, vi)
118+
delete!(o.bound_types, vi)
118119
delete!(o.reference, var(o, vi))
119120
delete(o.inner, VarRef(vi.value))
121+
# FIXME(odow): delete the associated ConstraintIndex
122+
delete!(o.bound_types, vi)
120123
o.name_to_variable = nothing
121124
return nothing
122125
end
@@ -176,22 +179,11 @@ function MOI.add_constraint(o::Optimizer, vi::MOI.VariableIndex, ::MOI.ZeroOne)
176179
v = var(o, vi)
177180
p_infeas = Ref{SCIP_Bool}()
178181
@SCIP_CALL SCIPchgVarType(o, v, SCIP_VARTYPE_BINARY, p_infeas)
179-
# Need to adjust bounds for SCIP, which fails with an error otherwise.
180-
# Check for conflicts with existing bounds first:
181182
lb, ub = SCIPvarGetLbOriginal(v), SCIPvarGetUbOriginal(v)
182-
if lb == -SCIPinfinity(o) && ub == SCIPinfinity(o)
183-
@SCIP_CALL SCIPchgVarLb(o, v, 0.0)
184-
@SCIP_CALL SCIPchgVarUb(o, v, 1.0)
185-
else
186-
# Store old bounds for later recovery.
187-
o.binbounds[vi] = MOI.Interval(lb, ub)
188-
if ub > 1.0
189-
@SCIP_CALL SCIPchgVarUb(o, v, 1.0)
190-
end
191-
if lb < 0.0
192-
@SCIP_CALL SCIPchgVarLb(o, v, 0.0)
193-
end
194-
end
183+
# Store old bounds for later recovery.
184+
o.binbounds[vi] = MOI.Interval(lb, ub)
185+
@SCIP_CALL SCIPchgVarLb(o, v, max(lb, 0.0))
186+
@SCIP_CALL SCIPchgVarUb(o, v, min(ub, 1.0))
195187
ci = MOI.ConstraintIndex{MOI.VariableIndex,MOI.ZeroOne}(vi.value)
196188
return register!(o, ci)
197189
end
@@ -207,10 +199,9 @@ function MOI.delete(
207199
p_infeas = Ref{SCIP_Bool}()
208200
@SCIP_CALL SCIPchgVarType(o, v, SCIP_VARTYPE_CONTINUOUS, p_infeas)
209201
bounds = get(o.binbounds, vi, nothing)
210-
if bounds !== nothing
211-
@SCIP_CALL SCIPchgVarLb(o, v, bounds.lower)
212-
@SCIP_CALL SCIPchgVarUb(o, v, bounds.upper)
213-
end
202+
@SCIP_CALL SCIPchgVarLb(o, v, bounds.lower)
203+
@SCIP_CALL SCIPchgVarUb(o, v, bounds.upper)
204+
delete!(o.binbounds, vi)
214205
delete!(o.constypes[MOI.VariableIndex, MOI.ZeroOne], ConsRef(ci.value))
215206
return nothing
216207
end
@@ -234,38 +225,89 @@ function MOI.supports_constraint(
234225
return true
235226
end
236227

228+
function _throw_if_existing_lower(x, type, new_set::S) where {S}
229+
if type == _kSCIP_EQUAL_TO
230+
throw(MOI.LowerBoundAlreadySet{MOI.EqualTo{Float64},S}(x))
231+
elseif type == _kSCIP_GREATER_THAN
232+
throw(MOI.LowerBoundAlreadySet{MOI.GreaterThan{Float64},S}(x))
233+
elseif type == _kSCIP_LESS_AND_GREATER_THAN
234+
throw(MOI.LowerBoundAlreadySet{MOI.GreaterThan{Float64},S}(x))
235+
elseif type == _kSCIP_INTERVAL
236+
throw(MOI.LowerBoundAlreadySet{MOI.Interval{Float64},S}(x))
237+
end
238+
return
239+
end
240+
241+
function _throw_if_existing_upper(x, type, new_set::S) where {S}
242+
if type == _kSCIP_EQUAL_TO
243+
throw(MOI.UpperBoundAlreadySet{MOI.EqualTo{Float64},S}(x))
244+
elseif type == _kSCIP_LESS_THAN
245+
throw(MOI.UpperBoundAlreadySet{MOI.LessThan{Float64},S}(x))
246+
elseif type == _kSCIP_LESS_AND_GREATER_THAN
247+
throw(MOI.UpperBoundAlreadySet{MOI.LessThan{Float64},S}(x))
248+
elseif type == _kSCIP_INTERVAL
249+
throw(MOI.UpperBoundAlreadySet{MOI.Interval{Float64},S}(x))
250+
end
251+
return
252+
end
253+
254+
function _update_bound(o::Optimizer, x, s::MOI.EqualTo, l, u)
255+
type = get(o.bound_types, x, nothing)
256+
_throw_if_existing_lower(x, type, s)
257+
_throw_if_existing_upper(x, type, s)
258+
o.bound_types[x] = _kSCIP_EQUAL_TO
259+
return s.value, s.value
260+
end
261+
262+
function _update_bound(o::Optimizer, x, s::MOI.Interval, l, u)
263+
type = get(o.bound_types, x, nothing)
264+
_throw_if_existing_lower(x, type, s)
265+
_throw_if_existing_upper(x, type, s)
266+
o.bound_types[x] = _kSCIP_INTERVAL
267+
return s.lower, s.upper
268+
end
269+
270+
function _update_bound(o::Optimizer, x, s::MOI.LessThan, l, u)
271+
type = get(o.bound_types, x, nothing)
272+
_throw_if_existing_upper(x, type, s)
273+
if type == _kSCIP_GREATER_THAN
274+
o.bound_types[x] = _kSCIP_LESS_AND_GREATER_THAN
275+
else
276+
o.bound_types[x] = _kSCIP_LESS_THAN
277+
end
278+
return l, s.upper
279+
end
280+
281+
function _update_bound(o::Optimizer, x, s::MOI.GreaterThan, l, u)
282+
type = get(o.bound_types, x, nothing)
283+
_throw_if_existing_lower(x, type, s)
284+
if type == _kSCIP_LESS_THAN
285+
o.bound_types[x] = _kSCIP_LESS_AND_GREATER_THAN
286+
else
287+
o.bound_types[x] = _kSCIP_GREATER_THAN
288+
end
289+
return s.lower, u
290+
end
291+
237292
function MOI.add_constraint(
238293
o::Optimizer,
239294
vi::MOI.VariableIndex,
240295
set::S,
241296
) where {S<:BOUNDS}
242297
allow_modification(o)
243298
v = var(o, vi)
244-
newlb, newub = bounds(set)
245-
inf = SCIPinfinity(o)
246-
newlb, newub = something(newlb, -inf), something(newub, inf)
247-
oldlb, oldub = SCIPvarGetLbOriginal(v), SCIPvarGetUbOriginal(v)
248-
# FIXME(odow): This section is broken for detecting existing bounds
249-
if (oldlb != -inf && newlb != -inf || oldub != inf && newub != inf)
250-
if SCIPvarGetType(v) == SCIP_VARTYPE_BINARY
251-
# Store new bounds
252-
o.binbounds[vi] = MOI.Interval(newlb, newub)
253-
if newlb < 0.0
254-
newlb = oldlb
255-
end
256-
if newub > 1.0
257-
newub = oldub
258-
end
259-
else
260-
throw(MOI.LowerBoundAlreadySet{S,S}(vi))
261-
end
262-
end
263-
if newlb != -inf
264-
@SCIP_CALL SCIPchgVarLb(o, v, newlb)
299+
l, u = SCIPvarGetLbOriginal(v), SCIPvarGetUbOriginal(v)
300+
if SCIPvarGetType(v) == SCIP_VARTYPE_BINARY
301+
s = o.binbounds[vi]
302+
l, u = s.lower, s.upper
265303
end
266-
if newub != inf
267-
@SCIP_CALL SCIPchgVarUb(o, v, newub)
304+
l, u = _update_bound(o, vi, set, l, u)
305+
if SCIPvarGetType(v) == SCIP_VARTYPE_BINARY
306+
o.binbounds[vi] = MOI.Interval(l, u)
307+
l, u = max(0.0, l), min(1.0, u)
268308
end
309+
@SCIP_CALL SCIPchgVarLb(o, v, l)
310+
@SCIP_CALL SCIPchgVarUb(o, v, u)
269311
ci = MOI.ConstraintIndex{MOI.VariableIndex,S}(vi.value)
270312
return register!(o, ci)
271313
end
@@ -303,6 +345,14 @@ function MOI.delete(
303345
end
304346
# but do delete the constraint reference
305347
delete!(o.binbounds, vi)
348+
type = o.bound_types[vi]
349+
if type == _kSCIP_LESS_AND_GREATER_THAN && S <: MOI.LessThan
350+
o.bound_types[vi] = _kSCIP_GREATER_THAN
351+
elseif type == _kSCIP_LESS_AND_GREATER_THAN && S <: MOI.GreaterThan
352+
o.bound_types[vi] = _kSCIP_LESS_AND_GREATER_THAN
353+
else
354+
delete!(o.bound_types, vi)
355+
end
306356
delete!(o.constypes[MOI.VariableIndex, S], ConsRef(ci.value))
307357
return nothing
308358
end
@@ -314,25 +364,25 @@ function MOI.set(
314364
set::S,
315365
) where {S<:BOUNDS}
316366
allow_modification(o)
317-
v = var(o, MOI.VariableIndex(ci.value))
318-
lb, ub = bounds(set)
319-
old_interval =
320-
get(o.binbounds, MOI.VariableIndex(ci.value), MOI.Interval(0.0, 1.0))
321-
if lb !== nothing
367+
vi = MOI.VariableIndex(ci.value)
368+
v = var(o, vi)
369+
lb, ub = bounds(o, set)
370+
inf = SCIPinfinity(o)
371+
if lb > -inf
322372
if SCIPvarGetType(v) == SCIP_VARTYPE_BINARY
373+
old_interval = o.binbounds[vi]
374+
o.binbounds[vi] = MOI.Interval(lb, old_interval.upper)
323375
lb = max(lb, 0.0)
324376
end
325377
@SCIP_CALL SCIPchgVarLb(o, v, lb)
326-
o.binbounds[MOI.VariableIndex(ci.value)] =
327-
MOI.Interval(lb, old_interval.upper)
328378
end
329-
if ub !== nothing
379+
if ub < inf
330380
if SCIPvarGetType(v) == SCIP_VARTYPE_BINARY
381+
old_interval = o.binbounds[vi]
382+
o.binbounds[vi] = MOI.Interval(old_interval.lower, ub)
331383
ub = min(ub, 1.0)
332384
end
333385
@SCIP_CALL SCIPchgVarUb(o, v, ub)
334-
o.binbounds[MOI.VariableIndex(ci.value)] =
335-
MOI.Interval(old_interval.lower, ub)
336386
end
337387
return nothing
338388
end

0 commit comments

Comments
 (0)