Skip to content

Commit 9ffdeb6

Browse files
committed
🚧 ✨ SparseSymmetric.
1 parent ce87010 commit 9ffdeb6

File tree

2 files changed

+236
-45
lines changed

2 files changed

+236
-45
lines changed

‎src/Networks/topologies.jl‎

Lines changed: 128 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ Regarding incident classes:
1414
- Foreign: the two classes differ.
1515
- Reflexive: source class and target class are the same class.
1616
- Symmetric: reflexive + edges are undirected ('a' points to 'b' => 'b' points to 'a').
17+
In this *bidirectional* situation: the number of edges
18+
is the number of conceptual *undirected* edges,
19+
And their order is the row-wise lower triangular only: (source <= target) pairs.
1720
1821
Regarding edges density:
1922
@@ -94,10 +97,13 @@ Raise flag to skip over targets with no sources.
9497
backward(::Topology; skip = false) = throw("unimplemented")
9598
export backward
9699

100+
Map = OrderedDict{Int,Int} # Used by the sparse variants.
101+
vecmap(n::Int) = [Map() for _ in 1:n]
102+
97103
# ==========================================================================================
98104
struct SparseForeign <: Topology
99-
forward::Vector{OrderedDict{Int,Int}} # [source: {target: edge}]
100-
backward::Vector{OrderedDict{Int,Int}} # [target: {source: edge}]
105+
forward::Vector{Map} # [source: {target: edge}]
106+
backward::Vector{Map} # [target: {source: edge}]
101107
n_edges::Int
102108
end
103109
export SparseForeign
@@ -114,11 +120,12 @@ n_targets(s::S, src::Int) = length(s.forward[src])
114120
is_edge(s::S, src::Int, tgt::Int) = haskey(s.forward[src], tgt)
115121
n_edges(s::S) = s.n_edges
116122
edge(s::S, src::Int, tgt::Int) = s.forward[src][tgt]
117-
edges(s::S) = I.flatten(I.map(enumerate(s.forward)) do (src, targets)
118-
I.map(keys(targets)) do tgt
119-
(src, tgt)
123+
edges(s::S) =
124+
I.flatmap(enumerate(s.forward)) do (src, targets)
125+
I.map(keys(targets)) do tgt
126+
(src, tgt)
127+
end
120128
end
121-
end)
122129
forward(s::S; skip = false) =
123130
filter_map(enumerate(s.forward)) do (src, targets)
124131
(skip && isempty(targets)) ? nothing : Some((src, I.map(targets) do (tgt, edge)
@@ -140,29 +147,29 @@ Construct from non-empty entries in a sparse matrix (disregarding values).
140147
"""
141148
function SparseForeign(m::AbstractSparseMatrix)
142149
n_sources, n_targets = size(m)
143-
(sources, targets, _) = findnz(m)
144-
n_edges = length(sources)
145-
# Restore row-wise ordering.
146-
o = sortperm(sources)
147-
sources, targets = sources[o], targets[o]
148-
D = OrderedDict{Int,Int}
149-
forward = [D() for _ in 1:n_sources]
150-
backward = [D() for _ in 1:n_targets]
150+
(sources, targets, n_edges) = rowwise(m)
151+
(forward, backward) = vecmap.((n_sources, n_targets))
151152
for (edge, (source, target)) in enumerate(zip(sources, targets))
152153
forward[source][target] = edge
153154
backward[target][source] = edge
154155
end
155156
SparseForeign(forward, backward, n_edges)
156157
end
158+
# Extract data from sparse matrix with *row-wise* ordering.
159+
function rowwise(m::AbstractSparseMatrix)
160+
(sources, targets, _) = findnz(m)
161+
n_edges = length(sources)
162+
o = sortperm(sources)
163+
(sources[o], targets[o], n_edges)
164+
end
165+
157166

158167
"""
159168
Construct from lit entries in a dense boolean matrix.
160169
"""
161170
function SparseForeign(m::AbstractMatrix{Bool})
162171
n_sources, n_targets = size(m)
163-
D = OrderedDict{Int,Int}
164-
forward = [D() for _ in 1:n_sources]
165-
backward = [D() for _ in 1:n_targets]
172+
(forward, backward) = vecmap.((n_sources, n_targets))
166173
n_edges = 0
167174
for source in 1:n_sources, target in 1:n_targets
168175
m[source, target] || continue
@@ -176,7 +183,7 @@ end
176183
# ==========================================================================================
177184
struct SparseReflexive <: Topology
178185
# [node: ({source: edge}, {target: edge})]
179-
nodes::Vector{Tuple{OrderedDict{Int,Int},OrderedDict{Int,Int}}}
186+
nodes::Vector{Tuple{Map,Map}}
180187
n_edges::Int
181188
end
182189
export SparseReflexive
@@ -186,19 +193,20 @@ export SparseReflexive
186193
S = SparseReflexive
187194
targets(s::S, src::Int) = I.map(p -> (first(p), last(p)), s.nodes[src][2])
188195
sources(s::S, tgt::Int) = I.map(p -> (first(p), last(p)), s.nodes[tgt][1])
189-
n_sources(s::S) = length(s.nodes)
190-
n_targets(s::S) = n_sources(s)
196+
n_sources(s::S) = n_nodes(s)
197+
n_targets(s::S) = n_nodes(s)
191198
n_sources(s::S, tgt::Int) = length(s.nodes[tgt][1])
192199
n_targets(s::S, src::Int) = length(s.nodes[src][2])
193200
is_edge(s::S, src::Int, tgt::Int) = haskey(s.nodes[src][2], tgt)
194201
n_edges(s::S) = s.n_edges
195202
edge(s::S, src::Int, tgt::Int) = s.nodes[src][2][tgt]
196-
edges(s::S) = I.flatten(I.map(enumerate(s.nodes)) do (src, neighbours)
197-
(_, targets) = neighbours
198-
I.map(keys(targets)) do tgt
199-
(src, tgt)
203+
edges(s::S) =
204+
I.flatmap(enumerate(s.nodes)) do (src, neighbours)
205+
(_, targets) = neighbours
206+
I.map(keys(targets)) do tgt
207+
(src, tgt)
208+
end
200209
end
201-
end)
202210
forward(s::S; skip = false) =
203211
filter_map(enumerate(s.nodes)) do (src, (_, targets))
204212
(skip && isempty(targets)) ? nothing : Some((src, I.map(targets) do (tgt, edge)
@@ -212,6 +220,11 @@ backward(s::S; skip = false) =
212220
end))
213221
end
214222

223+
#-------------------------------------------------------------------------------------------
224+
# Extra dedicated interface.
225+
n_nodes(s::S) = length(s.nodes)
226+
export n_nodes
227+
215228
#-------------------------------------------------------------------------------------------
216229
# Construct.
217230

@@ -220,13 +233,8 @@ Construct from non-empty entries in a sparse matrix (disregarding values).
220233
"""
221234
function SparseReflexive(m::AbstractSparseMatrix)
222235
n_nodes = check_square(m)
223-
(sources, targets, _) = findnz(m)
224-
n_edges = length(sources)
225-
# Restore row-wise ordering.
226-
o = sortperm(sources)
227-
sources, targets = sources[o], targets[o]
228-
D = OrderedDict{Int,Int}
229-
nodes = [(D(), D()) for _ in 1:n_nodes]
236+
(sources, targets, n_edges) = rowwise(m)
237+
nodes = [(Map(), Map()) for _ in 1:n_nodes]
230238
for (edge, (source, target)) in enumerate(zip(sources, targets))
231239
nodes[source][2][target] = edge
232240
nodes[target][1][source] = edge
@@ -246,8 +254,7 @@ Construct from lit entries in a dense boolean matrix.
246254
"""
247255
function SparseReflexive(m::AbstractMatrix{Bool})
248256
n_nodes = check_square(m)
249-
D = OrderedDict{Int,Int}
250-
nodes = [(D(), D()) for _ in 1:n_nodes]
257+
nodes = [(Map(), Map()) for _ in 1:n_nodes]
251258
n_edges = 0
252259
for source in 1:n_nodes, target in 1:n_nodes
253260
m[source, target] || continue
@@ -257,3 +264,90 @@ function SparseReflexive(m::AbstractMatrix{Bool})
257264
end
258265
SparseReflexive(nodes, n_edges)
259266
end
267+
268+
# ==========================================================================================
269+
struct SparseSymmetric <: Topology
270+
nodes::Vector{Map} # [node: {neighbour: edge}]
271+
n_edges::Int
272+
end
273+
export SparseSymmetric
274+
275+
#-------------------------------------------------------------------------------------------
276+
# Duties to Topology.
277+
S = SparseSymmetric # "Self"
278+
targets(s::S, src::Int) = I.map(p -> (first(p), last(p)), s.nodes[src])
279+
sources(s::S, tgt::Int) = targets(s, tgt)
280+
n_sources(s::S) = n_nodes(s)
281+
n_targets(s::S) = n_nodes(s)
282+
n_sources(s::S, tgt::Int) = n_neighbours(s, tgt)
283+
n_targets(s::S, src::Int) = n_neighbours(s, src)
284+
is_edge(s::S, src::Int, tgt::Int) = haskey(s.nodes[src], tgt) # Accept both directions.
285+
n_edges(s::S) = s.n_edges
286+
edge(s::S, src::Int, tgt::Int) = s.nodes[src][tgt]
287+
edges(s::S) =
288+
I.flatmap(enumerate(s.nodes)) do (src, targets)
289+
I.map(stopwhen(>(src), keys(targets))) do tgt
290+
(src, tgt)
291+
end
292+
end
293+
forward(s::S; skip = false) = adjacency(s; skip)
294+
backward(s::S; skip = false) = adjacency(s; skip)
295+
296+
#-------------------------------------------------------------------------------------------
297+
# Extra dedicated interface.
298+
n_nodes(s::S) = length(s.nodes)
299+
neighbours(s::S, src::Int) = I.map(p -> (first(p), last(p)), s.nodes[src])
300+
neighbour_nodes(s::S, src::Int) = I.map(first, neighbours(s, src))
301+
neighbour_edges(s::S, src::Int) = I.map(last, neighbours(s, src))
302+
n_neighbours(s::S, n::Int) = length(s.nodes[n])
303+
# Lower the 'upper' flag to only get lower triangle and have every edge yielded only once.
304+
adjacency(s::S; skip = false, upper = true) =
305+
filter_map(enumerate(s.nodes)) do (src, neighbours)
306+
(skip && (isempty(neighbours) || !upper && first(keys(neighbours)) > src)) ?
307+
nothing :
308+
Some((
309+
src,
310+
I.map(stopwhen(((tgt, _),) -> !upper && tgt > src, neighbours)) do (ngb, edge)
311+
(ngb, edge)
312+
end,
313+
))
314+
end
315+
export neighbours, neighbour_nodes, neighbour_edges, n_neighbours, adjacency
316+
317+
#-------------------------------------------------------------------------------------------
318+
# Construct.
319+
320+
"""
321+
Construct from non-empty entries in a lower-triangular sparse matrix
322+
(disregarding values and upper triangle).
323+
"""
324+
function SparseSymmetric(m::AbstractSparseMatrix)
325+
n_nodes = check_square(m)
326+
(sources, targets, _) = rowwise(m)
327+
nodes = vecmap(n_nodes)
328+
n_edges = 0
329+
for (source, target) in zip(sources, targets)
330+
source < target && continue # Dismiss upper triangle.
331+
n_edges += 1
332+
nodes[source][target] = n_edges
333+
nodes[target][source] = n_edges
334+
end
335+
SparseSymmetric(nodes, n_edges)
336+
end
337+
338+
"""
339+
Construct from lit entries in a lower-triangular dense boolean matrix
340+
(disregarding upper triangle).
341+
"""
342+
function SparseSymmetric(m::AbstractMatrix{Bool})
343+
n_nodes = check_square(m)
344+
nodes = vecmap(n_nodes)
345+
n_edges = 0
346+
for source in 1:n_nodes, target in 1:source # Triangular iteration.
347+
m[source, target] || continue
348+
n_edges += 1
349+
nodes[source][target] = n_edges
350+
nodes[target][source] = n_edges
351+
end
352+
SparseSymmetric(nodes, n_edges)
353+
end

0 commit comments

Comments
 (0)