Skip to content

Commit 0ea4d27

Browse files
committed
[Bridges] change graph to favor Constraint bridges over Variable bridges
1 parent 3e3e998 commit 0ea4d27

File tree

6 files changed

+49
-12
lines changed

6 files changed

+49
-12
lines changed

src/Bridges/debug.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ function print_active_bridges(
664664
# The constraint can be both variable bridged and constraint
665665
# bridged. Which is cheaper?
666666
v_node = b.variable_node[(S,)]
667-
if bridging_cost(b.graph, v_node) <= bridging_cost(b.graph, c_node)
667+
if MOI.Bridges.is_variable_edge_best(b.graph, v_node)
668668
return print_active_bridges(io, b, S, offset)
669669
end
670670
else

src/Bridges/graph.jl

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,22 @@ constrained on creation, or as a free variable followed by a constraint.
330330
"""
331331
function is_variable_edge_best(graph::Graph, node::VariableNode)
332332
_compute_bellman_ford(graph)
333-
return graph.variable_dist[node.index] == _dist(graph, node)
333+
# This is the cost of adding a constrained variable
334+
dist_as_variable = graph.variable_dist[node.index]
335+
# This is the cost of adding the constraint, if we were to add it.
336+
dist_as_constraint = INFINITY
337+
# If free variables are bridged but the functionize bridge was not
338+
# added, constraint_node is `ConstraintNode(INVALID_NODE_INDEX)`.
339+
constraint_node = graph.variable_constraint_node[node.index]
340+
if constraint_node.index != INVALID_NODE_INDEX
341+
dist_as_constraint = _dist(graph, constraint_node)
342+
if dist_as_constraint != INFINITY
343+
dist_as_constraint += graph.variable_constraint_cost[node.index]
344+
end
345+
end
346+
# We should prefer the variable bridge ONLY if the cost is strictly less
347+
# than bridging via constraints.
348+
return dist_as_variable < dist_as_constraint
334349
end
335350

336351
function _updated_dist(

src/Bridges/lazy_bridge_optimizer.jl

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,17 @@ function node(
293293
)
294294
end
295295
else
296-
# We add `+1` as we treat constrained variables as constraints.
297-
set_variable_constraint_node(b.graph, variable_node, node(b, F, S), 1)
296+
# Add a node to the graph so we can convert a constrained variable into
297+
# a free variable plus a constraint. In MOI v1.50 and earlier, the cost
298+
# associated with this node was +1. However, practice has shown that we
299+
# want to encourage constraint bridges over variable bridges, and
300+
# penalizing the switch from constrained variable to constraint with an
301+
# additional +1 meant we often used a bridge like Variable.Vectorize.
302+
# With +0 here, it's now better to use Constraint.Vectorize.
303+
#
304+
# For more details, see:
305+
# https://github.com/jump-dev/ParametricOptInterface.jl/issues/201
306+
set_variable_constraint_node(b.graph, variable_node, node(b, F, S), +0)
298307
end
299308
for (i, BT) in enumerate(b.variable_bridge_types)
300309
if Variable.supports_constrained_variable(BT, S)

test/Bridges/General/test_bridge_optimizer.jl

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,6 +1240,16 @@ function test_issue_2452_multiple_variable_bridges()
12401240
MOI.add_constraint(src, x, MOI.LessThan(1.0))
12411241
c = MOI.add_constraint(src, 2.0 * x, MOI.EqualTo(3.0))
12421242
dest = MOI.instantiate(Model2452{Float64}; with_bridge_type = Float64)
1243+
# Remove constraint bridges so that we do
1244+
# x in LessThan -> [y] in Nonpositives, y:=x-1, [z] in Nonnegatives, z:=-y
1245+
MOI.Bridges.remove_bridge(
1246+
dest,
1247+
MOI.Bridges.Constraint.LessToGreaterBridge{Float64},
1248+
)
1249+
MOI.Bridges.remove_bridge(
1250+
dest,
1251+
MOI.Bridges.Constraint.NonposToNonnegBridge{Float64},
1252+
)
12431253
index_map = MOI.copy_to(dest, src)
12441254
set = MOI.get(dest, MOI.ConstraintSet(), index_map[c])
12451255
@test set == MOI.EqualTo(3.0)
@@ -1264,13 +1274,11 @@ function test_2452()
12641274
@test MOI.get(dest, MOI.ConstraintSet(), index_map[c]) == set
12651275
@test (
12661276
MOI.get(dest.model, MOI.ConstraintFunction(), ci),
1267-
MOI.Utilities.operate(vcat, Float64, -1.0 + 2.0 * y),
1277+
MOI.Utilities.operate(vcat, Float64, -3.0 + 2.0 * y),
12681278
)
12691279
@test MOI.get(dest.model, MOI.ConstraintSet(), ci) == MOI.Zeros(1)
1270-
@test_throws(
1271-
MOI.SetAttributeNotAllowed,
1272-
MOI.set(dest, MOI.ConstraintSet(), index_map[c], set),
1273-
)
1280+
MOI.set(dest, MOI.ConstraintSet(), index_map[c], set)
1281+
@test MOI.get(dest.model, MOI.ConstraintSet(), ci) == MOI.Zeros(1)
12741282
return
12751283
end
12761284

test/Bridges/General/test_lazy_bridge_optimizer.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,11 @@ MOI.Utilities.@model(
13061306

13071307
function _test_constrained_variables_in_RSOC(T)
13081308
bridged = MOI.Bridges.full_bridge_optimizer(NoRSOCModel{T}(), T)
1309+
# Remove the constraint bridge to force via Variable bridges.
1310+
MOI.Bridges.remove_bridge(
1311+
bridged,
1312+
MOI.Bridges.Constraint.RSOCtoSOCBridge{T},
1313+
)
13091314
@test MOI.supports_constraint(
13101315
bridged,
13111316
MOI.VectorOfVariables,

test/Utilities/test_copy.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ function test_create_variables_using_supports_add_constrained_variable()
482482
Type[MOI.Nonpositives, MOI.Zeros, MOI.Nonnegatives]
483483
@test MOI.supports_add_constrained_variables(bridged_dest, MOI.Nonnegatives)
484484
@test MOI.get(bridged_dest, MOI.VariableBridgingCost{MOI.Nonnegatives}()) ==
485-
2.0
485+
1.0
486486
@test MOI.supports_constraint(
487487
bridged_dest,
488488
MOI.VectorOfVariables,
@@ -546,7 +546,7 @@ function test_create_variables_using_supports_add_constrained_variable()
546546
@test MOI.get(
547547
bridged_dest,
548548
MOI.VariableBridgingCost{MOI.GreaterThan{Float64}}(),
549-
) == 1.0
549+
) == 0.0
550550
@test MOI.get(
551551
bridged_dest,
552552
MOI.ConstraintBridgingCost{MOI.VariableIndex,MOI.GreaterThan{Float64}}(),
@@ -560,7 +560,7 @@ function test_create_variables_using_supports_add_constrained_variable()
560560
@test MOI.get(
561561
bridged_dest,
562562
MOI.VariableBridgingCost{MOI.LessThan{Float64}}(),
563-
) == 1.0
563+
) == 0.0
564564
@test MOI.get(
565565
bridged_dest,
566566
MOI.ConstraintBridgingCost{MOI.VariableIndex,MOI.LessThan{Float64}}(),

0 commit comments

Comments
 (0)