Skip to content

Commit c29ab5b

Browse files
committed
Complete dual variable.
1 parent 015e36e commit c29ab5b

File tree

2 files changed

+100
-50
lines changed

2 files changed

+100
-50
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ version = "0.1.0"
55

66
[deps]
77
CliqueTrees = "60701a23-6482-424a-84db-faee86b9b1f8"
8+
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
89
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
910
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
1011
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
1112

1213
[compat]
1314
CliqueTrees = "1.5.1"
15+
Graphs = "1.12.1"
1416
LinearAlgebra = "1.11.0"
1517
MathOptInterface = "1.39.0"
1618
SparseArrays = "1.11.0"

src/MathOptChordalDecomposition.jl

Lines changed: 98 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,21 @@ using Base: oneto
44
using CliqueTrees
55
using CliqueTrees: EliminationAlgorithm
66
using LinearAlgebra
7+
using Graphs
78
using SparseArrays
89

910
import MathOptInterface as MOI
1011

1112
struct 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}
1517
end
1618

19+
"""
20+
Optimizer(inner; alg::EliminationAlgorithm=MF())
21+
"""
1722
mutable 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)
9297
end
9398

94-
### AbstractOptimizerAttribute
99+
# -------------------------- #
100+
# AbstractOptimizerAttribute #
101+
# -------------------------- #
95102

96103
function 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)
107114
end
108115

109-
### AbstractModelAttribute
116+
# ---------------------- #
117+
# AbstractModelAttribute #
118+
# ---------------------- #
110119

111120
function MOI.supports(model::Optimizer, arg::MOI.AbstractModelAttribute)
112121
return MOI.supports(model.inner, arg)
113122
end
114123

115-
### AbstractVariableAttribute
124+
# ------------------------- #
125+
# AbstractVariableAttribute #
126+
# ------------------------- #
116127

117128
function 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
146157
end
147158

148-
### AbstractConstraintAttribute
159+
# --------------------------- #
160+
# AbstractConstraintAttribute #
161+
# --------------------------- #
149162

150163
function 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)
199212
end
200213

201-
# Decomposition
214+
#################
215+
# Decomposition #
216+
#################
202217

203218
function 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)
269275
end
270276

271277
function 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
363377
end
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
418434
end
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.
422467
function sparsitypattern(A::AbstractMatrix{T}) where {T}
@@ -429,7 +474,10 @@ end
429474
# idx ∘ (row, col) = id
430475
# (row, col) ∘ idx = id
431476
function 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
435483
end

0 commit comments

Comments
 (0)