Skip to content

Commit b429209

Browse files
amontoisongdalle
andauthored
Add a structurally_biorthogonal check for star bicoloring (#155)
* Add a function structurally_biorthogonal for star bicoloring * Improve code coverage * Increase code coverage * Use new check in random tests --------- Co-authored-by: Guillaume Dalle <22795598+gdalle@users.noreply.github.com>
1 parent a331efe commit b429209

5 files changed

Lines changed: 135 additions & 13 deletions

File tree

docs/src/dev.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ SparseMatrixColorings.remap_colors
4848
SparseMatrixColorings.directly_recoverable_columns
4949
SparseMatrixColorings.symmetrically_orthogonal_columns
5050
SparseMatrixColorings.structurally_orthogonal_columns
51+
SparseMatrixColorings.structurally_biorthogonal
5152
SparseMatrixColorings.valid_dynamic_order
5253
```
5354

src/check.jl

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,42 @@
11
function proper_length_coloring(
2-
A::AbstractMatrix, color::AbstractVector{<:Integer}; verbose::Bool
2+
A::AbstractMatrix, color::AbstractVector{<:Integer}; verbose::Bool=false
33
)
4-
if length(color) != size(A, 2)
4+
m, n = size(A)
5+
if length(color) != n
56
if verbose
6-
@warn "$(length(color)) colors provided for $(size(A, 2)) columns."
7+
@warn "$(length(color)) colors provided for $n columns."
78
end
89
return false
910
end
1011
return true
1112
end
1213

14+
function proper_length_bicoloring(
15+
A::AbstractMatrix,
16+
row_color::AbstractVector{<:Integer},
17+
column_color::AbstractVector{<:Integer};
18+
verbose::Bool=false,
19+
)
20+
m, n = size(A)
21+
bool = true
22+
if length(row_color) != m
23+
if verbose
24+
@warn "$(length(row_color)) colors provided for $m rows."
25+
end
26+
bool = false
27+
end
28+
if length(column_color) != n
29+
if verbose
30+
@warn "$(length(column_color)) colors provided for $n columns."
31+
end
32+
bool = false
33+
end
34+
return bool
35+
end
36+
1337
"""
1438
structurally_orthogonal_columns(
15-
A::AbstractMatrix, color::AbstractVector{<:Integer}
39+
A::AbstractMatrix, color::AbstractVector{<:Integer};
1640
verbose=false
1741
)
1842
@@ -56,8 +80,8 @@ end
5680
)
5781
5882
Return `true` if coloring the columns of the symmetric matrix `A` with the vector `color` results in a partition that is symmetrically orthogonal, and `false` otherwise.
59-
60-
A partition of the columns of a symmetrix matrix `A` is _symmetrically orthogonal_ if, for every nonzero element `A[i, j]`, either of the following statements holds:
83+
84+
A partition of the columns of a symmetric matrix `A` is _symmetrically orthogonal_ if, for every nonzero element `A[i, j]`, either of the following statements holds:
6185
6286
1. the group containing the column `A[:, j]` has no other column with a nonzero in row `i`
6387
2. the group containing the column `A[:, i]` has no other column with a nonzero in row `j`
@@ -102,6 +126,57 @@ function symmetrically_orthogonal_columns(
102126
return true
103127
end
104128

129+
"""
130+
structurally_biorthogonal(
131+
A::AbstractMatrix, row_color::AbstractVector{<:Integer}, column_color::AbstractVector{<:Integer};
132+
verbose=false
133+
)
134+
135+
Return `true` if bicoloring of the matrix `A` with the vectors `row_color` and `column_color` results in a bipartition that is structurally biorthogonal, and `false` otherwise.
136+
137+
A bipartition of the rows and columns of a matrix `A` is _structurally biorthogonal_ if, for every nonzero element `A[i, j]`, either of the following statements holds:
138+
139+
1. the group containing the column `A[:, j]` has no other column with a nonzero in row `i`
140+
2. the group containing the row `A[i, :]` has no other row with a nonzero in column `j`
141+
142+
!!! warning
143+
This function is not coded with efficiency in mind, it is designed for small-scale tests.
144+
"""
145+
function structurally_biorthogonal(
146+
A::AbstractMatrix,
147+
row_color::AbstractVector{<:Integer},
148+
column_color::AbstractVector{<:Integer};
149+
verbose::Bool=false,
150+
)
151+
if !proper_length_bicoloring(A, row_color, column_color; verbose)
152+
return false
153+
end
154+
column_group = group_by_color(column_color)
155+
row_group = group_by_color(row_color)
156+
for i in axes(A, 1), j in axes(A, 2)
157+
iszero(A[i, j]) && continue
158+
ci, cj = row_color[i], column_color[j]
159+
gi, gj = row_group[ci], column_group[cj]
160+
A_gj_rowi = view(A, i, gj)
161+
A_gi_columnj = view(A, gi, j)
162+
nonzeros_gj_rowi = count(!iszero, A_gj_rowi)
163+
nonzeros_gi_columnj = count(!iszero, A_gi_columnj)
164+
if nonzeros_gj_rowi > 1 && nonzeros_gi_columnj > 1
165+
if verbose
166+
gj_incompatible_columns = gj[findall(!iszero, A_gj_rowi)]
167+
gi_incompatible_rows = gi[findall(!iszero, A_gi_columnj)]
168+
@warn """
169+
For coefficient (i=$i, j=$j) with row color ci=$ci and column color cj=$cj:
170+
- In row color ci=$ci, rows $gi_incompatible_rows all have nonzeros in column j=$j.
171+
- In column color cj=$cj, columns $gj_incompatible_columns all have nonzeros in row i=$i.
172+
"""
173+
end
174+
return false
175+
end
176+
end
177+
return true
178+
end
179+
105180
"""
106181
directly_recoverable_columns(
107182
A::AbstractMatrix, color::AbstractVector{<:Integer}

src/graph.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ end
100100
101101
Undirected graph without self-loops representing the nonzeros of a symmetric matrix (typically a Hessian matrix).
102102
103-
The adjacency graph of a symmetrix matric `A ∈ ℝ^{n × n}` is `G(A) = (V, E)` where
103+
The adjacency graph of a symmetric matrix `A ∈ ℝ^{n × n}` is `G(A) = (V, E)` where
104104
105105
- `V = 1:n` is the set of rows or columns `i`/`j`
106106
- `(i, j) ∈ E` whenever `A[i, j] ≠ 0` and `i ≠ j`

test/check.jl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ using LinearAlgebra
22
using SparseMatrixColorings:
33
structurally_orthogonal_columns,
44
symmetrically_orthogonal_columns,
5+
structurally_biorthogonal,
56
directly_recoverable_columns,
67
what_fig_41,
78
efficient_fig_1
@@ -122,3 +123,37 @@ For coefficient (i=2, j=3) with column colors (ci=3, cj=1):
122123
@test !directly_recoverable_columns(A, [1, 2, 1, 3, 1, 4, 2, 5, 1, 2])
123124
@test !directly_recoverable_columns(A, [1, 2, 1, 4, 1, 4, 3, 5, 1, 2])
124125
end
126+
127+
@testset "Structurally biorthogonal" begin
128+
A = [
129+
1 5 7 9 11
130+
2 0 0 0 12
131+
3 0 0 0 13
132+
4 6 8 10 14
133+
]
134+
135+
# success
136+
137+
@test structurally_biorthogonal(A, [1, 2, 2, 3], [1, 2, 2, 2, 3])
138+
139+
# failure
140+
141+
@test !structurally_biorthogonal(A, [1, 2, 2, 3], [1, 2, 2, 2])
142+
@test !structurally_biorthogonal(A, [1, 2, 2, 3, 4], [1, 2, 2, 2, 3])
143+
@test !structurally_biorthogonal(A, [1, 1, 1, 2], [1, 1, 1, 1, 2])
144+
145+
@test_logs (:warn, "4 colors provided for 5 columns.") !structurally_biorthogonal(
146+
A, [1, 2, 2, 3], [1, 2, 2, 2]; verbose=true
147+
)
148+
@test_logs (:warn, "5 colors provided for 4 rows.") !structurally_biorthogonal(
149+
A, [1, 2, 2, 3, 4], [1, 2, 2, 2, 3]; verbose=true
150+
)
151+
@test_logs (
152+
:warn,
153+
"""
154+
For coefficient (i=1, j=1) with row color ci=1 and column color cj=1:
155+
- In row color ci=1, rows [1, 2, 3] all have nonzeros in column j=1.
156+
- In column color cj=1, columns [1, 2, 3, 4] all have nonzeros in row i=1.
157+
""",
158+
) !structurally_biorthogonal(A, [1, 1, 1, 2], [1, 1, 1, 1, 2]; verbose=true)
159+
end

test/utils.jl

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ using SparseMatrixColorings:
1010
matrix_versions,
1111
respectful_similar,
1212
structurally_orthogonal_columns,
13-
symmetrically_orthogonal_columns
13+
symmetrically_orthogonal_columns,
14+
structurally_biorthogonal
1415
using Test
1516

1617
function test_coloring_decompression(
@@ -59,13 +60,16 @@ function test_coloring_decompression(
5960
if partition == :column
6061
@test structurally_orthogonal_columns(A0, color)
6162
@test directly_recoverable_columns(A0, color)
62-
else
63+
elseif partition == :row
6364
@test structurally_orthogonal_columns(transpose(A0), color)
6465
@test directly_recoverable_columns(transpose(A0), color)
6566
end
6667
else
67-
@test symmetrically_orthogonal_columns(A0, color)
68-
@test directly_recoverable_columns(A0, color)
68+
# structure == :symmetric
69+
if partition == :column
70+
@test symmetrically_orthogonal_columns(A0, color)
71+
@test directly_recoverable_columns(A0, color)
72+
end
6973
end
7074
end
7175
end
@@ -163,9 +167,16 @@ function test_bicoloring_decompression(
163167
result = coloring(A, problem, algo; decompression_eltype=Float64)
164168
end
165169
Br, Bc = compress(A, result)
166-
@test size(Br, 1) == length(unique(row_colors(result)))
167-
@test size(Bc, 2) == length(unique(column_colors(result)))
170+
row_color, column_color = row_colors(result), column_colors(result)
171+
@test size(Br, 1) == length(unique(row_color))
172+
@test size(Bc, 2) == length(unique(column_color))
168173
@test ncolors(result) == size(Br, 1) + size(Bc, 2)
174+
175+
if decompression == :direct
176+
@testset "Recoverability" begin
177+
@test structurally_biorthogonal(A0, row_color, column_color)
178+
end
179+
end
169180
@testset "Full decompression" begin
170181
@test decompress(Br, Bc, result) A0
171182
@test decompress(Br, Bc, result) A0 # check result wasn't modified

0 commit comments

Comments
 (0)