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

Commit 2fbea52

Browse files
committed
Merge pull request #308 from JuliaGraphs/nonbacktracking_implicit
add Nonbacktracking type
2 parents d483b0e + ec34159 commit 2fbea52

File tree

5 files changed

+263
-52
lines changed

5 files changed

+263
-52
lines changed

bench/nonbacktracking.jl

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using LightGraphs
2+
3+
sizesnbm1 = Int64[@allocated non_backtracking_matrix(CycleGraph(2^i))for i in 4:10]
4+
sizesnbm2 = Int64[@allocated Nonbacktracking(CycleGraph(2^i)) for i in 4:10]
5+
6+
7+
println("Relative Sizes:\n $(float(sizesnbm1./sizesnbm2))")
8+
9+
macro storetime(name, expression)
10+
ex = quote
11+
val = $expression;
12+
timeinfo[$name] = @elapsed $expression;
13+
val
14+
end
15+
return ex
16+
end
17+
18+
function bench(g)
19+
nbt = @storetime :Construction_Nonbacktracking Nonbacktracking(g)
20+
x = ones(Float64, size(nbt)[1])
21+
info("Cycle with $n vertices has nbt in R^$(size(nbt))")
22+
23+
B, nmap = @storetime :Construction_Dense non_backtracking_matrix(g)
24+
y = @storetime :Multiplication_Nonbacktracking nbt*x
25+
z = @storetime :Multiplication_Dense B*x;
26+
S = @storetime :Construction_DS sparse(B)
27+
z = @storetime :Multiplication_Sparse z = S*x;
28+
Sp = @storetime :Construction_Sparse sparse(nbt)
29+
z = @storetime :Multiplication_Sparse Sp*x
30+
end
31+
32+
function report(timeinfo)
33+
info("Times")
34+
println("Function\t Constructors\t Multiplication")
35+
println("Dense \t $(timeinfo[:Construction_Dense])\t $(timeinfo[:Multiplication_Dense])")
36+
println("Sparse \t $(timeinfo[:Construction_Sparse])\t $(timeinfo[:Multiplication_Sparse])")
37+
println("Implicit\t $(timeinfo[:Construction_Nonbacktracking])\t $(timeinfo[:Multiplication_Nonbacktracking])")
38+
info("Implicit Multiplication is $(timeinfo[:Multiplication_Dense]/timeinfo[:Multiplication_Nonbacktracking]) faster than dense.")
39+
info("Sparse Multiplication is $(timeinfo[:Multiplication_Nonbacktracking]/timeinfo[:Multiplication_Sparse]) faster than implicit.")
40+
info("Direct Sparse Construction took $(timeinfo[:Construction_Sparse])
41+
Dense to Sparse took: $(timeinfo[:Construction_DS])")
42+
end
43+
44+
n = 2^13
45+
C = CycleGraph(n)
46+
timeinfo = Dict{Symbol, Float64}()
47+
bench(C)
48+
report(timeinfo)

src/LightGraphs.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ indegree_centrality, outdegree_centrality, katz_centrality, pagerank,
8282
# spectral
8383
adjacency_matrix,laplacian_matrix, adjacency_spectrum, laplacian_spectrum,
8484
CombinatorialAdjacency, non_backtracking_matrix, incidence_matrix,
85+
nonbacktrack_embedding, Nonbacktracking,
86+
contract,
8587

8688
# astar
8789
a_star,

src/community/detection.jl

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ return : array containing vertex assignments
99
"""
1010
function community_detection_nback(g::Graph, k::Int)
1111
#TODO insert check on connected_components
12-
ϕ = nonbacktrack_embedding(g, k)
12+
ϕ = real(nonbacktrack_embedding(g, k))
1313
if k==2
1414
c = community_detection_threshold(g, ϕ[1,:])
1515
else
1616
c = kmeans(ϕ, k).assignments
1717
end
18-
c
18+
return c
1919
end
2020

2121
function community_detection_threshold(g::SimpleGraph, coords::AbstractArray)
@@ -30,31 +30,29 @@ function community_detection_threshold(g::SimpleGraph, coords::AbstractArray)
3030
end
3131

3232

33-
3433
""" Spectral embedding of the non-backtracking matrix of `g`
3534
(see [Krzakala et al.](http://www.pnas.org/content/110/52/20935.short)).
3635
3736
`g`: imput Graph
3837
`k`: number of dimensions in which to embed
3938
4039
return : a matrix ϕ where ϕ[:,i] are the coordinates for vertex i.
40+
41+
Note does not explicitly construct the `non_backtracking_matrix`.
42+
See `Nonbacktracking` for details.
43+
4144
"""
4245
function nonbacktrack_embedding(g::Graph, k::Int)
43-
B, edgeid = non_backtracking_matrix(g)
44-
λ,eigv,_ = eigs(B, nev=k+1)
45-
ϕ = zeros(Float64, k-1, nv(g))
46+
B = Nonbacktracking(g)
47+
λ,eigv,conv = eigs(B, nev=k+1, v0=ones(Float64, B.m))
48+
ϕ = zeros(Complex64, nv(g), k-1)
4649
# TODO decide what to do with the stationary distribution ϕ[:,1]
4750
# this code just throws it away in favor of eigv[:,2:k+1].
4851
# we might also use the degree distribution to scale these vectors as is
4952
# common with the laplacian/adjacency methods.
5053
for n=1:k-1
5154
v= eigv[:,n+1]
52-
for i=1:nv(g)
53-
for j in neighbors(g, i)
54-
u = edgeid[Edge(j,i)]
55-
ϕ[n,i] += v[u]
56-
end
57-
end
55+
ϕ[:,n] = contract(B, v)
5856
end
59-
return ϕ
57+
return ϕ'
6058
end

src/spectral.jl

Lines changed: 149 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -49,45 +49,6 @@ function adjacency_matrix(g::SimpleGraph, dir::Symbol=:out, T::DataType=Int)
4949
end
5050

5151

52-
"""
53-
Given two oriented edges i->j and k->l in g, the
54-
non-backtraking matrix B is defined as
55-
56-
B[i->j, k->l] = δ(j,k)* (1 - δ(i,l))
57-
58-
returns a matrix B, and an edgemap storing the oriented edges' positions in B
59-
"""
60-
function non_backtracking_matrix(g::SimpleGraph)
61-
# idedgemap = Dict{Int, Edge}()
62-
edgeidmap = Dict{Edge, Int}()
63-
m = 0
64-
for e in edges(g)
65-
m += 1
66-
edgeidmap[e] = m
67-
end
68-
69-
if !is_directed(g)
70-
for e in edges(g)
71-
m += 1
72-
edgeidmap[reverse(e)] = m
73-
end
74-
end
75-
76-
B = zeros(Float64, m, m)
77-
78-
for (e,u) in edgeidmap
79-
i, j = src(e), dst(e)
80-
for k in in_neighbors(g,i)
81-
k == j && continue
82-
v = edgeidmap[Edge(k, i)]
83-
B[v, u] = 1
84-
end
85-
end
86-
87-
return B, edgeidmap
88-
end
89-
90-
9152
"""Returns a sparse [Laplacian matrix](https://en.wikipedia.org/wiki/Laplacian_matrix)
9253
for a graph `g`, indexed by `[src, dst]` vertices. For undirected graphs, `dir`
9354
defaults to `:out`; for directed graphs, `dir` defaults to `:both`. `T`
@@ -126,6 +87,7 @@ adjacency_spectrum(g::DiGraph, dir::Symbol=:both, T::DataType=Int) = eigvals(ful
12687

12788

12889
# GraphMatrices integration
90+
# CombinatorialAdjacency(g) returns a type that supports iterative linear solvers and eigenvector solvers.
12991
@require GraphMatrices begin
13092

13193
function CombinatorialAdjacency(g::Graph)
@@ -167,3 +129,151 @@ function incidence_matrix(g::SimpleGraph, T::DataType=Int)
167129
spmx = SparseMatrixCSC(n_v,n_e,colpt,rowval,nzval)
168130
return spmx
169131
end
132+
133+
"""
134+
Given two oriented edges i->j and k->l in g, the
135+
non-backtraking matrix B is defined as
136+
137+
B[i->j, k->l] = δ(j,k)* (1 - δ(i,l))
138+
139+
returns a matrix B, and an edgemap storing the oriented edges' positions in B
140+
"""
141+
function non_backtracking_matrix(g::SimpleGraph)
142+
# idedgemap = Dict{Int, Edge}()
143+
edgeidmap = Dict{Edge, Int}()
144+
m = 0
145+
for e in edges(g)
146+
m += 1
147+
edgeidmap[e] = m
148+
end
149+
150+
if !is_directed(g)
151+
for e in edges(g)
152+
m += 1
153+
edgeidmap[reverse(e)] = m
154+
end
155+
end
156+
157+
B = zeros(Float64, m, m)
158+
159+
for (e,u) in edgeidmap
160+
i, j = src(e), dst(e)
161+
for k in in_neighbors(g,i)
162+
k == j && continue
163+
v = edgeidmap[Edge(k, i)]
164+
B[v, u] = 1
165+
end
166+
end
167+
168+
return B, edgeidmap
169+
end
170+
171+
"""Nonbacktracking: a compact representation of the nonbacktracking operator
172+
173+
g: the underlying graph
174+
edgeidmap: the association between oriented edges and index into the NBT matrix
175+
176+
The Nonbacktracking operator can be used for community detection.
177+
This representation is compact in that it uses only ne(g) additional storage
178+
and provides an implicit representation of the matrix B_g defined below.
179+
180+
Given two oriented edges i->j and k->l in g, the
181+
non-backtraking matrix B is defined as
182+
183+
B[i->j, k->l] = δ(j,k)* (1 - δ(i,l))
184+
185+
This type is in the style of GraphMatrices.jl and supports the necessary operations
186+
for computed eigenvectors and conducting linear solves.
187+
188+
Additionally the contract!(vertexspace, nbt, edgespace) method takes vectors represented in
189+
the domain of B and represents them in the domain of the adjacency matrix of g.
190+
"""
191+
type Nonbacktracking{G}
192+
g::G
193+
edgeidmap::Dict{Edge,Int}
194+
m::Int
195+
end
196+
197+
function Nonbacktracking(g::SimpleGraph)
198+
edgeidmap = Dict{Edge, Int}()
199+
m = 0
200+
for e in edges(g)
201+
m += 1
202+
edgeidmap[e] = m
203+
end
204+
if !is_directed(g)
205+
for e in edges(g)
206+
m += 1
207+
edgeidmap[reverse(e)] = m
208+
end
209+
end
210+
return Nonbacktracking(g, edgeidmap, m)
211+
end
212+
213+
size(nbt::Nonbacktracking) = (nbt.m,nbt.m)
214+
eltype(nbt::Nonbacktracking) = Float64
215+
issym(nbt::Nonbacktracking) = false
216+
217+
function *{G, T<:Number}(nbt::Nonbacktracking{G}, x::Vector{T})
218+
length(x) == nbt.m || error("dimension mismatch")
219+
y = zeros(T, length(x))
220+
for (e,u) in nbt.edgeidmap
221+
i, j = src(e), dst(e)
222+
for k in in_neighbors(nbt.g,i)
223+
k == j && continue
224+
v = nbt.edgeidmap[Edge(k, i)]
225+
y[v] += x[u]
226+
end
227+
end
228+
return y
229+
end
230+
231+
function coo_sparse(nbt::Nonbacktracking)
232+
m = nbt.m
233+
#= I,J = zeros(Int, m), zeros(Int, m) =#
234+
I,J = zeros(Int, 0), zeros(Int, 0)
235+
for (e,u) in nbt.edgeidmap
236+
i, j = src(e), dst(e)
237+
for k in in_neighbors(nbt.g,i)
238+
k == j && continue
239+
v = nbt.edgeidmap[Edge(k, i)]
240+
#= J[u] = v =#
241+
#= I[u] = u =#
242+
push!(I, v)
243+
push!(J, u)
244+
end
245+
end
246+
return I,J,1.0
247+
end
248+
249+
sparse(nbt::Nonbacktracking) = sparse(coo_sparse(nbt)..., nbt.m,nbt.m)
250+
251+
function *{G, T<:Number}(nbt::Nonbacktracking{G}, x::AbstractMatrix{T})
252+
y = zeros(x)
253+
for i in 1:nbt.m
254+
y[:,i] = nbt * x[:,i]
255+
end
256+
return y
257+
end
258+
259+
"""contract!(vertexspace, nbt, edgespace) in place version of
260+
contract(nbt, edgespace). modifies first argument
261+
"""
262+
function contract!(vertexspace::Vector, nbt::Nonbacktracking, edgespace::Vector)
263+
for i=1:nv(nbt.g)
264+
for j in neighbors(nbt.g, i)
265+
u = nbt.edgeidmap[Edge(j,i)]
266+
vertexspace[i] += edgespace[u]
267+
end
268+
end
269+
end
270+
271+
"""contract(nbt, edgespace)
272+
Integrates out the edges by summing over the edges incident to each vertex.
273+
"""
274+
function contract(nbt::Nonbacktracking, edgespace::Vector)
275+
y = zeros(eltype(edgespace), nv(nbt.g))
276+
contract!(y,nbt,edgespace)
277+
return y
278+
end
279+

test/community/detection.jl

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,55 @@
1+
""" Spectral embedding of the non-backtracking matrix of `g`
2+
(see [Krzakala et al.](http://www.pnas.org/content/110/52/20935.short)).
3+
4+
`g`: imput Graph
5+
`k`: number of dimensions in which to embed
6+
7+
return : a matrix ϕ where ϕ[:,i] are the coordinates for vertex i.
8+
"""
9+
function nonbacktrack_embedding_dense(g::Graph, k::Int)
10+
B, edgeid = non_backtracking_matrix(g)
11+
λ,eigv,conv = eigs(B, nev=k+1, v0=ones(Float64, size(B,1)))
12+
ϕ = zeros(Complex64, k-1, nv(g))
13+
# TODO decide what to do with the stationary distribution ϕ[:,1]
14+
# this code just throws it away in favor of eigv[:,2:k+1].
15+
# we might also use the degree distribution to scale these vectors as is
16+
# common with the laplacian/adjacency methods.
17+
for n=1:k-1
18+
v= eigv[:,n+1]
19+
for i=1:nv(g)
20+
for j in neighbors(g, i)
21+
u = edgeid[Edge(j,i)]
22+
ϕ[n,i] += v[u]
23+
end
24+
end
25+
end
26+
return ϕ
27+
end
28+
29+
n = 10; k = 5
30+
pg = PathGraph(n)
31+
ϕ1 = nonbacktrack_embedding(pg, k)'
32+
33+
nbt = Nonbacktracking(pg)
34+
B, emap = non_backtracking_matrix(pg)
35+
Bs = sparse(nbt)
36+
@test sparse(B) == Bs
37+
38+
# check that matvec works
39+
x = ones(Float64, nbt.m)
40+
y = nbt * x
41+
z = B * x
42+
@test norm(y-z) < 1e-8
43+
44+
#check that matmat works and full(nbt) == B
45+
@test norm(nbt*eye(nbt.m) - B) < 1e-8
46+
47+
#check that we can use the implicit matvec in nonbacktrack_embedding
48+
@test size(y) == size(x)
49+
ϕ2 = nonbacktrack_embedding_dense(pg, k)'
50+
@test size(ϕ2) == size(ϕ1)
51+
52+
#check that this recovers communities in the path of cliques
153
n=10
254
g10 = CompleteGraph(n)
355
z = copy(g10)
@@ -13,3 +65,4 @@ for k=2:5
1365
end
1466
end
1567
end
68+

0 commit comments

Comments
 (0)