Skip to content

Commit db7eaa4

Browse files
committed
Store edge index in AdjacencyGraph & get rid of dicts
1 parent b33d436 commit db7eaa4

3 files changed

Lines changed: 91 additions & 43 deletions

File tree

src/coloring.jl

Lines changed: 52 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,19 @@ function star_coloring(g::AdjacencyGraph, order::AbstractOrder; postprocessing::
8282
ne = nb_edges(g)
8383
color = zeros(Int, nv)
8484
forbidden_colors = zeros(Int, nv)
85-
first_neighbor = fill((0, 0), nv) # at first no neighbors have been encountered
85+
first_neighbor = fill((0, 0, -1), nv) # at first no neighbors have been encountered
8686
treated = zeros(Int, nv)
87-
star = Dict{Tuple{Int,Int},Int}()
88-
sizehint!(star, ne)
87+
star = Vector{Int}(undef, ne)
8988
hub = Int[] # one hub for each star, including the trivial ones
9089
nb_spokes = Int[] # number of spokes for each star
9190
vertices_in_order = vertices(g, order)
9291

9392
for v in vertices_in_order
94-
for w in neighbors(g, v)
93+
for (w, e_vw) in neighbors_and_edgeinds(g, v)
94+
w == v && continue
9595
iszero(color[w]) && continue
9696
forbidden_colors[color[w]] = v
97-
(p, q) = first_neighbor[color[w]]
97+
(p, q, _) = first_neighbor[color[w]]
9898
if p == v # Case 1
9999
if treated[q] != v
100100
# forbid colors of neighbors of q
@@ -103,11 +103,11 @@ function star_coloring(g::AdjacencyGraph, order::AbstractOrder; postprocessing::
103103
# forbid colors of neighbors of w
104104
_treat!(treated, forbidden_colors, g, v, w, color)
105105
else
106-
first_neighbor[color[w]] = (v, w)
107-
for x in neighbors(g, w)
106+
first_neighbor[color[w]] = (v, w, e_vw)
107+
for (x, e_wx) in neighbors_and_edgeinds(g, w)
108+
x == w && continue
108109
(x == v || iszero(color[x])) && continue
109-
wx = _sort(w, x)
110-
if x == hub[star[wx]] # potential Case 2 (which is always false for trivial stars with two vertices, since the associated hub is negative)
110+
if x == hub[star[e_wx]] # potential Case 2 (which is always false for trivial stars with two vertices, since the associated hub is negative)
111111
forbidden_colors[color[x]] = v
112112
end
113113
end
@@ -121,7 +121,7 @@ function star_coloring(g::AdjacencyGraph, order::AbstractOrder; postprocessing::
121121
end
122122
_update_stars!(star, hub, nb_spokes, g, v, color, first_neighbor)
123123
end
124-
star_set = StarSet(star, hub, nb_spokes)
124+
star_set = StarSet(g, star, hub, nb_spokes)
125125
if postprocessing
126126
# Reuse the vector forbidden_colors to compute offsets during post-processing
127127
postprocess!(color, star_set, g, forbidden_colors)
@@ -140,30 +140,40 @@ $TYPEDFIELDS
140140
"""
141141
struct StarSet
142142
"a mapping from edges (pair of vertices) to their star index"
143-
star::Dict{Tuple{Int,Int},Int}
143+
star::Vector{Int}
144144
"a mapping from star indices to their hub (undefined hubs for single-edge stars are the negative value of one of the vertices, picked arbitrarily)"
145145
hub::Vector{Int}
146146
"a mapping from star indices to the vector of their spokes"
147147
spokes::Vector{Vector{Int}}
148148
end
149149

150-
function StarSet(star::Dict{Tuple{Int,Int},Int}, hub::Vector{Int}, nb_spokes::Vector{Int})
150+
function StarSet(
151+
g::AdjacencyGraph, star::Vector{Int}, hub::Vector{Int}, nb_spokes::Vector{Int}
152+
)
153+
(; S, edgeindex) = g
151154
# Create a list of spokes for each star, preallocating their sizes based on nb_spokes
152155
spokes = [Vector{Int}(undef, ns) for ns in nb_spokes]
153156

154157
# Reuse nb_spokes as counters to track the current index while filling the spokes
155158
fill!(nb_spokes, 0)
156159

157-
for ((i, j), s) in pairs(star)
158-
h = abs(hub[s])
159-
nb_spokes[s] += 1
160-
index = nb_spokes[s]
161-
162-
# Assign the non-hub vertex (spoke) to the correct position in spokes
163-
if i == h
164-
spokes[s][index] = j
165-
elseif j == h
166-
spokes[s][index] = i
160+
rvS = rowvals(S)
161+
for j in axes(S, 2)
162+
for k in nzrange(S, j)
163+
i = rvS[k]
164+
i >= j && continue
165+
e = edgeindex[k]
166+
s = star[e]
167+
h = abs(hub[s])
168+
nb_spokes[s] += 1
169+
index = nb_spokes[s]
170+
171+
# Assign the non-hub vertex (spoke) to the correct position in spokes
172+
if i == h
173+
spokes[s][index] = j
174+
elseif j == h
175+
spokes[s][index] = i
176+
end
167177
end
168178
end
169179
return StarSet(star, hub, spokes)
@@ -182,6 +192,7 @@ function _treat!(
182192
color::AbstractVector{<:Integer},
183193
)
184194
for x in neighbors(g, w)
195+
x == w && continue
185196
iszero(color[x]) && continue
186197
forbidden_colors[color[x]] = v
187198
end
@@ -191,7 +202,7 @@ end
191202

192203
function _update_stars!(
193204
# modified
194-
star::Dict{<:Tuple,<:Integer},
205+
star::Vector{Int},
195206
hub::AbstractVector{<:Integer},
196207
nb_spokes::AbstractVector{<:Integer},
197208
# not modified
@@ -200,33 +211,32 @@ function _update_stars!(
200211
color::AbstractVector{<:Integer},
201212
first_neighbor::AbstractVector{<:Tuple},
202213
)
203-
for w in neighbors(g, v)
214+
for (w, e_vw) in neighbors_and_edgeinds(g, v)
215+
w == v && continue
204216
iszero(color[w]) && continue
205-
vw = _sort(v, w)
206217
x_exists = false
207-
for x in neighbors(g, w)
218+
for (x, e_wx) in neighbors_and_edgeinds(g, w)
219+
x == w && continue
208220
if x != v && color[x] == color[v] # vw, wx ∈ E
209-
wx = _sort(w, x)
210-
star_wx = star[wx]
221+
star_wx = star[e_wx]
211222
hub[star_wx] = w # this may already be true
212223
nb_spokes[star_wx] += 1
213-
star[vw] = star_wx
224+
star[e_vw] = star_wx
214225
x_exists = true
215226
break
216227
end
217228
end
218229
if !x_exists
219-
(p, q) = first_neighbor[color[w]]
230+
(p, q, e_pq) = first_neighbor[color[w]]
220231
if p == v && q != w # vw, vq ∈ E and color[w] = color[q]
221-
vq = _sort(v, q)
222-
star_vq = star[vq]
232+
star_vq = star[e_pq]
223233
hub[star_vq] = v # this may already be true
224234
nb_spokes[star_vq] += 1
225-
star[vw] = star_vq
235+
star[e_vw] = star_vq
226236
else # vw forms a new star
227237
push!(hub, -max(v, w)) # star is trivial (composed only of two vertices) so we set the hub to a negative value, but it allows us to choose one of the two vertices
228238
push!(nb_spokes, 1)
229-
star[vw] = length(hub)
239+
star[e_vw] = length(hub)
230240
end
231241
end
232242
end
@@ -249,7 +259,7 @@ This function corresponds to algorithm `DirectRecover2` in the paper.
249259
> [_Efficient Computation of Sparse Hessians Using Coloring and Automatic Differentiation_](https://pubsonline.informs.org/doi/abs/10.1287/ijoc.1080.0286), Gebremedhin et al. (2009), Figure 3
250260
"""
251261
function symmetric_coefficient(
252-
i::Integer, j::Integer, color::AbstractVector{<:Integer}, star_set::StarSet
262+
i::Integer, j::Integer, e::Integer, color::AbstractVector{<:Integer}, star_set::StarSet
253263
)
254264
(; star, hub) = star_set
255265
if i == j
@@ -260,7 +270,7 @@ function symmetric_coefficient(
260270
# star only contains one triangle
261271
i, j = j, i
262272
end
263-
star_id = star[i, j]
273+
star_id = star[e]
264274
h = abs(hub[star_id])
265275
if h == j
266276
# i is the spoke
@@ -307,12 +317,15 @@ function acyclic_coloring(g::AdjacencyGraph, order::AbstractOrder; postprocessin
307317

308318
for v in vertices_in_order
309319
for w in neighbors(g, v)
320+
w == v && continue
310321
iszero(color[w]) && continue
311322
forbidden_colors[color[w]] = v
312323
end
313324
for w in neighbors(g, v)
325+
w == v && continue
314326
iszero(color[w]) && continue
315327
for x in neighbors(g, w)
328+
x == w && continue
316329
iszero(color[x]) && continue
317330
if forbidden_colors[color[x]] != v
318331
_prevent_cycle!(
@@ -328,12 +341,15 @@ function acyclic_coloring(g::AdjacencyGraph, order::AbstractOrder; postprocessin
328341
end
329342
end
330343
for w in neighbors(g, v) # grow two-colored stars around the vertex v
344+
w == v && continue
331345
iszero(color[w]) && continue
332346
_grow_star!(v, w, color, first_neighbor, forest)
333347
end
334348
for w in neighbors(g, v)
349+
w == v && continue
335350
iszero(color[w]) && continue
336351
for x in neighbors(g, w)
352+
x == w && continue
337353
(x == v || iszero(color[x])) && continue
338354
if color[x] == color[v]
339355
_merge_trees!(v, w, x, forest) # merge trees T₁ ∋ vw and T₂ ∋ wx if T₁ != T₂

src/graph.jl

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,17 +182,44 @@ The adjacency graph of a symmetric matrix `A ∈ ℝ^{n × n}` is `G(A) = (V, E)
182182
# Fields
183183
184184
- `S::SparsityPatternCSC{T}`: Underlying sparsity pattern, whose diagonal is empty whenever `has_diagonal` is `false`
185+
- `edgeindex::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)
185186
186187
# References
187188
188189
> [_What Color Is Your Jacobian? SparsityPatternCSC Coloring for Computing Derivatives_](https://epubs.siam.org/doi/10.1137/S0036144504444711), Gebremedhin et al. (2005)
189190
"""
190191
struct AdjacencyGraph{T,has_diagonal}
191192
S::SparsityPatternCSC{T}
193+
edgeindex::Vector{T}
194+
end
195+
196+
function build_edgeindex(S::SparsityPatternCSC{T}) where {T}
197+
offsets = zeros(T, size(S, 1))
198+
edgeindex = Vector{T}(undef, nnz(S))
199+
counter = zero(T)
200+
rvS = rowvals(S)
201+
for j in axes(S, 2)
202+
for k in nzrange(S, j)
203+
i = rvS[k]
204+
if i > j
205+
counter += 1
206+
# index lower triangle
207+
edgeindex[k] = counter
208+
# index upper triangle
209+
k2 = S.colptr[i] + offsets[i]
210+
edgeindex[k2] = counter
211+
offsets[i] += 1
212+
elseif i == j
213+
# this should never be used, make sure it errors
214+
edgeindex[k] = -1
215+
end
216+
end
217+
end
218+
return edgeindex
192219
end
193220

194221
function AdjacencyGraph(S::SparsityPatternCSC{T}; has_diagonal::Bool=true) where {T}
195-
return AdjacencyGraph{T,has_diagonal}(S)
222+
return AdjacencyGraph{T,has_diagonal}(S, build_edgeindex(S))
196223
end
197224

198225
function AdjacencyGraph(A::SparseMatrixCSC; has_diagonal::Bool=true)
@@ -204,21 +231,23 @@ function AdjacencyGraph(A::AbstractMatrix; has_diagonal::Bool=true)
204231
end
205232

206233
pattern(g::AdjacencyGraph) = g.S
234+
edgeindex(g::AdjacencyGraph) = g.edgeindex
207235
nb_vertices(g::AdjacencyGraph) = pattern(g).n
208236
vertices(g::AdjacencyGraph) = 1:nb_vertices(g)
209237

210238
has_diagonal(::AdjacencyGraph{T,hl}) where {T,hl} = hl
211239

212-
function neighbors(g::AdjacencyGraph{T,true}, v::Integer) where {T}
240+
function neighbors(g::AdjacencyGraph{T}, v::Integer) where {T}
213241
S = pattern(g)
214242
neighbors_v = view(rowvals(S), nzrange(S, v))
215-
return Iterators.filter(!=(v), neighbors_v) # TODO: optimize
243+
return neighbors_v # includes diagonal
216244
end
217245

218-
function neighbors(g::AdjacencyGraph{T,false}, v::Integer) where {T}
246+
function neighbors_and_edgeinds(g::AdjacencyGraph{T}, v::Integer) where {T}
219247
S = pattern(g)
220248
neighbors_v = view(rowvals(S), nzrange(S, v))
221-
return neighbors_v
249+
einds = view(edgeindex(g), nzrange(S, v))
250+
return zip(neighbors_v, einds) # includes diagonal
222251
end
223252

224253
function degree(g::AdjacencyGraph, v::Integer)
@@ -235,6 +264,7 @@ function nb_edges(g::AdjacencyGraph)
235264
ne = 0
236265
for v in vertices(g)
237266
for u in neighbors(g, v)
267+
u == v && continue
238268
ne += 1
239269
end
240270
end
@@ -246,6 +276,7 @@ minimum_degree(g::AdjacencyGraph) = minimum(Base.Fix1(degree, g), vertices(g))
246276

247277
function has_neighbor(g::AdjacencyGraph, v::Integer, u::Integer)
248278
for w in neighbors(g, v)
279+
w == v && continue
249280
if w == u
250281
return true
251282
end

src/result.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,15 +241,16 @@ end
241241
function StarSetColoringResult(
242242
A::AbstractMatrix, ag::AdjacencyGraph, color::Vector{Int}, star_set::StarSet
243243
)
244-
S = ag.S
244+
(; S, edgeindex) = ag
245245
group = group_by_color(color)
246246
n = size(S, 1)
247247
rv = rowvals(S)
248248
compressed_indices = zeros(Int, nnz(S))
249249
for j in axes(S, 2)
250250
for k in nzrange(S, j)
251251
i = rv[k]
252-
l, c = symmetric_coefficient(i, j, color, star_set)
252+
e = edgeindex[k]
253+
l, c = symmetric_coefficient(i, j, e, color, star_set)
253254
# A[i, j] = B[l, c]
254255
compressed_indices[k] = (c - 1) * n + l
255256
end

0 commit comments

Comments
 (0)