Skip to content

Commit a03e7bd

Browse files
authored
[Bridges] throw for bound already set on bridged variable (#2383)
1 parent bf2a8b9 commit a03e7bd

File tree

4 files changed

+97
-11
lines changed

4 files changed

+97
-11
lines changed

src/Bridges/Variable/map.jl

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ mutable struct Map <: AbstractDict{MOI.VariableIndex,AbstractBridge}
4444
# `(ci::ConstraintIndex{MOI.VectorOfVariables}).value` ->
4545
# the dimension of the set
4646
vector_of_variables_length::Vector{Int64}
47+
# Same as in `MOI.Utilities.VariablesContainer`
48+
set_mask::Vector{UInt16}
4749
end
4850

4951
function Map()
@@ -58,6 +60,7 @@ function Map()
5860
Dict{MOI.ConstraintIndex,Int64}(),
5961
Int64[],
6062
Int64[],
63+
UInt16[],
6164
)
6265
end
6366

@@ -88,6 +91,7 @@ function Base.empty!(map::Map)
8891
empty!(map.constraint_context)
8992
empty!(map.vector_of_variables_map)
9093
empty!(map.vector_of_variables_length)
94+
empty!(map.set_mask)
9195
return map
9296
end
9397

@@ -113,8 +117,9 @@ end
113117
function Base.delete!(map::Map, vi::MOI.VariableIndex)
114118
if iszero(map.info[-vi.value])
115119
# Delete scalar variable
116-
map.bridges[bridge_index(map, vi)] = nothing
117-
map.sets[bridge_index(map, vi)] = nothing
120+
index = bridge_index(map, vi)
121+
map.bridges[index] = nothing
122+
map.sets[index] = nothing
118123
elseif has_keys(map, [vi])
119124
# Delete whole vector
120125
delete!(map, [vi])
@@ -131,6 +136,7 @@ function Base.delete!(map::Map, vi::MOI.VariableIndex)
131136
map.index_in_vector[i] -= 1
132137
end
133138
end
139+
map.set_mask[-vi.value] = MOI.Utilities._DELETED_VARIABLE
134140
map.index_in_vector[-vi.value] = -1
135141
return map
136142
end
@@ -144,6 +150,7 @@ function Base.delete!(map::Map, vis::Vector{MOI.VariableIndex})
144150
)
145151
end
146152
for vi in vis
153+
map.set_mask[-vi.value] = MOI.Utilities._DELETED_VARIABLE
147154
map.index_in_vector[-vi.value] = -1
148155
end
149156
map.bridges[bridge_index(map, first(vis))] = nothing
@@ -274,6 +281,64 @@ function MOI.is_valid(
274281
map.sets[index] === S
275282
end
276283

284+
"""
285+
MOI.add_constraint(map::Map, vi::MOI.VariableIndex, set::MOI.AbstractScalarSet)
286+
287+
Record that a constraint `vi`-in-`set` is added and throws if a lower or upper bound
288+
is set by this constraint and such bound has already been set for `vi`.
289+
"""
290+
function MOI.add_constraint(::Map, ::MOI.VariableIndex, ::MOI.AbstractScalarSet)
291+
# Nothing to do as this is is not recognized as setting a lower or upper bound
292+
end
293+
294+
# We cannot use `SUPPORTED_VARIABLE_SCALAR_SETS` because
295+
# `Integer` and `ZeroOne` do not define `T` and we need `T`
296+
# for `_throw_if_lower_bound_set`.
297+
const _BOUNDED_VARIABLE_SCALAR_SETS{T} = Union{
298+
MOI.EqualTo{T},
299+
MOI.GreaterThan{T},
300+
MOI.LessThan{T},
301+
MOI.Interval{T},
302+
MOI.Semicontinuous{T},
303+
MOI.Semiinteger{T},
304+
MOI.Parameter{T},
305+
}
306+
307+
function MOI.add_constraint(
308+
map::Map,
309+
vi::MOI.VariableIndex,
310+
::S,
311+
) where {T,S<:_BOUNDED_VARIABLE_SCALAR_SETS{T}}
312+
flag = MOI.Utilities._single_variable_flag(S)
313+
index = -vi.value
314+
mask = map.set_mask[index]
315+
MOI.Utilities._throw_if_lower_bound_set(vi, S, mask, T)
316+
MOI.Utilities._throw_if_upper_bound_set(vi, S, mask, T)
317+
map.set_mask[index] = mask | flag
318+
return
319+
end
320+
321+
"""
322+
delete(map::Map, ci::MOI.ConstraintIndex{MOI.VariableIndex,<:MOI.AbstractScalarSet})
323+
324+
Record that the constraint `vi`-in-`S` is deleted.
325+
"""
326+
function MOI.delete(
327+
::Map,
328+
ci::MOI.ConstraintIndex{MOI.VariableIndex,<:MOI.AbstractScalarSet},
329+
)
330+
# Nothing to do as this is is not recognized as setting a lower or upper bound
331+
end
332+
333+
function MOI.delete(
334+
map::Map,
335+
ci::MOI.ConstraintIndex{MOI.VariableIndex,S},
336+
) where {T,S<:_BOUNDED_VARIABLE_SCALAR_SETS{T}}
337+
flag = MOI.Utilities._single_variable_flag(S)
338+
map.set_mask[-ci.value] &= ~flag
339+
return
340+
end
341+
277342
"""
278343
constraints_with_set(map::Map, S::Type{<:MOI.AbstractSet})
279344
@@ -384,6 +449,7 @@ function add_key_for_bridge(
384449
push!(map.index_in_vector, 0)
385450
push!(map.bridges, nothing)
386451
push!(map.sets, typeof(set))
452+
push!(map.set_mask, 0x0000)
387453
map.bridges[bridge_index] = call_in_context(map, bridge_index, bridge_fun)
388454
index = -bridge_index
389455
variable = MOI.VariableIndex(index)
@@ -400,6 +466,7 @@ function add_key_for_bridge(
400466
end
401467
end
402468
end
469+
MOI.add_constraint(map, variable, set)
403470
return variable, MOI.ConstraintIndex{MOI.VariableIndex,typeof(set)}(index)
404471
end
405472

@@ -443,12 +510,14 @@ function add_keys_for_bridge(
443510
push!(map.index_in_vector, 1)
444511
push!(map.bridges, nothing)
445512
push!(map.sets, typeof(set))
513+
push!(map.set_mask, 0x0000)
446514
for i in 2:MOI.dimension(set)
447515
push!(map.parent_index, 0)
448516
push!(map.info, i)
449517
push!(map.index_in_vector, i)
450518
push!(map.bridges, nothing)
451519
push!(map.sets, nothing)
520+
push!(map.set_mask, 0x0000)
452521
end
453522
map.bridges[bridge_index] = call_in_context(map, bridge_index, bridge_fun)
454523
variables = MOI.VariableIndex[

src/Bridges/bridge_optimizer.jl

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,10 @@ function MOI.delete(b::AbstractBridgeOptimizer, vi::MOI.VariableIndex)
671671
return
672672
end
673673

674-
function MOI.delete(b::AbstractBridgeOptimizer, ci::MOI.ConstraintIndex)
674+
function MOI.delete(
675+
b::AbstractBridgeOptimizer,
676+
ci::MOI.ConstraintIndex{F},
677+
) where {F}
675678
if is_bridged(b, ci)
676679
MOI.throw_if_not_valid(b, ci)
677680
br = bridge(b, ci)
@@ -687,6 +690,11 @@ function MOI.delete(b::AbstractBridgeOptimizer, ci::MOI.ConstraintIndex)
687690
else
688691
delete!(Constraint.bridges(b)::Constraint.Map, ci)
689692
end
693+
if F === MOI.VariableIndex && ci.value < 0
694+
# Constraint on a bridged variable so we need to remove the flag
695+
# if it is a bound
696+
MOI.delete(Variable.bridges(b), ci)
697+
end
690698
Variable.call_in_context(
691699
Variable.bridges(b),
692700
ci,
@@ -1838,6 +1846,7 @@ function MOI.add_constraint(
18381846
typeof(f),
18391847
typeof(s),
18401848
)
1849+
MOI.add_constraint(Variable.bridges(b), f, s)
18411850
return add_bridged_constraint(b, BridgeType, f, s)
18421851
end
18431852
elseif f isa MOI.VectorOfVariables

test/Bridges/Variable/map.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,21 @@ function test_EmptyMap()
279279
return
280280
end
281281

282+
function test_double_variable_bound()
283+
map = MOI.Bridges.Variable.Map()
284+
b1 = VariableDummyBridge(1)
285+
set1 = MOI.EqualTo(0.0)
286+
v1, c1 = MOI.Bridges.Variable.add_key_for_bridge(map, () -> b1, set1)
287+
MOI.is_valid(map, c1)
288+
MOI.add_constraint(map, v1, MOI.Integer())
289+
cint = MOI.ConstraintIndex{typeof(v1),MOI.Integer}(v1.value)
290+
MOI.delete(map, cint)
291+
set2 = MOI.LessThan(1.0)
292+
err = MOI.UpperBoundAlreadySet{typeof(set1),typeof(set2)}(v1)
293+
@test_throws err MOI.add_constraint(map, v1, set2)
294+
return
295+
end
296+
282297
end # module
283298

284299
TestVariableMap.runtests()

test/Bridges/lazy_bridge_optimizer.jl

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -316,14 +316,7 @@ function test_MOI_runtests_StandardSDPAModel()
316316
model,
317317
MOI.Test.Config(
318318
exclude = Any[MOI.optimize!, MOI.SolverName, MOI.SolverVersion],
319-
);
320-
exclude = String[
321-
# Skip these tests because the bridge reformulates bound
322-
# constraints, so there is no conflict. An error _is_ thrown if two
323-
# sets of the same type are added.
324-
"test_model_LowerBoundAlreadySet",
325-
"test_model_UpperBoundAlreadySet",
326-
],
319+
),
327320
)
328321
return
329322
end

0 commit comments

Comments
 (0)