LCOV - code coverage report
Current view: top level - experimental/algorithm - LAGraph_VertexCentrality_Triangle.c (source / functions) Hit Total Coverage
Test: LAGraph code coverage report. Commit id: 7b9d2ee. Current time (UTC): 2025-06-03T21:57:17Z Lines: 68 68 100.0 %
Date: 2025-06-03 22:02:40 Functions: 1 1 100.0 %

          Line data    Source code
       1             : //------------------------------------------------------------------------------
       2             : // LAGraph_VertexCentrality_triangle: vertex triangle-centrality
       3             : //------------------------------------------------------------------------------
       4             : 
       5             : // LAGraph, (c) 2019-2022 by The LAGraph Contributors, All Rights Reserved.
       6             : // SPDX-License-Identifier: BSD-2-Clause
       7             : //
       8             : // For additional details (including references to third party source code and
       9             : // other files) see the LICENSE file or contact permission@sei.cmu.edu. See
      10             : // Contributors.txt for a full list of contributors. Created, in part, with
      11             : // funding and support from the U.S. Government (see Acknowledgments.txt file).
      12             : // DM22-0790
      13             : 
      14             : // Contributed by Tim Davis, Texas A&M University.
      15             : 
      16             : //------------------------------------------------------------------------------
      17             : 
      18             : // TODO: ready for src, except remove all methods except method 3.
      19             : 
      20             : // LAGraph_VertexCentrality_Triangle: computes the TriangleCentrality of
      21             : // an undirected graph.  No self edges are allowed on the input graph.
      22             : // Methods 2 and 3 can tolerate any edge weights (they are ignored; only the
      23             : // structure of G->A is used).  Methods 1 and 1.5 require unit edge weights
      24             : // (this could be modified); results are undefined if this condition doesn't
      25             : // hold.
      26             : 
      27             : // Paul Burkhardt. 2024. Triangle Centrality. ACM Trans. Knowl. Discov. Data
      28             : // 18, 9, Article 217 (November 2024), 34 pages.
      29             : // https://doi.org/10.1145/3685677
      30             : 
      31             : // Method 3 is by far the fastest.
      32             : 
      33             : // This method uses pure GrB* methods from the v2.0 C API only.
      34             : // It does not rely on any SuiteSparse:GraphBLAS extensions.
      35             : 
      36             : // TC0: in python (called TC1 in the first draft of the paper)
      37             : //
      38             : // def triangle_centrality1(A):
      39             : //          T = A.mxm(A, mask=A)
      40             : //          y = T.reduce_vector()
      41             : //          k = y.reduce_float()
      42             : //          return(1/k)*(3*(A @ y) - 2*(T @ y) + y)
      43             : //          note: T@y is wrong. should be plus_second semiring
      44             : 
      45             : //  def TC1(A):
      46             : //      # this was "Method 1.5" in a draft, note the T.one@y is now correct:
      47             : //      T = A.mxm(A, mask=A, desc=ST1)
      48             : //      y = T.reduce_vector()
      49             : //      k = y.reduce_float()
      50             : //      return (3 * (A @ y) - 2 * (T.one() @ y) + y) / k
      51             : 
      52             : //  def TC2(A):
      53             : //      # this was TC2 in the first submission
      54             : //      T = A.plus_pair(A, mask=A, desc=ST1)
      55             : //      y = Vector.dense(FP64, A.nrows)
      56             : //      T.reduce_vector(out=y, accum=FP64.plus)
      57             : //      k = y.reduce_float()
      58             : //      return (3 * A.plus_second(y) - 2 * T.plus_second(y) + y) / k
      59             : 
      60             : //  def TC3(A):
      61             : //      M = A.tril(-1)
      62             : //      T = A.plus_pair(A, mask=M, desc=ST1)
      63             : //      y = T.reduce() + T.reduce(desc=ST0)
      64             : //      k = y.reduce_float()
      65             : //      return (
      66             : //          3 * A.plus_second(y) -
      67             : //          (2 * (T.plus_second(y) + T.plus_second(y, desc=ST0))) + y
      68             : //      ) / k
      69             : 
      70             : //------------------------------------------------------------------------------
      71             : 
      72             : #define LG_FREE_WORK                \
      73             : {                                   \
      74             :     GrB_free (&T) ;                 \
      75             :     GrB_free (&u) ;                 \
      76             :     GrB_free (&w) ;                 \
      77             :     GrB_free (&y) ;                 \
      78             :     GrB_free (&L) ;                 \
      79             : }
      80             : 
      81             : #define LG_FREE_ALL                 \
      82             : {                                   \
      83             :     LG_FREE_WORK ;                  \
      84             :     GrB_free (centrality) ;         \
      85             : }
      86             : 
      87             : #include "LG_internal.h"
      88             : 
      89             : //------------------------------------------------------------------------------
      90             : // LAGraph_VertexCentrality_Triangle: vertex triangle-centrality
      91             : //------------------------------------------------------------------------------
      92             : 
      93          49 : int LAGraph_VertexCentrality_Triangle       // vertex triangle-centrality
      94             : (
      95             :     // outputs:
      96             :     GrB_Vector *centrality,     // centrality(i): triangle centrality of i
      97             :     uint64_t *ntriangles,       // # of triangles in the graph
      98             :     // inputs:
      99             :     int method,                 // 0, 1, 2, or 3
     100             :     LAGraph_Graph G,            // input graph
     101             :     char *msg
     102             : )
     103             : {
     104             : 
     105             :     //--------------------------------------------------------------------------
     106             :     // check inputs
     107             :     //--------------------------------------------------------------------------
     108             : 
     109          49 :     LG_CLEAR_MSG ;
     110          49 :     GrB_Matrix T = NULL, L = NULL, A = NULL ;
     111          49 :     GrB_Vector y = NULL, u = NULL, w = NULL ;
     112             : 
     113          49 :     LG_ASSERT (centrality != NULL && ntriangles != NULL, GrB_NULL_POINTER) ;
     114          48 :     (*centrality) = NULL ;
     115          48 :     LG_TRY (LAGraph_CheckGraph (G, msg)) ;
     116             : 
     117          47 :     if (G->kind == LAGraph_ADJACENCY_UNDIRECTED ||
     118          10 :        (G->kind == LAGraph_ADJACENCY_DIRECTED &&
     119          10 :         G->is_symmetric_structure == LAGraph_TRUE))
     120             :     {
     121             :         // the structure of A is known to be symmetric
     122          46 :         A = G->A ;
     123             :     }
     124             :     else
     125             :     {
     126             :         // A is not known to be symmetric
     127           1 :         LG_ASSERT_MSG (false, -1005, "G->A must be symmetric") ;
     128             :     }
     129             : 
     130             :     // no self edges can be present
     131          46 :     LG_ASSERT_MSG (G->nself_edges == 0, -1004, "G->nself_edges must be zero") ;
     132             : 
     133             :     //--------------------------------------------------------------------------
     134             :     // create the T matrix
     135             :     //--------------------------------------------------------------------------
     136             : 
     137             :     GrB_Index n ;
     138          45 :     GRB_TRY (GrB_Matrix_nrows (&n, A)) ;
     139          45 :     GRB_TRY (GrB_Matrix_new (&T, GrB_FP64, n, n)) ;
     140          45 :     double k = 0 ;
     141             : 
     142             :     //--------------------------------------------------------------------------
     143             :     // compute the Triangle Centrality
     144             :     //--------------------------------------------------------------------------
     145             : 
     146          45 :     if (method == 0 || method == 1)
     147          27 :     {
     148             : 
     149             :         //----------------------------------------------------------------------
     150             :         // TC0, TC1: simplest method, requires that A has all entries equal to 1
     151             :         //----------------------------------------------------------------------
     152             : 
     153             :         // todo: remove this method when moving this code from experimental/
     154             :         // to src/
     155             : 
     156          27 :         if (method == 0)
     157             :         {
     158             :             // T<A> = A*A : method 0 (was TC1 in the first paper submission)
     159          18 :             GRB_TRY (GrB_mxm (T, A, NULL, GrB_PLUS_TIMES_SEMIRING_FP64, A, A,
     160             :                 NULL)) ;
     161             :         }
     162             :         else
     163             :         {
     164             :             // this is faster than method 0
     165             :             // T<A> = A*A' : method TC1 (was method TC1.5)
     166           9 :             GRB_TRY (GrB_mxm (T, A, NULL, GrB_PLUS_TIMES_SEMIRING_FP64, A, A,
     167             :                 GrB_DESC_T1)) ;
     168             :         }
     169             : 
     170             :         // y = sum (T), where y(i) = sum (T (i,:)) and y(i)=0 of T(i,:) is empty
     171          27 :         GRB_TRY (GrB_Vector_new (&y, GrB_FP64, n)) ;
     172          27 :         GRB_TRY (GrB_reduce (y, NULL, NULL, GrB_PLUS_MONOID_FP64, T, NULL)) ;
     173             : 
     174             :         // k = sum (y)
     175          27 :         GRB_TRY (GrB_reduce (&k, NULL, GrB_PLUS_MONOID_FP64, y, NULL)) ;
     176             : 
     177             :         // T = spones (T)
     178          27 :         GRB_TRY (GrB_assign (T, T, NULL, (double) 1, GrB_ALL, n, GrB_ALL, n,
     179             :             GrB_DESC_S)) ;
     180             : 
     181             :         // centrality = (3*A*y - 2*T*y + y) / k
     182             : 
     183             :         // w = T*y
     184          27 :         GRB_TRY (GrB_Vector_new (&w, GrB_FP64, n)) ;
     185          27 :         GRB_TRY (GrB_mxv (w, NULL, NULL, GrB_PLUS_TIMES_SEMIRING_FP64, T, y,
     186             :             NULL)) ;
     187             : 
     188             :         // w = (-2)*w
     189          27 :         double minus_two = -2 ;
     190          27 :         GRB_TRY (GrB_apply (w, NULL, NULL, GrB_TIMES_FP64, minus_two, w,
     191             :             NULL)) ;
     192             : 
     193             :         // u = A*y
     194          27 :         GRB_TRY (GrB_Vector_new (&u, GrB_FP64, n)) ;
     195          27 :         GRB_TRY (GrB_mxv (u, NULL, NULL, GrB_PLUS_TIMES_SEMIRING_FP64, A, y,
     196             :             NULL)) ;
     197             : 
     198             :     }
     199          18 :     else if (method == 2)
     200             :     {
     201             : 
     202             :         //----------------------------------------------------------------------
     203             :         // TC2: using LAGraph_plus_one_fp64 semiring
     204             :         //----------------------------------------------------------------------
     205             : 
     206             :         // todo: remove this method when moving this code from experimental/
     207             :         // to src/
     208             : 
     209             :         // T{A} = A*A' (each triangle is seen 6 times)
     210           9 :         GRB_TRY (GrB_mxm (T, A, NULL, LAGraph_plus_one_fp64, A, A,
     211             :             GrB_DESC_ST1)) ;
     212             : 
     213             :         // y = sum (T), where y(i) = sum (T (i,:)) and y(i)=0 of T(i,:) is empty
     214           9 :         GRB_TRY (GrB_Vector_new (&y, GrB_FP64, n)) ;
     215           9 :         GRB_TRY (GrB_assign (y, NULL, NULL, ((double) 0), GrB_ALL, n, NULL)) ;
     216           9 :         GRB_TRY (GrB_reduce (y, NULL, GrB_PLUS_FP64, GrB_PLUS_MONOID_FP64, T,
     217             :             NULL)) ;
     218             : 
     219             :         // k = sum (y)
     220           9 :         GRB_TRY (GrB_reduce (&k, NULL, GrB_PLUS_MONOID_FP64, y, NULL)) ;
     221             : 
     222             :         // centrality = (3*A*y - 2*T*y + y) / k
     223             : 
     224             :         // w = T*y
     225           9 :         GRB_TRY (GrB_Vector_new (&w, GrB_FP64, n)) ;
     226           9 :         GRB_TRY (GrB_mxv (w, NULL, NULL, LAGraph_plus_second_fp64, T, y,
     227             :             NULL)) ;
     228             : 
     229             :         // w = (-2)*w
     230           9 :         double minus_two = -2 ;
     231           9 :         GRB_TRY (GrB_apply (w, NULL, NULL, GrB_TIMES_FP64, minus_two, w,
     232             :             NULL)) ;
     233             : 
     234             :         // u = A*y
     235           9 :         GRB_TRY (GrB_Vector_new (&u, GrB_FP64, n)) ;
     236           9 :         GRB_TRY (GrB_mxv (u, NULL, NULL, LAGraph_plus_second_fp64, A, y,
     237             :             NULL)) ;
     238             : 
     239             :     }
     240           9 :     else if (method == 3)
     241             :     {
     242             : 
     243             :         //----------------------------------------------------------------------
     244             :         // TC3: using tril.  This is the fastest method.
     245             :         //----------------------------------------------------------------------
     246             : 
     247             :         // todo: When this method is moved to src/, keep this method only.
     248             : 
     249             :         // L = tril (A,-1)
     250           9 :         GRB_TRY (GrB_Matrix_new (&L, GrB_FP64, n, n)) ;
     251           9 :         GRB_TRY (GrB_select (L, NULL, NULL, GrB_TRIL, A, (int64_t) (-1),
     252             :             NULL)) ;
     253             : 
     254             :         // T{L}= A*A' (each triangle is seen 3 times; T is lower triangular)
     255           9 :         GRB_TRY (GrB_mxm (T, L, NULL, LAGraph_plus_one_fp64, A, A,
     256             :             GrB_DESC_ST1)) ;
     257           9 :         GRB_TRY (GrB_free (&L)) ;
     258             : 
     259             :         // y = sum (T'), where y(j) = sum (T (:,j)) and y(j)=0 if T(:,j) empty
     260           9 :         GRB_TRY (GrB_Vector_new (&y, GrB_FP64, n)) ;
     261           9 :         GRB_TRY (GrB_assign (y, NULL, NULL, ((double) 0), GrB_ALL, n, NULL)) ;
     262           9 :         GRB_TRY (GrB_reduce (y, NULL, GrB_PLUS_FP64, GrB_PLUS_MONOID_FP64, T,
     263             :             GrB_DESC_T0)) ;
     264             :         // y += sum (T)
     265           9 :         GRB_TRY (GrB_reduce (y, NULL, GrB_PLUS_FP64, GrB_PLUS_MONOID_FP64, T,
     266             :             NULL)) ;
     267             : 
     268             :         // k = sum (y).  y is the same as the other methods, above, just
     269             :         // computed using the lower triangular matrix T.  So k/6 is the total
     270             :         // number of triangles in the graph.
     271           9 :         GRB_TRY (GrB_reduce (&k, NULL, GrB_PLUS_MONOID_FP64, y, NULL)) ;
     272             : 
     273             :         // centrality = (3*A*y - 2* (T*y + T'*y) + y) / k
     274             : 
     275             :         // w = T*y
     276           9 :         GRB_TRY (GrB_Vector_new (&w, GrB_FP64, n)) ;
     277           9 :         GRB_TRY (GrB_mxv (w, NULL, NULL, LAGraph_plus_second_fp64, T, y,
     278             :             NULL)) ;
     279             :         // w += T'*y
     280           9 :         GRB_TRY (GrB_mxv (w, NULL, GrB_PLUS_FP64, LAGraph_plus_second_fp64,
     281             :             T, y, GrB_DESC_T0)) ;
     282             : 
     283             :         // w = (-2)*w
     284           9 :         double minus_two = -2 ;
     285           9 :         GRB_TRY (GrB_apply (w, NULL, NULL, GrB_TIMES_FP64, minus_two, w,
     286             :             NULL)) ;
     287             : 
     288             :         // u = A*y
     289           9 :         GRB_TRY (GrB_Vector_new (&u, GrB_FP64, n)) ;
     290           9 :         GRB_TRY (GrB_mxv (u, NULL, NULL, LAGraph_plus_second_fp64, A, y,
     291             :             NULL)) ;
     292             : 
     293             :     }
     294             : 
     295             :     //--------------------------------------------------------------------------
     296             :     // centrality = (3*u + w + y) / k for all 4 methods
     297             :     //--------------------------------------------------------------------------
     298             : 
     299             :     // centrality = 3*u
     300          45 :     GRB_TRY (GrB_Vector_new (centrality, GrB_FP64, n)) ;
     301          45 :     const double three = 3 ;
     302          45 :     GRB_TRY (GrB_apply (*centrality, NULL, NULL, GrB_TIMES_FP64, three, u,
     303             :         NULL)) ;
     304             : 
     305             :     // centrality += (w + y)
     306          45 :     GRB_TRY (GrB_eWiseAdd (*centrality, NULL, GrB_PLUS_FP64, GrB_PLUS_FP64,
     307             :         w, y, NULL)) ;
     308             : 
     309             :     // centrality = centrality / k
     310          45 :     GRB_TRY (GrB_apply (*centrality, NULL, NULL, GrB_TIMES_FP64,
     311             :         ((k == 0) ? 1.0 : (1.0/k)), *centrality, NULL)) ;
     312             : 
     313          45 :     (*ntriangles) = (uint64_t) (k/6) ;     // # triangles is k/6 for all methods
     314             : 
     315             :     //--------------------------------------------------------------------------
     316             :     // free workspace and return result
     317             :     //--------------------------------------------------------------------------
     318             : 
     319          45 :     LG_FREE_WORK ;
     320          45 :     return (GrB_SUCCESS) ;
     321             : }

Generated by: LCOV version 1.14