Skip to content

Commit 8c8636f

Browse files
blegatodow
andauthored
Add HermitianPositiveSemidefiniteConeTriangle with bridges (#1962)
* Add bridge from hermitian PSD to PSD * Update src/Bridges/Variable/bridges/hermitian.jl Co-authored-by: Oscar Dowson <[email protected]> * Update src/Bridges/Variable/bridges/hermitian.jl Co-authored-by: Oscar Dowson <[email protected]> * Update src/Test/test_conic.jl Co-authored-by: Oscar Dowson <[email protected]> * Update src/Bridges/Variable/bridges/hermitian.jl Co-authored-by: Oscar Dowson <[email protected]> * Update src/Bridges/Variable/bridges/hermitian.jl Co-authored-by: Oscar Dowson <[email protected]> * Update src/Test/test_conic.jl Co-authored-by: Oscar Dowson <[email protected]> * Update src/Test/test_conic.jl Co-authored-by: Oscar Dowson <[email protected]> * Fix format * Address review comments * Add docstring * Add bridge tests * Tidy new tests * Use import LinearAlgebra in tests * Simplifications * Update coverage * Add explanation back * Clean up test * Fix * Update src/Test/test_conic.jl Co-authored-by: Oscar Dowson <[email protected]> Co-authored-by: odow <[email protected]>
1 parent db2c00f commit 8c8636f

File tree

10 files changed

+731
-1
lines changed

10 files changed

+731
-1
lines changed

docs/src/manual/standard_form.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ The matrix-valued set types implemented in MathOptInterface.jl are:
9393
| [`LogDetConeTriangle(d)`](@ref MathOptInterface.LogDetConeTriangle) | ``\{ (t,u,X) \in \mathbb{R}^{2+d(1+d)/2} : t \le u\log(\det(X/u)), X \mbox{ is the upper triangle of a PSD matrix}, u > 0 \}`` |
9494
| [`LogDetConeSquare(d)`](@ref MathOptInterface.LogDetConeSquare) | ``\{ (t,u,X) \in \mathbb{R}^{2+d^2} : t \le u \log(\det(X/u)), X \mbox{ is a PSD matrix}, u > 0 \}`` |
9595
| [`NormSpectralCone(r, c)`](@ref MathOptInterface.NormSpectralCone) | ``\{ (t, X) \in \mathbb{R}^{1 + r \times c} : t \ge \sigma_1(X), X \mbox{ is a } r\times c\mbox{ matrix} \}``
96-
| [`NormNuclearCone(r, c)`](@ref MathOptInterface.NormNuclearCone) | ``\{ (t, X) \in \mathbb{R}^{1 + r \times c} : t \ge \sum_i \sigma_i(X), X \mbox{ is a } r\times c\mbox{ matrix} \}``
96+
| [`NormNuclearCone(r, c)`](@ref MathOptInterface.NormNuclearCone) | ``\{ (t, X) \in \mathbb{R}^{1 + r \times c} : t \ge \sum_i \sigma_i(X), X \mbox{ is a } r\times c\mbox{ matrix} \}`` |
97+
| [`HermitianPositiveSemidefiniteConeTriangle(d)`](@ref MathOptInterface.HermitianPositiveSemidefiniteConeTriangle) | The cone of Hermitian positive semidefinite matrices, with
98+
`side_dimension` rows and columns. |
9799

98100
Some of these cones can take two forms: `XXXConeTriangle` and `XXXConeSquare`.
99101

docs/src/reference/standard_form.md

+1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ List of recognized matrix sets.
141141
```@docs
142142
PositiveSemidefiniteConeTriangle
143143
PositiveSemidefiniteConeSquare
144+
HermitianPositiveSemidefiniteConeTriangle
144145
LogDetConeTriangle
145146
LogDetConeSquare
146147
RootDetConeTriangle

docs/src/submodules/Bridges/list_of_bridges.md

+1
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,5 @@ Bridges.Variable.RSOCtoSOCBridge
8484
Bridges.Variable.SOCtoRSOCBridge
8585
Bridges.Variable.VectorizeBridge
8686
Bridges.Variable.ZerosBridge
87+
Bridges.Variable.HermitianToSymmetricPSDBridge
8788
```

src/Bridges/Variable/Variable.jl

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ include("bridges/rsoc_soc.jl")
2222
include("bridges/soc_rsoc.jl")
2323
include("bridges/vectorize.jl")
2424
include("bridges/zeros.jl")
25+
include("bridges/hermitian.jl")
2526

2627
"""
2728
add_all_bridges(model, ::Type{T}) where {T}
@@ -38,6 +39,7 @@ function add_all_bridges(model, ::Type{T}) where {T}
3839
MOI.Bridges.add_bridge(model, SOCtoRSOCBridge{T})
3940
MOI.Bridges.add_bridge(model, RSOCtoSOCBridge{T})
4041
MOI.Bridges.add_bridge(model, RSOCtoPSDBridge{T})
42+
MOI.Bridges.add_bridge(model, HermitianToSymmetricPSDBridge{T})
4143
return
4244
end
4345

+334
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
# Copyright (c) 2017: Miles Lubin and contributors
2+
# Copyright (c) 2017: Google Inc.
3+
#
4+
# Use of this source code is governed by an MIT-style license that can be found
5+
# in the LICENSE.md file or at https://opensource.org/licenses/MIT.
6+
7+
"""
8+
HermitianToSymmetricPSDBridge{T} <: Bridges.Variable.AbstractBridge
9+
10+
`HermitianToSymmetricPSDBridge` implements the following reformulation:
11+
12+
* Hermitian positive semidefinite `n x n` complex matrix to a symmetric
13+
positive semidefinite `2n x 2n` real matrix satisfying equality constraints
14+
described below.
15+
16+
## Source node
17+
18+
`HermitianToSymmetricPSDBridge` supports:
19+
20+
* [`MOI.VectorOfVariables`](@ref) in
21+
[`MOI.HermitianPositiveSemidefiniteConeTriangle`](@ref)
22+
23+
## Target node
24+
25+
`HermitianToSymmetricPSDBridge` creates:
26+
27+
* [`MOI.VectorOfVariables`](@ref) in [`MOI.PositiveSemidefiniteConeTriangle`](@ref)
28+
* [`MOI.ScalarAffineFunction{T}`](@ref) in [`MOI.EqualTo{T}`](@ref)
29+
30+
## Reformulation
31+
32+
The reformulation is best described by example.
33+
34+
The Hermitian matrix:
35+
```math
36+
\\begin{bmatrix}
37+
x_{11} & x_{12} + y_{12}im & x_{13} + y_{13}im\\\\
38+
x_{12} - y_{12}im & x_{22} & x_{23} + y_{23}im\\\\
39+
x_{13} - y_{13}im & x_{23} - y_{23}im & x_{33}
40+
\\end{bmatrix}
41+
```
42+
is positive semidefinite if and only if the symmetric matrix:
43+
```math
44+
\\begin{bmatrix}
45+
x_{11} & x_{12} & x_{13} & 0 & y_{12} & y_{13} \\\\
46+
& x_{22} & x_{23} & -y_{12} & 0 & y_{23} \\\\
47+
& & x_{33} & -y_{13} & -y_{23} & 0 \\\\
48+
& & & x_{11} & x_{12} & x_{13} \\\\
49+
& & & & x_{22} & x_{23} \\\\
50+
& & & & & x_{33}
51+
\\end{bmatrix}
52+
```
53+
is positive semidefinite.
54+
55+
The bridge achieves this reformulation by adding a new set of variables in
56+
`MOI.PositiveSemidefiniteConeTriangle(6)`, and then adding three groups of
57+
equality constraints to:
58+
59+
* constrain the two `x` blocks to be equal
60+
* force the diagonal of the `y` blocks to be `0`
61+
* force the lower triangular of the `y` block to be the negative of the upper
62+
triangle.
63+
"""
64+
struct HermitianToSymmetricPSDBridge{T} <: AbstractBridge
65+
variables::Vector{MOI.VariableIndex}
66+
psd::MOI.ConstraintIndex{
67+
MOI.VectorOfVariables,
68+
MOI.PositiveSemidefiniteConeTriangle,
69+
}
70+
n::Int
71+
ceq::Vector{MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}}
72+
end
73+
74+
const HermitianToSymmetricPSD{T,OT<:MOI.ModelLike} =
75+
SingleBridgeOptimizer{HermitianToSymmetricPSDBridge{T},OT}
76+
77+
function bridge_constrained_variable(
78+
::Type{HermitianToSymmetricPSDBridge{T}},
79+
model::MOI.ModelLike,
80+
set::MOI.HermitianPositiveSemidefiniteConeTriangle,
81+
) where {T}
82+
n = set.side_dimension
83+
variables, psd_ci = MOI.add_constrained_variables(
84+
model,
85+
MOI.PositiveSemidefiniteConeTriangle(2n),
86+
)
87+
ceq = MOI.ConstraintIndex{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}}[]
88+
k11 = 0
89+
k12 = k22 = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(n))
90+
function X21(i, j)
91+
I, J = j, n + i
92+
k21 = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(J - 1)) + I
93+
return variables[k21]
94+
end
95+
for j in 1:n
96+
k22 += n
97+
for i in 1:j
98+
k11 += 1
99+
k12 += 1
100+
k22 += 1
101+
f_x = MOI.Utilities.operate(-, T, variables[k11], variables[k22])
102+
push!(ceq, MOI.add_constraint(model, f_x, MOI.EqualTo(zero(T))))
103+
if i == j # y_{ii} = 0
104+
f_0 = convert(MOI.ScalarAffineFunction{T}, variables[k12])
105+
push!(ceq, MOI.add_constraint(model, f_0, MOI.EqualTo(zero(T))))
106+
else # y_{ij} = -y_{ji}
107+
f_y = MOI.Utilities.operate(+, T, X21(i, j), variables[k12])
108+
push!(ceq, MOI.add_constraint(model, f_y, MOI.EqualTo(zero(T))))
109+
end
110+
end
111+
k12 += n
112+
end
113+
return HermitianToSymmetricPSDBridge(variables, psd_ci, n, ceq)
114+
end
115+
116+
function supports_constrained_variable(
117+
::Type{<:HermitianToSymmetricPSDBridge},
118+
::Type{MOI.HermitianPositiveSemidefiniteConeTriangle},
119+
)
120+
return true
121+
end
122+
123+
function MOI.Bridges.added_constrained_variable_types(
124+
::Type{<:HermitianToSymmetricPSDBridge},
125+
)
126+
return Tuple{Type}[(MOI.PositiveSemidefiniteConeTriangle,)]
127+
end
128+
129+
function MOI.Bridges.added_constraint_types(
130+
::Type{HermitianToSymmetricPSDBridge{T}},
131+
) where {T}
132+
return Tuple{Type,Type}[(MOI.ScalarAffineFunction{T}, MOI.EqualTo{T})]
133+
end
134+
135+
function MOI.get(bridge::HermitianToSymmetricPSDBridge, ::MOI.NumberOfVariables)
136+
return length(bridge.variables)
137+
end
138+
139+
function MOI.get(
140+
bridge::HermitianToSymmetricPSDBridge,
141+
::MOI.ListOfVariableIndices,
142+
)
143+
return copy(bridge.variables)
144+
end
145+
146+
function MOI.get(
147+
::HermitianToSymmetricPSDBridge,
148+
::MOI.NumberOfConstraints{
149+
MOI.VectorOfVariables,
150+
MOI.PositiveSemidefiniteConeTriangle,
151+
},
152+
)::Int64
153+
return 1
154+
end
155+
156+
function MOI.get(
157+
bridge::HermitianToSymmetricPSDBridge,
158+
::MOI.ListOfConstraintIndices{
159+
MOI.VectorOfVariables,
160+
MOI.PositiveSemidefiniteConeTriangle,
161+
},
162+
)
163+
return [bridge.psd]
164+
end
165+
166+
function MOI.get(
167+
bridge::HermitianToSymmetricPSDBridge{T},
168+
::MOI.NumberOfConstraints{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}},
169+
) where {T}
170+
return length(bridge.ceq)
171+
end
172+
173+
function MOI.get(
174+
bridge::HermitianToSymmetricPSDBridge{T},
175+
::MOI.ListOfConstraintIndices{MOI.ScalarAffineFunction{T},MOI.EqualTo{T}},
176+
) where {T}
177+
return copy(bridge.ceq)
178+
end
179+
180+
function MOI.delete(model::MOI.ModelLike, bridge::HermitianToSymmetricPSDBridge)
181+
MOI.delete(model, bridge.ceq)
182+
MOI.delete(model, bridge.variables)
183+
return
184+
end
185+
186+
function MOI.get(
187+
::MOI.ModelLike,
188+
::MOI.ConstraintSet,
189+
bridge::HermitianToSymmetricPSDBridge,
190+
)
191+
return MOI.HermitianPositiveSemidefiniteConeTriangle(bridge.n)
192+
end
193+
194+
function _matrix_indices(k)
195+
# If `k` is a diagonal index, `s(k)` is odd and 1 + 8k is a perfect square.
196+
n = 1 + 8k
197+
s = isqrt(n)
198+
j = if s^2 == n
199+
div(s, 2)
200+
else
201+
# Otherwise, if it is after the diagonal index `k` but before the
202+
# diagonal index `k'` with `s(k') = s(k) + 2`, we have
203+
# `s(k) <= s < s(k) + 2`.
204+
# By shifting by `+1` before `div`, we make sure to have the right
205+
# column.
206+
div(s + 1, 2)
207+
end
208+
i = k - MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(j - 1))
209+
return i, j
210+
end
211+
212+
function _variable_map(idx::MOI.Bridges.IndexInVector, n)
213+
N = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(n))
214+
if idx.value <= N
215+
return idx.value
216+
end
217+
i, j = _matrix_indices(idx.value - N)
218+
d = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(j))
219+
return N + j * n + d + i
220+
end
221+
222+
function _variable(
223+
bridge::HermitianToSymmetricPSDBridge,
224+
i::MOI.Bridges.IndexInVector,
225+
)
226+
return bridge.variables[_variable_map(i, bridge.n)]
227+
end
228+
229+
function MOI.get(
230+
model::MOI.ModelLike,
231+
attr::MOI.ConstraintPrimal,
232+
bridge::HermitianToSymmetricPSDBridge{T},
233+
) where {T}
234+
values = MOI.get(model, attr, bridge.psd)
235+
M = MOI.dimension(MOI.get(model, MOI.ConstraintSet(), bridge))
236+
n = bridge.n
237+
return [values[_variable_map(MOI.Bridges.IndexInVector(i), n)] for i in 1:M]
238+
end
239+
240+
# We don't need to take into account the equality constraints. We just need to
241+
# sum up (with appropriate +/-) each dual variable associated with the original
242+
# x or y element.
243+
# The reason for this is as follows:
244+
# Suppose for simplicity that the elements of a `2n x 2n` matrix are ordered as:
245+
# ```
246+
# \\ 1 |\\ 2
247+
# \\ | 3
248+
# \\ |4_\\
249+
# \\ 5
250+
# \\
251+
# \\
252+
# ```
253+
# Let `H = HermitianToSymmetricPSDBridge(n)`,
254+
# `S = PositiveSemidefiniteConeTriangle(2n)` and `ceq` be the linear space of
255+
# `2n x 2n` symmetric matrices such that the block `1` and `5` are equal, `2` and `4` are opposite and `3` is zero.
256+
# We consider the cone `P = S ∩ ceq`.
257+
# We have `P = A * H` where
258+
# ```
259+
# [I 0]
260+
# [0 I]
261+
# A = [0 0]
262+
# [0 -I]
263+
# [I 0]
264+
# ```
265+
# Therefore, `H* = A* * P*` where
266+
# ```
267+
# [I 0 0 0 I]
268+
# A* = [0 I 0 -I 0]
269+
# ```
270+
# Moreover, as `(S ∩ T)* = S* + T*` for cones `S` and `T`, we have
271+
# ```
272+
# P* = S* + ceq*
273+
# ```
274+
# the dual vector of `P*` is the dual vector of `S*` for which we add in the corresponding
275+
# entries the dual of the three constraints, multiplied by the coefficients for the `EqualTo` constraints.
276+
# Note that these contributions cancel out when we multiply them by `A*`:
277+
# A* * (S* + ceq*) = A* * S*
278+
# so we can just ignore them.
279+
function MOI.get(
280+
model::MOI.ModelLike,
281+
attr::MOI.ConstraintDual,
282+
bridge::HermitianToSymmetricPSDBridge{T},
283+
) where {T}
284+
dual = MOI.get(model, attr, bridge.psd)
285+
M = MOI.dimension(MOI.get(model, MOI.ConstraintSet(), bridge))
286+
result = zeros(T, M)
287+
n = bridge.n
288+
N = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(n))
289+
k11, k12, k22 = 0, N, N
290+
k21 = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(2n)) + 1
291+
k = 0
292+
for j in 1:n
293+
k21 -= n + 1 - j
294+
k22 += n
295+
for i in 1:j
296+
k11 += 1
297+
k12 += 1
298+
k21 -= 1
299+
k22 += 1
300+
result[k11] += dual[k11] + dual[k22]
301+
if i != j
302+
k += 1
303+
result[N+k] += dual[k12] - dual[k21]
304+
end
305+
end
306+
k12 += n
307+
k21 -= n - j
308+
end
309+
return result
310+
end
311+
312+
function MOI.get(
313+
model::MOI.ModelLike,
314+
attr::MOI.VariablePrimal,
315+
bridge::HermitianToSymmetricPSDBridge{T},
316+
i::MOI.Bridges.IndexInVector,
317+
) where {T}
318+
return MOI.get(model, attr, _variable(bridge, i))
319+
end
320+
321+
function MOI.Bridges.bridged_function(
322+
bridge::HermitianToSymmetricPSDBridge{T},
323+
i::MOI.Bridges.IndexInVector,
324+
) where {T}
325+
return convert(MOI.ScalarAffineFunction{T}, _variable(bridge, i))
326+
end
327+
328+
function unbridged_map(
329+
bridge::HermitianToSymmetricPSDBridge{T},
330+
vi::MOI.VariableIndex,
331+
i::MOI.Bridges.IndexInVector,
332+
) where {T}
333+
return (_variable(bridge, i) => convert(MOI.ScalarAffineFunction{T}, vi),)
334+
end

src/Test/Test.jl

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
module Test
88

9+
import LinearAlgebra
10+
911
using MathOptInterface
1012
const MOI = MathOptInterface
1113
const MOIU = MOI.Utilities

0 commit comments

Comments
 (0)