├── HelperFunctions ├── div_0.m ├── unique_fast.m ├── spblkdiag.m ├── global_cut.m ├── page_rank.m ├── LCC.m ├── laplace_eig.m ├── sweep_cut.m └── lobpcg.m ├── Multilayer ├── AggregateNetwork.m ├── state2nodelayer.m ├── nodelayer2state.m ├── supra_adjacency.m └── relax_rate_walk.m ├── ACLcut ├── APPR │ ├── APPR │ │ ├── mex.xcconfig │ │ ├── APPR.h │ │ └── APPR.cpp │ └── matlab_matrix │ │ ├── matlab_matrix.h │ │ ├── full.cpp │ │ └── sparse.cpp ├── compile_APPR_mex.m ├── APPR.m └── ACLcut.m ├── NormalizeAssociationMatrix.m ├── Contents.m ├── MOVcut ├── GPPR.m └── MOVcut.m ├── License.txt ├── InternalConductance.m ├── EGOcut └── EGOcut.m ├── Readme.md ├── MultilayerNCP.m ├── OptionStruct └── OptionStruct.m └── NCP.m /HelperFunctions/div_0.m: -------------------------------------------------------------------------------- 1 | function C=div_0(A,B) 2 | % DIV_0 pointwise division such that 0/0=0 3 | 4 | C=A; 5 | ind=find(A); 6 | C(ind)=A(ind)./B(ind); 7 | 8 | end 9 | -------------------------------------------------------------------------------- /HelperFunctions/unique_fast.m: -------------------------------------------------------------------------------- 1 | function u=unique_fast(a) 2 | % fast computation of unique elments of array without type-check overhead 3 | if ~isempty(a) 4 | a=sort(a(:)); 5 | d=diff(a)~=0; 6 | u=[a(d);a(end)]; 7 | else 8 | u=[]; 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /Multilayer/AggregateNetwork.m: -------------------------------------------------------------------------------- 1 | function [ AT ] = AggregateNetwork(A) 2 | % AggregateNetwork Aggregates multilayer network by summing over all layers 3 | 4 | % Version: 2.0.2 5 | % Date: Wed 20 Jun 2018 16:01:02 CEST 6 | % Author: Lucas Jeub 7 | % Email: lucasjeub@gmail.com 8 | 9 | T=length(A); 10 | AT=A{1}; 11 | if T>1 12 | for i=2:T 13 | AT=AT+A{i}; 14 | end 15 | end 16 | 17 | end 18 | 19 | -------------------------------------------------------------------------------- /ACLcut/APPR/APPR/mex.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // mex.xcconfig 3 | // 4 | // Xcode build settings for MEX functions 5 | // These settings are required to be compatible with MATLAB 6 | // 7 | 8 | // Installation directory of MATLAB 9 | MATLABROOT = $(LOCAL_APPS_DIR)/MATLAB_R2015b.app 10 | 11 | // Build Options 12 | //GCC_VERSION = com.apple.compilers.llvmgcc42 13 | 14 | // Deployment 15 | MACOSX_DEPLOYMENT_TARGET = 10.7 16 | -------------------------------------------------------------------------------- /HelperFunctions/spblkdiag.m: -------------------------------------------------------------------------------- 1 | function M=spblkdiag(varargin) 2 | % Block diagonal concatenation of matrix input arguments with sparse output. 3 | % 4 | % |A 0 .. 0| 5 | % Y = SPBLKDIAG(A,B,...) produces sparse( |0 B .. 0| ) 6 | % |0 0 .. | 7 | % 8 | % Class support for inputs: 9 | % float: double, single 10 | % integer: uint8, int8, uint16, int16, uint32, int32, uint64, int64 11 | % char, logical 12 | % 13 | % See also BLKDIAG, DIAG, HORZCAT, VERTCAT 14 | 15 | varargin=cellfun(@sparse,varargin,'UniformOutput',false); 16 | M=blkdiag(varargin{:}); 17 | 18 | 19 | end 20 | -------------------------------------------------------------------------------- /HelperFunctions/global_cut.m: -------------------------------------------------------------------------------- 1 | function [S,cond]=global_cut(A) 2 | % approximate the global minimal conductance cut 3 | % 4 | % Input: 5 | % A: adjacency matrix 6 | % 7 | % Outputs: 8 | % S: indicates bipartition found (entries are 1 or 2, 9 | % indicating which part of the partition the corresponding 10 | % node belongs to) 11 | % cond: the conductance value of the bipartition 12 | % 13 | % see also InternalConductance 14 | 15 | % Version: 2.0.2 16 | % Date: Wed 20 Jun 2018 16:01:02 CEST 17 | % Author: Lucas Jeub 18 | % Email: lucasjeub@gmail.com 19 | 20 | [~,v]=laplace_eig(A); 21 | 22 | [cond,ind]=sweep_cut(v,A,sum(A,2),inf); 23 | 24 | [cond,k]=min(cond); 25 | 26 | S=ones(length(A),1)*2; 27 | 28 | S(ind(1:k))=1; 29 | end 30 | -------------------------------------------------------------------------------- /NormalizeAssociationMatrix.m: -------------------------------------------------------------------------------- 1 | function assoc_mat=NormalizeAssociationMatrix(assoc_mat) 2 | % Normalize an association matrix returned by NCP 3 | % 4 | % Normalize an association matrix assoc_mat returned by NCP by dividing 5 | % each entry by the total number of times either node has appeared in a 6 | % community 7 | % 8 | % Input: 9 | % assoc_mat: association matrix (last output from NCP) 10 | % 11 | % Output: 12 | % assoc_mat: normalized version of the association matrix 13 | % 14 | % see also NCP 15 | 16 | % Version: 2.0.2 17 | % Date: Wed 20 Jun 2018 16:01:02 CEST 18 | % Author: Lucas Jeub 19 | % Email: lucasjeub@gmail.com 20 | 21 | n=length(assoc_mat); 22 | norm=repmat(diag(assoc_mat),1,n)+repmat(diag(assoc_mat)',n,1)-assoc_mat; 23 | 24 | assoc_mat=assoc_mat./norm; 25 | end 26 | -------------------------------------------------------------------------------- /HelperFunctions/page_rank.m: -------------------------------------------------------------------------------- 1 | function p=page_rank(P,alpha,s) 2 | % Compute page-rank vector 3 | % 4 | % Input: 5 | % P: transition matrix 6 | % 7 | % alpha: teleportation parameter (if alpha=0 computes stationary 8 | % distribution of P 9 | % 10 | % s: seed vector (defaults to uniform distribution) 11 | % 12 | % Output: 13 | % p: page-rank vector 14 | 15 | if nargin<3 16 | s=ones(length(P),1)/length(P); 17 | else 18 | s=s/sum(s); 19 | end 20 | 21 | if alpha==0 22 | [p,~]=eigs(P,1,'la'); 23 | else 24 | p=bicgstab((speye(length(P))+(alpha-1)*P),alpha*s,[],100); 25 | end 26 | 27 | % correct for potential numerical error (make sure p satisfies assumptions) 28 | if sum(p)<0 29 | p=-p; 30 | end 31 | p=max(0,p); 32 | p=p/sum(p); 33 | 34 | end 35 | -------------------------------------------------------------------------------- /Multilayer/state2nodelayer.m: -------------------------------------------------------------------------------- 1 | function nodelayer=state2nodelayer(N,state) 2 | % convert state indeces to node-layer indeces 3 | % 4 | % Input: 5 | % N: number of nodes in the network 6 | % 7 | % state: vector of state indeces or cell array of 8 | % vectors of state indeces. 9 | % 10 | % Output: 11 | % nodelayer: matrix of node-layer indeces 12 | 13 | % Version: 2.0.2 14 | % Date: Wed 20 Jun 2018 16:01:02 CEST 15 | % Author: Lucas Jeub 16 | % Email: lucasjeub@gmail.com 17 | 18 | if iscell(state) 19 | nodelayer=cell(size(state)); 20 | for st=1:numel(state) 21 | nodelayer{st}=state2nodelayer(N,state{st}); 22 | end 23 | else 24 | nodelayer=zeros(numel(state),2); 25 | nodelayer(:,1)=mod(state(:)-1,N)+1; 26 | nodelayer(:,2)=floor((state(:)-1)./N)+1; 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /ACLcut/APPR/APPR/APPR.h: -------------------------------------------------------------------------------- 1 | // Approximate Personalised PageRank computation using the algorithm from: 2 | // 3 | // Andersen, R., Chung, F. R. K., & Lang, K. J. (2006). 4 | // Local Graph Partitioning using PageRank Vectors (pp. 475–486). 5 | // in Proceedings of the 47th Annual Symposium on Foundations of Computer 6 | // Science, IEEE. http://doi.org/10.1109/FOCS.2006.44 7 | // 8 | // Version: 2.0.2 9 | // Date: Wed 20 Jun 2018 16:01:01 CEST 10 | // Author: Lucas Jeub 11 | // Email: lucasjeub@gmail.com 12 | 13 | #ifndef __APPR__main__ 14 | #define __APPR__main__ 15 | 16 | #include 17 | #include "matlab_matrix.h" 18 | #include "mex.h" 19 | #include "matrix.h" 20 | #include 21 | #include 22 | 23 | void push(full & p, full & r, const full & d, const sparse & W, std::queue & update, double alpha, double epsilon); 24 | 25 | #endif /* defined(__APPR__main__) */ 26 | -------------------------------------------------------------------------------- /ACLcut/compile_APPR_mex.m: -------------------------------------------------------------------------------- 1 | function compile_APPR_mex 2 | % Compile APPR mex function 3 | 4 | % different options for 32bit and 64bit Matlab 5 | ext=mexext; 6 | switch ext 7 | case {'mexw32','mexglx','mexmac','mexmaci'} %32bit 8 | arraydims='-compatibleArrayDims'; 9 | case {'mexw64','mexa64','mexmaci64'} %64bit 10 | arraydims='-largeArrayDims'; 11 | otherwise %potentially new architectures in the future 12 | warning('unknown mexext %s, assuming 64bit',ext) 13 | arraydims='-largeArrayDims'; 14 | end 15 | 16 | % set up input files 17 | location=fileparts(mfilename('fullpath')); 18 | includes=['-I',location,'/APPR/matlab_matrix']; 19 | compiles=strcat(location,'/APPR/',{'APPR/APPR','matlab_matrix/full','matlab_matrix/sparse'},'.cpp'); 20 | product=['/APPR.',ext]; 21 | 22 | % compile 23 | mex(includes,arraydims,compiles{:}); 24 | 25 | % move product to correct location 26 | if ~strcmp(pwd,location) 27 | movefile([pwd,product],[location,product]); 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /Multilayer/nodelayer2state.m: -------------------------------------------------------------------------------- 1 | function state=nodelayer2state(N,nodelayer) 2 | % convert node-layer indeces to state indeces 3 | % 4 | % Input: 5 | % N: number of physical nodes of the network 6 | % 7 | % nodelayer: nx2 matrix of node-layer indeces or cell array of 8 | % nx2 matrices of node-layer indeces. 9 | % 10 | % Output: 11 | % state: nx1 vector of state indeces 12 | % 13 | % Note: if input is a vector, the function returns the input unchanged. 14 | 15 | % Version: 2.0.2 16 | % Date: Wed 20 Jun 2018 16:01:02 CEST 17 | % Author: Lucas Jeub 18 | % Email: lucasjeub@gmail.com 19 | 20 | if iscell(nodelayer) 21 | state=cell(size(nodelayer)); 22 | for nl=1:numel(nodelayer) 23 | state{nl}=nodelayer2state(N,nodelayer{nl}); 24 | end 25 | else 26 | if size(nodelayer,2)==2 27 | state=(nodelayer(:,2)-1)*N+nodelayer(:,1); 28 | elseif isvector(nodelayer) 29 | state=nodelayer; 30 | else 31 | error('Format of nodelayer input is invalid'); 32 | end 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /HelperFunctions/LCC.m: -------------------------------------------------------------------------------- 1 | function [ A_c,ind] = LCC(A ) 2 | % Find the largest weakly connected component of a network 3 | % 4 | % Input: 5 | % A: adjacency matrix 6 | % 7 | % Outputs: 8 | % A_c: adjacency matrix of largest connected component 9 | % ind: node indeces of nodes in the largest connected component, i.e. 10 | % A(ind,ind)=A_c 11 | % 12 | % This function relies on the MatlabBgl library: 13 | % http://www.mathworks.co.uk/matlabcentral/fileexchange/10922-matlabbgl 14 | % 15 | % see also components 16 | 17 | % Version: 2.0.2 18 | % Date: Wed 20 Jun 2018 16:01:02 CEST 19 | % Author: Lucas Jeub 20 | % Email: lucasjeub@gmail.com 21 | 22 | if iscell(A) 23 | At=AggregateNetwork(A); 24 | [C,sizes]=components(max(At,At')); 25 | [~,k]=max(sizes); 26 | ind=find(C==k); 27 | A_c=cell(size(A)); 28 | for i=1:numel(A) 29 | A_c{i}=A{i}(ind,ind); 30 | end 31 | else 32 | 33 | [C,sizes]=components(max(A,A')); 34 | 35 | [~,k]=max(sizes); 36 | ind=find(C==k); 37 | 38 | A_c=A(ind,ind); 39 | end 40 | 41 | end 42 | 43 | -------------------------------------------------------------------------------- /ACLcut/APPR.m: -------------------------------------------------------------------------------- 1 | function [p,not_converged,r]=APPR(alpha,epsilon,s,A,d) 2 | % Compute a personalized PageRank vector 3 | % 4 | % Implements the ApproximatePR algorithm from: 5 | % Andersen, R., Chung, F. R. K., & Lang, K. J. (2006). 6 | % Local Graph Partitioning using PageRank Vectors (pp. 475-486). 7 | % FOCS'06 8 | % 9 | % Input: 10 | % 11 | % alpha: teleportation parameter between 0 and 1 12 | % epsilon: truncation parameter 13 | % s: seed node 14 | % A: adjacency matrix 15 | % d: vector of node strengths 16 | % 17 | % Output: 18 | % 19 | % p: PageRank vector as a row vector 20 | % not_converged: flag indicating that maxiter has been reached 21 | % r: residual vector 22 | % 23 | % see also ACLcut 24 | % 25 | % Implemented as a mex function 26 | 27 | % Version: 2.0.2 28 | % Date: Wed 20 Jun 2018 16:01:01 CEST 29 | % Author: Lucas Jeub 30 | % Email: lucasjeub@gmail.com 31 | 32 | % This function should never run if everything is installed correctly 33 | error(['APPR mex-function not found, make sure to compile the C++ code by '... 34 | 'running the ''compile_APPR_mex'' script']) 35 | end 36 | 37 | -------------------------------------------------------------------------------- /Contents.m: -------------------------------------------------------------------------------- 1 | % LOCALCOMMUNITIES 2 | % 3 | % This code implements the local community detection methods used in 4 | % 5 | % 1. Jeub, L. G. S., Balachandran, P., Porter, M. A., Mucha, P. J., & Mahoney, M. W. (2015). 6 | % Think locally, act locally: Detection of small, medium-sized, and large communities in large networks. 7 | % Physical Review E, 91(1), 012821. 8 | % http://doi.org/10.1103/PhysRevE.91.012821 9 | % 10 | % 2. Jeub, L. G. S., Mahoney, M. W., Mucha, P. J., & Porter, M. A. (2015). 11 | % A Local Perspective on Community Structure in Multilayer Networks. 12 | % Network Science 5(2), 144-163 13 | % https://doi.org/10.1017/nws.2016.22 14 | % 15 | % Main Interface 16 | % 17 | % NCP - Compute the NCP (Network Community Profile) for a network 18 | % MultilayerNCP - Convenience wrapper around NCP for multiplex networks 19 | % 20 | % Further Diagnostics 21 | % 22 | % InternalConductance - Compute internal conductance of communities 23 | % NormalizeAssociationMatrix - Normalize an association matrix returned by NCP 24 | % 25 | % See also ACLcut EGOcut MOVcut HelperFunctions Multilayer 26 | -------------------------------------------------------------------------------- /Multilayer/supra_adjacency.m: -------------------------------------------------------------------------------- 1 | function [AS,id,layer]=supra_adjacency(A,omega) 2 | % supra_adjacency Convert multilayer network to supra-adjacency matrix 3 | % 4 | % Input: 5 | % 6 | % A: cell array of adjacency matrices for each layer 7 | % 8 | % omega: weight of interlayer edges 9 | % 10 | % Output: 11 | % 12 | % AS: supra-adjacency matrix with interlayer edges of weight omega with 13 | % all-to-all (multiplex) coupling 14 | % 15 | % id: vector of node ids for state nodes that are present (i.e. have 16 | % connections) 17 | % 18 | % layer: vector of layer ids for state nodes that are present 19 | 20 | % Version: 2.0.2 21 | % Date: Wed 20 Jun 2018 16:01:02 CEST 22 | % Author: Lucas Jeub 23 | % Email: lucasjeub@gmail.com 24 | N=size(A{1},1); 25 | T=length(A); 26 | 27 | pos=0; 28 | for i=1:length(A) 29 | cid=find(sum(A{i},1)|sum(A{i},2)'); 30 | id(pos+(1:length(cid)))=cid; 31 | layer(pos+(1:length(cid)))=i; 32 | AS((i-1)*N+(1:length(A{i})),(i-1)*N+(1:length(A{i})))=A{i}; 33 | pos=pos+length(cid); 34 | end 35 | 36 | G=sparse(id+(layer-1)*N,id,true); 37 | for i=1:size(G,2) 38 | AS(G(:,i),G(:,i))=omega; 39 | end 40 | 41 | AS=AS-diag(diag(AS)); 42 | end 43 | -------------------------------------------------------------------------------- /MOVcut/GPPR.m: -------------------------------------------------------------------------------- 1 | function [p,flag]=GPPR(gamma,s,W,d) 2 | % Compute a Generalized Personalized Page Rank vector 3 | % 4 | % Inputs: 5 | % 6 | % gamma: a number between (-infty, lambda_2(G)) where lambda_2(G) is the 7 | % second smallest eigenvalue of the normalized Laplacian 8 | % s: a seed vector 9 | % W: Adjacency matrix for G 10 | % d: vector of node strengths 11 | % 12 | % Outputs: 13 | % 14 | % p: A generalized personal pagerank vector 15 | % flag: flag indicating convergence of bicgstab 16 | 17 | % Version: 2.0.2 18 | % Date: Wed 20 Jun 2018 16:01:02 CEST 19 | % Author: Lucas Jeub 20 | % Email: lucasjeub@gmail.com 21 | 22 | D=sparse(diag(d)); %Construct diagonal matrix of degree sequence 23 | Lcomb=D-W; %Constructs Combinatorial Laplacian 24 | 25 | try 26 | M=ichol(Lcomb-gamma*D); %incomplete Choleski factorization as preconditioner 27 | catch 28 | M=sparse(diag(diag(Lcomb-gamma*D))); 29 | disp('Incomplete Cholesky failed, using diagonal instead') 30 | end 31 | [p,flag,relres,iter]=bicgstab(Lcomb-gamma*D,D*s,10^-5,100,M,M'); 32 | 33 | %display shorter error messages 34 | pcgmessages={'maxiter reached','ill-conditioned','stagnated','too small or large'}; 35 | if flag>0 36 | disp(['pcgerror = ',pcgmessages{flag}]) 37 | disp(['gamma = ',num2str(gamma)]) 38 | disp(['relres = ',num2str(relres)]) 39 | disp(['iter = ',num2str(iter)]) 40 | end 41 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2016, Lucas Jeub 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /Multilayer/relax_rate_walk.m: -------------------------------------------------------------------------------- 1 | function [P,id,layer]=relax_rate_walk(A) 2 | % relax_rate_walk compute relax-rate walk matrices 3 | % 4 | % Input: 5 | % 6 | % A: cell array of adjacency matrices for each layer 7 | % 8 | % Output: 9 | % 10 | % P: function that returns relax-rate-walk matrix given relax rate as 11 | % input 12 | % 13 | % id: vector of node ids 14 | % 15 | % layer: vector of layer ids 16 | 17 | % Version: 2.0.2 18 | % Date: Wed 20 Jun 2018 16:01:02 CEST 19 | % Author: Lucas Jeub 20 | % Email: lucasjeub@gmail.com 21 | l_width=size(A{1},1); 22 | n_l=length(A); 23 | id=zeros(l_width*n_l,1); 24 | layer=zeros(l_width*n_l,1); 25 | pos=0; 26 | for i=1:n_l 27 | [ii,jj,vv]=find(A{i}); 28 | ASi(pos+(1:length(ii)))=ii+l_width*(i-1); 29 | ASj(pos+(1:length(jj)))=jj+l_width*(i-1); 30 | ARj(pos+(1:length(jj)))=jj; 31 | ASv(pos+(1:length(vv)))=vv; 32 | pos=pos+length(ii); 33 | id((1:l_width)+(i-1)*l_width)=1:l_width; 34 | layer((1:l_width)+(i-1)*l_width)=i; 35 | end 36 | 37 | AS=sparse(ASi,ASj,ASv,n_l*l_width,n_l*l_width); 38 | AR=repmat(sparse(ASi,ARj,ASv,n_l*l_width,l_width),1,n_l); 39 | 40 | ksout=sum(AS,1); 41 | krout=sum(AR,1); 42 | ksin=sum(AS,2); 43 | 44 | PS=div_0(AS,repmat(ksout,l_width*n_l,1)); 45 | %for i=find(ksout(:)'==0) 46 | % PS(i,i)=1; 47 | %end 48 | 49 | PR=div_0(AR,repmat(krout,l_width*n_l,1)); 50 | %for i=find(krout(:)'==0) 51 | % PR(i,i)=1; 52 | %end 53 | 54 | function P=walk_matrix(r) 55 | P=(1-r)*PS+r*PR; 56 | end 57 | 58 | P=@walk_matrix; 59 | end 60 | -------------------------------------------------------------------------------- /InternalConductance.m: -------------------------------------------------------------------------------- 1 | function [int_cond]=InternalConductance(W,S) 2 | % Compute internal conductance of communities 3 | % 4 | % Inputs: 5 | % W: adjacency matrix 6 | % S: cell array of communities. Each element of the cell array 7 | % should be a vector of node indeces, giving the nodes in the 8 | % community. This function also accepts a single community 9 | % given as a vector. 10 | % 11 | % Outputs: 12 | % int_cond: array of internal conductance values for the 13 | % communities. Internal conductance of empty communities is 14 | % is coded nan. 15 | % 16 | % see also NCP global_cut 17 | 18 | % Version: 2.0.2 19 | % Date: Wed 20 Jun 2018 16:01:02 CEST 20 | % Author: Lucas Jeub 21 | % Email: lucasjeub@gmail.com 22 | 23 | 24 | 25 | if ~iscell(S) 26 | S={S}; 27 | end 28 | 29 | if ~issparse(W) 30 | warning('Input adjacency matrix is full, converting to sparse') 31 | W=sparse(W); 32 | end 33 | 34 | int_cond=ones(size(S))*nan; %missing data will be coded nan; 35 | 36 | randorder=randperm(length(S(:))); %used for load balancing 37 | S=S(randorder); 38 | parfor i=1:length(S(:)) 39 | if ~isempty(S{i}) 40 | disp(['community size =',num2str(length(S{i}))]) 41 | if length(S{i})==1 42 | int_cond(i)=1; 43 | else 44 | A=W(S{i},S{i}); 45 | 46 | c=components(A) 47 | 48 | if max(c)>1 49 | disp(['cluster disconnected, LCC = ',num2str(max(c))]); 50 | int_cond(i)=0; 51 | else 52 | 53 | [~,v]=laplace_eig(A); 54 | 55 | cond=sweep_cut(v,A,sum(A,2),inf); 56 | 57 | int_cond(i)=min(cond); 58 | end 59 | end 60 | end 61 | end 62 | 63 | int_cond(randorder)=int_cond; 64 | end 65 | 66 | -------------------------------------------------------------------------------- /EGOcut/EGOcut.m: -------------------------------------------------------------------------------- 1 | function [support,conductance,flag,sweep_set]=EGOcut(W,d,seed,~,~,max_vol) 2 | % Compute sweep cuts based on ranking nodes by geodesic distance from a seed node. 3 | % 4 | % Inputs: 5 | % 6 | % W: adjacency matrix 7 | % d: vector of node strength 8 | % seed: seed node or seed set of nodes 9 | % 10 | % Outputs: 11 | % 12 | % support: node indeces in sweep cut ordering 13 | % conductance: vector of conductance values for each sweep set 14 | % flag: always false (no convergence issues to deal with) 15 | % sweep_set: identifies sets that correspond to sweep sets, i.e., 16 | % where all nodes with egorank equal to the minimum in the 17 | % community have been included. 18 | % 19 | % see also NCP sweep_cut 20 | 21 | % Version: 2.0.2 22 | % Date: Wed 20 Jun 2018 16:01:02 CEST 23 | % Author: Lucas Jeub 24 | % Email: lucasjeub@gmail.com 25 | 26 | % up to half the volume of the network by default 27 | if nargin<6 28 | max_vol=0.5*sum(d); 29 | end 30 | 31 | if iscell(seed) 32 | if length(seed)==1 33 | seed=seed{1}; 34 | else 35 | error('EGOcut only accepts a single seed set as input') 36 | end 37 | end 38 | 39 | % use inverse weight as distance 40 | D=W; 41 | D(W>0)=1./(W(W>0)); 42 | egorank=zeros(length(seed),length(W)); 43 | for i=1:length(seed) 44 | egorank(i,:)=shortest_paths(D,seed(i)); 45 | end 46 | egorank=min(egorank,[],1); 47 | egorank=1./(1+egorank); 48 | 49 | [conductance,support]=sweep_cut(egorank,W,d,max_vol); 50 | 51 | %sweep_set indicates actual communities, where all nodes 52 | %with egorank equal to the minimum in the community have been included. 53 | %(All communiites returned by this procedure are connected) 54 | sweep_set=logical(diff(sort(egorank,'Descend'))); 55 | sweep_set=sweep_set(1:length(support)); 56 | sweep_set=sweep_set(:)'; 57 | 58 | % no convergence issues to indicate 59 | flag = false; 60 | 61 | end 62 | 63 | 64 | -------------------------------------------------------------------------------- /MOVcut/MOVcut.m: -------------------------------------------------------------------------------- 1 | function [support, conductance, flag, connected]=MOVcut(W,d,seed,gamma,c) 2 | % Implements the MOVcut locally biased spectral optimisation 3 | % to find low conductance cuts around a seed vertex, see: 4 | % Mahoney, M. W., Orecchia, L., & Vishnoi, N. K. (2012). 5 | % A local spectral method for graphs: With applications to improving graph 6 | % partitions and exploring data graphs locally. JMLR, 13, 2339?2365. 7 | % 8 | % Input: 9 | % 10 | % W: adjacency matrix 11 | % d: vector of node strength 12 | % seed: nodes of interest 13 | % gamma: value of teleportation parameter 14 | % c: volume factor (if c is not specified, support is only over 15 | % positive values of the GPPR vector) 16 | % 17 | % Output: 18 | % 19 | % support: nodes in sweep cut ordering 20 | % conductance: vector of conductance values for each sweep set 21 | % flag: convergence flag 22 | % connected: identifies connected sweep sets 23 | % 24 | % see also NCP sweep_cut GPPR 25 | 26 | % Version: 2.0.2 27 | % Date: Wed 20 Jun 2018 16:01:02 CEST 28 | % Author: Lucas Jeub 29 | % Email: lucasjeub@gmail.com 30 | 31 | %unpack passed cell array 32 | if iscell(seed) 33 | if length(seed)==1 34 | seed=seed{1}; 35 | else 36 | error('MOVcut only accepts a single seed set as input') 37 | end 38 | end 39 | 40 | D=diag(d); 41 | 42 | s=sparse(seed,1,1/length(seed),size(W,2),1); %creates seed vector using seed 43 | s=s-(s'*d(:)/(d(:)'*d(:)))*d(:); %othogonalizes s relative to degree sequence 44 | s=s/sqrt((s'*D*s)); %normalizes s appropriately 45 | 46 | [p,flag]=GPPR(gamma,s,W,d); %Computes the GPPR vector 47 | 48 | if nargin>4 49 | 50 | p=p/sqrt(p'*D*p); 51 | kappa=(s'*D*p)^2; 52 | max_vol=c/kappa; 53 | 54 | [conductance,support,connected]=sweep_cut(p,W,d,max_vol); 55 | 56 | else 57 | [conductance,support,connected]=sweep_cut(p,W,d,0.5*sum(d)); 58 | end 59 | -------------------------------------------------------------------------------- /HelperFunctions/laplace_eig.m: -------------------------------------------------------------------------------- 1 | function [lambda_2,V]=laplace_eig(A,tol,maxiter) 2 | % Compute second-smallest eigenvalue and corresponding eigenvector of normalized Laplacian matrix 3 | % 4 | % Inputs: 5 | % A: adjacency matrix 6 | % tol: error tollerance for the computation 7 | % maxiter: maximum number of iterations for the computation 8 | % 9 | % Outputs: 10 | % lambda_2: second-smallest eigenvalue of the normalized 11 | % Laplacian matrix of A 12 | % V: corresponding eigenvector 13 | % 14 | % This function relies on the lobpcg method: 15 | % A. V. Knyazev, Toward the Optimal Preconditioned Eigensolver: 16 | % Locally Optimal Block Preconditioned Conjugate Gradient Method, 17 | % SIAM Journal on Scientific Computing 23 (2001), no. 2, pp. 517-541. 18 | % http://dx.doi.org/10.1137/S1064827500366124 19 | % 20 | % see also lobpcg 21 | 22 | % Version: 2.0.2 23 | % Date: Wed 20 Jun 2018 16:01:02 CEST 24 | % Author: Lucas Jeub 25 | % Email: lucasjeub@gmail.com 26 | 27 | if nargin<2 28 | tol=10^-6; 29 | end 30 | 31 | if nargin<3 32 | maxiter=1000; 33 | end 34 | 35 | if ~isequal(A,A') 36 | A=(A+A')/2; 37 | warning('symmetrised adjacency matrix') 38 | end 39 | 40 | 41 | D=diag(sum(A)); 42 | d=full(diag(D)).^(0.5); 43 | L=(D-A); 44 | D2=diag(diag(D).^-0.5); 45 | L=D2*L*D2; 46 | L=(L+L')/2; % eliminate roundoff error 47 | if length(L)<50 48 | L=full(L); 49 | [V,lambda_2]=eig(L); 50 | lambda_2=diag(lambda_2); 51 | [lambda_2,s]=sort(lambda_2); 52 | lambda_2=lambda_2(2); 53 | V=V(:,s(2)); 54 | else 55 | try 56 | R=ichol(L); 57 | RT=R'; 58 | precfun = @(x)RT\(R\x); 59 | catch er 60 | warning('Incomplete Cholesky factorisation failed') 61 | precfun = @(x) x; 62 | end 63 | 64 | in2=rand(length(A),1); 65 | [V,lambda_2,flag]=lobpcg(in2,L,[],precfun,tol,maxiter,0,d); 66 | if flag 67 | warning('not converged') 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /ACLcut/ACLcut.m: -------------------------------------------------------------------------------- 1 | function [support, conductance, flag, connected]=ACLcut(W,d,seed,alpha,epsilon,max_vol) 2 | % ACLcut local truncated PageRank method 3 | % 4 | % Implements the ACLcut truncated pagerank diffusion method to find 5 | % cuts with small conductance values around a seed vertex. 6 | % 7 | % Inputs: 8 | % 9 | % W: adjacency matrix 10 | % d: vector of node strength 11 | % seed: vector of seed node indeces 12 | % alpha: value of teleportation parameter 13 | % epsilon: truncation parameter for APPR vector 14 | % max_vol: maximum volume of communities returned 15 | % 16 | % Outputs: 17 | % 18 | % support: nodes in support of APPR vector in sweep cut ordering 19 | % conductance: vector of conductance values for each sweep set 20 | % flag: convergence flag (false if APPR vector calculation has not 21 | % converged after 10^6 iterations) 22 | % connected: identifies connected sweep sets 23 | % 24 | % Reference: 25 | % 26 | % Andersen, R., Chung, F. R. K., & Lang, K. J. (2006). 27 | % Local Graph Partitioning using PageRank Vectors . 28 | % Proceedings of the 47th Annual Symposium on Foundations of Computer Science, 29 | % IEEE (pp. 475?486). http://doi.org/10.1109/FOCS.2006.44 30 | % 31 | % see also NCP sweep_cut APPR 32 | 33 | % Version: 2.0.2 34 | % Date: Wed 20 Jun 2018 16:01:01 CEST 35 | % Author: Lucas Jeub 36 | % Email: lucasjeub@gmail.com 37 | 38 | 39 | % Up to half the volume of the network by default 40 | if nargin<6 41 | max_vol=0.5*sum(d); 42 | end 43 | 44 | %unpack passed cell array 45 | if iscell(seed) 46 | if length(seed)==1 47 | seed=seed{1}; 48 | else 49 | error('ACLcut only accepts a single seed set as input') 50 | end 51 | end 52 | 53 | 54 | %compute approximate pagerank vector for seed 55 | r=zeros(size(d)); 56 | r(seed)=1/length(seed); 57 | [p,flag]=APPR(alpha,epsilon,r,W,d); 58 | 59 | %Compute normalized sweep vector 60 | p(p>0)=p(p>0)./d(p>0); 61 | 62 | %compute conductance for sweep sets 63 | [conductance,support,connected]=sweep_cut(p,W,d,max_vol); 64 | 65 | end 66 | 67 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # LocalCommunities 2 | 3 | This code implements the local community detection methods used in 4 | 5 | 1. Jeub, L. G. S., Balachandran, P., Porter, M. A., Mucha, P. J., & Mahoney, M. W. (2015). 6 | Think locally, act locally: Detection of small, medium-sized, and large communities in large networks. 7 | Physical Review E, 91(1), 012821. 8 | http://doi.org/10.1103/PhysRevE.91.012821 9 | 10 | 2. Jeub, L. G. S., Mahoney, M. W., Mucha, P. J., & Porter, M. A. (2017). 11 | A Local Perspective on Community Structure in Multilayer Networks. 12 | Network Science 5(2), 144-163 13 | https://doi.org/10.1017/nws.2016.22 14 | 15 | ## Usage 16 | 17 | The main interface is the `NCP` function which computes local and global NCPs using the `ACLcut`, `MOVcut`, or `EGOcut` methods described in [1]. For multilayer networks `MultilayerNCP` provides a convenience wrapper around `NCP` that sets up `NCP` with appropriate options for the ‘classical’ or ‘relaxed’ random walk and allows for sampling using either ‘physical’ or ‘state’ nodes. 18 | 19 | To create a conductance ratio profile (CRP), first compute the network community profile using the `NCP` function, storing both the conductance values and communities. Then compute the internal conductance for these communities using the `InternalConductance` function. One then obtains the conductance ratio profile by dividing the NCP by the internal conductance, e.g. 20 | 21 | [conductance,S]=NCP(A,'ACL'); 22 | [internal_conductance]=InternalConductance(A,S); 23 | 24 | CRP=conductance./internal_conductance 25 | 26 | More detailed documentation of the different functions is available using `help LocalCommunities` or `doc LocalCommunities` in Matlab. 27 | 28 | ## Installation 29 | 30 | To use the code, make sure that the ‘LocalCommunities’ folder and its subfolders are on your MATLAB path. Next, compile the `APPR` mex function by running `compile_APPR_mex` in the ‘ACLcut’ folder. 31 | 32 | Some functions require the ‘matlab_bgl’ library which is available from http://www.mathworks.co.uk/matlabcentral/fileexchange/10922-matlabbgl 33 | 34 | ## License and Citation 35 | 36 | This code is licensed under a FreeBSD license (see License.txt), except 37 | for lobpcg.m by A.V. Knyazev, which is provided under the GNU LGPL ver 2.1. 38 | 39 | When using the code, please cite the relevant papers and include a pointer to the ‘LocalCommunities’ Github page https://github.com/LJeub/LocalCommunities . If you want to cite the code directly, include a citation similar to: 40 | 41 | * Jeub, L. G. S. (2014—2016). LocalCommunities. https://github.com/LJeub/LocalCommunities 42 | 43 | -------------------------------------------------------------------------------- /ACLcut/APPR/APPR/APPR.cpp: -------------------------------------------------------------------------------- 1 | // Approximate Personalised PageRank computation using the algorithm from: 2 | // 3 | // Andersen, R., Chung, F. R. K., & Lang, K. J. (2006). 4 | // Local Graph Partitioning using PageRank Vectors (pp. 475–486). 5 | // in Proceedings of the 47th Annual Symposium on Foundations of Computer 6 | // Science, IEEE. http://doi.org/10.1109/FOCS.2006.44 7 | // 8 | // Version: 2.0.2 9 | // Date: Wed 20 Jun 2018 16:01:01 CEST 10 | // Author: Lucas Jeub 11 | // Email: lucasjeub@gmail.com 12 | 13 | #include "APPR.h" 14 | 15 | #define MAXITER 1000000 16 | 17 | using namespace std; 18 | 19 | // Provides APPR function: 20 | // [p, flag, r] = APPR(alpha, epsilon, seed, W, d) 21 | void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]){ 22 | //input check 23 | if (nrhs!=5) { 24 | mexErrMsgIdAndTxt("APPR:input", "APPR needs 5 input arguments"); 25 | } 26 | 27 | //input: 28 | double alpha= * mxGetPr(prhs[0]); 29 | double epsilon= * mxGetPr(prhs[1]); 30 | full seed(prhs[2]); //make sure to copy as this will be modified 31 | full r=seed; 32 | 33 | sparse W(prhs[3]); 34 | full d(prhs[4]); 35 | 36 | 37 | //number of nodes 38 | mwIndex N=W.m; 39 | 40 | 41 | //initialise APPR vector 42 | full p(N,1); 43 | for (mwIndex i=0; i update; 50 | for (mwIndex i=0; iepsilon*d.get(i)) { 52 | update.push(i); 53 | } 54 | } 55 | 56 | 57 | //push until convergence 58 | mwIndex iter=0; 59 | while (!update.empty() && iter < MAXITER ) { 60 | push(p, r, d, W, update, alpha, epsilon); 61 | ++iter; 62 | } 63 | 64 | //output: 65 | p.export_matlab(plhs[0]); 66 | if (nlhs>1) { 67 | plhs[1]=mxCreateLogicalScalar(iter==MAXITER); 68 | } 69 | if (nlhs>2) { 70 | r.export_matlab(plhs[2]); 71 | } 72 | 73 | } 74 | 75 | 76 | //push algorithm 77 | void push(full & p, full & r, const full & d, const sparse & W, queue & update, double alpha, double epsilon) { 78 | mwIndex node=update.front(); 79 | update.pop(); 80 | if (r.get(node)>epsilon*d.get(node)) { 81 | double r_node=r.get(node); 82 | p.get(node)+= alpha*r_node; //push to p 83 | r.get(node)=(1-alpha)*r_node/2; 84 | if (r.get(node)>epsilon*d.get(node)) { 85 | update.push(node); 86 | } 87 | 88 | for (mwIndex i=W.col[node]; i=0 39 | supp=find(ps<=0,1,'first')-1; 40 | else 41 | supp=n-1; 42 | end 43 | 44 | 45 | if isempty(supp) 46 | supp=n-1; 47 | end 48 | 49 | 50 | if supp==0 51 | conductance=nan; 52 | support=[]; 53 | connected=[]; 54 | else 55 | 56 | %logical indeces of nodes not in community 57 | %indexcomp=true(n,1); 58 | 59 | E=0; 60 | i=1; 61 | Volsc=mm-d(support(1)); 62 | Vols=d(support(1)); 63 | conductance=ones(1,supp); 64 | connected=false(1,supp); 65 | 66 | if nargout>2 67 | components=zeros(supp,1); 68 | WS=W(support(1:supp),support(1:supp)); 69 | end 70 | 71 | num_comp=0; 72 | while i<=supp && Vols<=max_vol 73 | 74 | %indexcomp(support(i))=false; 75 | 76 | %compute change in conductance by adding node index(i) 77 | E=E-sum(sum(W(support(i),support(1:i-1))))+sum(sum(W(support(i+1:end),support(i)))); 78 | 79 | 80 | %store conductance values 81 | conductance(i)=E/min(Volsc,Vols); 82 | if conductance(i)<0 83 | warning('negative conductance value encountered (surface area: %f, volume: %f)',E,min(Volsc,Vols)); 84 | conductance(i)=nan; 85 | break 86 | end 87 | % cond(i)=mm*E(i)/(Volsc*Vols); 88 | 89 | % check if sweepset is connected 90 | if nargout>2 91 | merge=unique_fast([components(WS(1:i-1,i)~=0);components(WS(i,1:i-1)~=0)]); 92 | 93 | if isempty(merge) 94 | components(i)=num_comp+1; 95 | num_comp=num_comp+1; 96 | else 97 | %merge=unique(merge); 98 | components(i)=merge(1); 99 | if length(merge)>1 100 | no_merge=true(num_comp,1); 101 | no_merge(merge)=false; 102 | ind=1:i; 103 | C=sparse(1:i,components(ind),true); 104 | Cn=[C(:,no_merge),any(C(:,merge),2)]; 105 | num_comp=size(Cn,2); 106 | for it=1:num_comp 107 | components(ind(Cn(:,it)))=it; 108 | end 109 | end 110 | end 111 | connected(i)=num_comp==1; 112 | end 113 | 114 | %compute change in volumes 115 | %Volsc=Volsc-d(support(i+1)); 116 | Vols=Vols+d(support(i+1)); 117 | Volsc=mm-Vols; 118 | i=i+1; 119 | end 120 | support=support(1:i-1); 121 | conductance=conductance(1:i-1); 122 | connected=connected(1:i-1); 123 | end 124 | 125 | 126 | end 127 | 128 | 129 | -------------------------------------------------------------------------------- /ACLcut/APPR/matlab_matrix/matlab_matrix.h: -------------------------------------------------------------------------------- 1 | // 2 | // matlab_matrix.h 3 | // matlab_matrix 4 | // 5 | // Created by Lucas Jeub on 24/10/2012 6 | // 7 | // Implements thin wrapper classes for full and sparse matlab matrices 8 | // 9 | // 10 | // Last modified by Lucas Jeub on 25/07/2014 11 | 12 | 13 | 14 | 15 | 16 | 17 | #ifndef MATLAB_MATRIX_H 18 | #define MATLAB_MATRIX_H 19 | 20 | #define inf std::numeric_limits::infinity(); 21 | 22 | 23 | #include 24 | #include 25 | 26 | #include "mex.h" 27 | 28 | #ifndef OCTAVE 29 | #include "matrix.h" 30 | #endif 31 | 32 | struct full; 33 | 34 | struct sparse{ 35 | sparse(); 36 | sparse(mwSize m, mwSize n, mwSize nmax); 37 | sparse(const sparse &matrix); 38 | sparse(const mxArray *matrix); 39 | 40 | ~sparse(); 41 | 42 | sparse & operator = (const sparse & matrix); 43 | 44 | sparse & operator = (const full & matrix); 45 | 46 | sparse & operator = (const mxArray *matrix); 47 | 48 | 49 | /*operations*/ 50 | /*pointwise division*/ 51 | 52 | sparse operator / (const sparse & B); 53 | sparse operator / (const full & B); 54 | 55 | mwSize nzero() const { return col[n];} 56 | 57 | double get(mwIndex i, mwIndex j); 58 | 59 | void export_matlab(mxArray * & out); 60 | 61 | mwSize m; 62 | mwSize n; 63 | mwSize nmax; 64 | mwIndex *row; 65 | mwIndex *col; 66 | double *val; 67 | 68 | private: 69 | 70 | bool export_flag; 71 | }; 72 | 73 | 74 | struct full{ 75 | full(); 76 | full(mwSize m, mwSize n); 77 | full(const full &matrix); 78 | full(const mxArray * matrix); 79 | 80 | ~full(); 81 | 82 | void export_matlab(mxArray * & out); 83 | 84 | full & operator = (const full & matrix); 85 | 86 | full & operator = (const sparse & matrix); 87 | 88 | full & operator = (const mxArray * matrix); 89 | 90 | double & get(mwIndex i, mwIndex j); 91 | double get(mwIndex i,mwIndex j) const; 92 | double & get(mwIndex i); 93 | double & operator [] (mwIndex i); 94 | double get(mwIndex i) const; 95 | double operator [] (mwIndex i) const; 96 | 97 | 98 | full operator / (const sparse &B); 99 | full operator / (const full &B); 100 | 101 | mwSize m; 102 | mwSize n; 103 | 104 | double *val; 105 | 106 | private: 107 | 108 | bool export_flag; 109 | 110 | public: 111 | class rowiterator : public std::iterator { 112 | double * p; 113 | mwSize m; 114 | mwSize n; 115 | mwSignedIndex rowpos; 116 | public: 117 | rowiterator() : p(nullptr), m(0) {} 118 | rowiterator(double * init, mwSize _m, mwSize _n, mwSignedIndex _rowpos) : p(init), m(_m), n(_n) ,rowpos(_rowpos) {} 119 | rowiterator & operator=(const rowiterator & it) {p=it.p; m=it.m; n=it.n; rowpos=it.rowpos; return *this;} 120 | bool operator == (const rowiterator & it) {return rowpos==it.rowpos;} 121 | bool operator != (const rowiterator & it) {return rowpos!=it.rowpos;} 122 | bool operator < (const rowiterator & it) {return rowpos (const rowiterator & it) {return rowpos>it.rowpos;} 125 | bool operator >= (const rowiterator & it) {return rowpos>=it.rowpos;} 126 | double & operator*(); 127 | rowiterator & operator ++ (){rowpos++;return *this;} 128 | rowiterator operator ++ (int) {rowiterator tmp(*this); operator++(); return tmp;} 129 | rowiterator & operator -- () {rowpos--;return *this;} 130 | rowiterator operator -- (int) {rowiterator tmp(*this); operator--(); return tmp;} 131 | rowiterator & operator + (mwSignedIndex i) {rowpos+=i; return *this;} 132 | mwSignedIndex operator + (const rowiterator & it) {return it.rowpos+rowpos;} 133 | rowiterator & operator - (mwSignedIndex i) {rowpos-=i; return *this;} 134 | mwSignedIndex operator - (const rowiterator & it) {return it.rowpos-rowpos;} 135 | double & operator [] (mwSignedIndex i); 136 | double operator [] (mwSignedIndex i) const; 137 | }; 138 | 139 | rowiterator rowit(mwIndex i); 140 | rowiterator rowit(mwIndex i,mwIndex j); 141 | 142 | typedef double * coliterator; 143 | 144 | coliterator colit(mwIndex i) {return val+i;} 145 | coliterator colit(mwIndex i,mwIndex j) {return val+(i+j*m);} 146 | }; 147 | 148 | 149 | #endif 150 | -------------------------------------------------------------------------------- /MultilayerNCP.m: -------------------------------------------------------------------------------- 1 | function [conductance_con,communities_con,conductance_dis,communities_dis,assoc_mat]=MultilayerNCP(A,cut_function,varargin) 2 | % Convenience wrapper around NCP for multiplex networks 3 | % 4 | % 5 | % 6 | % Input: 7 | % A: cell array of adjacency matrices for each layer of the 8 | % network (all layers need to be the same size) 9 | % 10 | % cut_function: 'ACL','MOV','EGO' to select algorithm to identify 11 | % communities 12 | % 13 | % options: 14 | % walktype: 'classical' or 'relaxed' 15 | % [default: 'classical'] 16 | % 17 | % layercoupling: strength of interlayer edges for 18 | % 'classical' walk or relax rate for 'relaxed' walk 19 | % [default: 1] 20 | % 21 | % teleportation: teleportation rate (useful for 22 | % directed networks) for unrecorded link 23 | % teleportation 24 | % [default: 0] 25 | % 26 | % physicalnodes: when set to true, sample NCP 27 | % using physical nodes 28 | % [default: false] 29 | % 30 | % + all options for NCP 31 | % 32 | % Note about 'local' option for NCP: 33 | % If option 'physicalnodes' is set to false, 34 | % nodes can be specified either using the state-node 35 | % id (single number from 1:#nodes*#layers) or as a 36 | % pair of node id and layer id. If providing two state 37 | % indeces make sure they are provided as a column 38 | % vector (otherwise it's treated as a node-layer 39 | % pair). Node-layer pairs need to be provided as a 40 | % nx2 matrix, where each row is a node-layer pair. 41 | % 42 | % If option 'physicalnodes' is set to true, 'local' 43 | % should contain indeces of physical nodes. 44 | % 45 | % Output: 46 | % conductance_con: vector of minimum conductance values 47 | % considering only connected communities, e.g. 48 | % conductance_con(10) gives the minimum conductance found for 49 | % communities with 10 state nodes that are connected. 50 | % For cut_function='EGO', this only considers communities 51 | % that are actual sweep sets (i.e. nodes with the same 52 | % distance from the seed node are either all included or not 53 | % included) 54 | % 55 | % communities_con: returns communities that achieve the 56 | % minimum conductance values in conductance_con 57 | % 58 | % conductance_dis: vector of minimum conductance values, also 59 | % allowing disconnected communities, and in the case 'EGO', 60 | % also communities that are not sweep sets. 61 | % 62 | % communities_dis: communities corresponding to conductance_dis 63 | % 64 | % assoc_mat: association matrix, assoc_mat(i,j) = number of times 65 | % nodes i and j have appeared together in a sampled 66 | % community. (The association matrix counts only the best 67 | % community for a seed node and choice of parameter values) 68 | % 69 | % see also NCP nodelayer2state state2nodelayer 70 | 71 | % Version: 2.0.2 72 | % Date: Wed 20 Jun 2018 16:01:02 CEST 73 | % Author: Lucas Jeub 74 | % Email: lucasjeub@gmail.com 75 | 76 | options=OptionStruct('walktype','classical','layercoupling',1,'teleportation',0,'physicalnodes',false); 77 | NCPoptions=OptionStruct('nodes',length(A)*length(A{1}),'local',[],'alpha',[],'truncation',[],... 78 | 'viscount',10,'aggressive',true,'transitionmatrix',false,'stationarydistribution',[],'teleportation',[]); 79 | ncpopts=options.setvalid(varargin); 80 | NCPoptions.set(ncpopts); 81 | 82 | N=length(A{1}); 83 | % deal with physicalnodes option by setting the local option for NCP 84 | if options.physicalnodes 85 | p_nodes=cell(N,1); 86 | for i=1:N 87 | p_nodes{i}=[repmat(i,length(A),1),(1:length(A))']; 88 | end 89 | if NCPoptions.isset('local') 90 | if iscell(NCPoptions.local) 91 | local=cell(length(NCPoptions.local),1); 92 | for i=1:length(NCPoptions.local) 93 | local{i}=vertcat(p_nodes{NCPoptions.local{i}}); 94 | end 95 | NCPoptions.local=local; 96 | else 97 | NCPoptions.local=p_nodes(NCPoptions.local); 98 | end 99 | else 100 | NCPoptions.local=p_nodes; 101 | end 102 | end 103 | 104 | % convert 'local' option given as nodelayer indeces to state indeces 105 | if NCPoptions.isset('local') 106 | NCPoptions.local=nodelayer2state(N,NCPoptions.local); 107 | end 108 | 109 | % set up different walk types 110 | switch options.walktype 111 | case 'classical' 112 | A=supra_adjacency(A,options.layercoupling); 113 | kin=sum(A,2); 114 | kout=sum(A,1); 115 | [row,col,val]=find(A); 116 | A=sparse(row,col,val./kout(col)',size(A,1),size(A,2)); 117 | p=page_rank(A,options.teleportation,kin); 118 | if any(kin==0) 119 | if max(p(kin==0))<10^-10 120 | p(kin==0)=0; % ensure these get removed later 121 | else 122 | error('excessive numerical error in page_rank calculation') 123 | end 124 | end 125 | NCPoptions.stationarydistribution=p; 126 | NCPoptions.transitionmatrix=true; 127 | 128 | case 'relaxed' 129 | P=relax_rate_walk(A); 130 | A=spblkdiag(A{:}); 131 | kin=sum(A,2); 132 | A=P(options.layercoupling); 133 | p=page_rank(A,options.teleportation,kin); 134 | if any(kin==0) 135 | if max(p(kin==0))<10^-10 136 | p(kin==0)=0; % ensure these get removed later 137 | else 138 | error('excessive numerical error in page_rank calculation') 139 | end 140 | end 141 | NCPoptions.stationarydistribution=p; 142 | NCPoptions.transitionmatrix=true; 143 | end 144 | 145 | % Call NCP with appropriate number of outputs for efficiency 146 | switch nargout 147 | case {0,1} 148 | [conductance_con]=NCP(A,cut_function,NCPoptions); 149 | case 2 150 | [conductance_con,communities_con]=NCP(A,cut_function,NCPoptions); 151 | communities_con=state2nodelayer(N,communities_con); 152 | case 3 153 | [conductance_con,communities_con,conductance_dis]=NCP(A,cut_function,NCPoptions); 154 | communities_con=state2nodelayer(N,communities_con); 155 | case 4 156 | [conductance_con,communities_con,conductance_dis,communities_dis]=NCP(A,cut_function,NCPoptions); 157 | communities_con=state2nodelayer(N,communities_con); 158 | communities_dis=state2nodelayer(N,communities_dis); 159 | case 5 160 | [conductance_con,communities_con,conductance_dis,communities_dis,assoc_mat]=NCP(A,cut_function,NCPoptions); 161 | communities_con=state2nodelayer(N,communities_con); 162 | communities_dis=state2nodelayer(N,communities_dis); 163 | end 164 | 165 | end 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /ACLcut/APPR/matlab_matrix/full.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // full.cpp 3 | // full 4 | // 5 | // Created by Lucas Jeub on 24/10/2012 6 | // 7 | // 8 | // Implements thin wrapper class for full matlab matrices 9 | // 10 | // 11 | // Last modified by Lucas Jeub on 27/11/2012 12 | 13 | 14 | #include "matlab_matrix.h" 15 | 16 | //default constructor 17 | full::full(): n(0), m(0), val(NULL), export_flag(0) {} 18 | 19 | 20 | //copy constructor 21 | full::full(const full &matrix): m(matrix.m), n(matrix.n), export_flag(0) { 22 | 23 | //allocate memory 24 | val=(double *) mxCalloc(m*n, sizeof(double)); 25 | 26 | //copy values 27 | for(mwIndex i=0; iB.row[k])&&(k0 56 | %unpack nested cells (usually created by passing varargins) 57 | input=unpack_nested(varargin); 58 | if length(input)==1 59 | if isstruct(input) 60 | obj.options=fieldnames(input); 61 | obj.set(input); 62 | elseif isa(input,'OptionStruct') 63 | obj=copy(input); 64 | else 65 | obj.options=input; 66 | end 67 | else 68 | if iscellstr(input)||ischar(input) 69 | obj.options=input; 70 | else 71 | if ~mod(length(input),2) 72 | for i=1:2:length(input) 73 | if isvarname(lower(input{i})) 74 | obj.options=lower(input{i}); 75 | obj.option_struct.(lower(input{i}))=input{i+1}; 76 | else 77 | error('%s is not a valid option name',input{i}) 78 | end 79 | end 80 | else 81 | error('option list has odd length') 82 | end 83 | end 84 | end 85 | end 86 | end 87 | 88 | 89 | 90 | function obj=subsasgn(obj, S, value) 91 | %subscripted assignment 92 | if isequal(S(1).type,'.') 93 | if ismember(S(1).subs,properties(obj))||ismember(S(1).subs,methods(obj)) 94 | obj=builtin('subsasgn',obj,S,value); 95 | return; 96 | end 97 | 98 | S(1).subs=lower(S(1).subs); 99 | if obj.isfield(S(1).subs) 100 | obj.option_struct=builtin('subsasgn',obj.option_struct,S,value); 101 | else 102 | error('option %s does not exist',S.subs); 103 | end 104 | else 105 | error('subscripted assignment %s undefined',S.type); 106 | end 107 | end 108 | 109 | 110 | function varargout=subsref(obj,S) 111 | %subscripted reference 112 | if isequal(S(1).type,'.') 113 | try 114 | if nargout>0 115 | varargout{:}=builtin('subsref',obj,S); 116 | else 117 | builtin('subsref',obj,S); 118 | end 119 | return; 120 | catch err 121 | if strcmp(err.identifier,'MATLAB:noSuchMethodOrField') 122 | S(1).subs=lower(S(1).subs); 123 | if obj.isfield(S(1).subs) 124 | if nargout>0 125 | varargout{:}=builtin('subsref',obj.option_struct,S); 126 | else 127 | builtin('subsref',obj.option_struct,S); 128 | end 129 | else 130 | error('option %s does not exist',S.subs); 131 | end 132 | else 133 | rethrow(err); 134 | end 135 | end 136 | else 137 | error('subscripted reference %s is undefined',S(1).type); 138 | end 139 | end 140 | 141 | 142 | function disp(obj) 143 | %nice display of options 144 | disp('option struct with fields:') 145 | disp(obj.option_struct) 146 | end 147 | 148 | %set and get possible option names 149 | function set.options(obj,fieldnames) 150 | if ischar(fieldnames) 151 | fieldnames={fieldnames}; 152 | end 153 | if ~iscellstr(fieldnames) 154 | error('cannot parse fieldnames'); 155 | end 156 | fieldnames=lower(fieldnames); 157 | for i=1:length(fieldnames) 158 | obj.option_struct(1).(fieldnames{i})=[]; 159 | end 160 | end 161 | function opt=get.options(obj) 162 | opt=fieldnames(obj.option_struct); 163 | end 164 | 165 | 166 | function set(obj,varargin) 167 | % set options (takes a 'struct' or 'key','value' pairs, errors 168 | % for bad input) 169 | input=unpack_nested(varargin); 170 | if isa(input,'OptionStruct') 171 | input=input.struct; 172 | end 173 | if length(input)==1 174 | if isstruct(input) 175 | fields=fieldnames(input); 176 | for i=1:length(fields) 177 | if obj.isfield(fields{i}) 178 | obj.option_struct.(lower(fields{i}))=input.(fields{i}); 179 | else 180 | error('option ''%s'' does not exist',fields{i}); 181 | end 182 | end 183 | else 184 | error('need input struct'); 185 | end 186 | else 187 | obj.parse_option_list(input); 188 | end 189 | end 190 | 191 | function remaining_options=setvalid(obj,varargin) 192 | % set valid options from input and return the remaining options 193 | input=unpack_nested(varargin); 194 | 195 | if length(input)==1 196 | if isa(input,'OptionStruct') 197 | input=input.struct; 198 | return_type='ostruct'; 199 | fields=fieldnames(input); 200 | elseif isstruct(input) 201 | fields=fieldnames(input); 202 | return_type='struct'; 203 | else 204 | error('need struct or option struct as input'); 205 | end 206 | else 207 | if mod(length(input),2)==0 208 | fields=input(1:2:end); 209 | values=input(2:2:end); 210 | input=cell2struct(values(:),fields,1); 211 | return_type='list'; 212 | else 213 | error('option list has odd length'); 214 | end 215 | end 216 | for i=1:length(fields) 217 | if obj.isfield(lower(fields{i})) 218 | obj.option_struct.(lower(fields{i}))=input.(fields{i}); 219 | input=rmfield(input,fields{i}); 220 | end 221 | end 222 | switch return_type 223 | case 'ostruct' 224 | remaining_options=OptionStruct(input); 225 | 226 | case 'struct' 227 | remaining_options=input; 228 | 229 | case 'list' 230 | fields=fieldnames(input); 231 | values=struct2cell(input); 232 | remaining_options=[fields(:)';values(:)']; 233 | remaining_options=remaining_options(:)'; 234 | end 235 | end 236 | function is_opt=isfield(obj,fieldname) 237 | % check if fieldname is a valid option 238 | is_opt=isfield(obj.option_struct,lower(fieldname)); 239 | end 240 | 241 | function is_set=isset(obj,fieldname) 242 | % check if fieldname is set (i.e. not empty) 243 | fieldname=lower(fieldname); 244 | if ~iscell(fieldname) 245 | fieldname={fieldname}; 246 | end 247 | opt=obj.isfield(fieldname); 248 | if any(~opt) 249 | error('option ''%s'' does not exist \n',fieldname{~opt}); 250 | end 251 | 252 | is_set=false(size(fieldname)); 253 | for i=1:length(fieldname) 254 | is_set(i)=~isempty(obj.option_struct.(fieldname{i})); 255 | end 256 | end 257 | 258 | function options=struct(obj) 259 | % return options as struct 260 | options=obj.option_struct; 261 | end 262 | 263 | function options=list(obj) 264 | % return options as list of key-value pairs 265 | opts=obj.options; 266 | vals=struct2cell(obj.option_struct); 267 | options=[opts(:)';vals(:)']; 268 | options=options(:)'; 269 | end 270 | 271 | end 272 | 273 | methods (Access=private) 274 | function parse_option_list(obj,list) 275 | if iscell(list) 276 | if ~mod(length(list),2) 277 | for i=1:2:length(list) 278 | if obj.isfield(list{i}) 279 | obj.option_struct.(lower(list{i}))=list{i+1}; 280 | else 281 | optstr=sprintf('%s \n',obj.options{:}); 282 | error('option ''%s'' does not exist, valid options are %s',list{i},optstr); 283 | end 284 | end 285 | else 286 | error('option list has odd length') 287 | end 288 | else 289 | error('option list has to be a cell array') 290 | end 291 | end 292 | 293 | 294 | end 295 | 296 | end 297 | 298 | function input=unpack_nested(input) 299 | while iscell(input)&&length(input)==1 300 | input=input{1}; 301 | end 302 | end 303 | 304 | -------------------------------------------------------------------------------- /NCP.m: -------------------------------------------------------------------------------- 1 | function [conductance_con,communities_con,conductance_dis,communities_dis,assoc_mat]=NCP(W,cut_function,varargin) 2 | % Compute the NCP (Network Community Profile) for a network 3 | % 4 | % This function (with the appropriate options listed below) can be used 5 | % to compute approximate local and global NCPs for a network using any 6 | % of the three methods in: 7 | % 8 | % Jeub, L. G. S., Balachandran, P., Porter, M. A., Mucha, P. J., 9 | % & Mahoney, M. W. (2014). 10 | % Think Locally, Act Locally: The Detection of Small, Medium-Sized, and 11 | % Large Communities in Large Networks. arXiv:1403.3795 [cs.SI] 12 | % 13 | % Inputs: 14 | % W: adjacency matrix 15 | % 16 | % cut_function: choose method to find local communities 17 | % (either 'ACL', 'MOV', or 'EGO'). 18 | % 19 | % Additional options can be given using 'key',value pairs listed 20 | % below (with default values given in []). 21 | % 22 | % Outputs: 23 | % conductance_con: vector of minimum conductance values 24 | % considering only connected communities, e.g. 25 | % conductance_con(10) gives the minimum conductance found for 26 | % communities with 10 nodes that are connected. 27 | % For cut_function='EGO', this only considers communities 28 | % that are actual sweep sets (i.e. nodes with the same 29 | % distance from the seed node are either all included or not 30 | % included) 31 | % 32 | % communities_con: returns communities that achieve the 33 | % minimum conductance values in conductance_con 34 | % 35 | % conductance_dis: vector of minimum conductance values, also 36 | % allowing disconnected communities, and in the case 'EGO', 37 | % also communities that are not sweep sets. 38 | % 39 | % communities_dis: communities corresponding to conductance_dis 40 | % 41 | % assoc_mat: association matrix, assoc_mat(i,j) = number of times 42 | % nodes i and j have appeared together in a sampled 43 | % community. (The association matrix counts only the best 44 | % community for a seed node and choice of parameter values) 45 | % 46 | % 47 | % Options: 48 | % nodes [all nodes]: number of nodes to sample for each pair of 49 | % parameter values (nodes are sampled uniformly at random). 50 | % 51 | % local [[]]: compute a local NCP by specifying a vector of node 52 | % indeces (i.e. compute a NCP where only the specified nodes 53 | % are used as seed nodes). If the number of nodes to sample 54 | % given by the nodes option is less than the number of nodes 55 | % specified by local, nodes are sampled uniformly at random 56 | % from those specified in local. 57 | % 58 | % alpha [network and method dependend]: teleportation parameter, 59 | % for 'ACL' this corresponds to the teleportation parameter 60 | % in the lazy random walk, and for 'MOV' this corresponds to 61 | % 'gamma'. This option has no effect for 'EGO'. 62 | % 63 | % 64 | % truncation [network and method dependend]: for 'ACL' this is 65 | % the 'epsilon' parameter which controls the truncation in 66 | % the approximation, whereas for 'MOV' this is the 'c' 67 | % parameter, which limits the volume of communities. This 68 | % option has no effect for 'EGO'. 69 | % 70 | % viscount [10]: minimum number of times each node needs to be in 71 | % the best community before the sampling is stopped. 72 | % 73 | % aggressive: [true]: if set to `true`, a node that has 74 | % been in the best community at least `viscount` times is 75 | % never used as a seed node, if set to 'false', only stop 76 | % iterating early when all nodes have been visited at least 77 | % 'viscount' times. 78 | % 79 | % teleportation: [0.1]: for directed networks use unrecorded link 80 | % teleportations with teleportation parameter `teleportation` 81 | % to estimate stationary distribution (No effect when 82 | % `stationarydistribution` is given) 83 | % 84 | % stationarydistribution: []: specify to use stationary 85 | % distribution to estimate adjacency matrix based on 86 | % unrecorded teleportations 87 | % 88 | % transitionmatrix: [false]: if set to true, `A` is treated as a 89 | % random walk transition matrix instead of an adjacency 90 | % matrix 91 | % 92 | % For example, to compute an NCP for a network with adjacency matrix W 93 | % using the ACLcut method with alpha=0.01, one would call the function as 94 | % conductance=NCP(W,'ACL','alpha',0.01); 95 | % 96 | % One can then plot the NCP using 97 | % loglog(conductance) 98 | % 99 | % see also ACLcut EGOcut MOVcut NormalizeAssociationMatrix 100 | 101 | % Version: 2.0.2 102 | % Date: Wed 20 Jun 2018 16:01:02 CEST 103 | % Author: Lucas Jeub 104 | % Email: lucasjeub@gmail.com 105 | 106 | % Parse Options 107 | options=OptionStruct('nodes',length(W),'local',[],'alpha',[],... 108 | 'truncation',[],'viscount',10,'aggressive',true,... 109 | 'transitionmatrix',false,'stationarydistribution',[],... 110 | 'teleportation',0.1); %set defaults 111 | options.set(varargin); %set given options 112 | 113 | 114 | W=sparse(W); 115 | N=options.nodes; 116 | aggressive=options.aggressive; 117 | 118 | if nargout>4 119 | assoc_mat=zeros(length(W)); 120 | end 121 | 122 | if ~options.isset('stationarydistribution') 123 | % check W is connected 124 | [WC,original_node_index]=LCC(W); 125 | 126 | if length(WC)~=length(W) 127 | warning('considering only largest connnected component'); 128 | W=WC; 129 | N=min(N,length(WC)); 130 | end 131 | clear('WC'); 132 | else 133 | % remove nodes with 0 mass in stationary distribution 134 | original_node_index=find(options.stationarydistribution); 135 | if length(original_node_index)~=length(W) 136 | warning('considering only nodes with non-zero mass in stationary distribution'); 137 | W=W(original_node_index,original_node_index); 138 | N=min(N,length(original_node_index)); 139 | options.stationarydistribution=options.stationarydistribution(original_node_index); 140 | end 141 | end 142 | 143 | if ~options.transitionmatrix 144 | if sum(diag(W)) 145 | warning('removed self-edges') 146 | W=W-diag(diag(W)); 147 | end 148 | k=sum(W,1); 149 | vol=sum(k); 150 | [row,col,val]=find(W); 151 | P=sparse(row,col,val./k(col)'); 152 | else 153 | P=W; 154 | vol=length(P); 155 | end 156 | 157 | if ~options.isset('stationarydistribution') 158 | if ~isequal(W,W') 159 | d=page_rank(P,options.teleportation,sum(W,2)); 160 | [row,col,val]=find(P); 161 | d=d.*vol; 162 | W=sparse(row,col,val.*d(col),size(P,1),size(P,2)); 163 | else 164 | d=sum(W,2); 165 | end 166 | else 167 | d=options.stationarydistribution(:); 168 | [row,col,val]=find(P); 169 | d=d.*vol; 170 | W=sparse(row,col,val.*d(col),size(P,1),size(P,2)); 171 | end 172 | 173 | 174 | if options.isset('local') 175 | local=options.local; 176 | %reindex to LCC 177 | if iscell(local) 178 | for i=1:length(local) 179 | local{i}=find(ismember(original_node_index,local{i})); 180 | end 181 | local=local(~cellfun(@isempty,local)); 182 | else 183 | local=find(ismember(original_node_index,local)); 184 | end 185 | if length(local)>N 186 | node_sampler=@() local(randsample(1:length(local),N,false)); 187 | else 188 | node_sampler=@() local(randperm(length(local))); 189 | end 190 | else 191 | node_sampler=@() randsample(1:length(W),N,false); 192 | end 193 | 194 | min_viscount=options.viscount; 195 | 196 | % set defaults for truncation and alpha based on cut_function 197 | switch cut_function 198 | 199 | case 'MOV' 200 | f_handle=@MOVcut; 201 | 202 | if isempty(options.alpha) 203 | e=laplace_eig(W); 204 | a=linspace(0.7,1/(1-e),20); 205 | alpha=(a-1)./a-10^-10; 206 | else 207 | alpha=options.alpha; 208 | end 209 | if isempty(options.truncation) 210 | truncation=inf; 211 | else 212 | truncation=options.truncation; 213 | end 214 | 215 | case 'ACL' 216 | f_handle=@ACLcut; 217 | 218 | if isempty(options.alpha) 219 | alpha=.001; 220 | else 221 | alpha=options.alpha; 222 | end 223 | if isempty(options.truncation) 224 | nmax=full(max(sum(W))); 225 | m=full(sum(sum(W))); 226 | truncation=logspace(-log10(nmax),-log10(m),20); 227 | else 228 | truncation=options.truncation; 229 | end 230 | 231 | case 'EGO' 232 | f_handle=@EGOcut; 233 | alpha=1; 234 | truncation=1; 235 | 236 | otherwise 237 | error('NCP:cut_function','unknown cut function'); 238 | end 239 | 240 | %preallocate 241 | conductance_con=ones(length(W)-1,1)*inf; 242 | if nargout>1 243 | communities_con=cell(length(W)-1,1); 244 | end 245 | if nargout>2 246 | conductance_dis=ones(length(W)-1,1)*inf; 247 | end 248 | if nargout>3 249 | communities_dis=cell(length(W)-1,1); 250 | end 251 | 252 | 253 | for j=1:length(alpha) 254 | for k=1:length(truncation) 255 | visitcount=zeros(length(W),1); 256 | i=1; 257 | disp(['alpha =',num2str(alpha(j)),', trunc = ',num2str(truncation(k))]); 258 | %random reordering of nodes to avoid always sampling same subset 259 | %due to visitcount reached 260 | nodes=node_sampler(); 261 | while min(visitcount)1 280 | for l=update(:)' 281 | communities_con{l}=original_node_index(supp(1:l)); 282 | end 283 | end 284 | 285 | if nargout>2 286 | update_dis=find(cond(:)3 289 | for l=update(:)' 290 | communities_dis{l}=original_node_index(supp(1:l)); 291 | end 292 | end 293 | end 294 | 295 | if nargout>4 296 | if ~isempty(supp) 297 | assoc_mat(original_node_index(supp(1:mk)),original_node_index(supp(1:mk)))=... 298 | assoc_mat(original_node_index(supp(1:mk)),original_node_index(supp(1:mk)))+1; 299 | end 300 | end 301 | 302 | if min(visitcount)>=min_viscount 303 | disp(['min_viscount reached with ',num2str(min(visitcount)),' visits after ',num2str(i),' iterations']); 304 | end 305 | 306 | if aggressive 307 | if iscell(nodes) 308 | remove=find(cellfun(@(nn) all(visitcount(nn)>=min_viscount),nodes(i+1:end))); 309 | else 310 | remove=find(visitcount(nodes(i+1:end))>=min_viscount); 311 | end 312 | nodes((i)+remove)=[]; 313 | end 314 | 315 | i=i+1; 316 | end 317 | fprintf('sampled %u nodes\n',i-1); 318 | end 319 | end 320 | 321 | end 322 | -------------------------------------------------------------------------------- /HelperFunctions/lobpcg.m: -------------------------------------------------------------------------------- 1 | function [blockVectorX,lambda,varargout] = ... 2 | lobpcg(blockVectorX,operatorA,varargin) 3 | %LOBPCG solves Hermitian partial eigenproblems using preconditioning 4 | % 5 | % [blockVectorX,lambda]=lobpcg(blockVectorX,operatorA) 6 | % 7 | % outputs the array of algebraic smallest eigenvalues lambda and 8 | % corresponding matrix of orthonormalized eigenvectors blockVectorX of the 9 | % Hermitian (full or sparse) operator operatorA using input matrix 10 | % blockVectorX as an initial guess, without preconditioning, somewhat 11 | % similar to 12 | % 13 | % opts.issym=1;opts.isreal=1;K=size(blockVectorX,2); 14 | % [blockVectorX,lambda]=eigs(operatorA,K,'SR',opts); 15 | % 16 | % for real symmetric operator operatorA, or 17 | % 18 | % K=size(blockVectorX,2);[blockVectorX,lambda]=eigs(operatorA,K,'SR'); 19 | % for Hermitian operator operatorA. 20 | % 21 | % [blockVectorX,lambda,failureFlag]=lobpcg(blockVectorX,operatorA) 22 | % also returns a convergence flag. 23 | % If failureFlag is 0 then all the eigenvalues converged; otherwise not all 24 | % converged. 25 | % 26 | % [blockVectorX,lambda,failureFlag,lambdaHistory,residualNormsHistory]=... 27 | % lobpcg(blockVectorX,'operatorA','operatorB','operatorT',blockVectorY,... 28 | % residualTolerance,maxIterations,verbosityLevel); 29 | % 30 | % computes smallest eigenvalues lambda and corresponding eigenvectors 31 | % blockVectorX of the generalized eigenproblem Ax=lambda Bx, where 32 | % Hermitian operators operatorA and operatorB are given as functions, as 33 | % well as a preconditioner, operatorT. The operators operatorB and 34 | % operatorT must be in addition POSITIVE DEFINITE. To compute the largest 35 | % eigenpairs of operatorA, simply apply the code to operatorA multiplied by 36 | % -1. The code does not involve ANY matrix factorizations of operratorA and 37 | % operatorB, thus, e.g., it preserves the sparsity and the structure of 38 | % operatorA and operatorB. 39 | % 40 | % residualTolerance and maxIterations control tolerance and max number of 41 | % steps, and verbosityLevel = 0, 1, or 2 controls the amount of printed 42 | % info. lambdaHistory is a matrix with all iterative lambdas, and 43 | % residualNormsHistory are matrices of the history of 2-norms of residuals 44 | % 45 | % Required input: 46 | % * blockVectorX (class numeric) - initial approximation to eigenvectors, 47 | % full or sparse matrix n-by-blockSize. blockVectorX must be full rank. 48 | % * operatorA (class numeric, char, or function_handle) - the main operator 49 | % of the eigenproblem, can be a matrix, a function name, or handle 50 | % 51 | % Optional function input: 52 | % * operatorB (class numeric, char, or function_handle) - the second 53 | % operator, if solving a generalized eigenproblem, can be a matrix, 54 | % a function name, or handle; by default if empty, operatorB=I. 55 | % * operatorT (class char or function_handle) - the preconditioner, 56 | % by default operatorT(blockVectorX)=blockVectorX. 57 | % 58 | % Optional constraints input: 59 | % blockVectorY (class numeric) - a full or sparse n-by-sizeY matrix of 60 | % constraints, where sizeY < n. blockVectorY must be full rank. 61 | % The iterations will be performed in the (operatorB-) 62 | % orthogonal complement of the column-space of blockVectorY. 63 | % 64 | % Optional scalar input parameters: 65 | % residualTolerance (class numeric) - tolerance, by default, 66 | % residualTolerance=n*sqrt(eps) maxIterations - max number of iterations, 67 | % by default, maxIterations = min(n,20) verbosityLevel - either 0 (no 68 | % info), 1, or 2 (with pictures); by default, verbosityLevel = 0. 69 | % 70 | % Required output: blockVectorX and lambda (both class numeric) are 71 | % computed blockSize eigenpairs, where blockSize=size(blockVectorX,2) 72 | % for the initial guess blockVectorX if it is full rank. 73 | % 74 | % Optional output: failureFlag (class integer), lambdaHistory (class numeric) 75 | % and residualNormsHistory (class numeric) are described above. 76 | % 77 | % Functions operatorA(blockVectorX), operatorB(blockVectorX) and 78 | % operatorT(blockVectorX) must support blockVectorX being a matrix, not 79 | % just a column vector. 80 | % 81 | % Every iteration involves one application of operatorA and operatorB, and 82 | % one of operatorT. 83 | % 84 | % Main memory requirements: 6 (9 if isempty(operatorB)=0) matrices of the 85 | % same size as blockVectorX, 2 matrices of the same size as blockVectorY 86 | % (if present), and two square matrices of the size 3*blockSize. 87 | % 88 | % In all examples below, we use the Laplacian operator in a 20x20 square 89 | % with the mesh size 1 which can be generated in MATLAB by running 90 | % A = delsq(numgrid('S',21)); n=size(A,1); 91 | % or in MATLAB and Octave by 92 | % [~,~,A] = laplacian([19,19]); n=size(A,1); 93 | % see http://www.mathworks.com/matlabcentral/fileexchange/27279 94 | % 95 | % The following Example: 96 | % 97 | % [blockVectorX,lambda,failureFlag]=lobpcg(randn(n,8),A,1e-5,50,2); 98 | % 99 | % attempts to compute 8 first eigenpairs without preconditioning, 100 | % but not all eigenpairs converge after 50 steps, so failureFlag=1. 101 | % 102 | % The next Example: 103 | % 104 | % blockVectorY=[];lambda_all=[]; 105 | % for j=1:4 106 | % [blockVectorX,lambda]=... 107 | % lobpcg(randn(n,2),A,blockVectorY,1e-5,200,2); 108 | % blockVectorY=[blockVectorY,blockVectorX]; 109 | % lambda_all=[lambda_all' lambda']'; pause; 110 | % end 111 | % 112 | % attemps to compute the same 8 eigenpairs by calling the code 4 times 113 | % with blockSize=2 using orthogonalization to the previously founded 114 | % eigenvectors. 115 | % 116 | % The following Example: 117 | % 118 | % R=ichol(A,struct('michol','on')); precfun = @(x)R\(R'\x); 119 | % [blockVectorX,lambda,failureFlag]=lobpcg(randn(n,8),A,[],@(x)precfun(x),1e-5,60,2); 120 | % 121 | % computes the same eigenpairs in less then 25 steps, so that failureFlag=0 122 | % using the preconditioner function "precfun", defined inline. If "precfun" 123 | % is defined as a MATLAB function in a file, the function handle 124 | % @(x)precfun(x) can be equivalently replaced by the function name 'precfun' 125 | % Running 126 | % 127 | % [blockVectorX,lambda,failureFlag]=... 128 | % lobpcg(randn(n,8),A,speye(n),@(x)precfun(x),1e-5,50,2); 129 | % 130 | % produces similar answers, but is somewhat slower and needs more memory as 131 | % technically a generalized eigenproblem with B=I is solved here. 132 | % 133 | % The following Example for a mostly diagonally dominant sparse matrix A 134 | % demonstrates different types of preconditioning, compared to the standard 135 | % use of the main diagonal of A: 136 | % 137 | % clear all; close all; 138 | % n = 1000; M = spdiags([1:n]',0,n,n); precfun=@(x)M\x; 139 | % A=M+sprandsym(n,.1); Xini=randn(n,5); maxiter=15; tol=1e-5; 140 | % [~,~,~,~,rnp]=lobpcg(Xini,A,tol,maxiter,1); 141 | % [~,~,~,~,r]=lobpcg(Xini,A,[],@(x)precfun(x),tol,maxiter,1); 142 | % subplot(2,2,1), semilogy(r'); hold on; semilogy(rnp',':>'); 143 | % title('No preconditioning (top)'); axis tight; 144 | % M(1,2) = 2; precfun=@(x)M\x; % M is no longer symmetric 145 | % [~,~,~,~,rns]=lobpcg(Xini,A,[],@(x)precfun(x),tol,maxiter,1); 146 | % subplot(2,2,2), semilogy(r'); hold on; semilogy(rns','--s'); 147 | % title('Nonsymmetric preconditioning (square)'); axis tight; 148 | % M(1,2) = 0; precfun=@(x)M\(x+10*sin(x)); % nonlinear preconditioning 149 | % [~,~,~,~,rnl]=lobpcg(Xini,A,[],@(x)precfun(x),tol,maxiter,1); 150 | % subplot(2,2,3), semilogy(r'); hold on; semilogy(rnl','-.*'); 151 | % title('Nonlinear preconditioning (star)'); axis tight; 152 | % M=abs(M-3.5*speye(n,n)); precfun=@(x)M\x; 153 | % [~,~,~,~,rs]=lobpcg(Xini,A,[],@(x)precfun(x),tol,maxiter,1); 154 | % subplot(2,2,4), semilogy(r'); hold on; semilogy(rs','-d'); 155 | % title('Selective preconditioning (diamond)'); axis tight; 156 | % 157 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 158 | % 159 | % This main function LOBPCG is a version of 160 | % the preconditioned conjugate gradient method (Algorithm 5.1) described in 161 | % A. V. Knyazev, Toward the Optimal Preconditioned Eigensolver: 162 | % Locally Optimal Block Preconditioned Conjugate Gradient Method, 163 | % SIAM Journal on Scientific Computing 23 (2001), no. 2, pp. 517-541. 164 | % http://dx.doi.org/10.1137/S1064827500366124 165 | % 166 | % Known bugs/features: 167 | % 168 | % - an excessively small requested tolerance may result in often restarts 169 | % and instability. The code is not written to produce an eps-level 170 | % accuracy! Use common sense. 171 | % 172 | % - the code may be very sensitive to the number of eigenpairs computed, 173 | % if there is a cluster of eigenvalues not completely included, cf. 174 | % 175 | % operatorA=diag([1 1.99 2:99]); 176 | % [blockVectorX,lambda]=lobpcg(randn(100,1),operatorA,1e-10,80,2); 177 | % [blockVectorX,lambda]=lobpcg(randn(100,2),operatorA,1e-10,80,2); 178 | % [blockVectorX,lambda]=lobpcg(randn(100,3),operatorA,1e-10,80,2); 179 | % 180 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 181 | % The main distribution site: 182 | % http://math.ucdenver.edu/~aknyazev/ 183 | % 184 | % A C-version of this code is a part of the 185 | % http://code.google.com/p/blopex/ 186 | % package and is directly available, e.g., in PETSc and HYPRE. 187 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 188 | 189 | % License: GNU LGPL ver 2.1 or above 190 | % Copyright (c) 2000-2011 A.V. Knyazev, Andrew.Knyazev@ucdenver.edu 191 | % $Revision: 4.13 $ $Date: 16-Oct-2011 192 | % Tested in MATLAB 6.5-7.13 and Octave 3.2.3-3.4.2. 193 | 194 | 195 | %Begin 196 | 197 | % constants 198 | 199 | CONVENTIONAL_CONSTRAINTS = 1; 200 | SYMMETRIC_CONSTRAINTS = 2; 201 | 202 | %Initial settings 203 | 204 | failureFlag = 1; 205 | if nargin < 2 206 | error('BLOPEX:lobpcg:NotEnoughInputs',... 207 | strcat('There must be at least 2 input agruments: ',... 208 | 'blockVectorX and operatorA')); 209 | end 210 | if nargin > 8 211 | warning('BLOPEX:lobpcg:TooManyInputs',... 212 | strcat('There must be at most 8 input agruments ',... 213 | 'unless arguments are passed to a function')); 214 | end 215 | 216 | if ~isnumeric(blockVectorX) 217 | error('BLOPEX:lobpcg:FirstInputNotNumeric',... 218 | 'The first input argument blockVectorX must be numeric'); 219 | end 220 | [n,blockSize]=size(blockVectorX); 221 | if blockSize > n 222 | error('BLOPEX:lobpcg:FirstInputFat',... 223 | 'The first input argument blockVectorX must be tall, not fat'); 224 | end 225 | if n < 6 226 | error('BLOPEX:lobpcg:MatrixTooSmall',... 227 | 'The code does not work for matrices of small sizes'); 228 | end 229 | 230 | if isa(operatorA,'numeric') 231 | nA = size(operatorA,1); 232 | if any(size(operatorA) ~= nA) 233 | error('BLOPEX:lobpcg:MatrixNotSquare',... 234 | 'operatorA must be a square matrix or a string'); 235 | end 236 | if size(operatorA) ~= n 237 | error('BLOPEX:lobpcg:MatrixWrongSize',... 238 | ['The size ' int2str(size(operatorA))... 239 | ' of operatorA is not the same as ' int2str(n)... 240 | ' - the number of rows of blockVectorX']); 241 | end 242 | end 243 | 244 | count_string = 0; 245 | 246 | operatorT = []; 247 | operatorB = []; 248 | residualTolerance = []; 249 | maxIterations = []; 250 | verbosityLevel = []; 251 | blockVectorY = []; sizeY = 0; 252 | for j = 1:nargin-2 253 | if isequal(size(varargin{j}),[n,n]) 254 | if isempty(operatorB) 255 | operatorB = varargin{j}; 256 | else 257 | error('BLOPEX:lobpcg:TooManyMatrixInputs',... 258 | strcat('Too many matrix input arguments. ',... 259 | 'Preconditioner operatorT must be an M-function')); 260 | end 261 | elseif isequal(size(varargin{j},1),n) && size(varargin{j},2) < n 262 | if isempty(blockVectorY) 263 | blockVectorY = varargin{j}; 264 | sizeY=size(blockVectorY,2); 265 | else 266 | error('BLOPEX:lobpcg:WrongConstraintsFormat',... 267 | 'Something wrong with blockVectorY input argument'); 268 | end 269 | elseif ischar(varargin{j}) || isa(varargin{j},'function_handle') 270 | if count_string == 0 271 | if isempty(operatorB) 272 | operatorB = varargin{j}; 273 | count_string = count_string + 1; 274 | else 275 | operatorT = varargin{j}; 276 | end 277 | elseif count_string == 1 278 | operatorT = varargin{j}; 279 | else 280 | warning('BLOPEX:lobpcg:TooManyStringFunctionHandleInputs',... 281 | 'Too many string or FunctionHandle input arguments'); 282 | end 283 | elseif isequal(size(varargin{j}),[n,n]) 284 | error('BLOPEX:lobpcg:WrongPreconditionerFormat',... 285 | 'Preconditioner operatorT must be an M-function'); 286 | elseif max(size(varargin{j})) == 1 287 | if isempty(residualTolerance) 288 | residualTolerance = varargin{j}; 289 | elseif isempty(maxIterations) 290 | maxIterations = varargin{j}; 291 | elseif isempty(verbosityLevel) 292 | verbosityLevel = varargin{j}; 293 | else 294 | warning('BLOPEX:lobpcg:TooManyScalarInputs',... 295 | 'Too many scalar parameters, need only three'); 296 | end 297 | elseif isempty(varargin{j}) 298 | if isempty(operatorB) 299 | count_string = count_string + 1; 300 | elseif ~isempty(operatorT) 301 | count_string = count_string + 1; 302 | elseif ~isempty(blockVectorY) 303 | error('BLOPEX:lobpcg:UnrecognizedEmptyInput',... 304 | ['Unrecognized empty input argument number ' int2str(j+2)]); 305 | end 306 | else 307 | error('BLOPEX:lobpcg:UnrecognizedInput',... 308 | ['Input argument number ' int2str(j+2) ' not recognized.']); 309 | end 310 | end 311 | 312 | if verbosityLevel 313 | if issparse(blockVectorX) 314 | fprintf(['The sparse initial guess with %i colunms '... 315 | 'and %i raws is detected \n'],n,blockSize); 316 | else 317 | fprintf(['The full initial guess with %i colunms '... 318 | 'and %i raws is detected \n'],n,blockSize); 319 | end 320 | if ischar(operatorA) 321 | fprintf('The main operator is detected as an M-function %s \n',... 322 | operatorA); 323 | elseif isa(operatorA,'function_handle') 324 | fprintf('The main operator is detected as an M-function %s \n',... 325 | func2str(operatorA)); 326 | elseif issparse(operatorA) 327 | fprintf('The main operator is detected as a sparse matrix \n'); 328 | else 329 | fprintf('The main operator is detected as a full matrix \n'); 330 | end 331 | if isempty(operatorB) 332 | fprintf('Solving standard eigenvalue problem, not generalized \n'); 333 | elseif ischar(operatorB) 334 | fprintf(['The second operator of the generalized eigenproblem \n'... 335 | 'is detected as an M-function %s \n'],operatorB); 336 | elseif isa(operatorB,'function_handle') 337 | fprintf(['The second operator of the generalized eigenproblem \n'... 338 | 'is detected as an M-function %s \n'],func2str(operatorB)); 339 | elseif issparse(operatorB) 340 | fprintf(strcat('The second operator of the generalized',... 341 | 'eigenproblem \n is detected as a sparse matrix \n')); 342 | else 343 | fprintf(strcat('The second operator of the generalized',... 344 | 'eigenproblem \n is detected as a full matrix \n')); 345 | end 346 | if isempty(operatorT) 347 | fprintf('No preconditioner is detected \n'); 348 | elseif ischar(operatorT) 349 | fprintf('The preconditioner is detected as an M-function %s \n',... 350 | operatorT); 351 | elseif isa(operatorT,'function_handle') 352 | fprintf('The preconditioner is detected as an M-function %s \n',... 353 | func2str(operatorT)); 354 | end 355 | if isempty(blockVectorY) 356 | fprintf('No matrix of constraints is detected \n') 357 | elseif issparse(blockVectorY) 358 | fprintf('The sparse matrix of %i constraints is detected \n',sizeY); 359 | else 360 | fprintf('The full matrix of %i constraints is detected \n',sizeY); 361 | end 362 | if issparse(blockVectorY) ~= issparse(blockVectorX) 363 | warning('BLOPEX:lobpcg:SparsityInconsistent',... 364 | strcat('The sparsity formats of the initial guess and ',... 365 | 'the constraints are inconsistent')); 366 | end 367 | end 368 | 369 | % Set defaults 370 | 371 | if isempty(residualTolerance) 372 | residualTolerance = sqrt(eps)*n; 373 | end 374 | if isempty(maxIterations) 375 | maxIterations = min(n,20); 376 | end 377 | if isempty(verbosityLevel) 378 | verbosityLevel = 0; 379 | end 380 | 381 | if verbosityLevel 382 | fprintf('Tolerance %e and maximum number of iterations %i \n',... 383 | residualTolerance,maxIterations) 384 | end 385 | 386 | %constraints preprocessing 387 | if isempty(blockVectorY) 388 | constraintStyle = 0; 389 | else 390 | constraintStyle = SYMMETRIC_CONSTRAINTS; % more accurate? 391 | %constraintStyle = CONVENTIONAL_CONSTRAINTS; 392 | end 393 | 394 | if constraintStyle == CONVENTIONAL_CONSTRAINTS 395 | 396 | if isempty(operatorB) 397 | gramY = blockVectorY'*blockVectorY; 398 | else 399 | if isnumeric(operatorB) 400 | blockVectorBY = operatorB*blockVectorY; 401 | else 402 | blockVectorBY = feval(operatorB,blockVectorY); 403 | end 404 | gramY=blockVectorY'*blockVectorBY; 405 | end 406 | gramY=(gramY'+gramY)*0.5; 407 | if isempty(operatorB) 408 | blockVectorX = blockVectorX - ... 409 | blockVectorY*(gramY\(blockVectorY'*blockVectorX)); 410 | else 411 | blockVectorX =blockVectorX - ... 412 | blockVectorY*(gramY\(blockVectorBY'*blockVectorX)); 413 | end 414 | 415 | elseif constraintStyle == SYMMETRIC_CONSTRAINTS 416 | 417 | if ~isempty(operatorB) 418 | if isnumeric(operatorB) 419 | blockVectorY = operatorB*blockVectorY; 420 | else 421 | blockVectorY = feval(operatorB,blockVectorY); 422 | end 423 | end 424 | if isempty(operatorT) 425 | gramY = blockVectorY'*blockVectorY; 426 | else 427 | blockVectorTY = feval(operatorT,blockVectorY); 428 | gramY = blockVectorY'*blockVectorTY; 429 | end 430 | gramY=(gramY'+gramY)*0.5; 431 | if isempty(operatorT) 432 | blockVectorX = blockVectorX - ... 433 | blockVectorY*(gramY\(blockVectorY'*blockVectorX)); 434 | else 435 | blockVectorX = blockVectorX - ... 436 | blockVectorTY*(gramY\(blockVectorY'*blockVectorX)); 437 | end 438 | 439 | end 440 | 441 | %Making the initial vectors (operatorB-) orthonormal 442 | if isempty(operatorB) 443 | %[blockVectorX,gramXBX] = qr(blockVectorX,0); 444 | gramXBX=blockVectorX'*blockVectorX; 445 | if ~isreal(gramXBX) 446 | gramXBX=(gramXBX+gramXBX')*0.5; 447 | end 448 | [gramXBX,cholFlag]=chol(gramXBX); 449 | if cholFlag ~= 0 450 | error('BLOPEX:lobpcg:ConstraintsTooTight',... 451 | 'The initial approximation after constraints is not full rank'); 452 | end 453 | blockVectorX = blockVectorX/gramXBX; 454 | else 455 | %[blockVectorX,blockVectorBX] = orth(operatorB,blockVectorX); 456 | if isnumeric(operatorB) 457 | blockVectorBX = operatorB*blockVectorX; 458 | else 459 | blockVectorBX = feval(operatorB,blockVectorX); 460 | end 461 | gramXBX=blockVectorX'*blockVectorBX; 462 | if ~isreal(gramXBX) 463 | gramXBX=(gramXBX+gramXBX')*0.5; 464 | end 465 | [gramXBX,cholFlag]=chol(gramXBX); 466 | if cholFlag ~= 0 467 | error('BLOPEX:lobpcg:InitialNotFullRank',... 468 | sprintf('%s\n%s', ... 469 | 'The initial approximation after constraints is not full rank',... 470 | 'or/and operatorB is not positive definite')); 471 | end 472 | blockVectorX = blockVectorX/gramXBX; 473 | blockVectorBX = blockVectorBX/gramXBX; 474 | end 475 | 476 | % Checking if the problem is big enough for the algorithm, 477 | % i.e. n-sizeY > 5*blockSize 478 | % Theoretically, the algorithm should be able to run if 479 | % n-sizeY > 3*blockSize, 480 | % but the extreme cases might be unstable, so we use 5 instead of 3 here. 481 | if n-sizeY < 5*blockSize 482 | error('BLOPEX:lobpcg:MatrixTooSmall','%s\n%s', ... 483 | 'The problem size is too small, relative to the block size.',... 484 | 'Try using eig() or eigs() instead.'); 485 | end 486 | 487 | % Preallocation 488 | residualNormsHistory=zeros(blockSize,maxIterations); 489 | lambdaHistory=zeros(blockSize,maxIterations+1); 490 | condestGhistory=zeros(1,maxIterations+1); 491 | 492 | blockVectorBR=zeros(n,blockSize); 493 | blockVectorAR=zeros(n,blockSize); 494 | blockVectorP=zeros(n,blockSize); 495 | blockVectorAP=zeros(n,blockSize); 496 | blockVectorBP=zeros(n,blockSize); 497 | 498 | %Initial settings for the loop 499 | if isnumeric(operatorA) 500 | blockVectorAX = operatorA*blockVectorX; 501 | else 502 | blockVectorAX = feval(operatorA,blockVectorX); 503 | end 504 | 505 | gramXAX = full(blockVectorX'*blockVectorAX); 506 | gramXAX = (gramXAX + gramXAX')*0.5; 507 | % eig(...,'chol') uses only the diagonal and upper triangle - 508 | % not true in MATLAB 509 | % Octave v3.2.3-4, eig() does not support inputting 'chol' 510 | [coordX,gramXAX]=eig(gramXAX,eye(blockSize)); 511 | 512 | lambda=diag(gramXAX); %eig returns non-ordered eigenvalues on the diagonal 513 | 514 | if issparse(blockVectorX) 515 | coordX=sparse(coordX); 516 | end 517 | 518 | blockVectorX = blockVectorX*coordX; 519 | blockVectorAX = blockVectorAX*coordX; 520 | if ~isempty(operatorB) 521 | blockVectorBX = blockVectorBX*coordX; 522 | end 523 | clear coordX 524 | 525 | condestGhistory(1)=-log10(eps)/2; %if too small cause unnecessary restarts 526 | 527 | lambdaHistory(1:blockSize,1)=lambda; 528 | 529 | activeMask = true(blockSize,1); 530 | % currentBlockSize = blockSize; %iterate all 531 | % 532 | % restart=1;%steepest descent 533 | 534 | %The main part of the method is the loop of the CG method: begin 535 | for iterationNumber=1:maxIterations 536 | 537 | % %Computing the active residuals 538 | % if isempty(operatorB) 539 | % if currentBlockSize > 1 540 | % blockVectorR(:,activeMask)=blockVectorAX(:,activeMask) - ... 541 | % blockVectorX(:,activeMask)*spdiags(lambda(activeMask),0,currentBlockSize,currentBlockSize); 542 | % else 543 | % blockVectorR(:,activeMask)=blockVectorAX(:,activeMask) - ... 544 | % blockVectorX(:,activeMask)*lambda(activeMask); 545 | % %to make blockVectorR full when lambda is just a scalar 546 | % end 547 | % else 548 | % if currentBlockSize > 1 549 | % blockVectorR(:,activeMask)=blockVectorAX(:,activeMask) - ... 550 | % blockVectorBX(:,activeMask)*spdiags(lambda(activeMask),0,currentBlockSize,currentBlockSize); 551 | % else 552 | % blockVectorR(:,activeMask)=blockVectorAX(:,activeMask) - ... 553 | % blockVectorBX(:,activeMask)*lambda(activeMask); 554 | % %to make blockVectorR full when lambda is just a scalar 555 | % end 556 | % end 557 | 558 | %Computing all residuals 559 | if isempty(operatorB) 560 | if blockSize > 1 561 | blockVectorR = blockVectorAX - ... 562 | blockVectorX*spdiags(lambda,0,blockSize,blockSize); 563 | else 564 | blockVectorR = blockVectorAX - blockVectorX*lambda; 565 | %to make blockVectorR full when lambda is just a scalar 566 | end 567 | else 568 | if blockSize > 1 569 | blockVectorR=blockVectorAX - ... 570 | blockVectorBX*spdiags(lambda,0,blockSize,blockSize); 571 | else 572 | blockVectorR = blockVectorAX - blockVectorBX*lambda; 573 | %to make blockVectorR full when lambda is just a scalar 574 | end 575 | end 576 | 577 | %Satisfying the constraints for the active residulas 578 | if constraintStyle == SYMMETRIC_CONSTRAINTS 579 | %if constraintStyle 580 | if isempty(operatorT) 581 | blockVectorR(:,activeMask) = blockVectorR(:,activeMask) - ... 582 | blockVectorY*(gramY\(blockVectorY'*... 583 | blockVectorR(:,activeMask))); 584 | else 585 | blockVectorR(:,activeMask) = blockVectorR(:,activeMask) - ... 586 | blockVectorY*(gramY\(blockVectorTY'*... 587 | blockVectorR(:,activeMask))); 588 | end 589 | end 590 | 591 | residualNorms=full(sqrt(sum(conj(blockVectorR).*blockVectorR)')); 592 | residualNormsHistory(1:blockSize,iterationNumber)=residualNorms; 593 | 594 | %index antifreeze 595 | activeMask = full(residualNorms > residualTolerance) & activeMask; 596 | %activeMask = full(residualNorms > residualTolerance); 597 | %above allows vectors back into active, which causes problems with frosen Ps 598 | %activeMask = full(residualNorms > 0); %iterate all, ignore freeze 599 | 600 | currentBlockSize = sum(activeMask); 601 | if currentBlockSize == 0 602 | failureFlag=0; %all eigenpairs converged 603 | break 604 | end 605 | 606 | %Applying the preconditioner operatorT to the active residulas 607 | if ~isempty(operatorT) 608 | blockVectorR(:,activeMask) = ... 609 | feval(operatorT,blockVectorR(:,activeMask)); 610 | end 611 | 612 | if constraintStyle == CONVENTIONAL_CONSTRAINTS 613 | %if constraintStyle 614 | if isempty(operatorB) 615 | blockVectorR(:,activeMask) = blockVectorR(:,activeMask) - ... 616 | blockVectorY*(gramY\(blockVectorY'*... 617 | blockVectorR(:,activeMask))); 618 | else 619 | blockVectorR(:,activeMask) = blockVectorR(:,activeMask) - ... 620 | blockVectorY*(gramY\(blockVectorBY'*... 621 | blockVectorR(:,activeMask))); 622 | end 623 | end 624 | 625 | %Making active (preconditioned) residuals orthogonal to blockVectorX 626 | if isempty(operatorB) 627 | blockVectorR(:,activeMask) = blockVectorR(:,activeMask) - ... 628 | blockVectorX*(blockVectorX'*blockVectorR(:,activeMask)); 629 | else 630 | blockVectorR(:,activeMask) = blockVectorR(:,activeMask) - ... 631 | blockVectorX*(blockVectorBX'*blockVectorR(:,activeMask)); 632 | end 633 | 634 | %Making active residuals orthonormal 635 | if isempty(operatorB) 636 | %[blockVectorR(:,activeMask),gramRBR]=... 637 | %qr(blockVectorR(:,activeMask),0); %to increase stability 638 | gramRBR=blockVectorR(:,activeMask)'*blockVectorR(:,activeMask); 639 | if ~isreal(gramRBR) 640 | gramRBR=(gramRBR+gramRBR')*0.5; 641 | end 642 | [gramRBR,cholFlag]=chol(gramRBR); 643 | if cholFlag == 0 644 | blockVectorR(:,activeMask) = blockVectorR(:,activeMask)/gramRBR; 645 | else 646 | warning('BLOPEX:lobpcg:ResidualNotFullRank',... 647 | 'The residual is not full rank.'); 648 | break 649 | end 650 | else 651 | if isnumeric(operatorB) 652 | blockVectorBR(:,activeMask) = ... 653 | operatorB*blockVectorR(:,activeMask); 654 | else 655 | blockVectorBR(:,activeMask) = ... 656 | feval(operatorB,blockVectorR(:,activeMask)); 657 | end 658 | gramRBR=blockVectorR(:,activeMask)'*blockVectorBR(:,activeMask); 659 | if ~isreal(gramRBR) 660 | gramRBR=(gramRBR+gramRBR')*0.5; 661 | end 662 | [gramRBR,cholFlag]=chol(gramRBR); 663 | if cholFlag == 0 664 | blockVectorR(:,activeMask) = ... 665 | blockVectorR(:,activeMask)/gramRBR; 666 | blockVectorBR(:,activeMask) = ... 667 | blockVectorBR(:,activeMask)/gramRBR; 668 | else 669 | warning('BLOPEX:lobpcg:ResidualNotFullRankOrElse',... 670 | strcat('The residual is not full rank or/and operatorB ',... 671 | 'is not positive definite.')); 672 | break 673 | end 674 | 675 | end 676 | clear gramRBR; 677 | 678 | if isnumeric(operatorA) 679 | blockVectorAR(:,activeMask) = operatorA*blockVectorR(:,activeMask); 680 | else 681 | blockVectorAR(:,activeMask) = ... 682 | feval(operatorA,blockVectorR(:,activeMask)); 683 | end 684 | 685 | if iterationNumber > 1 686 | 687 | %Making active conjugate directions orthonormal 688 | if isempty(operatorB) 689 | %[blockVectorP(:,activeMask),gramPBP] = qr(blockVectorP(:,activeMask),0); 690 | gramPBP=blockVectorP(:,activeMask)'*blockVectorP(:,activeMask); 691 | if ~isreal(gramPBP) 692 | gramPBP=(gramPBP+gramPBP')*0.5; 693 | end 694 | [gramPBP,cholFlag]=chol(gramPBP); 695 | if cholFlag == 0 696 | blockVectorP(:,activeMask) = ... 697 | blockVectorP(:,activeMask)/gramPBP; 698 | blockVectorAP(:,activeMask) = ... 699 | blockVectorAP(:,activeMask)/gramPBP; 700 | else 701 | warning('BLOPEX:lobpcg:DirectionNotFullRank',... 702 | 'The direction matrix is not full rank.'); 703 | break 704 | end 705 | else 706 | gramPBP=blockVectorP(:,activeMask)'*blockVectorBP(:,activeMask); 707 | if ~isreal(gramPBP) 708 | gramPBP=(gramPBP+gramPBP')*0.5; 709 | end 710 | [gramPBP,cholFlag]=chol(gramPBP); 711 | if cholFlag == 0 712 | blockVectorP(:,activeMask) = ... 713 | blockVectorP(:,activeMask)/gramPBP; 714 | blockVectorAP(:,activeMask) = ... 715 | blockVectorAP(:,activeMask)/gramPBP; 716 | blockVectorBP(:,activeMask) = ... 717 | blockVectorBP(:,activeMask)/gramPBP; 718 | else 719 | warning('BLOPEX:lobpcg:DirectionNotFullRank',... 720 | strcat('The direction matrix is not full rank ',... 721 | 'or/and operatorB is not positive definite.')); 722 | break 723 | end 724 | end 725 | clear gramPBP 726 | end 727 | 728 | condestGmean = mean(condestGhistory(max(1,iterationNumber-10-... 729 | round(log(currentBlockSize))):iterationNumber)); 730 | 731 | % restart=1; 732 | 733 | % The Raileight-Ritz method for [blockVectorX blockVectorR blockVectorP] 734 | 735 | if residualNorms > eps^0.6 736 | explicitGramFlag = 0; 737 | else 738 | explicitGramFlag = 1; %suggested by Garrett Moran, private 739 | end 740 | 741 | activeRSize=size(blockVectorR(:,activeMask),2); 742 | if iterationNumber == 1 743 | activePSize=0; 744 | restart=1; 745 | else 746 | activePSize=size(blockVectorP(:,activeMask),2); 747 | restart=0; 748 | end 749 | 750 | gramXAR=full(blockVectorAX'*blockVectorR(:,activeMask)); 751 | gramRAR=full(blockVectorAR(:,activeMask)'*blockVectorR(:,activeMask)); 752 | gramRAR=(gramRAR'+gramRAR)*0.5; 753 | 754 | if explicitGramFlag 755 | gramXAX=full(blockVectorAX'*blockVectorX); 756 | gramXAX=(gramXAX'+gramXAX)*0.5; 757 | if isempty(operatorB) 758 | gramXBX=full(blockVectorX'*blockVectorX); 759 | gramRBR=full(blockVectorR(:,activeMask)'*... 760 | blockVectorR(:,activeMask)); 761 | gramXBR=full(blockVectorX'*blockVectorR(:,activeMask)); 762 | else 763 | gramXBX=full(blockVectorBX'*blockVectorX); 764 | gramRBR=full(blockVectorBR(:,activeMask)'*... 765 | blockVectorR(:,activeMask)); 766 | gramXBR=full(blockVectorBX'*blockVectorR(:,activeMask)); 767 | end 768 | gramXBX=(gramXBX'+gramXBX)*0.5; 769 | gramRBR=(gramRBR'+gramRBR)*0.5; 770 | 771 | end 772 | 773 | for cond_try=1:2, %cond_try == 2 when restart 774 | 775 | if ~restart 776 | gramXAP=full(blockVectorAX'*blockVectorP(:,activeMask)); 777 | gramRAP=full(blockVectorAR(:,activeMask)'*... 778 | blockVectorP(:,activeMask)); 779 | gramPAP=full(blockVectorAP(:,activeMask)'*... 780 | blockVectorP(:,activeMask)); 781 | gramPAP=(gramPAP'+gramPAP)*0.5; 782 | 783 | if explicitGramFlag 784 | gramA = [ gramXAX gramXAR gramXAP 785 | gramXAR' gramRAR gramRAP 786 | gramXAP' gramRAP' gramPAP ]; 787 | else 788 | gramA = [ diag(lambda) gramXAR gramXAP 789 | gramXAR' gramRAR gramRAP 790 | gramXAP' gramRAP' gramPAP ]; 791 | end 792 | 793 | clear gramXAP gramRAP gramPAP 794 | 795 | if isempty(operatorB) 796 | gramXBP=full(blockVectorX'*blockVectorP(:,activeMask)); 797 | gramRBP=full(blockVectorR(:,activeMask)'*... 798 | blockVectorP(:,activeMask)); 799 | else 800 | gramXBP=full(blockVectorBX'*blockVectorP(:,activeMask)); 801 | gramRBP=full(blockVectorBR(:,activeMask)'*... 802 | blockVectorP(:,activeMask)); 803 | %or blockVectorR(:,activeMask)'*blockVectorBP(:,activeMask); 804 | end 805 | 806 | if explicitGramFlag 807 | if isempty(operatorB) 808 | gramPBP=full(blockVectorP(:,activeMask)'*... 809 | blockVectorP(:,activeMask)); 810 | else 811 | gramPBP=full(blockVectorBP(:,activeMask)'*... 812 | blockVectorP(:,activeMask)); 813 | end 814 | gramPBP=(gramPBP'+gramPBP)*0.5; 815 | gramB = [ gramXBX gramXBR gramXBP 816 | gramXBR' gramRBR gramRBP 817 | gramXBP' gramRBP' gramPBP ]; 818 | clear gramPBP 819 | else 820 | gramB=[eye(blockSize) zeros(blockSize,activeRSize) gramXBP 821 | zeros(blockSize,activeRSize)' eye(activeRSize) gramRBP 822 | gramXBP' gramRBP' eye(activePSize) ]; 823 | end 824 | 825 | clear gramXBP gramRBP; 826 | 827 | else 828 | 829 | if explicitGramFlag 830 | gramA = [ gramXAX gramXAR 831 | gramXAR' gramRAR ]; 832 | gramB = [ gramXBX gramXBR 833 | gramXBR' eye(activeRSize) ]; 834 | clear gramXAX gramXBX gramXBR 835 | else 836 | gramA = [ diag(lambda) gramXAR 837 | gramXAR' gramRAR ]; 838 | gramB = eye(blockSize+activeRSize); 839 | end 840 | 841 | clear gramXAR gramRAR; 842 | 843 | end 844 | 845 | condestG = log10(cond(gramB))+1; 846 | if (condestG/condestGmean > 2 && condestG > 2 )|| condestG > 8 847 | 848 | %black magic - need to guess the restart 849 | if verbosityLevel 850 | fprintf('Restart on step %i as condestG %5.4e \n',... 851 | iterationNumber,condestG); 852 | end 853 | if cond_try == 1 && ~restart 854 | restart=1; %steepest descent restart for stability 855 | else 856 | warning('BLOPEX:lobpcg:IllConditioning',... 857 | 'Gramm matrix ill-conditioned: results unpredictable'); 858 | end 859 | else 860 | break 861 | end 862 | 863 | end 864 | 865 | [gramA,gramB]=eig(gramA,gramB); 866 | lambda=diag(gramB(1:blockSize,1:blockSize)); 867 | coordX=gramA(:,1:blockSize); 868 | 869 | clear gramA gramB 870 | 871 | if issparse(blockVectorX) 872 | coordX=sparse(coordX); 873 | end 874 | 875 | if ~restart 876 | blockVectorP = blockVectorR(:,activeMask)*... 877 | coordX(blockSize+1:blockSize+activeRSize,:) + ... 878 | blockVectorP(:,activeMask)*... 879 | coordX(blockSize+activeRSize+1:blockSize + ... 880 | activeRSize+activePSize,:); 881 | blockVectorAP = blockVectorAR(:,activeMask)*... 882 | coordX(blockSize+1:blockSize+activeRSize,:) + ... 883 | blockVectorAP(:,activeMask)*... 884 | coordX(blockSize+activeRSize+1:blockSize + ... 885 | activeRSize+activePSize,:); 886 | if ~isempty(operatorB) 887 | blockVectorBP = blockVectorBR(:,activeMask)*... 888 | coordX(blockSize+1:blockSize+activeRSize,:) + ... 889 | blockVectorBP(:,activeMask)*... 890 | coordX(blockSize+activeRSize+1:blockSize+activeRSize+activePSize,:); 891 | end 892 | else %use block steepest descent 893 | blockVectorP = blockVectorR(:,activeMask)*... 894 | coordX(blockSize+1:blockSize+activeRSize,:); 895 | blockVectorAP = blockVectorAR(:,activeMask)*... 896 | coordX(blockSize+1:blockSize+activeRSize,:); 897 | if ~isempty(operatorB) 898 | blockVectorBP = blockVectorBR(:,activeMask)*... 899 | coordX(blockSize+1:blockSize+activeRSize,:); 900 | end 901 | end 902 | 903 | blockVectorX = blockVectorX*coordX(1:blockSize,:) + blockVectorP; 904 | blockVectorAX=blockVectorAX*coordX(1:blockSize,:) + blockVectorAP; 905 | if ~isempty(operatorB) 906 | blockVectorBX=blockVectorBX*coordX(1:blockSize,:) + blockVectorBP; 907 | end 908 | clear coordX 909 | %%end RR 910 | 911 | lambdaHistory(1:blockSize,iterationNumber+1)=lambda; 912 | condestGhistory(iterationNumber+1)=condestG; 913 | 914 | if verbosityLevel 915 | fprintf('Iteration %i current block size %i \n',... 916 | iterationNumber,currentBlockSize); 917 | fprintf('Eigenvalues lambda %17.16e \n',lambda); 918 | fprintf('Residual Norms %e \n',residualNorms'); 919 | end 920 | end 921 | %The main step of the method was the CG cycle: end 922 | 923 | %Postprocessing 924 | 925 | %Making sure blockVectorX's "exactly" satisfy the blockVectorY constrains?? 926 | 927 | %Making sure blockVectorX's are "exactly" othonormalized by final "exact" RR 928 | if isempty(operatorB) 929 | gramXBX=full(blockVectorX'*blockVectorX); 930 | else 931 | if isnumeric(operatorB) 932 | blockVectorBX = operatorB*blockVectorX; 933 | else 934 | blockVectorBX = feval(operatorB,blockVectorX); 935 | end 936 | gramXBX=full(blockVectorX'*blockVectorBX); 937 | end 938 | gramXBX=(gramXBX'+gramXBX)*0.5; 939 | 940 | if isnumeric(operatorA) 941 | blockVectorAX = operatorA*blockVectorX; 942 | else 943 | blockVectorAX = feval(operatorA,blockVectorX); 944 | end 945 | gramXAX = full(blockVectorX'*blockVectorAX); 946 | gramXAX = (gramXAX + gramXAX')*0.5; 947 | 948 | %Raileigh-Ritz for blockVectorX, which is already operatorB-orthonormal 949 | [coordX,gramXBX] = eig(gramXAX,gramXBX); 950 | lambda=diag(gramXBX); 951 | 952 | if issparse(blockVectorX) 953 | coordX=sparse(coordX); 954 | end 955 | 956 | blockVectorX = blockVectorX*coordX; 957 | blockVectorAX = blockVectorAX*coordX; 958 | if ~isempty(operatorB) 959 | blockVectorBX = blockVectorBX*coordX; 960 | end 961 | 962 | %Computing all residuals 963 | if isempty(operatorB) 964 | if blockSize > 1 965 | blockVectorR = blockVectorAX - ... 966 | blockVectorX*spdiags(lambda,0,blockSize,blockSize); 967 | else 968 | blockVectorR = blockVectorAX - blockVectorX*lambda; 969 | %to make blockVectorR full when lambda is just a scalar 970 | end 971 | else 972 | if blockSize > 1 973 | blockVectorR=blockVectorAX - ... 974 | blockVectorBX*spdiags(lambda,0,blockSize,blockSize); 975 | else 976 | blockVectorR = blockVectorAX - blockVectorBX*lambda; 977 | %to make blockVectorR full when lambda is just a scalar 978 | end 979 | end 980 | 981 | residualNorms=full(sqrt(sum(conj(blockVectorR).*blockVectorR)')); 982 | residualNormsHistory(1:blockSize,iterationNumber)=residualNorms; 983 | 984 | if verbosityLevel 985 | fprintf('Final Eigenvalues lambda %17.16e \n',lambda); 986 | fprintf('Final Residual Norms %e \n',residualNorms'); 987 | end 988 | 989 | if verbosityLevel == 2 990 | whos 991 | figure(491) 992 | semilogy((abs(residualNormsHistory(1:blockSize,1:iterationNumber-1)))'); 993 | title('Residuals for Different Eigenpairs','fontsize',16); 994 | ylabel('Eucledian norm of residuals','fontsize',16); 995 | xlabel('Iteration number','fontsize',16); 996 | %axis tight; 997 | %axis([0 maxIterations+1 1e-15 1e3]) 998 | set(gca,'FontSize',14); 999 | figure(492); 1000 | semilogy(abs((lambdaHistory(1:blockSize,1:iterationNumber)-... 1001 | repmat(lambda,1,iterationNumber)))'); 1002 | title('Eigenvalue errors for Different Eigenpairs','fontsize',16); 1003 | ylabel('Estimated eigenvalue errors','fontsize',16); 1004 | xlabel('Iteration number','fontsize',16); 1005 | %axis tight; 1006 | %axis([0 maxIterations+1 1e-15 1e3]) 1007 | set(gca,'FontSize',14); 1008 | drawnow; 1009 | end 1010 | 1011 | varargout(1)={failureFlag}; 1012 | varargout(2)={lambdaHistory(1:blockSize,1:iterationNumber)}; 1013 | varargout(3)={residualNormsHistory(1:blockSize,1:iterationNumber-1)}; 1014 | end 1015 | --------------------------------------------------------------------------------