Skip to content

Commit

Permalink
[Bridges] use dual of equality in slack bridge (take 2) (#2515)
Browse files Browse the repository at this point in the history
* [Bridges] use dual of equality in slack bridge (#2508)

* Add scaling

* Update slack.jl

* Update src/Bridges/Constraint/bridges/slack.jl

* Update slack.jl

* Update slack.jl

* Update slack.jl

* Update

* Update src/Bridges/Constraint/bridges/slack.jl

* Update

---------

Co-authored-by: Oscar Dowson <[email protected]>
Co-authored-by: odow <[email protected]>
  • Loading branch information
3 people authored Jun 18, 2024
1 parent 9d37d19 commit 193aec6
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 14 deletions.
50 changes: 36 additions & 14 deletions src/Bridges/Constraint/bridges/slack.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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 <dual, primal> with the
# standard inner product but <dual, primal>_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

Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand Down
12 changes: 12 additions & 0 deletions test/Bridges/Constraint/slack.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 193aec6

Please sign in to comment.