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
2 changes: 2 additions & 0 deletions src/Bridges/Variable/Variable.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ include("zeros.jl")
const Zeros{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{ZerosBridge{T}, OT}
include("flip_sign.jl")
const NonposToNonneg{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NonposToNonnegBridge{T}, OT}
include("vectorize.jl")
const Vectorize{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{VectorizeBridge{T}, OT}
include("rsoc_to_psd.jl")
const RSOCtoPSD{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{RSOCtoPSDBridge{T}, OT}

Expand Down
113 changes: 113 additions & 0 deletions src/Bridges/Variable/vectorize.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"""
VectorizeBridge{T, S}

Transforms a constrained variable in `scalar_set_type(S, T)` where
`S <: VectorLinearSet` into a constrained vector of one variable in `S`. For
instance, `VectorizeBridge{Float64, MOI.Nonnegatives}` transforms a constrained
variable in `MOI.GreaterThan{Float64}` into a constrained vector of one
variable in `MOI.Nonnegatives`.
"""
mutable struct VectorizeBridge{T, S} <: AbstractBridge
variable::MOI.VariableIndex
vector_constraint::MOI.ConstraintIndex{MOI.VectorOfVariables, S}
set_constant::T # constant in scalar set
end
function bridge_constrained_variable(
::Type{VectorizeBridge{T, S}},
model::MOI.ModelLike, set::MOIU.ScalarLinearSet{T}) where {T, S}
set_constant = MOI.constant(set)
variables, vector_constraint = MOI.add_constrained_variables(model, S(1))
return VectorizeBridge{T, S}(variables[1], vector_constraint, set_constant)
end

function supports_constrained_variable(
::Type{VectorizeBridge{T}}, ::Type{<:MOIU.ScalarLinearSet{T}}) where T
return true
end
function MOIB.added_constrained_variable_types(::Type{VectorizeBridge{T, S}}) where {T, S}
return [(S,)]
end
function MOIB.added_constraint_types(::Type{<:VectorizeBridge})
return Tuple{DataType, DataType}[]
end
function concrete_bridge_type(::Type{<:VectorizeBridge{T}},
S::Type{<:MOIU.ScalarLinearSet{T}}) where T
return VectorizeBridge{T, MOIU.vector_set_type(S)}
end

# Attributes, Bridge acting as a model
function MOI.get(bridge::VectorizeBridge, ::MOI.NumberOfVariables)
return 1
end
function MOI.get(bridge::VectorizeBridge, ::MOI.ListOfVariableIndices)
return [bridge.variable]
end
function MOI.get(::VectorizeBridge{T, S},
::MOI.NumberOfConstraints{MOI.VectorOfVariables, S}) where {T, S}
return 1
end
function MOI.get(bridge::VectorizeBridge{T, S},
::MOI.ListOfConstraintIndices{MOI.VectorOfVariables, S}) where {T, S}
return [bridge.vector_constraint]
end

# References
function MOI.delete(model::MOI.ModelLike, bridge::VectorizeBridge)
MOI.delete(model, bridge.variable)
end

# Attributes, Bridge acting as a constraint

function MOI.get(model::MOI.ModelLike, ::MOI.ConstraintSet,
bridge::VectorizeBridge{T, S}) where {T, S}
return MOIU.scalar_set_type(S, T)(bridge.set_constant)
end

function MOI.set(model::MOI.ModelLike, attr::MOI.ConstraintSet,
bridge::VectorizeBridge, new_set::MOIU.ScalarLinearSet)
# This would require modifing any constraint which uses the bridged
# variable.
throw(MOI.SetAttributeNotAllowed(attr,
"The variable `$(bridge.variable)` is bridged by the `VectorizeBridge`."))
end

function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal,
bridge::VectorizeBridge)
x = MOI.get(model, attr, bridge.vector_constraint)
@assert length(x) == 1
y = x[1]
if !MOIU.is_ray(MOI.get(model, MOI.PrimalStatus(attr.N)))
# If it is an infeasibility certificate, it is a ray and satisfies the
# homogenized problem, see https://github.com/JuliaOpt/MathOptInterface.jl/issues/433
# Otherwise, we need to add the set constant since the ConstraintPrimal
# is defined as the value of the function and the set_constant was
# removed from the original function
y += bridge.set_constant
end
return y
end
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintDual,
bridge::VectorizeBridge)
x = MOI.get(model, attr, bridge.vector_constraint)
@assert length(x) == 1
return x[1]
end

function MOI.get(model::MOI.ModelLike, attr::MOI.VariablePrimal,
bridge::VectorizeBridge)
value = MOI.get(model, attr, bridge.variable)
if !MOIU.is_ray(MOI.get(model, MOI.PrimalStatus(attr.N)))
value += bridge.set_constant
end
return value
end

function MOIB.bridged_function(bridge::VectorizeBridge{T}) where T
func = MOI.SingleVariable(bridge.variable)
return MOIU.operate(+, T, func, bridge.set_constant)
end
function unbridged_map(bridge::VectorizeBridge{T}, vi::MOI.VariableIndex) where T
func = MOIU.operate(-, T, MOI.SingleVariable(vi),
bridge.set_constant)
return (bridge.variable => func,)
end
12 changes: 5 additions & 7 deletions src/Bridges/bridge_optimizer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -999,13 +999,11 @@ scalar.
function unbridged_constraint_function end

function unbridged_constraint_function(
b::AbstractBridgeOptimizer,
func::Union{MOI.AbstractVectorSet, MOI.SingleVariable}
)
b::AbstractBridgeOptimizer, func::MOI.AbstractVectorFunction)
return unbridged_function(b, func)
end
function unbridged_constraint_function(b::AbstractBridgeOptimizer,
func::MOI.AbstractScalarFunction)
function unbridged_constraint_function(
b::AbstractBridgeOptimizer, func::MOI.AbstractScalarFunction)
if !Variable.has_bridges(Variable.bridges(b))
return func
end
Expand All @@ -1017,8 +1015,8 @@ function unbridged_constraint_function(b::AbstractBridgeOptimizer,
return f
end
function unbridged_constraint_function(
::AbstractBridgeOptimizer, func::MOI.AbstractVectorFunction)
return func
b::AbstractBridgeOptimizer, func::MOI.SingleVariable)
return unbridged_function(b, func)
end


Expand Down
3 changes: 3 additions & 0 deletions test/Bridges/Variable/Variable.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ end
@testset "FlipSign" begin
include("flip_sign.jl")
end
@testset "Vectorize" begin
include("vectorize.jl")
end
@testset "RSOCtoPSD" begin
include("rsoc_to_psd.jl")
end
170 changes: 170 additions & 0 deletions test/Bridges/Variable/vectorize.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
using Test

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

include("../utilities.jl")

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

bridged_mock = MOIB.Variable.Vectorize{Float64}(mock)

@testset "get scalar constraint" begin
x, cx = MOI.add_constrained_variable(bridged_mock, MOI.GreaterThan(1.0))
fx = MOI.SingleVariable(x)
func = 2.0 * fx
set = MOI.GreaterThan(5.0)
err = MOI.ScalarFunctionConstantNotZero{
Float64, typeof(func), typeof(set)}(1.0)
@test_throws err MOI.add_constraint(bridged_mock, func + 1.0, set)

c = MOI.add_constraint(bridged_mock, func, set)
@test MOI.get(bridged_mock, MOI.ConstraintFunction(), c) ≈ func
@test MOI.get(bridged_mock, MOI.ConstraintSet(), c) == set
MOI.set(bridged_mock, MOI.ConstraintName(), c, "c")

@testset "Mock model" begin
MOI.set(mock, MOI.VariableName(),
MOI.get(mock, MOI.ListOfVariableIndices()), ["y"])
MOI.set(mock, MOI.ConstraintName(),
MOI.get(mock, MOI.ListOfConstraintIndices{
MOI.VectorOfVariables, MOI.Nonnegatives}()),
["cy"])
s = """
variables: y
cy: [y] in MathOptInterface.Nonnegatives(1)
c: 2.0y >= 3.0
"""
model = MOIU.Model{Float64}()
MOIU.loadfromstring!(model, s)
MOIU.test_models_equal(mock, model, ["y"], ["cy", "c"])
end
@testset "Bridged model" begin
MOI.set(bridged_mock, MOI.VariableName(), x, "x")
MOI.set(bridged_mock, MOI.ConstraintName(), cx, "cx")
s = """
variables: x
cx: x >= 1.0
c: 2.0x >= 5.0
"""
model = MOIU.Model{Float64}()
MOIU.loadfromstring!(model, s)
MOIU.test_models_equal(bridged_mock, model, ["x"], ["cx", "c"])
end
end

@testset "exp3 with add_constrained_variable for `y`" begin
mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [log(5), 0.0],
(MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [0.0],
(MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone) => [[-1.0, log(5)-1, 1/5]])

MOI.empty!(bridged_mock)
x = MOI.add_variable(bridged_mock)
@test MOI.get(bridged_mock, MOI.NumberOfVariables()) == 1
fx = MOI.SingleVariable(x)
xc = MOI.add_constraint(bridged_mock, 2.0fx, MOI.LessThan(4.0))
y, yc = MOI.add_constrained_variable(bridged_mock, MOI.LessThan(5.0))
@test yc.value == y.value == -1
@test MOI.get(bridged_mock, MOI.NumberOfVariables()) == 2
@test length(MOI.get(bridged_mock, MOI.ListOfVariableIndices())) == 2
@test Set(MOI.get(bridged_mock, MOI.ListOfVariableIndices())) == Set([x, y])
fy = MOI.SingleVariable(y)
ec = MOI.add_constraint(bridged_mock,
MOIU.operate(vcat, Float64, fx, 1.0, fy),
MOI.ExponentialCone())

MOI.optimize!(bridged_mock)
@test MOI.get(bridged_mock, MOI.VariablePrimal(), x) ≈ log(5)
@test MOI.get(bridged_mock, MOI.VariablePrimal(), y) ≈ 5.0
@test MOI.get(bridged_mock, MOI.ConstraintPrimal(), xc) ≈ 2log(5)
@test MOI.get(bridged_mock, MOI.ConstraintPrimal(), yc) ≈ 5
@test MOI.get(bridged_mock, MOI.ConstraintPrimal(), ec) ≈ [log(5), 1., 5.0]
@test MOI.get(bridged_mock, MOI.ConstraintDual(), xc) ≈ 0.0
@test MOI.get(bridged_mock, MOI.ConstraintDual(), yc) ≈ -1/5
@test MOI.get(bridged_mock, MOI.ConstraintDual(), ec) ≈ [-1., log(5)-1, 1/5]

err = ErrorException(
"Cannot add two `SingleVariable`-in-`MathOptInterface.LessThan{Float64}`" *
" on the same variable MathOptInterface.VariableIndex(-1)."
)
@test_throws err MOI.add_constraint(bridged_mock, MOI.SingleVariable(y), MOI.LessThan(4.0))

cis = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{
MOI.VectorAffineFunction{Float64}, MOI.ExponentialCone}())
@test length(cis) == 1

@testset "get `UnknownVariableAttribute``" begin
err = ArgumentError(
"Variable bridge of type `MathOptInterface.Bridges.Variable.VectorizeBridge{Float64,MathOptInterface.Nonpositives}`" *
" does not support accessing the attribute `MathOptInterface.Test.UnknownVariableAttribute()`."
)
@test_throws err MOI.get(bridged_mock, MOIT.UnknownVariableAttribute(), y)
end

@testset "set `ConstraintSet`" begin
ci = MOI.ConstraintIndex{MOI.SingleVariable, MOI.LessThan{Float64}}(y.value)
attr = MOI.ConstraintSet()
err = MOI.SetAttributeNotAllowed(attr,
"The variable `MathOptInterface.VariableIndex(12345676)` is bridged by the `VectorizeBridge`.")
@test_throws err MOI.set(bridged_mock, attr, ci, MOI.LessThan(4.0))
end

@testset "MultirowChange" begin
change = MOI.MultirowChange(y, [(3, 0.0)])
message = "The change MathOptInterface.MultirowChange{Float64}(MathOptInterface.VariableIndex(-1), Tuple{Int64,Float64}[(3, 0.0)])" *
" contains variables bridged into a function with nonzero constant."
err = MOI.ModifyConstraintNotAllowed(cis[1], change, message)
@test_throws err MOI.modify(bridged_mock, cis[1], change)
end

@testset "ScalarCoefficientChange" begin
change = MOI.ScalarCoefficientChange(y, 0.0)
attr = MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}()
message = "The change MathOptInterface.ScalarCoefficientChange{Float64}(MathOptInterface.VariableIndex(-1), 0.0)" *
" contains variables bridged into a function with nonzero constant."
err = MOI.ModifyObjectiveNotAllowed(change, message)
@test_throws err MOI.modify(bridged_mock, attr, change)
end

MOI.set(bridged_mock, MOI.VariableName(), x, "x")
MOI.set(bridged_mock, MOI.ConstraintName(), xc, "xc")
MOI.set(bridged_mock, MOI.ConstraintName(), ec, "ec")
@testset "Mock model" begin
MOI.set(mock, MOI.VariableName(),
MOI.get(mock, MOI.ListOfVariableIndices())[2], "z")
MOI.set(mock, MOI.ConstraintName(),
MOI.get(mock, MOI.ListOfConstraintIndices{
MOI.VectorOfVariables, MOI.Nonpositives}()),
["zc"])
s = """
variables: x, z
zc: [z] in MathOptInterface.Nonpositives(1)
xc: 2.0x <= 4.0
ec: [x, 1.0, z + 5.0] in MathOptInterface.ExponentialCone()
"""
model = MOIU.Model{Float64}()
MOIU.loadfromstring!(model, s)
MOIU.test_models_equal(mock, model, ["x", "z"], ["zc", "xc", "ec"])
end
@testset "Bridged model" begin
MOI.set(bridged_mock, MOI.VariableName(), y, "y")
MOI.set(bridged_mock, MOI.ConstraintName(), yc, "yc")
s = """
variables: x, y
yc: y <= 5.0
xc: 2.0x <= 4.0
ec: [x, 1.0, y] in MathOptInterface.ExponentialCone()
"""
model = MOIU.Model{Float64}()
MOIU.loadfromstring!(model, s)
MOIU.test_models_equal(bridged_mock, model, ["x", "y"], ["yc", "xc", "ec"])
end

test_delete_bridged_variable(bridged_mock, y, MOI.LessThan{Float64}, 2, (
(MOI.VectorOfVariables, MOI.Nonpositives, 0),
))
end