Skip to content

Commit 636aeca

Browse files
committed
Add a function substitutable_columns
1 parent a314d12 commit 636aeca

4 files changed

Lines changed: 257 additions & 3 deletions

File tree

docs/src/dev.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ SparseMatrixColorings.directly_recoverable_columns
5050
SparseMatrixColorings.symmetrically_orthogonal_columns
5151
SparseMatrixColorings.structurally_orthogonal_columns
5252
SparseMatrixColorings.structurally_biorthogonal
53+
SparseMatrixColorings.substitutable_columns
5354
SparseMatrixColorings.valid_dynamic_order
5455
```
5556

src/check.jl

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,15 @@ A partition of the columns of a symmetric matrix `A` is _symmetrically orthogona
8686
1. the group containing the column `A[:, j]` has no other column with a nonzero in row `i`
8787
2. the group containing the column `A[:, i]` has no other column with a nonzero in row `j`
8888
89+
It is equivalent to a __star coloring__.
90+
8991
!!! warning
9092
This function is not coded with efficiency in mind, it is designed for small-scale tests.
9193
9294
# References
9395
96+
> [_On the Estimation of Sparse Hessian Matrices_](https://doi.org/10.1137/0716078), Powell and Toint (1979)
97+
> [_Estimation of sparse hessian matrices and graph coloring problems_](https://doi.org/10.1007/BF02612334), Coleman and Moré (1984)
9498
> [_What Color Is Your Jacobian? Graph Coloring for Computing Derivatives_](https://epubs.siam.org/doi/10.1137/S0036144504444711), Gebremedhin et al. (2005)
9599
"""
96100
function symmetrically_orthogonal_columns(
@@ -102,7 +106,7 @@ function symmetrically_orthogonal_columns(
102106
end
103107
issymmetric(A) || return false
104108
group = group_by_color(color)
105-
for i in axes(A, 2), j in axes(A, 2)
109+
for i in axes(A, 1), j in axes(A, 2)
106110
iszero(A[i, j]) && continue
107111
ci, cj = color[i], color[j]
108112
check = _bilateral_check(
@@ -261,6 +265,128 @@ function directly_recoverable_columns(
261265
return true
262266
end
263267

268+
"""
269+
substitutable_columns(
270+
A::AbstractMatrix, order_nonzeros::AbstractMatrix, color::AbstractVector{<:Integer};
271+
verbose=false
272+
)
273+
274+
Return `true` if coloring the columns of the symmetric matrix `A` with the vector `color` results in a partition that is substitutable, and `false` otherwise.
275+
For all nonzeros `A[i, j]`, `order_nonzeros[i, j]` provides its order of recovery.
276+
277+
A partition of the columns of a symmetric matrix `A` is _substitutable_ if, for every nonzero element `A[i, j]`, either of the following statements holds:
278+
279+
1. the group containing the column `A[:, j]` has all nonzeros in row `i` ordered before `A[i, j]`
280+
2. the group containing the column `A[:, i]` has all nonzeros in row `j` ordered before `A[i, j]`
281+
282+
It is equivalent to an __acyclic coloring__.
283+
284+
!!! warning
285+
This function is not coded with efficiency in mind, it is designed for small-scale tests.
286+
287+
# References
288+
289+
> [_On the Estimation of Sparse Hessian Matrices_](https://doi.org/10.1137/0716078), Powell and Toint (1979)
290+
> [_The Cyclic Coloring Problem and Estimation of Sparse Hessian Matrices_](https://doi.org/10.1137/0607026), Coleman and Cai (1986)
291+
> [_What Color Is Your Jacobian? Graph Coloring for Computing Derivatives_](https://epubs.siam.org/doi/10.1137/S0036144504444711), Gebremedhin et al. (2005)
292+
"""
293+
function substitutable_columns(
294+
A::AbstractMatrix, order_nonzeros::AbstractMatrix, color::AbstractVector{<:Integer}; verbose::Bool=false
295+
)
296+
checksquare(A)
297+
if !proper_length_coloring(A, color; verbose)
298+
return false
299+
end
300+
issymmetric(A) || return false
301+
group = group_by_color(color)
302+
for i in axes(A, 1), j in axes(A, 2)
303+
iszero(A[i, j]) && continue
304+
ci, cj = color[i], color[j]
305+
check = _substitutable_check(
306+
A, order_nonzeros; i, j, ci, cj, row_group=group, column_group=group, verbose
307+
)
308+
!check && return false
309+
end
310+
return true
311+
end
312+
313+
function _substitutable_check(
314+
A::AbstractMatrix,
315+
order_nonzeros::AbstractMatrix;
316+
i::Integer,
317+
j::Integer,
318+
ci::Integer,
319+
cj::Integer,
320+
row_group::AbstractVector,
321+
column_group::AbstractVector,
322+
verbose::Bool)
323+
order_ij = order_nonzeros[i,j]
324+
k_row = 0
325+
k_column = 0
326+
if ci != 0
327+
for k in row_group[ci]
328+
(k == i) && continue
329+
if !iszero(A[k, j])
330+
order_kj = order_nonzeros[k, j]
331+
@assert !iszero(order_kj)
332+
if order_kj > order_ij
333+
k_row = k
334+
end
335+
end
336+
end
337+
end
338+
if cj != 0
339+
for k in column_group[cj]
340+
(k == j) && continue
341+
if !iszero(A[i, k])
342+
order_ik = order_nonzeros[i, k]
343+
@assert !iszero(order_ik)
344+
if order_ik > order_ij
345+
k_column = k
346+
end
347+
end
348+
end
349+
end
350+
if ci == 0 && cj == 0
351+
if verbose
352+
@warn """
353+
For coefficient (i=$i, j=$j) with colors (ci=$ci, cj=$cj):
354+
- Row color ci=$ci is neutral.
355+
- Column color cj=$cj is neutral.
356+
"""
357+
end
358+
return false
359+
elseif ci == 0 && !iszero(k_column)
360+
if verbose
361+
@warn """
362+
For coefficient (i=$i, j=$j) with colors (ci=$ci, cj=$cj):
363+
- Row color ci=$ci is neutral.
364+
- For the column $k_column in column color cj=$cj, A[$i, $k_column] is ordered after A[$i, $j].
365+
"""
366+
end
367+
return false
368+
elseif cj == 0 && !iszero(k_row)
369+
if verbose
370+
@warn """
371+
For coefficient (i=$i, j=$j) with colors (ci=$ci, cj=$cj):
372+
- For the row $k_row in row color ci=$ci, A[$k_row, $j] is ordered after A[$i, $j].
373+
- Column color cj=$cj is neutral.
374+
"""
375+
end
376+
return false
377+
elseif !iszero(k_row) && !iszero(k_column)
378+
if verbose
379+
@warn """
380+
For coefficient (i=$i, j=$j) with colors (ci=$ci, cj=$cj):
381+
- For the row $k_row in row color ci=$ci, A[$k_row, $j] is ordered after A[$i, $j].
382+
- For the column $k_column in column color cj=$cj, A[$i, $k_column] is ordered after A[$i, $j].
383+
"""
384+
end
385+
return false
386+
end
387+
return true
388+
end
389+
264390
"""
265391
valid_dynamic_order(g::AdjacencyGraph, π::AbstractVector{<:Integer}, order::DynamicDegreeBasedOrder)
266392
valid_dynamic_order(bg::AdjacencyGraph, ::Val{side}, π::AbstractVector{<:Integer}, order::DynamicDegreeBasedOrder)

test/check.jl

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ using SparseMatrixColorings:
44
symmetrically_orthogonal_columns,
55
structurally_biorthogonal,
66
directly_recoverable_columns,
7+
substitutable_columns,
78
what_fig_41,
89
efficient_fig_1
910
using Test
@@ -184,3 +185,96 @@ For coefficient (i=1, j=1) with colors (ci=0, cj=0):
184185
""",
185186
) structurally_biorthogonal(A, [0, 2, 2, 3], [0, 2, 2, 2, 3], verbose=true)
186187
end
188+
189+
@testset "Substitutable columns" begin
190+
A1 = [
191+
1 1 1 1 1
192+
1 1 0 0 0
193+
1 0 1 0 0
194+
1 0 0 1 0
195+
1 0 0 0 1
196+
]
197+
B1 = [
198+
1 6 7 8 9
199+
6 2 0 0 0
200+
7 0 3 0 0
201+
8 0 0 4 0
202+
9 0 0 0 5
203+
]
204+
A2 = [
205+
1 1 0 0 0
206+
1 1 1 0 0
207+
0 1 1 1 0
208+
0 0 1 1 1
209+
0 0 0 1 1
210+
]
211+
B2 = [
212+
5 1 0 0 0
213+
1 6 2 0 0
214+
0 2 7 3 0
215+
0 0 3 8 4
216+
0 0 0 4 9
217+
]
218+
A3 = [
219+
0 1 1 1 1
220+
1 0 1 1 1
221+
1 1 0 1 1
222+
1 1 1 0 1
223+
1 1 1 1 0
224+
]
225+
B3 = [
226+
0 1 2 3 4
227+
1 0 5 6 7
228+
2 5 0 8 9
229+
3 6 8 0 10
230+
4 7 9 10 0
231+
]
232+
233+
# success
234+
235+
substitutable_columns(A1, B1, [1, 2, 2, 2, 2])
236+
substitutable_columns(A2, B2, [1, 2, 3, 1, 2])
237+
substitutable_columns(A3, B3, [1, 2, 3, 4, 0])
238+
239+
# failure
240+
241+
@test !substitutable_columns(A1, B1, [1, 1, 1, 1, 1])
242+
@test_logs (
243+
:warn,
244+
"""
245+
For coefficient (i=1, j=1) with colors (ci=1, cj=1):
246+
- For the row 5 in row color ci=1, A[5, 1] is ordered after A[1, 1].
247+
- For the column 5 in column color cj=1, A[1, 5] is ordered after A[1, 1].
248+
""",
249+
) substitutable_columns(A1, B1, [1, 1, 1, 1, 1]; verbose=true)
250+
251+
@test !substitutable_columns(A2, B2, [1, 2, 0, 1, 2])
252+
@test_logs (
253+
:warn,
254+
"""
255+
For coefficient (i=3, j=3) with colors (ci=0, cj=0):
256+
- Row color ci=0 is neutral.
257+
- Column color cj=0 is neutral.
258+
""",
259+
) substitutable_columns(A2, B2, [1, 2, 0, 1, 2]; verbose=true)
260+
261+
@test !substitutable_columns(A3, B3, [0, 1, 2, 3, 3])
262+
@test_logs (
263+
:warn,
264+
"""
265+
For coefficient (i=1, j=4) with colors (ci=0, cj=3):
266+
- Row color ci=0 is neutral.
267+
- For the column 5 in column color cj=3, A[1, 5] is ordered after A[1, 4].
268+
""",
269+
) substitutable_columns(A3, B3, [0, 1, 2, 3, 3]; verbose=true)
270+
271+
@test !substitutable_columns(A3, B3, [1, 2, 3, 3, 0])
272+
@test_logs (
273+
:warn,
274+
"""
275+
For coefficient (i=3, j=5) with colors (ci=3, cj=0):
276+
- For the row 4 in row color ci=3, A[4, 5] is ordered after A[3, 5].
277+
- Column color cj=0 is neutral.
278+
""",
279+
) substitutable_columns(A3, B3, [1, 2, 3, 3, 0]; verbose=true)
280+
end

test/utils.jl

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,45 @@ using SparseMatrixColorings
77
using SparseMatrixColorings:
88
AdjacencyGraph,
99
LinearSystemColoringResult,
10+
TreeSetColoringResult,
1011
directly_recoverable_columns,
1112
matrix_versions,
1213
respectful_similar,
1314
structurally_orthogonal_columns,
1415
symmetrically_orthogonal_columns,
15-
structurally_biorthogonal
16+
structurally_biorthogonal,
17+
substitutable_columns
1618
using Test
1719

1820
const _ALL_ORDERS = (
1921
NaturalOrder(), LargestFirst(), SmallestLast(), IncidenceDegree(), DynamicLargestFirst()
2022
)
2123

24+
function order_from_trees(result::TreeSetColoringResult)
25+
(; ag, color, reverse_bfs_orders, diagonal_indices, tree_edge_indices, nt) = result
26+
(; S) = ag
27+
n = length(color)
28+
nnzS = nnz(S)
29+
nzval = zeros(Int, nnzS)
30+
order_nonzeros = SparseMatrixCSC(n, n, S.colptr, S.rowval, nzval)
31+
counter = 0
32+
for i in diagonal_indices
33+
counter += 1
34+
order_nonzeros[i, i] = counter
35+
end
36+
for k in 1:nt
37+
first = tree_edge_indices[k]
38+
last = tree_edge_indices[k + 1] - 1
39+
for pos in first:last
40+
(i, j) = reverse_bfs_orders[pos]
41+
counter += 1
42+
order_nonzeros[i, j] = counter
43+
order_nonzeros[j, i] = counter
44+
end
45+
end
46+
return order_nonzeros
47+
end
48+
2249
function test_coloring_decompression(
2350
A0::AbstractMatrix,
2451
problem::ColoringProblem{structure,partition},
@@ -77,7 +104,6 @@ function test_coloring_decompression(
77104
end
78105

79106
@testset "Recoverability" begin
80-
# TODO: find tests for recoverability for substitution decompression
81107
if decompression == :direct
82108
if structure == :nonsymmetric
83109
if partition == :column
@@ -97,6 +123,13 @@ function test_coloring_decompression(
97123
end
98124
end
99125

126+
if decompression == :substitution
127+
if structure == :symmetric
128+
order_nonzeros = order_from_trees(result)
129+
@test substitutable_columns(A0, order_nonzeros, color)
130+
end
131+
end
132+
100133
@testset "Single-color decompression" begin
101134
if decompression == :direct # TODO: implement for :substitution too
102135
A2 = respectful_similar(A, eltype(B))

0 commit comments

Comments
 (0)