-
Notifications
You must be signed in to change notification settings - Fork 93
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ReverseView and UndirectedView #376
base: master
Are you sure you want to change the base?
Changes from all commits
df88100
143c83c
45d08e1
64f05f1
a88a227
714c1f8
78ee8cd
c5627ad
06b04cc
fbad556
995e075
24f255b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Graph views formats | ||
|
||
*Graphs.jl* provides views around directed graphs. | ||
`ReverseGraph` is a graph view that wraps a directed graph and reverse the direction of every edge. | ||
`UndirectedGraph` is a graph view that wraps a directed graph and consider every edge as undirected. | ||
|
||
## Index | ||
|
||
```@index | ||
Pages = ["wrappedgraphs.md"] | ||
``` | ||
|
||
## Full docs | ||
|
||
```@autodocs | ||
Modules = [Graphs] | ||
Pages = [ | ||
"wrappedgraphs/graphviews.jl", | ||
] | ||
|
||
``` |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,125 @@ | ||||||
""" | ||||||
ReverseView{T<:Integer,G<:AbstractGraph} <: AbstractGraph{T} | ||||||
|
||||||
A graph view that wraps a directed graph and reverse the direction of every edge. | ||||||
|
||||||
# Examples | ||||||
```jldoctest | ||||||
julia> using Graphs | ||||||
|
||||||
julia> g = SimpleDiGraph(2); | ||||||
|
||||||
julia> add_edge!(g, 1, 2); | ||||||
|
||||||
julia> rg = ReverseView(g); | ||||||
|
||||||
julia> neighbors(rg, 1) | ||||||
Int64[] | ||||||
|
||||||
julia> neighbors(rg, 2) | ||||||
1-element Vector{Int64}: | ||||||
1 | ||||||
``` | ||||||
""" | ||||||
struct ReverseView{T<:Integer,G<:AbstractGraph} <: AbstractGraph{T} | ||||||
g::G | ||||||
|
||||||
@traitfn ReverseView{T,G}(g::::(IsDirected)) where {T<:Integer,G<:AbstractGraph{T}} = | ||||||
new(g) | ||||||
@traitfn ReverseView{T,G}(g::::(!IsDirected)) where {T<:Integer,G<:AbstractGraph{T}} = | ||||||
throw(ArgumentError("Your graph needs to be directed")) | ||||||
end | ||||||
|
||||||
ReverseView(g::G) where {T<:Integer,G<:AbstractGraph{T}} = ReverseView{T,G}(g) | ||||||
|
||||||
wrapped_graph(g::ReverseView) = g.g | ||||||
|
||||||
Graphs.is_directed(::ReverseView{T,G}) where {T,G} = true | ||||||
Graphs.is_directed(::Type{<:ReverseView{T,G}}) where {T,G} = true | ||||||
|
||||||
Graphs.edgetype(g::ReverseView) = Graphs.edgetype(g.g) | ||||||
Graphs.has_vertex(g::ReverseView, v) = Graphs.has_vertex(g.g, v) | ||||||
Graphs.ne(g::ReverseView) = Graphs.ne(g.g) | ||||||
Graphs.nv(g::ReverseView) = Graphs.nv(g.g) | ||||||
Graphs.vertices(g::ReverseView) = Graphs.vertices(g.g) | ||||||
Graphs.edges(g::ReverseView) = (reverse(e) for e in Graphs.edges(g.g)) | ||||||
Graphs.has_edge(g::ReverseView, s, d) = Graphs.has_edge(g.g, d, s) | ||||||
Graphs.inneighbors(g::ReverseView, v) = Graphs.outneighbors(g.g, v) | ||||||
Graphs.outneighbors(g::ReverseView, v) = Graphs.inneighbors(g.g, v) | ||||||
|
||||||
""" | ||||||
UndirectedView{T<:Integer,G<:AbstractGraph} <: AbstractGraph{T} | ||||||
|
||||||
A graph view that wraps a directed graph and consider every edge as undirected. | ||||||
|
||||||
# Examples | ||||||
```jldoctest | ||||||
julia> using Graphs | ||||||
|
||||||
julia> g = SimpleDiGraph(2); | ||||||
|
||||||
julia> add_edge!(g, 1, 2); | ||||||
|
||||||
julia> ug = UndirectedView(g); | ||||||
|
||||||
julia> neighbors(ug, 1) | ||||||
1-element Vector{Int64}: | ||||||
2 | ||||||
|
||||||
julia> neighbors(ug, 2) | ||||||
1-element Vector{Int64}: | ||||||
1 | ||||||
``` | ||||||
""" | ||||||
struct UndirectedView{T<:Integer,G<:AbstractGraph} <: AbstractGraph{T} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we distinguish between weak and strong connectivity? I.e. weaks means that |
||||||
g::G | ||||||
ne::Int | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Precalculating I think it might be worth not to cache it and then make sure that all our algorithms calculate |
||||||
@traitfn function UndirectedView{T,G}( | ||||||
g::::(IsDirected) | ||||||
) where {T<:Integer,G<:AbstractGraph{T}} | ||||||
ne = count(e -> src(e) <= dst(e) || !has_edge(g, dst(e), src(e)), Graphs.edges(g)) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This way we can avoid the expensive call to |
||||||
return new(g, ne) | ||||||
end | ||||||
|
||||||
@traitfn UndirectedView{T,G}( | ||||||
g::::(!IsDirected) | ||||||
) where {T<:Integer,G<:AbstractGraph{T}} = | ||||||
throw(ArgumentError("Your graph needs to be directed")) | ||||||
end | ||||||
|
||||||
UndirectedView(g::G) where {T<:Integer,G<:AbstractGraph{T}} = UndirectedView{T,G}(g) | ||||||
|
||||||
""" | ||||||
wrapped_graph(g) | ||||||
|
||||||
Return the graph wrapped by `g` | ||||||
""" | ||||||
function wrapped_graph end | ||||||
|
||||||
wrapped_graph(g::UndirectedView) = g.g | ||||||
|
||||||
Graphs.is_directed(::UndirectedView) = false | ||||||
Graphs.is_directed(::Type{<:UndirectedView}) = false | ||||||
|
||||||
Graphs.edgetype(g::UndirectedView) = Graphs.edgetype(g.g) | ||||||
Graphs.has_vertex(g::UndirectedView, v) = Graphs.has_vertex(g.g, v) | ||||||
Graphs.ne(g::UndirectedView) = g.ne | ||||||
Graphs.nv(g::UndirectedView) = Graphs.nv(g.g) | ||||||
Graphs.vertices(g::UndirectedView) = Graphs.vertices(g.g) | ||||||
function Graphs.has_edge(g::UndirectedView, s, d) | ||||||
return Graphs.has_edge(g.g, s, d) || Graphs.has_edge(g.g, d, s) | ||||||
end | ||||||
Graphs.inneighbors(g::UndirectedView, v) = Graphs.all_neighbors(g.g, v) | ||||||
Graphs.outneighbors(g::UndirectedView, v) = Graphs.all_neighbors(g.g, v) | ||||||
function Graphs.edges(g::UndirectedView) | ||||||
return ( | ||||||
begin | ||||||
(u, v) = src(e), dst(e) | ||||||
if (v < u) | ||||||
(u, v) = (v, u) | ||||||
end | ||||||
Edge(u, v) | ||||||
end for | ||||||
e in Graphs.edges(g.g) if (src(e) <= dst(e) || !has_edge(g.g, dst(e), src(e))) | ||||||
) | ||||||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
@testset "Graph Views" begin | ||
@testset "ReverseView" begin | ||
gx = DiGraph([ | ||
Edge(1, 1), | ||
Edge(1, 2), | ||
Edge(1, 4), | ||
Edge(2, 1), | ||
Edge(2, 2), | ||
Edge(2, 4), | ||
Edge(3, 1), | ||
Edge(4, 3), | ||
]) | ||
|
||
gr = erdos_renyi(20, 0.1; is_directed=true) | ||
|
||
for g in hcat(test_generic_graphs(gx), test_generic_graphs(gr)) | ||
rg = ReverseView(g) | ||
allocated_rg = DiGraph(nv(g)) | ||
for e in edges(g) | ||
add_edge!(allocated_rg, Edge(dst(e), src(e))) | ||
end | ||
|
||
@test wrapped_graph(rg) == g | ||
@test is_directed(rg) == true | ||
@test eltype(rg) == eltype(g) | ||
@test edgetype(rg) == edgetype(g) | ||
@test has_vertex(rg, 4) == has_vertex(g, 4) | ||
@test nv(rg) == nv(g) == nv(allocated_rg) | ||
@test ne(rg) == ne(g) == ne(allocated_rg) | ||
@test all(adjacency_matrix(rg) .== adjacency_matrix(allocated_rg)) | ||
@test sort(collect(inneighbors(rg, 2))) == | ||
sort(collect(inneighbors(allocated_rg, 2))) | ||
@test sort(collect(outneighbors(rg, 2))) == | ||
sort(collect(outneighbors(allocated_rg, 2))) | ||
@test indegree(rg, 3) == indegree(allocated_rg, 3) | ||
@test degree(rg, 1) == degree(allocated_rg, 1) | ||
@test has_edge(rg, 1, 3) == has_edge(allocated_rg, 1, 3) | ||
@test has_edge(rg, 1, 4) == has_edge(allocated_rg, 1, 4) | ||
|
||
rg_res = @inferred(floyd_warshall_shortest_paths(rg)) | ||
allocated_rg_res = floyd_warshall_shortest_paths(allocated_rg) | ||
@test rg_res.dists == allocated_rg_res.dists # parents may not be the same | ||
|
||
rg_res = @inferred(strongly_connected_components(rg)) | ||
allocated_rg_res = strongly_connected_components(allocated_rg) | ||
@test length(rg_res) == length(allocated_rg_res) | ||
@test sort(length.(rg_res)) == sort(length.(allocated_rg_res)) | ||
end | ||
|
||
@test_throws ArgumentError ReverseView(path_graph(5)) | ||
end | ||
|
||
@testset "UndirectedView" begin | ||
gx = DiGraph([ | ||
Edge(1, 1), | ||
Edge(1, 2), | ||
Edge(1, 4), | ||
Edge(2, 1), | ||
Edge(2, 2), | ||
Edge(2, 4), | ||
Edge(3, 1), | ||
Edge(4, 3), | ||
]) | ||
|
||
gr = erdos_renyi(20, 0.05; is_directed=true) | ||
|
||
for g in test_generic_graphs(gx) | ||
ug = UndirectedView(g) | ||
@test ne(ug) == 7 # one less edge since there was two edges in reverse directions | ||
end | ||
|
||
for g in hcat(test_generic_graphs(gx), test_generic_graphs(gr)) | ||
ug = UndirectedView(g) | ||
allocated_ug = Graph(g) | ||
|
||
@test wrapped_graph(ug) == g | ||
@test is_directed(ug) == false | ||
@test eltype(ug) == eltype(g) | ||
@test edgetype(ug) == edgetype(g) | ||
@test has_vertex(ug, 4) == has_vertex(g, 4) | ||
@test nv(ug) == nv(g) == nv(allocated_ug) | ||
@test ne(ug) == ne(allocated_ug) | ||
@test all(adjacency_matrix(ug) .== adjacency_matrix(allocated_ug)) | ||
@test sort(collect(inneighbors(ug, 2))) == | ||
sort(collect(inneighbors(allocated_ug, 2))) | ||
@test sort(collect(outneighbors(ug, 2))) == | ||
sort(collect(outneighbors(allocated_ug, 2))) | ||
@test indegree(ug, 3) == indegree(allocated_ug, 3) | ||
@test degree(ug, 1) == degree(allocated_ug, 1) | ||
@test has_edge(ug, 1, 3) == has_edge(allocated_ug, 1, 3) | ||
@test has_edge(ug, 1, 4) == has_edge(allocated_ug, 1, 4) | ||
|
||
ug_res = @inferred(floyd_warshall_shortest_paths(ug)) | ||
allocated_ug_res = floyd_warshall_shortest_paths(allocated_ug) | ||
@test ug_res.dists == allocated_ug_res.dists # parents may not be the same | ||
|
||
ug_res = @inferred(biconnected_components(ug)) | ||
allocated_ug_res = biconnected_components(allocated_ug) | ||
@test length(ug_res) == length(allocated_ug_res) | ||
@test sort(length.(ug_res)) == sort(length.(allocated_ug_res)) | ||
end | ||
|
||
@test_throws ArgumentError UndirectedView(path_graph(5)) | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it make more sense if this function works on all graphs? Even if it does nothing for undirected graphs.