Triangle counting is an important problem in graph mining. Two frequently used metrics in complex network analysis that require the count of triangles are the clustering coefficients & the transitivity ratio of the graph. Triangle counting is used in several real-world applications, such as detection of spamming activity, uncovering the hidden thematic structure of the web & link recommendation in online social networks. Clustering coefficient is used as an index for measuring the concentration of clusters in graphs & its tendency to decompose into communities. It has also been demonstrated that the age of a community is related to the density of triangles i.e., when a group has just formed, people pull in their like-minded friends, but the number of triangles is relatively small. If A brings in friends B & C, it may well be that B & C do not know each other. As the community matures, B & C may interact because of their membership in the community. Thus, there is a good chance that at sometime the triangle {A,B,C} will be completed.

In this post, I’ll implement an algorithm for counting triangles in a graph using SuiteSparseGraphBLAS in Julia. The past couple of weeks I’ve been working on creating a simpler interface for creating GraphBLAS matrices, vectors & descriptors. I’ll be using those new interface functions here to create GraphBLAS objects.

First we create a new GraphBLAS matrix using the facebook graph from SNAP Datasets.

julia> using GraphBLASInterface, SuiteSparseGraphBLAS, SparseArrays, LightGraphs, SNAPDatasets, BenchmarkTools

julia> g = loadsnap(:facebook_combined)
{4039, 88234} undirected simple Int64 graph

julia> I, J, X = SparseArrays.findnz(adjacency_matrix(g));

julia> GrB_init(GrB_NONBLOCKING)
GrB_SUCCESS::GrB_Info = 0

julia> A = GrB_Matrix(OneBasedIndex.(I), OneBasedIndex.(J), X)
GrB_Matrix{Int64}

Now we’ll write the count_triangles function. For more details on the algorithm refer here.

julia> function count_triangles(A)
               L = LowerTriangular(A)
               C = GrB_Matrix(Int64, size(A)...)

               # Descriptor for mxm
               desc_tb = GrB_Descriptor(GrB_INP1 => GrB_TRAN) # transpose the second matrix

               GrB_mxm(C, L, GrB_NULL, GxB_PLUS_TIMES_INT64, L, L, desc_tb) # C<L> = L ∗.+ L'
               ntriangles = GrB_reduce(GxB_PLUS_INT64_MONOID, C, GrB_NULL)

               GrB_free(C)
               GrB_free(L)

               return ntriangles
       end
count_triangles (generic function with 1 method)

julia> @btime count_triangles(A)
  19.860 ms (129 allocations: 5.33 KiB)
1612010

Julia’s standard library doesn’t support masked matrix operations, so we’ll use another method which is similar to the above algorithm in order to compare benchmarks.

julia> using LightGraphs, SNAPDatasets, LinearAlgebra, BenchmarkTools

julia> adj = adjacency_matrix(loadsnap(:facebook_combined));

julia> function tricount(A)
               L = tril(A)
               ntri = sum(sum((L*L).*L))
               return ntri
       end
tricount (generic function with 1 method)

julia> @btime tricount(adj)
  30.221 ms (9869 allocations: 14.03 MiB)
1612010

Seems like the masked version is a bit faster.

This is just a simple approach to triangle counting & there might be better ways of implementing this using Julia’s standard library or GraphBLAS but the purpose here is to illustrate the new high-level Julian interface for GraphBLAS.