diff --git a/src/Bridges/Constraint/bridges/slack.jl b/src/Bridges/Constraint/bridges/slack.jl index 4109d9ccbf..65ce399a7c 100644 --- a/src/Bridges/Constraint/bridges/slack.jl +++ b/src/Bridges/Constraint/bridges/slack.jl @@ -124,26 +124,46 @@ end function MOI.get( model::MOI.ModelLike, a::Union{MOI.ConstraintDual,MOI.ConstraintDualStart}, - bridge::_AbstractSlackBridge, -) + bridge::_AbstractSlackBridge{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. - return MOI.get(model, a, bridge.slack_in_set) + # `-dual_slack_in_set + dual_equality = 0` so the two duals are + # 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 + return dual 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 @@ -209,6 +229,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 @@ -226,7 +247,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: @@ -341,6 +362,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 @@ -358,7 +380,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( 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