Skip to content

Commit 84249b2

Browse files
committed
Initial commit.
0 parents  commit 84249b2

File tree

2 files changed

+390
-0
lines changed

2 files changed

+390
-0
lines changed

Project.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name = "MathOptChordalDecomposition"
2+
uuid = "691ff971-50ce-4a2e-a182-eb537bf792c3"
3+
authors = ["Richard Samuelson <[email protected]>"]
4+
version = "0.1.0"
5+
6+
[deps]
7+
CliqueTrees = "60701a23-6482-424a-84db-faee86b9b1f8"
8+
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
9+
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
10+
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
11+
12+
[compat]
13+
CliqueTrees = "1.5.1"
14+
LinearAlgebra = "1.11.0"
15+
MathOptInterface = "1.39.0"
16+
SparseArrays = "1.11.0"

src/MathOptChordalDecomposition.jl

Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
module MathOptChordalDecomposition
2+
3+
using Base: oneto
4+
using CliqueTrees
5+
using CliqueTrees: EliminationAlgorithm
6+
using LinearAlgebra
7+
using SparseArrays
8+
9+
import MathOptInterface as MOI
10+
11+
const DICT = Dict{
12+
MOI.ConstraintIndex{
13+
MOI.VectorAffineFunction{Float64},
14+
MOI.PositiveSemidefiniteConeTriangle},
15+
Tuple{
16+
MOI.ConstraintIndex{
17+
MOI.VectorAffineFunction{Float64},
18+
MOI.Zeros},
19+
Vector{
20+
MOI.ConstraintIndex{
21+
MOI.VectorOfVariables,
22+
MOI.PositiveSemidefiniteConeTriangle,
23+
},
24+
},
25+
},
26+
}
27+
28+
mutable struct Optimizer <: MOI.AbstractOptimizer
29+
inner::MOI.AbstractOptimizer
30+
outer_to_inner::DICT
31+
32+
function Optimizer(optimizer_factory)
33+
return new(
34+
MOI.instantiate(
35+
optimizer_factory;
36+
with_cache_type = Float64,
37+
with_bridge_type = Float64,
38+
),
39+
DICT(),
40+
)
41+
end
42+
end
43+
44+
function MOI.empty!(model::Optimizer)
45+
MOI.empty!(model.inner)
46+
return
47+
end
48+
49+
function MOI.is_empty(model::Optimizer)
50+
return MOI.is_empty(model.inner)
51+
end
52+
53+
MOI.supports_incremental_interface(::Optimizer) = true
54+
55+
function MOI.copy_to(dest::Optimizer, src::MOI.ModelLike)
56+
return MOI.Utilities.default_copy_to(dest, src)
57+
end
58+
59+
MOI.optimize!(model::Optimizer) = MOI.optimize!(model.inner)
60+
61+
const _ATTRIBUTES = Union{
62+
MOI.AbstractConstraintAttribute,
63+
MOI.AbstractModelAttribute,
64+
MOI.AbstractOptimizerAttribute,
65+
MOI.AbstractVariableAttribute,
66+
}
67+
68+
function MOI.set(model::Optimizer, attr::_ATTRIBUTES, args...)
69+
MOI.set(model.inner, attr, args...)
70+
return
71+
end
72+
73+
function MOI.get(model::Optimizer, attr::_ATTRIBUTES, args...)
74+
if MOI.is_set_by_optimize(attr)
75+
msg = "MOCD does not support querying this attribute."
76+
throw(MOI.GetAttributeNotAllowed(attr, msg))
77+
end
78+
return MOI.get(model.inner, attr, args...)
79+
end
80+
81+
function MOI.get(model::Optimizer, attr::_ATTRIBUTES, arg::Vector{T}) where {T}
82+
if MOI.is_set_by_optimize(attr)
83+
msg = "MOCD does not support querying this attribute."
84+
throw(MOI.GetAttributeNotAllowed(attr, msg))
85+
end
86+
return MOI.get.(model, attr, arg)
87+
end
88+
89+
### AbstractOptimizerAttribute
90+
91+
function MOI.supports(model::Optimizer, arg::MOI.AbstractOptimizerAttribute)
92+
return MOI.supports(model.inner, arg)
93+
end
94+
95+
function MOI.set(model::Optimizer, attr::MOI.AbstractOptimizerAttribute, value)
96+
MOI.set(model.inner, attr, value)
97+
return
98+
end
99+
100+
function MOI.get(model::Optimizer, attr::MOI.AbstractOptimizerAttribute)
101+
return MOI.get(model.inner, attr)
102+
end
103+
104+
### AbstractModelAttribute
105+
106+
function MOI.supports(model::Optimizer, arg::MOI.AbstractModelAttribute)
107+
return MOI.supports(model.inner, arg)
108+
end
109+
110+
### AbstractVariableAttribute
111+
112+
function MOI.is_valid(model::Optimizer, x::MOI.VariableIndex)
113+
return MOI.is_valid(model.inner, x)
114+
end
115+
116+
MOI.add_variable(model::Optimizer) = MOI.add_variable(model.inner)
117+
118+
function MOI.delete(model::Optimizer, x::MOI.VariableIndex)
119+
MOI.delete(model.inner, x)
120+
return
121+
end
122+
123+
function MOI.supports(
124+
model::Optimizer,
125+
arg::MOI.AbstractVariableAttribute,
126+
::Type{MOI.VariableIndex},
127+
)
128+
return MOI.supports(model.inner, arg, MOI.VariableIndex)
129+
end
130+
131+
function MOI.set(
132+
model::Optimizer,
133+
attr::MOI.AbstractVariableAttribute,
134+
indices::Vector{<:MOI.VariableIndex},
135+
args::Vector{T},
136+
) where {T}
137+
MOI.set.(model, attr, indices, args)
138+
return
139+
end
140+
141+
### AbstractConstraintAttribute
142+
143+
function MOI.is_valid(model::Optimizer, ci::MOI.ConstraintIndex)
144+
return MOI.is_valid(model.inner, ci)
145+
end
146+
147+
function MOI.supports(
148+
model::Optimizer,
149+
arg::MOI.AbstractConstraintAttribute,
150+
::Type{MOI.ConstraintIndex{F,S}},
151+
) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet}
152+
return MOI.supports(model.inner, arg, MOI.ConstraintIndex{F,S})
153+
end
154+
155+
function MOI.set(
156+
model::Optimizer,
157+
attr::MOI.AbstractConstraintAttribute,
158+
indices::Vector{<:MOI.ConstraintIndex},
159+
args::Vector{T},
160+
) where {T}
161+
MOI.set.(model, attr, indices, args)
162+
return
163+
end
164+
165+
function MOI.get(model::Optimizer, ::Type{MOI.VariableIndex}, args...)
166+
return MOI.get(model.inner, MOI.VariableIndex, args...)
167+
end
168+
169+
function MOI.get(model::Optimizer, T::Type{<:MOI.ConstraintIndex}, args...)
170+
return MOI.get(model.inner, T, args...)
171+
end
172+
173+
function MOI.delete(model::Optimizer, ci::MOI.ConstraintIndex)
174+
MOI.delete(model.inner, ci)
175+
return
176+
end
177+
178+
function MOI.supports_constraint(
179+
model::Optimizer,
180+
F::Type{<:MOI.AbstractFunction},
181+
S::Type{<:MOI.AbstractSet},
182+
)
183+
return MOI.supports_constraint(model.inner, F, S)
184+
end
185+
186+
function MOI.add_constraint(
187+
model::Optimizer,
188+
f::MOI.AbstractFunction,
189+
s::MOI.AbstractSet,
190+
)
191+
return MOI.add_constraint(model.inner, f, s)
192+
end
193+
194+
# Decomposition
195+
196+
function MOI.add_constraint(
197+
model::Optimizer,
198+
f::MOI.VectorAffineFunction{T},
199+
s::MOI.PositiveSemidefiniteConeTriangle,
200+
) where {T}
201+
# construct sparse matrices
202+
V, A, b = decode(f, s)
203+
204+
# compute aggregate sparsity pattern
205+
pattern = sum(sparsitypattern, A; init=sparsitypattern(b))
206+
207+
# compute tree decomposition
208+
label, tree = cliquetree(pattern; alg=MF())
209+
210+
# compute cliques
211+
cliques = map(tree) do clique
212+
return sort!(label[clique])
213+
end
214+
215+
# terms
216+
indices = MOI.ConstraintIndex{MOI.VectorOfVariables, MOI.PositiveSemidefiniteConeTriangle}[]
217+
terms = MOI.VectorAffineTerm{T}[]
218+
219+
for clique in cliques
220+
m = length(clique)
221+
U = MOI.add_variables(model, m * (m + 1) ÷ 2)
222+
223+
for j in oneto(m), i in oneto(j)
224+
v = U[idx(i, j)]
225+
push!(terms, MOI.VectorAffineTerm(idx(clique[i], clique[j]), MOI.ScalarAffineTerm(-1.0, v)))
226+
end
227+
228+
push!(indices, MOI.add_constraint(model.inner, MOI.VectorOfVariables(U), MOI.PositiveSemidefiniteConeTriangle(m)))
229+
end
230+
231+
for (v, a) in zip(V, A), j in axes(a, 2)
232+
for p in nzrange(a, j)
233+
i = rowvals(a)[p]
234+
i > j && break
235+
x = nonzeros(a)[p]
236+
push!(terms, MOI.VectorAffineTerm(idx(i, j), MOI.ScalarAffineTerm(x, v)))
237+
end
238+
end
239+
240+
# constants
241+
n = size(b, 2)
242+
constants = zeros(T, n * (n + 1) ÷ 2)
243+
244+
for j in axes(b, 2)
245+
for p in nzrange(b, j)
246+
i = rowvals(b)[p]
247+
i > j && break
248+
x = nonzeros(b)[p]
249+
constants[idx(i, j)] = x
250+
end
251+
end
252+
253+
index = MOI.add_constraint(model.inner, MOI.VectorAffineFunction(terms, constants), MOI.Zeros(n * (n + 1) ÷ 2))
254+
outer = MOI.ConstraintIndex{MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle}(index.value)
255+
model.outer_to_inner[outer] = (index, indices)
256+
return outer
257+
end
258+
259+
function MOI.get(
260+
model::Optimizer,
261+
::MOI.NumberOfConstraints{F,S},
262+
) where {
263+
F<:MOI.VectorAffineFunction{Float64},
264+
S<:MOI.PositiveSemidefiniteConeTriangle,
265+
}
266+
return length(model.outer_to_inner)
267+
end
268+
269+
function MOI.get(
270+
model::Optimizer,
271+
::MOI.ListOfConstraintIndices{F,S},
272+
) where {
273+
F<:MOI.VectorAffineFunction{Float64},
274+
S<:MOI.PositiveSemidefiniteConeTriangle,
275+
}
276+
return sort!(collect(keys(model.outer_to_inner)); by = c -> c.value)
277+
end
278+
279+
function MOI.get(
280+
model::Optimizer,
281+
::MOI.ConstraintPrimal,
282+
::MOI.ConstraintIndex{F,S},
283+
) where {
284+
F<:MOI.VectorAffineFunction{Float64},
285+
S<:MOI.PositiveSemidefiniteConeTriangle,
286+
}
287+
return # ???
288+
end
289+
290+
function MOI.get(
291+
model::Optimizer,
292+
::MOI.ConstraintDual,
293+
::MOI.ConstraintIndex{F,S},
294+
) where {
295+
F<:MOI.VectorAffineFunction{Float64},
296+
S<:MOI.PositiveSemidefiniteConeTriangle,
297+
}
298+
return # ???
299+
end
300+
301+
function idx(i::Int, j::Int)
302+
@assert i <= j
303+
x = i + j * (j - 1) ÷ 2
304+
return x
305+
end
306+
307+
function col(x::Int)
308+
j = ceil(Int, (sqrt(1 + 8x) - 1.0) / 2)
309+
return j
310+
end
311+
312+
function row(x::Int)
313+
j = col(x)
314+
i = x - j * (j - 1) ÷ 2
315+
return i
316+
end
317+
318+
function decode(f::MOI.VectorAffineFunction{T}, S::MOI.PositiveSemidefiniteConeTriangle) where {T}
319+
n = S.side_dimension
320+
index = Dict{MOI.VariableIndex, Int}()
321+
322+
# V, A
323+
V = MOI.VariableIndex[]
324+
AI = Vector{Int}[]
325+
AJ = Vector{Int}[]
326+
AX = Vector{T}[]
327+
328+
for term in f.terms
329+
i = term.output_index
330+
v = term.scalar_term.variable
331+
x = term.scalar_term.coefficient
332+
333+
if !haskey(index, v)
334+
push!(V, v)
335+
push!(AI, Int[])
336+
push!(AJ, Int[])
337+
push!(AX, T[])
338+
index[v] = length(V)
339+
end
340+
341+
j = index[v]
342+
push!(AI[j], row(i))
343+
push!(AJ[j], col(i))
344+
push!(AX[j], x)
345+
end
346+
347+
A = map(zip(AI, AJ, AX)) do (I, J, X)
348+
return sparse(I, J, X, n, n)
349+
end
350+
351+
# b
352+
I = Int[]
353+
J = Int[]
354+
X = T[]
355+
356+
for (i, x) in enumerate(f.constants)
357+
if !iszero(x)
358+
push!(I, row(i))
359+
push!(J, col(i))
360+
push!(X, x)
361+
end
362+
end
363+
364+
b = sparse(I, J, X, n, n)
365+
return V, A, b
366+
end
367+
368+
function sparsitypattern(A::AbstractMatrix{T}) where {T}
369+
S = sparse(Symmetric(A, :U))
370+
nonzeros(S) .= one(T)
371+
return S
372+
end
373+
374+
end

0 commit comments

Comments
 (0)