Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docs/src/dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ SparseMatrixColorings.valid_dynamic_order
```@docs
SparseMatrixColorings.respectful_similar
SparseMatrixColorings.matrix_versions
SparseMatrixColorings.same_pattern
```

## Visualization
Expand Down
4 changes: 2 additions & 2 deletions src/adtypes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function coloring(
)
bg = BipartiteGraph(A)
color = convert(Vector{eltype(bg)}, ADTypes.column_coloring(A, algo))
return ColumnColoringResult(A, bg, color)
return ColumnColoringResult(A, bg, color; allow_denser=false)
end

function coloring(
Expand All @@ -17,5 +17,5 @@ function coloring(
)
bg = BipartiteGraph(A)
color = convert(Vector{eltype(bg)}, ADTypes.row_coloring(A, algo))
return RowColoringResult(A, bg, color)
return RowColoringResult(A, bg, color; allow_denser=false)
end
40 changes: 29 additions & 11 deletions src/constant.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,22 @@ Indeed, for symmetric coloring problems, we need more than just the vector of co

# Constructors

ConstantColoringAlgorithm{partition}(matrix_template, color)
ConstantColoringAlgorithm(matrix_template, color; partition=:column)

ConstantColoringAlgorithm{partition}(
matrix_template,
color;
allow_denser=false
)

ConstantColoringAlgorithm(
matrix_template, color;
partition=:column,
allow_denser=false
)

- `partition::Symbol`: either `:row` or `:column`.
- `matrix_template::AbstractMatrix`: matrix for which the vector of colors was precomputed (the algorithm will only accept matrices of the exact same size).
- `color::Vector{<:Integer}`: vector of integer colors, one for each row or column (depending on `partition`).
- `allow_denser::Bool`: whether or not to allow decompression into a `SparseMatrixCSC` which has more nonzeros than the original sparsity pattern (see [`decompress!`](@ref) to know when this is implemented).

!!! warning
The second constructor (based on keyword arguments) is type-unstable.
Expand Down Expand Up @@ -73,30 +83,38 @@ struct ConstantColoringAlgorithm{
matrix_template::M
color::Vector{T}
result::R
allow_denser::Bool
end

function ConstantColoringAlgorithm{:column}(
matrix_template::AbstractMatrix, color::Vector{<:Integer}
matrix_template::AbstractMatrix, color::Vector{<:Integer}; allow_denser::Bool=false
)
bg = BipartiteGraph(matrix_template)
result = ColumnColoringResult(matrix_template, bg, color)
result = ColumnColoringResult(matrix_template, bg, color; allow_denser)
T, M, R = eltype(bg), typeof(matrix_template), typeof(result)
return ConstantColoringAlgorithm{:column,M,T,R}(matrix_template, color, result)
return ConstantColoringAlgorithm{:column,M,T,R}(
matrix_template, color, result, allow_denser
)
end

function ConstantColoringAlgorithm{:row}(
matrix_template::AbstractMatrix, color::Vector{<:Integer}
matrix_template::AbstractMatrix, color::Vector{<:Integer}; allow_denser::Bool=false
)
bg = BipartiteGraph(matrix_template)
result = RowColoringResult(matrix_template, bg, color)
result = RowColoringResult(matrix_template, bg, color; allow_denser)
T, M, R = eltype(bg), typeof(matrix_template), typeof(result)
return ConstantColoringAlgorithm{:row,M,T,R}(matrix_template, color, result)
return ConstantColoringAlgorithm{:row,M,T,R}(
matrix_template, color, result, allow_denser
)
end

function ConstantColoringAlgorithm(
matrix_template::AbstractMatrix, color::Vector{<:Integer}; partition::Symbol=:column
matrix_template::AbstractMatrix,
color::Vector{<:Integer};
partition::Symbol=:column,
allow_denser::Bool=false,
)
return ConstantColoringAlgorithm{partition}(matrix_template, color)
return ConstantColoringAlgorithm{partition}(matrix_template, color; allow_denser)
end

function coloring(
Expand Down
120 changes: 105 additions & 15 deletions src/decompression.jl
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,21 @@ The out-of-place alternative is [`decompress`](@ref).
Compression means summing either the columns or the rows of `A` which share the same color.
It is done by calling [`compress`](@ref).

# Details

For `:symmetric` coloring results (and for those only), an optional positional argument `uplo in (:U, :L, :F)` can be passed to specify which part of the matrix `A` should be updated: the Upper triangle, the Lower triangle, or the Full matrix.
When `A isa SparseMatrixCSC`, using the `uplo` argument requires a target matrix which only stores the relevant triangle(s).

Some coloring algorithms ([`GreedyColoringAlgorithm`](@ref) and [`ConstantColoringAlgorithm`](@ref)) have an option called `allow_denser`, which enables in-place decompression into a `SparseMatrixCSC` containing more structural nonzeros than the sparsity pattern used for coloring.

!!! warning
Decompression into a denser `SparseMatrixCSC` is only implemented when all the conditions below are satisfied simultaneously:
- the partition is `:row` or `:column` (not `:bidirectional`)
- the decompression is `:direct` (not `:substitution`)
- the decompression is full (so [`decompress_single_color!`](@ref) will not work)
Comment thread
gdalle marked this conversation as resolved.
Outdated

Outside of these cases, it is up to the user to make sure that the sparsity pattern of the decompression target is an exact match.

# Example

```jldoctest
Expand All @@ -210,6 +222,8 @@ julia> A = sparse([

julia> result = coloring(A, ColoringProblem(), GreedyColoringAlgorithm());

julia> result_tolerant = coloring(A, ColoringProblem(), GreedyColoringAlgorithm(; allow_denser=true));

julia> collect.(column_groups(result))
3-element Vector{Vector{Int64}}:
[1, 2, 4]
Expand All @@ -225,6 +239,8 @@ julia> B = compress(A, result)

julia> A2 = similar(A);

julia> A3 = similar(A); A3[1, 1] = A3[end, end] = 1;

julia> decompress!(A2, B, result)
4×6 SparseMatrixCSC{Int64, Int64} with 9 stored entries:
⋅ ⋅ 4 6 ⋅ 9
Expand All @@ -234,6 +250,16 @@ julia> decompress!(A2, B, result)

julia> A2 == A
true

julia> decompress!(A3, B, result_tolerant)
4×6 SparseMatrixCSC{Int64, Int64} with 11 stored entries:
0 ⋅ 4 6 ⋅ 9
1 ⋅ ⋅ ⋅ 7 ⋅
⋅ 2 ⋅ ⋅ 8 ⋅
⋅ 3 5 ⋅ ⋅ 0

julia> A3 == A
true
```

# See also
Expand All @@ -258,6 +284,8 @@ Decompress the vector `b` corresponding to color `c` in-place into `A`, given a
!!! warning
This function will only update some coefficients of `A`, without resetting the rest to zero.

# Details

For `:symmetric` coloring results (and for those only), an optional positional argument `uplo in (:U, :L, :F)` can be passed to specify which part of the matrix `A` should be updated: the Upper triangle, the Lower triangle, or the Full matrix.
When `A isa SparseMatrixCSC`, using the `uplo` argument requires a target matrix which only stores the relevant triangle(s).

Expand Down Expand Up @@ -354,12 +382,28 @@ function decompress_single_color!(
end

function decompress!(A::SparseMatrixCSC, B::AbstractMatrix, result::ColumnColoringResult)
(; compressed_indices) = result
(; compressed_indices, allow_denser) = result
S = result.bg.S2
check_same_pattern(A, S)
check_same_pattern(A, S; allow_denser)
nzA = nonzeros(A)
for k in eachindex(nzA, compressed_indices)
nzA[k] = B[compressed_indices[k]]
if nnz(A) == nnz(S)
for k in eachindex(compressed_indices)
nzA[k] = B[compressed_indices[k]]
end
else # nnz(A) > nnz(S)
rvA, rvS = rowvals(A), rowvals(S)
shift = 0
for j in axes(S, 2)
for k in nzrange(S, j)
i = rvS[k]
while (k + shift) < A.colptr[j] || rvA[k + shift] < i
Comment thread
gdalle marked this conversation as resolved.
nzA[k + shift] = zero(eltype(A))
Comment thread
gdalle marked this conversation as resolved.
shift += 1
end
nzA[k + shift] = B[compressed_indices[k]]
end
end
nzA[(nnz(S) + shift + 1):end] .= zero(eltype(A))
end
return A
end
Expand Down Expand Up @@ -416,12 +460,28 @@ function decompress_single_color!(
end

function decompress!(A::SparseMatrixCSC, B::AbstractMatrix, result::RowColoringResult)
(; compressed_indices) = result
(; compressed_indices, allow_denser) = result
S = result.bg.S2
check_same_pattern(A, S)
check_same_pattern(A, S; allow_denser)
nzA = nonzeros(A)
for k in eachindex(nzA, compressed_indices)
nzA[k] = B[compressed_indices[k]]
if nnz(A) == nnz(S)
for k in eachindex(nzA, compressed_indices)
nzA[k] = B[compressed_indices[k]]
end
else # nnz(A) > nnz(S)
rvA, rvS = rowvals(A), rowvals(S)
shift = 0
for j in axes(S, 2)
for k in nzrange(S, j)
i = rvS[k]
while (k + shift) < A.colptr[j] || rvA[k + shift] < i
Comment thread
gdalle marked this conversation as resolved.
nzA[k + shift] = zero(eltype(A))
shift += 1
end
nzA[k + shift] = B[compressed_indices[k]]
end
end
nzA[(nnz(S) + shift + 1):end] .= zero(eltype(A))
end
return A
end
Expand Down Expand Up @@ -488,25 +548,54 @@ end
function decompress!(
A::SparseMatrixCSC, B::AbstractMatrix, result::StarSetColoringResult, uplo::Symbol=:F
)
(; ag, compressed_indices) = result
(; ag, compressed_indices, allow_denser) = result
(; S) = ag
nzA = nonzeros(A)
if uplo == :F
check_same_pattern(A, S)
for k in eachindex(nzA, compressed_indices)
nzA[k] = B[compressed_indices[k]]
check_same_pattern(A, S; allow_denser)
if nnz(A) == nnz(S)
for k in eachindex(nzA, compressed_indices)
nzA[k] = B[compressed_indices[k]]
end
else # nnz(A) > nnz(S)
rvA, rvS = rowvals(A), rowvals(S)
shift = 0
for j in axes(S, 2)
for k in nzrange(S, j)
i = rvS[k]
while (k + shift) < A.colptr[j] || rvA[k + shift] < i
Comment thread
gdalle marked this conversation as resolved.
nzA[k + shift] = zero(eltype(A))
shift += 1
end
nzA[k + shift] = B[compressed_indices[k]]
end
end
nzA[(nnz(S) + shift + 1):end] .= zero(eltype(A))
end
else
rvS = rowvals(S)
rvA, rvS = rowvals(A), rowvals(S)
l = 0 # assume A has the same pattern as the triangle
shift = 0
for j in axes(S, 2)
for k in nzrange(S, j)
i = rvS[k]
if in_triangle(i, j, uplo)
l += 1
nzA[l] = B[compressed_indices[k]]
while (l + shift) < A.colptr[j] || rvA[l + shift] < i
Comment thread
gdalle marked this conversation as resolved.
nzA[l + shift] = zero(eltype(A))
shift += 1
end
nzA[l + shift] = B[compressed_indices[k]]
end
end
nzA[(l + shift + 1):end] .= zero(eltype(A))
if !allow_denser && shift > 0
throw(
DimensionMismatch(
"Decompression target must have exactly as many nonzeros as the sparsity pattern used for coloring",
),
)
end
end
end
return A
Expand Down Expand Up @@ -749,7 +838,8 @@ end
function decompress!(
A::SparseMatrixCSC, Br::AbstractMatrix, Bc::AbstractMatrix, result::BicoloringResult
)
(; large_colptr, large_rowval, symmetric_result) = result
(; S, large_colptr, large_rowval, symmetric_result) = result
check_same_pattern(A, S)
m, n = size(A)
Br_and_Bc = _join_compressed!(result, Br, Bc)
# pretend A is larger
Expand Down
8 changes: 5 additions & 3 deletions src/graph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,10 @@ end

Return a [`SparsityPatternCSC`](@ref) corresponding to the matrix `[0 Aᵀ; A 0]`, with a minimum of allocations.
"""
bidirectional_pattern(A::AbstractMatrix; symmetric_pattern::Bool) =
bidirectional_pattern(SparsityPatternCSC(SparseMatrixCSC(A)); symmetric_pattern)
function bidirectional_pattern(A::AbstractMatrix; symmetric_pattern::Bool)
S = SparsityPatternCSC(SparseMatrixCSC(A))
return bidirectional_pattern(S; symmetric_pattern)
end

function bidirectional_pattern(S::SparsityPatternCSC{T}; symmetric_pattern::Bool) where {T}
m, n = size(S)
Expand Down Expand Up @@ -171,7 +173,7 @@ function bidirectional_pattern(S::SparsityPatternCSC{T}; symmetric_pattern::Bool

# Create the SparsityPatternCSC of the augmented adjacency matrix
S_and_Sᵀ = SparsityPatternCSC{T}(p, p, colptr, rowval)
return S_and_Sᵀ, edge_to_index
return S, S_and_Sᵀ, edge_to_index
end

function build_edge_to_index(S::SparsityPatternCSC{T}) where {T}
Expand Down
Loading