Skip to content
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
3 changes: 3 additions & 0 deletions src/Bridges/Constraint/Constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ const RSOCtoPSD{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{RSOCtoPSDBridge{T}
include("indicator_activate_on_zero.jl")
include("indicator_sos.jl")
const IndicatortoSOS1{T, BC <: MOI.AbstractScalarSet, MaybeBC} = SingleBridgeOptimizer{IndicatorSOS1Bridge{T, BC, MaybeBC}}
include("semi_to_binary.jl")
const SemiToBinary{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{SemiToBinaryBridge{T}, OT}

"""
add_all_bridges(bridged_model, ::Type{T})
Expand Down Expand Up @@ -98,6 +100,7 @@ function add_all_bridges(bridged_model, ::Type{T}) where {T}
MOIB.add_bridge(bridged_model, RSOCtoPSDBridge{T})
MOIB.add_bridge(bridged_model, IndicatorActiveOnFalseBridge{T})
MOIB.add_bridge(bridged_model, IndicatorSOS1Bridge{T})
MOIB.add_bridge(bridged_model, SemiToBinaryBridge{T})
return
end

Expand Down
192 changes: 192 additions & 0 deletions src/Bridges/Constraint/semi_to_binary.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
const SemiSets{T} = Union{MOI.Semicontinuous{T}, MOI.Semiinteger{T}}

"""
SemiToBinaryBridge{T, S <: MOI.AbstractScalarSet}

The `SemiToBinaryBridge` replaces an Semicontinuous constraint:
``x \\in \\mathsf{Semicontinuous}(l, u)``
is replaced by:
``z \\in \\{0, 1\\}``,
``x \\leq z \\cdot u ``,
``x \\geq z \\cdot l ``.

The `SemiToBinaryBridge` replaces an Semiinteger constraint:
``x \\in Semiinteger(l, u)``
is replaced by:
``z \\in \\{0, 1\\}``,
``x \\in \\Integer``,
``x \\leq z \\cdot u ``,
``x \\geq z \\cdot l ``.
"""
mutable struct SemiToBinaryBridge{T, S <: SemiSets{T}} <: AbstractBridge
semi_set::S
variable_index::MOI.VariableIndex
binary_variable_index::MOI.VariableIndex
binary_constraint_index::MOI.ConstraintIndex{MOI.SingleVariable, MOI.ZeroOne}
lower_bound_index::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, MOI.GreaterThan{T}}
upper_bound_index::MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, MOI.LessThan{T}}
integer_index::Union{Nothing, MOI.ConstraintIndex{MOI.SingleVariable, MOI.Integer}}
end

function bridge_constraint(::Type{SemiToBinaryBridge{T,S}}, model::MOI.ModelLike, f::MOI.SingleVariable, s::S) where {T <: Real, S<:SemiSets{T}}
binary, binary_con = MOI.add_constrained_variable(model, MOI.ZeroOne())

# var - LB * bin >= 0
lb = MOIU.operate(*, T, -s.lower, MOI.SingleVariable(binary))
lb = MOIU.operate!(+, T, lb, f)
lb_ci = MOI.add_constraint(model, lb, MOI.GreaterThan{T}(zero(T)))

# var - UB * bin <= 0
ub = MOIU.operate(*, T, -s.upper, MOI.SingleVariable(binary))
ub = MOIU.operate!(+, T, ub, f)
ub_ci = MOI.add_constraint(model, ub, MOI.LessThan{T}(zero(T)))

if s isa MOI.Semiinteger{T}
int_ci = MOI.add_constraint(model, f, MOI.Integer())
else
int_ci = nothing
end

return SemiToBinaryBridge{T,S}(s, f.variable, binary, binary_con, lb_ci, ub_ci, int_ci)
end


function MOIB.added_constrained_variable_types(::Type{<:SemiToBinaryBridge{T, S}}) where {T, S}
return [(MOI.ZeroOne,)]
end

function MOIB.added_constraint_types(::Type{<:SemiToBinaryBridge{T, S}}) where {T, S<:MOI.Semicontinuous{T}}
return [
(MOI.ScalarAffineFunction{T}, MOI.LessThan{T}),
(MOI.ScalarAffineFunction{T}, MOI.GreaterThan{T}),
]
end

function MOIB.added_constraint_types(::Type{<:SemiToBinaryBridge{T, S}}) where {T, S <: MOI.Semiinteger{T}}
return [
(MOI.ScalarAffineFunction{T}, MOI.LessThan{T}),
(MOI.ScalarAffineFunction{T}, MOI.GreaterThan{T}),
(MOI.SingleVariable, MOI.Integer),
]
end

function concrete_bridge_type(::Type{<:SemiToBinaryBridge{T}},
::Type{MOI.SingleVariable},
::Type{S}) where {T, S<:SemiSets}
return SemiToBinaryBridge{T, S}
end

function MOI.supports_constraint(::Type{<:SemiToBinaryBridge},
::Type{MOI.SingleVariable},
::Type{<:SemiSets})
return true
end

function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet,
b::SemiToBinaryBridge)
return b.semi_set
end

function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintSet,
bridge::SemiToBinaryBridge{T, S}, set::S) where {T, S}
bridge.semi_set = set
MOI.modify(model, bridge.upper_bound_index,
MOI.ScalarCoefficientChange(bridge.binary_variable_index, -set.upper))
MOI.modify(model, bridge.lower_bound_index,
MOI.ScalarCoefficientChange(bridge.binary_variable_index, -set.lower))
return
end

function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction,
b::SemiToBinaryBridge{T}) where {T}
return MOI.SingleVariable(b.variable_index)
end

function MOI.delete(model::MOI.ModelLike, bridge::SemiToBinaryBridge)
if bridge.integer_index !== nothing
MOI.delete(model, bridge.integer_index)
end
MOI.delete(model, bridge.upper_bound_index)
MOI.delete(model, bridge.lower_bound_index)
MOI.delete(model, bridge.binary_constraint_index)
MOI.delete(model, bridge.binary_variable_index)
return
end

function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal,
bridge::SemiToBinaryBridge)
MOI.get(model, MOI.VariablePrimal(attr.N), bridge.variable_index)
end

function MOI.supports(
::MOI.ModelLike,
::MOI.ConstraintPrimalStart,
::Type{<:SemiToBinaryBridge})
return true
end

function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimalStart, bridge::SemiToBinaryBridge)
return MOI.get(model, MOI.VariablePrimalStart(), bridge.variable_index)
end

function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintPrimalStart,
bridge::SemiToBinaryBridge{T}, value) where {T}
MOI.set(model, MOI.VariablePrimalStart(), bridge.variable_index, value)
bin_value = ifelse(iszero(value), 0.0, 1.0)
MOI.set(model, MOI.VariablePrimalStart(), bridge.binary_variable_index, bin_value)
MOI.set(model, MOI.ConstraintPrimalStart(),
bridge.upper_bound_index, value - bridge.semi_set.upper * bin_value)
MOI.set(model, MOI.ConstraintPrimalStart(),
bridge.lower_bound_index, value - bridge.semi_set.lower * bin_value)
return
end

# Attributes, Bridge acting as a model

function MOI.get(::SemiToBinaryBridge, ::MOI.NumberOfVariables)
return 1
end

function MOI.get(b::SemiToBinaryBridge, ::MOI.ListOfVariableIndices)
return [b.binary_variable_index]
end

function MOI.get(::SemiToBinaryBridge{T, S},
::MOI.NumberOfConstraints{MOI.SingleVariable, MOI.ZeroOne}) where {T, S}
return 1
end

function MOI.get(::SemiToBinaryBridge{T, S},
::MOI.NumberOfConstraints{MOI.SingleVariable, MOI.Integer}) where {T, S<:MOI.Semiinteger}
return 1
end

function MOI.get(::SemiToBinaryBridge{T, S},
::MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T}, MOI.GreaterThan{T}}) where {T, S}
return 1
end

function MOI.get(::SemiToBinaryBridge{T, S},
::MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T}, MOI.LessThan{T}}) where {T, S}
return 1
end

function MOI.get(b::SemiToBinaryBridge{T, S},
::MOI.ListOfConstraintIndices{MOI.SingleVariable, MOI.ZeroOne}) where {T, S}
return [b.binary_constraint_index]
end

function MOI.get(b::SemiToBinaryBridge{T, S},
::MOI.ListOfConstraintIndices{MOI.SingleVariable, MOI.Integer}) where {T, S<:MOI.Semiinteger}
return [b.integer_index]
end

function MOI.get(b::SemiToBinaryBridge{T, S},
::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T}, MOI.LessThan{T}}) where {T, S}
return [b.upper_bound_index]
end

function MOI.get(b::SemiToBinaryBridge{T, S},
::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T}, MOI.GreaterThan{T}}) where {T, S}
return [b.lower_bound_index]
end
4 changes: 2 additions & 2 deletions src/Utilities/functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ function test_variablenames_equal(model, variablenames)
end
for (vname,seen) in seen_name
if !seen
error("Did not find variable with name $vname in intance.")
error("Did not find variable with name $vname in instance.")
end
end
end
Expand All @@ -509,7 +509,7 @@ function test_constraintnames_equal(model, constraintnames)
end
for (cname,seen) in seen_name
if !seen
error("Did not find constraint with name $cname in intance.")
error("Did not find constraint with name $cname in instance.")
end
end
end
Expand Down
165 changes: 165 additions & 0 deletions test/Bridges/Constraint/semi_to_binary.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
using Test

using MathOptInterface
const MOI = MathOptInterface
const MOIT = MathOptInterface.Test
const MOIU = MathOptInterface.Utilities
const MOIB = MathOptInterface.Bridges
const MOIBC = MathOptInterface.Bridges.Constraint

include("../utilities.jl")

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

@testset "SemiToBinary" begin
bridged_mock = MOIBC.SemiToBinary{Float64}(mock)

bridge_type = MOIBC.SemiToBinaryBridge{Float64, MOI.Semiinteger{Float64}}
@test MOI.supports_constraint(bridge_type,
MOI.SingleVariable, MOI.Semiinteger{Float64})
@test MOIBC.concrete_bridge_type(bridge_type,
MOI.SingleVariable,
MOI.Semiinteger{Float64}) == bridge_type

@test MOI.supports(bridged_mock, MOI.ConstraintPrimalStart(), bridge_type)

MOIT.basic_constraint_tests(
bridged_mock, config,
include = [(F, S)
for F in [MOI.SingleVariable]
for S in [MOI.Semiinteger{Float64}, MOI.Semicontinuous{Float64}]])

MOIU.set_mock_optimize!(mock,
(mock::MOIU.MockOptimizer) -> begin
MOI.set(mock, MOI.ObjectiveBound(), 0.0)
MOIU.mock_optimize!(mock, [0.0, 0.0, 0.0])
end,
(mock::MOIU.MockOptimizer) -> begin
MOI.set(mock, MOI.ObjectiveBound(), 2.0)
MOIU.mock_optimize!(mock, [2.0, 1.0, 1.0])
end,
(mock::MOIU.MockOptimizer) -> begin
MOI.set(mock, MOI.ObjectiveBound(), 2.0)
MOIU.mock_optimize!(mock, [2.0, 2.0, 1.0])
end,
(mock::MOIU.MockOptimizer) -> begin
MOI.set(mock, MOI.ObjectiveBound(), 2.5)
MOIU.mock_optimize!(mock, [2.5, 2.5, 1.0])
end,
(mock::MOIU.MockOptimizer) -> begin
MOI.set(mock, MOI.ObjectiveBound(), 3.0)
MOIU.mock_optimize!(mock, [3.0, 3.0, 1.0])
end,
(mock::MOIU.MockOptimizer) -> MOI.set(mock, MOI.TerminationStatus(), MOI.INFEASIBLE)
)
MOIT.semiconttest(bridged_mock,config)

ci = first(MOI.get(
bridged_mock,
MOI.ListOfConstraintIndices{MOI.SingleVariable,
MOI.Semicontinuous{Float64}}()))

test_delete_bridge(bridged_mock, ci, 2, (
(MOI.SingleVariable, MOI.EqualTo{Float64}, 1),
(MOI.SingleVariable, MOI.ZeroOne, 0),
(MOI.SingleVariable, MOI.Integer, 0),
(MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}, 0),
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}, 1),
))

MOIU.set_mock_optimize!(mock,
(mock::MOIU.MockOptimizer) -> begin
MOI.set(mock, MOI.ObjectiveBound(), 0.0)
MOIU.mock_optimize!(mock, [0.0, 0.0, 0.0])
end,
(mock::MOIU.MockOptimizer) -> begin
MOI.set(mock, MOI.ObjectiveBound(), 2.0)
MOIU.mock_optimize!(mock, [2.0, 1.0, 1.0])
end,
(mock::MOIU.MockOptimizer) -> begin
MOI.set(mock, MOI.ObjectiveBound(), 2.0)
MOIU.mock_optimize!(mock, [2.0, 2.0, 1.0])
end,
(mock::MOIU.MockOptimizer) -> begin
MOI.set(mock, MOI.ObjectiveBound(), 3.0)
MOIU.mock_optimize!(mock, [3.0, 2.5, 1.0])
end,
(mock::MOIU.MockOptimizer) -> begin
MOI.set(mock, MOI.ObjectiveBound(), 3.0)
MOIU.mock_optimize!(mock, [3.0, 3.0, 1.0])
end,
(mock::MOIU.MockOptimizer) -> MOI.set(mock, MOI.TerminationStatus(), MOI.INFEASIBLE)
)
MOIT.semiinttest(bridged_mock,config)

ci = first(MOI.get(
bridged_mock,
MOI.ListOfConstraintIndices{MOI.SingleVariable,
MOI.Semiinteger{Float64}}()))

@test MOI.get(bridged_mock, MOI.ConstraintPrimal(), ci) == 3
new_set = MOI.Semiinteger{Float64}(19.0, 20.0)
MOI.set(bridged_mock, MOI.ConstraintSet(), ci, new_set)
@test MOI.get(bridged_mock, MOI.ConstraintSet(), ci) == new_set

@testset "$attr" for attr in [MOI.ConstraintPrimalStart(),]
@test MOI.supports(bridged_mock, attr, typeof(ci))
value = 2.0
MOI.set(bridged_mock, attr, ci, value)
@test MOI.get(bridged_mock, attr, ci) ≈ value
end

test_delete_bridge(bridged_mock, ci, 2, (
(MOI.SingleVariable, MOI.EqualTo{Float64}, 1),
(MOI.SingleVariable, MOI.ZeroOne, 0),
(MOI.SingleVariable, MOI.Integer, 0),
(MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}, 0),
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}, 1),
))


s = """
variables: x, y
cy: y == 4.0
cx: x in Semiinteger(2.0, 3.0)
minobjective: x
"""
model = MOIU.Model{Float64}()
MOIU.loadfromstring!(model, s)
sb = """
variables: x, y, z
cy: y == 4.0
bin: z in ZeroOne()
int: x in Integer()
clo: x + -2.0z >= 0.0
cup: x + -3.0z <= 0.0
minobjective: x
"""
modelb = MOIU.Model{Float64}()
MOIU.loadfromstring!(modelb, sb)

MOI.empty!(bridged_mock)
@test MOI.is_empty(bridged_mock)
MOIU.loadfromstring!(bridged_mock, s)
MOIU.test_models_equal(bridged_mock, model, ["x", "y"], ["cy", "cx"])

# setting names on mock
ci = first(MOI.get(mock,MOI.ListOfConstraintIndices{
MOI.SingleVariable, MOI.ZeroOne}()))
MOI.set(mock, MOI.ConstraintName(), ci, "bin")
z = MOI.VariableIndex(ci.value)
MOI.set(mock, MOI.VariableName(), z, "z")
ci = first(MOI.get(mock, MOI.ListOfConstraintIndices{
MOI.SingleVariable, MOI.Integer}()))
MOI.set(mock, MOI.ConstraintName(), ci, "int")
ci = first(MOI.get(mock,MOI.ListOfConstraintIndices{
MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}}()))
MOI.set(mock, MOI.ConstraintName(), ci, "clo")
ci = first(MOI.get(mock,MOI.ListOfConstraintIndices{
MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}()))
MOI.set(mock, MOI.ConstraintName(), ci, "cup")

MOIU.test_models_equal(mock, modelb, ["x", "y", "z"], ["cy", "bin", "int", "clo", "cup"])

end