Skip to content

Commit 3ced5b7

Browse files
committed
Update
1 parent fe1ee2f commit 3ced5b7

File tree

1 file changed

+82
-48
lines changed

1 file changed

+82
-48
lines changed

src/Bridges/bridge_optimizer.jl

Lines changed: 82 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,32 @@ function MOI.is_valid(
476476
v_map = Variable.bridges(b)::Variable.Map
477477
return MOI.is_valid(v_map, ci)
478478
else
479-
# This has the potential to be a false positive. see #2817
479+
# This return value has the potential to be a false positive: it
480+
# doesn't discriminate between constraints that the user added, and
481+
# constraints that a bridge added that were themselves bridged.
482+
#
483+
# "Fixing" this particular call has a number of wide reaching
484+
# effects because bridges need this to be "true" so that they can
485+
# query attributes of the constraint from `b`.
486+
#
487+
# In most cases a false positive doesn't matter, because we really
488+
# do support querying stuff about it. And also the user needs some
489+
# way of obtaining the correct index, which they won't have except
490+
# by luck/enumeration.
491+
#
492+
# The main place that this is problematic is when we come to delete
493+
# constraints, and particular VariableIndex constraints, because we
494+
# triviallly have their `.value` field from the `.value` of the
495+
# VariableIndex.
496+
#
497+
# Instead of fixing everything though, we implement some extra
498+
# checks when deleting, and we leave the false-positive as-is for
499+
# now. If you, future reader, hit this comment while debugging, we
500+
# might need to revisit this decision.
501+
#
502+
# x-ref https://github.com/jump-dev/MathOptInterface.jl/issues/2696
503+
# x-ref https://github.com/jump-dev/MathOptInterface.jl/issues/2817
504+
# x-ref https://github.com/jump-dev/MathOptInterface.jl/pull/2818
480505
return haskey(Constraint.bridges(b), ci)
481506
end
482507
else
@@ -490,32 +515,49 @@ function _delete_variables_in_vector_of_variables_constraint(
490515
ci::MOI.ConstraintIndex{MOI.VectorOfVariables,S},
491516
) where {S}
492517
func = MOI.get(b, MOI.ConstraintFunction(), ci)
493-
variables = copy(func.variables)
494-
if vis == variables
518+
if vis == func.variables
495519
MOI.delete(b, ci)
496-
else
497-
for vi in vis
498-
i = findfirst(isequal(vi), variables)
499-
if i !== nothing
500-
if MOI.supports_dimension_update(S)
501-
call_in_context(MOI.delete, b, ci, IndexInVector(i))
502-
else
503-
MOI.Utilities.throw_delete_variable_in_vov(vi)
504-
end
505-
end
520+
return
521+
end
522+
variables = copy(func.variables)
523+
for vi in vis
524+
i = findfirst(isequal(vi), variables)
525+
if i === nothing
526+
continue
527+
elseif MOI.supports_dimension_update(S)
528+
call_in_context(MOI.delete, b, ci, IndexInVector(i))
529+
else
530+
MOI.Utilities.throw_delete_variable_in_vov(vi)
506531
end
507532
end
533+
return
508534
end
509535

510-
function _is_added_by_bridge(c_map, ci::MOI.ConstraintIndex{F,S}) where {F,S}
511-
for (i, bridge) in c_map
512-
if i == ci
513-
continue
514-
elseif ci in MOI.get(bridge, MOI.ListOfConstraintIndices{F,S}())
515-
return true
536+
"""
537+
_is_added_by_bridge(
538+
c_map,
539+
cache::Dict{Any,Any},
540+
ci::MOI.ConstraintIndex{F,S},
541+
) where {F,S}
542+
543+
Return `true` if `ci` was added by one of the bridges in `c_map`.
544+
545+
For performance reasons, we store the `ListOfConstraintIndices{F,S}` in `cache`,
546+
so that we don't have to keep lopping through the bridges.
547+
"""
548+
function _is_added_by_bridge(
549+
c_map,
550+
cache::Dict{Any,Any},
551+
ci::MOI.ConstraintIndex{F,S},
552+
) where {F,S}
553+
ret = get!(cache, (F, S)) do
554+
list = MOI.ConstraintIndex{F,S}[]
555+
for bridge in values(c_map)
556+
append!(list, MOI.get(bridge, MOI.ListOfConstraintIndices{F,S}()))
516557
end
558+
return list
517559
end
518-
return false
560+
return ci in ret
519561
end
520562

521563
"""
@@ -524,45 +566,37 @@ end
524566
vis::Vector{MOI.VariableIndex},
525567
)
526568
527-
!!! warning
528-
There's a lot of subtle logic in this function.
569+
The point of this function is to delete constraints associated with the
570+
variables in `vis`.
571+
572+
## Warning
573+
574+
Because of the false positive potential in
575+
`is_valid(::AbstractBridgeOptimizer, MOI.ConstraintIndex)`, we need to ensure
576+
that we delete constraints only if they were not added by a different constraint
577+
bridge, otherwise when we come to delete the parent constraint we'll hit a
578+
runtime error where we have already deleted part of the bridged constraint.
579+
580+
x-ref https://github.com/jump-dev/MathOptInterface.jl/issues/2817
581+
x-ref https://github.com/jump-dev/MathOptInterface.jl/pull/2818
529582
"""
530583
function _delete_variables_in_variables_constraints(
531584
b::AbstractBridgeOptimizer,
532585
vis::Vector{MOI.VariableIndex},
533586
)
534587
c_map = Constraint.bridges(b)::Constraint.Map
535-
# The point of this function is to delete constraints associated with the
536-
# variables in `vis`.
537-
#
538-
# There are two problematic cases:
539-
#
540-
# 1. A VariableIndex bridged to a VectorOfVariables equivalent. For example,
541-
# x in Interval --> [x] in HyperRectangle
542-
# 2. A VectorOfVariables bridged to a VariableIndex equivalent. For example,
543-
# [x] in Nonnegatives --> x in GreaterThan
544-
#
545-
# First, we need to delete scalar constraints, UNLESS they were added by a
546-
# different constraint bridge.
588+
cache = Dict{Any,Any}()
547589
for vi in vis
548-
# If a bridged `VariableIndex` constraints creates a second one, then we
549-
# will delete the second one when deleting the first one hence we should
550-
# not delete it again in this loop. For this, we reverse the order so
551-
# that we encounter the first one first and we won't delete the second
552-
# one since `MOI.is_valid(b, ci)` will be `false`.
553-
for ci in Iterators.reverse(Constraint.variable_constraints(c_map, vi))
554-
if MOI.is_valid(b, ci) && !_is_added_by_bridge(c_map, ci)
590+
for ci in Constraint.variable_constraints(c_map, vi)
591+
if !_is_added_by_bridge(c_map, cache, ci)
555592
MOI.delete(b, ci)
556593
end
557594
end
558595
end
559-
# Delete all `MOI.VectorOfVariables` constraints of these variables.
560-
# We reverse for the same reason as for `VariableIndex` below.
561-
# As the iterators are lazy, when the inner bridge constraint is deleted,
562-
# it won't be part of the iteration.
563-
for ci in
564-
Iterators.reverse(Constraint.vector_of_variables_constraints(c_map))
565-
_delete_variables_in_vector_of_variables_constraint(b, vis, ci)
596+
for ci in Constraint.vector_of_variables_constraints(c_map)
597+
if !_is_added_by_bridge(c_map, cache, ci)
598+
_delete_variables_in_vector_of_variables_constraint(b, vis, ci)
599+
end
566600
end
567601
return
568602
end

0 commit comments

Comments
 (0)