Skip to content

Commit f8a56c4

Browse files
authored
Refactor GeometricMeanHypoCone into GenericConstraint (#648)
1 parent 846e4c1 commit f8a56c4

File tree

9 files changed

+148
-219
lines changed

9 files changed

+148
-219
lines changed

docs/src/manual/operations.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ solver (including SCS and Mosek).
116116
| `rootdet(X)` | n-th root-determinant of the $n$-by-$n$ matrix X, that is $det(X)^{1/n}$ | concave | not monotonic | |
117117
| `matrixfrac(x, P)` | $x^TP^{-1}x$ | convex | not monotonic | IC: P is positive semidefinite |
118118
| `sumlargesteigs(x, k)` | sum of top $k$ eigenvalues of $x$ | convex | not monotonic | IC: P symmetric |
119-
| `T in GeometricMeanHypoCone(A, B, t)` | $T \preceq A \#_t B = A^{1/2} (A^{-1/2} B A^{-1/2})^t A^{1/2}$ | concave | increasing | IC: $A \succeq 0$, $B \succeq 0$, $t \in [0,1]$ |
119+
| `Convex.GenericConstraint((T, A, B), GeometricMeanHypoConeSquare(t, size(T, 1)))` | $T \preceq A \#_t B = A^{1/2} (A^{-1/2} B A^{-1/2})^t A^{1/2}$ | concave | increasing | IC: $A \succeq 0$, $B \succeq 0$, $t \in [0,1]$ |
120120
| `Convex.GenericConstraint((T, A, B), GeometricMeanEpiConeSquare(t, size(T, 1)))` | $T \succeq A \#_t B = A^{1/2} (A^{-1/2} B A^{-1/2})^t A^{1/2}$ | convex | not monotonic | IC: $A \succeq 0$, $B \succeq 0$, $t \in [-1, 0] \cup [1, 2]$ |
121121
| `quantum_entropy(X)` | $-\textrm{Tr}(X \log X)$ | concave | not monotonic | IC: $X \succeq 0$; uses natural log |
122122
| `quantum_relative_entropy(A, B)` | $\textrm{Tr}(A \log A - A \log B)$ | convex | not monotonic | IC: $A \succeq 0$, $B \succeq 0$; uses natural log |

src/Convex.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export conv,
4444
sumlargesteigs,
4545
sumsmallest,
4646
sumsquares,
47-
GeometricMeanHypoCone,
47+
GeometricMeanHypoConeSquare,
4848
GeometricMeanEpiConeSquare,
4949
RelativeEntropyEpiCone,
5050
quantum_relative_entropy,

src/atoms/TraceMpowerAtom.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ function new_conic_form!(context::Context{T}, atom::TraceMpowerAtom) where {T}
9797
if 0 <= atom.t <= 1
9898
add_constraint!(
9999
context,
100-
tmp in GeometricMeanHypoCone(I, A, atom.t, false),
100+
GenericConstraint(
101+
(tmp, I, A),
102+
GeometricMeanHypoConeSquare(atom.t, n, false),
103+
),
101104
)
102105
else
103106
add_constraint!(

src/constraints/GeometricMeanEpiCone.jl

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,19 +84,32 @@ function _add_constraint!(
8484
)
8585
T, A, B = _get_matrices(constraint)
8686
t = constraint.set.t
87+
n = size(A, 1)
8788
Z = if sign(constraint.child) == ComplexSign()
88-
HermitianSemidefinite(size(A, 1))
89+
HermitianSemidefinite(n)
8990
else
90-
Semidefinite(size(A, 1))
91+
Semidefinite(n)
9192
end
9293
if t <= 0
9394
add_constraint!(context, [T A; A Z] 0)
94-
add_constraint!(context, Z in GeometricMeanHypoCone(A, B, -t, false))
95+
add_constraint!(
96+
context,
97+
GenericConstraint(
98+
(Z, A, B),
99+
GeometricMeanHypoConeSquare(-t, n, false),
100+
),
101+
)
95102
else
96103
# range of t checked in GeometricMeanEpiConeSquare constructor.
97104
@assert t >= 1
98105
add_constraint!(context, [T B; B Z] 0)
99-
add_constraint!(context, Z in GeometricMeanHypoCone(A, B, 2 - t, false))
106+
add_constraint!(
107+
context,
108+
GenericConstraint(
109+
(Z, A, B),
110+
GeometricMeanHypoConeSquare(2 - t, n, false),
111+
),
112+
)
100113
end
101114
return
102115
end

src/constraints/GeometricMeanHypoCone.jl

Lines changed: 70 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -30,158 +30,91 @@ REFERENCE
3030
theorem, matrix geometric means and semidefinite optimization" by Hamza
3131
Fawzi and James Saunderson (arXiv:1512.03401)
3232
"""
33-
mutable struct GeometricMeanHypoCone
34-
A::AbstractExpr
35-
B::AbstractExpr
33+
struct GeometricMeanHypoConeSquare <: MOI.AbstractVectorSet
3634
t::Rational
37-
size::Tuple{Int,Int}
35+
side_dimension::Int
3836
fullhyp::Bool
3937

40-
function GeometricMeanHypoCone(
41-
A::AbstractExpr,
42-
B::AbstractExpr,
38+
function GeometricMeanHypoConeSquare(
4339
t::Rational,
40+
side_dimension::Int,
4441
fullhyp::Bool = true,
4542
)
46-
if size(A) != size(B)
47-
throw(DimensionMismatch("A and B must be the same size"))
48-
end
49-
n = size(A)[1]
50-
if size(A) != (n, n)
51-
throw(DimensionMismatch("A and B must be square"))
52-
end
5343
if t < 0 || t > 1
5444
throw(DomainError(t, "t must be in the range [0, 1]"))
5545
end
56-
return new(A, B, t, (n, n), fullhyp)
57-
end
58-
59-
function GeometricMeanHypoCone(
60-
A::Value,
61-
B::AbstractExpr,
62-
t::Rational,
63-
fullhyp::Bool = true,
64-
)
65-
return GeometricMeanHypoCone(constant(A), B, t, fullhyp)
66-
end
67-
68-
function GeometricMeanHypoCone(
69-
A::AbstractExpr,
70-
B::Value,
71-
t::Rational,
72-
fullhyp::Bool = true,
73-
)
74-
return GeometricMeanHypoCone(A, constant(B), t, fullhyp)
46+
return new(t, side_dimension, fullhyp)
7547
end
48+
end
7649

77-
function GeometricMeanHypoCone(
78-
A::Value,
79-
B::Value,
80-
t::Rational,
81-
fullhyp::Bool = true,
82-
)
83-
return GeometricMeanHypoCone(constant(A), constant(B), t, fullhyp)
84-
end
50+
MOI.dimension(set::GeometricMeanHypoConeSquare) = 3 * set.side_dimension^2
8551

86-
function GeometricMeanHypoCone(
87-
A::Union{AbstractExpr,Value},
88-
B::Union{AbstractExpr,Value},
89-
t::Integer,
90-
fullhyp::Bool = true,
91-
)
92-
return GeometricMeanHypoCone(A, B, t // 1, fullhyp)
93-
end
52+
function head(io::IO, ::GeometricMeanHypoConeSquare)
53+
return print(io, "GeometricMeanHypoConeSquare")
9454
end
9555

96-
mutable struct GeometricMeanHypoConeConstraint <: Constraint
97-
T::AbstractExpr
98-
cone::GeometricMeanHypoCone
99-
100-
function GeometricMeanHypoConeConstraint(
101-
T::AbstractExpr,
102-
cone::GeometricMeanHypoCone,
103-
)
104-
if size(T) != cone.size
105-
throw(DimensionMismatch("T must be size $(cone.size)"))
56+
function GenericConstraint(func::Tuple, set::GeometricMeanHypoConeSquare)
57+
for f in func
58+
n = LinearAlgebra.checksquare(f)
59+
if n != set.side_dimension
60+
throw(
61+
DimensionMismatch(
62+
"Matrix of side dimension `$n` does not match set of side dimension `$(set.side_dimension)`",
63+
),
64+
)
10665
end
107-
return new(T, cone)
108-
end
109-
110-
function GeometricMeanHypoConeConstraint(
111-
T::Value,
112-
cone::GeometricMeanHypoCone,
113-
)
114-
return GeometricMeanHypoConeConstraint(constant(T), cone)
11566
end
67+
return GenericConstraint(vcat(vec.(func)...), set)
11668
end
11769

118-
function iscomplex(c::GeometricMeanHypoConeConstraint)
119-
return iscomplex(c.T) || iscomplex(c.cone)
120-
end
121-
122-
iscomplex(c::GeometricMeanHypoCone) = iscomplex(c.A) || iscomplex(c.B)
123-
124-
function head(io::IO, ::GeometricMeanHypoConeConstraint)
125-
return print(io, "∈(GeometricMeanHypoCone)")
126-
end
127-
128-
function Base.in(T, cone::GeometricMeanHypoCone)
129-
return GeometricMeanHypoConeConstraint(T, cone)
130-
end
131-
132-
function AbstractTrees.children(constraint::GeometricMeanHypoConeConstraint)
133-
return (
134-
constraint.T,
135-
constraint.cone.A,
136-
constraint.cone.B,
137-
constraint.cone.t,
138-
)
70+
function _get_matrices(c::GenericConstraint{GeometricMeanHypoConeSquare})
71+
n = c.set.side_dimension
72+
d = n^2
73+
T = reshape(c.child[1:d], n, n)
74+
A = reshape(c.child[d.+(1:d)], n, n)
75+
B = reshape(c.child[2d.+(1:d)], n, n)
76+
return T, A, B
13977
end
14078

14179
# For t ∈ [0,1] the t-weighted matrix geometric mean is matrix concave (arxiv:1512.03401).
14280
# So if A and B are convex sets, then T ⪯ A #_t B will be a convex set.
143-
function vexity(constraint::GeometricMeanHypoConeConstraint)
144-
A = vexity(constraint.cone.A)
145-
B = vexity(constraint.cone.B)
146-
T = vexity(constraint.T)
147-
148-
# NOTE: can't say A == NotDcp() because the NotDcp constructor prints a
149-
# warning message.
150-
if typeof(A) == ConvexVexity || typeof(A) == NotDcp
151-
return NotDcp()
152-
elseif typeof(B) == ConvexVexity || typeof(B) == NotDcp
153-
return NotDcp()
154-
end
155-
vex = -ConcaveVexity() + T
156-
if vex == ConcaveVexity()
81+
function vexity(constraint::GenericConstraint{GeometricMeanHypoConeSquare})
82+
T, A, B = _get_matrices(constraint)
83+
if vexity(A) in (ConvexVexity(), NotDcp()) ||
84+
vexity(B) in (ConvexVexity(), NotDcp())
15785
return NotDcp()
15886
end
159-
return vex
87+
return -ConcaveVexity() + vexity(T)
16088
end
16189

16290
function _add_constraint!(
16391
context::Context,
164-
constraint::GeometricMeanHypoConeConstraint,
92+
constraint::GenericConstraint{GeometricMeanHypoConeSquare},
16593
)
166-
A = constraint.cone.A
167-
B = constraint.cone.B
168-
t = constraint.cone.t
169-
T = constraint.T
170-
fullhyp = constraint.cone.fullhyp
94+
T, A, B = _get_matrices(constraint)
95+
t = constraint.set.t
96+
fullhyp = constraint.set.fullhyp
17197

17298
is_complex =
17399
sign(A) == ComplexSign() ||
174100
sign(B) == ComplexSign() ||
175101
sign(T) == ComplexSign()
102+
n = size(A, 1)
176103
if is_complex
177-
make_temporary = () -> HermitianSemidefinite(size(A)[1])
104+
make_temporary = () -> HermitianSemidefinite(n)
178105
else
179-
make_temporary = () -> Semidefinite(size(A)[1])
106+
make_temporary = () -> Semidefinite(n)
180107
end
181108

182109
if fullhyp && t != 0 && t != 1
183110
W = make_temporary()
184-
add_constraint!(context, W in GeometricMeanHypoCone(A, B, t, false))
111+
add_constraint!(
112+
context,
113+
GenericConstraint(
114+
(W, A, B),
115+
GeometricMeanHypoConeSquare(t, n, false),
116+
),
117+
)
185118
add_constraint!(context, W T)
186119
else
187120
p = t.num
@@ -206,13 +139,19 @@ function _add_constraint!(
206139
if t < 1 / 2
207140
add_constraint!(
208141
context,
209-
Z in GeometricMeanHypoCone(A, B, 2 * t, false),
142+
GenericConstraint(
143+
(Z, A, B),
144+
GeometricMeanHypoConeSquare(2 * t, n, false),
145+
),
210146
)
211147
add_constraint!(context, [A T; T' Z] 0)
212148
else
213149
add_constraint!(
214150
context,
215-
Z in GeometricMeanHypoCone(A, B, 2 * t - 1, false),
151+
GenericConstraint(
152+
(Z, A, B),
153+
GeometricMeanHypoConeSquare(2 * t - 1, n, false),
154+
),
216155
)
217156
add_constraint!(context, [B T; T' Z] 0)
218157
end
@@ -221,7 +160,10 @@ function _add_constraint!(
221160
Z = make_temporary()
222161
add_constraint!(
223162
context,
224-
Z in GeometricMeanHypoCone(A, T, (2 * p - q) // p, false),
163+
GenericConstraint(
164+
(Z, A, T),
165+
GeometricMeanHypoConeSquare((2 * p - q) // p, n, false),
166+
),
225167
)
226168
add_constraint!(context, [Z T; T B] 0)
227169
elseif t < 1 / 2
@@ -231,18 +173,28 @@ function _add_constraint!(
231173
l = floor(Int, log2(q))
232174
add_constraint!(
233175
context,
234-
X in GeometricMeanHypoCone(A, B, p // (2^l), false),
176+
GenericConstraint(
177+
(X, A, B),
178+
GeometricMeanHypoConeSquare(p // (2^l), n, false),
179+
),
235180
)
236181
add_constraint!(
237182
context,
238-
T in GeometricMeanHypoCone(A, X, (2^l) // q, false),
183+
GenericConstraint(
184+
(T, A, X),
185+
GeometricMeanHypoConeSquare((2^l) // q, n, false),
186+
),
239187
)
240188
else
241189
#println("geom_mean_hypocone p=$p q=$q else")
242190
add_constraint!(
243191
context,
244-
T in GeometricMeanHypoCone(B, A, 1 - t, false),
192+
GenericConstraint(
193+
(T, B, A),
194+
GeometricMeanHypoConeSquare(1 - t, n, false),
195+
),
245196
)
246197
end
247198
end
199+
return
248200
end

src/constraints/RelativeEntropyEpiCone.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,10 @@ function _add_constraint!(
185185
end
186186
add_constraint!(
187187
context,
188-
Z in GeometricMeanHypoCone(X, Y, 1 // (2^k), false),
188+
GenericConstraint(
189+
(Z, X, Y),
190+
GeometricMeanHypoConeSquare(1 // (2^k), n, false),
191+
),
189192
)
190193
for ii in 1:m
191194
# Note that we are dividing by w here because it is easier

0 commit comments

Comments
 (0)