diff --git a/.gitignore b/.gitignore index 2135f417..5628095a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ benchmark/Manifest.toml /docs/src/index.md /docs/src/contributing.md /docs/src/license.md +.aider* diff --git a/src/Graphs.jl b/src/Graphs.jl index a40a7816..6e565438 100644 --- a/src/Graphs.jl +++ b/src/Graphs.jl @@ -165,6 +165,7 @@ export egonet, merge_vertices!, merge_vertices, + line_graph, # bfs gdistances, diff --git a/src/operators.jl b/src/operators.jl index d8aeb217..b07dda91 100644 --- a/src/operators.jl +++ b/src/operators.jl @@ -879,3 +879,88 @@ function merge_vertices!(g::Graph{T}, vs::Vector{U} where {U<:Integer}) where {T return new_vertex_ids end + +""" + line_graph(g::SimpleGraph) ::SimpleGraph +Given a graph `g`, return the graph `lg`, whose vertices are integers that enumerate the +edges in `g`, and two vertices in `lg` form an edge iff the corresponding edges in `g` +share a common endpoint. In other words, edges in `lg` are length-2 paths in `g`. +Note that `i ∈ vertices(lg)` corresponds to `collect(edges(g))[i]`. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = path_graph(5); + +julia> lg = line_graph(g) +{4, 3} undirected simple Int64 graph +``` +""" +function line_graph(g::SimpleGraph) + vertex_to_edges = [Int[] for _ in 1:nv(g)] + for (i, e) in enumerate(edges(g)) + s, d = src(e), dst(e) + push!(vertex_to_edges[s], i) + s == d && continue # do not push self-loops twice + push!(vertex_to_edges[d], i) + end + + fadjlist = [Int[] for _ in 1:ne(g)] # edge to neighbors adjacency in lg + m = 0 # number of edges in the line-graph + for es in vertex_to_edges + n = length(es) + for i in 1:(n - 1), j in (i + 1):n # iterate through pairs of edges with same endpoint + ei, ej = es[i], es[j] + m += 1 + push!(fadjlist[ei], ej) + push!(fadjlist[ej], ei) + end + end + + foreach(sort!, fadjlist) + return SimpleGraph(m, fadjlist) +end + +""" + line_graph(g::SimpleDiGraph) ::SimpleDiGraph +Given a digraph `g`, return the digraph `lg`, whose vertices are integers that enumerate +the edges in `g`, and there is an edge in `lg` from `Edge(a,b)` to `Edge(c,d)` iff b==c. +In other words, edges in `lg` are length-2 directed paths in `g`. +Note that `i ∈ vertices(lg)` corresponds to `collect(edges(g))[i]`. + +# Examples +```jldoctest +julia> using Graphs + +julia> g = cycle_digraph(5); + +julia> lg = line_graph(g) +{5, 5} directed simple Int64 graph +``` +""" +function line_graph(g::SimpleDiGraph) + vertex_to_edgesout = [Int[] for _ in 1:nv(g)] + vertex_to_edgesin = [Int[] for _ in 1:nv(g)] + for (i, e) in enumerate(edges(g)) + s, d = src(e), dst(e) + push!(vertex_to_edgesout[s], i) + push!(vertex_to_edgesin[d], i) + end + + fadjilist = [Int[] for _ in 1:ne(g)] # edge to neighbors forward adjacency in lg + badjilist = [Int[] for _ in 1:ne(g)] # edge to neighbors backward adjacency in lg + m = 0 # number of edges in the line-graph + for (e_i, e_o) in zip(vertex_to_edgesin, vertex_to_edgesout) + for ei in e_i, eo in e_o # iterate through length-2 directed paths + ei == eo && continue # a self-loop in g does not induce a self-loop in lg + m += 1 + push!(fadjilist[ei], eo) + push!(badjilist[eo], ei) + end + end + + foreach(sort!, fadjilist) + foreach(sort!, badjilist) + return SimpleDiGraph(m, fadjilist, badjilist) +end diff --git a/test/operators.jl b/test/operators.jl index bf4931eb..2bcda762 100644 --- a/test/operators.jl +++ b/test/operators.jl @@ -352,4 +352,102 @@ @testset "Length: $(typeof(g))" for g in test_generic_graphs(SimpleGraph(100)) @test length(g) == 10000 end + + @testset "Undirected Line Graph" begin + @testset "Undirected Cycle Graphs" begin + for n in 3:9 + g = cycle_graph(n) + lg = line_graph(g) # checking if lg is an n-cycle + @test nv(lg) == n + @test ne(lg) == n + @test is_connected(lg) + @test all(degree(lg, v) == 2 for v in vertices(lg)) + end + end + + @testset "Undirected Path Graphs" begin + for n in 2:9 + g = path_graph(n) + lg = line_graph(g) # checking if lg is an n-1-path + @test nv(lg) == n - 1 + @test ne(lg) == n - 2 + @test is_connected(lg) + @test all(degree(lg, v) <= 2 for v in vertices(lg)) + @test any(degree(lg, v) == 1 for v in vertices(lg)) || n == 2 && ne(lg) == 0 + end + end + + @testset "Undirected Star Graphs" begin + for n in 3:9 + g = star_graph(n) + lg = line_graph(g) # checking if lg is a complete graph on n-1 vertices + @test nv(lg) == n - 1 + @test ne(lg) == binomial(n - 1, 2) # lg must be a complete graph + end + end + + @testset "Undirected Self-loops" begin + for T in + (Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128) + g = SimpleGraph{T}(2, [T[2], T[1, 2], T[]]) + lg = line_graph(g) + @test nv(lg) == 2 # only 2 edges (self-loop counts once) + @test ne(lg) == 1 # only connection between edge 1-2 and self-loop 2-2 + end + end + end + + @testset "Directed Line Graph" begin + @testset "Directed Cycle Graphs" begin + for n in 3:9 + g = cycle_digraph(n) + lg = line_graph(g) + @test nv(lg) == n + @test ne(lg) == n + @test is_directed(lg) + @test is_connected(lg) + @test all(outdegree(lg, v) == 1 for v in vertices(lg)) + @test all(indegree(lg, v) == 1 for v in vertices(lg)) + end + end + + @testset "Directed Path Graphs" begin + for n in 2:9 + g = path_digraph(n) + lg = line_graph(g) + @test nv(lg) == n - 1 + @test ne(lg) == n - 2 + @test is_directed(lg) + @test is_connected(lg) + @test all(outdegree(lg, v) == (v < n - 1 ? 1 : 0) for v in vertices(lg)) + @test all(indegree(lg, v) == (v > 1 ? 1 : 0) for v in vertices(lg)) + end + end + + @testset "Directed Star Graphs" begin + for m in 0:4, n in 0:4 + g = SimpleDiGraph(m + n + 1) + foreach(i -> add_edge!(g, i + 1, 1), 1:m) + foreach(j -> add_edge!(g, 1, j + 1 + m), 1:n) + lg = line_graph(g) # checking if lg is the complete bipartite digraph + @test nv(lg) == m + n + @test ne(lg) == m * n + @test all(outdegree(lg, v) == 0 && indegree(lg, v) == m for v in 1:n) + @test all( + outdegree(lg, v) == n && indegree(lg, v) == 0 for v in (n + 1):(n + m) + ) + end + end + + @testset "Directed Self-loops" begin + for T in + (Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128) + g = SimpleDiGraph{T}(2, [T[1, 2], T[], T[]], [T[1], T[1], T[]]) + lg = line_graph(g) + @test nv(lg) == 2 + @test ne(lg) == 1 + @test has_edge(lg, 1, 2) + end + end + end end diff --git a/test/runtests.jl b/test/runtests.jl index 34bd1c53..bcac411b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,11 +1,11 @@ using Aqua using Documenter using Graphs -using Graphs.SimpleGraphs using Graphs.Experimental +using Graphs.SimpleGraphs +using Graphs.Test using JET using JuliaFormatter -using Graphs.Test using Test using SparseArrays using LinearAlgebra @@ -150,31 +150,42 @@ tests = [ "experimental/experimental", ] +args = lowercase.(ARGS) + @testset verbose = true "Graphs" begin - @testset "Code quality (JET.jl)" begin - @assert get_pkg_version("JET") >= v"0.8.4" - JET.test_package( - Graphs; - target_defined_modules=true, - ignore_missing_comparison=true, - mode=:typo, # TODO: switch back to `:basic` once the union split caused by traits is fixed - ) + if "jet" in args || isempty(args) + @testset "Code quality (JET.jl)" begin + @assert get_pkg_version("JET") >= v"0.8.4" + JET.test_package( + Graphs; + target_defined_modules=true, + ignore_missing_comparison=true, + mode=:typo, # TODO: switch back to `:basic` once the union split caused by traits is fixed + ) + end end - @testset "Code quality (Aqua.jl)" begin - Aqua.test_all(Graphs; ambiguities=false) + if "aqua" in args || isempty(args) + @testset "Code quality (Aqua.jl)" begin + Aqua.test_all(Graphs; ambiguities=false) + end end - @testset "Code formatting (JuliaFormatter.jl)" begin - @test format(Graphs; verbose=false, overwrite=false) + if "juliaformatter" in args || isempty(args) + @testset "Code formatting (JuliaFormatter.jl)" begin + @test format(Graphs; verbose=false, overwrite=false) + end end - doctest(Graphs) + if "doctest" in args || isempty(args) + doctest(Graphs) + end @testset verbose = true "Actual tests" begin for t in tests - tp = joinpath(testdir, "$(t).jl") - include(tp) + if t in args || isempty(args) + include(joinpath(testdir, "$(t).jl")) + end end end end;