From 514c8b665d2359e2377880c45d6cf58004880044 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Mon, 24 Aug 2026 09:20:07 +1200 Subject: [PATCH 1/2] [Bridges.constraint] change conversion cost to 0.5 for ScalarAffineFunction --- .../bridges/AbstractFunctionConversionBridge.jl | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl b/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl index 80ea8a852d..e551bd13f6 100644 --- a/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl +++ b/src/Bridges/Constraint/bridges/AbstractFunctionConversionBridge.jl @@ -275,7 +275,22 @@ function conversion_cost( ::Type{<:MOI.ScalarAffineFunction}, ::Type{MOI.VariableIndex}, ) - return 1.0 + # We want to favor bridges which convert VariableIndex or VectorOfVariables + # into ScalarAffineFunction or VectorAffineFunction. + # + # This is most useful for conic solvers, which often support + # VectorAffineFunction-in-S, but not VectorOfVariables-in-S. + # + # The value in this function controls the cost of the transformation + # VectorOfVariables-in-S -> VectorAffineFunction-in-S. When we had the + # previous cost of `1.0`, there might be an alternative bridge + # VectorOfVariables-in-S -> VectorAffineFunction-in-S2 which also has a cost + # of `1.0`, and so, depending on the order in which we choose bridges, we + # might end up using a more complicated bridge that adds a different set + # type to the model. + # + # This was a practical concern for Hypatia in MathOptInterface.jl#3062. + return 0.5 end function conversion_cost( From a8bb0bd1320e5bc01a22ec1e09eaccdeefb6445b Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Mon, 24 Aug 2026 11:33:44 +1200 Subject: [PATCH 2/2] Fix --- test/Bridges/General/test_lazy_bridge_optimizer.jl | 14 +++++++------- test/Utilities/test_copy.jl | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/Bridges/General/test_lazy_bridge_optimizer.jl b/test/Bridges/General/test_lazy_bridge_optimizer.jl index 2e73b72c6f..65c5ad9b37 100644 --- a/test/Bridges/General/test_lazy_bridge_optimizer.jl +++ b/test/Bridges/General/test_lazy_bridge_optimizer.jl @@ -1150,14 +1150,14 @@ Objective function of type `MOI.ScalarQuadraticFunction{$T}` is not supported an """ Bridge graph with 2 variable nodes, 5 constraint nodes and 2 objective nodes. [1] constrained variables in `MOI.Reals` are bridged (distance 1) by $(MOI.Bridges.Variable.FreeBridge{T}). - [2] constrained variables in `MOI.RotatedSecondOrderCone` are bridged (distance 11) by $(MOI.Bridges.Variable.RSOCtoPSDBridge{T}). - (1) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.GreaterThan{$T}` constraints are bridged (distance 14) by $(MOI.Bridges.Constraint.QuadtoSOCBridge{T}). - (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.RotatedSecondOrderCone` constraints are bridged (distance 13) by $(MOI.Bridges.Constraint.VectorSlackBridge{T,MOI.VectorAffineFunction{T},MOI.RotatedSecondOrderCone}). - (3) `MOI.VariableIndex`-in-`MOI.EqualTo{$T}` constraints are bridged (distance 1) by $(MOI.Bridges.Constraint.ScalarFunctionizeBridge{T,MOI.EqualTo{T}}). + [2] constrained variables in `MOI.RotatedSecondOrderCone` are bridged (distance 10.5) by $(MOI.Bridges.Variable.RSOCtoPSDBridge{T}). + (1) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.GreaterThan{$T}` constraints are bridged (distance 13.5) by $(MOI.Bridges.Constraint.QuadtoSOCBridge{T}). + (2) `MOI.VectorAffineFunction{$T}`-in-`MOI.RotatedSecondOrderCone` constraints are bridged (distance 12.5) by $(MOI.Bridges.Constraint.VectorSlackBridge{T,MOI.VectorAffineFunction{T},MOI.RotatedSecondOrderCone}). + (3) `MOI.VariableIndex`-in-`MOI.EqualTo{$T}` constraints are bridged (distance 0.5) by $(MOI.Bridges.Constraint.ScalarFunctionizeBridge{T,MOI.EqualTo{T}}). (4) `MOI.VectorAffineFunction{$T}`-in-`MOI.Zeros` constraints are bridged (distance 1) by $(MOI.Bridges.Constraint.ScalarizeBridge{T,MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}). - (5) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.LessThan{$T}` constraints are bridged (distance 14) by $(MOI.Bridges.Constraint.QuadtoSOCBridge{T}). - |1| objective function of type `MOI.ScalarQuadraticFunction{$T}` is bridged (distance 31) by $(MOI.Bridges.Objective.SlackBridge{T,MOI.ScalarQuadraticFunction{T},MOI.ScalarQuadraticFunction{T}}). - |2| objective function of type `MOI.VariableIndex` is bridged (distance 1) by $(MOI.Bridges.Objective.FunctionizeBridge{T,MOI.VariableIndex}). + (5) `MOI.ScalarQuadraticFunction{$T}`-in-`MOI.LessThan{$T}` constraints are bridged (distance 13.5) by $(MOI.Bridges.Constraint.QuadtoSOCBridge{T}). + |1| objective function of type `MOI.ScalarQuadraticFunction{$T}` is bridged (distance 29.5) by $(MOI.Bridges.Objective.SlackBridge{T,MOI.ScalarQuadraticFunction{T},MOI.ScalarQuadraticFunction{T}}). + |2| objective function of type `MOI.VariableIndex` is bridged (distance 0.5) by $(MOI.Bridges.Objective.FunctionizeBridge{T,MOI.VariableIndex}). """, ) end diff --git a/test/Utilities/test_copy.jl b/test/Utilities/test_copy.jl index ef3c8671a1..32bd233693 100644 --- a/test/Utilities/test_copy.jl +++ b/test/Utilities/test_copy.jl @@ -503,7 +503,7 @@ function test_create_variables_using_supports_add_constrained_variable() @test MOI.get( bridged_dest, MOI.ConstraintBridgingCost{MOI.VectorOfVariables,MOI.Nonpositives}(), - ) == 1.0 + ) == 0.5 @test MOI.supports_add_constrained_variables(bridged_dest, MOI.Zeros) @test MOI.get(bridged_dest, MOI.VariableBridgingCost{MOI.Zeros}()) == 1.0 @test MOI.supports_constraint( @@ -514,7 +514,7 @@ function test_create_variables_using_supports_add_constrained_variable() @test MOI.get( bridged_dest, MOI.ConstraintBridgingCost{MOI.VectorOfVariables,MOI.Zeros}(), - ) == 3.0 + ) == 2.5 index_map = MOI.copy_to(bridged_dest, src) @test length(dest.constraintIndices) == 6