From 8c8252601e1775eed80b45bcf0e3f2ddbe308115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 26 May 2024 22:46:13 +0200 Subject: [PATCH 01/10] [Bridges] use dual of equality in slack bridge (#2508) --- src/Bridges/Constraint/bridges/slack.jl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Bridges/Constraint/bridges/slack.jl b/src/Bridges/Constraint/bridges/slack.jl index 4109d9ccbf..3bc5c8ea3d 100644 --- a/src/Bridges/Constraint/bridges/slack.jl +++ b/src/Bridges/Constraint/bridges/slack.jl @@ -127,9 +127,15 @@ function MOI.get( bridge::_AbstractSlackBridge, ) # The dual constraint on slack (since it is free) is - # -dual_slack_in_set + dual_equality = 0 so the two duals are + # `-dual_slack_in_set + dual_equality = 0` so the two duals are # equal and we can return either one of them. - return MOI.get(model, a, bridge.slack_in_set) + # We decide to use the dual of the equality constraints because + # the `slack_in_set` constraint might be bridge by a variable bridge that + # does not # support `ConstraintDual`. This is the case if the adjoint of + # the linear map on which the bridge is based is not invertible, as for + # instance with `Variable.ZerosBridge` or + # `SumOfSquares.Bridges.Variable.KernelBridge`. + return MOI.get(model, a, bridge.equality) end function MOI.set( From b5596e897e0dd1a2d35ccb6ef0df1711333a9698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Fri, 14 Jun 2024 08:47:36 +0200 Subject: [PATCH 02/10] Add scaling --- src/Bridges/Constraint/bridges/slack.jl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Bridges/Constraint/bridges/slack.jl b/src/Bridges/Constraint/bridges/slack.jl index 3bc5c8ea3d..d7122dbd7b 100644 --- a/src/Bridges/Constraint/bridges/slack.jl +++ b/src/Bridges/Constraint/bridges/slack.jl @@ -135,7 +135,9 @@ function MOI.get( # the linear map on which the bridge is based is not invertible, as for # instance with `Variable.ZerosBridge` or # `SumOfSquares.Bridges.Variable.KernelBridge`. - return MOI.get(model, a, bridge.equality) + dual = MOI.get(model, a, bridge.equality) + scale = MOI.Utilities.SetDotScalingVector{T}(bridge.set) + return MOI.Utilities.operate(*, T, LinearAlgebra.Diagonal(scale), dual) end function MOI.set( @@ -215,6 +217,7 @@ end struct ScalarSlackBridge{T,F,S} <: _AbstractSlackBridge{T,MOI.VariableIndex,MOI.EqualTo{T},F,S} slack::MOI.VariableIndex + set::S slack_in_set::MOI.ConstraintIndex{MOI.VariableIndex,S} equality::MOI.ConstraintIndex{F,MOI.EqualTo{T}} end @@ -232,7 +235,7 @@ function bridge_constraint( slack, slack_in_set = MOI.add_constrained_variable(model, s) new_f = MOI.Utilities.operate(-, T, f, slack) equality = MOI.add_constraint(model, new_f, MOI.EqualTo(zero(T))) - return ScalarSlackBridge{T,F,S}(slack, slack_in_set, equality) + return ScalarSlackBridge{T,F,S}(slack, s, slack_in_set, equality) end # Start by allowing all scalar constraints: @@ -347,6 +350,7 @@ end struct VectorSlackBridge{T,F,S} <: _AbstractSlackBridge{T,MOI.VectorOfVariables,MOI.Zeros,F,S} slack::Vector{MOI.VariableIndex} + set::S slack_in_set::MOI.ConstraintIndex{MOI.VectorOfVariables,S} equality::MOI.ConstraintIndex{F,MOI.Zeros} end @@ -364,7 +368,7 @@ function bridge_constraint( slack, slack_in_set = MOI.add_constrained_variables(model, s) new_f = MOI.Utilities.operate(-, T, f, MOI.VectorOfVariables(slack)) equality = MOI.add_constraint(model, new_f, MOI.Zeros(d)) - return VectorSlackBridge{T,F,S}(slack, slack_in_set, equality) + return VectorSlackBridge{T,F,S}(slack, s, slack_in_set, equality) end function MOI.supports_constraint( From 0ab3542003e9e0d226dac5823627fd65f7f6edb9 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Sat, 15 Jun 2024 10:28:50 +1200 Subject: [PATCH 03/10] Update slack.jl --- src/Bridges/Constraint/bridges/slack.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Bridges/Constraint/bridges/slack.jl b/src/Bridges/Constraint/bridges/slack.jl index d7122dbd7b..21613debdc 100644 --- a/src/Bridges/Constraint/bridges/slack.jl +++ b/src/Bridges/Constraint/bridges/slack.jl @@ -124,8 +124,8 @@ end function MOI.get( model::MOI.ModelLike, a::Union{MOI.ConstraintDual,MOI.ConstraintDualStart}, - bridge::_AbstractSlackBridge, -) + bridge::_AbstractSlackBridge{T}, +) whhere {T} # The dual constraint on slack (since it is free) is # `-dual_slack_in_set + dual_equality = 0` so the two duals are # equal and we can return either one of them. From d5b9733ee0c4504d7ad68e815207bf6bb38a29bb Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Sat, 15 Jun 2024 11:23:39 +1200 Subject: [PATCH 04/10] Update src/Bridges/Constraint/bridges/slack.jl --- src/Bridges/Constraint/bridges/slack.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/Constraint/bridges/slack.jl b/src/Bridges/Constraint/bridges/slack.jl index 21613debdc..8d6efddf08 100644 --- a/src/Bridges/Constraint/bridges/slack.jl +++ b/src/Bridges/Constraint/bridges/slack.jl @@ -125,7 +125,7 @@ function MOI.get( model::MOI.ModelLike, a::Union{MOI.ConstraintDual,MOI.ConstraintDualStart}, bridge::_AbstractSlackBridge{T}, -) whhere {T} +) where {T} # The dual constraint on slack (since it is free) is # `-dual_slack_in_set + dual_equality = 0` so the two duals are # equal and we can return either one of them. From 3942f18a3b4e50393a4ac7890d1ec13e0be22104 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Sat, 15 Jun 2024 14:46:01 +1200 Subject: [PATCH 05/10] Update slack.jl --- src/Bridges/Constraint/bridges/slack.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Bridges/Constraint/bridges/slack.jl b/src/Bridges/Constraint/bridges/slack.jl index 8d6efddf08..c66be29b27 100644 --- a/src/Bridges/Constraint/bridges/slack.jl +++ b/src/Bridges/Constraint/bridges/slack.jl @@ -136,8 +136,11 @@ function MOI.get( # instance with `Variable.ZerosBridge` or # `SumOfSquares.Bridges.Variable.KernelBridge`. dual = MOI.get(model, a, bridge.equality) - scale = MOI.Utilities.SetDotScalingVector{T}(bridge.set) - return MOI.Utilities.operate(*, T, LinearAlgebra.Diagonal(scale), dual) + if dual isa AbstractVector + scale = MOI.Utilities.SetDotScalingVector{T}(bridge.set) + return MOI.Utilities.operate(*, T, LinearAlgebra.Diagonal(scale), dual) + end + return scale[1] * dual end function MOI.set( From b01f53671ef1cf4756b201df806f9bd82e6d9771 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Sat, 15 Jun 2024 17:54:22 +1200 Subject: [PATCH 06/10] Update slack.jl --- src/Bridges/Constraint/bridges/slack.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/Constraint/bridges/slack.jl b/src/Bridges/Constraint/bridges/slack.jl index c66be29b27..eb65feca5b 100644 --- a/src/Bridges/Constraint/bridges/slack.jl +++ b/src/Bridges/Constraint/bridges/slack.jl @@ -136,8 +136,8 @@ function MOI.get( # instance with `Variable.ZerosBridge` or # `SumOfSquares.Bridges.Variable.KernelBridge`. dual = MOI.get(model, a, bridge.equality) + scale = MOI.Utilities.SetDotScalingVector{T}(bridge.set) if dual isa AbstractVector - scale = MOI.Utilities.SetDotScalingVector{T}(bridge.set) return MOI.Utilities.operate(*, T, LinearAlgebra.Diagonal(scale), dual) end return scale[1] * dual From cd8a8b68aaccd8fadf019b5effabdcead9de2a8e Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Sun, 16 Jun 2024 11:37:06 +1200 Subject: [PATCH 07/10] Update slack.jl --- src/Bridges/Constraint/bridges/slack.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Bridges/Constraint/bridges/slack.jl b/src/Bridges/Constraint/bridges/slack.jl index eb65feca5b..664d4372c8 100644 --- a/src/Bridges/Constraint/bridges/slack.jl +++ b/src/Bridges/Constraint/bridges/slack.jl @@ -136,6 +136,9 @@ function MOI.get( # instance with `Variable.ZerosBridge` or # `SumOfSquares.Bridges.Variable.KernelBridge`. dual = MOI.get(model, a, bridge.equality) + if dual === nothing + return nothing + end scale = MOI.Utilities.SetDotScalingVector{T}(bridge.set) if dual isa AbstractVector return MOI.Utilities.operate(*, T, LinearAlgebra.Diagonal(scale), dual) From 443c0a6412b3dd6010eb67b6b76d9ef7cb7164be Mon Sep 17 00:00:00 2001 From: odow Date: Mon, 17 Jun 2024 10:50:52 +1200 Subject: [PATCH 08/10] Update --- src/Bridges/Constraint/bridges/slack.jl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Bridges/Constraint/bridges/slack.jl b/src/Bridges/Constraint/bridges/slack.jl index 664d4372c8..bf6268f9b3 100644 --- a/src/Bridges/Constraint/bridges/slack.jl +++ b/src/Bridges/Constraint/bridges/slack.jl @@ -138,12 +138,11 @@ function MOI.get( dual = MOI.get(model, a, bridge.equality) if dual === nothing return nothing + elseif dual isa AbstractVector + scale = MOI.Utilities.SetDotScalingVector{T}(bridge.set) + return dual ./ scale.^2 end - scale = MOI.Utilities.SetDotScalingVector{T}(bridge.set) - if dual isa AbstractVector - return MOI.Utilities.operate(*, T, LinearAlgebra.Diagonal(scale), dual) - end - return scale[1] * dual + return dual end function MOI.set( From 397b7794f2e29dd4a391ef54833f7eb64f6743ff Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Mon, 17 Jun 2024 11:04:23 +1200 Subject: [PATCH 09/10] Update src/Bridges/Constraint/bridges/slack.jl --- src/Bridges/Constraint/bridges/slack.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bridges/Constraint/bridges/slack.jl b/src/Bridges/Constraint/bridges/slack.jl index bf6268f9b3..ff68c434a2 100644 --- a/src/Bridges/Constraint/bridges/slack.jl +++ b/src/Bridges/Constraint/bridges/slack.jl @@ -140,7 +140,7 @@ function MOI.get( return nothing elseif dual isa AbstractVector scale = MOI.Utilities.SetDotScalingVector{T}(bridge.set) - return dual ./ scale.^2 + return dual ./ scale .^ 2 end return dual end From 9f99f35a01b14d985fc075926b87bc7be3f3e669 Mon Sep 17 00:00:00 2001 From: odow Date: Tue, 18 Jun 2024 10:02:43 +1200 Subject: [PATCH 10/10] Update --- src/Bridges/Constraint/bridges/slack.jl | 35 +++++++++++++++---------- test/Bridges/Constraint/slack.jl | 12 +++++++++ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/Bridges/Constraint/bridges/slack.jl b/src/Bridges/Constraint/bridges/slack.jl index ff68c434a2..65ce399a7c 100644 --- a/src/Bridges/Constraint/bridges/slack.jl +++ b/src/Bridges/Constraint/bridges/slack.jl @@ -128,17 +128,22 @@ function MOI.get( ) where {T} # The dual constraint on slack (since it is free) is # `-dual_slack_in_set + dual_equality = 0` so the two duals are - # equal and we can return either one of them. - # We decide to use the dual of the equality constraints because - # the `slack_in_set` constraint might be bridge by a variable bridge that - # does not # support `ConstraintDual`. This is the case if the adjoint of - # the linear map on which the bridge is based is not invertible, as for - # instance with `Variable.ZerosBridge` or - # `SumOfSquares.Bridges.Variable.KernelBridge`. + # equal (modulo a rescaling for things like symmetric matrices) and we can + # return either one of them. + # + # We decide to use the dual of the equality constraints because the + # `slack_in_set` constraint might be bridged by a variable bridge that does + # not support `ConstraintDual`. This is the case if the adjoint of the + # linear map on which the bridge is based is not invertible, for example, + # `Variable.ZerosBridge` or `SumOfSquares.Bridges.Variable.KernelBridge`. dual = MOI.get(model, a, bridge.equality) if dual === nothing return nothing elseif dual isa AbstractVector + # The equality constraints gives the term with the + # standard inner product but _PSD is like scaling each + # entry of dual and primal by the entry of SetDotScalingVector. To undo, + # we need to divide by the square. scale = MOI.Utilities.SetDotScalingVector{T}(bridge.set) return dual ./ scale .^ 2 end @@ -148,15 +153,17 @@ end function MOI.set( model::MOI.ModelLike, attr::MOI.ConstraintDualStart, - bridge::_AbstractSlackBridge, + bridge::_AbstractSlackBridge{T}, value, -) - # As the slack appears `+slack` in `slack_in_set` and `-slack` in equality, - # giving `value` to both will cancel it out in the Lagrangian. - # Giving `value` to `bridge.equality` will put the function in the - # Lagrangian as expected. +) where {T} + # See comments in MOI.get for why we need to rescale, etc. MOI.set(model, attr, bridge.slack_in_set, value) - MOI.set(model, attr, bridge.equality, value) + if value isa AbstractVector + scale = MOI.Utilities.SetDotScalingVector{T}(bridge.set) + MOI.set(model, attr, bridge.equality, value .* scale .^ 2) + else + MOI.set(model, attr, bridge.equality, value) + end return end diff --git a/test/Bridges/Constraint/slack.jl b/test/Bridges/Constraint/slack.jl index 1f89f984e9..66096a3687 100644 --- a/test/Bridges/Constraint/slack.jl +++ b/test/Bridges/Constraint/slack.jl @@ -384,6 +384,18 @@ function test_runtests() [y, z] in SecondOrderCone(2) """, ) + MOI.Bridges.runtests( + MOI.Bridges.Constraint.VectorSlackBridge, + """ + variables: x11, x12, x22 + [1.0 * x11, x12, 2.0 * x22] in PositiveSemidefiniteConeTriangle(2) + """, + """ + variables: x11, x12, x22, y11, y12, y22 + [1.0 * x11 + -1.0 * y11, x12 + -1.0 * y12, 2.0 * x22 + -1.0 * y22] in Zeros(3) + [y11, y12, y22] in PositiveSemidefiniteConeTriangle(2) + """, + ) return end