Skip to content

Commit 0486dbd

Browse files
committed
Avoid leaking memory between structs
1 parent 3e68e0d commit 0486dbd

4 files changed

Lines changed: 77 additions & 70 deletions

File tree

src/coloring.jl

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,7 @@ function star_coloring(g::AdjacencyGraph, order::AbstractOrder, postprocessing::
8181
nv = nb_vertices(g)
8282
ne = nb_edges(g)
8383
color = zeros(Int, nv)
84-
forbidden_colors = g.vertex_buffer # Vector of length nv
85-
fill!(forbidden_colors, 0)
84+
forbidden_colors = zeros(Int, nv)
8685
first_neighbor = fill((0, 0, 0), nv) # at first no neighbors have been encountered
8786
treated = zeros(Int, nv)
8887
star = Vector{Int}(undef, ne)
@@ -123,7 +122,9 @@ function star_coloring(g::AdjacencyGraph, order::AbstractOrder, postprocessing::
123122
end
124123
star_set = StarSet(star, hub)
125124
if postprocessing
126-
postprocess!(color, star_set, g)
125+
# Reuse the vector forbidden_colors to compute offsets during post-processing
126+
offsets = forbidden_colors
127+
postprocess!(color, star_set, g, offsets)
127128
end
128129
return color, star_set
129130
end
@@ -230,8 +231,7 @@ function acyclic_coloring(g::AdjacencyGraph, order::AbstractOrder, postprocessin
230231
nv = nb_vertices(g)
231232
ne = nb_edges(g)
232233
color = zeros(Int, nv)
233-
forbidden_colors = g.vertex_buffer # Vector of length nv
234-
fill!(forbidden_colors, 0)
234+
forbidden_colors = zeros(Int, nv)
235235
first_neighbor = fill((0, 0, 0), nv) # at first no neighbors have been encountered
236236
first_visit_to_tree = fill((0, 0), ne)
237237
forest = Forest{Int}(ne)
@@ -289,7 +289,9 @@ function acyclic_coloring(g::AdjacencyGraph, order::AbstractOrder, postprocessin
289289

290290
tree_set = TreeSet(g, forest)
291291
if postprocessing
292-
postprocess!(color, tree_set, g)
292+
# Reuse the vector forbidden_colors to compute offsets during post-processing
293+
offsets = forbidden_colors
294+
postprocess!(color, tree_set, g, offsets)
293295
end
294296
return color, tree_set
295297
end
@@ -517,6 +519,7 @@ function postprocess!(
517519
color::AbstractVector{<:Integer},
518520
star_or_tree_set::Union{StarSet,TreeSet},
519521
g::AdjacencyGraph,
522+
offsets::Vector{Int},
520523
)
521524
S = pattern(g)
522525
edge_to_index = edge_indices(g)
@@ -621,7 +624,6 @@ function postprocess!(
621624

622625
# if at least one of the colors is useless, modify the color assignments of vertices
623626
if any(!, color_used)
624-
offsets = g.vertex_buffer
625627
num_colors_useless = 0
626628

627629
# determine what are the useless colors and compute the offsets

src/graph.jl

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ SparseArrays.nzrange(S::SparsityPatternCSC, j::Integer) = S.colptr[j]:(S.colptr[
3636
3737
Return a [`SparsityPatternCSC`](@ref) corresponding to the transpose of `S`.
3838
"""
39-
function Base.transpose(S::SparsityPatternCSC{T}) where {T<:Integer}
39+
function Base.transpose(S::SparsityPatternCSC{T}) where {T}
4040
m, n = size(S)
4141
nnzA = nnz(S)
4242
A_colptr = S.colptr
@@ -94,16 +94,14 @@ function Base.getindex(S::SparsityPatternCSC, i0::Integer, i1::Integer)
9494
end
9595

9696
"""
97-
bidirectional_pattern(A::AbstractMatrix; symmetric_pattern::Bool=false)
97+
bidirectional_pattern(A::AbstractMatrix; symmetric_pattern::Bool)
9898
9999
Return a [`SparsityPatternCSC`](@ref) corresponding to the matrix `[0 Aᵀ; A 0]`, with a minimum of allocations.
100100
"""
101-
bidirectional_pattern(A::AbstractMatrix; symmetric_pattern::Bool=false) =
101+
bidirectional_pattern(A::AbstractMatrix; symmetric_pattern::Bool) =
102102
bidirectional_pattern(SparsityPatternCSC(SparseMatrixCSC(A)); symmetric_pattern)
103103

104-
function bidirectional_pattern(
105-
S::SparsityPatternCSC{T}; symmetric_pattern::Bool=false
106-
) where {T<:Integer}
104+
function bidirectional_pattern(S::SparsityPatternCSC{T}; symmetric_pattern::Bool) where {T}
107105
m, n = size(S)
108106
p = m + n
109107
nnzS = nnz(S)
@@ -175,7 +173,7 @@ function bidirectional_pattern(
175173
return S_and_Sᵀ, edge_to_index
176174
end
177175

178-
function build_edge_to_index(S::SparsityPatternCSC{T}) where {T<:Integer}
176+
function build_edge_to_index(S::SparsityPatternCSC{T}) where {T}
179177
# edge_to_index gives an index for each edge
180178
edge_to_index = Vector{T}(undef, nnz(S))
181179
offsets = zeros(T, S.n)
@@ -196,7 +194,7 @@ function build_edge_to_index(S::SparsityPatternCSC{T}) where {T<:Integer}
196194
end
197195
end
198196
end
199-
return edge_to_index, offsets
197+
return edge_to_index
200198
end
201199

202200
## Adjacency graph
@@ -219,7 +217,6 @@ The adjacency graph of a symmetric matrix `A ∈ ℝ^{n × n}` is `G(A) = (V, E)
219217
220218
- `S::SparsityPatternCSC{T}`: Underlying sparsity pattern, whose diagonal is empty whenever `has_diagonal` is `false`
221219
- `edge_to_index::Vector{T}`: A vector mapping each nonzero of `S` to a unique edge index (ignoring diagonal and accounting for symmetry, so that `(i, j)` and `(j, i)` get the same index)
222-
- `vertex_buffer::Vector{T}`: A buffer of length `S.n`, which is the number of vertices in the graph
223220
224221
# References
225222
@@ -228,28 +225,14 @@ The adjacency graph of a symmetric matrix `A ∈ ℝ^{n × n}` is `G(A) = (V, E)
228225
struct AdjacencyGraph{T,has_diagonal}
229226
S::SparsityPatternCSC{T}
230227
edge_to_index::Vector{T}
231-
vertex_buffer::Vector{T}
232228
end
233229

234230
function AdjacencyGraph(
235231
S::SparsityPatternCSC{T},
236-
edge_to_index::Vector{T},
237-
vertex_buffer::Vector{T};
232+
edge_to_index::Vector{T}=build_edge_to_index(S);
238233
has_diagonal::Bool=true,
239234
) where {T}
240-
return AdjacencyGraph{T,has_diagonal}(S, edge_to_index, vertex_buffer)
241-
end
242-
243-
function AdjacencyGraph(
244-
S::SparsityPatternCSC{T}, edge_to_index::Vector{T}; has_diagonal::Bool=true
245-
) where {T}
246-
vertex_buffer = Vector{Int}(undef, S.n)
247-
return AdjacencyGraph(S, edge_to_index, vertex_buffer; has_diagonal)
248-
end
249-
250-
function AdjacencyGraph(S::SparsityPatternCSC; has_diagonal::Bool=true)
251-
edge_to_index, vertex_buffer = build_edge_to_index(S)
252-
return AdjacencyGraph(S, edge_to_index, vertex_buffer; has_diagonal)
235+
return AdjacencyGraph{T,has_diagonal}(S, edge_to_index)
253236
end
254237

255238
function AdjacencyGraph(A::SparseMatrixCSC; has_diagonal::Bool=true)

src/result.jl

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,15 +241,12 @@ end
241241
function StarSetColoringResult(
242242
A::AbstractMatrix, ag::AdjacencyGraph, color::Vector{Int}, star_set::StarSet
243243
)
244-
# Reuse edge_to_index to store the compressed indices for decompression
245-
edge_to_index = edge_indices(ag)
246-
compressed_indices = edge_to_index
247-
248244
(; star, hub) = star_set
249245
S = pattern(ag)
250246
n = S.n
251247
group = group_by_color(color)
252248
rvS = rowvals(S)
249+
compressed_indices = zeros(Int, nnz(S)) # needs to be independent from the storage in the graph, in case the graph gets reused
253250
for j in axes(S, 2)
254251
for k in nzrange(S, j)
255252
i = rvS[k]

test/graph.jl

Lines changed: 59 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,42 +16,47 @@ using Test
1616

1717
@testset "SparsityPatternCSC" begin
1818
@testset "Transpose" begin
19-
@test all(1:1000) do _
19+
for _ in 1:1000
2020
m, n = rand(100:1000), rand(100:1000)
2121
p = 0.05 * rand()
2222
A = sprand(m, n, p)
2323
S = SparsityPatternCSC(A)
2424
Sᵀ = transpose(S)
2525
Sᵀ_true = SparsityPatternCSC(sparse(transpose(A)))
26-
Sᵀ.colptr == Sᵀ_true.colptr && Sᵀ.rowval == Sᵀ_true.rowval
26+
@test Sᵀ.colptr == Sᵀ_true.colptr
27+
@test Sᵀ.rowval == Sᵀ_true.rowval
2728
end
2829
end
2930
@testset "Bidirectional" begin
3031
@testset "symmetric_pattern = false" begin
31-
m, n = rand(100:1000), rand(100:1000)
32-
p = 0.05 * rand()
33-
A = sprand(Bool, m, n, p)
34-
A_and_Aᵀ = [spzeros(Bool, n, n) transpose(A); A spzeros(Bool, m, m)]
35-
S_and_Sᵀ, edge_to_index = bidirectional_pattern(A; symmetric_pattern=false)
36-
@test S_and_Sᵀ.colptr == A_and_Aᵀ.colptr
37-
@test S_and_Sᵀ.rowval == A_and_Aᵀ.rowval
38-
M = SparseMatrixCSC(
39-
m + n, m + n, S_and_Sᵀ.colptr, S_and_Sᵀ.rowval, edge_to_index
40-
)
41-
@test issymmetric(M)
32+
for _ in 1:1000
33+
m, n = rand(100:1000), rand(100:1000)
34+
p = 0.05 * rand()
35+
A = sprand(Bool, m, n, p)
36+
A_and_Aᵀ = [spzeros(Bool, n, n) transpose(A); A spzeros(Bool, m, m)]
37+
S_and_Sᵀ, edge_to_index = bidirectional_pattern(A; symmetric_pattern=false)
38+
@test S_and_Sᵀ.colptr == A_and_Aᵀ.colptr
39+
@test S_and_Sᵀ.rowval == A_and_Aᵀ.rowval
40+
M = SparseMatrixCSC(
41+
m + n, m + n, S_and_Sᵀ.colptr, S_and_Sᵀ.rowval, edge_to_index
42+
)
43+
@test issymmetric(M)
44+
end
4245
end
4346
@testset "symmetric_pattern = true" begin
44-
m = rand(100:1000)
45-
p = 0.05 * rand()
46-
A = sparse(Symmetric(sprand(Bool, m, m, p)))
47-
A_and_Aᵀ = [spzeros(Bool, m, m) transpose(A); A spzeros(Bool, m, m)]
48-
S_and_Sᵀ, edge_to_index = bidirectional_pattern(A; symmetric_pattern=true)
49-
@test S_and_Sᵀ.colptr == A_and_Aᵀ.colptr
50-
@test S_and_Sᵀ.rowval == A_and_Aᵀ.rowval
51-
M = SparseMatrixCSC(
52-
2 * m, 2 * m, S_and_Sᵀ.colptr, S_and_Sᵀ.rowval, edge_to_index
53-
)
54-
@test issymmetric(M)
47+
for _ in 1:1000
48+
m = rand(100:1000)
49+
p = 0.05 * rand()
50+
A = sparse(Symmetric(sprand(Bool, m, m, p)))
51+
A_and_Aᵀ = [spzeros(Bool, m, m) transpose(A); A spzeros(Bool, m, m)]
52+
S_and_Sᵀ, edge_to_index = bidirectional_pattern(A; symmetric_pattern=true)
53+
@test S_and_Sᵀ.colptr == A_and_Aᵀ.colptr
54+
@test S_and_Sᵀ.rowval == A_and_Aᵀ.rowval
55+
M = SparseMatrixCSC(
56+
2 * m, 2 * m, S_and_Sᵀ.colptr, S_and_Sᵀ.rowval, edge_to_index
57+
)
58+
@test issymmetric(M)
59+
end
5560
end
5661
end
5762
@testset "size" begin
@@ -137,15 +142,35 @@ end;
137142
0 0 0 1 1 1 1 0
138143
])
139144

140-
B = transpose(A) * A
141-
g = AdjacencyGraph(B - Diagonal(B))
145+
g = AdjacencyGraph(transpose(A) * A)
142146
@test nb_vertices(g) == 8
143-
@test collect(neighbors(g, 1)) == [6, 7, 8]
144-
@test collect(neighbors(g, 2)) == [5, 7, 8]
145-
@test collect(neighbors(g, 3)) == [5, 6, 8]
146-
@test collect(neighbors(g, 4)) == [5, 6, 7]
147-
@test collect(neighbors(g, 5)) == [2, 3, 4, 6, 7, 8]
148-
@test collect(neighbors(g, 6)) == [1, 3, 4, 5, 7, 8]
149-
@test collect(neighbors(g, 7)) == [1, 2, 4, 5, 6, 8]
150-
@test collect(neighbors(g, 8)) == [1, 2, 3, 5, 6, 7]
147+
# wrong neighbors, it's okay they are filtered after
148+
@test collect(neighbors(g, 1)) == sort(vcat(1, [6, 7, 8]))
149+
@test collect(neighbors(g, 2)) == sort(vcat(2, [5, 7, 8]))
150+
@test collect(neighbors(g, 3)) == sort(vcat(3, [5, 6, 8]))
151+
@test collect(neighbors(g, 4)) == sort(vcat(4, [5, 6, 7]))
152+
@test collect(neighbors(g, 5)) == sort(vcat(5, [2, 3, 4, 6, 7, 8]))
153+
@test collect(neighbors(g, 6)) == sort(vcat(6, [1, 3, 4, 5, 7, 8]))
154+
@test collect(neighbors(g, 7)) == sort(vcat(7, [1, 2, 4, 5, 6, 8]))
155+
@test collect(neighbors(g, 8)) == sort(vcat(8, [1, 2, 3, 5, 6, 7]))
156+
# right degree
157+
@test degree(g, 1) == 3
158+
@test degree(g, 2) == 3
159+
@test degree(g, 3) == 3
160+
@test degree(g, 4) == 3
161+
@test degree(g, 5) == 6
162+
@test degree(g, 6) == 6
163+
@test degree(g, 7) == 6
164+
@test degree(g, 8) == 6
165+
166+
g = AdjacencyGraph(transpose(A) * A; has_diagonal=false)
167+
# wrong degree
168+
@test degree(g, 1) == 4
169+
@test degree(g, 2) == 4
170+
@test degree(g, 3) == 4
171+
@test degree(g, 4) == 4
172+
@test degree(g, 5) == 7
173+
@test degree(g, 6) == 7
174+
@test degree(g, 7) == 7
175+
@test degree(g, 8) == 7
151176
end

0 commit comments

Comments
 (0)