diff --git a/src/operators.jl b/src/operators.jl index e430937f2..9bdd3c6d0 100644 --- a/src/operators.jl +++ b/src/operators.jl @@ -34,27 +34,27 @@ Edge 5 => 3 ``` """ function complement(g::Graph) - gnv = nv(g) - h = SimpleGraph(gnv) - for i in 1:gnv - for j in (i+1):gnv - if !has_edge(g, i, j) - add_edge!(h, i, j) - end - end - end - return h + gnv = nv(g) + h = SimpleGraph(gnv) + for i in 1:gnv + for j in (i + 1):gnv + if !has_edge(g, i, j) + add_edge!(h, i, j) + end + end + end + return h end function complement(g::DiGraph) - gnv = nv(g) - h = SimpleDiGraph(gnv) - for i in vertices(g), j in vertices(g) - if i != j && !has_edge(g, i, j) - add_edge!(h, i, j) - end - end - return h + gnv = nv(g) + h = SimpleDiGraph(gnv) + for i in vertices(g), j in vertices(g) + if i != j && !has_edge(g, i, j) + add_edge!(h, i, j) + end + end + return h end """ @@ -82,14 +82,14 @@ Edge 5 => 4 ``` """ function reverse end -@traitfn function reverse(g::G::IsDirected) where {G <: AbstractSimpleGraph} - gnv = nv(g) - gne = ne(g) - h = SimpleDiGraph(gnv) - h.fadjlist = deepcopy_adjlist(g.badjlist) - h.badjlist = deepcopy_adjlist(g.fadjlist) - h.ne = gne - return h +@traitfn function reverse(g::G::IsDirected) where {G<:AbstractSimpleGraph} + gnv = nv(g) + gne = ne(g) + h = SimpleDiGraph(gnv) + h.fadjlist = deepcopy_adjlist(g.badjlist) + h.badjlist = deepcopy_adjlist(g.fadjlist) + h.ne = gne + return h end """ @@ -99,9 +99,9 @@ In-place reverse of a directed graph (modifies the original graph). See [`reverse`](@ref) for a non-modifying version. """ function reverse! end -@traitfn function reverse!(g::G::IsDirected) where {G <: AbstractSimpleGraph} - g.fadjlist, g.badjlist = g.badjlist, g.fadjlist - return g +@traitfn function reverse!(g::G::IsDirected) where {G<:AbstractSimpleGraph} + g.fadjlist, g.badjlist = g.badjlist, g.fadjlist + return g end """ @@ -137,16 +137,16 @@ Edge 7 => 8 Edge 8 => 6 ``` """ -function blockdiag(g::T, h::T) where {T <: AbstractGraph} - gnv = nv(g) - r = T(gnv + nv(h)) - for e in edges(g) - add_edge!(r, e) - end - for e in edges(h) - add_edge!(r, gnv + src(e), gnv + dst(e)) - end - return r +function blockdiag(g::T, h::T) where {T<:AbstractGraph} + gnv = nv(g) + r = T(gnv + nv(h)) + for e in edges(g) + add_edge!(r, e) + end + for e in edges(h) + add_edge!(r, gnv + src(e), gnv + dst(e)) + end + return r end """ @@ -172,15 +172,15 @@ Edge 2 => 3 Edge 3 => 1 ``` """ -function intersect(g::T, h::T) where {T <: AbstractGraph} - gnv = nv(g) - hnv = nv(h) +function intersect(g::T, h::T) where {T<:AbstractGraph} + gnv = nv(g) + hnv = nv(h) - r = T(min(gnv, hnv)) - for e in intersect(edges(g), edges(h)) - add_edge!(r, e) - end - return r + r = T(min(gnv, hnv)) + for e in intersect(edges(g), edges(h)) + add_edge!(r, e) + end + return r end """ @@ -206,15 +206,15 @@ Edge 4 => 5 Edge 5 => 4 ``` """ -function difference(g::T, h::T) where {T <: AbstractGraph} - gnv = nv(g) - hnv = nv(h) +function difference(g::T, h::T) where {T<:AbstractGraph} + gnv = nv(g) + hnv = nv(h) - r = T(gnv) - for e in edges(g) - !has_edge(h, e) && add_edge!(r, e) - end - return r + r = T(gnv) + for e in edges(g) + !has_edge(h, e) && add_edge!(r, e) + end + return r end """ @@ -249,18 +249,18 @@ julia> collect(edges(f)) Edge 2 => 3 ``` """ -function symmetric_difference(g::T, h::T) where {T <: AbstractGraph} - gnv = nv(g) - hnv = nv(h) - - r = T(max(gnv, hnv)) - for e in edges(g) - !has_edge(h, e) && add_edge!(r, e) - end - for e in edges(h) - !has_edge(g, e) && add_edge!(r, e) - end - return r +function symmetric_difference(g::T, h::T) where {T<:AbstractGraph} + gnv = nv(g) + hnv = nv(h) + + r = T(max(gnv, hnv)) + for e in edges(g) + !has_edge(h, e) && add_edge!(r, e) + end + for e in edges(h) + !has_edge(g, e) && add_edge!(r, e) + end + return r end """ @@ -300,22 +300,22 @@ julia> collect(edges(f)) Edge 4 => 5 ``` """ -function union(g::T, h::T) where {T <: AbstractSimpleGraph} - gnv = nv(g) - hnv = nv(h) - - r = T(max(gnv, hnv)) - r.ne = ne(g) - for i in vertices(g) - r.fadjlist[i] = deepcopy(g.fadjlist[i]) - if is_directed(g) - r.badjlist[i] = deepcopy(g.badjlist[i]) - end - end - for e in edges(h) - add_edge!(r, e) - end - return r +function union(g::T, h::T) where {T<:AbstractSimpleGraph} + gnv = nv(g) + hnv = nv(h) + + r = T(max(gnv, hnv)) + r.ne = ne(g) + for i in vertices(g) + r.fadjlist[i] = deepcopy(g.fadjlist[i]) + if is_directed(g) + r.badjlist[i] = deepcopy(g.badjlist[i]) + end + end + for e in edges(h) + add_edge!(r, e) + end + return r end """ @@ -348,14 +348,14 @@ julia> collect(edges(g)) Edge 4 => 5 ``` """ -function join(g::T, h::T) where {T <: AbstractGraph} - r = blockdiag(g, h) - for i in vertices(g) - for j in (nv(g)+1):(nv(g)+nv(h)) - add_edge!(r, i, j) - end - end - return r +function join(g::T, h::T) where {T<:AbstractGraph} + r = blockdiag(g, h) + for i in vertices(g) + for j in (nv(g) + 1):(nv(g) + nv(h)) + add_edge!(r, i, j) + end + end + return r end """ @@ -394,29 +394,29 @@ julia> collect(edges(g)) function crosspath end # see https://github.com/mauro3/SimpleTraits.jl/issues/47#issuecomment-327880153 for syntax @traitfn function crosspath( - len::Integer, g::AG::(!IsDirected), -) where {T, AG <: AbstractGraph{T}} - p = path_graph(len) - h = SimpleGraph{T}(p) - return cartesian_product(h, g) + len::Integer, g::AG::(!IsDirected) +) where {T,AG<:AbstractGraph{T}} + p = path_graph(len) + h = SimpleGraph{T}(p) + return cartesian_product(h, g) end # The following operators allow one to use a Graphs.Graph as a matrix in eigensolvers for spectral ranking and partitioning. # """Provides multiplication of a graph `g` by a vector `v` such that spectral # graph functions in [GraphMatrices.jl](https://github.com/jpfairbanks/GraphMatrices.jl) can utilize Graphs natively. # """ -function *(g::AbstractGraph, v::Vector{T}) where {T <: Real} - length(v) == nv(g) || throw(ArgumentError("Vector size must equal number of vertices")) - y = zeros(T, nv(g)) - for e in edges(g) - i = src(e) - j = dst(e) - y[i] += v[j] - if !is_directed(g) - y[j] += v[i] - end - end - return y +function *(g::AbstractGraph, v::Vector{T}) where {T<:Real} + length(v) == nv(g) || throw(ArgumentError("Vector size must equal number of vertices")) + y = zeros(T, nv(g)) + for e in edges(g) + i = src(e) + j = dst(e) + y[i] += v[j] + if !is_directed(g) + y[j] += v[i] + end + end + return y end """ @@ -448,9 +448,9 @@ julia> sum(g, 1) ``` """ function sum(g::AbstractGraph, dim::Int) - dim == 1 && return indegree(g, vertices(g)) - dim == 2 && return outdegree(g, vertices(g)) - throw(ArgumentError("dimension must be <= 2")) + dim == 1 && return indegree(g, vertices(g)) + dim == 2 && return outdegree(g, vertices(g)) + throw(ArgumentError("dimension must be <= 2")) end size(g::AbstractGraph) = (nv(g), nv(g)) @@ -505,16 +505,16 @@ length(g::AbstractGraph) = widen(nv(g)) * widen(nv(g)) ndims(g::AbstractGraph) = 2 @traitfn function issymmetric(g::AG) where {AG <: AbstractGraph; !IsDirected{AG}} - return true + return true end @traitfn function issymmetric(g::AG) where {AG <: AbstractGraph; IsDirected{AG}} - for e in edges(g) - if !has_edge(g, reverse(e)) - return false - end - end - return true + for e in edges(g) + if !has_edge(g, reverse(e)) + return false + end + end + return true end """ @@ -550,23 +550,23 @@ julia> collect(edges(g)) Edge 8 => 9 ``` """ -function cartesian_product(g::G, h::G) where {G <: AbstractGraph} - z = G(nv(g) * nv(h)) - id(i, j) = (i - 1) * nv(h) + j - for e in edges(g) - i1, i2 = Tuple(e) - for j in 1:nv(h) - add_edge!(z, id(i1, j), id(i2, j)) - end - end - - for e in edges(h) - j1, j2 = Tuple(e) - for i in vertices(g) - add_edge!(z, id(i, j1), id(i, j2)) - end - end - return z +function cartesian_product(g::G, h::G) where {G<:AbstractGraph} + z = G(nv(g) * nv(h)) + id(i, j) = (i - 1) * nv(h) + j + for e in edges(g) + i1, i2 = Tuple(e) + for j in 1:nv(h) + add_edge!(z, id(i1, j), id(i2, j)) + end + end + + for e in edges(h) + j1, j2 = Tuple(e) + for i in vertices(g) + add_edge!(z, id(i, j1), id(i, j2)) + end + end + return z end """ @@ -598,21 +598,21 @@ julia> collect(edges(g)) Edge 3 => 8 ``` """ -function tensor_product(g::G, h::G) where {G <: AbstractGraph} - z = G(nv(g) * nv(h)) - id(i, j) = (i - 1) * nv(h) + j - undirected = !is_directed(g) - for e1 in edges(g) - i1, i2 = Tuple(e1) - for e2 in edges(h) - j1, j2 = Tuple(e2) - add_edge!(z, id(i1, j1), id(i2, j2)) - if undirected - add_edge!(z, id(i1, j2), id(i2, j1)) - end - end - end - return z +function tensor_product(g::G, h::G) where {G<:AbstractGraph} + z = G(nv(g) * nv(h)) + id(i, j) = (i - 1) * nv(h) + j + undirected = !is_directed(g) + for e1 in edges(g) + i1, i2 = Tuple(e1) + for e2 in edges(h) + j1, j2 = Tuple(e2) + add_edge!(z, id(i1, j1), id(i2, j2)) + if undirected + add_edge!(z, id(i1, j2), id(i2, j1)) + end + end + end + return z end ## subgraphs ### @@ -655,54 +655,54 @@ julia> @assert sg == g[elist] ``` """ function induced_subgraph( - g::T, vlist::AbstractVector{U}, -) where {T <: AbstractGraph} where {U <: Integer} - allunique(vlist) || throw(ArgumentError("Vertices in subgraph list must be unique")) - h = T(length(vlist)) - newvid = Dict{U, U}() - vmap = Vector{U}(undef, length(vlist)) - for (i, v) in enumerate(vlist) - newvid[v] = U(i) - vmap[i] = v - end - - vset = Set(vlist) - for s in vlist - for d in outneighbors(g, s) - # println("s = $s, d = $d") - if d in vset && has_edge(g, s, d) - newe = Edge(newvid[s], newvid[d]) - add_edge!(h, newe) - end - end - end - return h, vmap + g::T, vlist::AbstractVector{U} +) where {T<:AbstractGraph} where {U<:Integer} + allunique(vlist) || throw(ArgumentError("Vertices in subgraph list must be unique")) + h = T(length(vlist)) + newvid = Dict{U,U}() + vmap = Vector{U}(undef, length(vlist)) + for (i, v) in enumerate(vlist) + newvid[v] = U(i) + vmap[i] = v + end + + vset = Set(vlist) + for s in vlist + for d in outneighbors(g, s) + # println("s = $s, d = $d") + if d in vset && has_edge(g, s, d) + newe = Edge(newvid[s], newvid[d]) + add_edge!(h, newe) + end + end + end + return h, vmap end function induced_subgraph(g::AbstractGraph, vlist::AbstractVector{Bool}) - length(vlist) == length(g) || throw(BoundsError(g, vlist)) - return induced_subgraph(g, findall(vlist)) + length(vlist) == length(g) || throw(BoundsError(g, vlist)) + return induced_subgraph(g, findall(vlist)) end function induced_subgraph( - g::AG, elist::AbstractVector{U}, -) where {AG <: AbstractGraph{T}} where {T} where {U <: AbstractEdge} - h = zero(g) - newvid = Dict{T, T}() - vmap = Vector{T}() - - for e in elist - u, v = Tuple(e) - for i in (u, v) - if !haskey(newvid, i) - add_vertex!(h) - newvid[i] = nv(h) - push!(vmap, i) - end - end - add_edge!(h, newvid[u], newvid[v]) - end - return h, vmap + g::AG, elist::AbstractVector{U} +) where {AG<:AbstractGraph{T}} where {T} where {U<:AbstractEdge} + h = zero(g) + newvid = Dict{T,T}() + vmap = Vector{T}() + + for e in elist + u, v = Tuple(e) + for i in (u, v) + if !haskey(newvid, i) + add_vertex!(h) + newvid[i] = nv(h) + push!(vmap, i) + end + end + add_edge!(h, newvid[u], newvid[v]) + end + return h, vmap end """ @@ -725,13 +725,13 @@ This is equivalent to [`induced_subgraph`](@ref)`(g, neighborhood(g, v, d, dir=d with respect to `v` (i.e. `:in` or `:out`). """ function egonet( - g::AbstractGraph{T}, - v::Integer, - d::Integer, - distmx::AbstractMatrix{U} = weights(g); - dir = :out, -) where {T <: Integer} where {U <: Real} - return g[neighborhood(g, v, d, distmx; dir = dir)] + g::AbstractGraph{T}, + v::Integer, + d::Integer, + distmx::AbstractMatrix{U}=weights(g); + dir=:out, +) where {T<:Integer} where {U<:Real} + return g[neighborhood(g, v, d, distmx; dir=dir)] end """ @@ -740,9 +740,9 @@ end Determine how many elements of `x` are less than `i` for all `i` in `1:n`. """ function compute_shifts(n::Integer, x::AbstractArray) - tmp = zeros(eltype(x), n) - tmp[x] .= 1 - return cumsum!(tmp, tmp) + tmp = zeros(eltype(x), n) + tmp[x] .= 1 + return cumsum!(tmp, tmp) end """ @@ -772,30 +772,30 @@ julia> collect(edges(h)) Edge 3 => 4 ``` """ -function merge_vertices(g::G, vs) where {G <: AbstractSimpleGraph} - # Use lowest value as new vertex id. - vs = unique!(sort(vs)) - merged_vertex = popfirst!(vs) - - nvnew = nv(g) - length(vs) - nvnew <= nv(g) || return g - merged_vertex > 0 || throw( - ArgumentError("invalid vertex ID: $merged_vertex in list of vertices to be merged"), - ) - vs[end] <= nv(g) || throw(ArgumentError("vertex $(vs[end]) not found in graph")) # TODO 0.7: change to DomainError? - - new_vertex_ids = collect(vertices(g)) .- compute_shifts(nv(g), vs) - new_vertex_ids[vs] .= merged_vertex - - # if v in vs then labels[v] == v0 else labels[v] == v - newg = G(nvnew) - for e in edges(g) - u, w = src(e), dst(e) - if new_vertex_ids[u] != new_vertex_ids[w] # not a new self loop - add_edge!(newg, new_vertex_ids[u], new_vertex_ids[w]) - end - end - return newg +function merge_vertices(g::G, vs) where {G<:AbstractSimpleGraph} + # Use lowest value as new vertex id. + vs = unique!(sort(vs)) + merged_vertex = popfirst!(vs) + + nvnew = nv(g) - length(vs) + nvnew <= nv(g) || return g + merged_vertex > 0 || throw( + ArgumentError("invalid vertex ID: $merged_vertex in list of vertices to be merged"), + ) + vs[end] <= nv(g) || throw(ArgumentError("vertex $(vs[end]) not found in graph")) # TODO 0.7: change to DomainError? + + new_vertex_ids = collect(vertices(g)) .- compute_shifts(nv(g), vs) + new_vertex_ids[vs] .= merged_vertex + + # if v in vs then labels[v] == v0 else labels[v] == v + newg = G(nvnew) + for e in edges(g) + u, w = src(e), dst(e) + if new_vertex_ids[u] != new_vertex_ids[w] # not a new self loop + add_edge!(newg, new_vertex_ids[u], new_vertex_ids[w]) + end + end + return newg end """ @@ -838,44 +838,44 @@ julia> collect(edges(g)) Edge 3 => 4 ``` """ -function merge_vertices!(g::Graph{T}, vs::Vector{U} where {U <: Integer}) where {T} - vs = unique!(sort(vs)) - (merged_vertex, vm) = extrema(vs) - - merged_vertex > 0 || throw( - ArgumentError("invalid vertex ID: $merged_vertex in list of vertices to be merged"), - ) - vm <= nv(g) || throw(ArgumentError("vertex $vm not found in graph")) # TODO 0.7: change to DomainError? - - new_vertex_ids = collect(vertices(g)) .- compute_shifts(nv(g), vs[2:end]) - new_vertex_ids[vs] .= merged_vertex - - for i in vertices(g) - # Adjust connections to merged vertices - if new_vertex_ids[i] != merged_vertex - nbrs_to_rewire = Set{T}() - for j in outneighbors(g, i) - push!(nbrs_to_rewire, new_vertex_ids[j]) - end - g.fadjlist[new_vertex_ids[i]] = sort!(collect(nbrs_to_rewire)) - - # Collect connections to new merged vertex - else - nbrs_to_merge = Set{T}() - for j in vs, e in outneighbors(g, j) - if new_vertex_ids[e] != merged_vertex - push!(nbrs_to_merge, new_vertex_ids[e]) - end - end - g.fadjlist[i] = sort(collect(nbrs_to_merge)) - end - end - - # Drop excess vertices - g.fadjlist = g.fadjlist[firstindex(g.fadjlist):(end-length(vs)+1)] - - # Correct edge counts - g.ne = sum(degree(g, i) for i in vertices(g)) / 2 - - return new_vertex_ids +function merge_vertices!(g::Graph{T}, vs::Vector{U} where {U<:Integer}) where {T} + vs = unique!(sort(vs)) + (merged_vertex, vm) = extrema(vs) + + merged_vertex > 0 || throw( + ArgumentError("invalid vertex ID: $merged_vertex in list of vertices to be merged"), + ) + vm <= nv(g) || throw(ArgumentError("vertex $vm not found in graph")) # TODO 0.7: change to DomainError? + + new_vertex_ids = collect(vertices(g)) .- compute_shifts(nv(g), vs[2:end]) + new_vertex_ids[vs] .= merged_vertex + + for i in vertices(g) + # Adjust connections to merged vertices + if new_vertex_ids[i] != merged_vertex + nbrs_to_rewire = Set{T}() + for j in outneighbors(g, i) + push!(nbrs_to_rewire, new_vertex_ids[j]) + end + g.fadjlist[new_vertex_ids[i]] = sort!(collect(nbrs_to_rewire)) + + # Collect connections to new merged vertex + else + nbrs_to_merge = Set{T}() + for j in vs, e in outneighbors(g, j) + if new_vertex_ids[e] != merged_vertex + push!(nbrs_to_merge, new_vertex_ids[e]) + end + end + g.fadjlist[i] = sort(collect(nbrs_to_merge)) + end + end + + # Drop excess vertices + g.fadjlist = g.fadjlist[firstindex(g.fadjlist):(end - length(vs) + 1)] + + # Correct edge counts + g.ne = sum(degree(g, i) for i in vertices(g)) / 2 + + return new_vertex_ids end diff --git a/test/operators.jl b/test/operators.jl index 70ec5d1ea..bf4931ebf 100644 --- a/test/operators.jl +++ b/test/operators.jl @@ -3,58 +3,57 @@ # by the AbstractGraph interface @testset "Operators" begin - rng = StableRNG(1) - - g3 = path_graph(5) - g4 = path_digraph(5) - - @testset "$g" for g in testlargegraphs(g3) - T = eltype(g) - @testset "complement" begin - c = @inferred(complement(g)) - @test nv(c) == 5 - @test ne(c) == 6 - end - - @testset "blockdiag" begin - gb = @inferred(blockdiag(g, g)) - @test nv(gb) == 10 - @test ne(gb) == 8 - end - - @testset "intersect" begin - hp = path_graph(2) - h = Graph{T}(hp) - @test @inferred(intersect(g, h)) == h - end - - @testset "difference / symmetric difference" begin - hp = path_graph(4) - h = Graph{T}(hp) - - z = @inferred(difference(g, h)) - @test nv(z) == 5 - @test ne(z) == 1 - z = @inferred(difference(h, g)) - @test nv(z) == 4 - @test ne(z) == 0 - z = @inferred(symmetric_difference(h, g)) - @test z == symmetric_difference(g, h) - @test nv(z) == 5 - @test ne(z) == 1 - end - - - @testset "union" begin - h = Graph{T}(6) - add_edge!(h, 5, 6) - e = SimpleEdge(5, 6) - - z = @inferred(union(g, h)) - @test has_edge(z, e) - @test z == path_graph(6) - end - + rng = StableRNG(1) + + g3 = path_graph(5) + g4 = path_digraph(5) + + @testset "$g" for g in testlargegraphs(g3) + T = eltype(g) + @testset "complement" begin + c = @inferred(complement(g)) + @test nv(c) == 5 + @test ne(c) == 6 + end + + @testset "blockdiag" begin + gb = @inferred(blockdiag(g, g)) + @test nv(gb) == 10 + @test ne(gb) == 8 + end + + @testset "intersect" begin + hp = path_graph(2) + h = Graph{T}(hp) + @test @inferred(intersect(g, h)) == h + end + + @testset "difference / symmetric difference" begin + hp = path_graph(4) + h = Graph{T}(hp) + + z = @inferred(difference(g, h)) + @test nv(z) == 5 + @test ne(z) == 1 + z = @inferred(difference(h, g)) + @test nv(z) == 4 + @test ne(z) == 0 + z = @inferred(symmetric_difference(h, g)) + @test z == symmetric_difference(g, h) + @test nv(z) == 5 + @test ne(z) == 1 + end + + @testset "union" begin + h = Graph{T}(6) + add_edge!(h, 5, 6) + e = SimpleEdge(5, 6) + + z = @inferred(union(g, h)) + @test has_edge(z, e) + @test z == path_graph(6) + end + @testset "merge vertices" begin # Check merge_vertices function. h1 = Graph{T}(7) @@ -69,7 +68,7 @@ @test neighbors(hmerged, 3) == [] @test neighbors(hmerged, 4) == [2, 5] @test eltype(hmerged) == eltype(g) - + new_map = @inferred(merge_vertices!(h1, vs)) @test new_map == [1, 2, 2, 3, 4, 5, 2] @test neighbors(h1, 1) == [2] @@ -77,7 +76,7 @@ @test neighbors(h1, 3) == [] @test neighbors(h1, 4) == [2, 5] @test hmerged == h1 - + h2 = path_digraph(4) h2 = DiGraph{T}(h2) hmerged = @inferred merge_vertices(h2, [2, 3]) @@ -89,8 +88,7 @@ @test outneighbors(hmerged, 2) == [3] @test outneighbors(hmerged, 3) == [] @test eltype(hmerged) == eltype(h2) - - + h3 = Graph{T}(7) add_edge!(h3, 1, 2) add_edge!(h3, 2, 3) @@ -106,7 +104,7 @@ @test neighbors(h3, 6) == [2] @test ne(h3) == 3 @test nv(h3) == 6 - + h4 = Graph{T}(7) add_edge!(h4, 1, 2) add_edge!(h4, 2, 3) @@ -123,7 +121,7 @@ @test neighbors(h4, 5) == [2] @test ne(h4) == 3 @test nv(h4) == 5 - + h5 = star_graph(5) h5 = Graph{T}(h5) h5merged = merge_vertices(h5, [1, 2]) @@ -132,226 +130,226 @@ @test neighbors(h5merged, 3) == [1] @test neighbors(h5merged, 4) == [1] end - end - - @testset "$g" for g in testlargegraphs(g4) - T = eltype(g) - @testset "complement" begin - c = @inferred(complement(g)) - @test nv(c) == 5 - @test ne(c) == 16 - end - - @testset "union" begin - h = DiGraph{T}(6) - add_edge!(h, 5, 6) - e = SimpleEdge(5, 6) - - z = @inferred(union(g, h)) - @test has_edge(z, e) - @test z == path_digraph(6) - end - end - - re1 = Edge(2, 1) - gr = @inferred(reverse(g4)) - @testset "Reverse $g" for g in testdigraphs(gr) - T = eltype(g) - @test re1 in edges(g) - @inferred(reverse!(g)) - @test g == DiGraph{T}(g4) - end - - gx = complete_graph(2) - - @testset "Blockdiag $g" for g in testlargegraphs(gx) - T = eltype(g) - hc = complete_graph(2) - h = Graph{T}(hc) - z = @inferred(blockdiag(g, h)) - @test nv(z) == nv(g) + nv(h) - @test ne(z) == ne(g) + ne(h) - @test has_edge(z, 1, 2) - @test has_edge(z, 3, 4) - @test !has_edge(z, 1, 3) - @test !has_edge(z, 1, 4) - @test !has_edge(z, 2, 3) - @test !has_edge(z, 2, 4) - end - - gx = SimpleGraph(2) - @testset "Join $g" for g in testgraphs(gx) - T = eltype(g) - h = Graph{T}(2) - z = @inferred(join(g, h)) - @test nv(z) == nv(g) + nv(h) - @test ne(z) == 4 - @test !has_edge(z, 1, 2) - @test !has_edge(z, 3, 4) - @test has_edge(z, 1, 3) - @test has_edge(z, 1, 4) - @test has_edge(z, 2, 3) - @test has_edge(z, 2, 4) - end - - px = path_graph(10) - @testset "Matrix operations: $(typeof(p))" for p in test_generic_graphs(px) - x = @inferred(p * ones(10)) - @test x[1] == 1.0 && all(x[2:(end-1)] .== 2.0) && x[end] == 1.0 - @test size(p) == (10, 10) - @test size(p, 1) == size(p, 2) == 10 - @test size(p, 3) == 1 - @test sum(p, 1) == sum(p, 2) - @test_throws ArgumentError sum(p, 3) - @test sparse(p) == adjacency_matrix(p) - @test length(p) == 100 - @test ndims(p) == 2 - @test issymmetric(p) - end - - gx = SimpleDiGraph(4) - add_edge!(gx, 1, 2) - add_edge!(gx, 2, 3) - add_edge!(gx, 1, 3) - add_edge!(gx, 3, 4) - @testset "Matrix operations: $(typeof(g))" for g in test_generic_graphs(gx) - @test @inferred(g * ones(nv(g))) == [2.0, 1.0, 1.0, 0.0] - @test sum(g, 1) == [0, 1, 2, 1] - @test sum(g, 2) == [2, 1, 1, 0] - @test sum(g) == 4 - @test @inferred(!issymmetric(g)) - end - - gx = SimpleDiGraph(4) - add_edge!(gx, 1, 2) - add_edge!(gx, 2, 1) - add_edge!(gx, 1, 3) - add_edge!(gx, 3, 1) - @testset "Matrix operations: $(typeof(g))" for g in test_generic_graphs(gx) - @test @inferred(issymmetric(g)) - end - - nx = 20 - ny = 21 - @testset "Cartesian Product / Crosspath: $g" for g in testlargegraphs(path_graph(ny)) - T = eltype(g) - hp = path_graph(nx) - h = Graph{T}(hp) - c = @inferred(cartesian_product(g, h)) - gz = @inferred(crosspath(ny, path_graph(nx))) - @test gz == c - end - function crosspath_slow(len, h) - g = h - m = nv(h) - for i in 1:(len-1) - k = nv(g) - g = blockdiag(g, h) - for v in 1:m - add_edge!(g, v + (k - m), v + k) - end - end - return g - end - - @testset "Cartesian Product / Tensor Product: $g" for g in testgraphs(complete_graph(2)) - h = @inferred(cartesian_product(g, g)) - @test nv(h) == 4 - @test ne(h) == 4 - - h = @inferred(tensor_product(g, g)) - @test nv(h) == 4 - @test ne(h) == 2 - end - g2 = complete_graph(2) - @testset "Crosspath: $g" for g in testgraphs(g2) - @test crosspath_slow(2, g) == crosspath(2, g) - end - for i in 3:4 - @testset "Tensor Product: $g" for g in testgraphs(path_graph(i)) - @test length(connected_components(tensor_product(g, g))) == 2 - end - end - - ## test subgraphs ## - - gb = smallgraph(:bull) - @testset "Subgraphs: $g" for g in testgraphs(gb) - n = 3 - h = @inferred(g[1:n]) - @test nv(h) == n - @test ne(h) == 3 - - h = @inferred(g[[1, 2, 4]]) - @test nv(h) == n - @test ne(h) == 2 - - h = @inferred(g[[1, 5]]) - @test nv(h) == 2 - @test ne(h) == 0 - @test typeof(h) == typeof(g) - end - - gx = SimpleDiGraph(100, 200; rng = rng) - @testset "Subgraphs: $g" for g in testdigraphs(gx) - h = @inferred(g[5:26]) - @test nv(h) == 22 - @test typeof(h) == typeof(g) - @test_throws ArgumentError g[[1, 1]] - - r = 5:26 - h2, vm = @inferred(induced_subgraph(g, r)) - @test h2 == h - @test vm == collect(r) - @test h2 == g[r] - - r2 = falses(length(g)) - r2[r] .= true - h2, vm = @inferred(induced_subgraph(g, r2)) - @test h2 == h - @test vm == findall(r2) - @test h2 == g[r2] - end - - g10 = complete_graph(10) - @testset "Induced Subgraphs: $g" for g in testgraphs(g10) - sg, vm = @inferred(induced_subgraph(g, 5:8)) - @test nv(sg) == 4 - @test ne(sg) == 6 - - sg2, vm = @inferred(induced_subgraph(g, [5, 6, 7, 8])) - @test sg2 == sg - @test vm[4] == 8 - - bv = falses(length(g)) - bv[5:8] .= true - sg2, vm = @inferred(induced_subgraph(g, bv)) - @test sg2 == sg - @test vm[4] == 8 - - elist = [ - SimpleEdge(1, 2), - SimpleEdge(2, 3), - SimpleEdge(3, 4), - SimpleEdge(4, 5), - SimpleEdge(5, 1), - ] - sg, vm = @inferred(induced_subgraph(g, elist)) - @test sg == cycle_graph(5) - @test sort(vm) == [1:5;] - end - - gs = star_graph(10) - distgs = fill(4.0, 10, 10) - @testset "Egonet: $g" for g in testgraphs(gs) - T = eltype(g) - @test @inferred(egonet(g, 1, 0)) == Graph{T}(1) - @test @inferred(egonet(g, 1, 3, distgs)) == Graph{T}(1) - @test @inferred(egonet(g, 1, 1)) == g - @test @inferred(ndims(g)) == 2 - end - - @testset "Length: $(typeof(g))" for g in test_generic_graphs(SimpleGraph(100)) - @test length(g) == 10000 - end + end + + @testset "$g" for g in testlargegraphs(g4) + T = eltype(g) + @testset "complement" begin + c = @inferred(complement(g)) + @test nv(c) == 5 + @test ne(c) == 16 + end + + @testset "union" begin + h = DiGraph{T}(6) + add_edge!(h, 5, 6) + e = SimpleEdge(5, 6) + + z = @inferred(union(g, h)) + @test has_edge(z, e) + @test z == path_digraph(6) + end + end + + re1 = Edge(2, 1) + gr = @inferred(reverse(g4)) + @testset "Reverse $g" for g in testdigraphs(gr) + T = eltype(g) + @test re1 in edges(g) + @inferred(reverse!(g)) + @test g == DiGraph{T}(g4) + end + + gx = complete_graph(2) + + @testset "Blockdiag $g" for g in testlargegraphs(gx) + T = eltype(g) + hc = complete_graph(2) + h = Graph{T}(hc) + z = @inferred(blockdiag(g, h)) + @test nv(z) == nv(g) + nv(h) + @test ne(z) == ne(g) + ne(h) + @test has_edge(z, 1, 2) + @test has_edge(z, 3, 4) + @test !has_edge(z, 1, 3) + @test !has_edge(z, 1, 4) + @test !has_edge(z, 2, 3) + @test !has_edge(z, 2, 4) + end + + gx = SimpleGraph(2) + @testset "Join $g" for g in testgraphs(gx) + T = eltype(g) + h = Graph{T}(2) + z = @inferred(join(g, h)) + @test nv(z) == nv(g) + nv(h) + @test ne(z) == 4 + @test !has_edge(z, 1, 2) + @test !has_edge(z, 3, 4) + @test has_edge(z, 1, 3) + @test has_edge(z, 1, 4) + @test has_edge(z, 2, 3) + @test has_edge(z, 2, 4) + end + + px = path_graph(10) + @testset "Matrix operations: $(typeof(p))" for p in test_generic_graphs(px) + x = @inferred(p * ones(10)) + @test x[1] == 1.0 && all(x[2:(end - 1)] .== 2.0) && x[end] == 1.0 + @test size(p) == (10, 10) + @test size(p, 1) == size(p, 2) == 10 + @test size(p, 3) == 1 + @test sum(p, 1) == sum(p, 2) + @test_throws ArgumentError sum(p, 3) + @test sparse(p) == adjacency_matrix(p) + @test length(p) == 100 + @test ndims(p) == 2 + @test issymmetric(p) + end + + gx = SimpleDiGraph(4) + add_edge!(gx, 1, 2) + add_edge!(gx, 2, 3) + add_edge!(gx, 1, 3) + add_edge!(gx, 3, 4) + @testset "Matrix operations: $(typeof(g))" for g in test_generic_graphs(gx) + @test @inferred(g * ones(nv(g))) == [2.0, 1.0, 1.0, 0.0] + @test sum(g, 1) == [0, 1, 2, 1] + @test sum(g, 2) == [2, 1, 1, 0] + @test sum(g) == 4 + @test @inferred(!issymmetric(g)) + end + + gx = SimpleDiGraph(4) + add_edge!(gx, 1, 2) + add_edge!(gx, 2, 1) + add_edge!(gx, 1, 3) + add_edge!(gx, 3, 1) + @testset "Matrix operations: $(typeof(g))" for g in test_generic_graphs(gx) + @test @inferred(issymmetric(g)) + end + + nx = 20 + ny = 21 + @testset "Cartesian Product / Crosspath: $g" for g in testlargegraphs(path_graph(ny)) + T = eltype(g) + hp = path_graph(nx) + h = Graph{T}(hp) + c = @inferred(cartesian_product(g, h)) + gz = @inferred(crosspath(ny, path_graph(nx))) + @test gz == c + end + function crosspath_slow(len, h) + g = h + m = nv(h) + for i in 1:(len - 1) + k = nv(g) + g = blockdiag(g, h) + for v in 1:m + add_edge!(g, v + (k - m), v + k) + end + end + return g + end + + @testset "Cartesian Product / Tensor Product: $g" for g in testgraphs(complete_graph(2)) + h = @inferred(cartesian_product(g, g)) + @test nv(h) == 4 + @test ne(h) == 4 + + h = @inferred(tensor_product(g, g)) + @test nv(h) == 4 + @test ne(h) == 2 + end + g2 = complete_graph(2) + @testset "Crosspath: $g" for g in testgraphs(g2) + @test crosspath_slow(2, g) == crosspath(2, g) + end + for i in 3:4 + @testset "Tensor Product: $g" for g in testgraphs(path_graph(i)) + @test length(connected_components(tensor_product(g, g))) == 2 + end + end + + ## test subgraphs ## + + gb = smallgraph(:bull) + @testset "Subgraphs: $g" for g in testgraphs(gb) + n = 3 + h = @inferred(g[1:n]) + @test nv(h) == n + @test ne(h) == 3 + + h = @inferred(g[[1, 2, 4]]) + @test nv(h) == n + @test ne(h) == 2 + + h = @inferred(g[[1, 5]]) + @test nv(h) == 2 + @test ne(h) == 0 + @test typeof(h) == typeof(g) + end + + gx = SimpleDiGraph(100, 200; rng=rng) + @testset "Subgraphs: $g" for g in testdigraphs(gx) + h = @inferred(g[5:26]) + @test nv(h) == 22 + @test typeof(h) == typeof(g) + @test_throws ArgumentError g[[1, 1]] + + r = 5:26 + h2, vm = @inferred(induced_subgraph(g, r)) + @test h2 == h + @test vm == collect(r) + @test h2 == g[r] + + r2 = falses(length(g)) + r2[r] .= true + h2, vm = @inferred(induced_subgraph(g, r2)) + @test h2 == h + @test vm == findall(r2) + @test h2 == g[r2] + end + + g10 = complete_graph(10) + @testset "Induced Subgraphs: $g" for g in testgraphs(g10) + sg, vm = @inferred(induced_subgraph(g, 5:8)) + @test nv(sg) == 4 + @test ne(sg) == 6 + + sg2, vm = @inferred(induced_subgraph(g, [5, 6, 7, 8])) + @test sg2 == sg + @test vm[4] == 8 + + bv = falses(length(g)) + bv[5:8] .= true + sg2, vm = @inferred(induced_subgraph(g, bv)) + @test sg2 == sg + @test vm[4] == 8 + + elist = [ + SimpleEdge(1, 2), + SimpleEdge(2, 3), + SimpleEdge(3, 4), + SimpleEdge(4, 5), + SimpleEdge(5, 1), + ] + sg, vm = @inferred(induced_subgraph(g, elist)) + @test sg == cycle_graph(5) + @test sort(vm) == [1:5;] + end + + gs = star_graph(10) + distgs = fill(4.0, 10, 10) + @testset "Egonet: $g" for g in testgraphs(gs) + T = eltype(g) + @test @inferred(egonet(g, 1, 0)) == Graph{T}(1) + @test @inferred(egonet(g, 1, 3, distgs)) == Graph{T}(1) + @test @inferred(egonet(g, 1, 1)) == g + @test @inferred(ndims(g)) == 2 + end + + @testset "Length: $(typeof(g))" for g in test_generic_graphs(SimpleGraph(100)) + @test length(g) == 10000 + end end