//------------------------------------------------------------------------------
// LAGraph_VertexCentrality_triangle: vertex triangle-centrality
//------------------------------------------------------------------------------
// LAGraph, (c) 2019-2022 by The LAGraph Contributors, All Rights Reserved.
// SPDX-License-Identifier: BSD-2-Clause
//
// For additional details (including references to third party source code and
// other files) see the LICENSE file or contact permission@sei.cmu.edu. See
// Contributors.txt for a full list of contributors. Created, in part, with
// funding and support from the U.S. Government (see Acknowledgments.txt file).
// DM22-0790
// Contributed by Tim Davis, Texas A&M University.
//------------------------------------------------------------------------------
// LAGraph_VertexCentrality_Triangle: computes the TriangleCentrality of
// an undirected graph. No self edges are allowed on the input graph.
// Methods 2 and 3 can tolerate any edge weights (they are ignored; only the
// structure of G->A is used). Methods 1 and 1.5 require unit edge weights
// (this could be modified); results are undefined if this condition doesn't
// hold.
// P. Burkhardt, "Triangle centrality," https://arxiv.org/pdf/2105.00110.pdf,
// April 2021.
// Method 3 is by far the fastest.
// This method uses pure GrB* methods from the v2.0 C API only.
// It does not rely on any SuiteSparse:GraphBLAS extensions.
// TC0: in python (called TC1 in the first draft of the paper)
//
// def triangle_centrality1(A):
// T = A.mxm(A, mask=A)
// y = T.reduce_vector()
// k = y.reduce_float()
// return(1/k)*(3*(A @ y) - 2*(T @ y) + y)
// note: T@y is wrong. should be plus_second semiring
// def TC1(A):
// # this was "Method 1.5" in a draft, note the T.one@y is now correct:
// T = A.mxm(A, mask=A, desc=ST1)
// y = T.reduce_vector()
// k = y.reduce_float()
// return (3 * (A @ y) - 2 * (T.one() @ y) + y) / k
// def TC2(A):
// # this was TC2 in the first submission
// T = A.plus_pair(A, mask=A, desc=ST1)
// y = Vector.dense(FP64, A.nrows)
// T.reduce_vector(out=y, accum=FP64.plus)
// k = y.reduce_float()
// return (3 * A.plus_second(y) - 2 * T.plus_second(y) + y) / k
// def TC3(A):
// M = A.tril(-1)
// T = A.plus_pair(A, mask=M, desc=ST1)
// y = T.reduce() + T.reduce(desc=ST0)
// k = y.reduce_float()
// return (
// 3 * A.plus_second(y) -
// (2 * (T.plus_second(y) + T.plus_second(y, desc=ST0))) + y
// ) / k
//------------------------------------------------------------------------------
#define LG_FREE_WORK \
{ \
GrB_free (&T) ; \
GrB_free (&u) ; \
GrB_free (&w) ; \
GrB_free (&y) ; \
GrB_free (&L) ; \
}
#define LG_FREE_ALL \
{ \
LG_FREE_WORK ; \
GrB_free (centrality) ; \
}
#include "LG_internal.h"
//------------------------------------------------------------------------------
// LAGraph_VertexCentrality_Triangle: vertex triangle-centrality
//------------------------------------------------------------------------------
int LAGraph_VertexCentrality_Triangle // vertex triangle-centrality
(
// outputs:
GrB_Vector *centrality, // centrality(i): triangle centrality of i
uint64_t *ntriangles, // # of triangles in the graph
// inputs:
int method, // 0, 1, 2, or 3
LAGraph_Graph G, // input graph
char *msg
)
{
//--------------------------------------------------------------------------
// check inputs
//--------------------------------------------------------------------------
LG_CLEAR_MSG ;
GrB_Matrix T = NULL, L = NULL, A = NULL ;
GrB_Vector y = NULL, u = NULL, w = NULL ;
LG_ASSERT (centrality != NULL && ntriangles != NULL, GrB_NULL_POINTER) ;
(*centrality) = NULL ;
LG_TRY (LAGraph_CheckGraph (G, msg)) ;
if (G->kind == LAGraph_ADJACENCY_UNDIRECTED ||
(G->kind == LAGraph_ADJACENCY_DIRECTED &&
G->is_symmetric_structure == LAGraph_TRUE))
{
// the structure of A is known to be symmetric
A = G->A ;
}
else
{
// A is not known to be symmetric
LG_ASSERT_MSG (false, -1005, "G->A must be symmetric") ;
}
// no self edges can be present
LG_ASSERT_MSG (G->nself_edges == 0, -1004, "G->nself_edges must be zero") ;
//--------------------------------------------------------------------------
// create the T matrix
//--------------------------------------------------------------------------
GrB_Index n ;
GRB_TRY (GrB_Matrix_nrows (&n, A)) ;
GRB_TRY (GrB_Matrix_new (&T, GrB_FP64, n, n)) ;
double k = 0 ;
//--------------------------------------------------------------------------
// compute the Triangle Centrality
//--------------------------------------------------------------------------
if (method == 0 || method == 1)
{
//----------------------------------------------------------------------
// TC0, TC1: simplest method, requires that A has all entries equal to 1
//----------------------------------------------------------------------
// todo: remove this method when moving this code from experimental/
// to src/
if (method == 0)
{
// T = A*A : method 0 (was TC1 in the first paper submission)
GRB_TRY (GrB_mxm (T, A, NULL, GrB_PLUS_TIMES_SEMIRING_FP64, A, A,
NULL)) ;
}
else
{
// this is faster than method 0
// T = A*A' : method TC1 (was method TC1.5)
GRB_TRY (GrB_mxm (T, A, NULL, GrB_PLUS_TIMES_SEMIRING_FP64, A, A,
GrB_DESC_T1)) ;
}
// y = sum (T), where y(i) = sum (T (i,:)) and y(i)=0 of T(i,:) is empty
GRB_TRY (GrB_Vector_new (&y, GrB_FP64, n)) ;
GRB_TRY (GrB_reduce (y, NULL, NULL, GrB_PLUS_MONOID_FP64, T, NULL)) ;
// k = sum (y)
GRB_TRY (GrB_reduce (&k, NULL, GrB_PLUS_MONOID_FP64, y, NULL)) ;
// T = spones (T)
GRB_TRY (GrB_assign (T, T, NULL, (double) 1, GrB_ALL, n, GrB_ALL, n,
GrB_DESC_S)) ;
// centrality = (3*A*y - 2*T*y + y) / k
// w = T*y
GRB_TRY (GrB_Vector_new (&w, GrB_FP64, n)) ;
GRB_TRY (GrB_mxv (w, NULL, NULL, GrB_PLUS_TIMES_SEMIRING_FP64, T, y,
NULL)) ;
// w = (-2)*w
double minus_two = -2 ;
GRB_TRY (GrB_apply (w, NULL, NULL, GrB_TIMES_FP64, minus_two, w,
NULL)) ;
// u = A*y
GRB_TRY (GrB_Vector_new (&u, GrB_FP64, n)) ;
GRB_TRY (GrB_mxv (u, NULL, NULL, GrB_PLUS_TIMES_SEMIRING_FP64, A, y,
NULL)) ;
}
else if (method == 2)
{
//----------------------------------------------------------------------
// TC2: using LAGraph_plus_one_fp64 semiring
//----------------------------------------------------------------------
// todo: remove this method when moving this code from experimental/
// to src/
// T{A} = A*A' (each triangle is seen 6 times)
GRB_TRY (GrB_mxm (T, A, NULL, LAGraph_plus_one_fp64, A, A,
GrB_DESC_ST1)) ;
// y = sum (T), where y(i) = sum (T (i,:)) and y(i)=0 of T(i,:) is empty
GRB_TRY (GrB_Vector_new (&y, GrB_FP64, n)) ;
GRB_TRY (GrB_assign (y, NULL, NULL, ((double) 0), GrB_ALL, n, NULL)) ;
GRB_TRY (GrB_reduce (y, NULL, GrB_PLUS_FP64, GrB_PLUS_MONOID_FP64, T,
NULL)) ;
// k = sum (y)
GRB_TRY (GrB_reduce (&k, NULL, GrB_PLUS_MONOID_FP64, y, NULL)) ;
// centrality = (3*A*y - 2*T*y + y) / k
// w = T*y
GRB_TRY (GrB_Vector_new (&w, GrB_FP64, n)) ;
GRB_TRY (GrB_mxv (w, NULL, NULL, LAGraph_plus_second_fp64, T, y,
NULL)) ;
// w = (-2)*w
double minus_two = -2 ;
GRB_TRY (GrB_apply (w, NULL, NULL, GrB_TIMES_FP64, minus_two, w,
NULL)) ;
// u = A*y
GRB_TRY (GrB_Vector_new (&u, GrB_FP64, n)) ;
GRB_TRY (GrB_mxv (u, NULL, NULL, LAGraph_plus_second_fp64, A, y,
NULL)) ;
}
else if (method == 3)
{
//----------------------------------------------------------------------
// TC3: using tril. This is the fastest method.
//----------------------------------------------------------------------
// todo: When this method is moved to src/, keep this method only.
// L = tril (A,-1)
GRB_TRY (GrB_Matrix_new (&L, GrB_FP64, n, n)) ;
GRB_TRY (GrB_select (L, NULL, NULL, GrB_TRIL, A, (int64_t) (-1),
NULL)) ;
// T{L}= A*A' (each triangle is seen 3 times; T is lower triangular)
GRB_TRY (GrB_mxm (T, L, NULL, LAGraph_plus_one_fp64, A, A,
GrB_DESC_ST1)) ;
GRB_TRY (GrB_free (&L)) ;
// y = sum (T'), where y(j) = sum (T (:,j)) and y(j)=0 if T(:,j) empty
GRB_TRY (GrB_Vector_new (&y, GrB_FP64, n)) ;
GRB_TRY (GrB_assign (y, NULL, NULL, ((double) 0), GrB_ALL, n, NULL)) ;
GRB_TRY (GrB_reduce (y, NULL, GrB_PLUS_FP64, GrB_PLUS_MONOID_FP64, T,
GrB_DESC_T0)) ;
// y += sum (T)
GRB_TRY (GrB_reduce (y, NULL, GrB_PLUS_FP64, GrB_PLUS_MONOID_FP64, T,
NULL)) ;
// k = sum (y). y is the same as the other methods, above, just
// computed using the lower triangular matrix T. So k/6 is the total
// number of triangles in the graph.
GRB_TRY (GrB_reduce (&k, NULL, GrB_PLUS_MONOID_FP64, y, NULL)) ;
// centrality = (3*A*y - 2* (T*y + T'*y) + y) / k
// w = T*y
GRB_TRY (GrB_Vector_new (&w, GrB_FP64, n)) ;
GRB_TRY (GrB_mxv (w, NULL, NULL, LAGraph_plus_second_fp64, T, y,
NULL)) ;
// w += T'*y
GRB_TRY (GrB_mxv (w, NULL, GrB_PLUS_FP64, LAGraph_plus_second_fp64,
T, y, GrB_DESC_T0)) ;
// w = (-2)*w
double minus_two = -2 ;
GRB_TRY (GrB_apply (w, NULL, NULL, GrB_TIMES_FP64, minus_two, w,
NULL)) ;
// u = A*y
GRB_TRY (GrB_Vector_new (&u, GrB_FP64, n)) ;
GRB_TRY (GrB_mxv (u, NULL, NULL, LAGraph_plus_second_fp64, A, y,
NULL)) ;
}
//--------------------------------------------------------------------------
// centrality = (3*u + w + y) / k for all 4 methods
//--------------------------------------------------------------------------
// centrality = 3*u
GRB_TRY (GrB_Vector_new (centrality, GrB_FP64, n)) ;
const double three = 3 ;
GRB_TRY (GrB_apply (*centrality, NULL, NULL, GrB_TIMES_FP64, three, u,
NULL)) ;
// centrality += (w + y)
GRB_TRY (GrB_eWiseAdd (*centrality, NULL, GrB_PLUS_FP64, GrB_PLUS_FP64,
w, y, NULL)) ;
// centrality = centrality / k
GRB_TRY (GrB_apply (*centrality, NULL, NULL, GrB_TIMES_FP64,
((k == 0) ? 1.0 : (1.0/k)), *centrality, NULL)) ;
(*ntriangles) = (uint64_t) (k/6) ; // # triangles is k/6 for all methods
//--------------------------------------------------------------------------
// free workspace and return result
//--------------------------------------------------------------------------
LG_FREE_WORK ;
return (GrB_SUCCESS) ;
}