Skip to content

Commit a917931

Browse files
authored
Add tests for ScaledDiagonallyDominantBridge (#367)
* Add tests for ScaledDiagonallyDominantBridge * Add dual * typo fix
1 parent 2ce16ca commit a917931

File tree

4 files changed

+165
-30
lines changed

4 files changed

+165
-30
lines changed

docs/src/tutorials/Polynomial Optimization/bilinear.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
#md # [![](https://mybinder.org/badge_logo.svg)](@__BINDER_ROOT_URL__/generated/Polynomial Optimization/bilinear.ipynb)
44
#md # [![](https://img.shields.io/badge/show-nbviewer-579ACA.svg)](@__NBVIEWER_ROOT_URL__/generated/Polynomial Optimization/bilinear.ipynb)
5-
# **Adapted from**: [Floudas1999; Section 3.1](@cite) and [Lasserre2009; Table 5.1](@cite)
5+
# **Adapted from**: [Floudas1999; Section 3.2](@cite) and [Lasserre2009; Table 5.1](@cite)
66

77
# ## Introduction
88

9-
# Consider the polynomial optimization problem from [Floudas1999; Section 3.1](@cite).
9+
# Consider the polynomial optimization problem from [Floudas1999; Section 3.2](@cite).
1010

1111
using Test #src
1212
using DynamicPolynomials

src/Bridges/Variable/copositive_inner.jl

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -95,22 +95,12 @@ end
9595

9696
# TODO ConstraintPrimal, ConstraintDual
9797

98-
# See https://jump.dev/MathOptInterface.jl/v0.9.1/apireference/#MathOptInterface.AbstractSymmetricMatrixSetTriangle
99-
function matrix_indices(k)
100-
j = div(1 + isqrt(8k - 7), 2)
101-
i = k - div((j - 1) * j, 2)
102-
return i, j
103-
end
104-
# Vector index for the vectorization of the triangular part.
105-
function vector_index(i, j)
106-
return div((j - 1) * j, 2) + i
107-
end
10898
# Vector index for the vectorization of the off-diagonal triangular part.
10999
function offdiag_vector_index(i, j)
110100
if i < j
111-
return vector_index(i, j - 1)
101+
return MOI.Utilities.trimap(i, j - 1)
112102
else
113-
throw(ArgumentError())
103+
throw(ArgumentError("Not off-diagonal"))
114104
end
115105
end
116106

@@ -121,7 +111,7 @@ function MOI.get(
121111
i::MOI.Bridges.IndexInVector,
122112
)
123113
value = MOI.get(model, attr, bridge.matrix_variables[i.value])
124-
row, col = matrix_indices(i.value)
114+
row, col = MOI.Utilities.inverse_trimap(i.value)
125115
if row != col
126116
value += MOI.get(
127117
model,
@@ -138,13 +128,13 @@ function MOI.Bridges.bridged_function(
138128
) where {T}
139129
func =
140130
convert(MOI.ScalarAffineFunction{T}, bridge.matrix_variables[i.value])
141-
row, col = matrix_indices(i.value)
131+
row, col = MOI.Utilities.inverse_trimap(i.value)
142132
if row != col
143133
func = MOI.Utilities.operate!(
144134
+,
145135
T,
146136
func,
147-
bridge.nonneg_variables[vector_index(row, col - 1)],
137+
bridge.nonneg_variables[MOI.Utilities.trimap(row, col - 1)],
148138
)
149139
end
150140
return func
@@ -157,11 +147,11 @@ function MOI.Bridges.Variable.unbridged_map(
157147
F = MOI.ScalarAffineFunction{T}
158148
func = convert(F, vi)
159149
map = bridge.matrix_variables[i.value] => func
160-
row, col = matrix_indices(i.value)
150+
row, col = MOI.Utilities.inverse_trimap(i.value)
161151
if row == col
162152
return (map,)
163153
else
164-
nneg = bridge.nonneg_variables[vector_index(row, col - 1)]
154+
nneg = bridge.nonneg_variables[MOI.Utilities.trimap(row, col - 1)]
165155
return (map, nneg => zero(F))
166156
end
167157
end

src/Bridges/Variable/scaled_diagonally_dominant.jl

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ end
6767
function MOI.Bridges.added_constrained_variable_types(
6868
::Type{<:ScaledDiagonallyDominantBridge},
6969
)
70-
return [(SOS.PositiveSemidefinite2x2ConeTriangle,)]
70+
return Tuple{Type}[(SOS.PositiveSemidefinite2x2ConeTriangle,)]
7171
end
7272
function MOI.Bridges.added_constraint_types(
7373
::Type{<:ScaledDiagonallyDominantBridge},
@@ -86,7 +86,7 @@ function MOI.get(
8686
bridge::ScaledDiagonallyDominantBridge,
8787
::MOI.ListOfVariableIndices,
8888
)
89-
return Iterators.flatten(bridge.variables)
89+
return collect(Iterators.flatten(bridge.variables))
9090
end
9191
function MOI.get(
9292
bridge::ScaledDiagonallyDominantBridge,
@@ -127,17 +127,88 @@ function MOI.get(
127127
return SOS.ScaledDiagonallyDominantConeTriangle(bridge.side_dimension)
128128
end
129129

130-
# TODO ConstraintPrimal, ConstraintDual
130+
# The map `A` is not injective because it maps the entry of several 2x2 matrices
131+
# into the same index so it is not invertible hence it's unclear how to implemented
132+
# `set` for `VariablePrimalStart`.
133+
# The adjoint `A'` is however injective and it is easy to invert.
131134

132-
trimap(i, j) = div(j * (j - 1), 2) + i
135+
function MOI.supports(
136+
model::MOI.ModelLike,
137+
attr::MOI.ConstraintDualStart,
138+
::Type{<:ScaledDiagonallyDominantBridge},
139+
)
140+
return MOI.supports(
141+
model,
142+
attr,
143+
MOI.ConstraintIndex{
144+
MOI.VectorOfVariables,
145+
SOS.PositiveSemidefinite2x2ConeTriangle,
146+
},
147+
)
148+
end
149+
150+
function MOI.set(
151+
model::MOI.ModelLike,
152+
attr::MOI.ConstraintDualStart,
153+
bridge::ScaledDiagonallyDominantBridge,
154+
value,
155+
)
156+
n = bridge.side_dimension
157+
k = 0
158+
for j in 1:n
159+
for i in 1:(j-1)
160+
k += 1
161+
# PSD constraints on 2x2 matrices are SOC representable
162+
if isnothing(value)
163+
dual = nothing
164+
else
165+
dual = [
166+
value[MOI.Utilities.trimap(i, i)],
167+
value[MOI.Utilities.trimap(i, j)],
168+
value[MOI.Utilities.trimap(j, j)],
169+
]
170+
end
171+
MOI.set(model, attr, bridge.constraints[k], dual)
172+
end
173+
end
174+
return
175+
end
176+
177+
function MOI.get(
178+
model::MOI.ModelLike,
179+
attr::Union{MOI.ConstraintDual,MOI.ConstraintDualStart},
180+
bridge::ScaledDiagonallyDominantBridge{T},
181+
) where {T}
182+
n = bridge.side_dimension
183+
value = zeros(T, MOI.Utilities.trimap(n, n))
184+
k = 0
185+
for j in 1:n
186+
for i in 1:(j-1)
187+
k += 1
188+
dual = MOI.get(model, attr, bridge.constraints[k])
189+
if isnothing(dual)
190+
return nothing
191+
end
192+
# There are `bridge.side_dimension - 1` possible candidate that should all have
193+
# the same `dual` so we take an arbitrary choice
194+
if j == i + 1
195+
value[MOI.Utilities.trimap(i, i)] = dual[1]
196+
elseif i == 1 && j == n
197+
value[MOI.Utilities.trimap(j, j)] = dual[3]
198+
end
199+
value[MOI.Utilities.trimap(i, j)] = dual[2]
200+
end
201+
end
202+
return value
203+
end
133204

134205
function MOI.get(
135206
model::MOI.ModelLike,
136207
attr::MOI.VariablePrimal,
137208
bridge::ScaledDiagonallyDominantBridge{T},
138-
i::MOI.Bridges.IndexInVector,
209+
index::MOI.Bridges.IndexInVector,
139210
) where {T}
140-
i, j = matrix_indices(i.value)
211+
i, j = MOI.Utilities.inverse_trimap(index.value)
141212
if i == j
142213
value = zero(T)
143214
for k in 1:(i-1)
@@ -159,7 +230,7 @@ function MOI.Bridges.bridged_function(
159230
bridge::ScaledDiagonallyDominantBridge{T},
160231
i::MOI.Bridges.IndexInVector,
161232
) where {T}
162-
i, j = matrix_indices(i.value)
233+
i, j = MOI.Utilities.inverse_trimap(i.value)
163234
if i == j
164235
func = zero(MOI.ScalarAffineFunction{T})
165236
for k in 1:(i-1)
@@ -188,19 +259,28 @@ function MOI.Bridges.Variable.unbridged_map(
188259
k = 0
189260
z = zero(SAF)
190261
saf(i) = convert(SAF, vis[i])
191-
# vis[trimap(j, j)] is replaced by a sum of several variables.
262+
# vis[MOI.Utilities.trimap(j, j)] is replaced by a sum of several variables.
192263
# The strategy is to replace all of them by zero except one.
193264
for j in 1:bridge.side_dimension
194265
for i in 1:(j-1)
195266
k += 1
196267
if i == 1 && j == 2
197-
push!(umap, bridge.variables[k][1] => saf(trimap(1, 1)))
268+
push!(
269+
umap,
270+
bridge.variables[k][1] => saf(MOI.Utilities.trimap(1, 1)),
271+
)
198272
else
199273
push!(umap, bridge.variables[k][1] => z)
200274
end
201-
push!(umap, bridge.variables[k][2] => saf(trimap(i, j)))
275+
push!(
276+
umap,
277+
bridge.variables[k][2] => saf(MOI.Utilities.trimap(i, j)),
278+
)
202279
if i == 1
203-
push!(umap, bridge.variables[k][3] => saf(trimap(j, j)))
280+
push!(
281+
umap,
282+
bridge.variables[k][3] => saf(MOI.Utilities.trimap(j, j)),
283+
)
204284
else
205285
push!(umap, bridge.variables[k][3] => z)
206286
end
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
module TestVariableScaledDiagonallyDominant
2+
3+
using Test
4+
import MultivariateBases as MB
5+
using DynamicPolynomials
6+
using SumOfSquares
7+
8+
function runtests()
9+
for name in names(@__MODULE__; all = true)
10+
if startswith("$(name)", "test_")
11+
@testset "$(name)" begin
12+
getfield(@__MODULE__, name)()
13+
end
14+
end
15+
end
16+
return
17+
end
18+
19+
function test_error_dim_1()
20+
model = MOI.Utilities.Model{Float64}()
21+
bridged = MOI.Bridges.Variable.SingleBridgeOptimizer{
22+
SumOfSquares.Bridges.Variable.ScaledDiagonallyDominantBridge{Float64},
23+
}(
24+
model,
25+
)
26+
err = ErrorException(
27+
"The bridges does not work with 1, `matrix_cone` should have returned `Nonnegatives` instead.",
28+
)
29+
@test_throws err MOI.add_constrained_variables(
30+
bridged,
31+
SumOfSquares.ScaledDiagonallyDominantConeTriangle(1),
32+
)
33+
end
34+
35+
function test_runtests()
36+
@polyvar x y
37+
MOI.Bridges.runtests(
38+
SumOfSquares.Bridges.Variable.ScaledDiagonallyDominantBridge,
39+
model -> begin
40+
p, _ = MOI.add_constrained_variables(
41+
model,
42+
SumOfSquares.ScaledDiagonallyDominantConeTriangle(3),
43+
)
44+
end,
45+
model -> begin
46+
q1, _ = MOI.add_constrained_variables(
47+
model,
48+
SumOfSquares.PositiveSemidefinite2x2ConeTriangle(),
49+
)
50+
q2, _ = MOI.add_constrained_variables(
51+
model,
52+
SumOfSquares.PositiveSemidefinite2x2ConeTriangle(),
53+
)
54+
q3, _ = MOI.add_constrained_variables(
55+
model,
56+
SumOfSquares.PositiveSemidefinite2x2ConeTriangle(),
57+
)
58+
end,
59+
)
60+
return
61+
end
62+
63+
end # module
64+
65+
TestVariableScaledDiagonallyDominant.runtests()

0 commit comments

Comments
 (0)