Skip to content

Commit

Permalink
Merge pull request #973 from JuliaOpt/bl/check_variable_bridge
Browse files Browse the repository at this point in the history
Only use variable bridge if it is optimal
  • Loading branch information
blegat committed Dec 19, 2019
2 parents 071e06d + 1241879 commit 9ccb510
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 26 deletions.
4 changes: 4 additions & 0 deletions src/Bridges/Variable/single_bridge_optimizer.jl
Expand Up @@ -36,6 +36,10 @@ function MOIB.supports_bridging_constrained_variable(
::SingleBridgeOptimizer{BT}, S::Type{<:MOI.AbstractSet}) where BT
return supports_constrained_variable(BT, S)
end
function MOIB.is_variable_bridged(
::SingleBridgeOptimizer, ::Type{<:MOI.AbstractSet})
return true
end
function MOIB.is_bridged(b::SingleBridgeOptimizer, S::Type{<:MOI.AbstractSet})
return MOIB.supports_bridging_constrained_variable(b, S)
end
Expand Down
56 changes: 35 additions & 21 deletions src/Bridges/bridge_optimizer.jl
Expand Up @@ -90,6 +90,39 @@ function supports_bridging_constrained_variable(
return false
end

"""
is_variable_bridged(
b::AbstractBridgeOptimizer, S::Type{<:MOI.AbstractSet})
Return a `Bool` indicating whether `b` bridges constrained variable in
`S` using a variable bridge, assuming `is_bridged(b, S)`. If it returns `false`,
it means that free variables and a constraint on these variables should be
added instead. The constraint is then bridged by a constraint bridge.
"""
function is_variable_bridged(
::AbstractBridgeOptimizer, ::Type{<:MOI.AbstractSet})
return false
end

"""
is_variable_bridged(b::AbstractBridgeOptimizer,
ci::MOI.ConstraintIndex)
Returns whether `ci` is the constraint of a bridged constrained variable. That
is, if it was returned by `Variable.add_key_for_bridge` or
`Variable.add_keys_for_bridge`. Note that it is not equivalent to
`ci.value < 0` as, it can also simply be a constraint on a bridged variable.
"""
is_variable_bridged(::AbstractBridgeOptimizer, ::MOI.ConstraintIndex) = false
function is_variable_bridged(
b::AbstractBridgeOptimizer,
ci::MOI.ConstraintIndex{<:Union{MOI.SingleVariable, MOI.VectorOfVariables}})
# It can be a constraint corresponding to bridged constrained variables so
# we `check` with `haskey(Constraint.bridges(b), ci)` whether this is the
# case.
return ci.value < 0 && !haskey(Constraint.bridges(b), ci)
end

"""
supports_bridging_constraint(
b::AbstractBridgeOptimizer,
Expand Down Expand Up @@ -127,25 +160,6 @@ This function should only be called if `is_bridged(b, F, S)`.
"""
function bridge_type end

"""
is_variable_bridged(b::AbstractBridgeOptimizer,
ci::MOI.ConstraintIndex)
Returns whether `ci` is the constraint of a bridged constrained variable. That
is, if it was returned by `Variable.add_key_for_bridge` or
`Variable.add_keys_for_bridge`. Note that it is not equivalent to
`ci.value < 0` as, it can also simply be a constraint on a bridged variable.
"""
is_variable_bridged(::AbstractBridgeOptimizer, ::MOI.ConstraintIndex) = false
function is_variable_bridged(
b::AbstractBridgeOptimizer,
ci::MOI.ConstraintIndex{<:Union{MOI.SingleVariable, MOI.VectorOfVariables}})
# It can be a constraint corresponding to bridged constrained variables so
# we `check` with `haskey(Constraint.bridges(b), ci)` whether this is the
# case.
return ci.value < 0 && !haskey(Constraint.bridges(b), ci)
end

"""
bridge(b::AbstractBridgeOptimizer, vi::MOI.VariableIndex)
Expand Down Expand Up @@ -1141,7 +1155,7 @@ function MOI.add_constrained_variables(b::AbstractBridgeOptimizer,
set::MOI.AbstractVectorSet)
if is_bridged(b, typeof(set)) ||
is_bridged(b, MOI.VectorOfVariables, typeof(set))
if set isa MOI.Reals || supports_bridging_constrained_variable(b, typeof(set))
if set isa MOI.Reals || is_variable_bridged(b, typeof(set))
BridgeType = Variable.concrete_bridge_type(b, typeof(set))
return Variable.add_keys_for_bridge(Variable.bridges(b),
() -> Variable.bridge_constrained_variable(BridgeType, b, set),
Expand All @@ -1159,7 +1173,7 @@ function MOI.add_constrained_variable(b::AbstractBridgeOptimizer,
set::MOI.AbstractScalarSet)
if is_bridged(b, typeof(set)) ||
is_bridged(b, MOI.SingleVariable, typeof(set))
if supports_bridging_constrained_variable(b, typeof(set))
if is_variable_bridged(b, typeof(set))
BridgeType = Variable.concrete_bridge_type(b, typeof(set))
return Variable.add_key_for_bridge(Variable.bridges(b),
() -> Variable.bridge_constrained_variable(BridgeType, b, set),
Expand Down
2 changes: 1 addition & 1 deletion src/Bridges/debug.jl
Expand Up @@ -18,7 +18,7 @@ function print_node_info(io::IO, b::LazyBridgeOptimizer, node::AbstractNode)
print(io, " not supported\n")
else
index = bridge_index(b.graph, node)
if iszero(index)
if iszero(index) || (node isa VariableNode && !is_variable_edge_best(b.graph, node))
@assert node isa VariableNode
println(io, " supported (distance $d) by adding free variables and then constrain them, see ($(b.graph.variable_constraint_node[node.index].index)).")
else
Expand Down
14 changes: 11 additions & 3 deletions src/Bridges/graph.jl
Expand Up @@ -152,6 +152,17 @@ function bridge_index(graph::Graph, node::ObjectiveNode)
return graph.objective_best[node.index]
end

"""
is_variable_edge_best(graph::Graph, node::VariableNode)
Return a `Bool` indicating whether the value of `_dist(graph, node)` can be
achieved with a variable bridge.
"""
function is_variable_edge_best(graph::Graph, node::VariableNode)
bellman_ford!(graph)
return graph.variable_dist[node.index] == _dist(graph, node)
end

# Update `b.variable_dist`, `b.constraint_dist` `b.variable_best` and
# `b.constraint_best` for constrained variable types in `variables` and
# constraint types in `constraints`.
Expand Down Expand Up @@ -218,9 +229,6 @@ function _dist(graph::Graph, node::VariableNode)
# If free variables are bridged but the functionize bridge was not added,
# constraint_node is `ConstraintNode(INVALID_NODE_INDEX)`.
dc = constraint_node.index == INVALID_NODE_INDEX ? INFINITY : _dist(graph, constraint_node)
if iszero(dc)
return dc
end
dv = graph.variable_dist[node.index]
if dc == INFINITY
return dv
Expand Down
4 changes: 3 additions & 1 deletion src/Bridges/lazy_bridge_optimizer.jl
Expand Up @@ -308,7 +308,9 @@ function supports_bridging_objective_function(
)
return !iszero(bridge_index(b, F))
end

function is_variable_bridged(b::LazyBridgeOptimizer, S::Type{<:MOI.AbstractSet})
return is_variable_edge_best(b.graph, node(b, S))
end
function bridge_type(b::LazyBridgeOptimizer, ::Type{MOI.Reals})
if b.variable_free_bridge_type === nothing
throw(MOI.UnsupportedConstraint{MOI.VectorOfVariables, MOI.Reals}())
Expand Down
39 changes: 39 additions & 0 deletions test/Bridges/lazy_bridge_optimizer.jl
Expand Up @@ -689,6 +689,45 @@ function MOI.supports_constraint(::NoVariableModel{T}, ::Type{MOI.SingleVariable
::Type{<:MOIU.SUPPORTED_VARIABLE_SCALAR_SETS{T}}) where T
return false
end

@testset "Continuous Conic with NoVariableModel{$T}" for T in [Float64, Float32]
model = NoVariableModel{T}()
bridged = MOIB.full_bridge_optimizer(model, T)
# The best variable bridge for SOC and RSOC are respectively `SOCtoRSOC` and `RSOCtoSOC`.
# This forms a cycle because using the variable bridges is not in the shortest path.
# Therefore they should not be used when calling `add_constrained_variables`.
# Moreover, the printing should say that the variable bridge is not used.
@test MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.SecondOrderCone)
@test MOIB.bridge_type(bridged, MOI.SecondOrderCone) == MOIB.Variable.SOCtoRSOCBridge{T}
@test !MOIB.is_variable_bridged(bridged, MOI.RotatedSecondOrderCone)
@test MOI.supports_constraint(bridged, MOI.VectorOfVariables, MOI.RotatedSecondOrderCone)
@test MOIB.bridge_type(bridged, MOI.RotatedSecondOrderCone) == MOIB.Variable.RSOCtoSOCBridge{T}
@test !MOIB.is_variable_bridged(bridged, MOI.RotatedSecondOrderCone)
x, cx = MOI.add_constrained_variables(bridged, MOI.SecondOrderCone(3))
@test !any(v -> MOIB.is_bridged(bridged, v), x)
@test !MOIB.is_variable_bridged(bridged, cx)
@test MOIB.bridge(bridged, cx) isa MOIB.Constraint.VectorFunctionizeBridge{T,MOI.SecondOrderCone}
y, cy = MOI.add_constrained_variables(bridged, MOI.RotatedSecondOrderCone(4))
@test !any(v -> MOIB.is_bridged(bridged, v), y)
@test !MOIB.is_variable_bridged(bridged, cy)
@test MOIB.bridge(bridged, cy) isa MOIB.Constraint.RSOCBridge{T}
@test sprint(MOIB.print_graph, bridged) == """
Bridge graph with 4 variable nodes, 8 variable nodes and 0 objective nodes.
[1] constrained variables in `MOI.SecondOrderCone` are supported (distance 2) by adding free variables and then constrain them, see (1).
[2] constrained variables in `MOI.RotatedSecondOrderCone` are supported (distance 2) by adding free variables and then constrain them, see (3).
[3] constrained variables in `MOI.PositiveSemidefiniteConeTriangle` are not supported
[4] constrained variables in `MOI.Nonnegatives` are supported (distance 2) by adding free variables and then constrain them, see (6).
(1) `MOI.VectorOfVariables`-in-`MOI.SecondOrderCone` constraints are bridged (distance 1) by MOIB.Constraint.VectorFunctionizeBridge{$T,MOI.SecondOrderCone}.
(2) `MOI.VectorAffineFunction{$T}`-in-`MOI.RotatedSecondOrderCone` constraints are bridged (distance 1) by MOIB.Constraint.RSOCBridge{$T,MOI.VectorAffineFunction{$T},MOI.VectorAffineFunction{$T}}.
(3) `MOI.VectorOfVariables`-in-`MOI.RotatedSecondOrderCone` constraints are bridged (distance 1) by MOIB.Constraint.RSOCBridge{$T,MOI.VectorAffineFunction{$T},MOI.VectorOfVariables}.
(4) `MOI.VectorAffineFunction{$T}`-in-`MOI.PositiveSemidefiniteConeTriangle` constraints are not supported
(5) `MOI.VectorOfVariables`-in-`MOI.PositiveSemidefiniteConeTriangle` constraints are not supported
(6) `MOI.VectorOfVariables`-in-`MOI.Nonnegatives` constraints are bridged (distance 1) by MOIB.Constraint.NonnegToNonposBridge{$T,MOI.VectorAffineFunction{$T},MOI.VectorOfVariables}.
(7) `MOI.SingleVariable`-in-`MOI.GreaterThan{$T}` constraints are bridged (distance 1) by MOIB.Constraint.GreaterToLessBridge{$T,MOI.ScalarAffineFunction{$T},MOI.SingleVariable}.
(8) `MOI.SingleVariable`-in-`MOI.EqualTo{$T}` constraints are bridged (distance 1) by MOIB.Constraint.VectorizeBridge{$T,MOI.VectorAffineFunction{$T},MOI.Zeros,MOI.SingleVariable}.
"""
end

MOIU.@model(OnlyNonnegVAF,
(), (), (MOI.Nonnegatives,), (),
(), (), (), (MOI.VectorAffineFunction,))
Expand Down

0 comments on commit 9ccb510

Please sign in to comment.