Skip to content

Commit 7c141ef

Browse files
committed
[wip] louvain
1 parent 7543de1 commit 7c141ef

2 files changed

Lines changed: 530 additions & 0 deletions

File tree

Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
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**)&degree, 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**)&degree, &degree_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

Comments
 (0)