Skip to content

Commit b6c38d9

Browse files
authored
Make decompression more efficient (#71)
* Make decompression more efficient * Add skipped test for decompression into dense matrix
1 parent cb1c2c6 commit b6c38d9

12 files changed

Lines changed: 430 additions & 397 deletions

File tree

docs/src/dev.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ SparseMatrixColorings.TreeSet
3434
## Concrete coloring results
3535

3636
```@docs
37-
SparseMatrixColorings.DefaultColoringResult
37+
SparseMatrixColorings.NonSymmetricColoringResult
3838
SparseMatrixColorings.StarSetColoringResult
3939
SparseMatrixColorings.TreeSetColoringResult
40-
SparseMatrixColorings.DirectSparseColoringResult
40+
SparseMatrixColorings.LinearSystemColoringResult
4141
```
4242

4343
## Testing

src/SparseMatrixColorings.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ using LinearAlgebra:
2020
Transpose,
2121
adjoint,
2222
checksquare,
23+
factorize,
2324
issymmetric,
25+
ldiv!,
2426
parent,
2527
transpose
2628
using Random: AbstractRNG, default_rng, randperm
@@ -45,7 +47,6 @@ include("matrices.jl")
4547
include("interface.jl")
4648
include("decompression.jl")
4749
include("check.jl")
48-
include("sparsematrixcsc.jl")
4950
include("examples.jl")
5051

5152
export ColoringProblem, GreedyColoringAlgorithm, AbstractColoringResult

src/decompression.jl

Lines changed: 72 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Compress `A` given a coloring `result` of the sparsity pattern of `A`.
77
- If `result` comes from a `:bidirectional` partition, the output is a tuple of matrices `(Br, Bc)`, where `Br` is compressed by row and `Bc` by column.
88
99
Compression means summing either the columns or the rows of `A` which share the same color.
10-
It is undone by calling [`decompress`](@ref).
10+
It is undone by calling [`decompress`](@ref) or [`decompress!`](@ref).
1111
1212
!!! warning
1313
At the moment, `:bidirectional` partitions are not implemented.
@@ -67,6 +67,7 @@ end
6767
decompress(B::AbstractMatrix, result::AbstractColoringResult)
6868
6969
Decompress `B` into a new matrix `A`, given a coloring `result` of the sparsity pattern of `A`.
70+
The in-place alternative is [`decompress!`](@ref).
7071
7172
Compression means summing either the columns or the rows of `A` which share the same color.
7273
It is done by calling [`compress`](@ref).
@@ -127,10 +128,14 @@ end
127128
)
128129
129130
Decompress `B` in-place into `A`, given a coloring `result` of the sparsity pattern of `A`.
131+
The out-of-place alternative is [`decompress`](@ref).
130132
131133
Compression means summing either the columns or the rows of `A` which share the same color.
132134
It is done by calling [`compress`](@ref).
133135
136+
!!! note
137+
In-place decompression is faster when `A isa SparseMatrixCSC`.
138+
134139
# Example
135140
136141
```jldoctest
@@ -190,10 +195,10 @@ function decompress!(
190195
return decompress_aux!(A, B, result)
191196
end
192197

198+
## NonSymmetricColoringResult
199+
193200
function decompress_aux!(
194-
A::AbstractMatrix{R},
195-
B::AbstractMatrix{R},
196-
result::AbstractColoringResult{:nonsymmetric,:column,:direct},
201+
A::AbstractMatrix{R}, B::AbstractMatrix{R}, result::NonSymmetricColoringResult{:column}
197202
) where {R<:Real}
198203
A .= zero(R)
199204
S = get_matrix(result)
@@ -209,9 +214,7 @@ function decompress_aux!(
209214
end
210215

211216
function decompress_aux!(
212-
A::AbstractMatrix{R},
213-
B::AbstractMatrix{R},
214-
result::AbstractColoringResult{:nonsymmetric,:row,:direct},
217+
A::AbstractMatrix{R}, B::AbstractMatrix{R}, result::NonSymmetricColoringResult{:row}
215218
) where {R<:Real}
216219
A .= zero(R)
217220
S = get_matrix(result)
@@ -227,24 +230,31 @@ function decompress_aux!(
227230
end
228231

229232
function decompress_aux!(
230-
A::AbstractMatrix{R},
231-
B::AbstractMatrix{R},
232-
result::AbstractColoringResult{:symmetric,:column,:direct},
233+
A::SparseMatrixCSC{R}, B::AbstractMatrix{R}, result::NonSymmetricColoringResult{:column}
233234
) where {R<:Real}
234-
A .= zero(R)
235-
S = get_matrix(result)
236-
color = column_colors(result)
237-
group = column_groups(result)
238-
for ij in findall(!iszero, S)
239-
i, j = Tuple(ij)
240-
k, l = symmetric_coefficient(i, j, color, group, S)
241-
A[i, j] = B[k, l]
235+
nzA = nonzeros(A)
236+
ind = result.compressed_indices
237+
for i in eachindex(nzA, ind)
238+
nzA[i] = B[ind[i]]
239+
end
240+
return A
241+
end
242+
243+
function decompress_aux!(
244+
A::SparseMatrixCSC{R}, B::AbstractMatrix{R}, result::NonSymmetricColoringResult{:row}
245+
) where {R<:Real}
246+
nzA = nonzeros(A)
247+
ind = result.compressed_indices
248+
for i in eachindex(nzA, ind)
249+
nzA[i] = B[ind[i]]
242250
end
243251
return A
244252
end
245253

254+
## StarSetColoringResult
255+
246256
function decompress_aux!(
247-
A::AbstractMatrix{R}, B::AbstractMatrix{R}, result::StarSetColoringResult{:column}
257+
A::AbstractMatrix{R}, B::AbstractMatrix{R}, result::StarSetColoringResult
248258
) where {R<:Real}
249259
A .= zero(R)
250260
S = get_matrix(result)
@@ -258,117 +268,35 @@ function decompress_aux!(
258268
end
259269

260270
function decompress_aux!(
261-
A::AbstractMatrix{R},
262-
B::AbstractMatrix{R},
263-
result::AbstractColoringResult{:symmetric,:column,:substitution},
271+
A::SparseMatrixCSC{R}, B::AbstractMatrix{R}, result::StarSetColoringResult
264272
) where {R<:Real}
265-
# build T such that T * strict_upper_nonzeros(A) = B
266-
# and solve a linear least-squares problem
267-
# only consider the strict upper triangle of A because of symmetry
268-
# TODO: make more efficient
269-
A .= zero(R)
270-
S = sparse(get_matrix(result))
271-
color = column_colors(result)
272-
273-
n = checksquare(S)
274-
strict_upper_nonzero_inds = Tuple{Int,Int}[]
275-
I, J, _ = findnz(S)
276-
for (i, j) in zip(I, J)
277-
(i < j) && push!(strict_upper_nonzero_inds, (i, j))
278-
(i == j) && (A[i, i] = B[i, color[i]])
279-
end
280-
281-
T = spzeros(float(R), length(B), length(strict_upper_nonzero_inds))
282-
for (l, (i, j)) in enumerate(strict_upper_nonzero_inds)
283-
ci = color[i]
284-
cj = color[j]
285-
ki = (ci - 1) * n + j # A[i, j] appears in B[j, ci]
286-
kj = (cj - 1) * n + i # A[i, j] appears in B[i, cj]
287-
T[ki, l] = one(float(R))
288-
T[kj, l] = one(float(R))
289-
end
290-
291-
strict_upper_nonzeros_A = T \ vec(B)
292-
for (l, (i, j)) in enumerate(strict_upper_nonzero_inds)
293-
A[i, j] = strict_upper_nonzeros_A[l]
294-
A[j, i] = strict_upper_nonzeros_A[l]
273+
nzA = nonzeros(A)
274+
ind = result.compressed_indices
275+
for i in eachindex(nzA, ind)
276+
nzA[i] = B[ind[i]]
295277
end
296278
return A
297279
end
298280

281+
## TreeSetColoringResult
282+
283+
# TODO: add method for A::SparseMatrixCSC
284+
299285
function decompress_aux!(
300286
A::AbstractMatrix{R}, B::AbstractMatrix{R}, result::TreeSetColoringResult
301287
) where {R<:Real}
302-
n = checksquare(A)
303288
A .= zero(R)
304289
S = get_matrix(result)
305290
color = column_colors(result)
306-
307-
# forest is a structure DisjointSets from DataStructures.jl
308-
# - forest.intmap: a dictionary that maps an edge (i, j) to an integer k
309-
# - forest.revmap: a dictionary that does the reverse of intmap, mapping an integer k to an edge (i, j)
310-
# - forest.internal.ngroups: the number of trees in the forest
311-
forest = result.tree_set.forest
312-
ntrees = forest.internal.ngroups
313-
314-
# vector of trees where each tree contains the indices of its edges
315-
trees = [Int[] for i in 1:ntrees]
316-
317-
# dictionary that maps a tree's root to the index of the tree
318-
roots = Dict{Int,Int}()
319-
320-
k = 0
321-
for edge in forest.revmap
322-
root_edge = find_root!(forest, edge)
323-
root = forest.intmap[root_edge]
324-
if !haskey(roots, root)
325-
k += 1
326-
roots[root] = k
327-
end
328-
index_tree = roots[root]
329-
push!(trees[index_tree], forest.intmap[edge])
330-
end
331-
332-
# vector of dictionaries where each dictionary stores the degree of each vertex in a tree
333-
degrees = [Dict{Int,Int}() for k in 1:ntrees]
334-
for k in 1:ntrees
335-
tree = trees[k]
336-
degree = degrees[k]
337-
for edge_index in tree
338-
i, j = forest.revmap[edge_index]
339-
!haskey(degree, i) && (degree[i] = 0)
340-
!haskey(degree, j) && (degree[j] = 0)
341-
degree[i] += 1
342-
degree[j] += 1
343-
end
344-
end
345-
346-
# depth-first search (DFS) traversal order for each tree in the forest
347-
dfs_orders = [Vector{Tuple{Int,Int}}() for k in 1:ntrees]
348-
for k in 1:ntrees
349-
tree = trees[k]
350-
degree = degrees[k]
351-
while sum(values(degree)) != 0
352-
for (t, edge_index) in enumerate(tree)
353-
if edge_index != 0
354-
i, j = forest.revmap[edge_index]
355-
if (degree[i] == 1) || (degree[j] == 1) # leaf vertex
356-
if degree[i] > degree[j] # vertex i is the parent of vertex j
357-
i, j = j, i # ensure that i always denotes a leaf vertex
358-
end
359-
degree[i] -= 1 # decrease the degree of vertex i
360-
degree[j] -= 1 # decrease the degree of vertex j
361-
tree[t] = 0 # remove the edge (i,j)
362-
push!(dfs_orders[k], (i, j))
363-
end
364-
end
365-
end
366-
end
367-
end
291+
@compat (; degrees, dfs_orders, stored_values) = result
368292

369293
# stored_values holds the sum of edge values for subtrees in a tree.
370294
# For each vertex i, stored_values[i] is the sum of edge values in the subtree rooted at i.
371-
stored_values = Vector{R}(undef, n)
295+
stored_values_right_type = if R == Float64
296+
stored_values
297+
else
298+
similar(stored_values, R)
299+
end
372300

373301
# Recover the diagonal coefficients of A
374302
for i in axes(A, 1)
@@ -378,19 +306,43 @@ function decompress_aux!(
378306
end
379307

380308
# Recover the off-diagonal coefficients of A
381-
for k in 1:ntrees
309+
for k in eachindex(degrees, dfs_orders)
382310
vertices = keys(degrees[k])
383311
for vertex in vertices
384-
stored_values[vertex] = zero(R)
312+
stored_values_right_type[vertex] = zero(R)
385313
end
386314

387315
tree = dfs_orders[k]
388316
for (i, j) in tree
389-
val = B[i, color[j]] - stored_values[i]
390-
stored_values[j] = stored_values[j] + val
317+
val = B[i, color[j]] - stored_values_right_type[i]
318+
stored_values_right_type[j] = stored_values_right_type[j] + val
391319
A[i, j] = val
392320
A[j, i] = val
393321
end
394322
end
395323
return A
396324
end
325+
326+
## MatrixInverseColoringResult
327+
328+
function decompress_aux!(
329+
A::AbstractMatrix{R}, B::AbstractMatrix{R}, result::LinearSystemColoringResult
330+
) where {R<:Real}
331+
S = get_matrix(result)
332+
color = column_colors(result)
333+
@compat (; strict_upper_nonzero_inds, T_factorization, strict_upper_nonzeros_A) = result
334+
335+
# TODO: for some reason I cannot use ldiv! with a sparse QR
336+
strict_upper_nonzeros_A = T_factorization \ vec(B)
337+
A .= zero(R)
338+
for i in axes(A, 1)
339+
if !iszero(S[i, i])
340+
A[i, i] = B[i, color[i]]
341+
end
342+
end
343+
for (l, (i, j)) in enumerate(strict_upper_nonzero_inds)
344+
A[i, j] = strict_upper_nonzeros_A[l]
345+
A[j, i] = strict_upper_nonzeros_A[l]
346+
end
347+
return A
348+
end

src/graph.jl

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ struct Graph{T<:Integer}
1616
end
1717

1818
Graph(S::SparseMatrixCSC) = Graph(S.colptr, S.rowval)
19-
Graph(S::AbstractMatrix) = Graph(sparse(S))
2019

2120
Base.length(g::Graph) = length(g.colptr) - 1
2221
SparseArrays.nnz(g::Graph) = length(g.rowval)
@@ -91,7 +90,6 @@ The adjacency graph of a symmetrix matric `A ∈ ℝ^{n × n}` is `G(A) = (V, E)
9190
> [_What Color Is Your Jacobian? Graph Coloring for Computing Derivatives_](https://epubs.siam.org/doi/10.1137/S0036144504444711), Gebremedhin et al. (2005)
9291
"""
9392
adjacency_graph(H::SparseMatrixCSC) = Graph(H - Diagonal(H))
94-
adjacency_graph(H::AbstractMatrix) = adjacency_graph(sparse(H))
9593

9694
"""
9795
bipartite_graph(J::AbstractMatrix)
@@ -109,9 +107,7 @@ The bipartite graph of a matrix `A ∈ ℝ^{m × n}` is `Gb(A) = (V₁, V₂, E)
109107
> [_What Color Is Your Jacobian? Graph Coloring for Computing Derivatives_](https://epubs.siam.org/doi/10.1137/S0036144504444711), Gebremedhin et al. (2005)
110108
"""
111109
function bipartite_graph(J::SparseMatrixCSC)
112-
g1 = Graph(transpose(J)) # rows to columns
110+
g1 = Graph(sparse(transpose(J))) # rows to columns
113111
g2 = Graph(J) # columns to rows
114112
return BipartiteGraph(g1, g2)
115113
end
116-
117-
bipartite_graph(J::AbstractMatrix) = bipartite_graph(sparse(J))

0 commit comments

Comments
 (0)