Skip to content

Commit 0f3b94d

Browse files
authored
Add connected_components, merge_vertices. Fix incident_edges.
2 parents a95cbe3 + 26e41f8 commit 0f3b94d

File tree

5 files changed

+266
-4
lines changed

5 files changed

+266
-4
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "NamedGraphs"
22
uuid = "678767b0-92e7-4007-89e4-4527a8725b19"
33
authors = ["Matthew Fishman <[email protected]> and contributors"]
4-
version = "0.1.4"
4+
version = "0.1.5"
55

66
[deps]
77
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"

src/Graphs/abstractgraph.jl

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,44 @@ function is_tree(graph::AbstractGraph)
150150
return (ne(graph) == nv(graph) - 1) && is_connected(graph)
151151
end
152152

153-
function incident_edges(graph::AbstractGraph, vertex)
153+
function out_incident_edges(graph::AbstractGraph, vertex)
154154
return [
155-
edgetype(graph)(vertex, neighbor_vertex) for neighbor_vertex in neighbors(graph, vertex)
155+
edgetype(graph)(vertex, neighbor_vertex) for neighbor_vertex in outneighbors(graph, vertex)
156156
]
157157
end
158158

159+
function in_incident_edges(graph::AbstractGraph, vertex)
160+
return [
161+
edgetype(graph)(neighbor_vertex, vertex) for neighbor_vertex in inneighbors(graph, vertex)
162+
]
163+
end
164+
165+
function all_incident_edges(graph::AbstractGraph, vertex)
166+
return out_incident_edges(graph, vertex) in_incident_edges(graph, vertex)
167+
end
168+
169+
"""
170+
incident_edges(graph::AbstractGraph, vertex; dir=:out)
171+
172+
Edges incident to the vertex `vertex`.
173+
174+
`dir ∈ (:in, :out, :both)`, defaults to `:out`.
175+
176+
For undirected graphs, returns all incident edges.
177+
178+
Like: https://juliagraphs.org/Graphs.jl/v1.7/algorithms/linalg/#Graphs.LinAlg.adjacency_matrix
179+
"""
180+
function incident_edges(graph::AbstractGraph, vertex; dir=:out)
181+
if dir == :out
182+
return out_incident_edges(graph, vertex)
183+
elseif dir == :in
184+
return in_incident_edges(graph, vertex)
185+
elseif dir == :both
186+
return all_incident_edges(graph, vertex)
187+
end
188+
return error("dir = $dir not supported.")
189+
end
190+
159191
# Get the leaf vertices of a tree-like graph
160192
#
161193
# For the directed case, could also use `AbstractTrees`:

src/NamedGraphs.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import Graphs:
2020
bfs_tree,
2121
blockdiag,
2222
common_neighbors,
23+
connected_components,
24+
connected_components!,
2325
degree,
2426
degree_histogram,
2527
dst,
@@ -38,6 +40,8 @@ import Graphs:
3840
is_directed,
3941
is_strongly_connected,
4042
is_weakly_connected,
43+
merge_vertices,
44+
merge_vertices!,
4145
ne,
4246
neighbors,
4347
neighborhood,

src/abstractnamedgraph.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ parent_graph(graph::AbstractNamedGraph) = not_implemented()
1212
# ?
1313
parent_graph_type(graph::AbstractNamedGraph) = not_implemented()
1414

15+
parent_vertextype(graph::AbstractNamedGraph) = vertextype(parent_graph(graph))
16+
1517
# Convert vertex to parent vertex
1618
# Inverse map of `parent_vertex_to_vertex`.
1719
vertex_to_parent_vertex(graph::AbstractNamedGraph, vertex) = not_implemented()
@@ -83,12 +85,20 @@ function vertices_to_parent_vertices(
8385
return map(vertex_to_parent_vertex(graph), vertices)
8486
end
8587

88+
function vertices_to_parent_vertices(graph::AbstractNamedGraph)
89+
return Base.Fix1(vertices_to_parent_vertices, graph)
90+
end
91+
8692
function parent_vertices_to_vertices(
8793
graph::AbstractNamedGraph, parent_vertices
8894
)
8995
return map(parent_vertex_to_vertex(graph), parent_vertices)
9096
end
9197

98+
function parent_vertices_to_vertices(graph::AbstractNamedGraph)
99+
return Base.Fix1(parent_vertices_to_vertices, graph)
100+
end
101+
92102
parent_vertices(graph::AbstractNamedGraph) = vertices(parent_graph(graph))
93103
parent_edges(graph::AbstractNamedGraph) = edges(parent_graph(graph))
94104
parent_edgetype(graph::AbstractNamedGraph) = edgetype(parent_graph(graph))
@@ -378,6 +388,36 @@ function adjacency_matrix(graph::AbstractNamedGraph, args...)
378388
return adjacency_matrix(parent_graph(graph), args...)
379389
end
380390

391+
function connected_components(graph::AbstractNamedGraph)
392+
parent_connected_components = connected_components(parent_graph(graph))
393+
return map(parent_vertices_to_vertices(graph), parent_connected_components)
394+
end
395+
396+
function merge_vertices!(graph::AbstractNamedGraph, merge_vertices; merged_vertex=first(merge_vertices))
397+
not_implemented()
398+
end
399+
400+
function merge_vertices(graph::AbstractNamedGraph, merge_vertices; merged_vertex=first(merge_vertices))
401+
merged_graph = copy(graph)
402+
if !has_vertex(graph, merged_vertex)
403+
add_vertex!(merged_graph, merged_vertex)
404+
end
405+
for vertex in merge_vertices
406+
for e in incident_edges(graph, vertex; dir=:both)
407+
merged_edge = rename_vertices(v -> v == vertex ? merged_vertex : v, e)
408+
if src(merged_edge) dst(merged_edge)
409+
add_edge!(merged_graph, merged_edge)
410+
end
411+
end
412+
end
413+
for vertex in merge_vertices
414+
if vertex merged_vertex
415+
rem_vertex!(merged_graph, vertex)
416+
end
417+
end
418+
return merged_graph
419+
end
420+
381421
#
382422
# Graph traversals
383423
#

test/test_namedgraph.jl

Lines changed: 187 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,82 @@ end
6363
@test has_path(g, "D", "E")
6464
@test !has_path(g, "A", "E")
6565
end
66+
@testset "neighborhood" begin
67+
g = named_grid((4, 4))
68+
@test issetequal(neighborhood(g, (1, 1), nv(g)), vertices(g))
69+
@test issetequal(neighborhood(g, (1, 1), 0), [(1, 1)])
70+
@test issetequal(neighborhood(g, (1, 1), 1), [(1, 1), (2, 1), (1, 2)])
71+
ns = [
72+
(1, 1),
73+
(2, 1),
74+
(1, 2),
75+
(3, 1),
76+
(2, 2),
77+
(1, 3),
78+
]
79+
@test issetequal(neighborhood(g, (1, 1), 2), ns)
80+
ns = [
81+
(1, 1),
82+
(2, 1),
83+
(1, 2),
84+
(3, 1),
85+
(2, 2),
86+
(1, 3),
87+
(4, 1),
88+
(3, 2),
89+
(2, 3),
90+
(1, 4),
91+
]
92+
@test issetequal(neighborhood(g, (1, 1), 3), ns)
93+
ns = [
94+
(1, 1),
95+
(2, 1),
96+
(1, 2),
97+
(3, 1),
98+
(2, 2),
99+
(1, 3),
100+
(4, 1),
101+
(3, 2),
102+
(2, 3),
103+
(1, 4),
104+
(4, 2),
105+
(3, 3),
106+
(2, 4),
107+
]
108+
@test issetequal(neighborhood(g, (1, 1), 4), ns)
109+
ns = [
110+
(1, 1),
111+
(2, 1),
112+
(1, 2),
113+
(3, 1),
114+
(2, 2),
115+
(1, 3),
116+
(4, 1),
117+
(3, 2),
118+
(2, 3),
119+
(1, 4),
120+
(4, 2),
121+
(3, 3),
122+
(2, 4),
123+
(4, 3),
124+
(3, 4),
125+
]
126+
@test issetequal(neighborhood(g, (1, 1), 5), ns)
127+
@test issetequal(neighborhood(g, (1, 1), 6), vertices(g))
128+
ns_ds = [
129+
((1, 1), 0),
130+
((2, 1), 1),
131+
((1, 2), 1),
132+
((3, 1), 2),
133+
((2, 2), 2),
134+
((1, 3), 2),
135+
((4, 1), 3),
136+
((3, 2), 3),
137+
((2, 3), 3),
138+
((1, 4), 3),
139+
]
140+
@test issetequal(neighborhood_dists(g, (1, 1), 3), ns_ds)
141+
end
66142
@testset "Basics (directed)" begin
67143
g = NamedDiGraph(["A", "B", "C", "D"])
68144
add_edge!(g, "A" => "B")
@@ -244,13 +320,123 @@ end
244320
)
245321
@test_broken f(g, "A")
246322
end
247-
@testset "has_self_loops" begin
323+
end
324+
@testset "Graph connectivity" begin
248325
g = NamedGraph(2)
249326
@test g isa NamedGraph{Int}
250327
add_edge!(g, 1, 2)
251328
@test !has_self_loops(g)
252329
add_edge!(g, 1, 1)
253330
@test has_self_loops(g)
331+
332+
g1 = named_grid((2, 2))
333+
g2 = named_grid((2, 2))
334+
g = g1 g2
335+
t = named_binary_tree(3)
336+
337+
@test is_cyclic(g1)
338+
@test is_cyclic(g2)
339+
@test is_cyclic(g)
340+
@test !is_cyclic(t)
341+
342+
@test is_connected(g1)
343+
@test is_connected(g2)
344+
@test !is_connected(g)
345+
@test is_connected(t)
346+
347+
cc = connected_components(g1)
348+
@test length(cc) == 1
349+
@test length(only(cc)) == nv(g1)
350+
@test issetequal(only(cc), vertices(g1))
351+
352+
cc = connected_components(g)
353+
@test length(cc) == 2
354+
@test length(cc[1]) == nv(g1)
355+
@test length(cc[2]) == nv(g2)
356+
@test issetequal(cc[1], map(v -> (v, 1), vertices(g1)))
357+
@test issetequal(cc[2], map(v -> (v, 2), vertices(g2)))
358+
end
359+
@testset "incident_edges" begin
360+
g = grid((3, 3))
361+
inc_edges = Edge.([2 => 1, 2 => 3, 2 => 5])
362+
@test issetequal(incident_edges(g, 2), inc_edges)
363+
@test issetequal(incident_edges(g, 2; dir=:in), reverse.(inc_edges))
364+
@test issetequal(incident_edges(g, 2; dir=:out), inc_edges)
365+
@test issetequal(incident_edges(g, 2; dir=:both), inc_edges reverse.(inc_edges))
366+
367+
g = named_grid((3, 3))
368+
inc_edges = NamedEdge.([
369+
(2, 1) => (1, 1),
370+
(2, 1) => (3, 1),
371+
(2, 1) => (2, 2),
372+
])
373+
@test issetequal(incident_edges(g, (2, 1)), inc_edges)
374+
@test issetequal(incident_edges(g, (2, 1); dir=:in), reverse.(inc_edges))
375+
@test issetequal(incident_edges(g, (2, 1); dir=:out), inc_edges)
376+
@test issetequal(incident_edges(g, (2, 1); dir=:both), inc_edges reverse.(inc_edges))
377+
378+
g = path_digraph(4)
379+
@test issetequal(incident_edges(g, 3), Edge.([3 => 4]))
380+
@test issetequal(incident_edges(g, 3; dir=:in), Edge.([2 => 3]))
381+
@test issetequal(incident_edges(g, 3; dir=:out), Edge.([3 => 4]))
382+
@test issetequal(incident_edges(g, 3; dir=:both), Edge.([2 => 3, 3 => 4]))
383+
384+
g = NamedDiGraph(path_digraph(4), ["A", "B", "C", "D"])
385+
@test issetequal(incident_edges(g, "C"), NamedEdge.(["C" => "D"]))
386+
@test issetequal(incident_edges(g, "C"; dir=:in), NamedEdge.(["B" => "C"]))
387+
@test issetequal(incident_edges(g, "C"; dir=:out), NamedEdge.(["C" => "D"]))
388+
@test issetequal(incident_edges(g, "C"; dir=:both), NamedEdge.(["B" => "C", "C" => "D"]))
254389
end
390+
@testset "merge_vertices" begin
391+
g = named_grid((3, 3))
392+
mg = merge_vertices(g, [(2, 2), (2, 3), (3, 3)])
393+
@test nv(mg) == 7
394+
@test ne(mg) == 9
395+
merged_vertices = [
396+
(1, 1),
397+
(2, 1),
398+
(3, 1),
399+
(1, 2),
400+
(2, 2),
401+
(3, 2),
402+
(1, 3),
403+
]
404+
for v in merged_vertices
405+
@test has_vertex(mg, v)
406+
end
407+
merged_edges = [
408+
(1, 1) => (2, 1),
409+
(1, 1) => (1, 2),
410+
(2, 1) => (3, 1),
411+
(2, 1) => (2, 2),
412+
(3, 1) => (3, 2),
413+
(1, 2) => (2, 2),
414+
(1, 2) => (1, 3),
415+
(2, 2) => (3, 2),
416+
(2, 2) => (1, 3),
417+
]
418+
for e in merged_edges
419+
@test has_edge(mg, e)
420+
end
421+
422+
sg = SimpleDiGraph(4)
423+
g = NamedDiGraph(sg, ["A", "B", "C", "D"])
424+
add_edge!(g, "A" => "B")
425+
add_edge!(g, "B" => "C")
426+
add_edge!(g, "C" => "D")
427+
mg = merge_vertices(g, ["B", "C"])
428+
@test ne(mg) == 2
429+
@test has_edge(mg, "A" => "B")
430+
@test has_edge(mg, "B" => "D")
431+
432+
sg = SimpleDiGraph(4)
433+
g = NamedDiGraph(sg, ["A", "B", "C", "D"])
434+
add_edge!(g, "B" => "A")
435+
add_edge!(g, "C" => "B")
436+
add_edge!(g, "D" => "C")
437+
mg = merge_vertices(g, ["B", "C"])
438+
@test ne(mg) == 2
439+
@test has_edge(mg, "B" => "A")
440+
@test has_edge(mg, "D" => "B")
255441
end
256442
end

0 commit comments

Comments
 (0)