Skip to content

Commit a04646c

Browse files
authored
Merge pull request #821 from JuliaOpt/bl/variable_rsoc_to_psd
Add Variable.RSOCtoPSD
2 parents 351269c + ae71c8b commit a04646c

File tree

4 files changed

+288
-0
lines changed

4 files changed

+288
-0
lines changed

src/Bridges/Variable/Variable.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,7 @@ include("zeros.jl")
1919
const Zeros{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{ZerosBridge{T}, OT}
2020
include("flip_sign.jl")
2121
const NonposToNonneg{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{NonposToNonnegBridge{T}, OT}
22+
include("rsoc_to_psd.jl")
23+
const RSOCtoPSD{T, OT<:MOI.ModelLike} = SingleBridgeOptimizer{RSOCtoPSDBridge{T}, OT}
2224

2325
end
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
"""
2+
RSOCtoPSDBridge{T} <: Bridges.Variable.AbstractBridge
3+
4+
Transforms constrained variables in [`MathOptInterface.RotatedSecondOrderCone`](@ref)
5+
to constrained variables in [`MathOptInterface.PositiveSemidefiniteConeTriangle`](@ref).
6+
"""
7+
struct RSOCtoPSDBridge{T} <: AbstractBridge
8+
# `t` is `variables[1]`
9+
# `u` is `variables[2]/2`
10+
# `x` is `variables[[3, 5, 8, ...]]`
11+
variables::Vector{MOI.VariableIndex}
12+
psd::MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle}
13+
off_diag::Vector{MOI.ConstraintIndex{MOI.SingleVariable, MOI.EqualTo{T}}}
14+
diag::Vector{MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}}}
15+
end
16+
function bridge_constrained_variable(::Type{RSOCtoPSDBridge{T}},
17+
model::MOI.ModelLike,
18+
set::MOI.RotatedSecondOrderCone) where T
19+
dim = set.dimension - 1
20+
variables, psd = MOI.add_constrained_variables(
21+
model, MOI.PositiveSemidefiniteConeTriangle(dim))
22+
# This is `2 * u`
23+
u2 = MOI.SingleVariable(variables[3])
24+
off_diag = MOI.ConstraintIndex{MOI.SingleVariable, MOI.EqualTo{T}}[]
25+
diag = MOI.ConstraintIndex{MOI.ScalarAffineFunction{T}, MOI.EqualTo{T}}[]
26+
k = 3
27+
for j in 3:dim
28+
k += 1
29+
for i in 2:(j-1)
30+
k += 1
31+
push!(off_diag,
32+
MOI.add_constraint(model, MOI.SingleVariable(variables[k]),
33+
MOI.EqualTo(zero(T))))
34+
end
35+
k += 1
36+
func = MOIU.operate(-, T, u2, MOI.SingleVariable(variables[k]))
37+
push!(diag, MOI.add_constraint(model, func, MOI.EqualTo(zero(T))))
38+
end
39+
@assert k == trimap(dim, dim)
40+
return RSOCtoPSDBridge{T}(variables, psd, off_diag, diag)
41+
end
42+
43+
function supports_constrained_variable(
44+
::Type{<:RSOCtoPSDBridge}, ::Type{MOI.RotatedSecondOrderCone})
45+
return true
46+
end
47+
function MOIB.added_constrained_variable_types(::Type{<:RSOCtoPSDBridge})
48+
return [(MOI.PositiveSemidefiniteConeTriangle,)]
49+
end
50+
function MOIB.added_constraint_types(::Type{RSOCtoPSDBridge{T}}) where T
51+
return [(MOI.SingleVariable, MOI.EqualTo{T}), (MOI.ScalarAffineFunction{T}, MOI.EqualTo{T})]
52+
end
53+
54+
# Attributes, Bridge acting as a model
55+
function MOI.get(bridge::RSOCtoPSDBridge, ::MOI.NumberOfVariables)
56+
return length(bridge.variables)
57+
end
58+
function MOI.get(bridge::RSOCtoPSDBridge, ::MOI.ListOfVariableIndices)
59+
return bridge.variables
60+
end
61+
function MOI.get(bridge::RSOCtoPSDBridge,
62+
::MOI.NumberOfConstraints{MOI.VectorOfVariables,
63+
MOI.PositiveSemidefiniteConeTriangle})
64+
return 1
65+
end
66+
function MOI.get(bridge::RSOCtoPSDBridge,
67+
::MOI.ListOfConstraintIndices{MOI.VectorOfVariables,
68+
MOI.PositiveSemidefiniteConeTriangle})
69+
return [bridge.psd]
70+
end
71+
function MOI.get(bridge::RSOCtoPSDBridge{T},
72+
::MOI.NumberOfConstraints{MOI.SingleVariable,
73+
MOI.EqualTo{T}}) where T
74+
return length(bridge.off_diag)
75+
end
76+
function MOI.get(bridge::RSOCtoPSDBridge{T},
77+
::MOI.ListOfConstraintIndices{MOI.SingleVariable,
78+
MOI.EqualTo{T}}) where T
79+
return bridge.off_diag
80+
end
81+
function MOI.get(bridge::RSOCtoPSDBridge{T},
82+
::MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},
83+
MOI.EqualTo{T}}) where T
84+
return length(bridge.diag)
85+
end
86+
function MOI.get(bridge::RSOCtoPSDBridge{T},
87+
::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T},
88+
MOI.EqualTo{T}}) where T
89+
return bridge.diag
90+
end
91+
92+
# References
93+
function MOI.delete(model::MOI.ModelLike, bridge::RSOCtoPSDBridge)
94+
for ci in bridge.diag
95+
MOI.delete(model, ci)
96+
end
97+
MOI.delete(model, bridge.variables)
98+
end
99+
100+
# Attributes, Bridge acting as a constraint
101+
102+
function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet,
103+
bridge::RSOCtoPSDBridge{T}) where T
104+
return MOI.RotatedSecondOrderCone(length(bridge.diag) + 3)
105+
end
106+
107+
function trimap(i::Integer, j::Integer)
108+
if i < j
109+
return trimap(j, i)
110+
else
111+
return div((i-1) * i, 2) + j
112+
end
113+
end
114+
function _variable_map(i::IndexInVector)
115+
if i.value == 1
116+
return 1
117+
elseif i.value == 2
118+
return 3
119+
else
120+
return trimap(1, i.value - 1)
121+
end
122+
end
123+
function _variable(bridge::RSOCtoPSDBridge, i::IndexInVector)
124+
return bridge.variables[_variable_map(i)]
125+
end
126+
127+
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal,
128+
bridge::RSOCtoPSDBridge{T}) where T
129+
values = MOI.get(model, attr, bridge.psd)
130+
n = MOI.dimension(MOI.get(model, MOI.ConstraintSet(), bridge))
131+
mapped = [values[_variable_map(IndexInVector(i))] for i in 1:n]
132+
mapped[2] /= 2
133+
return mapped
134+
end
135+
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintDual,
136+
bridge::RSOCtoPSDBridge{T}) where T
137+
dual = MOI.get(model, attr, bridge.psd)
138+
n = MOI.dimension(MOI.get(model, MOI.ConstraintSet(), bridge))
139+
mapped = [dual[_variable_map(IndexInVector(i))] for i in 1:n]
140+
for ci in bridge.diag
141+
mapped[2] += MOI.get(model, attr, ci)
142+
end
143+
for i in 2:length(mapped)
144+
# For `i = 2`, we multiply by 2 because it is `2u`.
145+
# For `i > 2`, we multiply by 2 because to account for the difference
146+
# of scalar product `MOIU.set_dot`.
147+
mapped[i] *= 2
148+
end
149+
return mapped
150+
end
151+
152+
function MOI.get(model::MOI.ModelLike, attr::MOI.VariablePrimal,
153+
bridge::RSOCtoPSDBridge{T}, i::IndexInVector) where T
154+
value = MOI.get(model, attr, _variable(bridge, i))
155+
if i.value == 2
156+
return value / 2
157+
else
158+
return value
159+
end
160+
end
161+
162+
function MOIB.bridged_function(bridge::RSOCtoPSDBridge{T}, i::IndexInVector) where T
163+
func = MOI.SingleVariable(_variable(bridge, i))
164+
if i.value == 2
165+
return MOIU.operate(/, T, func, convert(T, 2))
166+
else
167+
return convert(MOI.ScalarAffineFunction{T}, func)
168+
end
169+
end
170+
function unbridged_map(bridge::RSOCtoPSDBridge{T}, vi::MOI.VariableIndex,
171+
i::IndexInVector) where T
172+
sv = MOI.SingleVariable(vi)
173+
if i.value == 2
174+
func = MOIU.operate(*, T, convert(T, 2), sv)
175+
else
176+
func = convert(MOI.ScalarAffineFunction{T}, sv)
177+
end
178+
return (_variable(bridge, i) => func,)
179+
end

test/Bridges/Variable/Variable.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ end
88
@testset "FlipSign" begin
99
include("flip_sign.jl")
1010
end
11+
@testset "RSOCtoPSD" begin
12+
include("rsoc_to_psd.jl")
13+
end
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using Test
2+
3+
using MathOptInterface
4+
const MOI = MathOptInterface
5+
const MOIT = MathOptInterface.Test
6+
const MOIU = MathOptInterface.Utilities
7+
const MOIB = MathOptInterface.Bridges
8+
9+
include("../utilities.jl")
10+
11+
mock = MOIU.MockOptimizer(MOIU.Model{Float64}())
12+
config = MOIT.TestConfig()
13+
14+
bridged_mock = MOIB.Variable.RSOCtoPSD{Float64}(mock)
15+
16+
@testset "RSOC4" begin
17+
mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 1.0, 2.0, 1.0, 0.0, 2.0],
18+
(MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [0.25],
19+
(MOI.SingleVariable, MOI.EqualTo{Float64}) => [-0.5],
20+
(MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [-1.0],
21+
(MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle) => [[1.0, -0.5, 0.25, -0.5, 0.25, 0.25]])
22+
mock.eval_variable_constraint_dual = false
23+
MOIT.rotatedsoc4test(bridged_mock, config)
24+
mock.eval_variable_constraint_dual = true
25+
26+
@testset "Test mock model" begin
27+
var_names = ["Q$i$j" for j in 1:3 for i in 1:j]
28+
MOI.set(
29+
mock, MOI.VariableName(),
30+
MOI.get(mock, MOI.ListOfVariableIndices()), var_names)
31+
psd = MOI.get(mock, MOI.ListOfConstraintIndices{
32+
MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle}())
33+
@test length(psd) == 1
34+
MOI.set(mock, MOI.ConstraintName(), psd[1], "psd")
35+
off_diag = MOI.get(mock, MOI.ListOfConstraintIndices{
36+
MOI.SingleVariable, MOI.EqualTo{Float64}}())
37+
@test length(off_diag) == 1
38+
MOI.set(mock, MOI.ConstraintName(), off_diag[1], "off_diag23")
39+
diag = MOI.get(mock, MOI.ListOfConstraintIndices{
40+
MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}}())
41+
@test length(diag) == 1
42+
MOI.set(mock, MOI.ConstraintName(), diag[1], "diag33")
43+
c = MOI.get(mock, MOI.ListOfConstraintIndices{
44+
MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}())
45+
@test length(c) == 1
46+
MOI.set(mock, MOI.ConstraintName(), c[1], "c")
47+
48+
s = """
49+
variables: Q11, Q12, Q13, Q22, Q23, Q33
50+
psd: [Q11, Q12, Q22, Q13, Q23, Q33] in MathOptInterface.PositiveSemidefiniteConeTriangle(3)
51+
off_diag23: Q23 == 0.0
52+
diag33: Q22 + -1.0Q33 == 0.0
53+
c: Q11 + 0.5Q22 <= 2.0
54+
maxobjective: Q12 + Q13
55+
"""
56+
model = MOIU.Model{Float64}()
57+
MOIU.loadfromstring!(model, s)
58+
MOIU.test_models_equal(mock, model, var_names, ["psd", "off_diag23", "diag33", "c"])
59+
end
60+
61+
@testset "Test bridged model" begin
62+
var_names = ["t", "u", "x", "y"]
63+
MOI.set(
64+
bridged_mock, MOI.VariableName(),
65+
MOI.get(bridged_mock, MOI.ListOfVariableIndices()), var_names)
66+
rsoc = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{
67+
MOI.VectorOfVariables, MOI.RotatedSecondOrderCone}())
68+
@test length(rsoc) == 1
69+
MOI.set(bridged_mock, MOI.ConstraintName(), rsoc[1], "rsoc")
70+
c = MOI.get(bridged_mock, MOI.ListOfConstraintIndices{
71+
MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}}())
72+
@test length(c) == 1
73+
MOI.set(bridged_mock, MOI.ConstraintName(), c[1], "c")
74+
75+
s = """
76+
variables: t, u, x, y
77+
rsoc: [t, u, x, y] in MathOptInterface.RotatedSecondOrderCone(4)
78+
c: t + u <= 2.0
79+
maxobjective: x + y
80+
"""
81+
model = MOIU.Model{Float64}()
82+
MOIU.loadfromstring!(model, s)
83+
MOIU.test_models_equal(bridged_mock, model, var_names, ["rsoc", "c"])
84+
end
85+
86+
87+
@testset "Delete" begin
88+
v = MOI.get(bridged_mock, MOI.ListOfVariableIndices())
89+
@test length(v) == 4
90+
91+
message = string("Cannot delete variable as it is constrained with other",
92+
" variables in a `MOI.VectorOfVariables`.")
93+
for i in 1:4
94+
err = MOI.DeleteNotAllowed(v[i], message)
95+
@test_throws err MOI.delete(bridged_mock, v[i])
96+
end
97+
98+
test_delete_bridged_variables(bridged_mock, v, MOI.RotatedSecondOrderCone, 4, (
99+
(MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle, 0),
100+
(MOI.SingleVariable, MOI.EqualTo{Float64}, 0),
101+
(MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, 0),
102+
))
103+
end
104+
end

0 commit comments

Comments
 (0)