Skip to content

Commit 2a6d30e

Browse files
committed
Decompression for TreeSetBicoloringResult
1 parent a9da6bb commit 2a6d30e

5 files changed

Lines changed: 166 additions & 101 deletions

File tree

docs/src/dev.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ SparseMatrixColorings.symmetric_coefficient
2727
SparseMatrixColorings.star_coloring
2828
SparseMatrixColorings.acyclic_coloring
2929
SparseMatrixColorings.group_by_color
30+
SparseMatrixColorings.remap_colors
3031
SparseMatrixColorings.StarSet
3132
SparseMatrixColorings.TreeSet
3233
```
@@ -43,13 +44,6 @@ SparseMatrixColorings.StarSetBicoloringResult
4344
SparseMatrixColorings.TreeSetBicoloringResult
4445
```
4546

46-
## Decompression
47-
48-
```@docs
49-
SparseMatrixColorings.JoinCompressed
50-
SparseMatrixColorings.remap_colors
51-
```
52-
5347
## Testing
5448

5549
```@docs

src/decompression.jl

Lines changed: 108 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -531,12 +531,14 @@ end
531531
## TreeSetColoringResult
532532

533533
function decompress!(
534-
A::AbstractMatrix, B::AbstractMatrix, result::TreeSetColoringResult, uplo::Symbol=:F
535-
)
534+
A::AbstractMatrix{R},
535+
B::AbstractMatrix{R},
536+
result::TreeSetColoringResult,
537+
uplo::Symbol=:F,
538+
) where {R<:Real}
536539
(; ag, color, reverse_bfs_orders, buffer) = result
537540
(; S) = ag
538541
uplo == :F && check_same_pattern(A, S)
539-
R = eltype(A)
540542
fill!(A, zero(R))
541543

542544
if eltype(buffer) == R
@@ -715,49 +717,6 @@ function decompress!(
715717
return A
716718
end
717719

718-
## BicoloringResult
719-
720-
"""
721-
JoinCompressed{R<:Real}
722-
723-
For a bicoloring of an original matrix `A` of size `(m, n)`, we build an augmented matrix `A_and_Aᵀ = [0 Aᵀ; A 0]`.
724-
Symmetric column coloring is then performed on `A_and_Aᵀ`, and the column-wise compression of `A_and_Aᵀ` produces a matrix `Br_and_Bc`.
725-
In the case of bicoloring, `Br_and_Bc` is computed as two partial compressions: the row-wise compression `Br` (corresponding to `Aᵀ`) and the column-wise compression `Bc` (corresponding to `A`).
726-
727-
For the symmetric decompression, we lazily reconstruct `Br_and_Bc` from `Br` and `Bc`, knowing that the symmetric colors (those making up `Br_and_Bc`) may appear in either a row of `Br`, a column of `Bc`, or both.
728-
The columns of the top part of `Br_and_Bc` (rows between `1` and `n`) are taken from the rows of `Br`, interleaved with zero columns whenever the current color has not been used to color any row.
729-
The columns of the bottom part of `Br_and_Bc` (rows between `n+1` and `n+m`) are taken from the columns of `Bc`, interleaved with zero columns whenever the current color has not been used to color any column.
730-
731-
We use the vectors `symmetric_to_row` and `symmetric_to_column` to map colors obtained during star or acyclic coloring to row colors in `Br` and column colors in `Bc`.
732-
733-
# Fields
734-
735-
- `m::Int`: number of rows in `A` and `Bc`
736-
- `n::Int`: number of columns in `A` and `Br`
737-
- `Br::AbstractMatrix{R}`: row-wise compressed matrix
738-
- `Bc::AbstractMatrix{R}`: column-wise compressed matrix
739-
- `symmetric_to_row::Vector{Int}`: vector mapping symmetric colors to row indices in `Br`
740-
- `symmetric_to_column::Vector{Int}`: vector mapping symmetric colors to column indices in `Bc`
741-
"""
742-
struct JoinCompressed{R<:Real,M1<:AbstractMatrix{R},M2<:AbstractMatrix{R}} <:
743-
AbstractMatrix{R}
744-
m::Int
745-
n::Int
746-
Br::M1
747-
Bc::M2
748-
symmetric_to_row::Vector{Int}
749-
symmetric_to_column::Vector{Int}
750-
end
751-
752-
function Base.getindex(B::JoinCompressed, i::Int, j::Int)
753-
(; n, Br, Bc, symmetric_to_row, symmetric_to_column) = B
754-
if i n
755-
return Br[symmetric_to_row[j], i]
756-
else
757-
return Bc[i - n, symmetric_to_column[j]]
758-
end
759-
end
760-
761720
## StarSetBicoloringResult
762721

763722
function decompress!(
@@ -807,33 +766,118 @@ end
807766
## TreeSetBicoloringResult
808767

809768
function decompress!(
810-
A::AbstractMatrix,
811-
Br::AbstractMatrix,
812-
Bc::AbstractMatrix,
769+
A::AbstractMatrix{R},
770+
Br::AbstractMatrix{R},
771+
Bc::AbstractMatrix{R},
813772
result::TreeSetBicoloringResult,
814-
)
815-
(; symmetric_to_row, symmetric_to_column, symmetric_result) = result
773+
) where {R<:Real}
774+
(;
775+
ag,
776+
symmetric_color,
777+
symmetric_to_row,
778+
symmetric_to_column,
779+
reverse_bfs_orders,
780+
buffer,
781+
) = result
782+
(; S) = ag
783+
816784
m, n = size(A)
817-
Br_and_Bc = JoinCompressed(m, n, Br, Bc, symmetric_to_row, symmetric_to_column)
818-
A_and_Aᵀ = decompress(Br_and_Bc, symmetric_result)
819-
copyto!(A, A_and_Aᵀ[(n + 1):(n + m), 1:n]) # original matrix in bottom left corner
785+
fill!(A, zero(R))
786+
787+
if eltype(buffer) == R
788+
buffer_right_type = buffer
789+
else
790+
buffer_right_type = similar(buffer, R)
791+
end
792+
793+
for k in eachindex(reverse_bfs_orders)
794+
# Reset the buffer to zero for all vertices in a tree (except the root)
795+
for (vertex, _) in reverse_bfs_orders[k]
796+
buffer_right_type[vertex] = zero(R)
797+
end
798+
# Reset the buffer to zero for the root vertex
799+
(_, root) = reverse_bfs_orders[k][end]
800+
buffer_right_type[root] = zero(R)
801+
802+
for (i, j) in reverse_bfs_orders[k]
803+
cj = symmetric_color[j]
804+
if in_triangle(i, j, :L)
805+
val = Bc[i - n, symmetric_to_column[cj]] - buffer_right_type[i]
806+
buffer_right_type[j] = buffer_right_type[j] + val
807+
A[i - n, j] = val
808+
else
809+
val = Br[symmetric_to_row[cj], i] - buffer_right_type[i]
810+
buffer_right_type[j] = buffer_right_type[j] + val
811+
A[j - n, i] = val
812+
end
813+
end
814+
end
820815
return A
821816
end
822817

823818
function decompress!(
824-
A::SparseMatrixCSC,
825-
Br::AbstractMatrix,
826-
Bc::AbstractMatrix,
819+
A::SparseMatrixCSC{R},
820+
Br::AbstractMatrix{R},
821+
Bc::AbstractMatrix{R},
827822
result::TreeSetBicoloringResult,
828-
)
823+
) where {R<:Real}
829824
(;
830-
symmetric_to_row, symmetric_to_column, symmetric_result, large_colptr, large_rowval
825+
ag,
826+
symmetric_color,
827+
symmetric_to_column,
828+
symmetric_to_row,
829+
reverse_bfs_orders,
830+
A_indices,
831+
buffer,
831832
) = result
833+
(; S) = ag
834+
832835
m, n = size(A)
833-
Br_and_Bc = JoinCompressed(m, n, Br, Bc, symmetric_to_row, symmetric_to_column)
834-
# pretend A is larger
835-
A_and_noAᵀ = SparseMatrixCSC(m + n, m + n, large_colptr, large_rowval, A.nzval)
836-
# decompress lower triangle only
837-
decompress!(A_and_noAᵀ, Br_and_Bc, symmetric_result, :L)
836+
A_colptr = A.colptr
837+
nzA = nonzeros(A)
838+
839+
if eltype(buffer) == R
840+
buffer_right_type = buffer
841+
else
842+
buffer_right_type = similar(buffer, R)
843+
end
844+
845+
# Recover the coefficients of A
846+
counter = 0
847+
for k in eachindex(reverse_bfs_orders)
848+
# Reset the buffer to zero for all vertices in a tree (except the root)
849+
for (vertex, _) in reverse_bfs_orders[k]
850+
buffer_right_type[vertex] = zero(R)
851+
end
852+
# Reset the buffer to zero for the root vertex
853+
(_, root) = reverse_bfs_orders[k][end]
854+
buffer_right_type[root] = zero(R)
855+
856+
for (i, j) in reverse_bfs_orders[k]
857+
counter += 1
858+
cj = symmetric_color[j]
859+
860+
#! format: off
861+
# A[i,j] is in the lower triangular part of A
862+
if in_triangle(i, j, :L)
863+
val = Bc[i - n, symmetric_to_column[cj]] - buffer_right_type[i]
864+
buffer_right_type[j] = buffer_right_type[j] + val
865+
866+
# A[i,j] is stored at index_ij = A_indices[counter] in A.nzval
867+
nzind = A_indices[counter]
868+
nzA[nzind] = val
869+
870+
# A[i,j] is in the upper triangular part of A
871+
else
872+
val = Br[symmetric_to_row[cj], i] - buffer_right_type[i]
873+
buffer_right_type[j] = buffer_right_type[j] + val
874+
875+
# A[j,i] is stored at index_ji = A_indices[counter] in A.nzval
876+
nzind = A_indices[counter]
877+
nzA[nzind] = val
878+
end
879+
#! format: on
880+
end
881+
end
838882
return A
839883
end

src/interface.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,7 @@ function _coloring(
328328
ag, algo.order; postprocessing=algo.postprocessing
329329
)
330330
if speed_setting isa WithResult
331-
symmetric_result = TreeSetColoringResult(A_and_Aᵀ, ag, symmetric_color, tree_set, R)
332-
return TreeSetBicoloringResult(A, ag, symmetric_result)
331+
return TreeSetBicoloringResult(A, ag, symmetric_color, tree_set, R)
333332
else
334333
row_color, column_color, _ = remap_colors(
335334
symmetric_color, maximum(symmetric_color), size(A)...

src/result.jl

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ function TreeSetColoringResult(
292292
color::Vector{Int},
293293
tree_set::TreeSet,
294294
decompression_eltype::Type{R},
295-
) where {R}
295+
) where {R<:Real}
296296
(; reverse_bfs_orders) = tree_set
297297
(; S) = ag
298298
nvertices = length(color)
@@ -596,7 +596,6 @@ function StarSetBicoloringResult(
596596
end
597597
end
598598
end
599-
@assert pos_Bc - pos_Br == 1
600599

601600
return StarSetBicoloringResult(
602601
A,
@@ -628,16 +627,14 @@ $TYPEDFIELDS
628627
629628
- [`AbstractColoringResult`](@ref)
630629
"""
631-
struct TreeSetBicoloringResult{
632-
M<:AbstractMatrix,
633-
G<:AdjacencyGraph,
634-
V,
635-
SR<:AbstractColoringResult{:symmetric,:column,:substitution},
636-
} <: AbstractColoringResult{:nonsymmetric,:bidirectional,:substitution}
630+
struct TreeSetBicoloringResult{M<:AbstractMatrix,G<:AdjacencyGraph,V,R} <:
631+
AbstractColoringResult{:nonsymmetric,:bidirectional,:substitution}
637632
"matrix that was colored"
638633
A::M
639634
"augmented adjacency graph that was used for bicoloring"
640635
ag::G
636+
"one integer color for each axis of the augmented matrix"
637+
symmetric_color::Vector{Int}
641638
"one integer color for each column"
642639
column_color::Vector{Int}
643640
"one integer color for each row"
@@ -646,45 +643,80 @@ struct TreeSetBicoloringResult{
646643
column_group::V
647644
"color groups for rows"
648645
row_group::V
649-
"result for the coloring of the symmetric 2 x 2 block matrix"
650-
symmetric_result::SR
651646
"maps symmetric colors to column colors"
652647
symmetric_to_column::Vector{Int}
653648
"maps symmetric colors to row colors"
654649
symmetric_to_row::Vector{Int}
655-
"CSC storage of `A_and_noAᵀ - `colptr`"
656-
large_colptr::Vector{Int}
657-
"CSC storage of `A_and_noAᵀ - `rowval`"
658-
large_rowval::Vector{Int}
650+
"indices of the nonzeros in A that can recovered with Br and Bc"
651+
A_indices::Vector{Int}
652+
"reverse BFS orders of the trees"
653+
reverse_bfs_orders::Vector{Vector{Tuple{Int,Int}}}
654+
"buffer needed during acyclic decompression"
655+
buffer::Vector{R}
659656
end
660657

661658
function TreeSetBicoloringResult(
662659
A::AbstractMatrix,
663660
ag::AdjacencyGraph,
664-
symmetric_result::AbstractColoringResult{:symmetric,:column,:substitution},
665-
)
661+
symmetric_color::Vector{Int},
662+
tree_set::TreeSet,
663+
decompression_eltype::Type{R},
664+
) where {R<:Real}
665+
(; reverse_bfs_orders) = tree_set
666+
666667
m, n = size(A)
667-
symmetric_color = column_colors(symmetric_result)
668668
num_sym_colors = maximum(symmetric_color)
669669
row_color, column_color, symmetric_to_row, symmetric_to_column = remap_colors(
670670
symmetric_color, num_sym_colors, m, n
671671
)
672672
column_group = group_by_color(column_color)
673673
row_group = group_by_color(row_color)
674-
large_colptr = copy(ag.S.colptr)
675-
large_colptr[(n + 2):end] .= large_colptr[n + 1] # last few columns are empty
676-
large_rowval = ag.S.rowval[1:(end ÷ 2)] # forget the second half of nonzeros
674+
675+
S = ag.S
676+
rv = rowvals(S)
677+
nnzA = nnz(S) ÷ 2
678+
A_indices = Vector{Int}(undef, nnzA)
679+
680+
index = 0
681+
for k in eachindex(reverse_bfs_orders)
682+
for (i, j) in reverse_bfs_orders[k]
683+
index += 1
684+
685+
#! format: off
686+
# S[i,j] is in the lower triangular part of S
687+
if in_triangle(i, j, :L)
688+
# S[i,j] is stored at index_ij = (S.colptr[j] + offset) in S.nzval
689+
col_j = view(rv, nzrange(S, j))
690+
offset = searchsortedfirst(col_j, i)::Int - 1
691+
A_indices[index] = S.colptr[j] + offset
692+
693+
# S[i,j] is in the upper triangular part of S
694+
else
695+
# S[j,i] is stored at index_ji = (S.colptr[i] + offset) in S.nzval
696+
col_i = view(rv, nzrange(S, i))
697+
offset = searchsortedfirst(col_i, j)::Int - 1
698+
A_indices[index] = S.colptr[i] + offset
699+
end
700+
#! format: on
701+
end
702+
end
703+
704+
# buffer holds the sum of edge values for subtrees in a tree.
705+
# For each vertex i, buffer[i] is the sum of edge values in the subtree rooted at i.
706+
buffer = Vector{R}(undef, n + m)
707+
677708
return TreeSetBicoloringResult(
678709
A,
679710
ag,
711+
symmetric_color,
680712
column_color,
681713
row_color,
682714
column_group,
683715
row_group,
684-
symmetric_result,
685716
symmetric_to_column,
686717
symmetric_to_row,
687-
large_colptr,
688-
large_rowval,
718+
A_indices,
719+
reverse_bfs_orders,
720+
buffer,
689721
)
690722
end

test/allocations.jl

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,7 @@ function test_noallocs_sparse_decompression(
3535
bench1_full = @be similar(A) decompress!(_, Br, Bc, result) evals = 1
3636
bench2_full = @be similar(Matrix(A)) decompress!(_, Br, Bc, result) evals = 1
3737
@test minimum(bench1_full).allocs == 0
38-
if decompression == :direct
39-
@test minimum(bench2_full).allocs == 0
40-
else
41-
@test_broken minimum(bench2_full).allocs == 0
42-
end
38+
@test minimum(bench2_full).allocs == 0
4339
end
4440
else
4541
B = compress(A, result)

0 commit comments

Comments
 (0)