@@ -4,16 +4,21 @@ using Base: oneto
44using CliqueTrees
55using CliqueTrees: EliminationAlgorithm
66using LinearAlgebra
7+ using Graphs
78using SparseArrays
89
910import MathOptInterface as MOI
1011
1112struct Decomposition
1213 neqns:: Int
13- values:: Vector{Int}
14- cliques:: SparseMatrixCSC{Bool, Int}
14+ value:: Vector{Int}
15+ label:: Vector{Int}
16+ tree:: CliqueTree{Int, Int}
1517end
1618
19+ """
20+ Optimizer(inner; alg::EliminationAlgorithm=MF())
21+ """
1722mutable struct Optimizer{A <: EliminationAlgorithm } <: MOI.AbstractOptimizer
1823 inner:: MOI.AbstractOptimizer
1924 outer_to_inner:: Dict{Int, Decomposition}
@@ -91,7 +96,9 @@ function MOI.get(model::Optimizer, attr::_ATTRIBUTES, arg::Vector{T}) where {T}
9196 return MOI. get .(model, attr, arg)
9297end
9398
94- # ## AbstractOptimizerAttribute
99+ # -------------------------- #
100+ # AbstractOptimizerAttribute #
101+ # -------------------------- #
95102
96103function MOI. supports (model:: Optimizer , arg:: MOI.AbstractOptimizerAttribute )
97104 return MOI. supports (model. inner, arg)
@@ -106,13 +113,17 @@ function MOI.get(model::Optimizer, attr::MOI.AbstractOptimizerAttribute)
106113 return MOI. get (model. inner, attr)
107114end
108115
109- # ## AbstractModelAttribute
116+ # ---------------------- #
117+ # AbstractModelAttribute #
118+ # ---------------------- #
110119
111120function MOI. supports (model:: Optimizer , arg:: MOI.AbstractModelAttribute )
112121 return MOI. supports (model. inner, arg)
113122end
114123
115- # ## AbstractVariableAttribute
124+ # ------------------------- #
125+ # AbstractVariableAttribute #
126+ # ------------------------- #
116127
117128function MOI. is_valid (model:: Optimizer , x:: MOI.VariableIndex )
118129 return MOI. is_valid (model. inner, x)
@@ -145,7 +156,9 @@ function MOI.set(
145156 return
146157end
147158
148- # ## AbstractConstraintAttribute
159+ # --------------------------- #
160+ # AbstractConstraintAttribute #
161+ # --------------------------- #
149162
150163function MOI. is_valid (model:: Optimizer , ci:: MOI.ConstraintIndex )
151164 return MOI. is_valid (model. inner, ci)
@@ -198,7 +211,9 @@ function MOI.add_constraint(
198211 return MOI. add_constraint (model. inner, f, s)
199212end
200213
201- # Decomposition
214+ # ################
215+ # Decomposition #
216+ # ################
202217
203218function MOI. add_constraint (
204219 model:: Optimizer ,
@@ -214,32 +229,23 @@ function MOI.add_constraint(
214229 # compute tree decomposition
215230 label, tree = cliquetree (pattern; alg = model. alg)
216231
217- # compute cliques
218- cliques = spzeros (Bool, n, length (tree))
219-
220- for (b, bag) in enumerate (tree)
221- append! (cliques. rowval, bag)
222- cliques. colptr[b + 1 ] = cliques. colptr[b] + length (bag)
223- end
224-
225- resize! (cliques. nzval, length (cliques. rowval))
226- permute! (cliques, invperm (label), axes (cliques, 2 ))
227-
228232 # terms
229- indices = Int[]
233+ value = Int[]
230234 terms = MOI. VectorAffineTerm{T}[]
231235
232- for b in axes (cliques, 2 )
233- clique = view (cliques . rowval, nzrange (cliques, b)); m = length (clique )
236+ for bag in tree
237+ m = length (bag )
234238 U = MOI. add_variables (model, m * (m + 1 ) ÷ 2 )
235239
236240 for j in oneto (m), i in oneto (j)
237241 v = U[idx (i, j)]
238- push! (terms, MOI. VectorAffineTerm (idx (clique[i], clique[j]), MOI. ScalarAffineTerm (- 1.0 , v)))
242+ ii = label[bag[i]]
243+ jj = label[bag[j]]
244+ push! (terms, MOI. VectorAffineTerm (idx (ii, jj), MOI. ScalarAffineTerm (- 1.0 , v)))
239245 end
240246
241247 index = MOI. add_constraint (model. inner, MOI. VectorOfVariables (U), MOI. PositiveSemidefiniteConeTriangle (m))
242- push! (indices , index. value)
248+ push! (value , index. value)
243249 end
244250
245251 for (v, a) in zip (V, A), j in axes (a, 2 )
@@ -263,9 +269,9 @@ function MOI.add_constraint(
263269 end
264270 end
265271
266- value = MOI. add_constraint (model. inner, MOI. VectorAffineFunction (terms, constants), MOI. Zeros (n * (n + 1 ) ÷ 2 )). value
267- model. outer_to_inner[value ] = Decomposition (n, indices, cliques )
268- return MOI. ConstraintIndex {MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle} (value )
272+ i = MOI. add_constraint (model. inner, MOI. VectorAffineFunction (terms, constants), MOI. Zeros (n * (n + 1 ) ÷ 2 )). value
273+ model. outer_to_inner[i ] = Decomposition (n, value, label, tree )
274+ return MOI. ConstraintIndex {MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle} (i )
269275end
270276
271277function MOI. get (
@@ -302,25 +308,24 @@ function MOI.get(
302308
303309 decomposition = model. outer_to_inner[index. value]
304310 neqns = decomposition. neqns
305- values = decomposition. values
306- cliques = decomposition. cliques
311+ value = decomposition. value
312+ label = decomposition. label
313+ tree = decomposition. tree
307314 result = zeros (Float64, neqns * (neqns + 1 ) ÷ 2 )
308315
309- for b in axes (cliques, 2 )
310- clique = view (cliques. rowval, nzrange (cliques, b))
311- value = values[b]
316+ for (k, bag) in zip (value, tree)
317+ m = length (bag)
312318
313319 part = MOI. get (
314320 model. inner,
315321 attribute,
316- MOI. ConstraintIndex {MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle} (value ),
322+ MOI. ConstraintIndex {MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle} (k ),
317323 )
318324
319- for (j, w) in enumerate (clique)
320- for (i, v) in enumerate (clique)
321- i > j && break
322- result[idx (v, w)] += part[idx (i, j)]
323- end
325+ for j in oneto (m), i in oneto (j)
326+ ii = label[bag[i]]
327+ jj = label[bag[j]]
328+ result[idx (ii, jj)] += part[idx (i, j)]
324329 end
325330 end
326331
@@ -335,34 +340,45 @@ function MOI.get(
335340 F <: MOI.VectorAffineFunction{Float64} ,
336341 S <: MOI.PositiveSemidefiniteConeTriangle ,
337342 }
343+
338344 decomposition = model. outer_to_inner[index. value]
339345 neqns = decomposition. neqns
340- values = decomposition. values
341- cliques = decomposition. cliques
346+ value = decomposition. value
347+ label = decomposition. label
348+ tree = decomposition. tree
342349 result = zeros (Float64, neqns * (neqns + 1 ) ÷ 2 )
350+ W = zeros (Float64, neqns, neqns)
343351
344- for b in axes (cliques, 2 )
345- clique = view (cliques. rowval, nzrange (cliques, b))
346- value = values[b]
352+ for (k, bag) in zip (value, tree)
353+ m = length (bag)
347354
348355 part = MOI. get (
349356 model. inner,
350357 attribute,
351- MOI. ConstraintIndex {MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle} (value ),
358+ MOI. ConstraintIndex {MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle} (k ),
352359 )
353360
354- for (j, w) in enumerate (clique)
355- for (i, v) in enumerate (clique)
356- i > j && break
357- result[idx (v, w)] = part[idx (i, j)]
358- end
361+ for j in oneto (m), i in oneto (m)
362+ ii = bag[i]
363+ jj = bag[j]
364+ W[ii, jj] = part[idx (i, j)]
359365 end
360366 end
361367
368+ complete! (W, tree)
369+
370+ for j in oneto (neqns), i in oneto (j)
371+ ii = label[i]
372+ jj = label[j]
373+ result[idx (ii, jj)] = W[i, j]
374+ end
375+
362376 return result
363377end
364378
365- # Utilities
379+ # --------- #
380+ # Utilities #
381+ # --------- #
366382
367383# `(V, A, b) = decode(f, S)` satisfies
368384# f(Vᵢ) = Aᵢ + b
@@ -417,6 +433,35 @@ function decode(f::MOI.VectorAffineFunction{T}, S::MOI.PositiveSemidefiniteConeT
417433 return V, A, b
418434end
419435
436+ # Chordal Graphs and Semidefinite Optimization
437+ # Vandenberghe and Andersen
438+ # Algorithm 10.2: Positive semidefinite completion
439+ function complete! (W:: Matrix , tree:: CliqueTree )
440+ n = nv (FilledGraph (tree))
441+ η = sizehint! (Int[], n)
442+ marker = zeros (Int, n)
443+
444+ for bag in reverse (eachindex (tree))
445+ α = separator (tree, bag)
446+ ν = residual (tree, bag)
447+ marker[α] .= bag
448+
449+ for i in last (ν) + 1 : n
450+ marker[i] == bag || push! (η, i)
451+ end
452+
453+ Wηα = @view W[η, α]
454+ Wαα = @view W[α, α]
455+ Wαν = @view W[α, ν]
456+ Wην = Wηα * (qr (Wαα, ColumnNorm ()) \ Wαν)
457+ W[η, ν] = Wην
458+ W[ν, η] = Wην'
459+ empty! (η)
460+ end
461+
462+ return W
463+ end
464+
420465# `S = sparsitypattern(A)` is a binary matrix with the same
421466# sparsity pattern as A.
422467function sparsitypattern (A:: AbstractMatrix{T} ) where {T}
429474# idx ∘ (row, col) = id
430475# (row, col) ∘ idx = id
431476function idx (i:: Int , j:: Int )
432- @assert i <= j
477+ if i > j
478+ i, j = j, i
479+ end
480+
433481 x = i + j * (j - 1 ) ÷ 2
434482 return x
435483end
0 commit comments