Skip to content

Commit c1b2cf4

Browse files
authored
Start symmetric decompression (#10)
* Start symmetric decompression * Tests on more matrix formats * Add missing tests
1 parent 2c5d35c commit c1b2cf4

12 files changed

+380
-174
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "SparseMatrixColorings"
22
uuid = "0a514795-09f3-496d-8182-132a7b665d35"
33
authors = ["Guillaume Dalle <22795598+gdalle@users.noreply.github.com>"]
4-
version = "0.3.1"
4+
version = "0.3.2"
55

66
[deps]
77
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"

docs/src/api.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,25 @@ LargestFirst
2626
### Decompression
2727

2828
```@docs
29-
decompress_columns!
29+
color_groups
3030
decompress_columns
31-
decompress_rows!
31+
decompress_columns!
3232
decompress_rows
33-
color_groups
33+
decompress_rows!
34+
decompress_symmetric
35+
decompress_symmetric!
3436
```
3537

3638
## Private
3739

40+
### Matrices
41+
42+
```@docs
43+
matrix_versions
44+
respectful_similar
45+
same_sparsity_pattern
46+
```
47+
3848
### Graphs
3949

4050
```@docs

src/SparseMatrixColorings.jl

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,16 @@ module SparseMatrixColorings
88
using ADTypes: ADTypes, AbstractColoringAlgorithm
99
using Compat: @compat
1010
using DocStringExtensions: README
11-
using LinearAlgebra: Diagonal, Transpose, checksquare, parent, transpose
11+
using LinearAlgebra:
12+
Adjoint,
13+
Diagonal,
14+
Symmetric,
15+
Transpose,
16+
adjoint,
17+
checksquare,
18+
issymmetric,
19+
parent,
20+
transpose
1221
using Random: AbstractRNG, default_rng, randperm
1322
using SparseArrays:
1423
SparseArrays,
@@ -25,15 +34,18 @@ using SparseArrays:
2534
include("graph.jl")
2635
include("order.jl")
2736
include("coloring.jl")
37+
include("groups.jl")
2838
include("adtypes.jl")
29-
include("check.jl")
39+
include("matrices.jl")
3040
include("decompression.jl")
41+
include("check.jl")
3142

3243
@compat public GreedyColoringAlgorithm
3344
@compat public NaturalOrder, RandomOrder, LargestFirst
34-
@compat public decompress_columns!, decompress_columns
35-
@compat public decompress_rows!, decompress_rows
3645
@compat public color_groups
46+
@compat public decompress_columns, decompress_columns!
47+
@compat public decompress_rows, decompress_rows!
48+
@compat public decompress_symmetric, decompress_symmetric!
3749

3850
export GreedyColoringAlgorithm
3951

src/check.jl

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ A partition of the columns of a matrix `A` is _structurally orthogonal_ if, for
1212
This function is not coded with efficiency in mind, it is designed for small-scale tests.
1313
"""
1414
function check_structurally_orthogonal_columns(
15-
A::AbstractMatrix, colors::AbstractVector{<:Integer}; verbose::Bool=false
15+
A::AbstractMatrix, colors::AbstractVector{<:Integer}; verbose::Bool=true
1616
)
17-
for c in unique(colors)
18-
js = filter(j -> colors[j] == c, axes(A, 2))
19-
Ajs = @view A[:, js]
20-
nonzeros_per_row = count(!iszero, Ajs; dims=2)
21-
if maximum(nonzeros_per_row) > 1
22-
verbose && @warn "Color $c has columns $js sharing nonzeros"
17+
groups = color_groups(colors)
18+
for (c, g) in enumerate(groups)
19+
Ag = @view A[:, g]
20+
nonzeros_per_row = dropdims(count(!iszero, Ag; dims=2); dims=2)
21+
max_nonzeros_per_row, i = findmax(nonzeros_per_row)
22+
if max_nonzeros_per_row > 1
23+
verbose && @warn "Columns $g (with color $c) share nonzeros in row $i"
2324
return false
2425
end
2526
end
@@ -40,14 +41,15 @@ A partition of the rows of a matrix `A` is _structurally orthogonal_ if, for eve
4041
This function is not coded with efficiency in mind, it is designed for small-scale tests.
4142
"""
4243
function check_structurally_orthogonal_rows(
43-
A::AbstractMatrix, colors::AbstractVector{<:Integer}; verbose::Bool=false
44+
A::AbstractMatrix, colors::AbstractVector{<:Integer}; verbose::Bool=true
4445
)
45-
for c in unique(colors)
46-
is = filter(i -> colors[i] == c, axes(A, 1))
47-
Ais = @view A[is, :]
48-
nonzeros_per_column = count(!iszero, Ais; dims=1)
49-
if maximum(nonzeros_per_column) > 1
50-
verbose && @warn "Color $c has rows $is sharing nonzeros"
46+
groups = color_groups(colors)
47+
for (c, g) in enumerate(groups)
48+
Ag = @view A[g, :]
49+
nonzeros_per_col = dropdims(count(!iszero, Ag; dims=1); dims=1)
50+
max_nonzeros_per_col, j = findmax(nonzeros_per_col)
51+
if max_nonzeros_per_col > 1
52+
verbose && @warn "Rows $g (with color $c) share nonzeros in column $j"
5153
return false
5254
end
5355
end
@@ -71,24 +73,26 @@ A partition of the columns of a symmetrix matrix `A` is _symmetrically orthogona
7173
This function is not coded with efficiency in mind, it is designed for small-scale tests.
7274
"""
7375
function check_symmetrically_orthogonal(
74-
A::AbstractMatrix, colors::AbstractVector{<:Integer}; verbose::Bool=false
76+
A::AbstractMatrix, colors::AbstractVector{<:Integer}; verbose::Bool=true
7577
)
78+
checksquare(A)
79+
issymmetric(A) || return false
80+
groups = color_groups(colors)
7681
for i in axes(A, 2), j in axes(A, 2)
77-
if !iszero(A[i, j])
78-
group_i = filter(i2 -> (i2 != i) && (colors[i2] == colors[i]), axes(A, 2))
79-
group_j = filter(j2 -> (j2 != j) && (colors[j2] == colors[j]), axes(A, 2))
80-
A_group_i_column_j = @view A[group_i, j]
81-
A_group_j_column_i = @view A[group_j, i]
82-
nonzeros_group_i_column_j = count(!iszero, A_group_i_column_j)
83-
nonzeros_group_j_column_i = count(!iszero, A_group_j_column_i)
84-
if nonzeros_group_i_column_j > 0 && nonzeros_group_j_column_i > 0
85-
verbose && @warn """
86-
For coefficient $((i, j)), both of the following have confounding zeros:
87-
- color $(colors[j]) with group $group_j
88-
- color $(colors[i]) with group $group_i
89-
"""
90-
return false
91-
end
82+
iszero(A[i, j]) && continue
83+
ki, kj = colors[i], colors[j]
84+
gi, gj = groups[ki], groups[kj]
85+
A_gj_rowi = view(A, i, gj)
86+
A_gi_rowj = view(A, j, gi)
87+
nonzeros_gj_rowi = count(!iszero, A_gj_rowi)
88+
nonzeros_gi_rowj = count(!iszero, A_gi_rowj)
89+
if nonzeros_gj_rowi > 1 && nonzeros_gi_rowj > 1
90+
verbose && @warn """
91+
For coefficient $((i, j)):
92+
- columns $gj (with color $kj) share nonzeros in row $i
93+
- columns $gi (with color $ki) share nonzeros in row $j
94+
"""
95+
return false
9296
end
9397
end
9498
return true

src/decompression.jl

Lines changed: 79 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,3 @@
1-
transpose_respecting_similar(A::AbstractMatrix, ::Type{T}) where {T} = similar(A, T)
2-
3-
function transpose_respecting_similar(A::Transpose, ::Type{T}) where {T}
4-
return transpose(similar(parent(A), T))
5-
end
6-
7-
function same_sparsity_pattern(A::SparseMatrixCSC, B::SparseMatrixCSC)
8-
if size(A) != size(B)
9-
return false
10-
elseif nnz(A) != nnz(B)
11-
return false
12-
else
13-
for j in axes(A, 2)
14-
rA = nzrange(A, j)
15-
rB = nzrange(B, j)
16-
if rA != rB
17-
return false
18-
end
19-
# TODO: check rowvals?
20-
end
21-
return true
22-
end
23-
end
24-
25-
function same_sparsity_pattern(
26-
A::Transpose{<:Any,<:SparseMatrixCSC}, B::Transpose{<:Any,<:SparseMatrixCSC}
27-
)
28-
return same_sparsity_pattern(parent(A), parent(B))
29-
end
30-
31-
"""
32-
color_groups(colors)
33-
34-
Return `groups::Vector{Vector{Int}}` such that `i ∈ groups[c]` iff `colors[i] == c`.
35-
36-
Assumes the colors are contiguously numbered from `1` to some `cmax`.
37-
"""
38-
function color_groups(colors::AbstractVector{<:Integer})
39-
cmin, cmax = extrema(colors)
40-
@assert cmin == 1
41-
groups = [Int[] for c in 1:cmax]
42-
for (k, c) in enumerate(colors)
43-
push!(groups[c], k)
44-
end
45-
return groups
46-
end
47-
481
## Column decompression
492

503
"""
@@ -67,6 +20,9 @@ function decompress_columns!(
6720
C::AbstractMatrix{R},
6821
colors::AbstractVector{<:Integer},
6922
) where {R<:Real}
23+
if !same_sparsity_pattern(A, S)
24+
throw(DimensionMismatch("`A` and `S` must have the same sparsity pattern."))
25+
end
7026
A .= zero(R)
7127
for j in axes(A, 2)
7228
k = colors[j]
@@ -114,7 +70,7 @@ Here, `colors` is a column coloring of `S`, while `C` is a compressed representa
11470
function decompress_columns(
11571
S::AbstractMatrix{Bool}, C::AbstractMatrix{R}, colors::AbstractVector{<:Integer}
11672
) where {R<:Real}
117-
A = transpose_respecting_similar(S, R)
73+
A = respectful_similar(S, R)
11874
return decompress_columns!(A, S, C, colors)
11975
end
12076

@@ -140,6 +96,9 @@ function decompress_rows!(
14096
C::AbstractMatrix{R},
14197
colors::AbstractVector{<:Integer},
14298
) where {R<:Real}
99+
if !same_sparsity_pattern(A, S)
100+
throw(DimensionMismatch("`A` and `S` must have the same sparsity pattern."))
101+
end
143102
A .= zero(R)
144103
for i in axes(A, 1)
145104
k = colors[i]
@@ -152,8 +111,8 @@ function decompress_rows!(
152111
end
153112

154113
function decompress_rows!(
155-
A::Transpose{R,<:SparseMatrixCSC{R}},
156-
S::Transpose{Bool,<:SparseMatrixCSC{Bool}},
114+
A::TransposeOrAdjoint{R,<:SparseMatrixCSC{R}},
115+
S::TransposeOrAdjoint{Bool,<:SparseMatrixCSC{Bool}},
157116
C::AbstractMatrix{R},
158117
colors::AbstractVector{<:Integer},
159118
) where {R<:Real}
@@ -188,6 +147,75 @@ Here, `colors` is a row coloring of `S`, while `C` is a compressed representatio
188147
function decompress_rows(
189148
S::AbstractMatrix{Bool}, C::AbstractMatrix{R}, colors::AbstractVector{<:Integer}
190149
) where {R<:Real}
191-
A = transpose_respecting_similar(S, R)
150+
A = respectful_similar(S, R)
192151
return decompress_rows!(A, S, C, colors)
193152
end
153+
154+
## Symmetric decompression
155+
156+
"""
157+
decompress_symmetric!(
158+
A::AbstractMatrix{R},
159+
S::AbstractMatrix{Bool},
160+
C::AbstractMatrix{R},
161+
colors::AbstractVector{<:Integer}
162+
) where {R<:Real}
163+
164+
Decompress the thin matrix `C` into the symmetric matrix `A` which must have the same sparsity pattern as `S`.
165+
166+
Here, `colors` is a symmetric coloring of `S`, while `C` is a compressed representation of matrix `A` obtained by summing the columns that share the same color.
167+
"""
168+
function decompress_symmetric! end
169+
170+
function decompress_symmetric!(
171+
A::AbstractMatrix{R},
172+
S::AbstractMatrix{Bool},
173+
C::AbstractMatrix{R},
174+
colors::AbstractVector{<:Integer},
175+
) where {R<:Real}
176+
A .= zero(R)
177+
groups = color_groups(colors)
178+
checksquare(A)
179+
for i in axes(A, 1), j in axes(A, 2)
180+
iszero(S[i, j]) && continue
181+
ki, kj = colors[i], colors[j]
182+
gi, gj = groups[ki], groups[kj]
183+
if sum(!iszero, view(S, i, gj)) == 1
184+
A[i, j] = C[i, kj]
185+
elseif sum(!iszero, view(S, j, gi)) == 1
186+
A[i, j] = C[j, ki]
187+
else
188+
error("Symmetric coloring is not valid")
189+
end
190+
end
191+
return A
192+
end
193+
194+
function decompress_symmetric!(
195+
A::Symmetric{R},
196+
S::AbstractMatrix{Bool},
197+
C::AbstractMatrix{R},
198+
colors::AbstractVector{<:Integer},
199+
) where {R<:Real}
200+
# requires parent decompression to handle both upper and lower triangles
201+
decompress_symmetric!(parent(A), S, C, colors)
202+
return A
203+
end
204+
205+
"""
206+
decompress_symmetric(
207+
S::AbstractMatrix{Bool},
208+
C::AbstractMatrix{R},
209+
colors::AbstractVector{<:Integer}
210+
) where {R<:Real}
211+
212+
Decompress the thin matrix `C` into a new symmetric matrix `A` with the same sparsity pattern as `S`.
213+
214+
Here, `colors` is a symmetric coloring of `S`, while `C` is a compressed representation of matrix `A` obtained by summing the columns that share the same color.
215+
"""
216+
function decompress_symmetric(
217+
S::AbstractMatrix{Bool}, C::AbstractMatrix{R}, colors::AbstractVector{<:Integer}
218+
) where {R<:Real}
219+
A = respectful_similar(S, R)
220+
return decompress_symmetric!(A, S, C, colors)
221+
end

src/groups.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""
2+
color_groups(colors)
3+
4+
Return `groups::Vector{Vector{Int}}` such that `i ∈ groups[c]` iff `colors[i] == c`.
5+
6+
Assumes the colors are contiguously numbered from `1` to some `cmax`.
7+
"""
8+
function color_groups(colors::AbstractVector{<:Integer})
9+
cmin, cmax = extrema(colors)
10+
@assert cmin == 1
11+
groups = [Int[] for c in 1:cmax]
12+
for (k, c) in enumerate(colors)
13+
push!(groups[c], k)
14+
end
15+
return groups
16+
end

0 commit comments

Comments
 (0)