|
| 1 | +//------------------------------------------------------------------------------ |
| 2 | +// LAGraph_louvain.c: Louvain method |
| 3 | +//------------------------------------------------------------------------------ |
| 4 | + |
| 5 | +// LAGraph, (c) 2019-2026 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 Roi Lipman and Gabriel Gomez, FalkorDB |
| 15 | + |
| 16 | +//------------------------------------------------------------------------------ |
| 17 | + |
| 18 | +#include <LAGraph.h> |
| 19 | +#include <LAGraphX.h> |
| 20 | +#include <LG_internal.h> |
| 21 | + |
| 22 | +#undef LG_FREE_ALL |
| 23 | +#define LG_FREE_ALL \ |
| 24 | +{ \ |
| 25 | + GrB_free(&V); \ |
| 26 | + GrB_free(&cont); \ |
| 27 | +} |
| 28 | + |
| 29 | +// construct C, C[i:] = [i] |
| 30 | +// and N, N[i] = i's community id |
| 31 | +static GrB_Info _initCommunities |
| 32 | +( |
| 33 | + GrB_Matrix *C, // C [i] = i |
| 34 | + uint64_t **I, // C's indices array |
| 35 | + GrB_Index node_count // node count |
| 36 | +) |
| 37 | +{ |
| 38 | + //-------------------------------------------------------------------------- |
| 39 | + // initialize C |
| 40 | + //-------------------------------------------------------------------------- |
| 41 | + |
| 42 | + // create full vector V[i] = 1 |
| 43 | + char *msg = NULL ; |
| 44 | + GrB_Vector V = NULL ; |
| 45 | + GxB_Container cont = NULL ; |
| 46 | + |
| 47 | + GRB_TRY (GrB_Vector_new (&V, GrB_BOOL, node_count)) ; |
| 48 | + GRB_TRY (GrB_assign (V, NULL, NULL, true, GrB_ALL, node_count, NULL)) ; |
| 49 | + |
| 50 | + // create diagonal matrix C from V |
| 51 | + GRB_TRY (GrB_Matrix_diag (C, V, 0)) ; |
| 52 | + GRB_TRY (GrB_free (&V)) ; |
| 53 | + |
| 54 | + // C should be CSR with 64 bit indicies |
| 55 | + GRB_TRY (GrB_set (*C, 64, GxB_COLINDEX_INTEGER_HINT )) ; |
| 56 | + GRB_TRY (GrB_set (*C, GxB_SPARSE, GxB_SPARSITY_CONTROL )) ; |
| 57 | + GRB_TRY (GrB_set (*C, GrB_ROWMAJOR, GrB_STORAGE_ORIENTATION_HINT)) ; |
| 58 | + |
| 59 | + //-------------------------------------------------------------------------- |
| 60 | + // get a handle to C's indicies array |
| 61 | + //-------------------------------------------------------------------------- |
| 62 | + |
| 63 | + // unload C into a container |
| 64 | + GRB_TRY (GxB_Container_new (&cont)) ; |
| 65 | + |
| 66 | + // GrB_Matrix -> GxB_Container |
| 67 | + GRB_TRY (GxB_unload_Matrix_into_Container (*C, cont, NULL)) ; |
| 68 | + |
| 69 | + int handling ; |
| 70 | + GrB_Type type ; |
| 71 | + uint64_t n, X_memsize ; |
| 72 | + |
| 73 | + // unload I |
| 74 | + GRB_TRY (GxB_Vector_unload (cont->i, (void**)I, &type, &n, &X_memsize, |
| 75 | + &handling, NULL)) ; |
| 76 | + |
| 77 | + // load I, mark it as READONLY |
| 78 | + GRB_TRY (GxB_Vector_load (cont->i, (void **)I, type, n, X_memsize, |
| 79 | + GxB_IS_READONLY, NULL)) ; |
| 80 | + |
| 81 | + // GxB_Container -> GrB_Matrix |
| 82 | + GRB_TRY (GxB_load_Matrix_from_Container (*C, cont, NULL)) ; |
| 83 | + GRB_TRY (GrB_free (&cont)) ; |
| 84 | + |
| 85 | + LG_FREE_ALL ; |
| 86 | + |
| 87 | + return GrB_SUCCESS ; |
| 88 | +} |
| 89 | + |
| 90 | +#undef LG_FREE_WORK |
| 91 | +#define LG_FREE_WORK \ |
| 92 | +{ \ |
| 93 | + GrB_free(&C); \ |
| 94 | + GrB_free(&D); \ |
| 95 | + GrB_free(&it); \ |
| 96 | + GrB_free(&Ni); \ |
| 97 | + GrB_free(&desc); \ |
| 98 | + LAGraph_Free((void**)°ree, NULL); \ |
| 99 | + LAGraph_Free((void**)&community_degree, NULL); \ |
| 100 | +} |
| 101 | + |
| 102 | +#undef LG_FREE_ALL |
| 103 | +#define LG_FREE_ALL \ |
| 104 | +{ \ |
| 105 | + LG_FREE_WORK ; \ |
| 106 | +} |
| 107 | + |
| 108 | +// computes the modularity contribution of attaching a node of degree |
| 109 | +// i_degree, with kin edges into a community, to that community -- where |
| 110 | +// sigma_tot is the total degree of the community's members, *excluding* |
| 111 | +// the node itself if it happens to already be a member (the caller is |
| 112 | +// responsible for that adjustment; see base_gain) |
| 113 | +static inline double gain |
| 114 | +( |
| 115 | + uint64_t i_degree, // node i's degree |
| 116 | + uint64_t kjn, // number of edges connecting node i to community j |
| 117 | + uint64_t sigma_tot, // total degree of community j members |
| 118 | + double M2 // number of edges * 2 |
| 119 | +) |
| 120 | +{ |
| 121 | + return (2.0 * (double) kjn) / M2 - |
| 122 | + ((double) sigma_tot * (double) i_degree) / (M2 * M2) ; |
| 123 | +} |
| 124 | + |
| 125 | +// compute clustering by running the Louvain algorithm against the graph's |
| 126 | +// adjacency matrix A |
| 127 | +GrB_Info LAGraph_louvain |
| 128 | +( |
| 129 | + GrB_Vector *com, // output communities |
| 130 | + LAGraph_Graph G, // graph adjacency matrix |
| 131 | + int itermax, // max number of modularity improvements sweeps per level |
| 132 | + int levelmax, // max number of modularity improve and cluster condense |
| 133 | + float e, // min change in modularity considered an improvement |
| 134 | + char *msg // error message |
| 135 | +) |
| 136 | +{ |
| 137 | + //-------------------------------------------------------------------------- |
| 138 | + // check inputs |
| 139 | + //-------------------------------------------------------------------------- |
| 140 | + |
| 141 | + if (com == NULL || G == NULL || msg == NULL) { |
| 142 | + return (GrB_NULL_POINTER) ; |
| 143 | + } |
| 144 | + |
| 145 | + GrB_Matrix C = NULL ; // map between node to community id |
| 146 | + GrB_Vector D = NULL ; // map between nodes to community ids |
| 147 | + GxB_Iterator it = NULL ; |
| 148 | + GrB_Vector Ni = NULL ; // node i's neighbors |
| 149 | + GrB_Descriptor desc = NULL ; |
| 150 | + |
| 151 | + uint64_t *degree = NULL ; // node degree |
| 152 | + uint64_t *community_degree = NULL ; // sigma tot |
| 153 | + |
| 154 | + // find out if graph is symmetric, compute G->out_degree, and G->nself_edges |
| 155 | + LG_TRY (LAGraph_Cached_IsSymmetricStructure (G, msg)) ; |
| 156 | + LG_TRY (LAGraph_Cached_OutDegree (G, msg)) ; |
| 157 | + |
| 158 | + LG_TRY (LAGraph_Cached_NSelfEdges (G, msg)) ; |
| 159 | + LG_ASSERT_MSG (G->nself_edges == 0, GrB_INVALID_VALUE, |
| 160 | + "G->nself_edges must be zero") ; |
| 161 | + |
| 162 | + GrB_Matrix A = G->A ; |
| 163 | + |
| 164 | + // TODO: make sure `A` is row-wise |
| 165 | + |
| 166 | + GrB_Index nrows ; |
| 167 | + GrB_Index ncols ; |
| 168 | + GRB_TRY (GrB_Matrix_nrows (&nrows, A)) ; |
| 169 | + GRB_TRY (GrB_Matrix_ncols (&ncols, A)) ; |
| 170 | + |
| 171 | + // expecting a square matrix |
| 172 | + LG_TRY (LAGraph_CheckGraph (G, msg)) ; |
| 173 | + LG_ASSERT_MSG (nrows == ncols, LAGRAPH_INVALID_GRAPH, |
| 174 | + "adjacency matrix must be square") ; |
| 175 | + |
| 176 | + GrB_Type t ; |
| 177 | + GRB_TRY (GxB_Matrix_type (&t, A)) ; |
| 178 | + LG_ASSERT_MSG (t == GrB_BOOL, LAGRAPH_INVALID_GRAPH, "A must be boolean") ; |
| 179 | + |
| 180 | + GrB_Index nvals ; |
| 181 | + GRB_TRY (GrB_Matrix_nvals (&nvals, A)) ; |
| 182 | + double M2 = (double) nvals ; |
| 183 | + |
| 184 | + //-------------------------------------------------------------------------- |
| 185 | + // compute nodes degree |
| 186 | + //-------------------------------------------------------------------------- |
| 187 | + |
| 188 | + GRB_TRY (GrB_Vector_new (&D, GrB_UINT64, nrows)) ; |
| 189 | + GRB_TRY (GrB_assign (D, NULL, NULL, 0, GrB_ALL, nrows, NULL)) ; |
| 190 | + GRB_TRY (GrB_assign (D, NULL, GrB_PLUS_UINT64, G->out_degree, GrB_ALL, |
| 191 | + nrows, NULL)) ; |
| 192 | + |
| 193 | + // TODO: enable once we support weighted graphs |
| 194 | + //GRB_TRY (GrB_mxv (D, NULL, GrB_PLUS_UINT64, GxB_PLUS_PAIR_UINT64, A, D, |
| 195 | + //NULL)) ; |
| 196 | + |
| 197 | + // unpack D to an array for direct access |
| 198 | + uint64_t degree_size ; |
| 199 | + GRB_TRY (GxB_Vector_unpack_Full (D, (void**)°ree, °ree_size, NULL, |
| 200 | + NULL)) ; |
| 201 | + GRB_TRY (GrB_free (&D)) ; |
| 202 | + |
| 203 | + // community_degree [i] == degree [i] |
| 204 | + // as each node is in its own community |
| 205 | + LG_TRY (LAGraph_Malloc ((void **) &community_degree, 1, degree_size, msg)) ; |
| 206 | + memcpy (community_degree, degree, degree_size) ; |
| 207 | + |
| 208 | + // initialize C |
| 209 | + uint64_t *I = NULL ; |
| 210 | + GRB_TRY (_initCommunities (&C, &I, nrows)) ; |
| 211 | + LG_ASSERT (I != NULL, GrB_NULL_POINTER) ; |
| 212 | + |
| 213 | + GRB_TRY (GrB_Vector_new (&Ni, GrB_UINT64, ncols)) ; |
| 214 | + GRB_TRY (GxB_Iterator_new (&it)) ; |
| 215 | + |
| 216 | + GRB_TRY (GrB_Descriptor_new (&desc)) ; |
| 217 | + GRB_TRY (GrB_set (desc, GrB_STRUCTURE, GrB_MASK_FIELD)) ; |
| 218 | + GRB_TRY (GrB_set (desc, GxB_USE_INDICES, GxB_ROWINDEX_LIST)) ; |
| 219 | + |
| 220 | + bool improved ; |
| 221 | + double modularity_gain ; |
| 222 | + |
| 223 | + // start sweep |
| 224 | + do |
| 225 | + { |
| 226 | + modularity_gain = 0 ; |
| 227 | + |
| 228 | + // for each node |
| 229 | + for (GrB_Index i = 0 ; i < nrows ; i++) { |
| 230 | + // determine i's current community |
| 231 | + uint64_t ic = I [i] ; // i's community |
| 232 | + |
| 233 | + //------------------------------------------------------------------ |
| 234 | + // get a set of communities i can migrate to |
| 235 | + //------------------------------------------------------------------ |
| 236 | + |
| 237 | + // get i's neighbors; A[i:] |
| 238 | + GRB_TRY (GrB_Col_extract (Ni, NULL, NULL, A, GrB_ALL, ncols, |
| 239 | + i, GrB_DESC_T0)) ; |
| 240 | + |
| 241 | + // get neighbors communities |
| 242 | + // Ni * C = X[i]=j i community ID, j #neighbors in the ith community |
| 243 | + GRB_TRY (GrB_vxm (Ni, NULL, NULL, GxB_PLUS_PAIR_UINT64, Ni, C, NULL)) ; |
| 244 | + |
| 245 | + GRB_TRY (GxB_Vector_Iterator_attach (it, Ni, NULL)) ; |
| 246 | + GrB_Info info = GxB_Vector_Iterator_seek (it, 0) ; |
| 247 | + |
| 248 | + //------------------------------------------------------------------ |
| 249 | + // compute base score removing i from its current community |
| 250 | + //------------------------------------------------------------------ |
| 251 | + |
| 252 | + // i's degree |
| 253 | + uint64_t i_degree = degree [i] ; |
| 254 | + |
| 255 | + // number of edges connecting i to its current community |
| 256 | + uint64_t kin = 0 ; |
| 257 | + GRB_TRY (GrB_Vector_extractElement (&kin, Ni, ic)) ; |
| 258 | + |
| 259 | + // adjusted sigma tot |
| 260 | + uint64_t sigma_tot = community_degree [ic] ; |
| 261 | + //LG_ASSERT (sigma_tot >= i_degree) ; |
| 262 | + sigma_tot -= i_degree ; |
| 263 | + |
| 264 | + double base_gain = gain (i_degree, kin, sigma_tot, M2) ; |
| 265 | + double max_modularity = base_gain ; |
| 266 | + uint64_t best_community = ic ; |
| 267 | + |
| 268 | + while (info != GxB_EXHAUSTED) |
| 269 | + { |
| 270 | + // candidate community |
| 271 | + GrB_Index jc = GxB_Vector_Iterator_getIndex (it) ; |
| 272 | + |
| 273 | + // number of edges from node i to candidate community |
| 274 | + uint64_t kjn = GxB_Iterator_get_UINT64 (it) ; |
| 275 | + |
| 276 | + // move to the next entry in Ni |
| 277 | + info = GxB_Vector_Iterator_next (it) ; |
| 278 | + |
| 279 | + // skip i's community |
| 280 | + if (jc == ic) |
| 281 | + { |
| 282 | + continue ; |
| 283 | + } |
| 284 | + |
| 285 | + // total degree of nodes in community |
| 286 | + sigma_tot = community_degree [jc] ; |
| 287 | + |
| 288 | + // modularity gain of i joining the candidate community |
| 289 | + double g = gain (i_degree, kjn, sigma_tot, M2) ; |
| 290 | + |
| 291 | + if (g > max_modularity) |
| 292 | + { |
| 293 | + best_community = jc ; |
| 294 | + max_modularity = g ; |
| 295 | + } |
| 296 | + } |
| 297 | + |
| 298 | + if (best_community != ic) { |
| 299 | + // migrate i to its new community j |
| 300 | + I [i] = best_community ; |
| 301 | + |
| 302 | + //-------------------------------------------------------------- |
| 303 | + // update community degree |
| 304 | + // community_degree [ic] -= degree [i] |
| 305 | + // community_degree [best_community] += degree [i] |
| 306 | + //-------------------------------------------------------------- |
| 307 | + |
| 308 | + community_degree [ic] -= i_degree ; |
| 309 | + community_degree [best_community] += i_degree ; |
| 310 | + |
| 311 | + // accumulate modularity |
| 312 | + // TODO: at the end of the sweep compute modularity from A and C |
| 313 | + // compare that againt the initial modularity before the sweep |
| 314 | + modularity_gain += max_modularity - base_gain ; |
| 315 | + } |
| 316 | + } |
| 317 | + |
| 318 | + improved = (modularity_gain > e) ; |
| 319 | + } while (improved && --itermax > 0) ; |
| 320 | + |
| 321 | + |
| 322 | + for (uint l = 0 ; l < levelmax ; l++) |
| 323 | + { |
| 324 | + //---------------------------------------------------------------------- |
| 325 | + // pick a representative for each community |
| 326 | + //---------------------------------------------------------------------- |
| 327 | + |
| 328 | + GrB_Vector R ; // representatives R [i] = n; i community ID, n node ID |
| 329 | + GRB_TRY (GrB_Vector_new (&R, GrB_UINT64, nrows)) ; |
| 330 | + GRB_TRY (GrB_Matrix_reduce_Monoid (R, NULL, NULL, GrB_MAX_MONOID_UINT64, |
| 331 | + C, GrB_DESC_T0)) ; |
| 332 | + |
| 333 | + //---------------------------------------------------------------------- |
| 334 | + // map representative to its members |
| 335 | + //---------------------------------------------------------------------- |
| 336 | + |
| 337 | + // MAP [:i] nodes represented by i |
| 338 | + // MAP = MAP * (C * Rdiag) |
| 339 | + |
| 340 | + //---------------------------------------------------------------------- |
| 341 | + // compute A |
| 342 | + //---------------------------------------------------------------------- |
| 343 | + |
| 344 | + // A = CT * C; A[i,j] = x community i is connected to community j with x |
| 345 | + // different connections |
| 346 | + } |
| 347 | + |
| 348 | + //-------------------------------------------------------------------------- |
| 349 | + // set output |
| 350 | + //-------------------------------------------------------------------------- |
| 351 | + |
| 352 | + GRB_TRY (GrB_Vector_new (com, GrB_UINT64, nrows)) ; |
| 353 | + GRB_TRY (GxB_Vector_load (*com, (void **)(&I), GrB_UINT64, nrows, |
| 354 | + sizeof (uint64_t) * nrows, GrB_DEFAULT, NULL)) ; |
| 355 | + |
| 356 | + LG_FREE_WORK ; |
| 357 | + |
| 358 | + return GrB_SUCCESS ; |
| 359 | +} |
| 360 | + |
0 commit comments