LCOV - code coverage report
Current view: top level - experimental/algorithm - LAGraph_cdlp.c (source / functions) Hit Total Coverage
Test: LAGraph code coverage report. Commit id: 7543de1. Current time (UTC): 2026-07-21T18:04:51Z Lines: 124 124 100.0 %
Date: 2026-07-21 18:11:56 Functions: 13 13 100.0 %

          Line data    Source code
       1             : //------------------------------------------------------------------------------
       2             : // LAGraph_cdlp: community detection using label propagation
       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 Gabor Szarnyas and Balint Hegyi, Budapest University of
      15             : // Technology and Economics (with accented characters: G\'{a}bor Sz\'{a}rnyas
      16             : // and B\'{a}lint Hegyi, using LaTeX syntax).
      17             : // https://inf.mit.bme.hu/en/members/szarnyasg .
      18             : 
      19             : // Modified by Pascal Costanza, Intel, Belgium
      20             : 
      21             : // NOTE: the calloc/free below must be thread-safe,
      22             : // so it cannot use LAGraph_Malloc/LAGraph_Free (which can use
      23             : // the Rapids Memory Manager methods when using CUDA, and those
      24             : // methods are not yet thread-safe).
      25             : 
      26             : //------------------------------------------------------------------------------
      27             : 
      28             : // ## Background
      29             : //
      30             : // This function was originally written for the LDBC Graphalytics benchmark.
      31             : //
      32             : // The community detection using label propagation (CDLP) algorithm is
      33             : // defined both for directed and undirected graphs.
      34             : //
      35             : // The definition implemented here is described in the following document:
      36             : // https://ldbc.github.io/ldbc_graphalytics_docs/graphalytics_spec.pdf
      37             : //
      38             : // The algorithm is based on the one given in the following paper:
      39             : //
      40             : // Usha Raghavan, Reka Albert, and Soundar Kumara. "Near linear time algorithm
      41             : // to detect community structures in large-scale networks". In: Physical
      42             : // Review E 76.3 (2007), p. 036106, https://arxiv.org/abs/0709.2938
      43             : //
      44             : // The key idea of the algorithm is that each vertex is assigned the label
      45             : // that is most frequent among its neighbors. To allow reproducible
      46             : // experiments, the algorithm is modified to guarantee deterministic behavior:
      47             : // it always picks the smallest label in case of a tie:
      48             : //
      49             : // min ( argmax_{l} (#neighbors with label l) )
      50             : //
      51             : // In other words, we need to compute the *minimum mode value* (minmode) for
      52             : // the labels among the neighbors.
      53             : //
      54             : // For directed graphs, a label on a neighbor that is connected through both
      55             : // an outgoing and on an incoming edge counts twice:
      56             : //
      57             : // min ( argmax_{l} (#incoming neighbors with l + #outgoing neighbors with l) )
      58             : 
      59             : #define LG_FREE_ALL                                                     \
      60             : {                                                                       \
      61             :     GrB_free (&S) ;                                                     \
      62             :     GrB_free (&T) ;                                                     \
      63             :     LAGraph_Free ((void **) &Sp, NULL) ;                                \
      64             :     LAGraph_Free ((void **) &Si, NULL) ;                                \
      65             :     LAGraph_Free ((void **) &Tp, NULL) ;                                \
      66             :     LAGraph_Free ((void **) &Ti, NULL) ;                                \
      67             :     LAGraph_Free ((void **) &L, NULL) ;                                 \
      68             :     LAGraph_Free ((void **) &L_next, NULL) ;                            \
      69             :     ptable_pool_free (counts_pool, max_threads) ;                       \
      70             :     counts_pool = NULL ;                                                \
      71             :     GrB_free (&CDLP) ;                                                  \
      72             : }
      73             : 
      74             : #include <LAGraph.h>
      75             : #include <LAGraphX.h>
      76             : #if LAGRAPH_HAS_STDALIGN_H
      77             :     #include <stdalign.h>
      78             :     #define ALIGNAS_64 alignas(64)
      79             : #else
      80             :     // the alignas keyword/macro is not available
      81             :     #define ALIGNAS_64
      82             : #endif
      83             : #include "LG_internal.h"
      84             : 
      85             : // A Go-style slice / Lisp-style property list
      86             : typedef struct {
      87             :     GrB_Index* entries;
      88             :     size_t len, cap;
      89             : } plist;
      90             : 
      91        5120 : void plist_free(plist *list) {
      92        5120 :     free(list->entries);        // NOTE: cannot be LAGraph_Free
      93        5120 : }
      94             : 
      95   161728512 : void plist_clear(plist *list) {
      96   161728512 :     list->len = 0;
      97   161728512 : }
      98             : 
      99      727057 : void plist_append(plist* list, GrB_Index key, GrB_Index value) {
     100      727057 :     if (list->len == list->cap) {
     101        1177 :         size_t new_size = list->cap == 0 ? 16 : 2*list->cap;
     102        1177 :         list->entries = (GrB_Index*)realloc(list->entries, new_size * sizeof(GrB_Index));
     103        1177 :         list->cap = new_size;
     104             :     }
     105      727057 :     list->entries[list->len] = key;
     106      727057 :     list->entries[list->len+1] = value;
     107      727057 :     list->len += 2;
     108      727057 : }
     109             : 
     110    17652992 : GrB_Index plist_add(plist* list, GrB_Index entry) {
     111    17655030 :     for (size_t i = 0; i < list->len; i += 2) {
     112    16927973 :         if (list->entries[i] == entry) {
     113    16925935 :             return ++list->entries[i+1];
     114             :         }
     115             :     }
     116      727057 :     plist_append(list, entry, 1);
     117      727057 :     return 1;
     118             : }
     119             : 
     120             : typedef void (*plist_reducer) (GrB_Index* entry1, GrB_Index* count1, GrB_Index entry2, GrB_Index count2);
     121             : 
     122   161728512 : void plist_reduce(plist* list, GrB_Index* entry, GrB_Index* count, plist_reducer reducer) {
     123   162455569 :     for (size_t i = 0; i < list->len; i += 2) {
     124      727057 :         reducer(entry, count, list->entries[i], list->entries[i+1]);
     125             :     }
     126   161728512 : }
     127             : 
     128      727057 : void counts_reducer(GrB_Index* e1, GrB_Index* c1, GrB_Index e2, GrB_Index c2) {
     129      727057 :     if (*c1 > c2) {
     130      187542 :         return;
     131             :     }
     132      539515 :     if (c2 > *c1) {
     133      428889 :         *e1 = e2;
     134      428889 :         *c1 = c2;
     135      428889 :         return;
     136             :     }
     137      110626 :     if (*e1 < e2) {
     138       93071 :         return;
     139             :     }
     140       17555 :     *e1 = e2;
     141       17555 :     *c1 = c2;
     142             : }
     143             : 
     144             : 
     145             : #define bucket_bits 9llu
     146             : #define nof_buckets (1llu << bucket_bits)
     147             : #define bucket_shift (64llu - bucket_bits)
     148             : 
     149             : typedef struct {
     150             :     ALIGNAS_64 plist buckets[nof_buckets];
     151             : } ptable;
     152             : 
     153          10 : void ptable_free(ptable* table) {
     154        5130 :     for (size_t i = 0; i < nof_buckets; i++) {
     155        5120 :         plist_free(&table->buckets[i]);
     156             :     }
     157          10 : }
     158             : 
     159          20 : void ptable_pool_free(ptable* table, size_t n) {
     160          20 :     if (table == NULL) {
     161          10 :         return;
     162             :     }
     163          20 :     for (size_t i = 0; i < n; i++) {
     164          10 :         ptable_free(&table[i]);
     165             :     }
     166          10 :     free(table);        // NOTE: cannot be LAGraph_Free
     167             : }
     168             : 
     169      315876 : void ptable_clear(ptable* table) {
     170   162044388 :     for (size_t i = 0; i < nof_buckets; i++) {
     171   161728512 :         plist_clear(&table->buckets[i]);
     172             :     }
     173      315876 : }
     174             : 
     175    17652992 : GrB_Index fib_reduce(GrB_Index x)
     176             : {
     177             :     // 2^64 / golden ratio = 11400714819323198485
     178    17652992 :     GrB_Index fibhash = x * 11400714819323198485llu;
     179             :     // fast reduce
     180    17652992 :     return fibhash >> bucket_shift;
     181             : }
     182             : 
     183    17652992 : void ptable_add(ptable* table, GrB_Index entry) {
     184    17652992 :     plist_add(&table->buckets[fib_reduce(entry)], entry);
     185    17652992 : }
     186             : 
     187      315876 : void ptable_reduce(ptable* table, GrB_Index* entry, GrB_Index* count, plist_reducer reducer) {
     188      315876 :     *entry = GrB_INDEX_MAX + 1;
     189      315876 :     *count = 0;
     190   162044388 :     for (GrB_Index i = 0; i < nof_buckets; i++) {
     191   161728512 :         plist_reduce(&table->buckets[i], entry, count, reducer);
     192             :     }
     193      315876 : }
     194             : 
     195             : //****************************************************************************
     196             : 
     197          11 : int LAGraph_cdlp
     198             :         (
     199             :                 GrB_Vector *CDLP_handle,    // output vector
     200             :                 LAGraph_Graph G,            // input graph
     201             :                 int itermax,                // max number of iterations
     202             :                 char *msg
     203             :         )
     204             : {
     205             :     GrB_Info info;
     206          11 :     LG_CLEAR_MSG ;
     207             : 
     208          11 :     GrB_Matrix S = NULL, T = NULL ;
     209             :     GrB_Vector CDLP ;
     210          11 :     GrB_Index *Sp = NULL, *Si = NULL, *Tp = NULL, *Ti = NULL, *L = NULL, *L_next = NULL ;
     211          11 :     ptable *counts_pool = NULL ;
     212             : 
     213             :     #ifdef _OPENMP
     214             :     size_t max_threads = omp_get_max_threads();
     215             :     #else
     216          11 :     size_t max_threads = 1 ;
     217             :     #endif
     218             : 
     219             :     //--------------------------------------------------------------------------
     220             :     // check inputs
     221             :     //--------------------------------------------------------------------------
     222             : 
     223          11 :     if (CDLP_handle == NULL)
     224             :     {
     225           1 :         return GrB_NULL_POINTER;
     226             :     }
     227             : 
     228          10 :     GrB_Matrix A = G->A ;
     229             : 
     230             :     //--------------------------------------------------------------------------
     231             :     // ensure input is binary and has no self-edges
     232             :     //--------------------------------------------------------------------------
     233             : 
     234             :     GrB_Index n;
     235          10 :     GRB_TRY (GrB_Matrix_nrows(&n, A)) ;
     236             : 
     237          10 :     GRB_TRY (GrB_Matrix_new (&S, GrB_UINT64, n, n)) ;
     238          10 :     GRB_TRY (GrB_apply (S, GrB_NULL, GrB_NULL, GrB_ONEB_UINT64, A, 0, GrB_NULL)) ;
     239             : 
     240          10 :     if (G->kind == LAGraph_ADJACENCY_DIRECTED) {
     241          10 :         GRB_TRY (GrB_Matrix_new (&T, GrB_UINT64, n, n)) ;
     242          10 :         GRB_TRY (GrB_transpose (T, GrB_NULL, GrB_NULL, S, GrB_NULL)) ;
     243          10 :         void * Tx = NULL ;
     244             :         GrB_Index Tps, Tis, Txs ;
     245             : #if LAGRAPH_SUITESPARSE
     246             :         bool Tiso, Tjumbled ;
     247          10 :         GRB_TRY (GxB_Matrix_unpack_CSR (T, &Tp, &Ti, &Tx, &Tps, &Tis, &Txs, &Tiso, &Tjumbled, GrB_NULL)) ;
     248             : #else
     249             :         GRB_TRY (GrB_Matrix_exportSize (&Tps, &Tis, &Txs, GrB_CSR_FORMAT, T)) ;
     250             :         LAGRAPH_TRY (LAGraph_Malloc ((void *)&Tp, Tps, sizeof(GrB_Index), NULL)) ;
     251             :         LAGRAPH_TRY (LAGraph_Malloc ((void *)&Ti, Tis, sizeof(GrB_Index), NULL)) ;
     252             :         LAGRAPH_TRY (LAGraph_Malloc ((void *)&Tx, Txs, sizeof(GrB_UINT64), NULL)) ;
     253             :         GRB_TRY (GrB_Matrix_export (Tp, Ti, (uint64_t *)Tx, &Tps, &Tis, &Txs, GrB_CSR_FORMAT, T)) ;
     254             : #endif
     255          10 :         LAGRAPH_TRY (LAGraph_Free ((void *)&Tx, NULL)) ;
     256          10 :         GRB_TRY (GrB_free (&T)) ;
     257             :     }
     258             : 
     259             :     {
     260          10 :         void * Sx = NULL ;
     261             :         GrB_Index Sps, Sis, Sxs ;
     262             : #if LAGRAPH_SUITESPARSE
     263             :         bool Siso, Sjumbled ;
     264          10 :         GRB_TRY (GxB_Matrix_unpack_CSR (S, &Sp, &Si, &Sx, &Sps, &Sis, &Sxs, &Siso, &Sjumbled, GrB_NULL)) ;
     265             : #else
     266             :         GRB_TRY (GrB_Matrix_exportSize (&Sps, &Sis, &Sxs, GrB_CSR_FORMAT, S)) ;
     267             :         LAGRAPH_TRY (LAGraph_Malloc ((void *)&Sp, Sps, sizeof(GrB_Index), NULL)) ;
     268             :         LAGRAPH_TRY (LAGraph_Malloc ((void *)&Si, Sis, sizeof(GrB_Index), NULL)) ;
     269             :         LAGRAPH_TRY (LAGraph_Malloc ((void *)&Sx, Sxs, sizeof(GrB_UINT64), NULL)) ;
     270             :         GRB_TRY (GrB_Matrix_export (Sp, Si, (uint64_t *)Sx, &Sps, &Sis, &Sxs, GrB_CSR_FORMAT, S)) ;
     271             : #endif
     272          10 :         LAGRAPH_TRY (LAGraph_Free((void *)&Sx, NULL)) ;
     273          10 :         GRB_TRY (GrB_free (&S)) ;
     274             :     }
     275             : 
     276          10 :     LG_TRY (LAGraph_Malloc ((void **) &L, n, sizeof (GrB_Index), msg)) ;
     277             : 
     278        3304 :     for (GrB_Index i = 0; i < n; i++) {
     279        3294 :         L[i] = i ;
     280             :     }
     281          10 :     LG_TRY (LAGraph_Malloc ((void **) &L_next, n, sizeof (GrB_Index), msg)) ;
     282             : 
     283          10 :     counts_pool = calloc(max_threads, sizeof(ptable));
     284             : 
     285         353 :     for (int iteration = 0; iteration < itermax; iteration++) {
     286             : 
     287             :         int64_t i;
     288             : #pragma omp parallel for schedule(dynamic)
     289      316226 :         for (i = 0; i < n; i++) {
     290             :             #ifdef _OPENMP
     291             :             int thread_id = omp_get_thread_num() ;
     292             :             #else
     293      315876 :             int thread_id = 0 ;
     294             :             #endif
     295      315876 :             ptable *counts = &counts_pool [thread_id] ;
     296      315876 :             GrB_Index* neighbors = Si + Sp[i] ;
     297      315876 :             GrB_Index sz = Sp[i+1] - Sp[i] ;
     298     9142372 :             for (GrB_Index j = 0; j < sz; j++) {
     299     8826496 :                 ptable_add (counts, L[neighbors[j]]) ;
     300             :             }
     301      315876 :             if (G->kind == LAGraph_ADJACENCY_DIRECTED) {
     302      315876 :                 neighbors = Ti + Tp[i] ;
     303      315876 :                 sz = Tp[i+1] - Tp[i] ;
     304     9142372 :                 for (GrB_Index j = 0; j < sz; j++) {
     305     8826496 :                     ptable_add (counts, L[neighbors[j]]) ;
     306             :                 }
     307             :             }
     308             :             GrB_Index best_label, best_count ;
     309      315876 :             ptable_reduce (counts, &best_label, &best_count, counts_reducer) ;
     310      315876 :             L_next[i] = best_label ;
     311      315876 :             ptable_clear (counts) ;
     312             :         }
     313             : 
     314         350 :         GrB_Index* tmp = L ; L = L_next ; L_next = tmp ;
     315         350 :         bool changed = false ;
     316       47967 :         for (GrB_Index i = 0; i < n; i++) {
     317       47960 :             if (L[i] != L_next[i]) {
     318         343 :                 changed = true ;
     319         343 :                 break ;
     320             :             }
     321             :         }
     322         350 :         if (!changed) {
     323           7 :              break ;
     324             :         }
     325             :     }
     326             : 
     327          10 :     ptable_pool_free(counts_pool, max_threads); counts_pool = NULL;
     328             : 
     329             :     //--------------------------------------------------------------------------
     330             :     // extract final labels to the result vector
     331             :     //--------------------------------------------------------------------------
     332             : 
     333          10 :     GRB_TRY (GrB_Vector_new(&CDLP, GrB_UINT64, n))
     334        3304 :     for (GrB_Index i = 0; i < n; i++)
     335             :     {
     336        3294 :         GrB_Index l = L[i];
     337             : //      if (l == GrB_INDEX_MAX + 1) { l = i ; }
     338        3294 :         l = (l == GrB_INDEX_MAX + 1) ? i : l ;
     339        3294 :         GRB_TRY (GrB_Vector_setElement(CDLP, l, i))
     340             :     }
     341             : 
     342             :     //--------------------------------------------------------------------------
     343             :     // free workspace and return result
     344             :     //--------------------------------------------------------------------------
     345             : 
     346          10 :     (*CDLP_handle) = CDLP;
     347          10 :     CDLP = NULL;            // set to NULL so LG_FREE_ALL doesn't free it
     348          10 :     LG_FREE_ALL;
     349             : 
     350          10 :     return (GrB_SUCCESS);
     351             : }

Generated by: LCOV version 1.14