Skip to content

Commit c5374e3

Browse files
committed
Add allow_denser as option to the algorithm
1 parent 2caf5ec commit c5374e3

File tree

13 files changed

+287
-105
lines changed

13 files changed

+287
-105
lines changed

src/adtypes.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ function coloring(
66
)
77
bg = BipartiteGraph(A)
88
color = convert(Vector{eltype(bg)}, ADTypes.column_coloring(A, algo))
9-
return ColumnColoringResult(A, bg, color)
9+
return ColumnColoringResult(A, bg, color; allow_denser=false)
1010
end
1111

1212
function coloring(
@@ -17,5 +17,5 @@ function coloring(
1717
)
1818
bg = BipartiteGraph(A)
1919
color = convert(Vector{eltype(bg)}, ADTypes.row_coloring(A, algo))
20-
return RowColoringResult(A, bg, color)
20+
return RowColoringResult(A, bg, color; allow_denser=false)
2121
end

src/constant.jl

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,22 @@ Indeed, for symmetric coloring problems, we need more than just the vector of co
99

1010
# Constructors
1111

12-
ConstantColoringAlgorithm{partition}(matrix_template, color)
13-
ConstantColoringAlgorithm(matrix_template, color; partition=:column)
14-
12+
ConstantColoringAlgorithm{partition}(
13+
matrix_template,
14+
color;
15+
allow_denser=false
16+
)
17+
18+
ConstantColoringAlgorithm(
19+
matrix_template, color;
20+
partition=:column,
21+
allow_denser=false
22+
)
23+
1524
- `partition::Symbol`: either `:row` or `:column`.
1625
- `matrix_template::AbstractMatrix`: matrix for which the vector of colors was precomputed (the algorithm will only accept matrices of the exact same size).
1726
- `color::Vector{<:Integer}`: vector of integer colors, one for each row or column (depending on `partition`).
27+
- `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).
1828

1929
!!! warning
2030
The second constructor (based on keyword arguments) is type-unstable.
@@ -73,30 +83,38 @@ struct ConstantColoringAlgorithm{
7383
matrix_template::M
7484
color::Vector{T}
7585
result::R
86+
allow_denser::Bool
7687
end
7788

7889
function ConstantColoringAlgorithm{:column}(
79-
matrix_template::AbstractMatrix, color::Vector{<:Integer}
90+
matrix_template::AbstractMatrix, color::Vector{<:Integer}; allow_denser::Bool=false
8091
)
8192
bg = BipartiteGraph(matrix_template)
82-
result = ColumnColoringResult(matrix_template, bg, color)
93+
result = ColumnColoringResult(matrix_template, bg, color; allow_denser)
8394
T, M, R = eltype(bg), typeof(matrix_template), typeof(result)
84-
return ConstantColoringAlgorithm{:column,M,T,R}(matrix_template, color, result)
95+
return ConstantColoringAlgorithm{:column,M,T,R}(
96+
matrix_template, color, result, allow_denser
97+
)
8598
end
8699

87100
function ConstantColoringAlgorithm{:row}(
88-
matrix_template::AbstractMatrix, color::Vector{<:Integer}
101+
matrix_template::AbstractMatrix, color::Vector{<:Integer}; allow_denser::Bool=false
89102
)
90103
bg = BipartiteGraph(matrix_template)
91-
result = RowColoringResult(matrix_template, bg, color)
104+
result = RowColoringResult(matrix_template, bg, color; allow_denser)
92105
T, M, R = eltype(bg), typeof(matrix_template), typeof(result)
93-
return ConstantColoringAlgorithm{:row,M,T,R}(matrix_template, color, result)
106+
return ConstantColoringAlgorithm{:row,M,T,R}(
107+
matrix_template, color, result, allow_denser
108+
)
94109
end
95110

96111
function ConstantColoringAlgorithm(
97-
matrix_template::AbstractMatrix, color::Vector{<:Integer}; partition::Symbol=:column
112+
matrix_template::AbstractMatrix,
113+
color::Vector{<:Integer};
114+
partition::Symbol=:column,
115+
allow_denser::Bool=false,
98116
)
99-
return ConstantColoringAlgorithm{partition}(matrix_template, color)
117+
return ConstantColoringAlgorithm{partition}(matrix_template, color; allow_denser)
100118
end
101119

102120
function coloring(

src/decompression.jl

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -189,15 +189,25 @@ The out-of-place alternative is [`decompress`](@ref).
189189

190190
!!! note
191191
In-place decompression is faster when `A isa SparseMatrixCSC`.
192-
- In general, this case requires the sparsity pattern of `A` to match the sparsity pattern `S` from which the coloring result was computed.
193-
- For a coloring result with `decompression=:direct`, we also allow _full_ decompression into an `A` whose sparsity pattern is a strict superset of `S`.
194192

195193
Compression means summing either the columns or the rows of `A` which share the same color.
196194
It is done by calling [`compress`](@ref).
197195

196+
# Details
197+
198198
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.
199199
When `A isa SparseMatrixCSC`, using the `uplo` argument requires a target matrix which only stores the relevant triangle(s).
200200

201+
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.
202+
203+
!!! warning
204+
Decompression into a denser `SparseMatrixCSC` is only implemented when all the conditions below are satisfied simultaneously:
205+
- the partition is `:row` or `:column` (not `:bidirectional`)
206+
- the decompression is `:direct` (not `:substitution`)
207+
- the decompression is full (so [`decompress_single_color!`](@ref) will not work)
208+
209+
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.
210+
201211
# Example
202212

203213
```jldoctest
@@ -212,6 +222,8 @@ julia> A = sparse([
212222

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

225+
julia> result_tolerant = coloring(A, ColoringProblem(), GreedyColoringAlgorithm(; allow_denser=true));
226+
215227
julia> collect.(column_groups(result))
216228
3-element Vector{Vector{Int64}}:
217229
[1, 2, 4]
@@ -227,6 +239,8 @@ julia> B = compress(A, result)
227239

228240
julia> A2 = similar(A);
229241

242+
julia> A3 = similar(A); A3[1, 1] = A3[end, end] = 1;
243+
230244
julia> decompress!(A2, B, result)
231245
4×6 SparseMatrixCSC{Int64, Int64} with 9 stored entries:
232246
⋅ ⋅ 4 6 ⋅ 9
@@ -236,6 +250,16 @@ julia> decompress!(A2, B, result)
236250

237251
julia> A2 == A
238252
true
253+
254+
julia> decompress!(A3, B, result_tolerant)
255+
4×6 SparseMatrixCSC{Int64, Int64} with 11 stored entries:
256+
0 ⋅ 4 6 ⋅ 9
257+
1 ⋅ ⋅ ⋅ 7 ⋅
258+
⋅ 2 ⋅ ⋅ 8 ⋅
259+
⋅ 3 5 ⋅ ⋅ 0
260+
261+
julia> A3 == A
262+
true
239263
```
240264

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

287+
# Details
288+
263289
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.
264290
When `A isa SparseMatrixCSC`, using the `uplo` argument requires a target matrix which only stores the relevant triangle(s).
265291

@@ -356,27 +382,28 @@ function decompress_single_color!(
356382
end
357383

358384
function decompress!(A::SparseMatrixCSC, B::AbstractMatrix, result::ColumnColoringResult)
359-
(; compressed_indices) = result
385+
(; compressed_indices, allow_denser) = result
360386
S = result.bg.S2
361-
check_same_pattern(A, S; allow_superset=true)
387+
check_same_pattern(A, S; allow_denser)
362388
nzA = nonzeros(A)
363389
if nnz(A) == nnz(S)
364390
for k in eachindex(compressed_indices)
365391
nzA[k] = B[compressed_indices[k]]
366392
end
367-
else # nnz(A) > nnz(Z)
368-
fill!(nonzeros(A), zero(eltype(A)))
393+
else # nnz(A) > nnz(S)
369394
rvA, rvS = rowvals(A), rowvals(S)
370395
shift = 0
371396
for j in axes(S, 2)
372397
for k in nzrange(S, j)
373398
i = rvS[k]
374399
while (k + shift) < A.colptr[j] || rvA[k + shift] < i
400+
nzA[k + shift] = zero(eltype(A))
375401
shift += 1
376402
end
377403
nzA[k + shift] = B[compressed_indices[k]]
378404
end
379405
end
406+
nzA[(nnz(S) + shift + 1):end] .= zero(eltype(A))
380407
end
381408
return A
382409
end
@@ -433,27 +460,28 @@ function decompress_single_color!(
433460
end
434461

435462
function decompress!(A::SparseMatrixCSC, B::AbstractMatrix, result::RowColoringResult)
436-
(; compressed_indices) = result
463+
(; compressed_indices, allow_denser) = result
437464
S = result.bg.S2
438-
check_same_pattern(A, S; allow_superset=true)
465+
check_same_pattern(A, S; allow_denser)
439466
nzA = nonzeros(A)
440467
if nnz(A) == nnz(S)
441468
for k in eachindex(nzA, compressed_indices)
442469
nzA[k] = B[compressed_indices[k]]
443470
end
444471
else # nnz(A) > nnz(S)
445-
fill!(nonzeros(A), zero(eltype(A)))
446472
rvA, rvS = rowvals(A), rowvals(S)
447473
shift = 0
448474
for j in axes(S, 2)
449475
for k in nzrange(S, j)
450476
i = rvS[k]
451477
while (k + shift) < A.colptr[j] || rvA[k + shift] < i
478+
nzA[k + shift] = zero(eltype(A))
452479
shift += 1
453480
end
454481
nzA[k + shift] = B[compressed_indices[k]]
455482
end
456483
end
484+
nzA[(nnz(S) + shift + 1):end] .= zero(eltype(A))
457485
end
458486
return A
459487
end
@@ -520,40 +548,54 @@ end
520548
function decompress!(
521549
A::SparseMatrixCSC, B::AbstractMatrix, result::StarSetColoringResult, uplo::Symbol=:F
522550
)
523-
(; ag, compressed_indices) = result
551+
(; ag, compressed_indices, allow_denser) = result
524552
(; S) = ag
525553
nzA = nonzeros(A)
526554
if uplo == :F
527-
check_same_pattern(A, S; allow_superset=true)
555+
check_same_pattern(A, S; allow_denser)
528556
if nnz(A) == nnz(S)
529557
for k in eachindex(nzA, compressed_indices)
530558
nzA[k] = B[compressed_indices[k]]
531559
end
532560
else # nnz(A) > nnz(S)
533-
fill!(nonzeros(A), zero(eltype(A)))
534561
rvA, rvS = rowvals(A), rowvals(S)
535562
shift = 0
536563
for j in axes(S, 2)
537564
for k in nzrange(S, j)
538565
i = rvS[k]
539566
while (k + shift) < A.colptr[j] || rvA[k + shift] < i
567+
nzA[k + shift] = zero(eltype(A))
540568
shift += 1
541569
end
542570
nzA[k + shift] = B[compressed_indices[k]]
543571
end
544572
end
573+
nzA[(nnz(S) + shift + 1):end] .= zero(eltype(A))
545574
end
546575
else
547-
rvS = rowvals(S)
576+
rvA, rvS = rowvals(A), rowvals(S)
548577
l = 0 # assume A has the same pattern as the triangle
578+
shift = 0
549579
for j in axes(S, 2)
550580
for k in nzrange(S, j)
551581
i = rvS[k]
552582
if in_triangle(i, j, uplo)
553583
l += 1
554-
nzA[l] = B[compressed_indices[k]]
584+
while (l + shift) < A.colptr[j] || rvA[l + shift] < i
585+
nzA[l + shift] = zero(eltype(A))
586+
shift += 1
587+
end
588+
nzA[l + shift] = B[compressed_indices[k]]
555589
end
556590
end
591+
nzA[(l + shift + 1):end] .= zero(eltype(A))
592+
if !allow_denser && shift > 0
593+
throw(
594+
DimensionMismatch(
595+
"Decompression target must have exactly as many nonzeros as the sparsity pattern used for coloring",
596+
),
597+
)
598+
end
557599
end
558600
end
559601
return A
@@ -796,7 +838,8 @@ end
796838
function decompress!(
797839
A::SparseMatrixCSC, Br::AbstractMatrix, Bc::AbstractMatrix, result::BicoloringResult
798840
)
799-
(; large_colptr, large_rowval, symmetric_result) = result
841+
(; S, large_colptr, large_rowval, symmetric_result) = result
842+
check_same_pattern(A, S)
800843
m, n = size(A)
801844
Br_and_Bc = _join_compressed!(result, Br, Bc)
802845
# pretend A is larger

src/graph.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,10 @@ end
9999

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

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

172174
# Create the SparsityPatternCSC of the augmented adjacency matrix
173175
S_and_Sᵀ = SparsityPatternCSC{T}(p, p, colptr, rowval)
174-
return S_and_Sᵀ, edge_to_index
176+
return S, S_and_Sᵀ, edge_to_index
175177
end
176178

177179
function build_edge_to_index(S::SparsityPatternCSC{T}) where {T}

0 commit comments

Comments
 (0)