Skip to content

Commit 3b9fe4b

Browse files
authored
Add test functions substitutable_columns and substitutable_bidirectional (#297)
1 parent a314d12 commit 3b9fe4b

File tree

4 files changed

+397
-4
lines changed

4 files changed

+397
-4
lines changed

docs/src/dev.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ SparseMatrixColorings.directly_recoverable_columns
5050
SparseMatrixColorings.symmetrically_orthogonal_columns
5151
SparseMatrixColorings.structurally_orthogonal_columns
5252
SparseMatrixColorings.structurally_biorthogonal
53+
SparseMatrixColorings.substitutable_columns
54+
SparseMatrixColorings.substitutable_bidirectional
55+
SparseMatrixColorings.rank_nonzeros_from_trees
5356
SparseMatrixColorings.valid_dynamic_order
5457
```
5558

src/check.jl

Lines changed: 248 additions & 2 deletions
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(
@@ -126,6 +130,8 @@ A bipartition of the rows and columns of a matrix `A` is _structurally biorthogo
126130
1. the group containing the column `A[:, j]` has no other column with a nonzero in row `i`
127131
2. the group containing the row `A[i, :]` has no other row with a nonzero in column `j`
128132
133+
It is equivalent to a __star bicoloring__.
134+
129135
!!! warning
130136
This function is not coded with efficiency in mind, it is designed for small-scale tests.
131137
"""
@@ -138,8 +144,8 @@ function structurally_biorthogonal(
138144
if !proper_length_bicoloring(A, row_color, column_color; verbose)
139145
return false
140146
end
141-
column_group = group_by_color(column_color)
142147
row_group = group_by_color(row_color)
148+
column_group = group_by_color(column_color)
143149
for i in axes(A, 1), j in axes(A, 2)
144150
iszero(A[i, j]) && continue
145151
ci, cj = row_color[i], column_color[j]
@@ -261,6 +267,174 @@ function directly_recoverable_columns(
261267
return true
262268
end
263269

270+
"""
271+
substitutable_columns(
272+
A::AbstractMatrix, rank_nonzeros::AbstractMatrix, color::AbstractVector{<:Integer};
273+
verbose=false
274+
)
275+
276+
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.
277+
For all nonzeros `A[i, j]`, `rank_nonzeros[i, j]` provides its rank of recovery.
278+
279+
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:
280+
281+
1. the group containing the column `A[:, j]` has all nonzeros in row `i` ordered before `A[i, j]`
282+
2. the group containing the column `A[:, i]` has all nonzeros in row `j` ordered before `A[i, j]`
283+
284+
It is equivalent to an __acyclic coloring__.
285+
286+
!!! warning
287+
This function is not coded with efficiency in mind, it is designed for small-scale tests.
288+
289+
# References
290+
291+
> [_On the Estimation of Sparse Hessian Matrices_](https://doi.org/10.1137/0716078), Powell and Toint (1979)
292+
> [_The Cyclic Coloring Problem and Estimation of Sparse Hessian Matrices_](https://doi.org/10.1137/0607026), Coleman and Cai (1986)
293+
> [_What Color Is Your Jacobian? Graph Coloring for Computing Derivatives_](https://epubs.siam.org/doi/10.1137/S0036144504444711), Gebremedhin et al. (2005)
294+
"""
295+
function substitutable_columns(
296+
A::AbstractMatrix,
297+
rank_nonzeros::AbstractMatrix,
298+
color::AbstractVector{<:Integer};
299+
verbose::Bool=false,
300+
)
301+
checksquare(A)
302+
if !proper_length_coloring(A, color; verbose)
303+
return false
304+
end
305+
issymmetric(A) || return false
306+
group = group_by_color(color)
307+
for i in axes(A, 1), j in axes(A, 2)
308+
iszero(A[i, j]) && continue
309+
ci, cj = color[i], color[j]
310+
check = _substitutable_check(
311+
A, rank_nonzeros; i, j, ci, cj, row_group=group, column_group=group, verbose
312+
)
313+
!check && return false
314+
end
315+
return true
316+
end
317+
318+
"""
319+
substitutable_bidirectional(
320+
A::AbstractMatrix, rank_nonzeros::AbstractMatrix, row_color::AbstractVector{<:Integer}, column_color::AbstractVector{<:Integer};
321+
verbose=false
322+
)
323+
324+
Return `true` if bicoloring of the matrix `A` with the vectors `row_color` and `column_color` results in a bipartition that is substitutable, and `false` otherwise.
325+
For all nonzeros `A[i, j]`, `rank_nonzeros[i, j]` provides its rank of recovery.
326+
327+
A bipartition of the rows and columns of a matrix `A` is _substitutable_ if, for every nonzero element `A[i, j]`, either of the following statements holds:
328+
329+
1. the group containing the column `A[:, j]` has all nonzeros in row `i` ordered before `A[i, j]`
330+
2. the group containing the row `A[i, :]` has all nonzeros in column `j` ordered before `A[i, j]`
331+
332+
It is equivalent to an __acyclic bicoloring__.
333+
334+
!!! warning
335+
This function is not coded with efficiency in mind, it is designed for small-scale tests.
336+
"""
337+
function substitutable_bidirectional(
338+
A::AbstractMatrix,
339+
rank_nonzeros::AbstractMatrix,
340+
row_color::AbstractVector{<:Integer},
341+
column_color::AbstractVector{<:Integer};
342+
verbose::Bool=false,
343+
)
344+
if !proper_length_bicoloring(A, row_color, column_color; verbose)
345+
return false
346+
end
347+
row_group = group_by_color(row_color)
348+
column_group = group_by_color(column_color)
349+
for i in axes(A, 1), j in axes(A, 2)
350+
iszero(A[i, j]) && continue
351+
ci, cj = row_color[i], column_color[j]
352+
check = _substitutable_check(
353+
A, rank_nonzeros; i, j, ci, cj, row_group, column_group, verbose
354+
)
355+
!check && return false
356+
end
357+
return true
358+
end
359+
360+
function _substitutable_check(
361+
A::AbstractMatrix,
362+
rank_nonzeros::AbstractMatrix;
363+
i::Integer,
364+
j::Integer,
365+
ci::Integer,
366+
cj::Integer,
367+
row_group::AbstractVector,
368+
column_group::AbstractVector,
369+
verbose::Bool,
370+
)
371+
order_ij = rank_nonzeros[i, j]
372+
k_row = 0
373+
k_column = 0
374+
if ci != 0
375+
for k in row_group[ci]
376+
(k == i) && continue
377+
if !iszero(A[k, j])
378+
order_kj = rank_nonzeros[k, j]
379+
@assert !iszero(order_kj)
380+
if order_kj > order_ij
381+
k_row = k
382+
end
383+
end
384+
end
385+
end
386+
if cj != 0
387+
for k in column_group[cj]
388+
(k == j) && continue
389+
if !iszero(A[i, k])
390+
order_ik = rank_nonzeros[i, k]
391+
@assert !iszero(order_ik)
392+
if order_ik > order_ij
393+
k_column = k
394+
end
395+
end
396+
end
397+
end
398+
if ci == 0 && cj == 0
399+
if verbose
400+
@warn """
401+
For coefficient (i=$i, j=$j) with colors (ci=$ci, cj=$cj):
402+
- Row color ci=$ci is neutral.
403+
- Column color cj=$cj is neutral.
404+
"""
405+
end
406+
return false
407+
elseif ci == 0 && !iszero(k_column)
408+
if verbose
409+
@warn """
410+
For coefficient (i=$i, j=$j) with colors (ci=$ci, cj=$cj):
411+
- Row color ci=$ci is neutral.
412+
- For the column $k_column in column color cj=$cj, A[$i, $k_column] is ordered after A[$i, $j].
413+
"""
414+
end
415+
return false
416+
elseif cj == 0 && !iszero(k_row)
417+
if verbose
418+
@warn """
419+
For coefficient (i=$i, j=$j) with colors (ci=$ci, cj=$cj):
420+
- For the row $k_row in row color ci=$ci, A[$k_row, $j] is ordered after A[$i, $j].
421+
- Column color cj=$cj is neutral.
422+
"""
423+
end
424+
return false
425+
elseif !iszero(k_row) && !iszero(k_column)
426+
if verbose
427+
@warn """
428+
For coefficient (i=$i, j=$j) with colors (ci=$ci, cj=$cj):
429+
- For the row $k_row in row color ci=$ci, A[$k_row, $j] is ordered after A[$i, $j].
430+
- For the column $k_column in column color cj=$cj, A[$i, $k_column] is ordered after A[$i, $j].
431+
"""
432+
end
433+
return false
434+
end
435+
return true
436+
end
437+
264438
"""
265439
valid_dynamic_order(g::AdjacencyGraph, π::AbstractVector{<:Integer}, order::DynamicDegreeBasedOrder)
266440
valid_dynamic_order(bg::AdjacencyGraph, ::Val{side}, π::AbstractVector{<:Integer}, order::DynamicDegreeBasedOrder)
@@ -326,3 +500,75 @@ function valid_dynamic_order(
326500
end
327501
return true
328502
end
503+
504+
"""
505+
rank_nonzeros_from_trees(result::TreeSetColoringResult)
506+
rank_nonzeros_from_trees(result::BicoloringResult)
507+
508+
Construct a sparse matrix `rank_nonzeros` that assigns a unique recovery rank
509+
to each nonzero coefficient associated with an acyclic coloring or bicoloring.
510+
511+
For every nonzero entry `result.A[i, j]`, `rank_nonzeros[i, j]` stores a strictly positive
512+
integer representing the order in which this coefficient is recovered during the decompression.
513+
A larger value means the coefficient is recovered later.
514+
515+
This ranking is used to test substitutability (acyclicity) of colorings:
516+
for a given nonzero `result.A[i, j]`, the ranks allow one to check whether all competing
517+
nonzeros in the same row or column (within a color group) are recovered before it.
518+
"""
519+
function rank_nonzeros_from_trees end
520+
521+
function rank_nonzeros_from_trees(result::TreeSetColoringResult)
522+
(; A, ag, reverse_bfs_orders, diagonal_indices, tree_edge_indices, nt) = result
523+
(; S) = ag
524+
m, n = size(A)
525+
nnzS = nnz(S)
526+
nzval = zeros(Int, nnzS)
527+
rank_nonzeros = SparseMatrixCSC(n, n, S.colptr, S.rowval, nzval)
528+
counter = 0
529+
for i in diagonal_indices
530+
counter += 1
531+
rank_nonzeros[i, i] = counter
532+
end
533+
for k in 1:nt
534+
first = tree_edge_indices[k]
535+
last = tree_edge_indices[k + 1] - 1
536+
for pos in first:last
537+
(i, j) = reverse_bfs_orders[pos]
538+
counter += 1
539+
rank_nonzeros[i, j] = counter
540+
rank_nonzeros[j, i] = counter
541+
end
542+
end
543+
return rank_nonzeros
544+
end
545+
546+
function rank_nonzeros_from_trees(result::BicoloringResult)
547+
(; A, abg, row_color, column_color, symmetric_result, large_colptr, large_rowval) =
548+
result
549+
@assert symmetric_result isa TreeSetColoringResult
550+
(; ag, reverse_bfs_orders, tree_edge_indices, nt) = symmetric_result
551+
(; S) = ag
552+
m, n = size(A)
553+
nnzA = nnz(S) ÷ 2
554+
nzval = zeros(Int, nnzA)
555+
colptr = large_colptr[1:(n + 1)]
556+
rowval = large_rowval[1:nnzA]
557+
rowval .-= n
558+
rank_nonzeros = SparseMatrixCSC(m, n, colptr, rowval, nzval)
559+
counter = 0
560+
for k in 1:nt
561+
first = tree_edge_indices[k]
562+
last = tree_edge_indices[k + 1] - 1
563+
for pos in first:last
564+
(i, j) = reverse_bfs_orders[pos]
565+
counter += 1
566+
if i > j
567+
rank_nonzeros[i - n, j] = counter
568+
else
569+
rank_nonzeros[j - n, i] = counter
570+
end
571+
end
572+
end
573+
return rank_nonzeros
574+
end

0 commit comments

Comments
 (0)