Skip to content

Commit d9c998a

Browse files
authored
[Bridges] fix supports for VariableIndex: Take II (#1992)
1 parent 4d26244 commit d9c998a

File tree

2 files changed

+132
-8
lines changed

2 files changed

+132
-8
lines changed

src/Bridges/bridge_optimizer.jl

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,16 +1345,55 @@ end
13451345
function MOI.supports(
13461346
b::AbstractBridgeOptimizer,
13471347
attr::MOI.AbstractConstraintAttribute,
1348-
IndexType::Type{MOI.ConstraintIndex{F,S}},
1348+
::Type{MOI.ConstraintIndex{F,S}},
13491349
) where {F,S}
1350-
if is_bridged(b, F, S)
1351-
bridge = Constraint.concrete_bridge_type(b, F, S)
1352-
return MOI.supports(recursive_model(b), attr, bridge)
1353-
elseif F == MOI.Utilities.variable_function_type(S) && is_bridged(b, S)
1354-
bridge = Variable.concrete_bridge_type(b, S)
1355-
return MOI.supports(recursive_model(b), attr, bridge)
1350+
# !!! warning
1351+
# This function is slightly confusing, because we need to account for
1352+
# the different ways in which a constraint might be added.
1353+
if F == MOI.Utilities.variable_function_type(S)
1354+
# These are VariableIndex and VectorOfVariable constraints.
1355+
if is_bridged(b, S)
1356+
# If S needs to be bridged, it usually means that either there is a
1357+
# variable bridge, or that there is a free variable followed by a
1358+
# constraint bridge (i.e., the two cases handled below).
1359+
#
1360+
# However, it might be the case, like the tests in
1361+
# Variable/flip_sign.jl, that the model supports F-in-S constraints,
1362+
# but force-bridges S sets. If so, we might be in the unusual
1363+
# situation where we support the attribute if the index was added
1364+
# via add_constraint, but not if it was added via
1365+
# add_constrained_variable. Because MOI lacks the ability to tell
1366+
# which happened just based on the type, we're going to default to
1367+
# asking the variable bridge, at the risk of a false negative.
1368+
if is_variable_bridged(b, S)
1369+
bridge = Variable.concrete_bridge_type(b, S)
1370+
return MOI.supports(recursive_model(b), attr, bridge)
1371+
else
1372+
bridge = Constraint.concrete_bridge_type(b, F, S)
1373+
return MOI.supports(recursive_model(b), attr, bridge)
1374+
end
1375+
else
1376+
# If S doesn't need to be bridged, it usually means that either the
1377+
# solver supports add_constrained_variable, or it supports free
1378+
# variables and add_constraint.
1379+
#
1380+
# In some cases, it might be that the solver supports
1381+
# add_constrained_variable, but ends up bridging add_constraint.
1382+
# Because MOI lacks the ability to tell which one was called based
1383+
# on the index type, asking the model might give a false negative
1384+
# (we support the attribute via add_constrained_variable, but the
1385+
# bridge doesn't via add_constraint because it will be bridged).
1386+
return MOI.supports(b.model, attr, MOI.ConstraintIndex{F,S})
1387+
end
13561388
else
1357-
return MOI.supports(b.model, attr, IndexType)
1389+
# These are normal add_constraints, so we just check if they are
1390+
# bridged.
1391+
if is_bridged(b, F, S)
1392+
bridge = Constraint.concrete_bridge_type(b, F, S)
1393+
return MOI.supports(recursive_model(b), attr, bridge)
1394+
else
1395+
return MOI.supports(b.model, attr, MOI.ConstraintIndex{F,S})
1396+
end
13581397
end
13591398
end
13601399

test/Bridges/bridge_optimizer.jl

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,91 @@ function test_IssueIpopt333_supports_ConstraintDualStart_VariableIndex()
847847
return
848848
end
849849

850+
mutable struct _Issue1992 <: MOI.AbstractOptimizer
851+
supports::Bool
852+
variables::Int
853+
constraints::Int
854+
_Issue1992(flag) = new(flag, 0, 0)
855+
end
856+
857+
function MOI.supports_add_constrained_variables(
858+
::_Issue1992,
859+
::Type{<:Union{MOI.Nonpositives,MOI.Nonnegatives}},
860+
)
861+
return true
862+
end
863+
864+
function MOI.add_constrained_variables(
865+
model::_Issue1992,
866+
set::S,
867+
) where {S<:Union{MOI.Nonpositives,MOI.Nonnegatives}}
868+
model.variables += 1
869+
ci = MOI.ConstraintIndex{MOI.VectorOfVariables,S}(model.variables)
870+
return MOI.VariableIndex.(1:set.dimension), ci
871+
end
872+
873+
function MOI.add_constraint(
874+
model::_Issue1992,
875+
::F,
876+
::S,
877+
) where {F<:MOI.VectorAffineFunction{Float64},S<:MOI.Nonnegatives}
878+
model.constraints += 1
879+
return MOI.ConstraintIndex{F,S}(model.constraints)
880+
end
881+
882+
function MOI.supports_constraint(
883+
::_Issue1992,
884+
::Type{MOI.VectorAffineFunction{Float64}},
885+
::Type{MOI.Nonnegatives},
886+
)
887+
return true
888+
end
889+
890+
function MOI.supports(
891+
model::_Issue1992,
892+
::MOI.ConstraintDualStart,
893+
::Type{MOI.ConstraintIndex{F,MOI.Nonnegatives}},
894+
) where {F<:MOI.VectorAffineFunction{Float64}}
895+
return model.supports
896+
end
897+
898+
function test_Issue1992_supports_ConstraintDualStart_VariableIndex()
899+
# supports should be false
900+
model = MOI.Bridges.full_bridge_optimizer(_Issue1992(false), Float64)
901+
x, _ = MOI.add_constrained_variables(model, MOI.Nonpositives(1))
902+
c = MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.Nonnegatives(1))
903+
@test !MOI.supports(model, MOI.ConstraintDualStart(), typeof(c))
904+
# supports should be true
905+
model = MOI.Bridges.full_bridge_optimizer(_Issue1992(true), Float64)
906+
x, _ = MOI.add_constrained_variables(model, MOI.Nonpositives(1))
907+
c = MOI.add_constraint(model, MOI.VectorOfVariables(x), MOI.Nonnegatives(1))
908+
# !!! warning
909+
# This test is broken with a false negative. See the discussion in
910+
# PR#1992.
911+
@test_broken MOI.supports(model, MOI.ConstraintDualStart(), typeof(c))
912+
return
913+
end
914+
915+
function test_bridge_supports_issue_1992()
916+
inner = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
917+
model = MOI.Bridges.Variable.NonposToNonneg{Float64}(inner)
918+
x = MOI.add_variable(model)
919+
c = MOI.add_constraint(
920+
model,
921+
MOI.VectorOfVariables([x]),
922+
MOI.Nonpositives(1),
923+
)
924+
# !!! warning
925+
# This test is broken with a false negative. (Getting and setting the
926+
# attribute works, even though supports is false) See the discussion in
927+
# PR#1992.
928+
@test_broken MOI.supports(model, MOI.ConstraintDualStart(), typeof(c))
929+
@test MOI.get(model, MOI.ConstraintDualStart(), c) === nothing
930+
MOI.set(model, MOI.ConstraintDualStart(), c, [1.0])
931+
@test MOI.get(model, MOI.ConstraintDualStart(), c) == [1.0]
932+
return
933+
end
934+
850935
end # module
851936

852937
TestBridgeOptimizer.runtests()

0 commit comments

Comments
 (0)