Skip to content

Commit b56a9aa

Browse files
authored
Geometric conic form bridge (#296)
* Geometric conic form bridge * Fixes * Fixes * Fix format * Fixes * Fix format * Revert "Add test for typed tutorial" This reverts commit b406fcc. * Revert change * Add test * Fix format * Add docstring to docs * Use MOI master * Apply suggestions from code review * Update src/Bridges/Constraint/image.jl * Update src/Bridges/Constraint/image.jl
1 parent ef278a5 commit b56a9aa

File tree

7 files changed

+424
-4
lines changed

7 files changed

+424
-4
lines changed

docs/src/reference/standard_form.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ programming:
6060
```@docs
6161
SumOfSquares.DiagonallyDominantConeTriangle
6262
SumOfSquares.ScaledDiagonallyDominantConeTriangle
63-
SumOfSquares.Bridges.Variable.ScaledDiagonallyDominantBridge
6463
```
6564

6665
## Bridges
@@ -77,3 +76,13 @@ PolyJuMP.ScalarPolynomialFunction
7776
PolyJuMP.Bridges.Objective.ToPolynomialBridge
7877
PolyJuMP.Bridges.Constraint.ToPolynomialBridge
7978
```
79+
80+
Sum-of-Squares bridges
81+
```@docs
82+
SumOfSquares.Bridges.Constraint.ImageBridge
83+
```
84+
85+
Bridges for PSD cone approximations
86+
```@docs
87+
SumOfSquares.Bridges.Variable.ScaledDiagonallyDominantBridge
88+
```

src/Bridges/Constraint/Constraint.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ include("scaled_diagonally_dominant.jl")
2222

2323
# SOS polynomial bridges
2424
include("utilities.jl")
25+
include("image.jl")
2526
include("sos_polynomial.jl")
2627
include("sos_polynomial_in_semialgebraic_set.jl")
2728

@@ -30,7 +31,11 @@ include("sos_polynomial_in_semialgebraic_set.jl")
3031
function MOI.get(
3132
model::MOI.ModelLike,
3233
attr::SOS.SOSDecompositionAttribute,
33-
bridge::Union{SOSPolynomialBridge,SOSPolynomialInSemialgebraicSetBridge},
34+
bridge::Union{
35+
ImageBridge,
36+
SOSPolynomialBridge,
37+
SOSPolynomialInSemialgebraicSetBridge,
38+
},
3439
)
3540
gram = MOI.get(model, SOS.GramMatrixAttribute(attr.result_index), bridge)
3641
return SOS.SOSDecomposition(gram, attr.ranktol, attr.dec)

src/Bridges/Constraint/image.jl

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
"""
2+
ImageBridge{T,F,G,MT,MVT,CT} <: Bridges.Constraint.AbstractBridge
3+
4+
`ImageBridge` implements a reformulation from `SOSPolynomialSet{SemialgebraicSets.FullSpace}`
5+
into the positive semidefinite cone.
6+
7+
Let `Σ` be the SOS cone of polynomials of degree 2d and `S` be the PSD cone.
8+
There is a linear relation `Σ = A(S)`.
9+
The linear relation reads: `p` belongs to `Σ` iff there exists `q` in `S` such that `A(q) = p`.
10+
This allows defining a variable bridge that would create variables `p` and substitute `A(q)` for `p` but this is not the purpose of this bridge.
11+
This bridge exploit the following alternative read:
12+
`p` belongs to `Σ` iff there exists `q` in `S` such that ``q in A^{-1}(p)`` where `A^{-1}` is the preimage of `p`.
13+
This preimage can be obtained as `A^\\dagger p + \\mathrm{ker}(A)` where `A^\\dagger` is the pseudo-inverse of `A`.
14+
It turns out that for polynomial bases indexed by monomials, `A` is close to row echelon form so
15+
`A^\\dagger` and `\\mathrm{ker}(A)` can easily be obtained.
16+
17+
This is best described in an example.
18+
Consider the SOS constraint for the polynomial `p = 2x^4 + 2x^3 * y - x^2 * y^2 + 5y^4`
19+
with gram basis `b = [x^2, y^2, x * y]` of [Parrilo2003; Example 6.1](@cite).
20+
The product `b * b'` is
21+
```math
22+
\\begin{bmatrix}
23+
x^4 & x^2 y^2 & x^3 y\\
24+
x^2 y^2 & y^4 & x y^3\\
25+
x^3 y & x y^3 & x^2 y^2
26+
\\end{bmatrix}
27+
```
28+
Except for the entries `(1, 2)` and `(3, 3)`, all entries are unique so the value of the
29+
corresponding gram matrix is given by the corresponding coefficient of `p`.
30+
For the entries `(1, 2)` and `(3, 3)`, we let `-λ` be the `(1, 2)` and `(3, 3)` entries
31+
and we add `2λ` for the `(3, 3)` entry so as to parametrize entries for which the sum is
32+
the corresponding coefficient in `p`, i.e., `-1`.
33+
The gram matrix is therefore:
34+
```math
35+
\\begin{bmatrix}
36+
2 & -\\lambda & 1\\
37+
-\\lambda & 5 & 0\\
38+
1 & 0 & 2\\lambda - 1
39+
\\end{bmatrix}
40+
```
41+
42+
## Source node
43+
44+
`ImageBridge` supports:
45+
46+
* `H` in `SOSPolynomialSet{SemialgebraicSets.FullSpace}`
47+
48+
## Target nodes
49+
50+
`ImageBridge` creates one of the following, depending on the length of the gram basis:
51+
52+
* `F` in `MOI.PositiveSemidefiniteConeTriangle`, for gram basis of length at least 3
53+
* `F` in [`SumOfSquares.PositiveSemidefinite2x2ConeTriangle`](@ref), for gram basis of length 2
54+
* `F` in `MOI.Nonnegatives`, for gram basis of length 1
55+
* `F` in [`SumOfSquares.EmptyCone`](@ref), for empty gram basis
56+
57+
in addition to
58+
59+
* a constraint `G` in `MOI.Zeros` in case there is a monomial in `s.monomials`
60+
that cannot be obtained as product of elements in a gram basis.
61+
"""
62+
struct ImageBridge{
63+
T,
64+
F<:MOI.AbstractVectorFunction,
65+
G<:MOI.AbstractVectorFunction,
66+
M,
67+
} <: MOI.Bridges.Constraint.AbstractBridge
68+
variables::Vector{MOI.VariableIndex}
69+
constraints::Vector{MOI.ConstraintIndex{F}}
70+
zero_constraint::Union{Nothing,MOI.ConstraintIndex{G,MOI.Zeros}}
71+
set::SOS.WeightedSOSCone{M}
72+
first::Vector{Union{Nothing,Int}}
73+
end
74+
75+
function MOI.Bridges.Constraint.bridge_constraint(
76+
::Type{ImageBridge{T,F,G,M}},
77+
model::MOI.ModelLike,
78+
g::MOI.AbstractVectorFunction,
79+
set::SOS.WeightedSOSCone{M},
80+
) where {T,F,G,M}
81+
@assert MOI.output_dimension(g) == length(set.basis)
82+
scalars = MOI.Utilities.scalarize(g)
83+
k = 0
84+
found = Dict{eltype(set.basis.monomials),Int}()
85+
first = Union{Nothing,Int}[nothing for _ in eachindex(scalars)]
86+
variables = MOI.VariableIndex[]
87+
constraints = MOI.ConstraintIndex{F}[]
88+
for (gram_basis, weight) in zip(set.gram_bases, set.weights)
89+
cone = SOS.matrix_cone(M, length(gram_basis))
90+
f = MOI.Utilities.zero_with_output_dimension(F, MOI.dimension(cone))
91+
for j in eachindex(gram_basis.monomials)
92+
for i in 1:j
93+
k += 1
94+
mono = gram_basis.monomials[i] * gram_basis.monomials[j]
95+
is_diag = i == j
96+
if haskey(found, mono)
97+
var = MOI.add_variable(model)
98+
push!(variables, var)
99+
is_diag_found =
100+
MOI.Utilities.is_diagonal_vectorized_index(found[mono])
101+
if is_diag == is_diag_found
102+
MOI.Utilities.operate_output_index!(
103+
+,
104+
T,
105+
found[mono],
106+
f,
107+
var,
108+
)
109+
else
110+
coef = is_diag ? inv(T(2)) : T(2)
111+
MOI.Utilities.operate_output_index!(
112+
+,
113+
T,
114+
found[mono],
115+
f,
116+
coef * var,
117+
)
118+
end
119+
MOI.Utilities.operate_output_index!(-, T, k, f, var)
120+
else
121+
found[mono] = k
122+
t = MP.searchsortedfirst(set.basis.monomials, mono)
123+
if t in eachindex(set.basis.monomials) &&
124+
set.basis.monomials[t] == mono
125+
first[t] = k
126+
if is_diag
127+
MOI.Utilities.operate_output_index!(
128+
+,
129+
T,
130+
k,
131+
f,
132+
scalars[t],
133+
)
134+
else
135+
MOI.Utilities.operate_output_index!(
136+
+,
137+
T,
138+
k,
139+
f,
140+
inv(T(2)) * scalars[t],
141+
)
142+
end
143+
end
144+
end
145+
end
146+
end
147+
push!(constraints, MOI.add_constraint(model, f, cone))
148+
end
149+
if any(isnothing, first)
150+
z = findall(isnothing, first)
151+
zero_constraint = MOI.add_constraint(
152+
model,
153+
MOI.Utilities.vectorize(scalars[z]),
154+
MOI.Zeros(length(z)),
155+
)
156+
else
157+
zero_constraint = nothing
158+
end
159+
return ImageBridge{T,F,G,M}(
160+
variables,
161+
constraints,
162+
zero_constraint,
163+
set,
164+
first,
165+
)
166+
end
167+
168+
function MOI.supports_constraint(
169+
::Type{ImageBridge{T}},
170+
::Type{<:MOI.AbstractVectorFunction},
171+
::Type{<:SOS.WeightedSOSCone{M,<:MB.MonomialBasis,<:MB.MonomialBasis}},
172+
) where {T,M}
173+
return true
174+
end
175+
176+
function MOI.Bridges.added_constrained_variable_types(::Type{<:ImageBridge})
177+
return Tuple{Type}[(MOI.Reals,)]
178+
end
179+
180+
function MOI.Bridges.added_constraint_types(
181+
::Type{<:ImageBridge{T,F,G}},
182+
) where {T,F,G}
183+
return Tuple{Type,Type}[
184+
(F, MOI.PositiveSemidefiniteConeTriangle),
185+
(G, MOI.Zeros),
186+
] # TODO
187+
end
188+
189+
function MOI.Bridges.Constraint.concrete_bridge_type(
190+
::Type{<:ImageBridge{T}},
191+
G::Type{<:MOI.AbstractVectorFunction},
192+
::Type{<:SOS.WeightedSOSCone{M}},
193+
) where {T,M}
194+
# promotes VectorOfVariables into VectorAffineFunction, it should be enough
195+
# for most use cases
196+
F = MOI.Utilities.promote_operation(-, T, G, MOI.VectorOfVariables)
197+
return ImageBridge{T,F,G,M}
198+
end
199+
200+
# Attributes, Bridge acting as an model
201+
function MOI.get(bridge::ImageBridge, ::MOI.NumberOfVariables)
202+
return length(bridge.variables)
203+
end
204+
function MOI.get(bridge::ImageBridge, ::MOI.ListOfVariableIndices)
205+
return bridge.variables
206+
end
207+
function MOI.get(
208+
bridge::ImageBridge{T,F,G},
209+
::MOI.NumberOfConstraints{F,S},
210+
) where {T,F,G,S}
211+
return count(bridge.constraints) do ci
212+
return ci isa MOI.ConstraintIndex{F,S}
213+
end
214+
end
215+
function MOI.get(
216+
bridge::ImageBridge{T,F,G},
217+
::MOI.NumberOfConstraints{G,MOI.Zeros},
218+
) where {T,F,G}
219+
return isnothing(bridge.zero_constraint) ? 0 : 1
220+
end
221+
222+
function MOI.get(
223+
bridge::ImageBridge{T,F,G},
224+
::MOI.ListOfConstraintIndices{F,S},
225+
) where {T,F,G,S}
226+
return MOI.ConstraintIndex{F,S}[
227+
ci for ci in bridge.constraints if ci isa MOI.ConstraintIndex{F,S}
228+
]
229+
end
230+
231+
function MOI.get(
232+
bridge::ImageBridge{T,F,G},
233+
::MOI.ListOfConstraintIndices{G,MOI.Zeros},
234+
) where {T,F,G}
235+
if isnothing(bridge.zero_constraint)
236+
return MOI.ConstraintIndex{G,MOI.Zeros}[]
237+
else
238+
return [bridge.zero_constraint]
239+
end
240+
end
241+
242+
# Indices
243+
function MOI.delete(model::MOI.ModelLike, bridge::ImageBridge)
244+
if !isnothing(bridge.zero_constraint)
245+
MOI.delete(model, bridge.zero_constraint)
246+
end
247+
MOI.delete(model, bridge.constraints)
248+
if !isempty(bridge.variables)
249+
MOI.delete(model, bridge.variables)
250+
end
251+
return
252+
end
253+
254+
# Attributes, Bridge acting as a constraint
255+
function MOI.get(::MOI.ModelLike, ::MOI.ConstraintSet, bridge::ImageBridge)
256+
return bridge.set
257+
end
258+
259+
function MOI.get(
260+
model::MOI.ModelLike,
261+
attr::MOI.ConstraintFunction,
262+
bridge::ImageBridge{T},
263+
) where {T}
264+
if !isnothing(bridge.zero_constraint)
265+
z = MOI.Utilities.eachscalar(
266+
MOI.get(model, attr, bridge.zero_constraint),
267+
)
268+
end
269+
funcs =
270+
MOI.Utilities.eachscalar.(
271+
MOI.get.(model, MOI.ConstraintFunction(), bridge.constraints)
272+
)
273+
z_idx = 0
274+
return MOI.Utilities.vectorize(
275+
map(eachindex(bridge.first)) do i
276+
if isnothing(bridge.first[i])
277+
z_idx += 1
278+
return z[z_idx]
279+
else
280+
f = MOI.Utilities.filter_variables(
281+
!Base.Fix2(in, bridge.variables),
282+
funcs[1][bridge.first[i]], # FIXME
283+
)
284+
if !MOI.Utilities.is_diagonal_vectorized_index(bridge.first[i])
285+
f = T(2) * f
286+
end
287+
return f
288+
end
289+
end,
290+
)
291+
end
292+
293+
function MOI.get(::MOI.ModelLike, ::MOI.ConstraintPrimal, ::ImageBridge)
294+
throw(SOS.ValueNotSupported())
295+
end
296+
297+
function MOI.get(
298+
model::MOI.ModelLike,
299+
attr::Union{MOI.ConstraintDual,PolyJuMP.MomentsAttribute},
300+
bridge::ImageBridge{T},
301+
) where {T}
302+
dual =
303+
MOI.get(model, MOI.ConstraintDual(attr.result_index), bridge.constraint)
304+
output = similar(dual, length(bridge.set.monomials))
305+
for i in eachindex(bridge.set.monomials)
306+
output[i] = dual[bridge.first[i]]
307+
end
308+
return output
309+
end
310+
311+
function MOI.get(::MOI.ModelLike, ::SOS.CertificateBasis, bridge::ImageBridge)
312+
return bridge.gram_basis
313+
end
314+
315+
function MOI.get(
316+
model::MOI.ModelLike,
317+
attr::SOS.GramMatrixAttribute,
318+
bridge::ImageBridge{T,F,G,M},
319+
) where {T,F,G,M}
320+
# TODO there are several ones
321+
q = MOI.get(
322+
model,
323+
MOI.ConstraintPrimal(attr.result_index),
324+
bridge.constraint,
325+
)
326+
return SOS.build_gram_matrix(q, bridge.gram_basis, M, T)
327+
end
328+
function MOI.get(
329+
model::MOI.ModelLike,
330+
attr::SOS.MomentMatrixAttribute,
331+
bridge::ImageBridge,
332+
)
333+
# TODO there are several ones
334+
return SOS.build_moment_matrix(
335+
MOI.get(
336+
model,
337+
MOI.ConstraintDual(attr.result_index),
338+
bridge.constraint,
339+
),
340+
bridge.gram_basis,
341+
)
342+
end

src/constraints.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ function PolyJuMP.bridges(
316316
::Type{<:MOI.AbstractVectorFunction},
317317
S::Type{<:SOSPolynomialSet{<:AbstractAlgebraicSet}},
318318
)
319-
return [(
319+
return Tuple{Type,Type}[(
320320
Bridges.Constraint.SOSPolynomialBridge,
321321
_bridge_coefficient_type(S),
322322
)]

0 commit comments

Comments
 (0)