Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for starting values for slack bridges #938

Merged
merged 1 commit into from
Oct 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 115 additions & 108 deletions src/Bridges/Constraint/slack.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,101 @@
abstract type AbstractSlackBridge{T, VF, ZS, F, S} <: AbstractBridge end

function MOIB.added_constrained_variable_types(::Type{<:AbstractSlackBridge{T, VF, ZS, F, S}}) where {T, VF, ZS, F, S}
return [(S,)]
end
function MOIB.added_constraint_types(::Type{<:AbstractSlackBridge{T, VF, ZS, F}}) where {T, VF, ZS, F}
return [(F, ZS)]
end

function MOI.get(::AbstractSlackBridge{T, VF, ZS, F},
::MOI.NumberOfConstraints{F, ZS}) where {T, VF, ZS, F}
return 1
end
function MOI.get(::AbstractSlackBridge{T, VF, ZS, F, S},
::MOI.NumberOfConstraints{VF, S}) where {T, VF, ZS, F, S}
return 1
end
function MOI.get(bridge::AbstractSlackBridge{T, VF, ZS, F},
::MOI.ListOfConstraintIndices{F, ZS}) where {T, VF, ZS, F}
return [bridge.equality]
end
function MOI.get(bridge::AbstractSlackBridge{T, VF, ZS, F, S},
::MOI.ListOfConstraintIndices{VF, S}) where {T, VF, ZS, F, S}
return [bridge.slack_in_set]
end

# Indices
function MOI.delete(model::MOI.ModelLike, bridge::AbstractSlackBridge)
MOI.delete(model, bridge.equality)
MOI.delete(model, bridge.slack)
return
end

# Attributes, Bridge acting as a constraint
function MOI.supports(
::MOI.ModelLike,
::Union{MOI.ConstraintPrimalStart, MOI.ConstraintDualStart},
::Type{<:AbstractSlackBridge})

return true
end
function MOI.get(model::MOI.ModelLike,
attr::Union{MOI.ConstraintPrimal, MOI.ConstraintPrimalStart},
bridge::AbstractSlackBridge)
# due to equality, slack should have the same value as original affine function
return MOI.get(model, attr, bridge.slack_in_set)
end
function MOI.set(model::MOI.ModelLike,
attr::MOI.ConstraintPrimalStart,
bridge::AbstractSlackBridge,
value)
if bridge isa ScalarSlackBridge
MOI.set(model, MOI.VariablePrimalStart(), bridge.slack, value)
else
MOI.set.(model, MOI.VariablePrimalStart(), bridge.slack, value)
end
MOI.set(model, attr, bridge.slack_in_set, value)
MOI.set(model, attr, bridge.equality, zero(value))
end
function MOI.get(model::MOI.ModelLike,
a::Union{MOI.ConstraintDual, MOI.ConstraintDualStart},
bridge::AbstractSlackBridge)
# 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)
end
function MOI.set(model::MOI.ModelLike,
attr::MOI.ConstraintDualStart,
bridge::AbstractSlackBridge,
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.
MOI.set(model, attr, bridge.slack_in_set, value)
MOI.set(model, attr, bridge.equality, value)
end

function MOI.modify(model::MOI.ModelLike, bridge::AbstractSlackBridge,
change::MOI.AbstractFunctionModification)
MOI.modify(model, bridge.equality, change)
end

function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintSet,
bridge::AbstractSlackBridge{T, VF, ZS, F, S}, change::S) where {T, VF, ZS, F, S}
MOI.set(model, MOI.ConstraintSet(), bridge.slack_in_set, change)
end

function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction,
bridge::AbstractSlackBridge)
return MOIU.remove_variable(MOI.get(model, attr, bridge.equality), bridge.slack)
end
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet,
bridge::AbstractSlackBridge)
return MOI.get(model, attr, bridge.slack_in_set)
end

# scalar version

"""
Expand All @@ -8,7 +106,7 @@ from `SingleVariable` into the constraints `F`-in-`EqualTo{T}` and `SingleVariab
`F` is the result of subtracting a `SingleVariable` from `G`.
Tipically `G` is the same as `F`, but that is not mandatory.
"""
struct ScalarSlackBridge{T, F, S} <: AbstractBridge
struct ScalarSlackBridge{T, F, S} <: AbstractSlackBridge{T, MOI.SingleVariable, MOI.EqualTo{T}, F, S}
slack::MOI.VariableIndex
slack_in_set::CI{MOI.SingleVariable, S}
equality::CI{F, MOI.EqualTo{T}}
Expand All @@ -35,12 +133,6 @@ MOI.supports_constraint(::Type{ScalarSlackBridge{T}},
MOI.supports_constraint(::Type{ScalarSlackBridge{T}},
::Type{<:MOI.AbstractScalarFunction},
::Type{<:MOI.EqualTo}) where {T} = false
function MOIB.added_constrained_variable_types(::Type{<:ScalarSlackBridge{T, F, S}}) where {T, F, S}
return [(S,)]
end
function MOIB.added_constraint_types(::Type{ScalarSlackBridge{T, F, S}}) where {T, F, S}
return [(F, MOI.EqualTo{T})]
end
function concrete_bridge_type(::Type{<:ScalarSlackBridge{T}},
F::Type{<:MOI.AbstractScalarFunction},
S::Type{<:MOI.AbstractScalarSet}) where T
Expand All @@ -51,55 +143,16 @@ end
# Attributes, Bridge acting as a model
MOI.get(b::ScalarSlackBridge, ::MOI.NumberOfVariables) = 1
MOI.get(b::ScalarSlackBridge, ::MOI.ListOfVariableIndices) = [b.slack]
MOI.get(b::ScalarSlackBridge{T, F}, ::MOI.NumberOfConstraints{F, MOI.EqualTo{T}}) where {T, F} = 1
MOI.get(b::ScalarSlackBridge{T, F, S}, ::MOI.NumberOfConstraints{MOI.SingleVariable, S}) where {T, F, S} = 1
MOI.get(b::ScalarSlackBridge{T, F}, ::MOI.ListOfConstraintIndices{F, MOI.EqualTo{T}}) where {T, F} = [b.equality]
MOI.get(b::ScalarSlackBridge{T, F, S}, ::MOI.ListOfConstraintIndices{MOI.SingleVariable, S}) where {T, F, S} = [b.slack_in_set]

# Indices
function MOI.delete(model::MOI.ModelLike, c::ScalarSlackBridge)
MOI.delete(model, c.equality)
MOI.delete(model, c.slack)
return
end

# Attributes, Bridge acting as a constraint
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal, c::ScalarSlackBridge)
# due to equality, slack should have the same value as original affine function
return MOI.get(model, attr, c.slack_in_set)
end
function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintDual, c::ScalarSlackBridge)
# 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, c.slack_in_set)
end
function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintBasisStatus, c::ScalarSlackBridge)
MOI.get(model, MOI.ConstraintBasisStatus(), c.slack_in_set)
end

# Constraints
function MOI.modify(model::MOI.ModelLike, c::ScalarSlackBridge, change::MOI.AbstractFunctionModification)
MOI.modify(model, c.equality, change)
function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintBasisStatus, bridge::ScalarSlackBridge)
MOI.get(model, MOI.ConstraintBasisStatus(), bridge.slack_in_set)
end

function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintFunction,
c::ScalarSlackBridge{T, F, S}, func::F) where {T, F, S}
new_func = MOIU.operate(-, T, func, MOI.SingleVariable(c.slack))
MOI.set(model, MOI.ConstraintFunction(), c.equality, new_func)
end

function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintSet, c::ScalarSlackBridge{T, F, S}, change::S) where {T, F, S}
MOI.set(model, MOI.ConstraintSet(), c.slack_in_set, change)
end

function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction,
b::ScalarSlackBridge{T}) where T
return MOIU.remove_variable(MOI.get(model, attr, b.equality), b.slack)
end
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet,
b::ScalarSlackBridge)
return MOI.get(model, attr, b.slack_in_set)
bridge::ScalarSlackBridge{T, F, S}, func::F) where {T, F, S}
new_func = MOIU.operate(-, T, func, MOI.SingleVariable(bridge.slack))
MOI.set(model, MOI.ConstraintFunction(), bridge.equality, new_func)
end

# vector version
Expand All @@ -112,18 +165,18 @@ from `VectorOfVariables` into the constraints `F`in-`Zeros` and `VectorOfVariabl
`F` is the result of subtracting a `VectorOfVariables` from `G`.
Tipically `G` is the same as `F`, but that is not mandatory.
"""
struct VectorSlackBridge{T, F, S} <: AbstractBridge
slacks::Vector{MOI.VariableIndex}
slacks_in_set::CI{MOI.VectorOfVariables, S}
struct VectorSlackBridge{T, F, S} <: AbstractSlackBridge{T, MOI.VectorOfVariables, MOI.Zeros, F, S}
slack::Vector{MOI.VariableIndex}
slack_in_set::CI{MOI.VectorOfVariables, S}
equality::CI{F, MOI.Zeros}
end
function bridge_constraint(::Type{VectorSlackBridge{T, F, S}}, model,
f::MOI.AbstractVectorFunction, s::S) where {T, F, S}
d = MOI.dimension(s)
slacks, slacks_in_set = MOI.add_constrained_variables(model, s)
new_f = MOIU.operate(-, T, f, MOI.VectorOfVariables(slacks))
slack, slack_in_set = MOI.add_constrained_variables(model, s)
new_f = MOIU.operate(-, T, f, MOI.VectorOfVariables(slack))
equality = MOI.add_constraint(model, new_f, MOI.Zeros(d))
return VectorSlackBridge{T, F, S}(slacks, slacks_in_set, equality)
return VectorSlackBridge{T, F, S}(slack, slack_in_set, equality)
end

MOI.supports_constraint(::Type{VectorSlackBridge{T}},
Expand All @@ -138,12 +191,6 @@ MOI.supports_constraint(::Type{VectorSlackBridge{T}},
MOI.supports_constraint(::Type{VectorSlackBridge{T}},
::Type{<:MOI.VectorOfVariables},
::Type{<:MOI.AbstractVectorSet}) where {T} = false
function MOIB.added_constrained_variable_types(::Type{<:VectorSlackBridge{T, F, S}}) where {T, F, S}
return [(S,)]
end
function MOIB.added_constraint_types(::Type{VectorSlackBridge{T, F, S}}) where {T, F<:MOI.AbstractVectorFunction, S}
return [(F, MOI.Zeros)]
end
function concrete_bridge_type(::Type{<:VectorSlackBridge{T}},
F::Type{<:MOI.AbstractVectorFunction},
S::Type{<:MOI.AbstractVectorSet}) where T
Expand All @@ -152,52 +199,12 @@ function concrete_bridge_type(::Type{<:VectorSlackBridge{T}},
end

# Attributes, Bridge acting as a model
MOI.get(b::VectorSlackBridge, ::MOI.NumberOfVariables) = length(b.slacks)
MOI.get(b::VectorSlackBridge, ::MOI.ListOfVariableIndices) = b.slacks
MOI.get(b::VectorSlackBridge{T, F}, ::MOI.NumberOfConstraints{F, MOI.Zeros}) where {T, F} = 1
MOI.get(b::VectorSlackBridge{T, F, S}, ::MOI.NumberOfConstraints{MOI.VectorOfVariables, S}) where {T, F, S} = 1
MOI.get(b::VectorSlackBridge{T, F}, ::MOI.ListOfConstraintIndices{F, MOI.Zeros}) where {T, F} = [b.equality]
MOI.get(b::VectorSlackBridge{T, F, S}, ::MOI.ListOfConstraintIndices{MOI.VectorOfVariables, S}) where {T, F, S} = [b.slacks_in_set]

# Indices
function MOI.delete(model::MOI.ModelLike, c::VectorSlackBridge)
MOI.delete(model, c.equality)
MOI.delete(model, c.slacks)
return
end
MOI.get(b::VectorSlackBridge, ::MOI.NumberOfVariables) = length(b.slack)
MOI.get(b::VectorSlackBridge, ::MOI.ListOfVariableIndices) = b.slack

# Attributes, Bridge acting as a constraint
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal, c::VectorSlackBridge)
# due to equality, slacks should have the same value as original affine function
return MOI.get(model, attr, c.slacks_in_set)
end
function MOI.get(model::MOI.ModelLike, a::MOI.ConstraintDual, c::VectorSlackBridge)
# 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, c.slacks_in_set)
end

# Constraints
function MOI.modify(model::MOI.ModelLike, c::VectorSlackBridge, change::MOI.AbstractFunctionModification)
MOI.modify(model, c.equality, change)
end

function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintFunction,
c::VectorSlackBridge{T, F, S}, func::F) where {T, F, S}
new_func = MOIU.operate(-, T, func, MOI.VectorAffineFunction{T}(MOI.VectorOfVariables(c.slacks)))
MOI.set(model, MOI.ConstraintFunction(), c.equality, new_func)
end

function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintSet, c::VectorSlackBridge{T,F,S}, change::S) where {T, F, S}
MOI.set(model, MOI.ConstraintSet(), c.slacks_in_set, change)
end

function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction,
b::VectorSlackBridge{T}) where T
return MOIU.remove_variable(MOI.get(model, attr, b.equality), b.slacks)
end
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet,
b::VectorSlackBridge)
return MOI.get(model, attr, b.slacks_in_set)
bridge::VectorSlackBridge{T, F, S}, func::F) where {T, F, S}
new_func = MOIU.operate(-, T, func, MOI.VectorAffineFunction{T}(MOI.VectorOfVariables(bridge.slack)))
MOI.set(model, MOI.ConstraintFunction(), bridge.equality, new_func)
end
32 changes: 31 additions & 1 deletion test/Bridges/Constraint/slack.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const MOIB = MathOptInterface.Bridges

include("../utilities.jl")

mock = MOIU.MockOptimizer(MOIU.Model{Float64}())
mock = MOIU.MockOptimizer(MOIU.UniversalFallback(MOIU.Model{Float64}()))
config = MOIT.TestConfig()

@testset "Scalar slack" begin
Expand All @@ -19,6 +19,7 @@ config = MOIT.TestConfig()
y = MOI.add_variable(bridged_mock)
f = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}.([1.0, 2.0], [x, y]), 0.0)
ci = MOI.add_constraint(bridged_mock, f, MOI.GreaterThan(0.0))

@test MOI.get(bridged_mock, MOI.ConstraintFunction(), ci) ≈ f
newf = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}.([2.0, 1.0], [x, y]), 0.0)
MOI.set(bridged_mock, MOI.ConstraintFunction(), ci, newf)
Expand All @@ -29,6 +30,20 @@ config = MOIT.TestConfig()
MOI.modify(bridged_mock, ci, MOI.ScalarConstantChange{Float64}(1.0))
@test MOI.get(bridged_mock, MOI.ConstraintFunction(), ci) ≈
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm{Float64}.([2.0, 1.0], [x, y]), 1.0)

@testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()]
@test MOI.supports(bridged_mock, attr, typeof(ci))
MOI.set(bridged_mock, attr, ci, 2.0)
@test MOI.get(bridged_mock, attr, ci) == 2.0
bridge = MOIB.bridge(bridged_mock, ci)
if attr isa MOI.ConstraintPrimalStart
@test MOI.get(mock, MOI.VariablePrimalStart(), bridge.slack) == 2.0
@test MOI.get(mock, attr, bridge.equality) == 0.0
else
@test MOI.get(mock, attr, bridge.equality) == 2.0
end
end

test_delete_bridge(bridged_mock, ci, 2, (
(MOI.SingleVariable, MOI.GreaterThan{Float64}, 0),
(MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, 0)
Expand Down Expand Up @@ -94,6 +109,7 @@ end
y = MOI.add_variable(bridged_mock)
f = MOI.VectorAffineFunction(MOI.VectorAffineTerm.(1, MOI.ScalarAffineTerm.([1.0, 2.0], [x, y])), [0.0])
ci = MOI.add_constraint(bridged_mock, f, MOI.Nonpositives(1))

@test MOI.get(bridged_mock, MOI.ConstraintFunction(), ci) ≈ f
newf = MOI.VectorAffineFunction(MOI.VectorAffineTerm.(1, MOI.ScalarAffineTerm.([2.0, 1.0], [x, y])), [0.0])
MOI.set(bridged_mock, MOI.ConstraintFunction(), ci, newf)
Expand All @@ -102,6 +118,20 @@ end
MOI.modify(bridged_mock, ci, MOI.VectorConstantChange([1.0]))
@test MOI.get(bridged_mock, MOI.ConstraintFunction(), ci) ≈
MOI.VectorAffineFunction(MOI.VectorAffineTerm.(1, MOI.ScalarAffineTerm.([2.0, 1.0], [x, y])), [1.0])

@testset "$attr" for attr in [MOI.ConstraintPrimalStart(), MOI.ConstraintDualStart()]
@test MOI.supports(bridged_mock, attr, typeof(ci))
MOI.set(bridged_mock, attr, ci, [2.0])
@test MOI.get(bridged_mock, attr, ci) == [2.0]
bridge = MOIB.bridge(bridged_mock, ci)
if attr isa MOI.ConstraintPrimalStart
@test MOI.get(mock, MOI.VariablePrimalStart(), bridge.slack) == [2.0]
@test MOI.get(mock, attr, bridge.equality) == [0.0]
else
@test MOI.get(mock, attr, bridge.equality) == [2.0]
end
end

test_delete_bridge(bridged_mock, ci, 2, (
(MOI.VectorOfVariables, MOI.Nonpositives, 0),
(MOI.VectorAffineFunction{Float64}, MOI.Zeros, 0)
Expand Down