FormationControlSimulation/SOURCE/networks-toolbox/louvainCommunityFinding.m

131 lines
3.7 KiB
Matlab

% Implementation of a community finding algorithm by Blondel et al
% Source: "Fast unfolding of communities in large networks", July 2008
% https://sites.google.com/site/findcommunities/
% Note 1: This is just the first step of the Louvain community finding
% algorithm. To extract fewer communities, need to repeat with
% the resulting modules themselves.
% Note 2: Works for undirected graphs only.
% Note 3: Permuting randomly the node order at every step helps the
% algorithm performance. Unfortunately, node order in this
% algorithm affects the results.
%
% INPUTs: adjancency matrix, nxn
% OUTPUTs: modules, and node community labels (inmodule)
%
% Other routines used: numEdges.m, numNodes.m, kneighbors.m
% GB: last updated, Oct 17 2012
function [modules,inmodule] = louvainCommunityFinding(adj)
m = numEdges(adj);
n = numNodes(adj);
inmodule = {}; % inmodule{node} = module
for mm=1:length(adj); inmodule{mm} = mm; end;
modules = inmodule; % equal only for this step; modules{ind} = [nodes in module]
% for all nodes, visit and assess joining to their neighbors
% revisit until no improvement in dQ is available
converged = false;
while not(converged)
converged = true;
perm = randperm(n); % permute the order of the nodes randomly
% this helps the algorithm performance
for i=1:n
i = perm(i);
neigh = kneighbors(adj,i,1);
dQ = zeros(1,length(neigh));
c0 = inmodule{i};
for nei=1:length(neigh)
% attempt to join i and neigh(nei)
% that is: move i from modules{i} to modules{neigh(nei)}
% if dQs balance is positive, do move; else: move on
c1 = inmodule{neigh(nei)};
dQ(nei) = dQi(i,c0,c1,adj,modules,m);
end % loop across all neighbors
[maxdQ,maxnei] = max(dQ);
if maxdQ>0
modules{inmodule{i}} = setdiff(modules{inmodule{i}},i); % remove i from c0=inmodule{i}
inmodule{i}=inmodule{neigh(maxnei)}; % assign inmodule{i} to inmodule{neigh(maxnei)}
modules{inmodule{neigh(maxnei)}} = [modules{inmodule{neigh(maxnei)}} i]; % add i to modules{inmodule{neigh(maxnei)}}
converged = false;
end
% ...... print for debugging purposes ........................
%printf('current node %2i\n',i)
%printf('current dQ\n');
%dQ
%printf('based on dQ chose this neighbor: %2i\n',neigh(maxnei));
%printf('new modules\n');
%modules
%printf('new inmodule membership of i: %2i\n',inmodule{i});
% ............................................................
end % loop across all nodes
end % convergence loop
% remove empty modules
new_modules={};
for mm=1:length(modules)
if length(modules{mm})>0; new_modules{length(new_modules)+1}=modules{mm}; end
end
modules=new_modules;
printf('found %3i modules\n',length(modules));
function dqi = dQi(i,c0,c1,adj,modules,m)
% INPUTs:
% c0, c1: indices of the two modules
% i: node which changes membership
% modules: modules{c0} = [list of nodes] etc
% m: total number of nodes in graph
%
% OUTPUTs: dqi: modularity change if i moves from c0 to c1
if c0 == c1; dqi=0; return; end % same module - no change in modularity
% k_ic0: degree of i within c0
k_ic0 = sum(adj(i,modules{c0}));
% k_ic1: degree of i witnin c1
k_ic1 = sum(adj(i,modules{c1}));
% k_i: degree of i (in entire graph)
k_i = sum(adj(i,:));
% m_c0: number of edges in c0
m_c0 = numEdges(adj(modules{c0},modules{c0}));
% m_c1: number of edges in c1
m_c1 = numEdges(adj(modules{c1},modules{c1}));
dqi = (k_ic1-k_ic0)/(2*m) - k_i*(m_c1-m_c0)/(2*m^2);
function p = randperm(n)
[~,p] = sort(rand(n,1));