Skip to content

Commit 35243fd

Browse files
committed
Add DaggerGraphs subpackage
1 parent daec6e0 commit 35243fd

File tree

4 files changed

+291
-0
lines changed

4 files changed

+291
-0
lines changed

lib/DaggerGraphs/Project.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name = "DaggerGraphs"
2+
uuid = "304567ff-242f-4479-af00-6fcdcd11a1dd"
3+
authors = ["Julian P Samaroo <jpsamaroo@jpsamaroo.me>"]
4+
version = "0.1.0"
5+
6+
[deps]
7+
Dagger = "d58978e5-989f-55fb-8d15-ea34adc7bf54"
8+
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
9+
10+
[compat]
11+
Dagger = "0.17, 0.18"
12+
Graphs = "1"
13+
julia = "1"

lib/DaggerGraphs/src/DaggerGraphs.jl

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
module DaggerGraphs
2+
3+
using Dagger
4+
import Dagger: Chunk
5+
using Graphs
6+
7+
export DGraph
8+
9+
const ELTYPE = Union{Dagger.EagerThunk, Chunk}
10+
11+
struct DGraphState{T}
12+
# A set of locally-connected SimpleDiGraphs
13+
parts::Vector{ELTYPE}
14+
# The range of vertices within each of `parts`
15+
parts_nv::Vector{UnitRange{T}}
16+
# The number of edges in each of `parts`
17+
parts_ne::Vector{T}
18+
# The maximum number of nodes for each of `parts`
19+
parts_v_max::Int
20+
21+
# A set of `AdjList` for each of `parts`
22+
# An edge is present here if either src or dst (but not both) is in
23+
# the respective `parts` graph
24+
ext_adjs::Vector{ELTYPE}
25+
# The number of edges in each of `ext_adjs`
26+
ext_adjs_ne::Vector{T}
27+
# The number of edges in each of `ext_adjs` where the source is this partition
28+
ext_adjs_ne_src::Vector{T}
29+
end
30+
struct DGraph{T} <: Graphs.AbstractGraph{T}
31+
state::Dagger.Chunk{DGraphState{T}}
32+
end
33+
function DGraph{T}(; chunksize=8) where {T}
34+
state = DGraphState{T}(ELTYPE[],
35+
UnitRange{T}[],
36+
T[],
37+
chunksize,
38+
ELTYPE[],
39+
T[],
40+
T[])
41+
return DGraph{T}(Dagger.tochunk(state))
42+
end
43+
DGraph(; kwargs...) = DGraph{Int}(; kwargs...)
44+
45+
Base.eltype(::DGraph{T}) where T = T
46+
Graphs.edgetype(::DGraph{T}) where T = Tuple{T,T}
47+
Graphs.nv(g::DGraph) = fetch(Dagger.@spawn nv(g.state))
48+
function Graphs.nv(g::DGraphState)
49+
if !isempty(g.parts_nv)
50+
return last(g.parts_nv).stop
51+
else
52+
return 0
53+
end
54+
end
55+
Graphs.ne(g::DGraph) = fetch(Dagger.@spawn ne(g.state))
56+
Graphs.ne(g::DGraphState) = sum(g.parts_ne; init=0) + sum(g.ext_adjs_ne_src; init=0)
57+
Graphs.has_vertex(g::DGraph, v::Int) = 1 <= v <= nv(g)
58+
Graphs.has_edge(g::DGraph, src::Int, dst::Int) =
59+
fetch(Dagger.@spawn has_edge(g.state, src, dst))
60+
function Graphs.has_edge(g::DGraphState, src::Int, dst::Int)
61+
src_part_idx = findfirst(span->src in span, g.parts_nv)
62+
src_part_idx !== nothing || return false
63+
dst_part_idx = findfirst(span->dst in span, g.parts_nv)
64+
dst_part_idx !== nothing || return false
65+
66+
if src_part_idx == dst_part_idx
67+
# The edge will be within a graph partition
68+
part = g.parts[src_part_idx]
69+
return fetch(Dagger.@spawn has_edge(part, src, dst))
70+
else
71+
# The edge will be in an AdjList
72+
adj = g.ext_adjs[src_part_idx]
73+
return fetch(Dagger.@spawn has_ext_adj(adj, src, dst))
74+
end
75+
end
76+
Graphs.is_directed(::DGraph) = true
77+
Graphs.vertices(g::DGraph) = Base.OneTo(nv(g))
78+
Graphs.edges(g::DGraph) = DGraphEdgeIter(g)
79+
Graphs.zero(::Type{<:DGraph}) = DGraph()
80+
Graphs.add_vertex!(g::DGraph) = fetch(Dagger.@spawn add_vertex!(g.state))
81+
function Graphs.add_vertex!(g::DGraphState)
82+
n = nv(g)
83+
if fld(n, g.parts_v_max) == length(g.parts)
84+
# We need to create a new partition for this vertex
85+
push!(g.parts, Dagger.spawn() do
86+
g = SimpleDiGraph()
87+
add_vertex!(g)
88+
g
89+
end)
90+
push!(g.parts_nv, (n+1):(n+1))
91+
push!(g.parts_ne, 0)
92+
push!(g.ext_adjs, Dagger.@spawn AdjList())
93+
push!(g.ext_adjs_ne, 0)
94+
push!(g.ext_adjs_ne_src, 0)
95+
else
96+
# We will add this vertex to the last partition
97+
part = last(g.parts)
98+
fetch(Dagger.@spawn add_vertex!(part))
99+
span = g.parts_nv[end]
100+
g.parts_nv[end] = UnitRange{Int}(span.start, span.stop+1)
101+
end
102+
return true
103+
end
104+
Graphs.add_edge!(g::DGraph, src::Int, dst::Int) =
105+
fetch(Dagger.@spawn add_edge!(g.state, src, dst))
106+
function Graphs.add_edge!(g::DGraphState, src::Int, dst::Int)
107+
src_part_idx = findfirst(span->src in span, g.parts_nv)
108+
@assert src_part_idx !== nothing "Source vertex $src does not exist"
109+
110+
dst_part_idx = findfirst(span->dst in span, g.parts_nv)
111+
@assert dst_part_idx !== nothing "Destination vertex $dst does not exist"
112+
113+
if src_part_idx == dst_part_idx
114+
# Edge exists within a single partition
115+
part = g.parts[src_part_idx]
116+
if fetch(Dagger.@spawn add_edge!(part, src, dst))
117+
g.parts_ne[src_part_idx] += 1
118+
else
119+
return false
120+
end
121+
else
122+
# Edge spans two partitions
123+
src_ext_adj = g.ext_adjs[src_part_idx]
124+
dst_ext_adj = g.ext_adjs[dst_part_idx]
125+
if !fetch(Dagger.@spawn add_ext_adj!(src_ext_adj, src, dst))
126+
return false
127+
end
128+
fetch(Dagger.@spawn add_ext_adj!(dst_ext_adj, src, dst))
129+
g.ext_adjs_ne_src[src_part_idx] += 1
130+
g.ext_adjs_ne[src_part_idx] += 1
131+
g.ext_adjs_ne[dst_part_idx] += 1
132+
end
133+
134+
return true
135+
end
136+
137+
struct AdjList{T}
138+
adj::Vector{Tuple{T,T}}
139+
end
140+
AdjList() = AdjList{Int}(Tuple{Int,Int}[])
141+
function has_ext_adj(adj::AdjList, src::Int, dst::Int)
142+
idx = findfirst(edge->edge[1] == src && edge[2] == dst, adj.adj)
143+
if idx !== nothing
144+
return true
145+
end
146+
return false
147+
end
148+
function add_ext_adj!(adj::AdjList, src::Int, dst::Int)
149+
if has_ext_adj(adj, src, dst)
150+
return false
151+
end
152+
push!(adj.adj, (src, dst))
153+
return true
154+
end
155+
Graphs.edges(adj::AdjList) = adj.adj
156+
157+
struct DGraphEdgeIter{T} <: Graphs.AbstractEdgeIter
158+
graph::DGraphState{T}
159+
end
160+
DGraphEdgeIter(g::DGraph) = DGraphEdgeIter(fetch(g.state))
161+
struct DGraphEdgeIterState
162+
adj::Bool
163+
part::Int
164+
idx::Int
165+
cache::Any
166+
end
167+
Base.length(iter::DGraphEdgeIter) = ne(iter.graph)
168+
Base.eltype(iter::DGraphEdgeIter{T}) where T = Tuple{T,T}
169+
function Base.iterate(iter::DGraphEdgeIter)
170+
g = iter.graph
171+
if nv(g) == 0
172+
return nothing
173+
elseif sum(g.parts_ne; init=0) > 0
174+
# Start with partitions
175+
return iterate(iter, DGraphEdgeIterState(false, 1, 1, nothing))
176+
elseif sum(g.ext_adjs_ne_src; init=0) > 0
177+
# Start with external AdjLists
178+
return iterate(iter, DGraphEdgeIterState(true, 1, 1, nothing))
179+
else
180+
return nothing
181+
end
182+
end
183+
function Base.iterate(iter::DGraphEdgeIter{T}, state::DGraphEdgeIterState) where {T}
184+
g = iter.graph
185+
adj = state.adj
186+
part = state.part
187+
idx = state.idx
188+
cache = state.cache
189+
190+
@label start
191+
if !adj
192+
if part > length(g.parts)
193+
# Restart with external AdjLists
194+
return iterate(iter, DGraphEdgeIterState(true, 1, 1, nothing))
195+
end
196+
if cache === nothing
197+
cache = map(Tuple, fetch(Dagger.@spawn edges(g.parts[part])))
198+
end
199+
else
200+
if part > length(g.ext_adjs)
201+
# All done!
202+
return nothing
203+
end
204+
if cache === nothing
205+
cache = fetch(Dagger.@spawn edges(g.ext_adjs[part]))
206+
end
207+
end
208+
cache::Vector{<:Tuple}
209+
210+
# Skip empty edge sets
211+
if isempty(cache)
212+
part += 1
213+
idx = 1
214+
cache = nothing
215+
@goto start
216+
end
217+
cache::Vector{Tuple{T,T}}
218+
219+
# Get the current edge
220+
value = cache[idx]
221+
idx += 1
222+
cur_part = part
223+
224+
# Reset if this partition/AdjList is exhausted
225+
if idx > length(cache)
226+
part += 1
227+
idx = 1
228+
cache = nothing
229+
end
230+
231+
# Restart if this edge isn't "owned" by this AdjList
232+
if adj && !(value[1] in g.parts_nv[cur_part])
233+
@goto start
234+
end
235+
236+
return (value, DGraphEdgeIterState(adj, part, idx, cache))
237+
end
238+
239+
end # module DaggerGraphs

lib/DaggerGraphs/test/Project.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[deps]
2+
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
3+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

lib/DaggerGraphs/test/runtests.jl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using DaggerGraphs, Graphs
2+
using Test
3+
4+
function generate_random_dgraph(N)
5+
g = DGraph()
6+
n_ctr = 1
7+
add_vertex!(g)
8+
n_edges = 0
9+
for i in 1:N
10+
src = rand() < 0.1 ? n_ctr+1 : rand(1:n_ctr)
11+
if src > n_ctr
12+
add_vertex!(g)
13+
n_ctr += 1
14+
end
15+
dst = rand() < 0.1 ? n_ctr+1 : rand(1:n_ctr)
16+
if dst > n_ctr
17+
add_vertex!(g)
18+
n_ctr += 1
19+
end
20+
if add_edge!(g, src, dst)
21+
n_edges += 1
22+
end
23+
end
24+
@test nv(g) == n_ctr
25+
@test ne(g) == n_edges
26+
return g
27+
end
28+
29+
@testset "Basics" begin
30+
g = generate_random_dgraph(1000)
31+
@test vertices(g) == Base.OneTo(nv(g))
32+
@test length(edges(g)) == ne(g)
33+
e = collect(edges(g))
34+
@test e isa Vector{Tuple{Int,Int}}
35+
@test all(edge->has_edge(g, edge[1], edge[2]), e)
36+
end

0 commit comments

Comments
 (0)