Skip to content

Commit 961bb55

Browse files
authored
Fix double deletion of nested bridged SingleVariable constraint (#1232)
* Fix double deletion of nested bridged SingleVariable constraint * Add tests and fix for vector of variables
1 parent 677cb27 commit 961bb55

3 files changed

Lines changed: 49 additions & 3 deletions

File tree

src/Bridges/bridge_optimizer.jl

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -386,13 +386,23 @@ function _delete_variables_in_variables_constraints(
386386
vis::Vector{MOI.VariableIndex},
387387
)
388388
# Delete all `MOI.VectorOfVariables` constraints of these variables.
389-
for ci in Constraint.vector_of_variables_constraints(Constraint.bridges(b))
389+
# We reverse for the same reason as for `SingleVariable` below.
390+
# As the iterators are lazy, when the inner bridge constraint is deleted,
391+
# it won't be part of the iteration.
392+
for ci in Iterators.reverse(Constraint.vector_of_variables_constraints(Constraint.bridges(b)))
390393
_delete_variables_in_vector_of_variables_constraint(b, vis, ci)
391394
end
392395
# Delete all `MOI.SingleVariable` constraints of these variables.
393396
for vi in vis
394-
for ci in Constraint.variable_constraints(Constraint.bridges(b), vi)
395-
MOI.delete(b, ci)
397+
# If a bridged `SingleVariable` constraints creates a second one,
398+
# then we will delete the second one when deleting the first one hence we
399+
# should not delete it again in this loop.
400+
# For this, we reverse the order so that we encounter the first one first
401+
# and we won't delete the second one since `MOI.is_valid(b, ci)` will be `false`.
402+
for ci in Iterators.reverse(Constraint.variable_constraints(Constraint.bridges(b), vi))
403+
if MOI.is_valid(b, ci)
404+
MOI.delete(b, ci)
405+
end
396406
end
397407
end
398408
end

src/Utilities/lazy_iterators.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@ function Base.iterate(it::LazyMap, args...)
3333
end
3434
Base.IteratorSize(it::LazyMap) = Base.IteratorSize(it.data)
3535
Base.eltype(::LazyMap{T}) where {T} = T
36+
Iterators.reverse(it::LazyMap{T}) where T = LazyMap{T}(it.f, Iterators.reverse(it.data))

test/Bridges/bridge_optimizer.jl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,3 +276,38 @@ end
276276
with 0 constraint bridges
277277
with inner model $(MOIU.MockOptimizer{NoIntervalModel{Float64}})""")
278278
end
279+
280+
MOI.Utilities.@model(AffineOnlyModel, (), (MOI.Interval,), (MOI.PositiveSemidefiniteConeTriangle,), (), (), (MOI.ScalarAffineFunction,), (), (MOI.VectorAffineFunction,), true)
281+
MOI.supports_constraint(::AffineOnlyModel{T}, ::Type{MOI.SingleVariable}, ::Type{MOI.LessThan{T}}) where T = false
282+
MOI.supports_constraint(::AffineOnlyModel{T}, ::Type{MOI.SingleVariable}, ::Type{MOI.Interval{T}}) where T = false
283+
@testset "Double deletion of nested bridged SingleVariable constraint" begin
284+
@testset "Scalar" begin
285+
# The variable is bridged to `SingleVariable`-in-`Interval` and then `ScalarAffineFunction`-in-`Interval`.
286+
# Hence there is two bridged `SingleVariable` constraints on the same variables and we need to be
287+
# careful not to delete the second one twice, see https://github.com/jump-dev/MathOptInterface.jl/issues/1231
288+
model = MOI.instantiate(AffineOnlyModel{Float64}, with_bridge_type=Float64)
289+
x = MOI.add_variable(model)
290+
c = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(1.0))
291+
# Need to test the bridging to make sure it's not functionized first as otherwise,
292+
# this test would not cover the case we want to test
293+
b1 = MOI.Bridges.bridge(model, c)
294+
@test b1 isa MOI.Bridges.Constraint.LessToIntervalBridge
295+
b2 = MOI.Bridges.bridge(model, b1.constraint)
296+
@test b2 isa MOI.Bridges.Constraint.ScalarFunctionizeBridge
297+
@test !MOI.Bridges.is_bridged(model, b2.constraint)
298+
MOI.delete(model, x)
299+
@test !MOI.is_valid(model, x)
300+
end
301+
@testset "Vector" begin
302+
model = MOI.instantiate(AffineOnlyModel{Float64}, with_bridge_type=Float64)
303+
x = MOI.add_variables(model, 4)
304+
c = MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.PositiveSemidefiniteConeSquare(2))
305+
b1 = MOI.Bridges.bridge(model, c)
306+
@test b1 isa MOI.Bridges.Constraint.SquareBridge
307+
b2 = MOI.Bridges.bridge(model, b1.triangle)
308+
@test b2 isa MOI.Bridges.Constraint.VectorFunctionizeBridge
309+
@test !MOI.Bridges.is_bridged(model, b2.constraint)
310+
MOI.delete(model, x)
311+
@test all(vi -> !MOI.is_valid(model, vi), x)
312+
end
313+
end

0 commit comments

Comments
 (0)