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,5 +19,7 @@ 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("rsoc_to_psd.jl")
const RSOCtoPSD{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{RSOCtoPSDBridge{T}, OT}

end
179 changes: 179 additions & 0 deletions src/Bridges/Variable/rsoc_to_psd.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
"""
RSOCtoPSDBridge{T} <: Bridges.Variable.AbstractBridge

Transforms constrained variables in [`MathOptInterface.RotatedSecondOrderCone`](@ref)
to constrained variables in [`MathOptInterface.PositiveSemidefiniteConeTriangle`](@ref).
"""
struct RSOCtoPSDBridge{T} <: AbstractBridge
# `t` is `variables[1]`
# `u` is `variables[2]/2`
# `x` is `variables[[3, 5, 8, ...]]`
variables::Vector{MOI.VariableIndex}
psd::MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle}
off_diag::Vector{MOI.ConstraintIndex{MOI.SingleVariable, MOI.EqualTo{T}}}
diag::Vector{MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}}}
end
function bridge_constrained_variable(::Type{RSOCtoPSDBridge{T}},
model::MOI.ModelLike,
set::MOI.RotatedSecondOrderCone) where T
dim = set.dimension - 1
variables, psd = MOI.add_constrained_variables(
model, MOI.PositiveSemidefiniteConeTriangle(dim))
# This is `2 * u`
u2 = MOI.SingleVariable(variables[3])
off_diag = MOI.ConstraintIndex{MOI.SingleVariable, MOI.EqualTo{T}}[]
diag = MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}}[]
k = 3
for j in 3:dim
k += 1
for i in 2:(j-1)
k += 1
push!(off_diag,
MOI.add_constraint(model, MOI.SingleVariable(variables[k]),
MOI.EqualTo(zero(T))))
end
k += 1
func = MOIU.operate(-, T, u2, MOI.SingleVariable(variables[k]))
push!(diag, MOI.add_constraint(model, func, MOI.EqualTo(zero(T))))
end
@assert k == trimap(dim, dim)
return RSOCtoPSDBridge{T}(variables, psd, off_diag, diag)
end

function supports_constrained_variable(
::Type{<:RSOCtoPSDBridge}, ::Type{MOI.RotatedSecondOrderCone})
return true
end
function MOIB.added_constrained_variable_types(::Type{<:RSOCtoPSDBridge})
return [(MOI.PositiveSemidefiniteConeTriangle,)]
end
function MOIB.added_constraint_types(::Type{RSOCtoPSDBridge{T}}) where T
return [(MOI.SingleVariable, MOI.EqualTo{T}), (MOI.ScalarAffineFunction{T}, MOI.EqualTo{T})]
end

# Attributes, Bridge acting as a model
function MOI.get(bridge::RSOCtoPSDBridge, ::MOI.NumberOfVariables)
return length(bridge.variables)
end
function MOI.get(bridge::RSOCtoPSDBridge, ::MOI.ListOfVariableIndices)
return bridge.variables
end
function MOI.get(bridge::RSOCtoPSDBridge,
::MOI.NumberOfConstraints{MOI.VectorOfVariables,
MOI.PositiveSemidefiniteConeTriangle})
return 1
end
function MOI.get(bridge::RSOCtoPSDBridge,
::MOI.ListOfConstraintIndices{MOI.VectorOfVariables,
MOI.PositiveSemidefiniteConeTriangle})
return [bridge.psd]
end
function MOI.get(bridge::RSOCtoPSDBridge{T},
::MOI.NumberOfConstraints{MOI.SingleVariable,
MOI.EqualTo{T}}) where T
return length(bridge.off_diag)
end
function MOI.get(bridge::RSOCtoPSDBridge{T},
::MOI.ListOfConstraintIndices{MOI.SingleVariable,
MOI.EqualTo{T}}) where T
return bridge.off_diag
end
function MOI.get(bridge::RSOCtoPSDBridge{T},
::MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},
MOI.EqualTo{T}}) where T
return length(bridge.diag)
end
function MOI.get(bridge::RSOCtoPSDBridge{T},
::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T},
MOI.EqualTo{T}}) where T
return bridge.diag
end

# References
function MOI.delete(model::MOI.ModelLike, bridge::RSOCtoPSDBridge)
for ci in bridge.diag
MOI.delete(model, ci)
end
MOI.delete(model, bridge.variables)
end

# Attributes, Bridge acting as a constraint

function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet,
bridge::RSOCtoPSDBridge{T}) where T
return MOI.RotatedSecondOrderCone(length(bridge.diag) + 3)
end

function trimap(i::Integer, j::Integer)
if i < j
return trimap(j, i)
else
return div((i-1) * i, 2) + j
end
end
function _variable_map(i::IndexInVector)
if i.value == 1
return 1
elseif i.value == 2
return 3
else
return trimap(1, i.value - 1)
end
end
function _variable(bridge::RSOCtoPSDBridge, i::IndexInVector)
return bridge.variables[_variable_map(i)]
end

function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal,
bridge::RSOCtoPSDBridge{T}) where T
values = MOI.get(model, attr, bridge.psd)
n = MOI.dimension(MOI.get(model, MOI.ConstraintSet(), bridge))
mapped = [values[_variable_map(IndexInVector(i))] for i in 1:n]
mapped[2] /= 2
return mapped
end
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintDual,
bridge::RSOCtoPSDBridge{T}) where T
dual = MOI.get(model, attr, bridge.psd)
n = MOI.dimension(MOI.get(model, MOI.ConstraintSet(), bridge))
mapped = [dual[_variable_map(IndexInVector(i))] for i in 1:n]
for ci in bridge.diag
mapped[2] += MOI.get(model, attr, ci)
end
for i in 2:length(mapped)
# For `i = 2`, we multiply by 2 because it is `2u`.
# For `i > 2`, we multiply by 2 because to account for the difference
# of scalar product `MOIU.set_dot`.
mapped[i] *= 2
end
return mapped
end

function MOI.get(model::MOI.ModelLike, attr::MOI.VariablePrimal,
bridge::RSOCtoPSDBridge{T}, i::IndexInVector) where T
value = MOI.get(model, attr, _variable(bridge, i))
if i.value == 2
return value / 2
else
return value
end
end

function MOIB.bridged_function(bridge::RSOCtoPSDBridge{T}, i::IndexInVector) where T
func = MOI.SingleVariable(_variable(bridge, i))
if i.value == 2
return MOIU.operate(/, T, func, convert(T, 2))
else
return convert(MOI.ScalarAffineFunction{T}, func)
end
end
function unbridged_map(bridge::RSOCtoPSDBridge{T}, vi::MOI.VariableIndex,
i::IndexInVector) where T
sv = MOI.SingleVariable(vi)
if i.value == 2
func = MOIU.operate(*, T, convert(T, 2), sv)
else
func = convert(MOI.ScalarAffineFunction{T}, sv)
end
return (_variable(bridge, i) => func,)
end
3 changes: 3 additions & 0 deletions test/Bridges/Variable/Variable.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ end
@testset "FlipSign" begin
include("flip_sign.jl")
end
@testset "RSOCtoPSD" begin
include("rsoc_to_psd.jl")
end
104 changes: 104 additions & 0 deletions test/Bridges/Variable/rsoc_to_psd.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
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.RSOCtoPSD{Float64}(mock)

@testset "RSOC4" begin
mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1.0, 2.0, 1.0, 0.0, 2.0],
(MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [0.25],
(MOI.SingleVariable, MOI.EqualTo{Float64}) => [-0.5],
(MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1.0],
(MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle) => [[1.0, -0.5, 0.25, -0.5, 0.25, 0.25]])
mock.eval_variable_constraint_dual = false
MOIT.rotatedsoc4test(bridged_mock, config)
mock.eval_variable_constraint_dual = true

@testset "Test mock model" begin
var_names = ["Q$i$j" for j in 1:3 for i in 1:j]
MOI.set(
mock, MOI.VariableName(),
MOI.get(mock, MOI.ListOfVariableIndices()), var_names)
psd = MOI.get(mock, MOI.ListOfConstraintIndices{
MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle}())
@test length(psd) == 1
MOI.set(mock, MOI.ConstraintName(), psd[1], "psd")
off_diag = MOI.get(mock, MOI.ListOfConstraintIndices{
MOI.SingleVariable, MOI.EqualTo{Float64}}())
@test length(off_diag) == 1
MOI.set(mock, MOI.ConstraintName(), off_diag[1], "off_diag23")
diag = MOI.get(mock, MOI.ListOfConstraintIndices{
MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}}())
@test length(diag) == 1
MOI.set(mock, MOI.ConstraintName(), diag[1], "diag33")
c = MOI.get(mock, MOI.ListOfConstraintIndices{
MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}())
@test length(c) == 1
MOI.set(mock, MOI.ConstraintName(), c[1], "c")

s = """
variables: Q11, Q12, Q13, Q22, Q23, Q33
psd: [Q11, Q12, Q22, Q13, Q23, Q33] in MathOptInterface.PositiveSemidefiniteConeTriangle(3)
off_diag23: Q23 == 0.0
diag33: Q22 + -1.0Q33 == 0.0
c: Q11 + 0.5Q22 <= 2.0
maxobjective: Q12 + Q13
"""
model = MOIU.Model{Float64}()
MOIU.loadfromstring!(model, s)
MOIU.test_models_equal(mock, model, var_names, ["psd", "off_diag23", "diag33", "c"])
end

@testset "Test bridged model" begin
var_names = ["t", "u", "x", "y"]
MOI.set(
bridged_mock, MOI.VariableName(),
MOI.get(bridged_mock, MOI.ListOfVariableIndices()), var_names)
rsoc = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{
MOI.VectorOfVariables, MOI.RotatedSecondOrderCone}())
@test length(rsoc) == 1
MOI.set(bridged_mock, MOI.ConstraintName(), rsoc[1], "rsoc")
c = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{
MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}())
@test length(c) == 1
MOI.set(bridged_mock, MOI.ConstraintName(), c[1], "c")

s = """
variables: t, u, x, y
rsoc: [t, u, x, y] in MathOptInterface.RotatedSecondOrderCone(4)
c: t + u <= 2.0
maxobjective: x + y
"""
model = MOIU.Model{Float64}()
MOIU.loadfromstring!(model, s)
MOIU.test_models_equal(bridged_mock, model, var_names, ["rsoc", "c"])
end


@testset "Delete" begin
v = MOI.get(bridged_mock, MOI.ListOfVariableIndices())
@test length(v) == 4

message = string("Cannot delete variable as it is constrained with other",
" variables in a `MOI.VectorOfVariables`.")
for i in 1:4
err = MOI.DeleteNotAllowed(v[i], message)
@test_throws err MOI.delete(bridged_mock, v[i])
end

test_delete_bridged_variables(bridged_mock, v, MOI.RotatedSecondOrderCone, 4, (
(MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle, 0),
(MOI.SingleVariable, MOI.EqualTo{Float64}, 0),
(MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, 0),
))
end
end