Skip to content
This repository has been archived by the owner on Oct 8, 2021. It is now read-only.

[WIP] Lighter graphs with edge iterators #163

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/pathing.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Returns connected components of the undirected graph of `g`.
```
has_self_loop(g::Union{LightGraphs.DiGraph,LightGraphs.Graph})
```
Returns true if `g` is has any self loops.
Returns true if `g` has any self loops.

### attracting_components
```
Expand Down
8 changes: 6 additions & 2 deletions src/LightGraphs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ in_edges, out_edges, has_vertex, has_edge, is_directed,
nv, ne, add_edge!, rem_edge!, add_vertex!, add_vertices!,
indegree, outdegree, degree, degree_histogram, density, Δ, δ,
Δout, Δin, δout, δin, neighbors, in_neighbors, out_neighbors,
common_neighbors, all_neighbors, has_self_loop,
common_neighbors, all_neighbors, has_self_loop, ordered,

# distance
eccentricity, diameter, periphery, radius, center,
Expand Down Expand Up @@ -95,7 +95,10 @@ a_star,
readgraph, readgraphml, readgml, writegraphml, writegexf,

# randgraphs
erdos_renyi, watts_strogatz, random_regular_graph, random_regular_digraph, random_configuration_model
erdos_renyi, watts_strogatz, random_regular_graph, random_regular_digraph, random_configuration_model,

# edgeit - TODO EdgeItState could probably not be exported
edge_it, in_edge_it, out_edge_it, EdgeIt, EdgeItState

"""An optimized graphs package.

Expand All @@ -117,6 +120,7 @@ LightGraphs
include("core.jl")
include("digraph.jl")
include("graph.jl")
include("edgeit.jl")
include("traversals/graphvisit.jl")
include("traversals/bfs.jl")
include("traversals/dfs.jl")
Expand Down
18 changes: 11 additions & 7 deletions src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ src(e::Edge) = e.first
"""Return destination of an edge."""
dst(e::Edge) = e.second

ordered(e::Edge) = src(e) <= dst(e)

@deprecate rev(e::Edge) reverse(e)

==(e1::Edge, e2::Edge) = (e1.first == e2.first && e1.second == e2.second)
Expand All @@ -30,16 +32,18 @@ end

"""A type representing an undirected graph."""
type Graph
ne # number of edges
vertices::UnitRange{Int}
edges::Set{Edge}
# edges::Set{Edge}
fadjlist::Vector{Vector{Int}} # [src]: (dst, dst, dst)
badjlist::Vector{Vector{Int}} # [dst]: (src, src, src)
end

"""A type representing a directed graph."""
type DiGraph
ne # number of edges
vertices::UnitRange{Int}
edges::Set{Edge}
# edges::Set{Edge}
fadjlist::Vector{Vector{Int}} # [src]: (dst, dst, dst)
badjlist::Vector{Vector{Int}} # [dst]: (src, src, src)
end
Expand All @@ -50,8 +54,8 @@ typealias SimpleGraph @compat(Union{Graph, DiGraph})
"""Return the vertices of a graph."""
vertices(g::SimpleGraph) = g.vertices

"""Return the edges of a graph."""
edges(g::SimpleGraph) = g.edges
"""Return an iterator to the edges of a graph."""
edges(g::SimpleGraph) = edge_it(g)

"""Returns the forward adjacency list of a graph.

Expand Down Expand Up @@ -118,7 +122,7 @@ has_vertex(g::SimpleGraph, v::Int) = v in vertices(g)
"""The number of vertices in `g`."""
nv(g::SimpleGraph) = length(vertices(g))
"""The number of edges in `g`."""
ne(g::SimpleGraph) = length(edges(g))
ne(g::SimpleGraph) = g.ne

"""Add a new edge to `g` from `src` to `dst`.

Expand Down Expand Up @@ -194,8 +198,8 @@ neighbors(g::SimpleGraph, v::Int) = out_neighbors(g, v)
common_neighbors(g::SimpleGraph, u::Int, v::Int) = intersect(neighbors(g,u), neighbors(g,v))

function copy{T<:SimpleGraph}(g::T)
return T(g.vertices,copy(g.edges),deepcopy(g.fadjlist),deepcopy(g.badjlist))
return T(g.ne, g.vertices,deepcopy(g.fadjlist),deepcopy(g.badjlist))
end

"Returns true if `g` is has any self loops."
"Returns true if `g` has any self loops."
has_self_loop(g::SimpleGraph) = any(v->has_edge(g, v, v), vertices(g))
14 changes: 6 additions & 8 deletions src/digraph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function DiGraph(n::Int)
push!(badjlist, @compat(Vector{Int}()))
push!(fadjlist, @compat(Vector{Int}()))
end
return DiGraph(1:n, Set{Edge}(), badjlist, fadjlist)
return DiGraph(0, 1:n, badjlist, fadjlist)
end

DiGraph() = DiGraph(0)
Expand Down Expand Up @@ -50,25 +50,22 @@ end

function DiGraph(g::Graph)
h = DiGraph(nv(g))
for e in edges(g)
push!(h.edges,e)
push!(h.edges,reverse(e))
end
h.fadjlist = copy(fadj(g))
h.badjlist = copy(badj(g))
return h
end

#warning: could be slow,
function ==(g::DiGraph, h::DiGraph)
return (vertices(g) == vertices(h)) && (edges(g) == edges(h))
return (vertices(g) == vertices(h)) && (Set(edges(g)) == Set(edges(h)))
end

is_directed(g::DiGraph) = true

function unsafe_add_edge!(g::DiGraph, e::Edge)
push!(g.fadjlist[src(e)], dst(e))
push!(g.badjlist[dst(e)], src(e))
push!(g.edges, e)
g.ne+=1
return e
end

Expand All @@ -80,7 +77,8 @@ function rem_edge!(g::DiGraph, e::Edge)
deleteat!(g.fadjlist[src(e)], i)
i = findfirst(g.badjlist[dst(e)], src(e))
deleteat!(g.badjlist[dst(e)], i)
return pop!(g.edges, e)
g.ne -= 1
return e
end

has_edge(g::DiGraph, e::Edge) = e in edges(g)
Expand Down
66 changes: 66 additions & 0 deletions src/edgeit.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import Base: start, next, done, length, show,
in, ==

type EdgeItState
it::Int
v::Int
k::Int
end

type EdgeIt
m::Int
adj::Vector{Vector{Int}}
start::EdgeItState # =[it, v, k]
directed::Bool
end

edge_it(g::Graph) = EdgeIt(ne(g), g.fadjlist, EdgeItState(1,1,1), false)
edge_it(g::DiGraph) = EdgeIt(ne(g), g.fadjlist, EdgeItState(1,1,1), true)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this allocate a ton of memory from a copy of g.fadjlist? Test memory allocation on some graph with 10^9 edges and see what happens.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is not copying, adj points to the same memory of g.fadjlist


start(eit::EdgeIt) = eit.start
done(eit::EdgeIt, state::EdgeItState) = state.it > eit.m
function next(eit::EdgeIt, state::EdgeItState)
# println("$eit, $state")
assert(state.v <= length(eit.adj))
u = 0
while state.k <= length(eit.adj[state.v])
u = eit.adj[state.v][state.k]
eit.directed && break
u >= state.v && break
state.k += 1
end
while state.k > length(eit.adj[state.v])
state.k = 1
state.v += 1
while state.k <= length(eit.adj[state.v])
u = eit.adj[state.v][state.k]
eit.directed && break
u >= state.v && break
state.k += 1
end
end
e = !eit.directed && u < state.v ? Edge(u, state.v) : Edge(state.v, u)
state.k += 1
state.it += 1
return (e, state)
end
length(eit::EdgeIt) = eit.m

function in(e::Edge, eit::EdgeIt)
s = src(e)
t = dst(e)
n = length(eit.adj)
!(1 <= s <= n) && return false
!(1 <= t <= n) && return false
return t in eit.adj[s]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is O(n) and will be a performance bottleneck. The reason we went with edge sets is so we can do membership tests in O(1).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is O(deg(s)), that is O(1) in sparse graphs and O(n) in dense graphs. Since I usually work with sparse graphs I was not considering the latter case.

Here there is a trade-off beetween saving 2*m*64bit space ( 3*m*64bit if we also remove g.badjlist in Graph) and going from O(1) to O(n) in edge existence check in dense graphs. How common is the in operation? which of the algorithms uses it?

In case maybe we can add this modified Graph as another type in LightGraphs?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're quite right about the tradeoffs. It's one of the reasons there are so many different ways to represent graphs. :)

Most of my work is in sparse graphs as well, so it won't affect ME too much, but I don't want it to be a surprise for anyone else. If you find calls to has_edge() that's probably where you'll get tripped up.

end

#TODO implement using a loop and ∈, so that no memory is allocated
==(e1::EdgeIt, e2::Set{Edge}) = Set{Edge}(e1) == e2
==(e1::Set{Edge}, e2::EdgeIt) = e1 == Set{Edge}(e2)
==(e1::EdgeIt, e2::EdgeIt) = Set{Edge}(e1) == Set{Edge}(e2)



show(io::IO, eit::EdgeIt) = write(io, "edgeit $(eit.m)")
show(io::IO, s::EdgeItState) = write(io, "edgeitstate [ $(s.it),$(s.v),$(s.k) ]")
14 changes: 6 additions & 8 deletions src/graph.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function Graph(n::Int)
push!(badjlist, @compat(Vector{Int}()))
push!(fadjlist, @compat(Vector{Int}()))
end
return Graph(1:n, Set{Edge}(), badjlist, fadjlist)
return Graph(0, 1:n, badjlist, fadjlist)
end

Graph() = Graph(0)
Expand Down Expand Up @@ -48,6 +48,7 @@ function Graph(g::DiGraph)
return h
end

#warning: could be slow
function ==(g::Graph, h::Graph)
gdigraph = DiGraph(g)
hdigraph = DiGraph(h)
Expand All @@ -65,16 +66,12 @@ function unsafe_add_edge!(g::Graph, e::Edge)
push!(g.fadjlist[dst(e)], src(e))
push!(g.badjlist[src(e)], dst(e))
end
push!(g.edges, e)
g.ne += 1
return e
end

function rem_edge!(g::Graph, e::Edge)
if !(e in edges(g))
reve = reverse(e)
(reve in edges(g)) || error("Edge $e is not in graph")
e = reve
end
(e in edges(g)) || error("Edge $e is not in graph")

i = findfirst(g.fadjlist[src(e)], dst(e))
deleteat!(g.fadjlist[src(e)], i)
Expand All @@ -84,7 +81,8 @@ function rem_edge!(g::Graph, e::Edge)
deleteat!(g.fadjlist[dst(e)], i)
i = findfirst(g.badjlist[src(e)], dst(e))
deleteat!(g.badjlist[src(e)], i)
return pop!(g.edges, e)
g.ne -= 1
return ordered(e) ? e : reverse(e)
end


Expand Down
4 changes: 0 additions & 4 deletions src/operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@ end
function reverse!(g::DiGraph)
gne = ne(g)
reve = Set{Edge}()
for e in edges(g)
push!(reve, reverse(e))
end
g.edges = reve
g.fadjlist, g.badjlist = g.badjlist, g.fadjlist
return g
end
Expand Down
29 changes: 28 additions & 1 deletion test/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@ add_edge!(h, 3, 5)

@test sprint(show, e1) == "edge 1 - 2"
@test vertices(g) == 1:5
i = 0
for e in edges(g)
i+=1
end
@test i == 5
@test has_edge(g,3,5)
@test edges(g) == Set([e1, e2, e3, e4, e5])
@test Set{Edge}(edges(g)) == Set([e1, e2, e3, e4, e5])

@test degree(g) == [3, 2, 2, 1, 2]
@test indegree(g) == [3, 2, 2, 1, 2]
Expand Down Expand Up @@ -65,13 +72,33 @@ add_edge!(h, 3, 5)
@test common_neighbors(g, 2, 3) == [1, 5]
@test common_neighbors(h, 2, 3) == [5]

@test ne(g) == 5
@test rem_edge!(g, 1, 2) == e1
@test ne(g) == 4
eit = edges(g)
@test eit.m == 4
state = start(eit)
@test state.it == 1
@test state.v == 1
@test state.k == 1
# @test edges(g) == EdgeIt(4, g.fadjlist, EdgeItState(1,1,1), false)

@test_throws ErrorException rem_edge!(g, 2, 1)
add_edge!(g, 1, 2)
@test ne(g) == 5
eit = edges(g)
@test eit.m == 5
state = start(eit)
@test state.it == 1
@test state.v == 1
@test state.k == 1

@test Edge(2, 1) in edges(g)
@test Edge(1, 2) in edges(g)
@test rem_edge!(g, 2, 1) == e1

@test rem_edge!(h, 1, 2) == e1
@test_throws ErrorException rem_edge!(h, 1, 2)

@test g == copy(g)
@test !(g === copy(g))
@test !(g === copy(g))
6 changes: 3 additions & 3 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@ a1 = Graph(adjmx1)
a2 = DiGraph(adjmx2)

tests = [
"core",
"randgraphs",
"graphdigraph",
"persistence",
"core",
"smallgraphs",
"shortestpaths/astar",
"shortestpaths/bellman-ford",
Expand All @@ -65,7 +64,8 @@ tests = [
"centrality/degree",
"centrality/katz",
"centrality/pagerank",
"subgraphs"
"subgraphs",
"persistence"
]


Expand Down