BFS using GraphBLAS
onHere I present a simple BFS code written in Julia using GraphBLAS. This algorithm is taken straight from the API documentation & may not be the fastest method but it’s just for a simple illustration.
Okay, so first let’s create a SimpleGraph(a LightGraphs type) using the facebook undirected graph from SNAP Datasets and extract it’s adjacency matrix.
julia> using GraphBLASInterface, SuiteSparseGraphBLAS, SparseArrays, LightGraphs, SNAPDatasets
julia> g = loadsnap(:facebook_combined)
{4039, 88234} undirected simple Int64 graph
julia> adj = adjacency_matrix(g);
Now we’ll extract tuples from this adjacency matrix.
julia> I, J, X = SparseArrays.findnz(adj);
Let’s build a GraphBLAS adjacency matrix using I, J, X. The number of entries will be twice the number of edges in the graph.
julia> GrB_init(GrB_NONBLOCKING)
GrB_SUCCESS::GrB_Info = 0
julia> A = GrB_Matrix{Int64}()
GrB_Matrix{Int64}
julia> GrB_Matrix_new(A, GrB_INT64, nv(g), nv(g))
GrB_SUCCESS::GrB_Info = 0
julia> GrB_Matrix_build(A, OneBasedIndex.(I), OneBasedIndex.(J), X, 2*ne(g), GrB_FIRST_INT64)
GrB_SUCCESS::GrB_Info = 0
Now we write a function which performs the BFS traversal of a graph, given the adjacency matrix A and a source node s. It returns GraphBLAS vector v where v[i] is set to the level in which node i is visited. If i is not reacheable from s, then v[i] = 0. The graph A need not be Boolean on input; if it isn’t Boolean, the semiring will properly typecast it to Boolean.
julia> function GrB_bfs( # BFS of a graph (using vector assign & reduce)
A::GrB_Matrix, # input graph, treated as if boolean in semiring
s::Abstract_GrB_Index # starting node of the BFS
)
#--------------------------------------------------------------------------
# set up the semiring and initialize the vector v
#--------------------------------------------------------------------------
q = GrB_Vector{Bool}() # nodes visited at each level
v = GrB_Vector{Int64}() # result vector
Lor = GrB_Monoid() # Logical-or monoid
Boolean = GrB_Semiring() # Boolean semiring
desc = GrB_Descriptor() # Descriptor for vxm
n = GrB_Matrix_nrows(A) # n = number of rows in A
GrB_Vector_new(v, GrB_INT64, n) # v(n) = 0
GrB_assign(v, GrB_NULL, GrB_NULL, 0, GrB_ALL, n, GrB_NULL) # make v dense
n = GrB_Vector_nvals(v) # finish pending work on v
GrB_Vector_new(q, GrB_BOOL, n) # q(n) = false
GrB_Vector_setElement(q, true, s) # q[s] = true, false elsewhere
GrB_Monoid_new(Lor, GrB_LOR, false)
GrB_Semiring_new(Boolean, Lor, GrB_LAND)
GrB_Descriptor_new(desc)
GrB_Descriptor_set(desc, GrB_MASK, GrB_SCMP) # invert the mask
GrB_Descriptor_set(desc, GrB_OUTP, GrB_REPLACE) # clear q first
#--------------------------------------------------------------------------
# BFS traversal and label the nodes
#--------------------------------------------------------------------------
successor = true # true when some successor found
level = 1
while successor && level <= n
# v<q> = level, using vector assign with q as the mask
GrB_assign(v, q, GrB_NULL, level, GrB_ALL, n, GrB_NULL)
# q<!v> = q ||.&& A ; finds all the unvisited
# successors from current q, using !v as the mask
GrB_vxm(q, v, GrB_NULL, Boolean, q, A, desc)
# successor = ||(q)
successor = GrB_reduce(Lor, q, GrB_NULL)
level += 1
end
GrB_free(q)
GrB_free(Lor)
GrB_free(Boolean)
GrB_free(desc)
return v
end
GrB_bfs (generic function with 1 method)
(GrB_ALL
is used to specify all rows/columns of a vector/matrix. The parameter ni/nj is ignored.)
Okay, time to test this.
julia> source = 1;
julia> V = GrB_bfs(A, OneBasedIndex(source));
julia> dists = GrB_Vector_extractTuples(V)[2];
julia> dists.-1 == gdistances(g, source)
true
Levels in this algorithm begin at 1 as opposed to gdistances
where levels begin at 0. Hence, we subtract 1 to compare results.
Thus, we see how one can use GraphBLAS to find geodesic distances.