From 703246c18a881db64573e460377dd6ecc4376c33 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Wed, 2 Apr 2025 16:53:19 +0200 Subject: [PATCH 1/7] feat: allow full direct decompression into larger SparseMatrixCSC --- src/decompression.jl | 65 ++++++++++++++++++++++++++++++++++++++------ src/matrices.jl | 35 ++++++++++++------------ test/matrices.jl | 3 ++ test/utils.jl | 12 ++++++++ 4 files changed, 88 insertions(+), 27 deletions(-) diff --git a/src/decompression.jl b/src/decompression.jl index 64e3c517..4604a2f0 100644 --- a/src/decompression.jl +++ b/src/decompression.jl @@ -189,6 +189,8 @@ The out-of-place alternative is [`decompress`](@ref). !!! note In-place decompression is faster when `A isa SparseMatrixCSC`. + - In general, this case requires the sparsity pattern of `A` to match the sparsity pattern `S` from which the coloring result was computed. + - For a coloring result with `decompression=:direct`, we also allow _full_ decompression into an `A` whose sparsity pattern is a strict superset of `S`. Compression means summing either the columns or the rows of `A` which share the same color. It is done by calling [`compress`](@ref). @@ -356,10 +358,25 @@ end function decompress!(A::SparseMatrixCSC, B::AbstractMatrix, result::ColumnColoringResult) (; compressed_indices) = result S = result.bg.S2 - check_same_pattern(A, S) + check_same_pattern(A, S; allow_superset=true) 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(Z) + fill!(nonzeros(A), zero(eltype(A))) + 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 + shift += 1 + end + nzA[k + shift] = B[compressed_indices[k]] + end + end end return A end @@ -418,10 +435,25 @@ end function decompress!(A::SparseMatrixCSC, B::AbstractMatrix, result::RowColoringResult) (; compressed_indices) = result S = result.bg.S2 - check_same_pattern(A, S) + check_same_pattern(A, S; allow_superset=true) 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) + fill!(nonzeros(A), zero(eltype(A))) + 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 + shift += 1 + end + nzA[k + shift] = B[compressed_indices[k]] + end + end end return A end @@ -492,9 +524,24 @@ function decompress!( (; 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_superset=true) + if nnz(A) == nnz(S) + for k in eachindex(nzA, compressed_indices) + nzA[k] = B[compressed_indices[k]] + end + else # nnz(A) > nnz(S) + fill!(nonzeros(A), zero(eltype(A))) + 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 + shift += 1 + end + nzA[k + shift] = B[compressed_indices[k]] + end + end end else rvS = rowvals(S) diff --git a/src/matrices.jl b/src/matrices.jl index 795983c8..66f7d189 100644 --- a/src/matrices.jl +++ b/src/matrices.jl @@ -61,25 +61,24 @@ function respectful_similar(A::Union{Symmetric,Hermitian}, ::Type{T}) where {T} return respectful_similar(sparse(A), T) end -""" - same_pattern(A, B) - -Perform a partial equality check on the sparsity patterns of `A` and `B`: - -- if the return is `true`, they might have the same sparsity pattern but we're not sure -- if the return is `false`, they definitely don't have the same sparsity pattern -""" -same_pattern(A, B) = size(A) == size(B) - -function same_pattern( - A::Union{SparseMatrixCSC,SparsityPatternCSC}, - B::Union{SparseMatrixCSC,SparsityPatternCSC}, -) - return size(A) == size(B) && nnz(A) == nnz(B) +same_pattern(A::AbstractMatrix, S; allow_superset::Bool=false) = true +function same_pattern(A::SparseMatrixCSC, S; allow_superset::Bool=false) + return allow_superset ? nnz(A) >= nnz(S) : nnz(A) == nnz(S) end -function check_same_pattern(A, S) - if !same_pattern(A, S) - throw(DimensionMismatch("`A` and `S` must have the same sparsity pattern.")) +function check_same_pattern(A, S; allow_superset::Bool=false) + if size(A) != size(S) + throw( + DimensionMismatch( + "Decompression target must have the same size as sparsity pattern" + ), + ) + elseif !same_pattern(A, S; allow_superset) + throw( + DimensionMismatch( + """Decompression target must $(allow_superset ? "contain the nonzeros of the sparsity pattern" : "be equal to the sparsity pattern") used for coloring""", + ), + ) end + return true end diff --git a/test/matrices.jl b/test/matrices.jl index 1571497b..83764bd1 100644 --- a/test/matrices.jl +++ b/test/matrices.jl @@ -48,5 +48,8 @@ end @test !same_pattern(A2, S) @test same_pattern(Matrix(A2), S) + @test_throws DimensionMismatch check_same_pattern(vcat(A1, A1), S) @test_throws DimensionMismatch check_same_pattern(A2, S) + @test check_same_pattern(A1, S; allow_superset=true) + @test check_same_pattern(A2, S; allow_superset=true) end diff --git a/test/utils.jl b/test/utils.jl index fb3d42c5..38dadd29 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -86,6 +86,18 @@ function test_coloring_decompression( @test decompress(B, result) ≈ A0 # check result wasn't modified @test decompress!(respectful_similar(A, eltype(B)), B, result) ≈ A0 @test decompress!(respectful_similar(A, eltype(B)), B, result) ≈ A0 + if decompression == :direct && A isa SparseMatrixCSC + A_bigger = respectful_similar(A, eltype(B)) + for _ in 1:10 + nb_coeffs_added = rand(1:minimum(size(A))) + for _ in nb_coeffs_added + i = rand(axes(A, 1)) + j = rand(axes(A, 2)) + A_bigger[i, j] = one(eltype(B)) + end + @test decompress!(A_bigger, B, result) ≈ A0 + end + end end @testset "Single-color decompression" begin From ca93cbce9713b06794c3c921cd5ab18428ea0736 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Wed, 2 Apr 2025 17:09:24 +0200 Subject: [PATCH 2/7] Docs --- docs/src/dev.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/src/dev.md b/docs/src/dev.md index 2f253dbe..337182ad 100644 --- a/docs/src/dev.md +++ b/docs/src/dev.md @@ -58,7 +58,6 @@ SparseMatrixColorings.valid_dynamic_order ```@docs SparseMatrixColorings.respectful_similar SparseMatrixColorings.matrix_versions -SparseMatrixColorings.same_pattern ``` ## Visualization From 3911136a07d23a5beee9ad2519887dfef1945380 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Thu, 3 Apr 2025 10:32:57 +0200 Subject: [PATCH 3/7] Add `allow_denser` as option to the algorithm --- src/adtypes.jl | 4 +-- src/constant.jl | 40 +++++++++++++++++------- src/decompression.jl | 73 +++++++++++++++++++++++++++++++++++--------- src/graph.jl | 8 +++-- src/interface.jl | 59 +++++++++++++++++++++++------------ src/matrices.jl | 24 ++++++--------- src/result.jl | 23 ++++++++++---- test/constant.jl | 63 ++++++++++++++++++++++++++------------ test/graph.jl | 8 +++-- test/matrices.jl | 4 +-- test/random.jl | 12 +++++--- test/small.jl | 47 ++++++++++++++++++++++++++++ test/utils.jl | 27 +++++++++++----- 13 files changed, 287 insertions(+), 105 deletions(-) diff --git a/src/adtypes.jl b/src/adtypes.jl index 7500b1e2..47fae343 100644 --- a/src/adtypes.jl +++ b/src/adtypes.jl @@ -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( @@ -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 diff --git a/src/constant.jl b/src/constant.jl index d79caab0..ffb582b1 100644 --- a/src/constant.jl +++ b/src/constant.jl @@ -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. @@ -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( diff --git a/src/decompression.jl b/src/decompression.jl index 4604a2f0..6e72747c 100644 --- a/src/decompression.jl +++ b/src/decompression.jl @@ -189,15 +189,25 @@ The out-of-place alternative is [`decompress`](@ref). !!! note In-place decompression is faster when `A isa SparseMatrixCSC`. - - In general, this case requires the sparsity pattern of `A` to match the sparsity pattern `S` from which the coloring result was computed. - - For a coloring result with `decompression=:direct`, we also allow _full_ decompression into an `A` whose sparsity pattern is a strict superset of `S`. 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) + + 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 @@ -212,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] @@ -227,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 @@ -236,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 @@ -260,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). @@ -356,27 +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; allow_superset=true) + check_same_pattern(A, S; allow_denser) nzA = nonzeros(A) if nnz(A) == nnz(S) for k in eachindex(compressed_indices) nzA[k] = B[compressed_indices[k]] end - else # nnz(A) > nnz(Z) - fill!(nonzeros(A), zero(eltype(A))) + 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 + 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 @@ -433,27 +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; allow_superset=true) + check_same_pattern(A, S; allow_denser) nzA = nonzeros(A) if nnz(A) == nnz(S) for k in eachindex(nzA, compressed_indices) nzA[k] = B[compressed_indices[k]] end else # nnz(A) > nnz(S) - fill!(nonzeros(A), zero(eltype(A))) 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 + 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 @@ -520,40 +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; allow_superset=true) + 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) - fill!(nonzeros(A), zero(eltype(A))) 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 + 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 + 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 @@ -796,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 diff --git a/src/graph.jl b/src/graph.jl index 7f3863e3..31448680 100644 --- a/src/graph.jl +++ b/src/graph.jl @@ -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) @@ -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} diff --git a/src/interface.jl b/src/interface.jl index f54b5f73..6e244ee9 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -69,12 +69,23 @@ It is passed as an argument to the main function [`coloring`](@ref). # Constructors - GreedyColoringAlgorithm{decompression}(order=NaturalOrder(); postprocessing=false) - GreedyColoringAlgorithm(order=NaturalOrder(); postprocessing=false, decompression=:direct) + GreedyColoringAlgorithm{decompression}( + order=NaturalOrder(); + postprocessing=false, + allow_denser=false, + ) + + GreedyColoringAlgorithm( + order=NaturalOrder(); + decompression=:direct + postprocessing=false, + allow_denser=false, + ) - `order::AbstractOrder`: the order in which the columns or rows are colored, which can impact the number of colors. -- `postprocessing::Bool`: whether or not the coloring will be refined by assigning the neutral color `0` to some vertices. - `decompression::Symbol`: either `:direct` or `:substitution`. Usually `:substitution` leads to fewer colors, at the cost of a more expensive coloring (and decompression). When `:substitution` is not applicable, it falls back on `:direct` decompression. +- `postprocessing::Bool`: whether or not the coloring will be refined by assigning the neutral color `0` to some vertices. +- `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. @@ -92,28 +103,36 @@ See their respective docstrings for details. # See also - [`AbstractOrder`](@ref) -- [`decompress`](@ref) +- [`decompress`](@ref), [`decompress!`](@ref) """ struct GreedyColoringAlgorithm{decompression,O<:AbstractOrder} <: ADTypes.AbstractColoringAlgorithm order::O postprocessing::Bool + allow_denser::Bool end function GreedyColoringAlgorithm{decompression}( - order::AbstractOrder=NaturalOrder(); postprocessing::Bool=false + order::AbstractOrder=NaturalOrder(); + postprocessing::Bool=false, + allow_denser::Bool=false, ) where {decompression} check_valid_algorithm(decompression) - return GreedyColoringAlgorithm{decompression,typeof(order)}(order, postprocessing) + return GreedyColoringAlgorithm{decompression,typeof(order)}( + order, postprocessing, allow_denser + ) end function GreedyColoringAlgorithm( order::AbstractOrder=NaturalOrder(); - postprocessing::Bool=false, decompression::Symbol=:direct, + postprocessing::Bool=false, + allow_denser::Bool=false, ) check_valid_algorithm(decompression) - return GreedyColoringAlgorithm{decompression,typeof(order)}(order, postprocessing) + return GreedyColoringAlgorithm{decompression,typeof(order)}( + order, postprocessing, allow_denser + ) end ## Coloring @@ -232,7 +251,7 @@ function _coloring( ) color = partial_distance2_coloring(bg, Val(2), algo.order) if speed_setting isa WithResult - return ColumnColoringResult(A, bg, color) + return ColumnColoringResult(A, bg, color; allow_denser=algo.allow_denser) else return color end @@ -251,7 +270,7 @@ function _coloring( ) color = partial_distance2_coloring(bg, Val(1), algo.order) if speed_setting isa WithResult - return RowColoringResult(A, bg, color) + return RowColoringResult(A, bg, color; allow_denser=algo.allow_denser) else return color end @@ -268,7 +287,7 @@ function _coloring( ag = AdjacencyGraph(A; has_diagonal=true) color, star_set = star_coloring(ag, algo.order, algo.postprocessing) if speed_setting isa WithResult - return StarSetColoringResult(A, ag, color, star_set) + return StarSetColoringResult(A, ag, color, star_set; allow_denser=algo.allow_denser) else return color end @@ -299,12 +318,14 @@ function _coloring( decompression_eltype::Type{R}, symmetric_pattern::Bool, ) where {R} - A_and_Aᵀ, edge_to_index = bidirectional_pattern(A; symmetric_pattern) - ag = AdjacencyGraph(A_and_Aᵀ, edge_to_index; has_diagonal=false) + S, S_and_Sᵀ, edge_to_index = bidirectional_pattern(A; symmetric_pattern) + ag = AdjacencyGraph(S_and_Sᵀ, edge_to_index; has_diagonal=false) color, star_set = star_coloring(ag, algo.order, algo.postprocessing) if speed_setting isa WithResult - symmetric_result = StarSetColoringResult(A_and_Aᵀ, ag, color, star_set) - return BicoloringResult(A, ag, symmetric_result, R) + symmetric_result = StarSetColoringResult( + S_and_Sᵀ, ag, color, star_set; allow_denser=false + ) + return BicoloringResult(A, S, ag, symmetric_result, R) else row_color, column_color, _ = remap_colors( eltype(ag), color, maximum(color), size(A)... @@ -321,12 +342,12 @@ function _coloring( decompression_eltype::Type{R}, symmetric_pattern::Bool, ) where {R} - A_and_Aᵀ, edge_to_index = bidirectional_pattern(A; symmetric_pattern) - ag = AdjacencyGraph(A_and_Aᵀ, edge_to_index; has_diagonal=false) + S, S_and_Sᵀ, edge_to_index = bidirectional_pattern(A; symmetric_pattern) + ag = AdjacencyGraph(S_and_Sᵀ, edge_to_index; has_diagonal=false) color, tree_set = acyclic_coloring(ag, algo.order, algo.postprocessing) if speed_setting isa WithResult - symmetric_result = TreeSetColoringResult(A_and_Aᵀ, ag, color, tree_set, R) - return BicoloringResult(A, ag, symmetric_result, R) + symmetric_result = TreeSetColoringResult(S_and_Sᵀ, ag, color, tree_set, R) + return BicoloringResult(A, S, ag, symmetric_result, R) else row_color, column_color, _ = remap_colors( eltype(ag), color, maximum(color), size(A)... diff --git a/src/matrices.jl b/src/matrices.jl index 66f7d189..71d8c938 100644 --- a/src/matrices.jl +++ b/src/matrices.jl @@ -61,24 +61,18 @@ function respectful_similar(A::Union{Symmetric,Hermitian}, ::Type{T}) where {T} return respectful_similar(sparse(A), T) end -same_pattern(A::AbstractMatrix, S; allow_superset::Bool=false) = true -function same_pattern(A::SparseMatrixCSC, S; allow_superset::Bool=false) - return allow_superset ? nnz(A) >= nnz(S) : nnz(A) == nnz(S) +same_pattern(A::AbstractMatrix, S; allow_denser::Bool=false) = true +function same_pattern(A::SparseMatrixCSC, S; allow_denser::Bool=false) + return allow_denser ? nnz(A) >= nnz(S) : nnz(A) == nnz(S) end -function check_same_pattern(A, S; allow_superset::Bool=false) +function check_same_pattern(A, S; allow_denser::Bool=false) if size(A) != size(S) - throw( - DimensionMismatch( - "Decompression target must have the same size as sparsity pattern" - ), - ) - elseif !same_pattern(A, S; allow_superset) - throw( - DimensionMismatch( - """Decompression target must $(allow_superset ? "contain the nonzeros of the sparsity pattern" : "be equal to the sparsity pattern") used for coloring""", - ), - ) + msg = "Decompression target must have the same size as the sparsity pattern used for coloring" + throw(DimensionMismatch(msg)) + elseif !same_pattern(A, S; allow_denser) + msg = """Decompression target must have $(allow_denser ? "at least" : "exactly") as many nonzeros as the sparsity pattern used for coloring""" + throw(DimensionMismatch(msg)) end return true end diff --git a/src/result.jl b/src/result.jl index b1ec9409..06473a59 100644 --- a/src/result.jl +++ b/src/result.jl @@ -158,10 +158,12 @@ struct ColumnColoringResult{ group::GT "flattened indices mapping the compressed matrix `B` to the uncompressed matrix `A` when `A isa SparseMatrixCSC`. They satisfy `nonzeros(A)[k] = vec(B)[compressed_indices[k]]`" compressed_indices::Vector{T} + "whether to allow full decompression into a `SparseMatrixCSC` with a strictly larger sparsity pattern" + allow_denser::Bool end function ColumnColoringResult( - A::AbstractMatrix, bg::BipartiteGraph{T}, color::Vector{<:Integer} + A::AbstractMatrix, bg::BipartiteGraph{T}, color::Vector{<:Integer}; allow_denser::Bool ) where {T<:Integer} S = bg.S2 group = group_by_color(T, color) @@ -176,7 +178,7 @@ function ColumnColoringResult( compressed_indices[k] = (c - 1) * n + i end end - return ColumnColoringResult(A, bg, color, group, compressed_indices) + return ColumnColoringResult(A, bg, color, group, compressed_indices, allow_denser) end """ @@ -202,10 +204,11 @@ struct RowColoringResult{ color::Vector{T} group::GT compressed_indices::Vector{T} + allow_denser::Bool end function RowColoringResult( - A::AbstractMatrix, bg::BipartiteGraph{T}, color::Vector{<:Integer} + A::AbstractMatrix, bg::BipartiteGraph{T}, color::Vector{<:Integer}; allow_denser::Bool ) where {T<:Integer} S = bg.S2 group = group_by_color(T, color) @@ -220,7 +223,7 @@ function RowColoringResult( compressed_indices[k] = (j - 1) * C + c end end - return RowColoringResult(A, bg, color, group, compressed_indices) + return RowColoringResult(A, bg, color, group, compressed_indices, allow_denser) end """ @@ -247,13 +250,15 @@ struct StarSetColoringResult{ group::GT star_set::StarSet{T} compressed_indices::Vector{T} + allow_denser::Bool end function StarSetColoringResult( A::AbstractMatrix, ag::AdjacencyGraph{T}, color::Vector{<:Integer}, - star_set::StarSet{<:Integer}, + star_set::StarSet{<:Integer}; + allow_denser::Bool, ) where {T<:Integer} (; star, hub) = star_set S = pattern(ag) @@ -289,7 +294,9 @@ function StarSetColoringResult( end end - return StarSetColoringResult(A, ag, color, group, star_set, compressed_indices) + return StarSetColoringResult( + A, ag, color, group, star_set, compressed_indices, allow_denser + ) end """ @@ -574,6 +581,8 @@ struct BicoloringResult{ } <: AbstractColoringResult{:nonsymmetric,:bidirectional,decompression} "matrix that was colored" A::M + "sparsity pattern of `A`" + S::SparsityPatternCSC{T} "augmented adjacency graph that was used for bicoloring" abg::G "one integer color for each column" @@ -606,6 +615,7 @@ row_groups(result::BicoloringResult) = result.row_group function BicoloringResult( A::AbstractMatrix, + S::SparsityPatternCSC{T}, ag::AdjacencyGraph{T}, symmetric_result::AbstractColoringResult{:symmetric,:column}, decompression_eltype::Type{R}, @@ -624,6 +634,7 @@ function BicoloringResult( large_rowval = ag.S.rowval[1:(end ÷ 2)] # forget the second half of nonzeros return BicoloringResult( A, + S, ag, column_color, row_color, diff --git a/test/constant.jl b/test/constant.jl index 5de881b8..e3a4dce8 100644 --- a/test/constant.jl +++ b/test/constant.jl @@ -1,37 +1,62 @@ using ADTypes: ADTypes using SparseMatrixColorings +using StableRNGs using Test -matrix_template = ones(100, 200) +rng = StableRNG(63) + +A = sprand(rng, 100, 200, 0.05) @testset "Column coloring" begin problem = ColoringProblem(; structure=:nonsymmetric, partition=:column) - color = rand(1:5, size(matrix_template, 2)) - algo = ConstantColoringAlgorithm(matrix_template, color; partition=:column) - wrong_algo = ConstantColoringAlgorithm(matrix_template, color; partition=:row) - @test_throws DimensionMismatch coloring(transpose(matrix_template), problem, algo) - @test_throws MethodError coloring(matrix_template, problem, wrong_algo) - result = coloring(matrix_template, problem, algo) + color = fast_coloring(A, problem, GreedyColoringAlgorithm()) + algo = ConstantColoringAlgorithm(A, color; partition=:column) + algo_tolerant = ConstantColoringAlgorithm( + A, color; partition=:column, allow_denser=true + ) + wrong_algo = ConstantColoringAlgorithm(A, color; partition=:row) + @test_throws DimensionMismatch coloring(transpose(A), problem, algo) + @test_throws MethodError coloring(A, problem, wrong_algo) + result = coloring(A, problem, algo) + result_tolerant = coloring(A, problem, algo_tolerant) + B = compress(A, result) @test column_colors(result) == color - @test ADTypes.column_coloring(matrix_template, algo) == color - @test_throws MethodError ADTypes.row_coloring(matrix_template, algo) + @test ADTypes.column_coloring(A, algo) == color + @test_throws MethodError ADTypes.row_coloring(A, algo) + A2 = copy(A) + for added_coeff in 1:10 + i, j = rand(rng, axes(A, 1)), rand(rng, axes(A, 2)) + A2[i, j] = 1 + end + @test_throws DimensionMismatch decompress!(A2, B, result) + @test decompress!(A2, B, result_tolerant) == A end @testset "Row coloring" begin problem = ColoringProblem(; structure=:nonsymmetric, partition=:row) - color = rand(1:5, size(matrix_template, 1)) - algo = ConstantColoringAlgorithm(matrix_template, color; partition=:row) - @test_throws DimensionMismatch coloring(transpose(matrix_template), problem, algo) - result = coloring(matrix_template, problem, algo) + color = fast_coloring(A, problem, GreedyColoringAlgorithm()) + algo = ConstantColoringAlgorithm(A, color; partition=:row) + algo_tolerant = ConstantColoringAlgorithm(A, color; partition=:row, allow_denser=true) + @test_throws DimensionMismatch coloring(transpose(A), problem, algo) + result = coloring(A, problem, algo) + result_tolerant = coloring(A, problem, algo_tolerant) + B = compress(A, result) @test row_colors(result) == color - @test ADTypes.row_coloring(matrix_template, algo) == color - @test_throws MethodError ADTypes.column_coloring(matrix_template, algo) + @test ADTypes.row_coloring(A, algo) == color + @test_throws MethodError ADTypes.column_coloring(A, algo) + A2 = copy(A) + for added_coeff in 1:10 + i, j = rand(rng, axes(A, 1)), rand(rng, axes(A, 2)) + A2[i, j] = 1 + end + @test decompress!(A2, B, result_tolerant) == A + @test_throws DimensionMismatch decompress!(A2, B, result) end @testset "Symmetric coloring" begin wrong_problem = ColoringProblem(; structure=:symmetric, partition=:column) - color = rand(1:5, size(matrix_template, 2)) - algo = ConstantColoringAlgorithm(matrix_template, color; partition=:column) - @test_throws MethodError coloring(matrix_template, wrong_problem, algo) - @test_throws MethodError ADTypes.symmetric_coloring(matrix_template, algo) + color = ones(Int, size(A, 2)) + algo = ConstantColoringAlgorithm(A, color; partition=:column) + @test_throws MethodError coloring(A, wrong_problem, algo) + @test_throws MethodError ADTypes.symmetric_coloring(A, algo) end diff --git a/test/graph.jl b/test/graph.jl index 9c8f66c1..155ac079 100644 --- a/test/graph.jl +++ b/test/graph.jl @@ -35,7 +35,9 @@ using Test p = 0.05 * rand() A = sprand(Bool, m, n, p) A_and_Aᵀ = [spzeros(Bool, n, n) transpose(A); A spzeros(Bool, m, m)] - S_and_Sᵀ, edge_to_index = bidirectional_pattern(A; symmetric_pattern=false) + _, S_and_Sᵀ, edge_to_index = bidirectional_pattern( + A; symmetric_pattern=false + ) @test S_and_Sᵀ.colptr == A_and_Aᵀ.colptr @test S_and_Sᵀ.rowval == A_and_Aᵀ.rowval M = SparseMatrixCSC( @@ -50,7 +52,9 @@ using Test p = 0.05 * rand() A = sparse(Symmetric(sprand(Bool, m, m, p))) A_and_Aᵀ = [spzeros(Bool, m, m) transpose(A); A spzeros(Bool, m, m)] - S_and_Sᵀ, edge_to_index = bidirectional_pattern(A; symmetric_pattern=true) + _, S_and_Sᵀ, edge_to_index = bidirectional_pattern( + A; symmetric_pattern=true + ) @test S_and_Sᵀ.colptr == A_and_Aᵀ.colptr @test S_and_Sᵀ.rowval == A_and_Aᵀ.rowval M = SparseMatrixCSC( diff --git a/test/matrices.jl b/test/matrices.jl index 83764bd1..3cef744b 100644 --- a/test/matrices.jl +++ b/test/matrices.jl @@ -50,6 +50,6 @@ end @test_throws DimensionMismatch check_same_pattern(vcat(A1, A1), S) @test_throws DimensionMismatch check_same_pattern(A2, S) - @test check_same_pattern(A1, S; allow_superset=true) - @test check_same_pattern(A2, S; allow_superset=true) + @test check_same_pattern(A1, S; allow_denser=true) + @test check_same_pattern(A2, S; allow_denser=true) end diff --git a/test/random.jl b/test/random.jl index 02c1a3a1..06485c54 100644 --- a/test/random.jl +++ b/test/random.jl @@ -21,7 +21,7 @@ symmetric_params = vcat( @testset "Column coloring & decompression" begin problem = ColoringProblem(; structure=:nonsymmetric, partition=:column) - algo = GreedyColoringAlgorithm(; decompression=:direct) + algo = GreedyColoringAlgorithm(; decompression=:direct, allow_denser=true) @testset "$((; m, n, p))" for (m, n, p) in asymmetric_params A0 = sprand(rng, m, n, p) color0 = column_coloring(A0, algo) @@ -36,7 +36,7 @@ end; @testset "Row coloring & decompression" begin problem = ColoringProblem(; structure=:nonsymmetric, partition=:row) - algo = GreedyColoringAlgorithm(; decompression=:direct) + algo = GreedyColoringAlgorithm(; decompression=:direct, allow_denser=true) @testset "$((; m, n, p))" for (m, n, p) in asymmetric_params A0 = sprand(rng, m, n, p) color0 = row_coloring(A0, algo) @@ -52,8 +52,12 @@ end; @testset "Symmetric coloring & direct decompression" begin problem = ColoringProblem(; structure=:symmetric, partition=:column) @testset for algo in ( - GreedyColoringAlgorithm(; postprocessing=false, decompression=:direct), - GreedyColoringAlgorithm(; postprocessing=true, decompression=:direct), + GreedyColoringAlgorithm(; + postprocessing=false, decompression=:direct, allow_denser=true + ), + GreedyColoringAlgorithm(; + postprocessing=true, decompression=:direct, allow_denser=true + ), ) @testset "$((; n, p))" for (n, p) in symmetric_params A0 = sparse(Symmetric(sprand(rng, n, n, p))) diff --git a/test/small.jl b/test/small.jl index 2e12372c..c92b8468 100644 --- a/test/small.jl +++ b/test/small.jl @@ -1,3 +1,4 @@ +using LinearAlgebra using SparseArrays using SparseMatrixColorings using SparseMatrixColorings: @@ -196,3 +197,49 @@ end; ) end end; + +@testset verbose = true "Mismatched sparsity pattern during decompression" begin + A = sparse(Diagonal(ones(10))) + A_denser = copy(A) + A_denser[1, 2] = A_denser[2, 1] = 1 + A_sparser = copy(A) + A_sparser[1, 1] = 0 + dropzeros!(A_sparser) + + @testset "$structure - $partition - $decompression" for ( + structure, partition, decompression + ) in [ + (:nonsymmetric, :column, :direct), + (:nonsymmetric, :row, :direct), + (:symmetric, :column, :direct), + (:symmetric, :column, :substitution), + (:nonsymmetric, :bidirectional, :direct), + (:nonsymmetric, :bidirectional, :substitution), + ] + problem = ColoringProblem(; structure, partition) + algo = GreedyColoringAlgorithm(; decompression) + algo_tolerant = GreedyColoringAlgorithm(; decompression, allow_denser=true) + result = coloring(A, problem, algo) + result_tolerant = coloring(A, problem, algo_tolerant) + Bs = if partition == :bidirectional + compress(A, result) + else + (compress(A, result),) + end + @test_throws DimensionMismatch decompress!(copy(A_denser), Bs..., result) + @test_throws DimensionMismatch decompress!(copy(A_sparser), Bs..., result_tolerant) + if decompression == :direct && partition in (:row, :column) + @test decompress!(copy(A_denser), Bs..., result_tolerant) == A + else + @test_throws DimensionMismatch decompress!( + copy(A_denser), Bs..., result_tolerant + ) + end + if structure == :symmetric && decompression == :direct + @test decompress!(copy(tril(A_denser)), Bs..., result_tolerant, :L) == tril(A) + @test_throws DimensionMismatch decompress!( + copy(tril(A_denser)), Bs..., result, :L + ) + end + end +end; diff --git a/test/utils.jl b/test/utils.jl index 38dadd29..4f6fb777 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -86,7 +86,7 @@ function test_coloring_decompression( @test decompress(B, result) ≈ A0 # check result wasn't modified @test decompress!(respectful_similar(A, eltype(B)), B, result) ≈ A0 @test decompress!(respectful_similar(A, eltype(B)), B, result) ≈ A0 - if decompression == :direct && A isa SparseMatrixCSC + if algo.allow_denser && decompression == :direct && A isa SparseMatrixCSC A_bigger = respectful_similar(A, eltype(B)) for _ in 1:10 nb_coeffs_added = rand(1:minimum(size(A))) @@ -132,6 +132,22 @@ function test_coloring_decompression( @test A3upper ≈ triu(A0) @test A3lower ≈ tril(A0) @test A3both ≈ A0 + + if algo.allow_denser && decompression == :direct && A isa SparseMatrixCSC + A3upper_bigger = respectful_similar(A3upper, eltype(B)) + A3lower_bigger = respectful_similar(A3lower, eltype(B)) + for _ in 1:10 + nb_coeffs_added = rand(1:minimum(size(A))) + for _ in nb_coeffs_added + i = rand(axes(A, 1)) + j = rand(axes(A, 2)) + A3upper_bigger[min(i, j), max(i, j)] = one(eltype(B)) + A3lower_bigger[max(i, j), min(i, j)] = one(eltype(B)) + end + @test decompress!(A3upper_bigger, B, result, :U) ≈ triu(A0) + @test decompress!(A3lower_bigger, B, result, :L) ≈ tril(A0) + end + end end end @@ -195,6 +211,7 @@ function test_bicoloring_decompression( end Br, Bc = compress(A, result) row_color, column_color = row_colors(result), column_colors(result) + T = promote_eltype(Br, Bc) @testset "Coherence" begin @test size(Br, 1) == length(unique(row_color[row_color .> 0])) @@ -214,12 +231,8 @@ function test_bicoloring_decompression( @testset "Full decompression" begin @test decompress(Br, Bc, result) ≈ A0 @test decompress(Br, Bc, result) ≈ A0 # check result wasn't modified - @test decompress!( - respectful_similar(A, promote_eltype(Br, Bc)), Br, Bc, result - ) ≈ A0 - @test decompress!( - respectful_similar(A, promote_eltype(Br, Bc)), Br, Bc, result - ) ≈ A0 + @test decompress!(respectful_similar(A, T), Br, Bc, result) ≈ A0 + @test decompress!(respectful_similar(A, T), Br, Bc, result) ≈ A0 end end end From d7a1ab288876a2f53021748d937913af69c93388 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Fri, 4 Apr 2025 09:17:33 +0200 Subject: [PATCH 4/7] Update src/decompression.jl --- src/decompression.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decompression.jl b/src/decompression.jl index 6e72747c..7f33d51d 100644 --- a/src/decompression.jl +++ b/src/decompression.jl @@ -204,7 +204,7 @@ Some coloring algorithms ([`GreedyColoringAlgorithm`](@ref) and [`ConstantColori 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) + - the decompression is for all colors (so [`decompress_single_color!`](@ref) will not work) 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. From 2667a2899f6b2853817a3c6982194ed2c437531f Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Fri, 4 Apr 2025 09:47:51 +0200 Subject: [PATCH 5/7] Minor fixes --- src/decompression.jl | 29 +++++++++++++++-------------- src/matrices.jl | 11 +++++++---- test/matrices.jl | 15 +++++++-------- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/decompression.jl b/src/decompression.jl index 7f33d51d..366d352a 100644 --- a/src/decompression.jl +++ b/src/decompression.jl @@ -352,7 +352,7 @@ end function decompress!(A::AbstractMatrix, B::AbstractMatrix, result::ColumnColoringResult) (; color) = result S = result.bg.S2 - check_same_pattern(A, S) + @assert size(A) == size(S) fill!(A, zero(eltype(A))) rvS = rowvals(S) for j in axes(S, 2) @@ -370,7 +370,7 @@ function decompress_single_color!( ) (; group) = result S = result.bg.S2 - check_same_pattern(A, S) + @assert size(A) == size(S) rvS = rowvals(S) for j in group[c] for k in nzrange(S, j) @@ -384,7 +384,7 @@ end function decompress!(A::SparseMatrixCSC, B::AbstractMatrix, result::ColumnColoringResult) (; compressed_indices, allow_denser) = result S = result.bg.S2 - check_same_pattern(A, S; allow_denser) + check_compatible_pattern(A, S; allow_denser) nzA = nonzeros(A) if nnz(A) == nnz(S) for k in eachindex(compressed_indices) @@ -413,7 +413,7 @@ function decompress_single_color!( ) (; group) = result S = result.bg.S2 - check_same_pattern(A, S) + check_compatible_pattern(A, S) rvS = rowvals(S) nzA = nonzeros(A) for j in group[c] @@ -430,7 +430,7 @@ end function decompress!(A::AbstractMatrix, B::AbstractMatrix, result::RowColoringResult) (; color) = result S = result.bg.S2 - check_same_pattern(A, S) + @assert size(A) == size(S) fill!(A, zero(eltype(A))) rvS = rowvals(S) for j in axes(S, 2) @@ -448,7 +448,7 @@ function decompress_single_color!( ) (; group) = result S, Sᵀ = result.bg.S2, result.bg.S1 - check_same_pattern(A, S) + @assert size(A) == size(S) rvSᵀ = rowvals(Sᵀ) for i in group[c] for k in nzrange(Sᵀ, i) @@ -462,7 +462,7 @@ end function decompress!(A::SparseMatrixCSC, B::AbstractMatrix, result::RowColoringResult) (; compressed_indices, allow_denser) = result S = result.bg.S2 - check_same_pattern(A, S; allow_denser) + check_compatible_pattern(A, S; allow_denser) nzA = nonzeros(A) if nnz(A) == nnz(S) for k in eachindex(nzA, compressed_indices) @@ -493,7 +493,7 @@ function decompress!( ) (; ag, compressed_indices) = result (; S) = ag - uplo == :F && check_same_pattern(A, S) + @assert size(A) == size(S) fill!(A, zero(eltype(A))) rvS = rowvals(S) @@ -517,7 +517,7 @@ function decompress_single_color!( ) (; ag, compressed_indices, group) = result (; S) = ag - uplo == :F && check_same_pattern(A, S) + @assert size(A) == size(S) lower_index = (c - 1) * S.n + 1 upper_index = c * S.n @@ -552,7 +552,7 @@ function decompress!( (; S) = ag nzA = nonzeros(A) if uplo == :F - check_same_pattern(A, S; allow_denser) + check_compatible_pattern(A, S; allow_denser) if nnz(A) == nnz(S) for k in eachindex(nzA, compressed_indices) nzA[k] = B[compressed_indices[k]] @@ -608,7 +608,7 @@ function decompress!( ) (; ag, color, reverse_bfs_orders, buffer) = result (; S) = ag - uplo == :F && check_same_pattern(A, S) + @assert size(A) == size(S) R = eltype(A) fill!(A, zero(R)) @@ -671,7 +671,7 @@ function decompress!( (; S) = ag A_colptr = A.colptr nzA = nonzeros(A) - uplo == :F && check_same_pattern(A, S) + @assert size(A) == size(S) if eltype(buffer) == R buffer_right_type = buffer @@ -768,7 +768,7 @@ function decompress!( ) (; color, strict_upper_nonzero_inds, M_factorization, strict_upper_nonzeros_A) = result S = result.ag.S - uplo == :F && check_same_pattern(A, S) + @assert size(A) == size(S) # TODO: for some reason I cannot use ldiv! with a sparse QR strict_upper_nonzeros_A = M_factorization \ vec(B) @@ -829,6 +829,7 @@ function decompress!( A::AbstractMatrix, Br::AbstractMatrix, Bc::AbstractMatrix, result::BicoloringResult ) m, n = size(A) + @assert size(A) == size(result.S) Br_and_Bc = _join_compressed!(result, Br, Bc) A_and_Aᵀ = decompress(Br_and_Bc, result.symmetric_result) copyto!(A, A_and_Aᵀ[(n + 1):(n + m), 1:n]) # original matrix in bottom left corner @@ -839,7 +840,7 @@ function decompress!( A::SparseMatrixCSC, Br::AbstractMatrix, Bc::AbstractMatrix, result::BicoloringResult ) (; S, large_colptr, large_rowval, symmetric_result) = result - check_same_pattern(A, S) + check_compatible_pattern(A, S) m, n = size(A) Br_and_Bc = _join_compressed!(result, Br, Bc) # pretend A is larger diff --git a/src/matrices.jl b/src/matrices.jl index 71d8c938..268eb7b8 100644 --- a/src/matrices.jl +++ b/src/matrices.jl @@ -61,16 +61,19 @@ function respectful_similar(A::Union{Symmetric,Hermitian}, ::Type{T}) where {T} return respectful_similar(sparse(A), T) end -same_pattern(A::AbstractMatrix, S; allow_denser::Bool=false) = true -function same_pattern(A::SparseMatrixCSC, S; allow_denser::Bool=false) +function compatible_pattern( + A::Union{SparseMatrixCSC,SparsityPatternCSC}, + S::Union{SparseMatrixCSC,SparsityPatternCSC}; + allow_denser::Bool=false, +) return allow_denser ? nnz(A) >= nnz(S) : nnz(A) == nnz(S) end -function check_same_pattern(A, S; allow_denser::Bool=false) +function check_compatible_pattern(A, S; allow_denser::Bool=false) if size(A) != size(S) msg = "Decompression target must have the same size as the sparsity pattern used for coloring" throw(DimensionMismatch(msg)) - elseif !same_pattern(A, S; allow_denser) + elseif !compatible_pattern(A, S; allow_denser) msg = """Decompression target must have $(allow_denser ? "at least" : "exactly") as many nonzeros as the sparsity pattern used for coloring""" throw(DimensionMismatch(msg)) end diff --git a/test/matrices.jl b/test/matrices.jl index 3cef744b..b20e0c37 100644 --- a/test/matrices.jl +++ b/test/matrices.jl @@ -1,7 +1,7 @@ using LinearAlgebra using SparseArrays using SparseMatrixColorings: - check_same_pattern, matrix_versions, respectful_similar, same_pattern + check_compatible_pattern, matrix_versions, respectful_similar, compatible_pattern using StableRNGs using Test @@ -44,12 +44,11 @@ end A2 = copy(S) A2[1, 1] = 1 - @test same_pattern(A1, S) - @test !same_pattern(A2, S) - @test same_pattern(Matrix(A2), S) + @test compatible_pattern(A1, S) + @test !compatible_pattern(A2, S) - @test_throws DimensionMismatch check_same_pattern(vcat(A1, A1), S) - @test_throws DimensionMismatch check_same_pattern(A2, S) - @test check_same_pattern(A1, S; allow_denser=true) - @test check_same_pattern(A2, S; allow_denser=true) + @test_throws DimensionMismatch check_compatible_pattern(vcat(A1, A1), S) + @test_throws DimensionMismatch check_compatible_pattern(A2, S) + @test check_compatible_pattern(A1, S; allow_denser=true) + @test check_compatible_pattern(A2, S; allow_denser=true) end From c02ce1b3c2bf6b9e404f172707577f16d3135e72 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Fri, 4 Apr 2025 09:58:34 +0200 Subject: [PATCH 6/7] Fix --- src/decompression.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decompression.jl b/src/decompression.jl index 366d352a..0280eac0 100644 --- a/src/decompression.jl +++ b/src/decompression.jl @@ -671,7 +671,7 @@ function decompress!( (; S) = ag A_colptr = A.colptr nzA = nonzeros(A) - @assert size(A) == size(S) + uplo == :F && check_same_pattern(A, S) if eltype(buffer) == R buffer_right_type = buffer From 03847906a1753dc527606395033318db06929e77 Mon Sep 17 00:00:00 2001 From: Guillaume Dalle <22795598+gdalle@users.noreply.github.com> Date: Fri, 4 Apr 2025 10:13:11 +0200 Subject: [PATCH 7/7] Fix --- src/decompression.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decompression.jl b/src/decompression.jl index 0280eac0..9f7e4200 100644 --- a/src/decompression.jl +++ b/src/decompression.jl @@ -671,7 +671,7 @@ function decompress!( (; S) = ag A_colptr = A.colptr nzA = nonzeros(A) - uplo == :F && check_same_pattern(A, S) + uplo == :F && check_compatible_pattern(A, S) if eltype(buffer) == R buffer_right_type = buffer