Skip to content

Commit 8450830

Browse files
committed
Add nb_self_loops in AdjacencyGraph
1 parent a5f32f1 commit 8450830

7 files changed

Lines changed: 137 additions & 71 deletions

File tree

docs/src/dev.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ SparseMatrixColorings.valid_dynamic_order
5858
```@docs
5959
SparseMatrixColorings.respectful_similar
6060
SparseMatrixColorings.matrix_versions
61-
SparseMatrixColorings.same_pattern
61+
SparseMatrixColorings.compatible_pattern
6262
```
6363

6464
## Visualization

src/decompression.jl

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -339,9 +339,9 @@ end
339339
## ColumnColoringResult
340340

341341
function decompress!(A::AbstractMatrix, B::AbstractMatrix, result::ColumnColoringResult)
342-
(; color) = result
343-
S = result.bg.S2
344-
check_same_pattern(A, S)
342+
(; bg, color) = result
343+
S = bg.S2
344+
check_compatible_pattern(A, bg)
345345
fill!(A, zero(eltype(A)))
346346
rvS = rowvals(S)
347347
for j in axes(S, 2)
@@ -357,9 +357,9 @@ end
357357
function decompress_single_color!(
358358
A::AbstractMatrix, b::AbstractVector, c::Integer, result::ColumnColoringResult
359359
)
360-
(; group) = result
361-
S = result.bg.S2
362-
check_same_pattern(A, S)
360+
(; bg, group) = result
361+
S = bg.S2
362+
check_compatible_pattern(A, bg)
363363
rvS = rowvals(S)
364364
for j in group[c]
365365
for k in nzrange(S, j)
@@ -371,9 +371,9 @@ function decompress_single_color!(
371371
end
372372

373373
function decompress!(A::SparseMatrixCSC, B::AbstractMatrix, result::ColumnColoringResult)
374-
(; compressed_indices) = result
375-
S = result.bg.S2
376-
check_same_pattern(A, S)
374+
(; bg, compressed_indices) = result
375+
S = bg.S2
376+
check_compatible_pattern(A, bg)
377377
nzA = nonzeros(A)
378378
for k in eachindex(nzA, compressed_indices)
379379
nzA[k] = B[compressed_indices[k]]
@@ -384,9 +384,9 @@ end
384384
function decompress_single_color!(
385385
A::SparseMatrixCSC, b::AbstractVector, c::Integer, result::ColumnColoringResult
386386
)
387-
(; group) = result
388-
S = result.bg.S2
389-
check_same_pattern(A, S)
387+
(; bg, group) = result
388+
S = bg.S2
389+
check_compatible_pattern(A, bg)
390390
rvS = rowvals(S)
391391
nzA = nonzeros(A)
392392
for j in group[c]
@@ -401,9 +401,9 @@ end
401401
## RowColoringResult
402402

403403
function decompress!(A::AbstractMatrix, B::AbstractMatrix, result::RowColoringResult)
404-
(; color) = result
405-
S = result.bg.S2
406-
check_same_pattern(A, S)
404+
(; bg, color) = result
405+
S = bg.S2
406+
check_compatible_pattern(A, bg)
407407
fill!(A, zero(eltype(A)))
408408
rvS = rowvals(S)
409409
for j in axes(S, 2)
@@ -419,9 +419,9 @@ end
419419
function decompress_single_color!(
420420
A::AbstractMatrix, b::AbstractVector, c::Integer, result::RowColoringResult
421421
)
422-
(; group) = result
423-
S, Sᵀ = result.bg.S2, result.bg.S1
424-
check_same_pattern(A, S)
422+
(; bg, group) = result
423+
S, Sᵀ = bg.S2, bg.S1
424+
check_compatible_pattern(A, bg)
425425
rvSᵀ = rowvals(Sᵀ)
426426
for i in group[c]
427427
for k in nzrange(Sᵀ, i)
@@ -433,9 +433,9 @@ function decompress_single_color!(
433433
end
434434

435435
function decompress!(A::SparseMatrixCSC, B::AbstractMatrix, result::RowColoringResult)
436-
(; compressed_indices) = result
437-
S = result.bg.S2
438-
check_same_pattern(A, S)
436+
(; bg, compressed_indices) = result
437+
S = bg.S2
438+
check_compatible_pattern(A, bg)
439439
nzA = nonzeros(A)
440440
for k in eachindex(nzA, compressed_indices)
441441
nzA[k] = B[compressed_indices[k]]
@@ -450,7 +450,7 @@ function decompress!(
450450
)
451451
(; ag, compressed_indices) = result
452452
(; S) = ag
453-
uplo == :F && check_same_pattern(A, S)
453+
check_compatible_pattern(A, ag, uplo)
454454
fill!(A, zero(eltype(A)))
455455

456456
rvS = rowvals(S)
@@ -474,7 +474,7 @@ function decompress_single_color!(
474474
)
475475
(; ag, compressed_indices, group) = result
476476
(; S) = ag
477-
uplo == :F && check_same_pattern(A, S)
477+
check_compatible_pattern(A, ag, uplo)
478478

479479
lower_index = (c - 1) * S.n + 1
480480
upper_index = c * S.n
@@ -508,8 +508,8 @@ function decompress!(
508508
(; ag, compressed_indices) = result
509509
(; S) = ag
510510
nzA = nonzeros(A)
511+
check_compatible_pattern(A, ag, uplo)
511512
if uplo == :F
512-
check_same_pattern(A, S)
513513
for k in eachindex(nzA, compressed_indices)
514514
nzA[k] = B[compressed_indices[k]]
515515
end
@@ -537,7 +537,7 @@ function decompress!(
537537
(; ag, color, reverse_bfs_orders, tree_edge_indices, nt, diagonal_indices, buffer) =
538538
result
539539
(; S) = ag
540-
uplo == :F && check_same_pattern(A, S)
540+
check_compatible_pattern(A, ag, uplo)
541541
R = eltype(A)
542542
fill!(A, zero(R))
543543

@@ -606,7 +606,7 @@ function decompress!(
606606
(; S) = ag
607607
A_colptr = A.colptr
608608
nzA = nonzeros(A)
609-
uplo == :F && check_same_pattern(A, S)
609+
check_compatible_pattern(A, ag, uplo)
610610

611611
if eltype(buffer) == R
612612
buffer_right_type = buffer
@@ -707,9 +707,10 @@ function decompress!(
707707
result::LinearSystemColoringResult,
708708
uplo::Symbol=:F,
709709
)
710-
(; color, strict_upper_nonzero_inds, M_factorization, strict_upper_nonzeros_A) = result
711-
S = result.ag.S
712-
uplo == :F && check_same_pattern(A, S)
710+
(; ag, color, strict_upper_nonzero_inds, M_factorization, strict_upper_nonzeros_A) =
711+
result
712+
S = ag.S
713+
check_compatible_pattern(A, ag, uplo)
713714

714715
# TODO: for some reason I cannot use ldiv! with a sparse QR
715716
strict_upper_nonzeros_A = M_factorization \ vec(B)

src/graph.jl

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ function build_edge_to_index(S::SparsityPatternCSC{T}) where {T}
179179
# edge_to_index gives an index for each edge
180180
edge_to_index = Vector{T}(undef, nnz(S))
181181
offsets = zeros(T, S.n)
182+
nb_self_loops = 0
182183
counter = 0
183184
rvS = rowvals(S)
184185
for j in axes(S, 2)
@@ -193,10 +194,11 @@ function build_edge_to_index(S::SparsityPatternCSC{T}) where {T}
193194
elseif i == j
194195
# this should never be used, make sure it errors
195196
edge_to_index[k] = 0
197+
nb_self_loops += 1
196198
end
197199
end
198200
end
199-
return edge_to_index
201+
return edge_to_index, nb_self_loops
200202
end
201203

202204
## Adjacency graph
@@ -227,16 +229,23 @@ The adjacency graph of a symmetric matrix `A ∈ ℝ^{n × n}` is `G(A) = (V, E)
227229
struct AdjacencyGraph{T<:Integer,augmented_graph}
228230
S::SparsityPatternCSC{T}
229231
edge_to_index::Vector{T}
232+
nb_self_loops::Int
230233
end
231234

232235
Base.eltype(::AdjacencyGraph{T}) where {T} = T
233236

234237
function AdjacencyGraph(
235238
S::SparsityPatternCSC{T},
236-
edge_to_index::Vector{T}=build_edge_to_index(S);
239+
edge_to_index::Vector{T},
240+
nb_self_loops::Int;
237241
augmented_graph::Bool=false,
238242
) where {T}
239-
return AdjacencyGraph{T,augmented_graph}(S, edge_to_index)
243+
return AdjacencyGraph{T,augmented_graph}(S, edge_to_index, nb_self_loops)
244+
end
245+
246+
function AdjacencyGraph(S::SparsityPatternCSC; augmented_graph::Bool=false)
247+
edge_to_index, nb_self_loops = build_edge_to_index(S)
248+
return AdjacencyGraph(S, edge_to_index, nb_self_loops; augmented_graph)
240249
end
241250

242251
function AdjacencyGraph(A::SparseMatrixCSC; augmented_graph::Bool=false)
@@ -275,15 +284,7 @@ function degree(g::AdjacencyGraph{T,false}, v::Integer) where {T}
275284
return g.S.colptr[v + 1] - g.S.colptr[v] - has_selfloop
276285
end
277286

278-
nb_edges(g::AdjacencyGraph{T,true}) where {T} = nnz(g.S) ÷ 2
279-
280-
function nb_edges(g::AdjacencyGraph{T,false}) where {T}
281-
ne = 0
282-
for v in vertices(g)
283-
ne += degree(g, v)
284-
end
285-
return ne ÷ 2
286-
end
287+
nb_edges(g::AdjacencyGraph) = (nnz(g.S) - g.nb_self_loops) ÷ 2
287288

288289
maximum_degree(g::AdjacencyGraph) = maximum(Base.Fix1(degree, g), vertices(g))
289290
minimum_degree(g::AdjacencyGraph) = minimum(Base.Fix1(degree, g), vertices(g))

src/interface.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ function _coloring(
323323
forced_colors::Union{AbstractVector{<:Integer},Nothing}=nothing,
324324
) where {R}
325325
A_and_Aᵀ, edge_to_index = bidirectional_pattern(A; symmetric_pattern)
326-
ag = AdjacencyGraph(A_and_Aᵀ, edge_to_index; augmented_graph=true)
326+
ag = AdjacencyGraph(A_and_Aᵀ, edge_to_index, 0; augmented_graph=true)
327327
outputs_by_order = map(algo.orders) do order
328328
vertices_in_order = vertices(ag, order)
329329
_color, _star_set = star_coloring(
@@ -370,7 +370,7 @@ function _coloring(
370370
symmetric_pattern::Bool,
371371
) where {R}
372372
A_and_Aᵀ, edge_to_index = bidirectional_pattern(A; symmetric_pattern)
373-
ag = AdjacencyGraph(A_and_Aᵀ, edge_to_index; augmented_graph=true)
373+
ag = AdjacencyGraph(A_and_Aᵀ, edge_to_index, 0; augmented_graph=true)
374374
outputs_by_order = map(algo.orders) do order
375375
vertices_in_order = vertices(ag, order)
376376
_color, _tree_set = acyclic_coloring(ag, vertices_in_order, algo.postprocessing)

src/matrices.jl

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,24 +62,59 @@ function respectful_similar(A::Union{Symmetric,Hermitian}, ::Type{T}) where {T}
6262
end
6363

6464
"""
65-
same_pattern(A, B)
65+
compatible_pattern(A::AbstractMatrix, bg::BipartiteGraph)
66+
compatible_pattern(A::AbstractMatrix, ag::AdjacencyGraph, uplo::Symbol)
6667
67-
Perform a partial equality check on the sparsity patterns of `A` and `B`:
68+
Perform a coarse compatibility check between the sparsity pattern of `A`
69+
and the reference sparsity pattern encoded in a graph structure.
6870
69-
- if the return is `true`, they might have the same sparsity pattern but we're not sure
70-
- if the return is `false`, they definitely don't have the same sparsity pattern
71+
This function only checks necessary conditions for the two sparsity patterns to match.
72+
In particular, it may return `true` even if the patterns are not identical.
73+
74+
When A is a `SparseMatrixCSC`, additional checks on the sparsity structure are performed.
75+
76+
# Return value
77+
- `true` : the sparsity patterns are potentially compatible
78+
- `false` : the sparsity patterns are definitely incompatible
7179
"""
72-
same_pattern(A, B) = size(A) == size(B)
80+
compatible_pattern(A::AbstractMatrix, bg::BipartiteGraph) = size(A) == size(bg.S2)
81+
function compatible_pattern(A::SparseMatrixCSC, bg::BipartiteGraph)
82+
size(A) == size(bg.S2) && nnz(A) == nnz(bg.S2)
83+
end
84+
85+
function compatible_pattern(A::AbstractMatrix, ag::AdjacencyGraph, uplo::Symbol)
86+
size(A) == size(ag.S)
87+
end
88+
function compatible_pattern(A::SparseMatrixCSC, ag::AdjacencyGraph, uplo::Symbol)
89+
if uplo == :L || uplo == :U
90+
return size(A) == size(ag.S) && nnz(A) == (nb_edges(ag) + ag.nb_self_loops)
91+
else
92+
return size(A) == size(ag.S) && nnz(A) == nnz(ag.S)
93+
end
94+
end
7395

74-
function same_pattern(
75-
A::Union{SparseMatrixCSC,SparsityPatternCSC},
76-
B::Union{SparseMatrixCSC,SparsityPatternCSC},
77-
)
78-
return size(A) == size(B) && nnz(A) == nnz(B)
96+
function check_compatible_pattern(A::AbstractMatrix, bg::BipartiteGraph)
97+
if !compatible_pattern(A, bg)
98+
throw(DimensionMismatch("`A` and `bg.S2` must have the same sparsity pattern."))
99+
end
79100
end
80101

81-
function check_same_pattern(A, S)
82-
if !same_pattern(A, S)
83-
throw(DimensionMismatch("`A` and `S` must have the same sparsity pattern."))
102+
function check_compatible_pattern(A::AbstractMatrix, ag::AdjacencyGraph, uplo::Symbol)
103+
if !compatible_pattern(A, ag, uplo)
104+
if uplo == :L
105+
throw(
106+
DimensionMismatch(
107+
"`A` and `tril(ag.S)` must have the same sparsity pattern."
108+
),
109+
)
110+
elseif uplo == :U
111+
throw(
112+
DimensionMismatch(
113+
"`A` and `triu(ag.S)` must have the same sparsity pattern."
114+
),
115+
)
116+
else # uplo == :F
117+
throw(DimensionMismatch("`A` and `ag.S` must have the same sparsity pattern."))
118+
end
84119
end
85120
end

src/result.jl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -401,31 +401,31 @@ function TreeSetColoringResult(
401401
decompression_eltype::Type{R},
402402
) where {T<:Integer,R}
403403
(; reverse_bfs_orders, tree_edge_indices, nt) = tree_set
404-
(; S) = ag
404+
(; S, nb_self_loops) = ag
405405
nvertices = length(color)
406406
group = group_by_color(T, color)
407407
rv = rowvals(S)
408408

409409
# Vector for the decompression of the diagonal coefficients
410-
diagonal_indices = T[]
411-
diagonal_nzind = T[]
412-
ndiag = 0
410+
diagonal_indices = Vector{T}(undef, nb_self_loops)
411+
diagonal_nzind = Vector{T}(undef, nb_self_loops)
413412

414413
if !augmented_graph(ag)
414+
l = 0
415415
for j in axes(S, 2)
416416
for k in nzrange(S, j)
417417
i = rv[k]
418418
if i == j
419-
push!(diagonal_indices, i)
420-
push!(diagonal_nzind, k)
421-
ndiag += 1
419+
l += 1
420+
diagonal_indices[l] = i
421+
diagonal_nzind[l] = k
422422
end
423423
end
424424
end
425425
end
426426

427427
# Vectors for the decompression of the off-diagonal coefficients
428-
nedges = (nnz(S) - ndiag) ÷ 2
428+
nedges = nb_edges(ag)
429429
lower_triangle_offsets = Vector{T}(undef, nedges)
430430
upper_triangle_offsets = Vector{T}(undef, nedges)
431431

0 commit comments

Comments
 (0)