diff --git a/DefaultData/2019_03_03_BCT/adjacency_plot_und.m b/DefaultData/2019_03_03_BCT/adjacency_plot_und.m new file mode 100755 index 0000000..64ddbd2 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/adjacency_plot_und.m @@ -0,0 +1,53 @@ +function [X,Y,Z] = adjacency_plot_und(aij,coor) +%ADJACENCY_PLOT_UND Quick visualization tool +% +% [X,Y,Z] = ADJACENCY_PLOT(AIJ,COOR) takes adjacency matrix AIJ and node +% spatial coordinates COOR and generates three vectors that can be used +% for quickly plotting the edges in AIJ. If no coordinates are specified, +% then each node is assigned a position on a circle. COOR can, in +% general, be 2D or 3D. +% +% Example: +% +% >> load AIJ; % load your adjacency matrix +% >> load COOR; % load 3D coordinates for each node +% >> [x,y,z] = adjacency_plot_und(AIJ,COOR); % call function +% >> plot3(x,y,z); % plots network as a single line object +% +% If COOR were 2D, the PLOT3 command changes to a PLOT command. +% +% NOTE: This function is similar to MATLAB's GPLOT command. +% +% Richard Betzel, Indiana University, 2013 + +n = length(aij); +if nargin < 2 + coor = zeros(n,2); + for i = 1:n + coor(i,:) = [cos(2*pi*(i - 1)./n), sin(2*pi*(i - 1)./n)]; + end +end + +[i,j] = find(triu(aij,1)); +[~, p] = sort(max(i,j)); +i = i(p); +j = j(p); + +X = [ coor(i,1) coor(j,1)]'; +Y = [ coor(i,2) coor(j,2)]'; +if size(coor,2) == 3 + Z = [ coor(i,3) coor(j,3)]'; +end +if isfloat(coor) || nargout ~= 0 + X = [X; NaN(size(i))']; + Y = [Y; NaN(size(i))']; + if size(coor,2) == 3 + Z = [Z; NaN(size(i))']; + end +end + +X = X(:); +Y = Y(:); +if size(coor,2) == 3 + Z = Z(:); +end diff --git a/DefaultData/2019_03_03_BCT/agreement.m b/DefaultData/2019_03_03_BCT/agreement.m new file mode 100755 index 0000000..50c75ce --- /dev/null +++ b/DefaultData/2019_03_03_BCT/agreement.m @@ -0,0 +1,60 @@ +function D = agreement(ci,buffsz) +%AGREEMENT Agreement matrix from clusters +% +% D = AGREEMENT(CI) takes as input a set of vertex partitions CI of +% dimensions [vertex x partition]. Each column in CI contains the +% assignments of each vertex to a class/community/module. This function +% aggregates the partitions in CI into a square [vertex x vertex] +% agreement matrix D, whose elements indicate the number of times any two +% vertices were assigned to the same class. +% +% In the case that the number of nodes and partitions in CI is large +% (greater than ~1000 nodes or greater than ~1000 partitions), the script +% can be made faster by computing D in pieces. The optional input BUFFSZ +% determines the size of each piece. Trial and error has found that +% BUFFSZ ~ 150 works well. +% +% Inputs, CI, set of (possibly) degenerate partitions +% BUFFSZ, optional second argument to set buffer size +% +% Outputs: D, agreement matrix +% +% Richard Betzel, Indiana University, 2012 + +%modification history +%09.24.2012 - added loop for big N that makes the function slower but also +% prevents it from maxing out memory. + +n = size(ci,2); + +if nargin < 2 + buffsz = 1000; +end + +if n <= buffsz + + ind = dummyvar(ci); + D = ind*ind'; + +else + + a = 1:buffsz:n; + b = buffsz:buffsz:n; + + if length(a) ~= length(b) + b = [b, n]; + end + + x = [a' b']; + nbuff = size(x,1); + + D = zeros(size(ci,1)); + for i = 1:nbuff + y = ci(:,x(i,1):x(i,2)); + ind = dummyvar(y); + D = D + ind*ind'; + end + +end + +D = D.*~eye(length(D)); diff --git a/DefaultData/2019_03_03_BCT/agreement_weighted.m b/DefaultData/2019_03_03_BCT/agreement_weighted.m new file mode 100755 index 0000000..d917f43 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/agreement_weighted.m @@ -0,0 +1,28 @@ +function D = agreement_weighted(CI,Wts) +% AGREEMENT_WEIGHTED Weights agreement matrix +% +% D = AGREEMENT_WEIGHTED(CI,WTS) is identical to AGREEMENT, with the +% exception that each partitions contribution is weighted according to +% the corresponding scalar value stored in the vector WTS. As an example, +% suppose CI contained partitions obtained using some heuristic for +% maximizing modularity. A possible choice for WTS might be the Q metric +% (Newman's modularity score). Such a choice would add more weight to +% higher modularity partitions. +% +% NOTE: Unlike AGREEMENT, this script does not have the input argument +% BUFFSZ. +% +% Inputs: CI, set of partitions +% WTS, relative weight of importance of each paritition +% +% Outputs: D, weighted agreement matrix +% +% Richard Betzel, Indiana University, 2013 + +Wts = Wts./sum(Wts); +[N,M] = size(CI); +D = zeros(N); +for i = 1:M + d = dummyvar(CI(:,i)); + D = D + (d*d')*Wts(i); +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/align_matrices.m b/DefaultData/2019_03_03_BCT/align_matrices.m new file mode 100755 index 0000000..db4742e --- /dev/null +++ b/DefaultData/2019_03_03_BCT/align_matrices.m @@ -0,0 +1,135 @@ +function [Mreordered,Mindices,cost] = align_matrices(M1,M2,dfun,flag) +%ALIGN_MATRICES Alignment of two matrices +% +% [Mreordered,Mindices,cost] = align_matrices(M1,M2,dfun,flag) +% +% This function aligns two matrices relative to one another by reordering +% the nodes in M2. The function uses a version of simulated annealing. +% +% Inputs: M1 = first connection matrix (square) +% M2 = second connection matrix (square) +% dfun = distance metric to use for matching: +% 'absdff' = absolute difference +% 'sqrdff' = squared difference +% 'cosang' = cosine of vector angle +% +% Mreordered = reordered connection matrix M2 +% Mindices = reordered indices +% cost = distance between M1 and Mreordered +% +% Connection matrices can be weighted or binary, directed or undirected. +% They must have the same number of nodes. M1 can be entered in any +% node ordering. +% +% Note that in general, the outcome will depend on the initial condition +% (the setting of the random number seed). Also, there is no good way to +% determine optimal annealing parameters in advance - these parameters +% will need to be adjusted "by hand" (particularly H, Texp, T0, and Hbrk). +% For large and/or dense matrices, it is highly recommended to perform +% exploratory runs varying the settings of 'H' and 'Texp' and then select +% the best values. +% +% Based on extensive testing, it appears that T0 and Hbrk can remain +% unchanged in most cases. Texp may be varied from 1-1/H to 1-10/H, for +% example. H is the most important parameter - set to larger values as +% the problem size increases. Good solutions can be obtained for +% matrices up to about 100 nodes. It is advisable to run this function +% multiple times and select the solution(s) with the lowest 'cost'. +% +% If the two matrices are related it may be very helpful to pre-align them +% by reordering along their largest eigenvectors: +% [v,~] = eig(M1); v1 = abs(v(:,end)); [a1,b1] = sort(v1); +% [v,~] = eig(M2); v2 = abs(v(:,end)); [a2,b2] = sort(v2); +% [a,b,c] = overlapMAT2(M1(b1,b1),M2(b2,b2),'dfun',1); +% +% Setting 'Texp' to zero cancels annealing and uses a greedy algorithm +% instead. +% +% Yusuke Adachi, University of Tokyo, 2010 +% Olaf Sporns, Indiana University, 2010 + +N = size(M1,1); + +% define maxcost (greatest possible difference) +switch dfun +case 'absdff' + maxcost = sum(abs(sort(M1(:))-(sort(M2(:),'descend')))); +case 'sqrdff' + maxcost = sum((sort(M1(:))-(sort(M2(:),'descend'))).^2); +case 'cosang' + maxcost = pi/2; +end; + +% initialize lowcost +switch dfun +case 'absdff' + lowcost = sum(sum(abs(M1-M2)))/maxcost; +case 'sqrdff' + lowcost = sum(sum((M1-M2).^2))/maxcost; +case 'cosang' + lowcost = acos(dot(M1(:),M2(:))./sqrt(dot(M1(:),M1(:))*dot(M2(:),M2(:))))/maxcost; +end; + +% initialize +mincost = lowcost; +anew = 1:N; +amin = 1:N; +h = 0; hcnt = 0; + +% set annealing parameters +% H determines the maximal number of steps +% Texp determines the steepness of the temperature gradient +% T0 sets the initial temperature (and scales the energy term) +% Hbrk sets a break point for the simulation (no further improvement) +H = 1e06; Texp = 1-1/H; T0 = 1e-03; Hbrk = H/10; +%Texp = 0; + +while hHbrk) + break; + end; + % current temperature + T = T0*Texp^h; + % choose two positions at random and flip them + atmp = anew; + %r = randperm(N); % slower + r = ceil(rand(1,2).*N); + atmp(r(1)) = anew(r(2)); + atmp(r(2)) = anew(r(1)); + switch dfun + case 'absdff' + costnew = sum(sum(abs(M1-M2(atmp,atmp))))/maxcost; + case 'sqrdff' + costnew = sum(sum((M1-M2(atmp,atmp)).^2))/maxcost; + case 'cosang' + M2atmp = M2(atmp,atmp); + costnew = acos(dot(M1(:),M2atmp(:))./sqrt(dot(M1(:),M1(:))*dot(M2atmp(:),M2atmp(:))))/maxcost; + end; + % annealing step + if (costnew < lowcost) || (rand < exp(-(costnew-lowcost)/T)) + anew = atmp; + lowcost = costnew; + % is this the absolute best? + if (lowcost +im = [i(1) i(2)]; +jm = [j(1) j(2)]; + +% copy into tree graph +CIJtree(im,jm) = CIJ(im,jm); +in = im; +out = setdiff(1:N,in); + +% repeat N-2 times +for n=1:N-2 + + % find strongest link between 'in' and 'out',ignore tied ranks + [i,j,s] = find(max(max(CIJ(in,out)))==CIJ(in,out)); + im = in(i(1)); + jm = out(j(1)); + + % copy into tree graph + CIJtree(im,jm) = CIJ(im,jm); CIJtree(jm,im) = CIJ(jm,im); + in = [in jm]; %#ok + out = setdiff(1:N,in); + +end; + +% now add connections back, with the total number of added connections +% determined by the desired 'avgdeg' +CIJnotintree = CIJ.*~CIJtree; +[a,b] = sort(nonzeros(CIJnotintree),'descend'); +cutoff = avgdeg*N - 2*(N-1); +thr = a(cutoff); +CIJclus = CIJtree + CIJnotintree.*(CIJnotintree>=thr); + diff --git a/DefaultData/2019_03_03_BCT/betweenness_bin.m b/DefaultData/2019_03_03_BCT/betweenness_bin.m new file mode 100755 index 0000000..2dcd104 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/betweenness_bin.m @@ -0,0 +1,53 @@ +function BC=betweenness_bin(G) +%BETWEENNESS_BIN Node betweenness centrality +% +% BC = betweenness_bin(A); +% +% Node betweenness centrality is the fraction of all shortest paths in +% the network that contain a given node. Nodes with high values of +% betweenness centrality participate in a large number of shortest paths. +% +% Input: A, binary (directed/undirected) connection matrix. +% +% Output: BC, node betweenness centrality vector. +% +% Note: Betweenness centrality may be normalised to the range [0,1] as +% BC/[(N-1)(N-2)], where N is the number of nodes in the network. +% +% Reference: Kintali (2008) arXiv:0809.1906v2 [cs.DS] +% (generalization to directed and disconnected graphs) +% +% +% Mika Rubinov, UNSW/U Cambridge, 2007-2012 + + +n=length(G); %number of nodes +I=eye(n)~=0; %logical identity matrix +d=1; %path length +NPd=G; %number of paths of length |d| +NSPd=NPd; %number of shortest paths of length |d| +NSP=NSPd; NSP(I)=1; %number of shortest paths of any length +L=NSPd; L(I)=1; %length of shortest paths + +%calculate NSP and L +while find(NSPd,1) + d=d+1; + NPd=NPd*G; + NSPd=NPd.*(L==0); + NSP=NSP+NSPd; + L=L+d.*(NSPd~=0); +end +L(~L)=inf; L(I)=0; %L for disconnected vertices is inf +NSP(~NSP)=1; %NSP for disconnected vertices is 1 + +Gt=G.'; +DP=zeros(n); %vertex on vertex dependency +diam=d-1; %graph diameter + +%calculate DP +for d=diam:-1:2 + DPd1=(((L==d).*(1+DP)./NSP)*Gt).*((L==(d-1)).*NSP); + DP=DP + DPd1; %DPd1: dependencies on vertices |d-1| from source +end + +BC=sum(DP,1); %compute betweenness \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/betweenness_wei.m b/DefaultData/2019_03_03_BCT/betweenness_wei.m new file mode 100755 index 0000000..6ea3b01 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/betweenness_wei.m @@ -0,0 +1,76 @@ +function BC=betweenness_wei(G) +%BETWEENNESS_WEI Node betweenness centrality +% +% BC = betweenness_wei(L); +% +% Node betweenness centrality is the fraction of all shortest paths in +% the network that contain a given node. Nodes with high values of +% betweenness centrality participate in a large number of shortest paths. +% +% Input: L, Directed/undirected connection-length matrix. +% +% Output: BC, node betweenness centrality vector. +% +% Notes: +% The input matrix must be a connection-length matrix, typically +% obtained via a mapping from weight to length. For instance, in a +% weighted correlation network higher correlations are more naturally +% interpreted as shorter distances and the input matrix should +% consequently be some inverse of the connectivity matrix. +% Betweenness centrality may be normalised to the range [0,1] as +% BC/[(N-1)(N-2)], where N is the number of nodes in the network. +% +% Reference: Brandes (2001) J Math Sociol 25:163-177. +% +% +% Mika Rubinov, UNSW/U Cambridge, 2007-2012 + +n=length(G); +% E=find(G); G(E)=1./G(E); %invert weights +BC=zeros(n,1); %vertex betweenness + +for u=1:n + D=inf(1,n); D(u)=0; %distance from u + NP=zeros(1,n); NP(u)=1; %number of paths from u + S=true(1,n); %distance permanence (true is temporary) + P=false(n); %predecessors + Q=zeros(1,n); q=n; %order of non-increasing distance + + G1=G; + V=u; + while 1 + S(V)=0; %distance u->V is now permanent + G1(:,V)=0; %no in-edges as already shortest + for v=V + Q(q)=v; q=q-1; + W=find(G1(v,:)); %neighbours of v + for w=W + Duw=D(v)+G1(v,w); %path length to be tested + if Duww shorter than old + D(w)=Duw; + NP(w)=NP(v); %NP(u->w) = NP of new path + P(w,:)=0; + P(w,v)=1; %v is the only predecessor + elseif Duw==D(w) %if new u->w equal to old + NP(w)=NP(w)+NP(v); %NP(u->w) sum of old and new + P(w,v)=1; %v is also a predecessor + end + end + end + + minD=min(D(S)); + if isempty(minD), break %all nodes reached, or + elseif isinf(minD) %...some cannot be reached: + Q(1:q)=find(isinf(D)); break %...these are first-in-line + end + V=find(D==minD); + end + + DP=zeros(n,1); %dependency + for w=Q(1:n-1) + BC(w)=BC(w)+DP(w); + for v=find(P(w,:)) + DP(v)=DP(v)+(1+DP(w)).*NP(v)./NP(w); + end + end +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/breadth.m b/DefaultData/2019_03_03_BCT/breadth.m new file mode 100755 index 0000000..b1cc339 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/breadth.m @@ -0,0 +1,62 @@ +function [distance,branch] = breadth(CIJ,source) +%BREADTH Auxiliary function for breadthdist.m +% +% [distance,branch] = breadth(CIJ,source); +% +% Implementation of breadth-first search. +% +% Input: CIJ, binary (directed/undirected) connection matrix +% source, source vertex +% +% Outputs: distance, distance between 'source' and i'th vertex +% (0 for source vertex) +% branch, vertex that precedes i in the breadth-first search tree +% (-1 for source vertex) +% +% Notes: Breadth-first search tree does not contain all paths (or all +% shortest paths), but allows the determination of at least one path with +% minimum distance. The entire graph is explored, starting from source +% vertex 'source'. +% +% +% Olaf Sporns, Indiana University, 2002/2007/2008 + +N = size(CIJ,1); + +% colors: white, gray, black +white = 0; +gray = 1; +black = 2; + +% initialize colors +color = zeros(1,N); +% initialize distances +distance = inf*ones(1,N); +% initialize branches +branch = zeros(1,N); + +% start on vertex 'source' +color(source) = gray; +distance(source) = 0; +branch(source) = -1; +Q = source; + +% keep going until the entire graph is explored +while ~isempty(Q) + u = Q(1); + ns = find(CIJ(u,:)); + for v=ns +% this allows the 'source' distance to itself to be recorded + if (distance(v)==0) + distance(v) = distance(u)+1; + end; + if (color(v)==white) + color(v) = gray; + distance(v) = distance(u)+1; + branch(v) = u; + Q = [Q v]; %#ok + end; + end; + Q = Q(2:length(Q)); + color(u) = black; +end diff --git a/DefaultData/2019_03_03_BCT/breadthdist.m b/DefaultData/2019_03_03_BCT/breadthdist.m new file mode 100755 index 0000000..3d189f9 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/breadthdist.m @@ -0,0 +1,39 @@ +function [R,D] = breadthdist(CIJ) +%BREADTHDIST Reachability and distance matrices +% +% [R,D] = breadthdist(CIJ); +% +% The binary reachability matrix describes reachability between all pairs +% of nodes. An entry (u,v)=1 means that there exists a path from node u +% to node v; alternatively (u,v)=0. +% +% The distance matrix contains lengths of shortest paths between all +% pairs of nodes. An entry (u,v) represents the length of shortest path +% from node u to node v. The average shortest path length is the +% characteristic path length of the network. +% +% Input: CIJ, binary (directed/undirected) connection matrix +% +% Outputs: R, reachability matrix +% D, distance matrix +% +% Note: slower but less memory intensive than "reachdist.m". +% +% Algorithm: Breadth-first search. +% +% +% Olaf Sporns, Indiana University, 2002/2007/2008 + +N = size(CIJ,1); + +D = zeros(N); +for i=1:N + D(i,:) = breadth(CIJ,i); +end; + +% replace zeros with 'Inf's +D(D==0) = Inf; + +% construct R +R = double(D~=Inf); + diff --git a/DefaultData/2019_03_03_BCT/charpath.m b/DefaultData/2019_03_03_BCT/charpath.m new file mode 100755 index 0000000..0866997 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/charpath.m @@ -0,0 +1,79 @@ +function [lambda,efficiency,ecc,radius,diameter] = charpath(D,diagonal_dist,infinite_dist) +%CHARPATH Characteristic path length, global efficiency and related statistics +% +% lambda = charpath(D); +% lambda = charpath(D); +% [lambda,efficiency] = charpath(D); +% [lambda,efficiency,ecc,radius,diameter] = charpath(D,diagonal_dist,infinite_dist); +% +% The network characteristic path length is the average shortest path +% length between all pairs of nodes in the network. The global efficiency +% is the average inverse shortest path length in the network. The nodal +% eccentricity is the maximal path length between a node and any other +% node in the network. The radius is the minimal eccentricity, and the +% diameter is the maximal eccentricity. +% +% Input: D, distance matrix +% diagonal_dist optional argument +% include distances on the main diagonal +% (default: diagonal_dist=0) +% infinite_dist optional argument +% include infinite distances in calculation +% (default: infinite_dist=1) +% +% Outputs: lambda, network characteristic path length +% efficiency, network global efficiency +% ecc, nodal eccentricity +% radius, network radius +% diameter, network diameter +% +% Notes: +% The input distance matrix may be obtained with any of the distance +% functions, e.g. distance_bin, distance_wei. +% Characteristic path length is defined here as the mean shortest +% path length between all pairs of nodes, for consistency with common +% usage. Note that characteristic path length is also defined as the +% median of the mean shortest path length from each node to all other +% nodes. +% Infinitely long paths (i.e. paths between disconnected nodes) are +% included in computations by default. This behavior may be modified with +% via the infinite_dist argument. +% +% +% Olaf Sporns, Indiana University, 2002/2007/2008 +% Mika Rubinov, U Cambridge, 2010/2015 + +% Modification history +% 2002: original (OS) +% 2010: incorporation of global efficiency (MR) +% 2015: exclusion of diagonal weights by default (MR) +% 2016: inclusion of infinite distances by default (MR) + +n = size(D,1); +if any(any(isnan(D))) + error('The distance matrix must not contain NaN values'); +end +if ~exist('diagonal_dist','var') || ~diagonal_dist || isempty(diagonal_dist) + D(1:n+1:end) = NaN; % set diagonal distance to NaN +end +if exist('infinite_dist','var') && ~infinite_dist + D(isinf(D)) = NaN; % ignore infinite path lengths +end + +Dv = D(~isnan(D)); % get non-NaN indices of D + +% Mean of entries of D(G) +lambda = mean(Dv); + +% Efficiency: mean of inverse entries of D(G) +efficiency = mean(1./Dv); + +% Eccentricity for each vertex +ecc = nanmax(D,[],2); + +% Radius of graph +radius = min(ecc); + +% Diameter of graph +diameter = max(ecc); + diff --git a/DefaultData/2019_03_03_BCT/clique_communities.m b/DefaultData/2019_03_03_BCT/clique_communities.m new file mode 100755 index 0000000..76aa90e --- /dev/null +++ b/DefaultData/2019_03_03_BCT/clique_communities.m @@ -0,0 +1,102 @@ +function M = clique_communities(A, cq_thr) +% CLIQUE_COMMUNITIES Overlapping community structure via clique percolation +% +% M = clique_communities(A, cq_thr) +% +% The optimal community structure is a subdivision of the network into +% groups of nodes which have a high number of within-group connections +% and a low number of between group connections. +% +% This algorithm uncovers overlapping community structure in binary +% undirected networks via the clique percolation method. +% +% Inputs: +% A, Binary undirected connection matrix. +% +% cq_thr, Clique size threshold (integer). Larger clique size +% thresholds potentially result in larger communities. +% +% Output: +% M, Overlapping community-affiliation matrix +% Binary matrix of size CxN [communities x nodes] +% +% Algorithms: +% Bron–Kerbosch algorithm for detection of maximal cliques. +% Dulmage-Mendelsohn decomposition for detection of components +% (implemented in get_components.m) +% +% +% Note: This algorithm can be slow and memory intensive in large +% matrices. The algorithm requires the function get_components.m +% +% Reference: Palla et al. (2005) Nature 435, 814-818. +% +% Mika Rubinov, Janelia HHMI, 2017 + +if ~isequal(A, A.') + error('A must be undirected.') +end +if ~isequal(size(A, 1), size(A, 2)) + error('A must be square.') +end +if ~issparse(A) + A = sparse(A); +end +if ~islogical(A) + A = logical(A); +end + +n = length(A); % number of nodes +A(1:n+1:end) = 0; % clear diagonal +MQ = maximal_cliques(A, n); % get maximal cliques +Cq = double(cell2mat(MQ)).'; % convert to matrix +Cq = Cq(sum(Cq, 2) >= cq_thr, :); % remove subthreshold cliques +Ov = Cq * Cq.'; % compute clique overlap +Ov_thr = (Ov >= cq_thr - 1); % keep percolating cliques + +Cq_components = get_components(Ov_thr); % find components + +m = max(Cq_components); % get number of components +M = zeros(m, n); % collect communities +for i = 1:m + M(i, any( Cq(Cq_components==i, :), 1)) = 1; +end + +end + +function MQ = maximal_cliques(A, n) % Bron-Kerbosch algorithm + +MQ = cell(1, 1000*n); + +R = false(n, 1); %current +P = true(n, 1); %prospective +X = false(n, 1); %processed +q = 0; + +BK(R, P, X); + + function BK(R, P, X) + if ~any(P | X) + q = q + 1; + MQ{q} = R; + else + U_p = find(any([P X], 2)); + [~, idx] = max(A(:,U_p).' * double(P)); + u_p = U_p(idx); + + U = find(all([P ~A(:,u_p)], 2)).'; + for u = U + Nu = A(:,u); + P(u) = 0; + Rnew = R; Rnew(u) = 1; + Pnew = all([P Nu],2); + Xnew = all([X Nu],2); + BK(Rnew, Pnew, Xnew) + X(u) = 1; + end + end + end + +MQ=MQ(1:q); + +end diff --git a/DefaultData/2019_03_03_BCT/clustering_coef_bd.m b/DefaultData/2019_03_03_BCT/clustering_coef_bd.m new file mode 100755 index 0000000..8506341 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/clustering_coef_bd.m @@ -0,0 +1,35 @@ +function C=clustering_coef_bd(A) +%CLUSTERING_COEF_BD Clustering coefficient +% +% C = clustering_coef_bd(A); +% +% The clustering coefficient is the fraction of triangles around a node +% (equiv. the fraction of node's neighbors that are neighbors of each other). +% +% Input: A, binary directed connection matrix +% +% Output: C, clustering coefficient vector +% +% Reference: Fagiolo (2007) Phys Rev E 76:026107. +% +% +% Mika Rubinov, UNSW, 2007-2010 + +%Methodological note: In directed graphs, 3 nodes generate up to 8 +%triangles (2*2*2 edges). The number of existing triangles is the main +%diagonal of S^3/2. The number of all (in or out) neighbour pairs is +%K(K-1)/2. Each neighbour pair may generate two triangles. "False pairs" +%are i<->j edge pairs (these do not generate triangles). The number of +%false pairs is the main diagonal of A^2. +%Thus the maximum possible number of triangles = +% = (2 edges)*([ALL PAIRS] - [FALSE PAIRS]) +% = 2 * (K(K-1)/2 - diag(A^2)) +% = K(K-1) - 2(diag(A^2)) + +S=A+A.'; %symmetrized input graph +K=sum(S,2); %total degree (in + out) +cyc3=diag(S^3)/2; %number of 3-cycles (ie. directed triangles) +K(cyc3==0)=inf; %if no 3-cycles exist, make C=0 (via K=inf) +CYC3=K.*(K-1)-2*diag(A^2); %number of all possible 3-cycles +C=cyc3./CYC3; %clustering coefficient + diff --git a/DefaultData/2019_03_03_BCT/clustering_coef_bu.m b/DefaultData/2019_03_03_BCT/clustering_coef_bu.m new file mode 100755 index 0000000..ae492be --- /dev/null +++ b/DefaultData/2019_03_03_BCT/clustering_coef_bu.m @@ -0,0 +1,28 @@ +function C=clustering_coef_bu(G) +%CLUSTERING_COEF_BU Clustering coefficient +% +% C = clustering_coef_bu(A); +% +% The clustering coefficient is the fraction of triangles around a node +% (equiv. the fraction of node's neighbors that are neighbors of each other). +% +% Input: A, binary undirected connection matrix +% +% Output: C, clustering coefficient vector +% +% Reference: Watts and Strogatz (1998) Nature 393:440-442. +% +% +% Mika Rubinov, UNSW, 2007-2010 + +n=length(G); +C=zeros(n,1); + +for u=1:n + V=find(G(u,:)); + k=length(V); + if k>=2 %degree must be at least 2 + S=G(V,V); + C(u)=sum(S(:))/(k^2-k); + end +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/clustering_coef_wd.m b/DefaultData/2019_03_03_BCT/clustering_coef_wd.m new file mode 100755 index 0000000..059c29e --- /dev/null +++ b/DefaultData/2019_03_03_BCT/clustering_coef_wd.m @@ -0,0 +1,42 @@ +function C=clustering_coef_wd(W) +%CLUSTERING_COEF_WD Clustering coefficient +% +% C = clustering_coef_wd(W); +% +% The weighted clustering coefficient is the average "intensity" +% (geometric mean) of all triangles associated with each node. +% +% Input: W, weighted directed connection matrix +% (all weights must be between 0 and 1) +% +% Output: C, clustering coefficient vector +% +% Reference: Fagiolo (2007) Phys Rev E 76:026107. +% +% Note: All weights must be between 0 and 1. +% This may be achieved using the weight_conversion.m function, +% W_nrm = weight_conversion(W, 'normalize'); +% +% Mika Rubinov, UNSW/U Cambridge, 2007-2015 + +% Modification history: +% 2007: original +% 2015: expanded documentation + + +% Methodological note (also see clustering_coef_bd) +% The weighted modification is as follows: +% - The numerator: adjacency matrix is replaced with weights matrix ^ 1/3 +% - The denominator: no changes from the binary version +% +% The above reduces to symmetric and/or binary versions of the clustering +% coefficient for respective graphs. + +A=W~=0; %adjacency matrix +S=W.^(1/3)+(W.').^(1/3); %symmetrized weights matrix ^1/3 +K=sum(A+A.',2); %total degree (in + out) +cyc3=diag(S^3)/2; %number of 3-cycles (ie. directed triangles) +K(cyc3==0)=inf; %if no 3-cycles exist, make C=0 (via K=inf) +CYC3=K.*(K-1)-2*diag(A^2); %number of all possible 3-cycles +C=cyc3./CYC3; %clustering coefficient + diff --git a/DefaultData/2019_03_03_BCT/clustering_coef_wu.m b/DefaultData/2019_03_03_BCT/clustering_coef_wu.m new file mode 100755 index 0000000..bd18514 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/clustering_coef_wu.m @@ -0,0 +1,30 @@ +function C=clustering_coef_wu(W) +%CLUSTERING_COEF_WU Clustering coefficient +% +% C = clustering_coef_wu(W); +% +% The weighted clustering coefficient is the average "intensity" +% (geometric mean) of all triangles associated with each node. +% +% Input: W, weighted undirected connection matrix +% (all weights must be between 0 and 1) +% +% Output: C, clustering coefficient vector +% +% Note: All weights must be between 0 and 1. +% This may be achieved using the weight_conversion.m function, +% W_nrm = weight_conversion(W, 'normalize'); +% +% Reference: Onnela et al. (2005) Phys Rev E 71:065103 +% +% +% Mika Rubinov, UNSW/U Cambridge, 2007-2015 + +% Modification history: +% 2007: original +% 2015: expanded documentation + +K=sum(W~=0,2); +cyc3=diag((W.^(1/3))^3); +K(cyc3==0)=inf; %if no 3-cycles exist, make C=0 (via K=inf) +C=cyc3./(K.*(K-1)); %clustering coefficient diff --git a/DefaultData/2019_03_03_BCT/clustering_coef_wu_sign.m b/DefaultData/2019_03_03_BCT/clustering_coef_wu_sign.m new file mode 100755 index 0000000..00bc691 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/clustering_coef_wu_sign.m @@ -0,0 +1,136 @@ +function [C_pos,C_neg,Ctot_pos,Ctot_neg] = clustering_coef_wu_sign(W,coef_type) +%CLUSTERING_COEF_WU_SIGN Multiple generalizations of the clustering coefficient +% +% [C_pos,C_neg,Ctot_pos,Ctot_neg] = clustering_coef_wu_sign(W,coef_type); +% +% The weighted clustering coefficient is the average weight or intensity +% of all triangles associated with each node. +% +% Inputs: +% W, +% Weighted undirected connection matrix +% +% corr_type, +% Desired type of clustering coefficient. +% Options: +% 1, (default) Onnela et al. formula, used in original +% clustering_coef_wu.m. Computed separately for positive & +% negative weights. +% 2, Zhang & Horvath formula, similar to Onnela formula except +% denominator of Onnela formula relies on binarizing the +% network whereas this denominator is based on weight value, +% which reduces the sensitivity of this measure to the +% weights directly connected to the node of interest. +% Computed separately for positive & negative weights. +% 3, Constantini & Perugini's generalization of the Zhang & +% Horvath formula. This formula takes both positive & +% negative weights into account simultaneously, & is +% particularly sensitive to non-redundancy in path +% information based on sign (i.e., when two weights are +% positive & one negative, or all three are negative, both of +% which indicate that the weight of the third path is not +% redundant information). Produces only one value. +% +% +% Outputs: +% C_pos/C_neg, +% Clustering coefficient vector for positive/negative weights. +% For the third option, only one vector is outputted (as C_pos). +% Ctot_pos/Ctot_neg, +% Mean clustering coefficient for positive and negative weights. +% +% References: +% Onnela et al. (2005) Phys Rev E 71:065103 +% Zhang & Horvath (2005) Stat Appl Genet Mol Biol 41:1544-6115 +% Costantini & Perugini (2014) PLOS ONE 9:e88669 +% +% +% Contributor: Jeff Spielberg, Boston University, 2014-2015 +% (script based on clustering_coef_wu.m) + +% +% Modification History: +% May 2014: Added computation of pos & neg weights separately & +% computation of mean coefficient (Jeff Spielberg) +% May 2015: Added computation of Zhang & Horvath and Constantini & +% Perugini formulas (Jeff Spielberg) +% May 2016: Bugfix in computation of the denominator of the Costantini & +% Perugini (flag 3) version (Chiara Pintossi) + +if ~exist('coef_type','var') + coef_type = 1; +end + +n = length(W); %number of nodes +W(1:n+1:end) = 0; + +switch coef_type + case 1 + W_pos = W.*(W>0); + K_pos = sum(W_pos~=0,2); + cyc3_pos = diag((W_pos.^(1/3))^3); + K_pos(cyc3_pos == 0) = inf; %if no 3-cycles exist, make C=0 (via K=inf) + C_pos = cyc3_pos./(K_pos.*(K_pos-1)); %clustering coefficient + Ctot_pos = mean(C_pos); + + W_neg = -W.*(W<0); + K_neg = sum(W_neg~=0,2); + cyc3_neg = diag((W_neg.^(1/3))^3); + K_neg(cyc3_neg == 0) = inf; %if no 3-cycles exist, make C=0 (via K=inf) + C_neg = cyc3_neg./(K_neg.*(K_neg-1)); %clustering coefficient + Ctot_neg = mean(C_neg); + case 2 + W_pos = W.*(W>0); + cyc3_pos = zeros(n,1); + cyc2_pos = zeros(n,1); + for i = 1:n + for j = 1:n + for q = 1:n + cyc3_pos(i) = cyc3_pos(i)+(W_pos(j,i)*W_pos(i,q)*W_pos(j,q)); + if j~=q + cyc2_pos(i) = cyc2_pos(i)+(W_pos(j,i)*W_pos(i,q)); + end + end + end + end + cyc2_pos(cyc3_pos == 0) = inf; %if no 3-cycles exist, make C=0 (via K=inf) + C_pos = cyc3_pos./cyc2_pos; %clustering coefficient + Ctot_pos = mean(C_pos); + + W_neg = -W.*(W<0); + cyc3_neg = zeros(n,1); + cyc2_neg = zeros(n,1); + for i = 1:n + for j = 1:n + for q = 1:n + cyc3_neg(i) = cyc3_neg(i)+(W_neg(j,i)*W_neg(i,q)*W_neg(j,q)); + if j~=q + cyc2_neg(i) = cyc2_neg(i)+(W_neg(j,i)*W_neg(i,q)); + end + end + end + end + cyc2_neg(cyc3_neg == 0) = inf; %if no 3-cycles exist, make C=0 (via K=inf) + C_neg = cyc3_neg./cyc2_neg; %clustering coefficient + Ctot_neg = mean(C_neg); + case 3 + cyc3 = zeros(n,1); + cyc2 = zeros(n,1); + + for i = 1:n + for j = 1:n + for q = 1:n + cyc3(i) = cyc3(i)+(W(j,i)*W(i,q)*W(j,q)); + if j~=q + cyc2(i) = cyc2(i)+abs(W(j,i)*W(i,q)); + end + end + end + end + + cyc2(cyc3 == 0) = inf; %if no 3-cycles exist, make C=0 (via K=inf) + C_pos = cyc3./cyc2; %clustering coefficient + Ctot_pos = mean(C_pos); + C_neg = nan(size(C_pos)); + Ctot_neg = nan(size(Ctot_pos)); +end diff --git a/DefaultData/2019_03_03_BCT/community_louvain.m b/DefaultData/2019_03_03_BCT/community_louvain.m new file mode 100755 index 0000000..6fb367f --- /dev/null +++ b/DefaultData/2019_03_03_BCT/community_louvain.m @@ -0,0 +1,198 @@ +function [M,Q]=community_louvain(W,gamma,M0,B) +%COMMUNITY_LOUVAIN Optimal community structure +% +% M = community_louvain(W); +% [M,Q] = community_louvain(W,gamma); +% [M,Q] = community_louvain(W,gamma,M0); +% [M,Q] = community_louvain(W,gamma,M0,'potts'); +% [M,Q] = community_louvain(W,gamma,M0,'negative_asym'); +% [M,Q] = community_louvain(W,[],[],B); +% +% The optimal community structure is a subdivision of the network into +% nonoverlapping groups of nodes which maximizes the number of within- +% group edges, and minimizes the number of between-group edges. +% +% This function is a fast and accurate multi-iterative generalization of +% the Louvain community detection algorithm. This function subsumes and +% improves upon, +% modularity_louvain_und.m, modularity_finetune_und.m, +% modularity_louvain_dir.m, modularity_finetune_dir.m, +% modularity_louvain_und_sign.m +% and additionally allows to optimize other objective functions (includes +% built-in Potts-model Hamiltonian, allows for custom objective-function +% matrices). +% +% Inputs: +% W, +% directed/undirected weighted/binary connection matrix with +% positive and possibly negative weights. +% gamma, +% resolution parameter (optional) +% gamma>1, detects smaller modules +% 0<=gamma<1, detects larger modules +% gamma=1, classic modularity (default) +% M0, +% initial community affiliation vector (optional) +% B, +% objective-function type or custom objective matrix (optional) +% 'modularity', modularity (default) +% 'potts', Potts-model Hamiltonian (for binary networks) +% 'negative_sym', symmetric treatment of negative weights +% 'negative_asym', asymmetric treatment of negative weights +% B, custom objective-function matrix +% +% Note: see Rubinov and Sporns (2011) for a discussion of +% symmetric vs. asymmetric treatment of negative weights. +% +% Outputs: +% M, +% community affiliation vector +% Q, +% optimized community-structure statistic (modularity by default) +% +% Example: +% % Iterative community finetuning. +% % W is the input connection matrix. +% n = size(W,1); % number of nodes +% M = 1:n; % initial community affiliations +% Q0 = -1; Q1 = 0; % initialize modularity values +% while Q1-Q0>1e-5; % while modularity increases +% Q0 = Q1; % perform community detection +% [M, Q1] = community_louvain(W, [], M); +% end +% +% References: +% Blondel et al. (2008) J. Stat. Mech. P10008. +% Reichardt and Bornholdt (2006) Phys. Rev. E 74, 016110. +% Ronhovde and Nussinov (2008) Phys. Rev. E 80, 016109 +% Sun et al. (2008) Europhysics Lett 86, 28004. +% Rubinov and Sporns (2011) Neuroimage 56:2068-79. +% +% Mika Rubinov, U Cambridge 2015-2016 + +% Modification history +% 2015: Original +% 2016: Included generalization for negative weights. +% Enforced binary network input for Potts-model Hamiltonian. +% Streamlined code and expanded documentation. + +W=double(W); % convert to double format +n=length(W); % get number of nodes +s=sum(sum(W)); % get sum of edges + +if ~exist('B','var') || isempty(B) + type_B = 'modularity'; +elseif ischar(B) + type_B = B; +else + type_B = 0; + if exist('gamma','var') && ~isempty(gamma) + warning('Value of gamma is ignored in generalized mode.') + end +end +if ~exist('gamma','var') || isempty(gamma) + gamma = 1; +end + +if strcmp(type_B,'negative_sym') || strcmp(type_B,'negative_asym') + W0 = W.*(W>0); %positive weights matrix + s0 = sum(sum(W0)); %weight of positive links + B0 = W0-gamma*(sum(W0,2)*sum(W0,1))/s0; %positive modularity + + W1 =-W.*(W<0); %negative weights matrix + s1 = sum(sum(W1)); %weight of negative links + if s1 %negative modularity + B1 = W1-gamma*(sum(W1,2)*sum(W1,1))/s1; + else + B1 = 0; + end +elseif min(min(W))<-1e-10 + err_string = [ + 'The input connection matrix contains negative weights.\nSpecify ' ... + '''negative_sym'' or ''negative_asym'' objective-function types.']; + error(sprintf(err_string)) %#ok +end +if strcmp(type_B,'potts') && any(any(W ~= logical(W))) + error('Potts-model Hamiltonian requires a binary W.') +end + +if type_B + switch type_B + case 'modularity'; B = (W-gamma*(sum(W,2)*sum(W,1))/s)/s; + case 'potts'; B = W-gamma*(~W); + case 'negative_sym'; B = B0/(s0+s1) - B1/(s0+s1); + case 'negative_asym'; B = B0/s0 - B1/(s0+s1); + otherwise; error('Unknown objective function.'); + end +else % custom objective function matrix as input + B = double(B); + if ~isequal(size(W),size(B)) + error('W and B must have the same size.') + end +end +if ~exist('M0','var') || isempty(M0) + M0=1:n; +elseif numel(M0)~=n + error('M0 must contain n elements.') +end + +[~,~,Mb] = unique(M0); +M = Mb; + +B = (B+B.')/2; % symmetrize modularity matrix +Hnm=zeros(n,n); % node-to-module degree +for m=1:max(Mb) % loop over modules + Hnm(:,m)=sum(B(:,Mb==m),2); +end + +Q0 = -inf; +Q = sum(B(bsxfun(@eq,M0,M0.'))); % compute modularity +first_iteration = true; +while Q-Q0>1e-10 + flag = true; % flag for within-hierarchy search + while flag + flag = false; + for u=randperm(n) % loop over all nodes in random order + ma = Mb(u); % current module of u + dQ = Hnm(u,:) - Hnm(u,ma) + B(u,u); + dQ(ma) = 0; % (line above) algorithm condition + + [max_dQ,mb] = max(dQ); % maximal increase in modularity and corresponding module + if max_dQ>1e-10 % if maximal increase is positive + flag = true; + Mb(u) = mb; % reassign module + + Hnm(:,mb) = Hnm(:,mb)+B(:,u); % change node-to-module strengths + Hnm(:,ma) = Hnm(:,ma)-B(:,u); + end + end + end + [~,~,Mb] = unique(Mb); % new module assignments + + M0 = M; + if first_iteration + M=Mb; + first_iteration=false; + else + for u=1:n % loop through initial module assignments + M(M0==u)=Mb(u); % assign new modules + end + end + + n=max(Mb); % new number of modules + B1=zeros(n); % new weighted matrix + for u=1:n + for v=u:n + bm=sum(sum(B(Mb==u,Mb==v))); % pool weights of nodes in same module + B1(u,v)=bm; + B1(v,u)=bm; + end + end + B=B1; + + Mb=1:n; % initial module assignments + Hnm=B; % node-to-module strength + + Q0=Q; + Q=trace(B); % compute modularity +end diff --git a/DefaultData/2019_03_03_BCT/consensus_und.m b/DefaultData/2019_03_03_BCT/consensus_und.m new file mode 100755 index 0000000..089b1e0 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/consensus_und.m @@ -0,0 +1,96 @@ +function ciu = consensus_und(d,tau,reps) +% CONSENSUS_UND Consensus clustering +% +% CIU = CONSENSUS(D,TAU,REPS) seeks a consensus partition of the +% agreement matrix D. The algorithm used here is almost identical to the +% one introduced in Lancichinetti & Fortunato (2012): The agreement +% matrix D is thresholded at a level TAU to remove an weak elements. The +% resulting matrix is then partitions REPS number of times using the +% Louvain algorithm (in principle, any clustering algorithm that can +% handle weighted matrixes is a suitable alternative to the Louvain +% algorithm and can be substituted in its place). This clustering +% produces a set of partitions from which a new agreement is built. If +% the partitions have not converged to a single representative partition, +% the above process repeats itself, starting with the newly built +% agreement matrix. +% +% NOTE: In this implementation, the elements of the agreement matrix must +% be converted into probabilities. +% +% NOTE: This implementation is slightly different from the original +% algorithm proposed by Lanchichinetti & Fortunato. In its original +% version, if the thresholding produces singleton communities, those +% nodes are reconnected to the network. Here, we leave any singleton +% communities disconnected. +% +% Inputs: D, agreement matrix with entries between 0 and 1 +% denoting the probability of finding node i in the +% same cluster as node j +% TAU, threshold which controls the resolution of the +% reclustering +% REPS, number of times that the clustering algorithm is +% reapplied +% +% Outputs: CIU, consensus partition +% +% References: Lancichinetti & Fortunato (2012). Consensus clustering in +% complex networks. Scientific Reports 2, Article number: 336. +% +% Richard Betzel, Indiana University, 2012 +% +% modified on 3/2014 to include "unique_partitions" + +n = length(d); flg = 1; +while flg == 1 + + flg = 0; + dt = d.*(d >= tau).*~eye(n); + if nnz(dt) == 0 + ciu = (1:n)'; + else + ci = zeros(n,reps); + for iter = 1:reps + ci(:,iter) = community_louvain(dt); + end + ci = relabel_partitions(ci); + ciu = unique_partitions(ci); + nu = size(ciu,2); + if nu > 1 + flg = 1; + d = agreement(ci)./reps; + end + end + +end + +function cinew = relabel_partitions(ci) +[n,m] = size(ci); +cinew = zeros(n,m); +for i = 1:m + c = ci(:,i); + d = zeros(size(c)); + count = 0; + while sum(d ~= 0) < n + count = count + 1; + ind = find(c,1,'first'); + tgt = c(ind); + rep = c == tgt; + d(rep) = count; + c(rep) = 0; + end + cinew(:,i) = d; +end + +function ciu = unique_partitions(ci) +ci = relabel_partitions(ci); +ciu = []; +count = 0; +c = 1:size(ci,2); +while ~isempty(ci) + count = count + 1; + tgt = ci(:,1); + ciu = [ciu,tgt]; %#ok + dff = sum(abs(bsxfun(@minus,ci,tgt))) == 0; + ci(:,dff) = []; + c(dff) = []; +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/core_periphery_dir.m b/DefaultData/2019_03_03_BCT/core_periphery_dir.m new file mode 100755 index 0000000..c607be4 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/core_periphery_dir.m @@ -0,0 +1,88 @@ +function [C, q]=core_periphery_dir(W,gamm,C) +%CORE_PERIPHERY_DIR Core/periphery structure and core-ness statistic +% +% C = core_periphery_dir(W) +% [C,q] = core_periphery_dir(W,gamm,C0) +% +% The optimal core/periphery subdivision is a partition of the network +% into two non-overlapping groups of nodes, a core group and a periphery +% group, in a way that maximizes the number/weight of within core-group +% edges, and minimizes the number/weight of within periphery-group edges. +% +% The core-ness is a statistic which quantifies the goodness of the +% optimal core/periphery subdivision. +% +% Input: W directed (weighted or binary) connection matrix. +% gamma, core-ness resolution parameter (optional) +% gamma>1 detects small core/large periphery +% 0<=gamma<1 detects large core/small periphery +% default is gamma=1 +% +% Outputs: C, binary vector of optimal core structure +% C = 1 represents nodes in the core +% C = 0 represents nodes in the periphery +% q, maximized core-ness statistic +% +% Algorithm: A version of Kernighan-Lin algorithm for graph partitioning +% used in community detection (Newman, 2006) applied to optimize a +% core-structure objective described in Borgatti and Everett (2000). +% +% Reference: Borgatti and Everett (2000) Soc Networks 21:375–395. +% Newman (2006) Phys Rev E 74:036104, PNAS 23:8577-8582. +% Rubinov, Ypma et al. (2015) PNAS 112:10032-7 +% +% 2015, Mika Rubinov, U Cambridge + +n = length(W); % number of nodes +W = double(W); % convert from logical +W(1:n+1:end) = 0; % clear diagonal +if ~exist('gamm','var') + gamm = 1; +end +if ~exist('C','var') + C = (rand(1,n)<0.5); +else + C = logical(reshape(C,1,n)); +end + +% Methodological note: cf. community detection, the core-detection +% null model is not corrected for degree (to enable detection of hubs). +s = sum(W(:)); +p = mean(W(:)); +b = W - gamm*p; +B = (b+b.')/(2*s); % directed core-ness matrix +q = sum(sum(B(C,C))) - sum(sum(B(~C,~C))); % core-ness statistic + +f=1; % loop flag +while f + f=0; + Idx = 1:n; % initial node indices + Ct = C; + while any(Idx) + Qt = zeros(1,n); % check swaps of node indices + q0 = sum(sum(B(Ct,Ct))) - sum(sum(B(~Ct,~Ct))); + Qt( Ct) = q0 - 2*sum(B( Ct, :),2); + Qt(~Ct) = q0 + 2*sum(B(~Ct, :),2); + + %%% verification that the above update is equivalent to: + % for u=Idx + % Ct(u) = ~Ct(u); + % Qt(u) = sum(sum(B(Ct,Ct))) - sum(sum(B(~Ct,~Ct))); + % Ct(u) = ~Ct(u); + % end + + max_Qt = max(Qt(Idx)); % make swap with maximal + u = find(abs(Qt(Idx)-max_Qt)<1e-10);% increase in core-ness + u = u(randi(numel(u))); + Ct(Idx(u)) = ~Ct(Idx(u)); + Idx(u)=[]; % remove index from consideration + + if max_Qt-q>1e-10 % recompute core-ness statistic + f = 1; + C = Ct; + q = sum(sum(B(C,C))) - sum(sum(B(~C,~C))); + end + end +end + +q = sum(sum(B(C,C))) - sum(sum(B(~C,~C))); % return core-ness statistic \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/cycprob.m b/DefaultData/2019_03_03_BCT/cycprob.m new file mode 100755 index 0000000..0202231 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/cycprob.m @@ -0,0 +1,45 @@ +function [fcyc,pcyc] = cycprob(Pq) +% CYCPROB Cycle probability +% +% [fcyc,pcyc] = cycprob(Pq); +% +% Cycles are paths which begin and end at the same node. Cycle +% probability for path length d, is the fraction of all paths of length +% d-1 that may be extended to form cycles of length d. +% +% Input: Pq, 3D matrix, with Pq(i,j,q) = number of paths from +% 'i' to 'j' of length 'q' (produced by 'findpaths') +% +% Outputs: fcyc, fraction of all paths that are cycles for each path +% length 'q'. +% pcyc, probability that a non-cyclic path of length 'q-1' +% can be extended to form a cycle of length 'q', for +% each path length 'q', +% +% +% Olaf Sporns, Indiana University, 2002/2007/2008 + + +% Note: fcyc(1) must be zero, as there cannot be cycles of length one. +fcyc = zeros(1,size(Pq,3)); +for q=1:size(Pq,3) + if(sum(sum(Pq(:,:,q)))>0) + fcyc(q) = sum(diag(Pq(:,:,q)))/sum(sum(Pq(:,:,q))); + else + fcyc(q) = 0; + end; +end; + +% Note: pcyc(1) is not defined (set to zero). +% Note: pcyc(2) is equal to the fraction of reciprocal connections, +% 'frecip', delivered by 'reciprocal.m'. +% Note: there are no non-cyclic paths of length N and no cycles of length N+1 +pcyc = zeros(1,size(Pq,3)); +for q=2:size(Pq,3) + if((sum(sum(Pq(:,:,q-1)))-sum(diag(Pq(:,:,q-1))))>0) + pcyc(q) = sum(diag(Pq(:,:,q)))/... + (sum(sum(Pq(:,:,q-1)))-sum(diag(Pq(:,:,q-1)))); + else + pcyc(q) = 0; + end; +end; diff --git a/DefaultData/2019_03_03_BCT/data_and_demos/Coactivation_matrix.mat b/DefaultData/2019_03_03_BCT/data_and_demos/Coactivation_matrix.mat new file mode 100755 index 0000000..a01e31b Binary files /dev/null and b/DefaultData/2019_03_03_BCT/data_and_demos/Coactivation_matrix.mat differ diff --git a/DefaultData/2019_03_03_BCT/data_and_demos/GroupAverage_rsfMRI_matrix.mat b/DefaultData/2019_03_03_BCT/data_and_demos/GroupAverage_rsfMRI_matrix.mat new file mode 100755 index 0000000..47db07b Binary files /dev/null and b/DefaultData/2019_03_03_BCT/data_and_demos/GroupAverage_rsfMRI_matrix.mat differ diff --git a/DefaultData/2019_03_03_BCT/data_and_demos/cat.mat b/DefaultData/2019_03_03_BCT/data_and_demos/cat.mat new file mode 100755 index 0000000..b0a7716 Binary files /dev/null and b/DefaultData/2019_03_03_BCT/data_and_demos/cat.mat differ diff --git a/DefaultData/2019_03_03_BCT/data_and_demos/demo_efficiency_measures.m b/DefaultData/2019_03_03_BCT/data_and_demos/demo_efficiency_measures.m new file mode 100755 index 0000000..c6493d2 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/data_and_demos/demo_efficiency_measures.m @@ -0,0 +1,116 @@ +% This script loads 7 unweighted, undirected adjacency matrices +% corresponding to a clique, chain, ring, 1D lattice, star, rich-club, and +% bi-modular toy networks (all graphs have 50 nodes). The following +% efficiency measures are computed for each graph: +% +% - prob_SPL: probability of one particle traveling through shortest-paths +% - Erout: efficiency of routing -> based on shortest-paths +% - Ediff: efficiency of diffusion -> based on mean-first-passage-times +% - Eres: efficiency of resources -> based on number of particles +% necessary so that at least one particle taking shortest-paths with +% certain probability (lambda). +% +% If you are using this efficiency package for your research, plase kindly +% cite the paper: +% +% "Exploring the Morphospace of Communication Efficiency in Complex +% Networks" Goñi J, Avena-Koenigsberger A, Velez de Mendizabal N, van den +% Heuvel M, Betzel RF and Sporns O. PLoS ONE. 2013 +% +% These examples and results correspond to Table 1 in the paper. +% +% Joaquin Goñi and Andrea Avena-Koenigsberger, IU Bloomington, 2012 + +close all; +clear all; +clc; + +load demo_efficiency_measures_data.mat; % 7 adjacency matrices corresponding to the examples shown in Table 1 are loaded. +lambda = 0.5; % this parameter is an input for the computation of Eres. + +% run and display efficiency measures for the 7 graphs +disp([' prob_SPL ',' Erout ',' Ediff ',' Eres ']) + +fprintf('----- clique ----- \n') +adj = clique; +N = size(adj,1); +EYE = logical(eye(N,N)); +SPL = distance_wei_floyd(adj); +Erout = rout_efficiency(adj); +Ediff = diffusion_efficiency(adj); +[Eres,prob_SPL] = resource_efficiency_bin(adj,lambda,SPL); +prob_SPL = mean(prob_SPL(~EYE)); +Eres = mean(Eres(~EYE)); +disp([prob_SPL,Erout,Ediff,Eres]) + +fprintf('----- chain ----- \n') +adj = chain; +N = size(adj,1); +EYE = logical(eye(N,N)); +SPL = distance_wei_floyd(adj); +Erout = rout_efficiency(adj); +Ediff = diffusion_efficiency(adj); +[Eres,prob_SPL] = resource_efficiency_bin(adj,lambda,SPL); +prob_SPL = mean(prob_SPL(~EYE)); +Eres = mean(Eres(~EYE)); +disp([prob_SPL,Erout,Ediff,Eres]) + +fprintf('----- ring ----- \n') +adj = ring; +N = size(adj,1); +EYE = logical(eye(N,N)); +SPL = distance_wei_floyd(adj); +Erout = rout_efficiency(adj); +Ediff = diffusion_efficiency(adj); +[Eres,prob_SPL] = resource_efficiency_bin(adj,lambda,SPL); +prob_SPL = mean(prob_SPL(~EYE)); +Eres = mean(Eres(~EYE)); +disp([prob_SPL,Erout,Ediff,Eres]) + +fprintf('----- lattice1D ----- \n') +adj = lattice1D; +N = size(adj,1); +EYE = logical(eye(N,N)); +SPL = distance_wei_floyd(adj); +Erout = rout_efficiency(adj); +Ediff = diffusion_efficiency(adj); +[Eres,prob_SPL] = resource_efficiency_bin(adj,lambda,SPL); +prob_SPL = mean(prob_SPL(~EYE)); +Eres = mean(Eres(~EYE)); +disp([prob_SPL,Erout,Ediff,Eres]) + +fprintf('----- star ----- \n') +adj = star; +N = size(adj,1); +EYE = logical(eye(N,N)); +SPL = distance_wei_floyd(adj); +Erout = rout_efficiency(adj); +Ediff = diffusion_efficiency(adj); +[Eres,prob_SPL] = resource_efficiency_bin(adj,lambda,SPL); +prob_SPL = mean(prob_SPL(~EYE)); +Eres = mean(Eres(~EYE)); +disp([prob_SPL,Erout,Ediff,Eres]) + +fprintf('----- rich-club ----- \n') +adj = rich_club; +N = size(adj,1); +EYE = logical(eye(N,N)); +SPL = distance_wei_floyd(adj); +Erout = rout_efficiency(adj); +Ediff = diffusion_efficiency(adj); +[Eres,prob_SPL] = resource_efficiency_bin(adj,lambda,SPL); +prob_SPL = mean(prob_SPL(~EYE)); +Eres = mean(Eres(~EYE)); +disp([prob_SPL,Erout,Ediff,Eres]) + +fprintf('----- bi-modular ----- \n') +adj = bi_modular; +N = size(adj,1); +EYE = logical(eye(N,N)); +SPL = distance_wei_floyd(adj); +Erout = rout_efficiency(adj); +Ediff = diffusion_efficiency(adj); +[Eres,prob_SPL] = resource_efficiency_bin(adj,lambda,SPL); +prob_SPL = mean(prob_SPL(~EYE)); +Eres = mean(Eres(~EYE)); +disp([prob_SPL,Erout,Ediff,Eres]) diff --git a/DefaultData/2019_03_03_BCT/data_and_demos/demo_efficiency_measures_data.mat b/DefaultData/2019_03_03_BCT/data_and_demos/demo_efficiency_measures_data.mat new file mode 100755 index 0000000..ca3b307 Binary files /dev/null and b/DefaultData/2019_03_03_BCT/data_and_demos/demo_efficiency_measures_data.mat differ diff --git a/DefaultData/2019_03_03_BCT/data_and_demos/demo_generative_models_data.mat b/DefaultData/2019_03_03_BCT/data_and_demos/demo_generative_models_data.mat new file mode 100755 index 0000000..cfcdc0a Binary files /dev/null and b/DefaultData/2019_03_03_BCT/data_and_demos/demo_generative_models_data.mat differ diff --git a/DefaultData/2019_03_03_BCT/data_and_demos/demo_generative_models_geometric.m b/DefaultData/2019_03_03_BCT/data_and_demos/demo_generative_models_geometric.m new file mode 100755 index 0000000..95adf34 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/data_and_demos/demo_generative_models_geometric.m @@ -0,0 +1,52 @@ +% Demonstration of generative model functions. +% +% See GENERATIVE_MODEL and EVALUATE_GENERATIVE_MODEL for further details +% and interpretation. + +clear +close all +clc + +data = load('demo_generative_models_data'); +A = data.A; +Aseed = data.Aseed; +D = data.D; + +% get cardinality of network +n = length(A); + +% set model type +modeltype = 'sptl'; + +% set whether the model is based on powerlaw or exponentials +modelvar = [{'powerlaw'},{'powerlaw'}]; + +% choose some model parameters +nparams = 100; +params = unifrnd(-10,0,nparams,1); + +% generate synthetic networks and energy for the neighbors model; +[B,E,K] = evaluate_generative_model(Aseed,A,D,modeltype,modelvar,params); +X = [E,K]; + +% show scatterplot of parameter values versus energy and KS statistics +names = [... + {'energy'},... + {'degree'},... + {'clustering'},... + {'betweenness'},... + {'edge length'}]; + +f = figure(... + 'units','inches',... + 'position',[2,2,4,4]); +for i = 1:size(X,2) + subplot(3,2,i); + scatter(params,X(:,i),100,X(:,i),'filled'); + set(gca,... + 'ylim',[0,1],... + 'clim',[0,1]); + colormap(jet); + xlabel('geometric parameter, \eta'); + ylabel(names{i}); +end diff --git a/DefaultData/2019_03_03_BCT/data_and_demos/demo_generative_models_neighbors.m b/DefaultData/2019_03_03_BCT/data_and_demos/demo_generative_models_neighbors.m new file mode 100755 index 0000000..61074dd --- /dev/null +++ b/DefaultData/2019_03_03_BCT/data_and_demos/demo_generative_models_neighbors.m @@ -0,0 +1,52 @@ +% Demonstration of generative model functions. +% +% See GENERATIVE_MODEL and EVALUATE_GENERATIVE_MODEL for further details +% and interpretation. + +clear +close all +clc + +data = load('demo_generative_models_data'); +A = data.A; +Aseed = data.Aseed; +D = data.D; + +% get cardinality of network +n = length(A); + +% set model type +modeltype = 'matching'; + +% set whether the model is based on powerlaw or exponentials +modelvar = [{'powerlaw'},{'powerlaw'}]; + +% choose some model parameters +nparams = 100; +params = [unifrnd(-10,0,nparams,1), unifrnd(-1,1,nparams,1)]; + +% generate synthetic networks and energy for the neighbors model; +[B,E,K] = evaluate_generative_model(Aseed,A,D,modeltype,modelvar,params); +X = [E,K]; + +% show scatterplot of parameter values versus energy and KS statistics +names = [... + {'energy'},... + {'degree'},... + {'clustering'},... + {'betweenness'},... + {'edge length'}]; + +f = figure(... + 'units','inches',... + 'position',[2,2,4,4]); +for i = 1:size(X,2) + subplot(3,2,i); + scatter(params(:,1),params(:,2),100,X(:,i),'filled'); + set(gca,... + 'clim',[0,1]); + colormap(jet); + xlabel('geometric parameter, \eta'); + ylabel('topological parameter, \gamma'); + title(names{i}); +end diff --git a/DefaultData/2019_03_03_BCT/data_and_demos/fve30.mat b/DefaultData/2019_03_03_BCT/data_and_demos/fve30.mat new file mode 100755 index 0000000..a6e0b3d Binary files /dev/null and b/DefaultData/2019_03_03_BCT/data_and_demos/fve30.mat differ diff --git a/DefaultData/2019_03_03_BCT/data_and_demos/fve32.mat b/DefaultData/2019_03_03_BCT/data_and_demos/fve32.mat new file mode 100755 index 0000000..d758330 Binary files /dev/null and b/DefaultData/2019_03_03_BCT/data_and_demos/fve32.mat differ diff --git a/DefaultData/2019_03_03_BCT/data_and_demos/macaque47.mat b/DefaultData/2019_03_03_BCT/data_and_demos/macaque47.mat new file mode 100755 index 0000000..b53368f Binary files /dev/null and b/DefaultData/2019_03_03_BCT/data_and_demos/macaque47.mat differ diff --git a/DefaultData/2019_03_03_BCT/data_and_demos/macaque71.mat b/DefaultData/2019_03_03_BCT/data_and_demos/macaque71.mat new file mode 100755 index 0000000..5e8a7c7 Binary files /dev/null and b/DefaultData/2019_03_03_BCT/data_and_demos/macaque71.mat differ diff --git a/DefaultData/2019_03_03_BCT/data_and_demos/motif34lib.mat b/DefaultData/2019_03_03_BCT/data_and_demos/motif34lib.mat new file mode 100755 index 0000000..fa3c304 Binary files /dev/null and b/DefaultData/2019_03_03_BCT/data_and_demos/motif34lib.mat differ diff --git a/DefaultData/2019_03_03_BCT/degrees_dir.m b/DefaultData/2019_03_03_BCT/degrees_dir.m new file mode 100755 index 0000000..aa77875 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/degrees_dir.m @@ -0,0 +1,31 @@ +function [id,od,deg] = degrees_dir(CIJ) +%DEGREES_DIR Indegree and outdegree +% +% [id,od,deg] = degrees_dir(CIJ); +% +% Node degree is the number of links connected to the node. The indegree +% is the number of inward links and the outdegree is the number of +% outward links. +% +% Input: CIJ, directed (binary/weighted) connection matrix +% +% Output: id, node indegree +% od, node outdegree +% deg, node degree (indegree + outdegree) +% +% Notes: Inputs are assumed to be on the columns of the CIJ matrix. +% Weight information is discarded. +% +% +% Olaf Sporns, Indiana University, 2002/2006/2008 + + +% ensure CIJ is binary... +CIJ = double(CIJ~=0); + +% compute degrees +id = sum(CIJ,1); % indegree = column sum of CIJ +od = sum(CIJ,2)'; % outdegree = row sum of CIJ +deg = id+od; % degree = indegree+outdegree + + diff --git a/DefaultData/2019_03_03_BCT/degrees_und.m b/DefaultData/2019_03_03_BCT/degrees_und.m new file mode 100755 index 0000000..86b91b3 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/degrees_und.m @@ -0,0 +1,22 @@ +function [deg] = degrees_und(CIJ) +%DEGREES_UND Degree +% +% deg = degrees_und(CIJ); +% +% Node degree is the number of links connected to the node. +% +% Input: CIJ, undirected (binary/weighted) connection matrix +% +% Output: deg, node degree +% +% Note: Weight information is discarded. +% +% +% Olaf Sporns, Indiana University, 2002/2006/2008 + + +% ensure CIJ is binary... +CIJ = double(CIJ~=0); + +deg = sum(CIJ); + diff --git a/DefaultData/2019_03_03_BCT/density_dir.m b/DefaultData/2019_03_03_BCT/density_dir.m new file mode 100755 index 0000000..b36d0a7 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/density_dir.m @@ -0,0 +1,24 @@ +function [kden,N,K] = density_dir(CIJ) +% DENSITY_DIR Density +% +% kden = density_dir(CIJ); +% [kden,N,K] = density_dir(CIJ); +% +% Density is the fraction of present connections to possible connections. +% +% Input: CIJ, directed (weighted/binary) connection matrix +% +% Output: kden, density +% N, number of vertices +% K, number of edges +% +% Notes: Assumes CIJ is directed and has no self-connections. +% Weight information is discarded. +% +% +% Olaf Sporns, Indiana University, 2002/2007/2008 + +N = size(CIJ,1); +K = nnz(CIJ); +kden = K/(N^2-N); + diff --git a/DefaultData/2019_03_03_BCT/density_und.m b/DefaultData/2019_03_03_BCT/density_und.m new file mode 100755 index 0000000..87debf4 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/density_und.m @@ -0,0 +1,28 @@ +function [kden,N,K] = density_und(CIJ) +% DENSITY_UND Density +% +% kden = density_und(CIJ); +% [kden,N,K] = density_und(CIJ); +% +% Density is the fraction of present connections to possible connections. +% +% Input: CIJ, undirected (weighted/binary) connection matrix +% +% Output: kden, density +% N, number of vertices +% K, number of edges +% +% Notes: Assumes CIJ is undirected and has no self-connections. +% Weight information is discarded. +% +% +% Olaf Sporns, Indiana University, 2002/2007/2008 + + +% Modification history: +% 2009-10: K fixed to sum over one half of CIJ [Tony Herdman, SFU] + +N = size(CIJ,1); +K = nnz(triu(CIJ)); +kden = K/((N^2-N)/2); + diff --git a/DefaultData/2019_03_03_BCT/diffusion_efficiency.m b/DefaultData/2019_03_03_BCT/diffusion_efficiency.m new file mode 100755 index 0000000..835bb97 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/diffusion_efficiency.m @@ -0,0 +1,33 @@ +function [GEdiff,Ediff] = diffusion_efficiency(adj) +% DIFFUSION_EFFICIENCY Global mean and pair-wise diffusion efficiency +% +% [GEdiff,Ediff] = diffusion_efficiency(adj); +% +% The diffusion efficiency between nodes i and j is the inverse of the +% mean first passage time from i to j, that is the expected number of +% steps it takes a random walker starting at node i to arrive for the +% first time at node j. Note that the mean first passage time is not a +% symmetric measure -- mfpt(i,j) may be different from mfpt(j,i) -- and +% the pair-wise diffusion efficiency matrix is hence also not symmetric. +% +% +% Input: +% adj, Weighted/Unweighted, directed/undirected adjacency matrix +% +% +% Outputs: +% GEdiff, Mean Global diffusion efficiency (scalar) +% Ediff, Pair-wise diffusion efficiency (matrix) +% +% +% References: Goñi J, et al (2013) PLoS ONE +% +% Joaquin Goñi and Andrea Avena-Koenigsberger, IU Bloomington, 2012 + + +n = size(adj,1); +mfpt = mean_first_passage_time(adj); +Ediff = 1./mfpt; +Ediff(eye(n)>0) = 0; +GEdiff = sum(Ediff(~eye(n)>0))/(n^2-n); + diff --git a/DefaultData/2019_03_03_BCT/distance_bin.m b/DefaultData/2019_03_03_BCT/distance_bin.m new file mode 100755 index 0000000..5210b78 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/distance_bin.m @@ -0,0 +1,45 @@ +function D=distance_bin(A) +%DISTANCE_BIN Distance matrix +% +% D = distance_bin(A); +% +% The distance matrix contains lengths of shortest paths between all +% pairs of nodes. An entry (u,v) represents the length of shortest path +% from node u to node v. The average shortest path length is the +% characteristic path length of the network. +% +% Input: A, binary directed/undirected connection matrix +% +% Output: D, distance matrix +% +% Notes: +% Lengths between disconnected nodes are set to Inf. +% Lengths on the main diagonal are set to 0. +% +% Algorithm: Algebraic shortest paths. +% +% +% Mika Rubinov, U Cambridge +% Jonathan Clayden, UCL +% 2007-2013 + +% Modification history: +% 2007: Original (MR) +% 2013: Bug fix, enforce zero distance for self-connections (JC) + +A=double(A~=0); %binarize and convert to double format + +l=1; %path length +Lpath=A; %matrix of paths l +D=A; %distance matrix + +Idx=true; +while any(Idx(:)) + l=l+1; + Lpath=Lpath*A; + Idx=(Lpath~=0)&(D==0); + D(Idx)=l; +end + +D(~D)=inf; %assign inf to disconnected nodes +D(1:length(A)+1:end)=0; %clear diagonal \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/distance_wei.m b/DefaultData/2019_03_03_BCT/distance_wei.m new file mode 100755 index 0000000..9665051 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/distance_wei.m @@ -0,0 +1,71 @@ +function [D,B]=distance_wei(L) +% DISTANCE_WEI Distance matrix (Dijkstra's algorithm) +% +% D = distance_wei(L); +% [D,B] = distance_wei(L); +% +% The distance matrix contains lengths of shortest paths between all +% pairs of nodes. An entry (u,v) represents the length of shortest path +% from node u to node v. The average shortest path length is the +% characteristic path length of the network. +% +% Input: L, Directed/undirected connection-length matrix. +% *** NB: The length matrix L isn't the weights matrix W (see below) *** +% +% Output: D, distance (shortest weighted path) matrix +% B, number of edges in shortest weighted path matrix +% +% Notes: +% The input matrix must be a connection-length matrix, typically +% obtained via a mapping from weight to length. For instance, in a +% weighted correlation network higher correlations are more naturally +% interpreted as shorter distances and the input matrix should +% consequently be some inverse of the connectivity matrix. +% The number of edges in shortest weighted paths may in general +% exceed the number of edges in shortest binary paths (i.e. shortest +% paths computed on the binarized connectivity matrix), because shortest +% weighted paths have the minimal weighted distance, but not necessarily +% the minimal number of edges. +% Lengths between disconnected nodes are set to Inf. +% Lengths on the main diagonal are set to 0. +% +% Algorithm: Dijkstra's algorithm. +% +% +% Mika Rubinov, UNSW/U Cambridge, 2007-2012. +% Rick Betzel and Andrea Avena, IU, 2012 + +%Modification history +%2007: original (MR) +%2009-08-04: min() function vectorized (MR) +%2012: added number of edges in shortest path as additional output (RB/AA) +%2013: variable names changed for consistency with other functions (MR) + +n=length(L); +D=inf(n); +D(1:n+1:end)=0; %distance matrix +B=zeros(n); %number of edges matrix + +for u=1:n + S=true(1,n); %distance permanence (true is temporary) + L1=L; + V=u; + while 1 + S(V)=0; %distance u->V is now permanent + L1(:,V)=0; %no in-edges as already shortest + for v=V + T=find(L1(v,:)); %neighbours of shortest nodes + [d,wi]=min([D(u,T);D(u,v)+L1(v,T)]); + D(u,T)=d; %smallest of old/new path lengths + ind=T(wi==2); %indices of lengthened paths + B(u,ind)=B(u,v)+1; %increment no. of edges in lengthened paths + end + + minD=min(D(u,S)); + if isempty(minD)||isinf(minD) %isempty: all nodes reached; + break, %isinf: some nodes cannot be reached + end; + + V=find(D(u,:)==minD); + end +end diff --git a/DefaultData/2019_03_03_BCT/distance_wei_floyd.m b/DefaultData/2019_03_03_BCT/distance_wei_floyd.m new file mode 100755 index 0000000..ac4b5ad --- /dev/null +++ b/DefaultData/2019_03_03_BCT/distance_wei_floyd.m @@ -0,0 +1,121 @@ +function [SPL,hops,Pmat] = distance_wei_floyd(D,transform) +% DISTANCE_WEI_FLOYD Distance matrix (Floyd-Warshall algorithm) +% +% [SPL,hops,Pmat] = distance_wei_floyd(D,transform) +% +% Computes the topological length of the shortest possible path +% connecting every pair of nodes in the network. +% +% Inputs: +% +% D, +% Weighted/unweighted directed/undirected +% connection *weight* OR *length* matrix. +% +% transform, +% If the input matrix is a connection *weight* matrix, specify a +% transform that map input connection weights to connection +% lengths. Two transforms are available. +% 'log' -> l_ij = -log(w_ij) +% 'inv' -> l_ij = 1/w_ij +% +% If the input matrix is a connection *length* matrix, do not +% specify a transform (or specify an empty transform argument). +% +% +% Outputs: +% +% SPL, +% Unweighted/Weighted shortest path-length matrix. +% If W is directed matrix, then SPL is not symmetric. +% +% hops, +% Number of edges in the shortest path matrix. If W is +% unweighted, SPL and hops are identical. +% +% Pmat, +% Elements {i,j} of this matrix indicate the next node in the +% shortest path between i and j. This matrix is used as an input +% argument for function 'retrieve_shortest_path.m', which returns +% as output the sequence of nodes comprising the shortest path +% between a given pair of nodes. +% +% +% Notes: +% +% There may be more than one shortest path between any pair of nodes +% in the network. Non-unique shortest paths are termed shortest path +% degeneracies, and are most likely to occur in unweighted networks. +% When the shortest-path is degenerate, The elements of matrix Pmat +% correspond to the first shortest path discovered by the algorithm. +% +% The input matrix may be either a connection weight matrix, or a +% connection length matrix. The connection length matrix is typically +% obtained with a mapping from weight to length, such that higher +% weights are mapped to shorter lengths (see above). +% +% +% Algorithm: Floyd–Warshall Algorithm +% +% +% Andrea Avena-Koenigsberger, IU, 2012 + +% Modification history +% 2016 - included transform variable that maps weights to lengths + +if exist('transform','var') && ~isempty(transform) + + switch transform + + case 'log' + + if any((D<0) & D>1) + error('connection-strengths must be in the interval [0,1) to use the transform -log(w_ij) \n') + else + SPL = -log(D); + end + + case 'inv' + + SPL = 1./D; + + otherwise + + error('Unexpected transform type. Only "log" and "inv" are accepted \n') + end + +else % the input is a connection lengths matrix. + SPL = D; + SPL(SPL == 0) = inf; +end + +n=size(D,2); + +if nargout > 1 + flag_find_paths = true; + hops = double(D ~= 0); + Pmat = 1:n; + Pmat = Pmat(ones(n,1),:); +else + flag_find_paths = false; +end + +for k=1:n + i2k_k2j = bsxfun(@plus, SPL(:,k), SPL(k,:)); + + if flag_find_paths + path = bsxfun(@gt, SPL, i2k_k2j); + [i,j] = find(path); + hops(path) = hops(i,k) + hops(k,j)'; + Pmat(path) = Pmat(i,k); + end + + SPL = min(SPL, i2k_k2j); +end + +SPL(eye(n)>0)=0; + +if flag_find_paths + hops(eye(n)>0)=0; + Pmat(eye(n)>0)=0; +end diff --git a/DefaultData/2019_03_03_BCT/diversity_coef_sign.m b/DefaultData/2019_03_03_BCT/diversity_coef_sign.m new file mode 100755 index 0000000..661dcbc --- /dev/null +++ b/DefaultData/2019_03_03_BCT/diversity_coef_sign.m @@ -0,0 +1,46 @@ +function [Hpos,Hneg] = diversity_coef_sign(W, Ci) +%DIVERSITY_COEF_SIGN Shannon-entropy based diversity coefficient +% +% [Hpos Hneg] = diversity_coef_sign(W,Ci); +% +% The Shannon-entropy based diversity coefficient measures the diversity +% of intermodular connections of individual nodes and ranges from 0 to 1. +% +% Inputs: W, undirected connection matrix with positive and +% negative weights +% +% Ci, community affiliation vector +% +% Output: Hpos, diversity coefficient based on positive connections +% Hneg, diversity coefficient based on negative connections +% +% References: Shannon CE (1948) Bell Syst Tech J 27, 379-423. +% Rubinov and Sporns (2011) NeuroImage. +% +% +% 2011-2012, Mika Rubinov, U Cambridge + +% Modification History: +% Mar 2011: Original +% Sep 2012: Fixed treatment of nodes with no negative strength +% (thanks to Alex Fornito and Martin Monti) + + +n = length(W); %number of nodes +m = max(Ci); %number of modules + +Hpos = entropy(W.*(W>0)); +Hneg = entropy(-W.*(W<0)); + + function H = entropy(W_) + S = sum(W_,2); %strength + Snm = zeros(n,m); %node-to-module degree + for i = 1:m %loop over modules + Snm(:,i) = sum(W_(:,Ci==i),2); + end + pnm = Snm ./ S(:,ones(1,m)); + pnm(isnan(pnm)) = 0; + pnm(~pnm) = 1; + H = -sum(pnm.*log(pnm),2)/log(m); + end +end diff --git a/DefaultData/2019_03_03_BCT/edge_betweenness_bin.m b/DefaultData/2019_03_03_BCT/edge_betweenness_bin.m new file mode 100755 index 0000000..d2ebccb --- /dev/null +++ b/DefaultData/2019_03_03_BCT/edge_betweenness_bin.m @@ -0,0 +1,68 @@ +function [EBC,BC]=edge_betweenness_bin(G) +%EDGE_BETWEENNESS_BIN Edge betweenness centrality +% +% EBC = edge_betweenness_bin(A); +% [EBC BC] = edge_betweenness_bin(A); +% +% Edge betweenness centrality is the fraction of all shortest paths in +% the network that contain a given edge. Edges with high values of +% betweenness centrality participate in a large number of shortest paths. +% +% Input: A, binary (directed/undirected) connection matrix. +% +% Output: EBC, edge betweenness centrality matrix. +% BC, node betweenness centrality vector. +% +% Note: Betweenness centrality may be normalised to the range [0,1] as +% BC/[(N-1)(N-2)], where N is the number of nodes in the network. +% +% Reference: Brandes (2001) J Math Sociol 25:163-177. +% +% +% Mika Rubinov, UNSW/U Cambridge, 2007-2012 + + +n=length(G); +BC=zeros(n,1); %vertex betweenness +EBC=zeros(n); %edge betweenness + +for u=1:n + D=false(1,n); D(u)=1; %distance from u + NP=zeros(1,n); NP(u)=1; %number of paths from u + P=false(n); %predecessors + Q=zeros(1,n); q=n; %order of non-increasing distance + + Gu=G; + V=u; + while V + Gu(:,V)=0; %remove remaining in-edges + for v=V + Q(q)=v; q=q-1; + W=find(Gu(v,:)); %neighbours of v + for w=W + if D(w) + NP(w)=NP(w)+NP(v); %NP(u->w) sum of old and new + P(w,v)=1; %v is a predecessor + else + D(w)=1; + NP(w)=NP(v); %NP(u->w) = NP of new path + P(w,v)=1; %v is a predecessor + end + end + end + V=find(any(Gu(V,:),1)); + end + if ~all(D) %if some vertices unreachable, + Q(1:q)=find(~D); %...these are first-in-line + end + + DP=zeros(n,1); %dependency + for w=Q(1:n-1) + BC(w)=BC(w)+DP(w); + for v=find(P(w,:)) + DPvw=(1+DP(w)).*NP(v)./NP(w); + DP(v)=DP(v)+DPvw; + EBC(v,w)=EBC(v,w)+DPvw; + end + end +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/edge_betweenness_wei.m b/DefaultData/2019_03_03_BCT/edge_betweenness_wei.m new file mode 100755 index 0000000..86fa9ff --- /dev/null +++ b/DefaultData/2019_03_03_BCT/edge_betweenness_wei.m @@ -0,0 +1,82 @@ +function [EBC,BC]=edge_betweenness_wei(G) +%EDGE_BETWEENNESS_WEI Edge betweenness centrality +% +% EBC = edge_betweenness_wei(L); +% [EBC BC] = edge_betweenness_wei(L); +% +% Edge betweenness centrality is the fraction of all shortest paths in +% the network that contain a given edge. Edges with high values of +% betweenness centrality participate in a large number of shortest paths. +% +% Input: L, Directed/undirected connection-length matrix. +% +% Output: EBC, edge betweenness centrality matrix. +% BC, nodal betweenness centrality vector. +% +% Notes: +% The input matrix must be a connection-length matrix, typically +% obtained via a mapping from weight to length. For instance, in a +% weighted correlation network higher correlations are more naturally +% interpreted as shorter distances and the input matrix should +% consequently be some inverse of the connectivity matrix. +% Betweenness centrality may be normalised to the range [0,1] as +% BC/[(N-1)(N-2)], where N is the number of nodes in the network. +% +% Reference: Brandes (2001) J Math Sociol 25:163-177. +% +% +% Mika Rubinov, UNSW/U Cambridge, 2007-2012 + + +n=length(G); +% E=find(G); G(E)=1./G(E); %invert weights +BC=zeros(n,1); %vertex betweenness +EBC=zeros(n); %edge betweenness + +for u=1:n + D=inf(1,n); D(u)=0; %distance from u + NP=zeros(1,n); NP(u)=1; %number of paths from u + S=true(1,n); %distance permanence (true is temporary) + P=false(n); %predecessors + Q=zeros(1,n); q=n; %order of non-increasing distance + + G1=G; + V=u; + while 1 + S(V)=0; %distance u->V is now permanent + G1(:,V)=0; %no in-edges as already shortest + for v=V + Q(q)=v; q=q-1; + W=find(G1(v,:)); %neighbours of v + for w=W + Duw=D(v)+G1(v,w); %path length to be tested + if Duww shorter than old + D(w)=Duw; + NP(w)=NP(v); %NP(u->w) = NP of new path + P(w,:)=0; + P(w,v)=1; %v is the only predecessor + elseif Duw==D(w) %if new u->w equal to old + NP(w)=NP(w)+NP(v); %NP(u->w) sum of old and new + P(w,v)=1; %v is also a predecessor + end + end + end + + minD=min(D(S)); + if isempty(minD), break %all nodes reached, or + elseif isinf(minD) %...some cannot be reached: + Q(1:q)=find(isinf(D)); break %...these are first-in-line + end + V=find(D==minD); + end + + DP=zeros(n,1); %dependency + for w=Q(1:n-1) + BC(w)=BC(w)+DP(w); + for v=find(P(w,:)) + DPvw=(1+DP(w)).*NP(v)./NP(w); + DP(v)=DP(v)+DPvw; + EBC(v,w)=EBC(v,w)+DPvw; + end + end +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/edge_nei_overlap_bd.m b/DefaultData/2019_03_03_BCT/edge_nei_overlap_bd.m new file mode 100755 index 0000000..4efa6ad --- /dev/null +++ b/DefaultData/2019_03_03_BCT/edge_nei_overlap_bd.m @@ -0,0 +1,48 @@ +function [EC,ec,degij] = edge_nei_overlap_bd(CIJ) +% EDGE_NEI_OVERLAP_BD Overlap amongst neighbors of two adjacent nodes +% +% [EC,ec,degij] = edge_nei_bd(CIJ); +% +% This function determines the neighbors of two nodes that are linked by +% an edge, and then computes their overlap. Connection matrix must be +% binary and directed. Entries of 'EC' that are 'inf' indicate that no +% edge is present. Entries of 'EC' that are 0 denote "local bridges", +% i.e. edges that link completely non-overlapping neighborhoods. Low +% values of EC indicate edges that are "weak ties". +% +% If CIJ is weighted, the weights are ignored. Neighbors of a node can be +% linked by incoming, outgoing, or reciprocal connections. +% +% Inputs: CIJ, directed (binary/weighted) connection matrix +% +% Outputs: EC, edge neighborhood overlap matrix +% ec, edge neighborhood overlap per edge, in vector format +% degij, degrees of node pairs connected by each edge +% +% Reference: +% +% Easley and Kleinberg (2010) Networks, Crowds, and Markets. +% Cambridge University Press, Chapter 3 +% +% Olaf Sporns, Indiana University, 2012 + +[ik,jk,ck] = find(CIJ); +lel = length(ck); +N = size(CIJ,1); + +[~,~,deg] = degrees_dir(CIJ); + +ec = zeros(1,lel); +degij = zeros(2,lel); +for e=1:lel + neiik = setdiff(union(find(CIJ(ik(e),:)),find(CIJ(:,ik(e))')),[ik(e) jk(e)]); + neijk = setdiff(union(find(CIJ(jk(e),:)),find(CIJ(:,jk(e))')),[ik(e) jk(e)]); + ec(e) = length(intersect(neiik,neijk))/length(union(neiik,neijk)); + degij(:,e) = [deg(ik(e)) deg(jk(e))]; +end; + +ff = find(CIJ); +EC = 1./zeros(N); +EC(ff) = ec; %#ok + + diff --git a/DefaultData/2019_03_03_BCT/edge_nei_overlap_bu.m b/DefaultData/2019_03_03_BCT/edge_nei_overlap_bu.m new file mode 100755 index 0000000..4aed3ab --- /dev/null +++ b/DefaultData/2019_03_03_BCT/edge_nei_overlap_bu.m @@ -0,0 +1,45 @@ +function [EC,ec,degij] = edge_nei_overlap_bu(CIJ) +% EDGE_NEI_OVERLAP_BU Overlap amongst neighbors of two adjacent nodes +% +% [EC,ec,degij] = edge_nei_bu(CIJ); +% +% This function determines the neighbors of two nodes that are linked by +% an edge, and then computes their overlap. Connection matrix must be +% binary and directed. Entries of 'EC' that are 'inf' indicate that no +% edge is present. Entries of 'EC' that are 0 denote "local bridges", i.e. +% edges that link completely non-overlapping neighborhoods. Low values +% of EC indicate edges that are "weak ties". +% +% If CIJ is weighted, the weights are ignored. +% +% Inputs: CIJ, undirected (binary/weighted) connection matrix +% +% Outputs: EC, edge neighborhood overlap matrix +% ec, edge neighborhood overlap per edge, in vector format +% degij, degrees of node pairs connected by each edge +% +% Reference: Easley and Kleinberg (2010) Networks, Crowds, and Markets. +% Cambridge University Press, Chapter 3. +% +% Olaf Sporns, Indiana University, 2012 + +[ik,jk,ck] = find(CIJ); +lel = length(ck); +N = size(CIJ,1); + +[deg] = degrees_und(CIJ); + +ec = zeros(1,lel); +degij = zeros(2,lel); +for e=1:lel + neiik = setdiff(union(find(CIJ(ik(e),:)),find(CIJ(:,ik(e))')),[ik(e) jk(e)]); + neijk = setdiff(union(find(CIJ(jk(e),:)),find(CIJ(:,jk(e))')),[ik(e) jk(e)]); + ec(e) = length(intersect(neiik,neijk))/length(union(neiik,neijk)); + degij(:,e) = [deg(ik(e)) deg(jk(e))]; +end; + +ff = find(CIJ); +EC = 1./zeros(N); +EC(ff) = ec; %#ok + + diff --git a/DefaultData/2019_03_03_BCT/efficiency_bin.m b/DefaultData/2019_03_03_BCT/efficiency_bin.m new file mode 100755 index 0000000..a1eda71 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/efficiency_bin.m @@ -0,0 +1,76 @@ +function E=efficiency_bin(A,local) +%EFFICIENCY_BIN Global efficiency, local efficiency. +% +% Eglob = efficiency_bin(A); +% Eloc = efficiency_bin(A,1); +% +% The global efficiency is the average of inverse shortest path length, +% and is inversely related to the characteristic path length. +% +% The local efficiency is the global efficiency computed on the +% neighborhood of the node, and is related to the clustering coefficient. +% +% Inputs: A, binary undirected or directed connection matrix +% local, optional argument +% local=0 computes global efficiency (default) +% local=1 computes local efficiency +% +% Output: Eglob, global efficiency (scalar) +% Eloc, local efficiency (vector) +% +% +% Algorithm: algebraic path count +% +% Reference: Latora and Marchiori (2001) Phys Rev Lett 87:198701. +% Fagiolo (2007) Phys Rev E 76:026107. +% Rubinov M, Sporns O (2010) NeuroImage 52:1059-69 +% +% +% Mika Rubinov, U Cambridge +% Jonathan Clayden, UCL +% 2008-2013 + +% Modification history: +% 2008: Original (MR) +% 2013: Bug fix, enforce zero distance for self-connections (JC) +% 2013: Local efficiency generalized to directed networks + +n=length(A); %number of nodes +A(1:n+1:end)=0; %clear diagonal +A=double(A~=0); %enforce double precision + +if exist('local','var') && local %local efficiency + E=zeros(n,1); + for u=1:n + V=find(A(u,:)|A(:,u).'); %neighbors + sa=A(u,V)+A(V,u).'; %symmetrized adjacency vector + e=distance_inv(A(V,V)); %inverse distance matrix + se=e+e.'; %symmetrized inverse distance matrix + numer=sum(sum((sa.'*sa).*se))/2; %numerator + if numer~=0 + denom=sum(sa).^2 - sum(sa.^2); %denominator + E(u)=numer/denom; %local efficiency + end + end +else %global efficiency + e=distance_inv(A); + E=sum(e(:))./(n^2-n); +end + + +function D=distance_inv(A_) +l=1; %path length +Lpath=A_; %matrix of paths l +D=A_; %distance matrix +n_=length(A_); + +Idx=true; +while any(Idx(:)) + l=l+1; + Lpath=Lpath*A_; + Idx=(Lpath~=0)&(D==0); + D(Idx)=l; +end + +D(~D | eye(n_))=inf; %assign inf to disconnected nodes and to diagonal +D=1./D; %invert distance diff --git a/DefaultData/2019_03_03_BCT/efficiency_wei.m b/DefaultData/2019_03_03_BCT/efficiency_wei.m new file mode 100755 index 0000000..394ff4d --- /dev/null +++ b/DefaultData/2019_03_03_BCT/efficiency_wei.m @@ -0,0 +1,136 @@ +function E = efficiency_wei(W, local) +%EFFICIENCY_WEI Global efficiency, local efficiency. +% +% Eglob = efficiency_wei(W); +% Eloc = efficiency_wei(W,2); +% +% The global efficiency is the average of inverse shortest path length, +% and is inversely related to the characteristic path length. +% +% The local efficiency is the global efficiency computed on the +% neighborhood of the node, and is related to the clustering coefficient. +% +% Inputs: W, +% weighted undirected or directed connection matrix +% +% local, +% optional argument +% local=0 computes the global efficiency (default). +% local=1 computes the original version of the local +% efficiency. +% local=2 computes the modified version of the local +% efficiency (recommended, see below). +% +% Output: Eglob, +% global efficiency (scalar) +% Eloc, +% local efficiency (vector) +% +% Notes: +% The efficiency is computed using an auxiliary connection-length +% matrix L, defined as L_ij = 1/W_ij for all nonzero L_ij; This has an +% intuitive interpretation, as higher connection weights intuitively +% correspond to shorter lengths. +% The weighted local efficiency broadly parallels the weighted +% clustering coefficient of Onnela et al. (2005) and distinguishes the +% influence of different paths based on connection weights of the +% corresponding neighbors to the node in question. In other words, a path +% between two neighbors with strong connections to the node in question +% contributes more to the local efficiency than a path between two weakly +% connected neighbors. Note that the original weighted variant of the +% local efficiency (described in Rubinov and Sporns, 2010) is not a +% true generalization of the binary variant, while the modified variant +% (described in Wang et al., 2016) is a true generalization. +% For ease of interpretation of the local efficiency it may be +% advantageous to rescale all weights to lie between 0 and 1. +% +% Algorithm: Dijkstra's algorithm +% +% References: Latora and Marchiori (2001) Phys Rev Lett 87:198701. +% Onnela et al. (2005) Phys Rev E 71:065103 +% Fagiolo (2007) Phys Rev E 76:026107. +% Rubinov M, Sporns O (2010) NeuroImage 52:1059-69 +% Wang Y et al. (2016) Neural Comput 21:1-19. +% +% Mika Rubinov, U Cambridge/Janelia HHMI, 2011-2017 + +%Modification history +% 2011: Original (based on efficiency.m and distance_wei.m) +% 2013: Local efficiency generalized to directed networks +% 2017: Added the modified local efficiency and updated documentation. + +n = length(W); % number of nodes +ot = 1 / 3; % one third + +L = W; % connection-length matrix +A = W > 0; % adjacency matrix +L(A) = 1 ./ L(A); +A = double(A); + +if exist('local','var') && local % local efficiency + E = zeros(n, 1); + cbrt_W = W.^ot; + switch local + case 1 + for u = 1:n + V = find(A(u, :) | A(:, u).'); % neighbors + sw = cbrt_W(u, V) + cbrt_W(V, u).'; % symmetrized weights vector + di = distance_inv_wei(L(V, V)); % inverse distance matrix + se = di.^ot + di.'.^ot; % symmetrized inverse distance matrix + numer = (sum(sum((sw.' * sw) .* se)))/2; % numerator + if numer~=0 + sa = A(u, V) + A(V, u).'; % symmetrized adjacency vector + denom = sum(sa).^2 - sum(sa.^2); % denominator + E(u) = numer / denom; % local efficiency + end + end + case 2 + cbrt_L = L.^ot; + for u = 1:n + V = find(A(u, :) | A(:, u).'); % neighbors + sw = cbrt_W(u, V) + cbrt_W(V, u).'; % symmetrized weights vector + di = distance_inv_wei(cbrt_L(V, V)); % inverse distance matrix + se = di + di.'; % symmetrized inverse distance matrix + numer=(sum(sum((sw.' * sw) .* se)))/2; % numerator + if numer~=0 + sa = A(u, V) + A(V, u).'; % symmetrized adjacency vector + denom = sum(sa).^2 - sum(sa.^2); % denominator + E(u) = numer / denom; % local efficiency + end + end + end +else + di = distance_inv_wei(L); + E = sum(di(:)) ./ (n^2 - n); % global efficiency +end + + +function D=distance_inv_wei(W_) + +n_=length(W_); +D=inf(n_); % distance matrix +D(1:n_+1:end)=0; + +for u=1:n_ + S=true(1,n_); % distance permanence (true is temporary) + W1_=W_; + V=u; + while 1 + S(V)=0; % distance u->V is now permanent + W1_(:,V)=0; % no in-edges as already shortest + for v=V + T=find(W1_(v,:)); % neighbours of shortest nodes + D(u,T)=min([D(u,T);D(u,v)+W1_(v,T)]); % smallest of old/new path lengths + end + + minD=min(D(u,S)); + if isempty(minD)||isinf(minD) % isempty: all nodes reached; + break, % isinf: some nodes cannot be reached + end; + + V=find(D(u,:)==minD); + end +end + +D=1./D; % invert distance +D(1:n_+1:end)=0; diff --git a/DefaultData/2019_03_03_BCT/eigenvector_centrality_und.m b/DefaultData/2019_03_03_BCT/eigenvector_centrality_und.m new file mode 100755 index 0000000..ce05698 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/eigenvector_centrality_und.m @@ -0,0 +1,37 @@ +function v = eigenvector_centrality_und(CIJ) +%EIGENVECTOR_CENTRALITY_UND Spectral measure of centrality +% +% v = eigenvector_centrality_und(CIJ) +% +% Eigenector centrality is a self-referential measure of centrality: +% nodes have high eigenvector centrality if they connect to other nodes +% that have high eigenvector centrality. The eigenvector centrality of +% node i is equivalent to the ith element in the eigenvector +% corresponding to the largest eigenvalue of the adjacency matrix. +% +% Inputs: CIJ, binary/weighted undirected adjacency matrix. +% +% Outputs: v, eigenvector associated with the largest +% eigenvalue of the adjacency matrix CIJ. +% +% Reference: Newman, MEJ (2002). The mathematics of networks. +% +% Contributors: +% Xi-Nian Zuo, Chinese Academy of Sciences, 2010 +% Rick Betzel, Indiana University, 2012 +% Mika Rubinov, University of Cambridge, 2015 + +% MODIFICATION HISTORY +% 2010/2012: original (XNZ, RB) +% 2015: ensure the use of leading eigenvector (MR) + + +n = length(CIJ); +if n < 1000 + [V,D] = eig(CIJ); +else + [V,D] = eigs(sparse(CIJ)); +end +[~,idx] = max(diag(D)); +ec = abs(V(:,idx)); +v = reshape(ec, length(ec), 1); \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/erange.m b/DefaultData/2019_03_03_BCT/erange.m new file mode 100755 index 0000000..7501416 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/erange.m @@ -0,0 +1,45 @@ +function [Erange,eta,Eshort,fs] = erange(CIJ) +%ERANGE Shortcuts +% +% [Erange,eta,Eshort,fs] = erange(CIJ); +% +% Shorcuts are central edges which significantly reduce the +% characteristic path length in the network. +% +% Input: CIJ, binary directed connection matrix +% +% Outputs: Erange, range for each edge, i.e. the length of the +% shortest path from i to j for edge c(i,j) AFTER +% the edge has been removed from the graph. +% eta average range for entire graph. +% Eshort entries are ones for shortcut edges. +% fs fraction of shortcuts in the graph. +% +% Follows the treatment of 'shortcuts' by Duncan Watts +% +% +% Olaf Sporns, Indiana University, 2002/2007/2008 + + +N = size(CIJ,1); +K = length(nonzeros(CIJ)); +Erange = zeros(N,N); +[i,j] = find(CIJ==1); + +for c=1:length(i) + CIJcut = CIJ; + CIJcut(i(c),j(c)) = 0; + [~, D] = reachdist(CIJcut); + Erange(i(c),j(c)) = D(i(c),j(c)); +end; + +% average range (ignore Inf) +eta = sum(Erange((Erange>0)&(Erange0)&(Erange 2, then the edge is a shortcut. +% 'fshort' is the fraction of shortcuts over the entire graph. + +Eshort = Erange>2; +fs = length(nonzeros(Eshort))/K; \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/evaluate_generative_model.m b/DefaultData/2019_03_03_BCT/evaluate_generative_model.m new file mode 100755 index 0000000..50f1476 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/evaluate_generative_model.m @@ -0,0 +1,100 @@ +function [B,E,K] = evaluate_generative_model(A,Atgt,D,modeltype,modelvar,params) +% EVALUATE_GENERATIVE_MODEL Generation and evaluation of synthetic networks +% +% [B,E,K] = EVALUATE_GENERATIVE_MODEL(A,Atgt,D,m,modeltype,modelvar,params) +% +% Generates synthetic networks and evaluates their energy function (see +% below) using the models described in the study by Betzel et al (2016) +% in Neuroimage. +% +% Inputs: +% A, binary network of seed connections +% Atgt, binary network against which synthetic networks are +% compared +% D, Euclidean distance/fiber length matrix +% m, number of connections that should be present in +% final synthetic network +% modeltype, specifies the generative rule (see below) +% modelvar, specifies whether the generative rules are based on +% power-law or exponential relationship +% ({'powerlaw'}|{'exponential}) +% params, either a vector (in the case of the geometric +% model) or a matrix (for all other models) of +% parameters at which the model should be evaluated. +% +% Outputs: +% B, m x number of networks matrix of connections +% E, energy for each synthetic network +% K, Kolmogorov-Smirnov statistics for each synthetic +% network. +% +% Full list of model types: +% (each model type realizes a different generative rule) +% +% 1. 'sptl' spatial model +% 2. 'neighbors' number of common neighbors +% 3. 'matching' matching index +% 4. 'clu-avg' average clustering coeff. +% 5. 'clu-min' minimum clustering coeff. +% 6. 'clu-max' maximum clustering coeff. +% 7. 'clu-diff' difference in clustering coeff. +% 8. 'clu-prod' product of clustering coeff. +% 9. 'deg-avg' average degree +% 10. 'deg-min' minimum degree +% 11. 'deg-max' maximum degree +% 12. 'deg-diff' difference in degree +% 13. 'deg-prod' product of degree +% +% Note: Energy is calculated in exactly the same way as in Betzel et +% al (2016). There are four components to the energy are KS statistics +% comparing degree, clustering coefficient, betweenness centrality, and +% edge length distributions. Energy is calculated as the maximum across +% all four statistics. +% +% Reference: Betzel et al (2016) Neuroimage 124:1054-64. +% +% Richard Betzel, Indiana University/University of Pennsylvania, 2015 + +m = nnz(Atgt)/2; +n = length(Atgt); +x = cell(4,1); +x{1} = sum(Atgt,2); +x{2} = clustering_coef_bu(Atgt); +x{3} = betweenness_bin(Atgt)'; +x{4} = D(triu(Atgt,1) > 0); + +B = generative_model(A,D,m,modeltype,modelvar,params); +nB = size(B,2); + +K = zeros(nB,4); +for iB = 1:nB + b = zeros(n); + b(B(:,iB)) = 1; + b = b + b'; + y = cell(4,1); + y{1} = sum(b,2); + y{2} = clustering_coef_bu(b); + y{3} = betweenness_bin(b)'; + y{4} = D(triu(b,1) > 0); + for j = 1:4 + K(iB,j) = fcn_ks(x{j},y{j}); + end +end +E = max(K,[],2); + + +function kstat = fcn_ks(x1,x2) +binEdges = [-inf ; sort([x1;x2]) ; inf]; + +binCounts1 = histc (x1 , binEdges, 1); +binCounts2 = histc (x2 , binEdges, 1); + +sumCounts1 = cumsum(binCounts1)./sum(binCounts1); +sumCounts2 = cumsum(binCounts2)./sum(binCounts2); + +sampleCDF1 = sumCounts1(1:end-1); +sampleCDF2 = sumCounts2(1:end-1); + +deltaCDF = abs(sampleCDF1 - sampleCDF2); +kstat = max(deltaCDF); + diff --git a/DefaultData/2019_03_03_BCT/find_motif34.m b/DefaultData/2019_03_03_BCT/find_motif34.m new file mode 100755 index 0000000..18550da --- /dev/null +++ b/DefaultData/2019_03_03_BCT/find_motif34.m @@ -0,0 +1,50 @@ +function M=find_motif34(m,n) +%FIND_MOTIF34 Motif legend +% +% Motif_matrices = find_motif34(Motif_id,Motif_class); +% Motif_id = find_motif34(Motif_matrix); +% +% This function returns all motif isomorphs for a given motif id and +% class (3 or 4). The function also returns the motif id for a given +% motif matrix +% +% 1. Input: Motif_id, e.g. 1 to 13, if class is 3 +% Motif_class, number of nodes, 3 or 4. +% +% Output: Motif_matrices, all isomorphs for the given motif +% +% 2. Input: Motif_matrix e.g. [0 1 0; 0 0 1; 1 0 0] +% +% Output Motif_id e.g. 1 to 13, if class is 3 +% +% +%Mika Rubinov, UNSW, 2007-2008 + +persistent M3 ID3 M4 ID4 + +if isscalar(m) + if n==3 + if isempty(ID3) + load motif34lib M3 ID3; + end + ind=find(ID3==m).'; + M=zeros(3,3,length(ind)); + for i=1:length(ind) + M(:,:,i)=reshape([0 M3(ind(i),1:3) 0 ... + M3(ind(i),4:6) 0],3,3); + end + elseif n==4 + if isempty(ID4) + load motif34lib M4 ID4; + end + ind=find(ID4==m).'; + M=zeros(4,4,length(ind)); + for i=1:length(ind) + M(:,:,i)=reshape([0 M4(ind(i),1:4) 0 ... + M4(ind(i),5:8) 0 M4(ind(i),9:12) 0],4,4); + end + end +else + n=size(m,1); + M=eval(['find(motif' int2str(n) 'struct_bin(m))']); +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/findpaths.m b/DefaultData/2019_03_03_BCT/findpaths.m new file mode 100755 index 0000000..c232409 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/findpaths.m @@ -0,0 +1,158 @@ +function [Pq,tpath,plq,qstop,allpths,util] = findpaths(CIJ,sources,qmax,savepths) +%FINDPATHS Network paths +% +% [Pq,tpath,plq,qstop,allpths,util] = findpaths(CIJ,sources,qmax,savepths); +% +% Paths are sequences of linked nodes, that never visit a single node +% more than once. This function finds all paths that start at a set of +% source nodes, up to a specified length. Warning: very memory-intensive. +% +% Inputs: CIJ, binary (directed/undirected) connection matrix +% qmax, maximal path length +% sources, source units from which paths are grown +% savepths, set to 1 if all paths are to be collected in +% 'allpths' +% +% Outputs: Pq, 3D matrix, with P(i,j,q) = number of paths from +% 'i' to 'j' of length 'q'. +% tpath, total number of paths found (lengths 1 to 'qmax') +% plq, path length distribution as a function of 'q' +% qstop, path length at which 'findpaths' is stopped +% allpths, a matrix containing all paths up to 'qmax' +% util, node use index +% +% Note that Pq(:,:,N) can only carry entries on the diagonal, as all +% "legal" paths of length N-1 must terminate. Cycles of length N are +% possible, with all vertices visited exactly once (except for source and +% target). 'qmax = N' can wreak havoc (due to memory problems). +% +% Note: Weights are discarded. +% Note: I am certain that this algorithm is rather inefficient - +% suggestions for improvements are welcome. +% +% Olaf Sporns, Indiana University, 2002/2007/2008/2010 + +% 2010 version: +% -- a bug affecting the calculation of 'util' was fixed -- thanks to +% Steve Williams +% -- better pre-allocation for 'npths' +% -- note that this code assumes a directed graph as input - calculation +% of paths and 'util' indices can be easily adapted to undirected +% graphs. + +% ensure CIJ is binary... +CIJ = double(CIJ~=0); + +% initialize some variables +N = size(CIJ,1); K = sum(sum(CIJ)); +pths = []; +Pq = zeros(N,N,qmax); +util = zeros(N,qmax); + +% this code is for pathlength = 1 +% paths are seeded from 'sources' +q = 1; +for j=1:N + for i=1:length(sources) + is = sources(i); + if (CIJ(is,j) == 1) + pths = [pths [is j]']; + end; + end; +end; + +% calculate the use index per vertex (for paths of length 1) +util(1:N,q) = util(1:N,q)+hist(reshape(pths,1,size(pths,1)*size(pths,2)),1:N)'; +% now enter the found paths of length 1 into the pathmatrix Pq +for np=1:size(pths,2) + Pq(pths(1,np),pths(q+1,np),q) = Pq(pths(1,np),pths(q+1,np),q) + 1; +end; + +% begin saving all paths +if (savepths==1) + allpths = pths; +end; +if (savepths~=1) + allpths = []; +end; + +% initialize +npthscnt = K; + +% "big loop" for all other pathlengths 'q' +% ---------------------------------------------------------------------- +for q=2:qmax + + % to keep track of time... + disp(['current pathlength (q) = ',num2str(q),' number of paths so far (up to q-1)= ',num2str(sum(sum(sum(Pq))))]) + + % old paths are now in 'pths' + % new paths are about to be collected in 'npths' + % estimate needed allocation for new paths + len_npths = min(ceil(1.1*npthscnt*K/N),100000000); + npths = zeros(q+1,len_npths); + + % find the unique set of endpoints of 'pths' + endp = unique(pths(q,:)); + npthscnt = 0; + + for ii=1:length(endp) % set of endpoints of previous paths + i = endp(ii); + % in 'pb' collect all previous paths with 'i' as their endpoint + [pa,pb] = find(pths(q,:) == i); + % find the outgoing connections from 'i' ("breadth-first") + nendp = find(CIJ(i,:)==1); + % if 'i' is not a dead end + if (~isempty(nendp)) + for jj=1:length(nendp) % endpoints of next edge + j = nendp(jj); + % find new paths - only "legal" ones, i.e. no vertex is visited twice + pb_temp = pb(sum(j==pths(2:q,pb),1)==0); + % add new paths to 'npths' + npths(:,npthscnt+1:npthscnt+length(pb_temp)) = [pths(:,pb_temp)' ones(length(pb_temp),1)*j]'; + npthscnt = npthscnt+length(pb_temp); + % count new paths and add the number to 'P' + Pq(1:N,j,q) = Pq(1:N,j,q)+(hist(pths(1,pb_temp),1:N))'; + end; + end; + end; + + % note: 'npths' now contains a list of all the paths of length 'q' + if (len_npths>npthscnt) + npths = npths(:,1:npthscnt); + end; + + % append the matrix of all paths + if (savepths==1) + allpths = [allpths; zeros(1,size(allpths,2))]; + allpths = [allpths npths(:,1:npthscnt)]; + end; + + % calculate the use index per vertex (correct for cycles, count + % source/target only once) + util(1:N,q) = util(1:N,q) + hist(reshape(npths(:,1:npthscnt),1,size(npths,1)*npthscnt),1:N)' - diag(Pq(:,:,q)); + % eliminate cycles from "making it" to the next level, so that + % 'pths' contains all the paths that have a chance of being continued + if (~isempty(npths)) + pths = npths(:,npths(1,:)~=npths(q+1,:)); + else + pths = []; + end; + + % if there are no 'pths' paths left, end the search + if (isempty(pths)) + qstop = q; + tpath = sum(sum(sum(Pq))); + plq = reshape(sum(sum(Pq)),1,qmax); + return; + end; + +end; % q +% ---------------------------------------------------------------------- +qstop = q; + +% total number of paths +tpath = sum(sum(sum(Pq))); + +% path length distribution +plq = reshape(sum(sum(Pq)),1,qmax); diff --git a/DefaultData/2019_03_03_BCT/findwalks.m b/DefaultData/2019_03_03_BCT/findwalks.m new file mode 100755 index 0000000..3efd4b5 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/findwalks.m @@ -0,0 +1,41 @@ +function [Wq,twalk,wlq] = findwalks(CIJ) +%FINDWALKS Network walks +% +% [Wq,twalk,wlq] = findwalks(CIJ); +% +% Walks are sequences of linked nodes, that may visit a single node more +% than once. This function finds the number of walks of a given length, +% between any two nodes. +% +% Input: CIJ binary (directed/undirected) connection matrix +% +% Outputs: Wq 3D matrix, Wq(i,j,q) is the number of walks +% from 'i' to 'j' of length 'q'. +% twalk total number of walks found +% wlq walk length distribution as function of 'q' +% +% Notes: Wq grows very quickly for larger N,K,q. Weights are discarded. +% +% Algorithm: algebraic path count +% +% +% Olaf Sporns, Indiana University, 2002/2007/2008 + +% ensure CIJ is binary... +CIJ = double(CIJ~=0); + +N = size(CIJ,1); +Wq = zeros(N,N,N); +CIJpwr = CIJ; +Wq(:,:,1) = CIJ; +for q=2:N + CIJpwr = CIJpwr*CIJ; + Wq(:,:,q) = CIJpwr; +end; + +% total number of walks +twalk = sum(sum(sum(Wq))); + +% walk length distribution +wlq = reshape(sum(sum(Wq)),1,N); + diff --git a/DefaultData/2019_03_03_BCT/flow_coef_bd.m b/DefaultData/2019_03_03_BCT/flow_coef_bd.m new file mode 100755 index 0000000..997e482 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/flow_coef_bd.m @@ -0,0 +1,51 @@ +function [fc,FC,total_flo] = flow_coef_bd(CIJ) +%FLOW_COEF_BD Node-wise flow coefficients +% +% [hc,HC,total_flo] = flow_coef_bd(CIJ) +% +% Computes the flow coefficient for each node and averaged over the +% network, as described in Honey et al. (2007) PNAS. The flow coefficient +% is similar to betweenness centrality, but works on a local +% neighborhood. It is mathematically related to the clustering +% coefficient (cc) at each node as, fc+cc <= 1. +% +% input: CIJ, connection/adjacency matrix (binary, directed) +% output: fc, flow coefficient for each node +% FC, average flow coefficient over the network +% total_flo, number of paths that "flow" across the central node +% +% Reference: Honey et al. (2007) Proc Natl Acad Sci U S A +% +% Olaf Sporns, Indiana University, 2007/2010/2012 + +N = size(CIJ,1); + +% initialize ... +fc = zeros(1,N); +total_flo = fc; +max_flo = fc; + +% loop over nodes +for v=1:N + % find neighbors - note: treats incoming and outgoing connections as equal + [nb] = find(CIJ(v,:) + CIJ(:,v)'); + fc(v) = 0; + if (~isempty(nb)) + CIJflo = -CIJ(nb,nb); + for i=1:length(nb) + for j=1:length(nb) + if((CIJ(nb(i),v))==1)&&(CIJ(v,nb(j))==1) + CIJflo(i,j) = CIJflo(i,j) + 1; + end; + end; + end; + total_flo(v) = sum(sum(double(CIJflo==1).*~eye(length(nb)))); + max_flo(v) = length(nb)^2-length(nb); + fc(v) = total_flo(v)/max_flo(v); + end; +end; + +% handle nodes that are NaNs +fc(isnan(fc)) = 0; + +FC = mean(fc); diff --git a/DefaultData/2019_03_03_BCT/gateway_coef_sign.m b/DefaultData/2019_03_03_BCT/gateway_coef_sign.m new file mode 100755 index 0000000..2d6a02d --- /dev/null +++ b/DefaultData/2019_03_03_BCT/gateway_coef_sign.m @@ -0,0 +1,83 @@ +function [GWpos,GWneg] = gateway_coef_sign(W,Ci,centtype) + +% Gateway coefficient +% +% [Gpos,Gneg] = gateway_coef_sign(W,Ci,centtype); +% +% Gateway coefficient is a variant of participation coefficient. Similar +% to participation coefficient, gateway coefficient measures the +% diversity of intermodular connections of individual nodes, but this is +% weighted by how critical these connections are to intermodular +% connectivity (e.g., if a node is the only connection between it's +% module and another module, it will have a higher gateway coefficient). +% +% Inputs: W, undirected connection matrix with positive and +% negative weights +% +% Ci, community affiliation vector +% +% centtype, centrality measure to use +% 1 = Node Strength +% 2 = Betweenness Centrality +% +% Output: Gpos, gateway coefficient for positive weights +% Gneg, gateway coefficient for negative weights +% +% Reference: Vargas ER, Wahl LM. Eur Phys J B (2014) 87:1-10. +% +% Jeff Spielberg, University of Delaware + +% Modification History: +% May 2015: Original (originally adapted from participation_coef_sign.m) +% July 2018: Bugfix, change in how weighted matrices are handled, +% improvements for efficiency, additional line documentation + +[~,~,Ci] = unique(Ci); % Remap module indices to consecutive numbers +n = length(W); % Number of nodes +W(1:(n+1):end) = 0; % Ensure diagonal is zero +GWpos = gcoef(W.*(W>0)); % Compute gateway coefficient for positive weights +GWneg = gcoef(-W.*(W<0)); % Compute gateway coefficient for negative weights + + function GW = gcoef(W_) + k = sum(W_,2); % Compute node strength + Gc = (W_~=0)*diag(Ci); % Create neighbor community affiliation matrix + nmod = max(Ci); % Find # of modules + ks = zeros(n,nmod); % Preallocate space + kjs = zeros(n,nmod); % Preallocate space + cs = zeros(n,nmod); % Preallocate space + switch centtype % Which centrality measure to use? + case 1 % Node Strength + cent = sum(W_,2); + case 2 % Betweenness Centrality + L = weight_conversion(W_,'lengths'); + cent = betweenness_wei(L); + end + mcn = 0; % Set max summed centrality per module to 0 + for i = 1:nmod % For each module + if sum(cent(Ci==i))>mcn % If current module has a higher sum + mcn = sum(cent(Ci==i)); % Reassign value + end + ks(:,i) = sum(W_.*(Gc==i),2); % Compute the total weight of the connections per node to each module + end + for i = 1:nmod % For each module + if sum(Ci==i)>1 % If there is more than 1 node in a module + kjs(Ci==i,:) = ones(sum(Ci==i),1)*sum(ks(Ci==i,:)); % Compute total module-module connections + kjs(Ci==i,i) = kjs(Ci==i,i)/2; % Account for redundancy due to double counting within-network work weights + end + end + for i = 1:n % For each node + if k(i)>0 % If node is connected + for ii = 1:nmod % For each module + cs(i,ii) = sum(cent((Ci.*(W_(:,i)>0))==ii)); % Sum of centralities of neighbors of a node within a module + end + end + end + ksm = ks./kjs; % Normalize by total connections + ksm(kjs==0) = 0; % Account for division by 0 + csm = cs./mcn; % Normalize by max summed centrality + gs = (1-(ksm.*csm)).^2; % Calculate total weighting + GW = 1-sum((ks.^2)./(k.^2).*gs,2); % Compute gateway coefficient + GW(isnan(GW)) = 0; % Account for division by 0 + GW(~GW) = 0; % Set to 0 if no neighbors + end +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/generate_fc.m b/DefaultData/2019_03_03_BCT/generate_fc.m new file mode 100755 index 0000000..98b74a7 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/generate_fc.m @@ -0,0 +1,241 @@ +function [FCpre,pred_data,Fcorr] = generate_fc(SC,beta,ED,pred_var,model,FC) +% GENERATE_FC Generation of synthetic functional connectivity matrices +% +% [FCpre,pred_data,Fcorr] = generate_fc(SC,beta,ED,{'SPLwei_log','SIwei_log'},FC) +% [FCpre,pred_data] = generate_fc(SC,beta,[],{'SPLwei_log','SIwei_log'}) +% +% Uses a vector beta of regression coefficients from the model +% FC = pred_var*beta to predict FC. pred_var are structural-based network +% measures derived from the structural connectivity network. +% +% Inputs: +% +% SC, +% Weighted/unweighted undirected NxN Structural Connectivity matrix. +% +% beta, +% Regression coefficients (vector). These may be obtained as an +% output parameter from function predict_fc.m +% +% ED, +% Euclidean distance matrix or upper triangular vector of the +% matrix (optional) +% +% pred_var, +% Set of M predictors. These can be given as an KxM array where +% K = ((N*(N-1))/2) and M is the number of predictors. +% Alternatively, pred_var can be a cell with the names of network +% measures to be used as predictors. Accepted network measure +% names are: +% SPLbin - Shortest-path length (binary) +% SPLwei_inv - Shortest-path length computed with an inv transform +% SPLwei_log - Shortest-path length computed with a log transform +% SPLdist - Shortest-path length computed with no transform +% SIbin - Search Information of binary shortest-paths +% SIwei_inv - Search Information of shortest-paths computed with an inv transform +% SIwei_log - Search Information of shortest-paths computed with a log transform +% SIdist - Search Information of shortest-paths computed with no transform +% T - Path Transitivity +% deltaMFPT - Column-wise z-scored mean first passage time +% neighOverlap - Neighborhood Overlap +% MI - Matching Index +% +% Predictors must be specified in the order that matches the +% given beta values. +% +% model, +% Specifies the order of the regression model used within +% matlab's function regstats.m. 'model' can be any option +% accepted by matlab's regstats.m function (e.g.'linear', +% 'interaction' 'quadratic', etc). If no model is specified, +% 'linear' is the default. +% +% FC, +% Functional connections. FC can be a NxN symmetric matrix or a +% ((N*(N-1))/2) x 1 vector containing the upper triangular +% elements of the square FC matrix (excluding diagonal elements). +% This argument is optional and only used to compute the +% correlation between the predicted FC and empirical FC. +% +% +% Outputs: +% +% FCpre, +% Predicted NxN Functional Connectivity matrix +% +% pred_data, +% KxM array of predictors. +% +% FCcorr, +% Pearson Correlation between FCpred and FC +% +% +% Reference: Goñi et al. (2014) PNAS 111: 833–838 +% +% +% Andrea Avena-Koenigsberger, Joaquin Goñi and Olaf Sporns; IU Bloomington, 2016 + + +[b1,b2] = size(beta); +if b1 == 1 && b2 >= b1 + beta = beta'; % beta must be a column vector +elseif b1 > 1 && b2 > 1 + error('beta must be a vector of scalar regression coefficients') +end + +pred_names = {'SPLbin','SPLwei_inv','SPLwei_log','SPLdist','SIbin',... + 'SIwei_inv','SIwei_log','SIdist','T','deltaMFPT','neighOverlap','MI'}; + +% select model +if ~exist('model','var') || isempty(model) + model = 'linear'; +end + +N = size(SC,1); +indx = find(triu(ones(N),1)); + +if ~exist('pred_var','var') && ~isempty(ED) + pred_var = {'ED','SPLwei_log','SI','T'}; + flag_var_names = true; + flag_ED = true; +elseif ~exist('pred_var','var') && isempty(ED) + pred_var = {'SPLwei_log','SI','T'}; + flag_var_names = true; +elseif exist('pred_var','var') && ~isnumeric(pred_var) && ~isempty(ED) + flag_var_names = true; + flag_ED = true; +elseif exist('pred_var','var') && ~isnumeric(pred_var) && isempty(ED) + flag_var_names = true; + flag_ED = false; +elseif exist('pred_var','var') && isnumeric(pred_var) && ~isempty(ED) + flag_var_names = false; + flag_ED = true; +elseif exist('pred_var','var') && isnumeric(pred_var) && isempty(ED) + flag_var_names = false; + flag_ED = false; +else + err_str = '"pred_var" must be an KxM array of M predictors, or any of the following graph-measure names:'; + s1 = sprintf('SPLbin - Shortest-path length (binary) \n'); + s2 = sprintf('SPLwei_inv - Shortest-path length computed with an inv transform \n'); + s3 = sprintf('SPLwei_log - Shortest-path length computed with a log transform \n'); + s4 = sprintf('SPLdist - Shortest-path length computed with no transform \n'); + s5 = sprintf('SIbin - Search Information of binary shortest-paths \n'); + s6 = sprintf('SIwei_inv - Search Information of shortest-paths computed with an inv transform \n'); + s7 = sprintf('SIwei_log - Search Information of shortest-paths computed with a log transform \n'); + s8 = sprintf('SIdist - Search Information of shortest-paths computed with no transform \n'); + s9 = sprintf('T - Path Transitivity \n'); + s10 = sprintf('deltaMFPT - Column-wise z-scored mean first passage time \n'); + s11 = sprintf('neighOverlap - Neighborhood Overlap \n'); + s12 = sprintf('MI - Matching Index \n'); + error('%s \n %s %s %s %s %s %s %s %s %s %s %s %s',err_str,s1,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12); +end + +if flag_ED + [n1,n2] = size(ED); + if n1 == n2 && n1 == N + % square ED matrix + pred_data = ED(indx); + elseif n1 == length(indx) && n2 == 1 + % ED is already an upper-triangle vector + pred_data = ED; + else + error('ED must be square matrix or a vector containing the upper triangle of the square ED matrix \n') + end +else + pred_data = []; +end + + +if flag_var_names + fprintf('\n----------------------'); + fprintf('\n Selected predictors: \n'); + ind2start = size(pred_data,2); + pred_data = [pred_data,zeros(length(indx),length(pred_var))]; + + for v = 1:length(pred_var) + var_ind = find(strcmp(pred_var{v},pred_names)); + switch var_ind + + case 1 %SPLbin + fprintf('Shortest-path length (binary) \n\n'); + data = distance_wei_floyd(double(SC>0)); + case 2 %SPLwei_inv + fprintf('Shortest-path length computed with an inv transform \n'); + data = distance_wei_floyd(SC,'inv'); + case 3 %SPLwei_log + fprintf('Shortest-path length computed with a log transform \n'); + data = distance_wei_floyd(SC,'log'); + case 4 %SPLdist + fprintf('Shortest-path length computed with no transform \n'); + data = distance_wei_floyd(SC); + case 5 %SIbin + fprintf('Search Information of binary shortest-paths \n'); + data = search_information(double(SC>0)); + data = data + data'; + case 6 %SIwei_inv + fprintf('Search Information of shortest-paths computed with an inv transform \n'); + data = search_information(SC,'inv'); + data = data + data'; + case 7 %SIwei_log + fprintf('Search Information of shortest-paths computed with a log transform \n'); + data = search_information(SC,'log'); + data = data + data'; + case 8 %SIdist + fprintf('Search Information of shortest-paths computed with no transform \n'); + data = search_information(SC); + data = data + data'; + case 9 %T + fprintf('Path Transitivity \n'); + data = path_transitivity(double(SC>0)); + case 10 %deltaMFPT + fprintf('Column-wise z-scored mean first passage time \n'); + mfpt = mean_first_passage_time(SC); + deltamfpt = zscore(mfpt,[],1); + data = deltamfpt+deltamfpt'; + case 11 %neighOverlap + fprintf('Neighborhood Overlap \n'); + data = double(SC>0) * double(SC>0)'; + case 12 %MI + fprintf('Matching Index \n'); + data = matching_ind(SC); + otherwise + error('This is not an accepted predictor. See list of available predictors \n') + end + pred_data(:,ind2start+v) = data(indx); + end +else + if size(pred_var,1) == length(indx) + pred_data = [pred_data,pred_var]; + else + error('Custom predictors must be provided as KxM array of M predictors \n'); + end +end + +pred_data = x2fx(pred_data,model); + +if size(pred_data,2) == size(beta,1) + Y = pred_data*beta; + FCpre = zeros(N); + FCpre(indx) = Y; + FCpre = FCpre+FCpre'; +end + +if nargin == 6 && ~isempty(FC) + flag_nan_corr = false; + [n1,n2] = size(FC); + if n1 == n2 && n1 == N + % square FC matrix + FCemp = FC(indx); + elseif n1 == length(indx) && n2 == 1 + % FC is already an upper-triangle vector + FCemp = FC; + else + warning('FC must be square matrix or a vector containing the upper triangle (no diagonal elements) of the square FC matrix \n') + flag_nan_corr = true; + end + if ~flag_nan_corr + Fcorr = corr(Y,FCemp); + else + Fcorr = nan; + end +end diff --git a/DefaultData/2019_03_03_BCT/generative_model.m b/DefaultData/2019_03_03_BCT/generative_model.m new file mode 100755 index 0000000..c5516fe --- /dev/null +++ b/DefaultData/2019_03_03_BCT/generative_model.m @@ -0,0 +1,897 @@ +function b = generative_model(A,D,m,modeltype,modelvar,params,epsilon) +% GENERATIVE_MODEL Run generative model code +% +% B = GENERATIVE_MODEL(A,D,m,modeltype,modelvar,params) +% +% Generates synthetic networks using the models described in the study by +% Betzel et al (2016) in Neuroimage. +% +% Inputs: +% A, binary network of seed connections +% D, Euclidean distance/fiber length matrix +% m, number of connections that should be present in +% final synthetic network +% modeltype, specifies the generative rule (see below) +% modelvar, specifies whether the generative rules are based on +% power-law or exponential relationship +% ({'powerlaw'}|{'exponential}) +% params, either a vector (in the case of the geometric +% model) or a matrix (for all other models) of +% parameters at which the model should be evaluated. +% epsilon, the baseline probability of forming a particular +% connection (should be a very small number +% {default = 1e-5}). +% +% Output: +% B, m x number of networks matrix of connections +% +% +% Full list of model types: +% (each model type realizes a different generative rule) +% +% 1. 'sptl' spatial model +% 2. 'neighbors' number of common neighbors +% 3. 'matching' matching index +% 4. 'clu-avg' average clustering coeff. +% 5. 'clu-min' minimum clustering coeff. +% 6. 'clu-max' maximum clustering coeff. +% 7. 'clu-diff' difference in clustering coeff. +% 8. 'clu-prod' product of clustering coeff. +% 9. 'deg-avg' average degree +% 10. 'deg-min' minimum degree +% 11. 'deg-max' maximum degree +% 12. 'deg-diff' difference in degree +% 13. 'deg-prod' product of degree +% +% +% Example usage: +% +% load demo_generative_models_data +% +% % get number of bi-directional connections +% m = nnz(A)/2; +% +% % get cardinality of network +% n = length(A); +% +% % set model type +% modeltype = 'neighbors'; +% +% % set whether the model is based on powerlaw or exponentials +% modelvar = [{'powerlaw'},{'powerlaw'}]; +% +% % choose some model parameters +% params = [-2,0.2; -5,1.2; -1,1.5]; +% nparams = size(params,1); +% +% % generate synthetic networks +% B = generative_model(Aseed,D,m,modeltype,modelvar,params); +% +% % store them in adjacency matrix format +% Asynth = zeros(n,n,nparams); +% for i = 1:nparams; +% a = zeros(n); a(B(:,i)) = 1; a = a + a'; +% Asynth(:,:,i) = a; +% end +% +% Reference: Betzel et al (2016) Neuroimage 124:1054-64. +% +% Richard Betzel, Indiana University/University of Pennsylvania, 2015 + +if ~exist('epsilon','var') + epsilon = 1e-5; +end + +n = length(D); +nparams = size(params,1); +b = zeros(m,nparams); + +switch modeltype + + case 'clu-avg' + clu = clustering_coef_bu(A); + Kseed = bsxfun(@plus,clu(:,ones(1,n)),clu')/2; + for iparam = 1:nparams + eta = params(iparam,1); + gam = params(iparam,2); + b(:,iparam) = fcn_clu_avg(A,Kseed,D,m,eta,gam,modelvar,epsilon); + end + + case 'clu-diff' + clu = clustering_coef_bu(A); + Kseed = abs(bsxfun(@minus,clu(:,ones(1,n)),clu')); + for iparam = 1:nparams + eta = params(iparam,1); + gam = params(iparam,2); + b(:,iparam) = fcn_clu_diff(A,Kseed,D,m,eta,gam,modelvar,epsilon); + end + + case 'clu-max' + clu = clustering_coef_bu(A); + Kseed = bsxfun(@max,clu(:,ones(1,n)),clu'); + for iparam = 1:nparams + eta = params(iparam,1); + gam = params(iparam,2); + b(:,iparam) = fcn_clu_max(A,Kseed,D,m,eta,gam,modelvar,epsilon); + end + + case 'clu-min' + clu = clustering_coef_bu(A); + Kseed = bsxfun(@min,clu(:,ones(1,n)),clu'); + for iparam = 1:nparams + eta = params(iparam,1); + gam = params(iparam,2); + b(:,iparam) = fcn_clu_min(A,Kseed,D,m,eta,gam,modelvar,epsilon); + end + + case 'clu-prod' + clu = clustering_coef_bu(A); + Kseed = clu*clu'; + for iparam = 1:nparams + eta = params(iparam,1); + gam = params(iparam,2); + b(:,iparam) = fcn_clu_prod(A,Kseed,D,m,eta,gam,modelvar,epsilon); + end + + case 'deg-avg' + kseed = sum(A,2); + Kseed = bsxfun(@plus,kseed(:,ones(1,n)),kseed')/2; + for iparam = 1:nparams + eta = params(iparam,1); + gam = params(iparam,2); + b(:,iparam) = fcn_deg_avg(A,Kseed,D,m,eta,gam,modelvar,epsilon); + end + + case 'deg-diff' + kseed = sum(A,2); + Kseed = abs(bsxfun(@minus,kseed(:,ones(1,n)),kseed')); + for iparam = 1:nparams + eta = params(iparam,1); + gam = params(iparam,2); + b(:,iparam) = fcn_deg_diff(A,Kseed,D,m,eta,gam,modelvar,epsilon); + end + + case 'deg-max' + kseed = sum(A,2); + Kseed = bsxfun(@max,kseed(:,ones(1,n)),kseed'); + for iparam = 1:nparams + eta = params(iparam,1); + gam = params(iparam,2); + b(:,iparam) = fcn_deg_max(A,Kseed,D,m,eta,gam,modelvar,epsilon); + end + + case 'deg-min' + kseed = sum(A,2); + Kseed = bsxfun(@min,kseed(:,ones(1,n)),kseed'); + for iparam = 1:nparams + eta = params(iparam,1); + gam = params(iparam,2); + b(:,iparam) = fcn_deg_min(A,Kseed,D,m,eta,gam,modelvar,epsilon); + end + + case 'deg-prod' + kseed = sum(A,2); + Kseed = (kseed*kseed').*~eye(n); + for iparam = 1:nparams + eta = params(iparam,1); + gam = params(iparam,2); + b(:,iparam) = fcn_deg_prod(A,Kseed,D,m,eta,gam,modelvar,epsilon); + end + + case 'neighbors' + Kseed = (A*A).*~eye(n); + for iparam = 1:nparams + eta = params(iparam,1); + gam = params(iparam,2); + b(:,iparam) = fcn_nghbrs(A,Kseed,D,m,eta,gam,modelvar,epsilon); + end + + case 'matching' + Kseed = matching_ind(A); + Kseed = Kseed + Kseed'; + for iparam = 1:nparams + eta = params(iparam,1); + gam = params(iparam,2); + b(:,iparam) = fcn_matching(A,Kseed,D,m,eta,gam,modelvar,epsilon); + end + + case 'sptl' + for iparam = 1:nparams + eta = params(iparam,1); + b(:,iparam) = fcn_sptl(A,D,m,eta,modelvar{1}); + end + +end + +function b = fcn_clu_avg(A,K,D,m,eta,gam,modelvar,epsilon) +K = K + epsilon; +n = length(D); +mseed = nnz(A)/2; +A = A > 0; +mv1 = modelvar{1}; +mv2 = modelvar{2}; +switch mv1 + case 'powerlaw' + Fd = D.^eta; + case 'exponential' + Fd = exp(eta*D); +end +switch mv2 + case 'powerlaw' + Fk = K.^gam; + case 'exponential' + Fk = exp(gam*K); +end + +c = clustering_coef_bu(A); +k = sum(A,2); + +Ff = Fd.*Fk.*~A; +[u,v] = find(triu(ones(n),1)); +indx = (v - 1)*n + u; +P = Ff(indx); + +for i = (mseed + 1):m + C = [0; cumsum(P)]; + r = sum(rand*C(end) >= C); + uu = u(r); + vv = v(r); + A(uu,vv) = 1; + A(vv,uu) = 1; + k([uu,vv]) = k([uu,vv]) + 1; + bu = A(uu,:); + su = A(bu,bu); + bv = A(vv,:); + sv = A(bv,bv); + bth = bu & bv; + c(bth) = c(bth) + 2./(k(bth).^2 - k(bth)); + c(uu) = nnz(su)/(k(uu)*(k(uu) - 1)); + c(vv) = nnz(sv)/(k(vv)*(k(vv) - 1)); + c(k <= 1) = 0; + bth([uu,vv]) = true; + K(:,bth) = bsxfun(@plus,c(:,ones(1,sum(bth))),c(bth,:)')/2 + epsilon; + K(bth,:) = bsxfun(@plus,c(:,ones(1,sum(bth))),c(bth,:)')'/2 + epsilon; + + switch mv2 + case 'powerlaw' + Ff(bth,:) = Fd(bth,:).*((K(bth,:)).^gam); + Ff(:,bth) = Fd(:,bth).*((K(:,bth)).^gam); + case 'exponential' + Ff(bth,:) = Fd(bth,:).*exp((K(bth,:))*gam); + Ff(:,bth) = Fd(:,bth).*exp((K(:,bth))*gam); + end + Ff = Ff.*~A; + P = Ff(indx); +end +b = find(triu(A,1)); + +function b = fcn_clu_diff(A,K,D,m,eta,gam,modelvar,epsilon) +K = K + epsilon; +n = length(D); +mseed = nnz(A)/2; +A = A > 0; +mv1 = modelvar{1}; +mv2 = modelvar{2}; +switch mv1 + case 'powerlaw' + Fd = D.^eta; + case 'exponential' + Fd = exp(eta*D); +end +switch mv2 + case 'powerlaw' + Fk = K.^gam; + case 'exponential' + Fk = exp(gam*K); +end + +c = clustering_coef_bu(A); +k = sum(A,2); + +Ff = Fd.*Fk.*~A; +[u,v] = find(triu(ones(n),1)); +indx = (v - 1)*n + u; +P = Ff(indx); + +for i = (mseed + 1):m + C = [0; cumsum(P)]; + r = sum(rand*C(end) >= C); + uu = u(r); + vv = v(r); + A(uu,vv) = 1; + A(vv,uu) = 1; + k([uu,vv]) = k([uu,vv]) + 1; + bu = A(uu,:); + su = A(bu,bu); + bv = A(vv,:); + sv = A(bv,bv); + bth = bu & bv; + c(bth) = c(bth) + 2./(k(bth).^2 - k(bth)); + c(uu) = nnz(su)/(k(uu)*(k(uu) - 1)); + c(vv) = nnz(sv)/(k(vv)*(k(vv) - 1)); + c(k <= 1) = 0; + bth([uu,vv]) = true; + K(:,bth) = abs(bsxfun(@minus,c(:,ones(1,sum(bth))),c(bth,:)')) + epsilon; + K(bth,:) = abs(bsxfun(@minus,c(:,ones(1,sum(bth))),c(bth,:)'))' + epsilon; + + switch mv2 + case 'powerlaw' + Ff(bth,:) = Fd(bth,:).*((K(bth,:)).^gam); + Ff(:,bth) = Fd(:,bth).*((K(:,bth)).^gam); + case 'exponential' + Ff(bth,:) = Fd(bth,:).*exp((K(bth,:))*gam); + Ff(:,bth) = Fd(:,bth).*exp((K(:,bth))*gam); + end + Ff = Ff.*~A; + P = Ff(indx); +end +b = find(triu(A,1)); + +function b = fcn_clu_max(A,K,D,m,eta,gam,modelvar,epsilon) +K = K + epsilon; +n = length(D); +mseed = nnz(A)/2; +A = A > 0; +mv1 = modelvar{1}; +mv2 = modelvar{2}; +switch mv1 + case 'powerlaw' + Fd = D.^eta; + case 'exponential' + Fd = exp(eta*D); +end +switch mv2 + case 'powerlaw' + Fk = K.^gam; + case 'exponential' + Fk = exp(gam*K); +end + +c = clustering_coef_bu(A); +k = sum(A,2); + +Ff = Fd.*Fk.*~A; +[u,v] = find(triu(ones(n),1)); +indx = (v - 1)*n + u; +P = Ff(indx); + +for i = (mseed + 1):m + C = [0; cumsum(P)]; + r = sum(rand*C(end) >= C); + uu = u(r); + vv = v(r); + A(uu,vv) = 1; + A(vv,uu) = 1; + k([uu,vv]) = k([uu,vv]) + 1; + bu = A(uu,:); + su = A(bu,bu); + bv = A(vv,:); + sv = A(bv,bv); + bth = bu & bv; + c(bth) = c(bth) + 2./(k(bth).^2 - k(bth)); + c(uu) = nnz(su)/(k(uu)*(k(uu) - 1)); + c(vv) = nnz(sv)/(k(vv)*(k(vv) - 1)); + c(k <= 1) = 0; + bth([uu,vv]) = true; + K(:,bth) = bsxfun(@max,c(:,ones(1,sum(bth))),c(bth,:)') + epsilon; + K(bth,:) = bsxfun(@max,c(:,ones(1,sum(bth))),c(bth,:)')' + epsilon; + + switch mv2 + case 'powerlaw' + Ff(bth,:) = Fd(bth,:).*((K(bth,:)).^gam); + Ff(:,bth) = Fd(:,bth).*((K(:,bth)).^gam); + case 'exponential' + Ff(bth,:) = Fd(bth,:).*exp((K(bth,:))*gam); + Ff(:,bth) = Fd(:,bth).*exp((K(:,bth))*gam); + end + Ff = Ff.*~A; + P = Ff(indx); +end +b = find(triu(A,1)); + +function b = fcn_clu_min(A,K,D,m,eta,gam,modelvar,epsilon) +K = K + epsilon; +n = length(D); +mseed = nnz(A)/2; +A = A > 0; +mv1 = modelvar{1}; +mv2 = modelvar{2}; +switch mv1 + case 'powerlaw' + Fd = D.^eta; + case 'exponential' + Fd = exp(eta*D); +end +switch mv2 + case 'powerlaw' + Fk = K.^gam; + case 'exponential' + Fk = exp(gam*K); +end + +c = clustering_coef_bu(A); +k = sum(A,2); + +Ff = Fd.*Fk.*~A; +[u,v] = find(triu(ones(n),1)); +indx = (v - 1)*n + u; +P = Ff(indx); + +for i = (mseed + 1):m + C = [0; cumsum(P)]; + r = sum(rand*C(end) >= C); + uu = u(r); + vv = v(r); + A(uu,vv) = 1; + A(vv,uu) = 1; + k([uu,vv]) = k([uu,vv]) + 1; + bu = A(uu,:); + su = A(bu,bu); + bv = A(vv,:); + sv = A(bv,bv); + bth = bu & bv; + c(bth) = c(bth) + 2./(k(bth).^2 - k(bth)); + c(uu) = nnz(su)/(k(uu)*(k(uu) - 1)); + c(vv) = nnz(sv)/(k(vv)*(k(vv) - 1)); + c(k <= 1) = 0; + bth([uu,vv]) = true; + K(:,bth) = bsxfun(@min,c(:,ones(1,sum(bth))),c(bth,:)') + epsilon; + K(bth,:) = bsxfun(@min,c(:,ones(1,sum(bth))),c(bth,:)')' + epsilon; + + switch mv2 + case 'powerlaw' + Ff(bth,:) = Fd(bth,:).*((K(bth,:)).^gam); + Ff(:,bth) = Fd(:,bth).*((K(:,bth)).^gam); + case 'exponential' + Ff(bth,:) = Fd(bth,:).*exp((K(bth,:))*gam); + Ff(:,bth) = Fd(:,bth).*exp((K(:,bth))*gam); + end + Ff = Ff.*~A; + P = Ff(indx); +end +b = find(triu(A,1)); + +function b = fcn_clu_prod(A,K,D,m,eta,gam,modelvar,epsilon) +K = K + epsilon; +n = length(D); +mseed = nnz(A)/2; +A = A > 0; +mv1 = modelvar{1}; +mv2 = modelvar{2}; +switch mv1 + case 'powerlaw' + Fd = D.^eta; + case 'exponential' + Fd = exp(eta*D); +end +switch mv2 + case 'powerlaw' + Fk = K.^gam; + case 'exponential' + Fk = exp(gam*K); +end + +c = clustering_coef_bu(A); +k = sum(A,2); + +Ff = Fd.*Fk.*~A; +[u,v] = find(triu(ones(n),1)); +indx = (v - 1)*n + u; +P = Ff(indx); + +for i = (mseed + 1):m + C = [0; cumsum(P)]; + r = sum(rand*C(end) >= C); + uu = u(r); + vv = v(r); + A(uu,vv) = 1; + A(vv,uu) = 1; + k([uu,vv]) = k([uu,vv]) + 1; + bu = A(uu,:); + su = A(bu,bu); + bv = A(vv,:); + sv = A(bv,bv); + bth = bu & bv; + c(bth) = c(bth) + 2./(k(bth).^2 - k(bth)); + c(uu) = nnz(su)/(k(uu)*(k(uu) - 1)); + c(vv) = nnz(sv)/(k(vv)*(k(vv) - 1)); + c(k <= 1) = 0; + bth([uu,vv]) = true; + K(bth,:) = (c(bth,:)*c') + epsilon; + K(:,bth) = (c*c(bth,:)') + epsilon; + + switch mv2 + case 'powerlaw' + Ff(bth,:) = Fd(bth,:).*((K(bth,:)).^gam); + Ff(:,bth) = Fd(:,bth).*((K(:,bth)).^gam); + case 'exponential' + Ff(bth,:) = Fd(bth,:).*exp((K(bth,:))*gam); + Ff(:,bth) = Fd(:,bth).*exp((K(:,bth))*gam); + end + Ff = Ff.*~A; + P = Ff(indx); +end +b = find(triu(A,1)); + +function b = fcn_deg_avg(A,K,D,m,eta,gam,modelvar,epsilon) +n = length(D); +mseed = nnz(A)/2; +k = sum(A,2); +[u,v] = find(triu(ones(n),1)); +indx = (v - 1)*n + u; +D = D(indx); +mv1 = modelvar{1}; +mv2 = modelvar{2}; +switch mv1 + case 'powerlaw' + Fd = D.^eta; + case 'exponential' + Fd = exp(eta*D); +end +K = K + epsilon; +switch mv2 + case 'powerlaw' + Fk = K.^gam; + case 'exponential' + Fk = exp(gam*K); +end +P = Fd.*Fk(indx).*~A(indx); +b = zeros(m,1); +b(1:mseed) = find(A(indx)); +for i = (mseed + 1):m + C = [0; cumsum(P)]; + r = sum(rand*C(end) >= C); + w = [u(r),v(r)]; + k(w) = k(w) + 1; + switch mv2 + case 'powerlaw' + Fk(:,w) = [((k + k(w(1)))/2) + epsilon, ((k + k(w(2)))/2) + epsilon].^gam; + Fk(w,:) = ([((k + k(w(1)))/2) + epsilon, ((k + k(w(2)))/2) + epsilon].^gam)'; + case 'exponential' + Fk(:,w) = exp([((k + k(w(1)))/2) + epsilon, ((k + k(w(2)))/2) + epsilon]*gam); + Fk(w,:) = exp([((k + k(w(1)))/2) + epsilon, ((k + k(w(2)))/2) + epsilon]*gam)'; + end + P = Fd.*Fk(indx); + b(i) = r; + P(b(1:i)) = 0; +end +b = indx(b); + +function b = fcn_deg_diff(A,K,D,m,eta,gam,modelvar,epsilon) +n = length(D); +mseed = nnz(A)/2; +k = sum(A,2); +[u,v] = find(triu(ones(n),1)); +indx = (v - 1)*n + u; +D = D(indx); +mv1 = modelvar{1}; +mv2 = modelvar{2}; +switch mv1 + case 'powerlaw' + Fd = D.^eta; + case 'exponential' + Fd = exp(eta*D); +end +K = K + epsilon; +switch mv2 + case 'powerlaw' + Fk = K.^gam; + case 'exponential' + Fk = exp(gam*K); +end +P = Fd.*Fk(indx).*~A(indx); +b = zeros(m,1); +b(1:mseed) = find(A(indx)); +for i = (mseed + 1):m + C = [0; cumsum(P)]; + r = sum(rand*C(end) >= C); + + w = [u(r),v(r)]; + k(w) = k(w) + 1; + switch mv2 + case 'powerlaw' + Fk(:,w) = (abs([k - k(w(1)), k - k(w(2))]) + epsilon).^gam; + Fk(w,:) = ((abs([k - k(w(1)), k - k(w(2))]) + epsilon).^gam)'; + case 'exponential' + Fk(:,w) = exp((abs([k - k(w(1)), k - k(w(2))]) + epsilon)*gam); + Fk(w,:) = exp((abs([k - k(w(1)), k - k(w(2))]) + epsilon)*gam)'; + end + P = Fd.*Fk(indx); + b(i) = r; + P(b(1:i)) = 0; +end +b = indx(b); + +function b = fcn_deg_min(A,K,D,m,eta,gam,modelvar,epsilon) +n = length(D); +mseed = nnz(A)/2; +k = sum(A,2); +[u,v] = find(triu(ones(n),1)); +indx = (v - 1)*n + u; +D = D(indx); +mv1 = modelvar{1}; +mv2 = modelvar{2}; +switch mv1 + case 'powerlaw' + Fd = D.^eta; + case 'exponential' + Fd = exp(eta*D); +end +K = K + epsilon; +switch mv2 + case 'powerlaw' + Fk = K.^gam; + case 'exponential' + Fk = exp(gam*K); +end +P = Fd.*Fk(indx).*~A(indx); +b = zeros(m,1); +b(1:mseed) = find(A(indx)); +for i = (mseed + 1):m + C = [0; cumsum(P)]; + r = sum(rand*C(end) >= C); + w = [u(r),v(r)]; + k(w) = k(w) + 1; + switch mv2 + case 'powerlaw' + Fk(:,w) = [min(k,k(w(1))) + epsilon, min(k,k(w(2))) + epsilon].^gam; + Fk(w,:) = ([min(k,k(w(1))) + epsilon, min(k,k(w(2))) + epsilon].^gam)'; + case 'exponential' + Fk(:,w) = exp([min(k,k(w(1))) + epsilon, min(k,k(w(2))) + epsilon]*gam); + Fk(w,:) = exp([min(k,k(w(1))) + epsilon, min(k,k(w(2))) + epsilon]*gam)'; + end + P = Fd.*Fk(indx); + b(i) = r; + P(b(1:i)) = 0; +end +b = indx(b); + +function b = fcn_deg_max(A,K,D,m,eta,gam,modelvar,epsilon) +n = length(D); +mseed = nnz(A)/2; +k = sum(A,2); +[u,v] = find(triu(ones(n),1)); +indx = (v - 1)*n + u; +D = D(indx); +mv1 = modelvar{1}; +mv2 = modelvar{2}; +switch mv1 + case 'powerlaw' + Fd = D.^eta; + case 'exponential' + Fd = exp(eta*D); +end +K = K + epsilon; +switch mv2 + case 'powerlaw' + Fk = K.^gam; + case 'exponential' + Fk = exp(gam*K); +end +P = Fd.*Fk(indx).*~A(indx); +b = zeros(m,1); +b(1:mseed) = find(A(indx)); +for i = (mseed + 1):m + C = [0; cumsum(P)]; + r = sum(rand*C(end) >= C); + w = [u(r),v(r)]; + k(w) = k(w) + 1; + switch mv2 + case 'powerlaw' + Fk(:,w) = [max(k,k(w(1))) + epsilon, max(k,k(w(2))) + epsilon].^gam; + Fk(w,:) = ([max(k,k(w(1))) + epsilon, max(k,k(w(2))) + epsilon].^gam)'; + case 'exponential' + Fk(:,w) = exp([max(k,k(w(1))) + epsilon, max(k,k(w(2))) + epsilon]*gam); + Fk(w,:) = exp([max(k,k(w(1))) + epsilon, max(k,k(w(2))) + epsilon]*gam)'; + end + P = Fd.*Fk(indx); + b(i) = r; + P(b(1:i)) = 0; +end +b = indx(b); + +function b = fcn_deg_prod(A,K,D,m,eta,gam,modelvar,epsilon) +n = length(D); +mseed = nnz(A)/2; +k = sum(A,2); +[u,v] = find(triu(ones(n),1)); +indx = (v - 1)*n + u; +D = D(indx); +mv1 = modelvar{1}; +mv2 = modelvar{2}; +switch mv1 + case 'powerlaw' + Fd = D.^eta; + case 'exponential' + Fd = exp(eta*D); +end +K = K + epsilon; +switch mv2 + case 'powerlaw' + Fk = K.^gam; + case 'exponential' + Fk = exp(gam*K); +end +P = Fd.*Fk(indx).*~A(indx); +b = zeros(m,1); +b(1:mseed) = find(A(indx)); +for i = (mseed + 1):m + C = [0; cumsum(P)]; + r = sum(rand*C(end) >= C); + w = [u(r),v(r)]; + k(w) = k(w) + 1; + switch mv2 + case 'powerlaw' + Fk(:,w) = ([k*k(w(1)) + epsilon, k*k(w(2)) + epsilon].^gam); + Fk(w,:) = (([k*k(w(1)) + epsilon, k*k(w(2)) + epsilon].^gam)'); + case 'exponential' + Fk(:,w) = exp([k*k(w(1)) + epsilon, k*k(w(2)) + epsilon]*gam); + Fk(w,:) = exp([k*k(w(1)) + epsilon, k*k(w(2)) + epsilon]*gam)'; + end + P = Fd.*Fk(indx); + b(i) = r; + P(b(1:i)) = 0; +end +b = indx(b); + +function b = fcn_nghbrs(A,K,D,m,eta,gam,modelvar,epsilon) +K = K + epsilon; +n = length(D); +mseed = nnz(A)/2; +A = A > 0; +mv1 = modelvar{1}; +mv2 = modelvar{2}; +switch mv1 + case 'powerlaw' + Fd = D.^eta; + case 'exponential' + Fd = exp(eta*D); +end +switch mv2 + case 'powerlaw' +% gam = abs(gam); + Fk = K.^gam; + case 'exponential' + Fk = exp(gam*K); +end +Ff = Fd.*Fk.*~A; +[u,v] = find(triu(ones(n),1)); +indx = (v - 1)*n + u; +P = Ff(indx); +for i = (mseed + 1):m + C = [0; cumsum(P)]; + r = sum(rand*C(end) >= C); + uu = u(r); + vv = v(r); + x = A(uu,:); + y = A(:,vv); + A(uu,vv) = 1; + A(vv,uu) = 1; + K(uu,y) = K(uu,y) + 1; + K(y,uu) = K(y,uu) + 1; + K(vv,x) = K(vv,x) + 1; + K(x,vv) = K(x,vv) + 1; + switch mv2 + case 'powerlaw' + Ff(uu,y) = Fd(uu,y).*(K(uu,y).^gam); + Ff(y,uu) = Ff(uu,y)'; + Ff(vv,x) = Fd(vv,x).*(K(vv,x).^gam); + Ff(x,vv) = Ff(vv,x)'; + case 'exponential' + Ff(uu,y) = Fd(uu,y).*exp(K(uu,y)*gam); + Ff(y,uu) = Ff(uu,y)'; + Ff(vv,x) = Fd(vv,x).*exp(K(vv,x)*gam); + Ff(x,vv) = Ff(vv,x)'; + end + Ff(A) = 0; + P = Ff(indx); +end +b = find(triu(A,1)); + +function b = fcn_matching(A,K,D,m,eta,gam,modelvar,epsilon) +K = K + epsilon; +n = length(D); +mseed = nnz(A)/2; +mv1 = modelvar{1}; +mv2 = modelvar{2}; +switch mv1 + case 'powerlaw' + Fd = D.^eta; + case 'exponential' + Fd = exp(eta*D); +end +switch mv2 + case 'powerlaw' + Fk = K.^gam; + case 'exponential' + Fk = exp(gam*K); +end +Ff = Fd.*Fk.*~A; +[u,v] = find(triu(ones(n),1)); +indx = (v - 1)*n + u; +P = Ff(indx); +for ii = (mseed + 1):m + + C = [0; cumsum(P)]; + r = sum(rand*C(end) >= C); + uu = u(r); + vv = v(r); + + A(uu,vv) = 1; + A(vv,uu) = 1; + + updateuu = find(A*A(:,uu)); + updateuu(updateuu == uu) = []; + updateuu(updateuu == vv) = []; + + updatevv = find(A*A(:,vv)); + updatevv(updatevv == uu) = []; + updatevv(updatevv == vv) = []; + + c1 = [A(:,uu)', A(uu,:)]; + for i = 1:length(updateuu) + j = updateuu(i); + c2 = [A(:,j)' A(j,:)]; + use = ~(~c1&~c2); + use(uu) = 0; use(uu+n) = 0; + use(j) = 0; use(j+n) = 0; + ncon = sum(c1(use))+sum(c2(use)); + if (ncon==0) + K(uu,j) = epsilon; + K(j,uu) = epsilon; + else + K(uu,j) = (2*(sum(c1(use)&c2(use))/ncon)) + epsilon; + K(j,uu) = K(uu,j); + end + + end + + c1 = [A(:,vv)', A(vv,:)]; + for i = 1:length(updatevv) + j = updatevv(i); + c2 = [A(:,j)' A(j,:)]; + use = ~(~c1&~c2); + use(vv) = 0; use(vv+n) = 0; + use(j) = 0; use(j+n) = 0; + ncon = sum(c1(use))+sum(c2(use)); + if (ncon==0) + K(vv,j) = epsilon; + K(j,vv) = epsilon; + else + K(vv,j) = (2*(sum(c1(use)&c2(use))/ncon)) + epsilon; + K(j,vv) = K(vv,j); + end + end + switch mv2 + case 'powerlaw' + Fk = K.^gam; + case 'exponential' + Fk = exp(gam*K); + end + Ff = Fd.*Fk.*~A; + P = Ff(indx); +end +b = find(triu(A,1)); + +function b = fcn_sptl(A,D,m,eta,modelvar) +n = length(D); +mseed = nnz(A)/2; +switch modelvar + case 'powerlaw' + Fd = D.^eta; + case 'exponential' + Fd = exp(eta*D); +end +[u,v] = find(triu(ones(n),1)); +indx = (v - 1)*n + u; +P = Fd(indx).*~A(indx); +b = zeros(m,1); +b(1:mseed) = find(A(indx)); +for i = (mseed + 1):m + C = [0; cumsum(P)]; + r = sum(rand*C(end) >= C); + b(i) = r; + P = Fd(indx); + P(b(1:i)) = 0; +end +b = indx(b); diff --git a/DefaultData/2019_03_03_BCT/get_components.m b/DefaultData/2019_03_03_BCT/get_components.m new file mode 100755 index 0000000..b05bbbd --- /dev/null +++ b/DefaultData/2019_03_03_BCT/get_components.m @@ -0,0 +1,57 @@ +function [comps,comp_sizes] = get_components(adj) +% GET_COMPONENTS Connected components +% +% [comps,comp_sizes] = get_components(adj); +% +% Returns the components of an undirected graph specified by the binary and +% undirected adjacency matrix adj. Components and their constitutent nodes are +% assigned the same index and stored in the vector, comps. The vector, comp_sizes, +% contains the number of nodes beloning to each component. +% +% Inputs: adj, binary and undirected adjacency matrix +% +% Outputs: comps, vector of component assignments for each node +% comp_sizes, vector of component sizes +% +% Note: disconnected nodes will appear as components of size 1 +% +% J Goni, University of Navarra and Indiana University, 2009/2011 + +if size(adj,1)~=size(adj,2) + error('this adjacency matrix is not square'); +end + +if ~any(adj-triu(adj)) + adj = adj | adj'; +end + +%if main diagonal of adj do not contain all ones, i.e. autoloops +if sum(diag(adj))~=size(adj,1) + + %the main diagonal is set to ones + adj = adj|speye(size(adj)); +end + +%Dulmage-Mendelsohn decomposition +[~,p,~,r] = dmperm(adj); + +%p indicates a permutation (along rows and columns) +%r is a vector indicating the component boundaries + +% List including the number of nodes of each component. ith entry is r(i+1)-r(i) +comp_sizes = diff(r); + +% Number of components found. +num_comps = numel(comp_sizes); + +% initialization +comps = zeros(1,size(adj,1)); + +% first position of each component is set to one +comps(r(1:num_comps)) = ones(1,num_comps); + +% cumulative sum produces a label for each component (in a consecutive way) +comps = cumsum(comps); + +%re-order component labels according to adj. +comps(p) = comps; diff --git a/DefaultData/2019_03_03_BCT/grid_communities.m b/DefaultData/2019_03_03_BCT/grid_communities.m new file mode 100755 index 0000000..25b4590 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/grid_communities.m @@ -0,0 +1,46 @@ +function [X,Y,indsort] = grid_communities(c) +% GRID_COMMUNITIES Outline communities along diagonal +% +% [X Y INDSORT] = GRID_COMMUNITIES(C) takes a vector of community +% assignments C and returns three output arguments for visualizing the +% communities. The third is INDSORT, which is an ordering of the vertices +% so that nodes with the same community assignment are next to one +% another. The first two arguments are vectors that, when overlaid on the +% adjacency matrix using the PLOT function, highlight the communities. +% +% Example: +% +% >> load AIJ; % load adjacency matrix +% >> [C,Q] = modularity_louvain_und(AIJ); % get community assignments +% >> [X,Y,INDSORT] = fcn_grid_communities(C); % call function +% >> imagesc(AIJ(INDSORT,INDSORT)); % plot ordered adjacency matrix +% >> hold on; % hold on to overlay community visualization +% >> plot(X,Y,'r','linewidth',2); % plot community boundaries +% +% Inputs: C, community assignments +% +% Outputs: X, x coor +% Y, y coor +% INDSORT, indices +% +% Richard Betzel, Indiana University, 2012 +% + +%#ok<*AGROW> + +nc = max(c); +[c,indsort] = sort(c); + +X = []; +Y = []; +for i = 1:nc + ind = find(c == i); + if ~isempty(ind) + mn = min(ind) - 0.5; + mx = max(ind) + 0.5; + x = [mn mn mx mx mn NaN]; + y = [mn mx mx mn mn NaN]; + X = [X, x]; + Y = [Y, y]; + end +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/gtom.m b/DefaultData/2019_03_03_BCT/gtom.m new file mode 100755 index 0000000..54dc653 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/gtom.m @@ -0,0 +1,81 @@ +function gt = gtom(adj,numSteps) +%GTOM Generalized topological overlap measure +% +% gt = gtom(adj,numSteps); +% +% The m-th step generalized topological overlap measure (GTOM) quantifies +% the extent to which a pair of nodes have similar m-th step neighbors. +% Mth-step neighbors are nodes that are reachable by a path of at most +% length m. +% +% This function computes the the M x M generalized topological overlap +% measure (GTOM) matrix for number of steps, numSteps. +% +% Inputs: adj, adjacency matrix (binary,undirected) +% numSteps, number of steps +% +% Outputs: gt, GTOM matrix +% +% NOTE: When numSteps is equal to 1, GTOM is identical to the topological +% overlap measure (TOM) from reference [2]. In that case the 'gt' matrix +% records, for each pair of nodes, the fraction of neighbors the two +% nodes share in common, where "neighbors" are one step removed. As +% 'numSteps' is increased, neighbors that are furter out are considered. +% Elements of 'gt' are bounded between 0 and 1. The 'gt' matrix can be +% converted from a similarity to a distance matrix by taking 1-gt. +% +% References: [1] Yip & Horvath (2007) BMC Bioinformatics 2007, 8:22 +% [2] Ravasz et al (2002) Science 297 (5586), 1551. +% +% J Goni, University of Navarra and Indiana University, 2009/2011 + +%#ok<*ASGLU> + +%initial state for bm matrix; +bm = adj; +bmAux = bm; +numNodes = size(adj,1); + +if (numSteps > numNodes) + disp('warning, reached maximum value for numSteps. numSteps reduced to adj-size') + numSteps = numNodes; +end + +if (numSteps == 0) + %GTOM0 + gt = adj; +else + + for steps = 2:numSteps + for i = 1:numNodes + + %neighbours of node i + [neighRow,neighColumn] = find(bm(i,:)==1); + + %neighbours of neighbours of node i + [neighNeighRow,neighNeighColumn] = find(bm(neighColumn,:)==1); + newNeigh = setdiff(unique(neighNeighColumn),i); + + %neighbours of neighbours of node i become considered node i neighbours + bmAux(i,newNeigh) = 1; + + %keep symmetry of matrix + bmAux(newNeigh,i) = 1; + end + %bm is updated with new step all at once + bm = bmAux; + + end + + clear bmAux newNeigh; + + %numerators of GTOM formula + numeratorMatrix = bm*bm + adj + speye(numNodes,numNodes); + + %vector containing degree of each node + bmSum=sum(bm); + clear bm; + + denominatorMatrix = -adj + min(repmat(bmSum,numNodes,1),repmat(bmSum',1,numNodes)) + 1; + gt = numeratorMatrix ./ denominatorMatrix; +end diff --git a/DefaultData/2019_03_03_BCT/jdegree.m b/DefaultData/2019_03_03_BCT/jdegree.m new file mode 100755 index 0000000..fa262ae --- /dev/null +++ b/DefaultData/2019_03_03_BCT/jdegree.m @@ -0,0 +1,48 @@ +function [J,J_od,J_id,J_bl] = jdegree(CIJ) +%JDEGREE Joint degree distribution +% +% [J,J_od,J_id,J_bl] = jdegree(CIJ); +% +% This function returns a matrix in which the value of each element (u,v) +% corresponds to the number of nodes that have u outgoing connections +% and v incoming connections. +% +% Input: CIJ, directed (weighted/binary) connection matrix +% +% Outputs: J, joint degree distribution matrix (shifted by one) +% J_od, number of vertices with od>id. +% J_id, number of vertices with id>od. +% J_bl, number of vertices with id=od. +% +% Note: Weights are discarded. +% +% +% Olaf Sporns, Indiana University, 2002/2006/2008 + + +% ensure CIJ is binary... +CIJ = double(CIJ~=0); + +N = size(CIJ,1); + +id = sum(CIJ,1); % indegree = column sum of CIJ +od = sum(CIJ,2)'; % outdegree = row sum of CIJ + +% Create the joint degree distribution matrix +% Note: the matrix is shifted by one, to accomodate zero id and od in the first row/column. +% Upper triangular part of the matrix has vertices with an excess of +% outgoing edges (od>id) +% Lower triangular part of the matrix has vertices with an excess of +% outgoing edges (id>od) +% Main diagonal has units with id=od + +szJ = max(max(id,od))+1; +J = zeros(szJ); + +for i=1:N + J(id(i)+1,od(i)+1) = J(id(i)+1,od(i)+1) + 1; +end; + +J_od = sum(sum(triu(J,1))); +J_id = sum(sum(tril(J,-1))); +J_bl = sum(diag(J)); diff --git a/DefaultData/2019_03_03_BCT/kcore_bd.m b/DefaultData/2019_03_03_BCT/kcore_bd.m new file mode 100755 index 0000000..44a4b3f --- /dev/null +++ b/DefaultData/2019_03_03_BCT/kcore_bd.m @@ -0,0 +1,59 @@ +function [CIJkcore,kn,peelorder,peellevel] = kcore_bd(CIJ,k) +%KCORE_BD K-core +% +% [CIJkcore,kn,peelorder,peellevel] = kcore_bd(CIJ,k); +% +% The k-core is the largest subnetwork comprising nodes of degree at +% least k. This function computes the k-core for a given binary directed +% connection matrix by recursively peeling off nodes with degree lower +% than k, until no such nodes remain. +% +% input: CIJ, connection/adjacency matrix (binary, directed) +% k, level of k-core +% +% output: CIJkcore, connection matrix of the k-core. This matrix +% only contains nodes of degree at least k. +% kn, size of k-core +% peelorder, indices in the order in which they were +% peeled away during k-core decomposition +% peellevel, corresponding level - nodes at the same +% level have been peeled away at the same time +% +% 'peelorder' and 'peellevel' are similar the the k-core sub-shells +% described in Modha and Singh (2010). +% +% References: e.g. Hagmann et al. (2008) PLoS Biology +% +% Olaf Sporns, Indiana University, 2007/2008/2010/2012 + +%#ok<*ASGLU> +%#ok<*AGROW> + +peelorder = []; +peellevel = []; +iter = 0; + +while 1 + + % get degrees of matrix + [id,od,deg] = degrees_dir(CIJ); + + % find nodes with degree 0)); + + % if none found -> stop + if (isempty(ff)) break; end; %#ok + + % peel away found nodes + iter = iter+1; + CIJ(ff,:) = 0; + CIJ(:,ff) = 0; + + peelorder = [peelorder; ff']; + peellevel = [peellevel; iter.*ones(1,length(ff))']; + +end; + +CIJkcore = CIJ; +kn = sum(deg>0); + diff --git a/DefaultData/2019_03_03_BCT/kcore_bu.m b/DefaultData/2019_03_03_BCT/kcore_bu.m new file mode 100755 index 0000000..b0bd23b --- /dev/null +++ b/DefaultData/2019_03_03_BCT/kcore_bu.m @@ -0,0 +1,58 @@ +function [CIJkcore,kn,peelorder,peellevel] = kcore_bu(CIJ,k) +%KCORE_BU K-core +% +% [CIJkcore,kn,peelorder,peellevel] = kcore_bu(CIJ,k); +% +% The k-core is the largest subnetwork comprising nodes of degree at +% least k. This function computes the k-core for a given binary +% undirected connection matrix by recursively peeling off nodes with +% degree lower than k, until no such nodes remain. +% +% input: CIJ, connection/adjacency matrix (binary, undirected) +% k, level of k-core +% +% output: CIJkcore, connection matrix of the k-core. This matrix +% only contains nodes of degree at least k. +% kn, size of k-core +% peelorder, indices in the order in which they were +% peeled away during k-core decomposition +% peellevel, corresponding level - nodes at the same +% level were peeled away at the same time +% +% 'peelorder' and 'peellevel' are similar the the k-core sub-shells +% described in Modha and Singh (2010). +% +% References: e.g. Hagmann et al. (2008) PLoS Biology +% +% Olaf Sporns, Indiana University, 2007/2008/2010/2012 + +%#ok<*AGROW> + +peelorder = []; +peellevel = []; +iter = 0; + +while 1 + + % get degrees of matrix + [deg] = degrees_und(CIJ); + + % find nodes with degree 0)); + + % if none found -> stop + if (isempty(ff)) break; end; %#ok + + % peel away found nodes + iter = iter+1; + CIJ(ff,:) = 0; + CIJ(:,ff) = 0; + + peelorder = [peelorder; ff']; + peellevel = [peellevel; iter.*ones(1,length(ff))']; + +end; + +CIJkcore = CIJ; +kn = sum(deg>0); + diff --git a/DefaultData/2019_03_03_BCT/kcoreness_centrality_bd.m b/DefaultData/2019_03_03_BCT/kcoreness_centrality_bd.m new file mode 100755 index 0000000..b59ad5e --- /dev/null +++ b/DefaultData/2019_03_03_BCT/kcoreness_centrality_bd.m @@ -0,0 +1,27 @@ +function [coreness,kn] = kcoreness_centrality_bd(CIJ) +%KCORENESS_CENTRALITY_BD K-coreness centrality +% +% [coreness,kn] = kcoreness_centrality_bd(CIJ) +% +% The k-core is the largest subgraph comprising nodes of degree at least +% k. The coreness of a node is k if the node belongs to the k-core but +% not to the (k+1)-core. This function computes k-coreness of all nodes +% for a given binary directed connection matrix. +% +% input: CIJ, connection/adjacency matrix (binary, directed) +% +% output: coreness, node coreness. +% kn, size of k-core +% +% References: e.g. Hagmann et al. (2008) PLoS Biology +% +% Olaf Sporns, Indiana University, 2007/2008/2010/2012 + +N = size(CIJ,1); + +coreness = zeros(1,N); kn = zeros(1,N); +for k=1:N + [CIJkcore,kn(k)] = kcore_bd(CIJ,k); + ss = sum(CIJkcore)>0; + coreness(ss) = k; +end; diff --git a/DefaultData/2019_03_03_BCT/kcoreness_centrality_bu.m b/DefaultData/2019_03_03_BCT/kcoreness_centrality_bu.m new file mode 100755 index 0000000..0db57a7 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/kcoreness_centrality_bu.m @@ -0,0 +1,34 @@ +function [coreness,kn] = kcoreness_centrality_bu(CIJ) +%KCORENESS_CENTRALITY_BU K-coreness centrality +% +% [coreness,kn] = kcoreness_centrality_bu(CIJ) +% +% The k-core is the largest subgraph comprising nodes of degree at least +% k. The coreness of a node is k if the node belongs to the k-core but +% not to the (k+1)-core. This function computes the coreness of all nodes +% for a given binary undirected connection matrix. +% +% input: CIJ, connection/adjacency matrix (binary, undirected) +% +% output: coreness, node coreness. +% kn, size of k-core +% +% References: e.g. Hagmann et al. (2008) PLoS Biology +% +% Olaf Sporns, Indiana University, 2007/2008/2010/2012 + +N = size(CIJ,1); + +% determine if the network is undirected - if not, compute coreness on the +% corresponding undirected network +CIJund = CIJ+CIJ'; +if (any(CIJund(:)>1)) + CIJ = double(CIJund>0); +end; + +coreness = zeros(1,N); kn = zeros(1,N); +for k=1:N + [CIJkcore,kn(k)] = kcore_bu(CIJ,k); + ss = sum(CIJkcore)>0; + coreness(ss) = k; +end; diff --git a/DefaultData/2019_03_03_BCT/latmio_dir.m b/DefaultData/2019_03_03_BCT/latmio_dir.m new file mode 100755 index 0000000..21dc2c2 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/latmio_dir.m @@ -0,0 +1,91 @@ +function [Rlatt,Rrp,ind_rp,eff] = latmio_dir(R,ITER,D) +%LATMIO_DIR Lattice with preserved in/out degree distribution +% +% [Rlatt,Rrp,ind_rp,eff] = latmio_dir(R,ITER,D); +% +% This function "latticizes" a directed network, while preserving the in- +% and out-degree distributions. In weighted networks, the function +% preserves the out-strength but not the in-strength distributions. +% +% Input: R, directed (binary/weighted) connection matrix +% ITER, rewiring parameter +% (each edge is rewired approximately ITER times) +% D, distance-to-diagonal matrix +% +% Output: Rlatt, latticized network in original node ordering +% Rrp, latticized network in node ordering used for +% latticization +% ind_rp, node ordering used for latticization +% eff, number of actual rewirings carried out +% +% References: Maslov and Sneppen (2002) Science 296:910 +% Sporns and Zwi (2004) Neuroinformatics 2:145 +% +% Mika Rubinov, UNSW, 2007-2010 +% Olaf Sporns, IU, 2012 + +n=size(R,1); + +% randomly reorder matrix +ind_rp = randperm(n); +R = R(ind_rp,ind_rp); + +% create 'distance to diagonal' matrix +if nargin<3 %if D is not specified by user + D=zeros(n); + u=[0 min([mod(1:n-1,n);mod(n-1:-1:1,n)])]; + for v=1:ceil(n/2) + D(n-v+1,:)=u([v+1:n 1:v]); + D(v,:)=D(n-v+1,n:-1:1); + end +end +%end create + +[i,j]=find(R); +K=length(i); +ITER=K*ITER; + +% maximal number of rewiring attempts per 'iter' +maxAttempts= round(n*K/(n*(n-1))); +% actual number of successful rewirings +eff = 0; + +for iter=1:ITER + att=0; + while (att<=maxAttempts) %while not rewired + while 1 + e1=ceil(K*rand); + e2=ceil(K*rand); + while (e2==e1) + e2=ceil(K*rand); + end + a=i(e1); b=j(e1); + c=i(e2); d=j(e2); + + if all(a~=[c d]) && all(b~=[c d]) + break %all four vertices must be different + end + end + + %rewiring condition + if ~(R(a,d) || R(c,b)) + %lattice condition + if (D(a,b)*R(a,b)+D(c,d)*R(c,d))>=(D(a,d)*R(a,b)+D(c,b)*R(c,d)) + R(a,d)=R(a,b); R(a,b)=0; + R(c,b)=R(c,d); R(c,d)=0; + + j(e1) = d; %reassign edge indices + j(e2) = b; + eff = eff+1; + break; + end %lattice condition + end %rewiring condition + att=att+1; + end %while not rewired +end %iterations + +% lattice in node order used for latticization +Rrp = R; +% reverse random permutation of nodes +[~,ind_rp_reverse] = sort(ind_rp); +Rlatt = Rrp(ind_rp_reverse,ind_rp_reverse); \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/latmio_dir_connected.m b/DefaultData/2019_03_03_BCT/latmio_dir_connected.m new file mode 100755 index 0000000..f15a599 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/latmio_dir_connected.m @@ -0,0 +1,119 @@ +function [Rlatt,Rrp,ind_rp,eff] = latmio_dir_connected(R,ITER,D) +%LATMIO_DIR_CONNECTED Lattice with preserved in/out degree distribution +% +% [Rlatt,Rrp,ind_rp,eff] = latmio_dir_connected(R,ITER,D); +% +% This function "latticizes" a directed network, while preserving the in- +% and out-degree distributions. In weighted networks, the function +% preserves the out-strength but not the in-strength distributions. The +% function also ensures that the randomized network maintains +% connectedness, the ability for every node to reach every other node in +% the network. The input network for this function must be connected. +% +% Input: R, directed (binary/weighted) connection matrix +% ITER, rewiring parameter +% (each edge is rewired approximately ITER times) +% D, distance-to-diagonal matrix +% +% Output: Rlatt, latticized network in original node ordering +% Rrp, latticized network in node ordering used for +% latticization +% ind_rp, node ordering used for latticization +% eff, number of actual rewirings carried out +% +% References: Maslov and Sneppen (2002) Science 296:910 +% Sporns and Zwi (2004) Neuroinformatics 2:145 +% +% Mika Rubinov, UNSW, 2007-2010 +% Olaf Sporns, Indiana University, 2012 + +n=size(R,1); + +% randomly reorder matrix +ind_rp = randperm(n); +R = R(ind_rp,ind_rp); + +% create 'distance to diagonal' matrix +if nargin<3 %if D is not specified by user + D=zeros(n); + u=[0 min([mod(1:n-1,n);mod(n-1:-1:1,n)])]; + for v=1:ceil(n/2) + D(n-v+1,:)=u([v+1:n 1:v]); + D(v,:)=D(n-v+1,n:-1:1); + end +end +%end create + +[i,j]=find(R); +K=length(i); +ITER=K*ITER; + +% maximal number of rewiring attempts per 'iter' +maxAttempts= round(n*K/(n*(n-1))); +% actual number of successful rewirings +eff = 0; + +for iter=1:ITER + att=0; + while (att<=maxAttempts) %while not rewired + rewire=1; + while 1 + e1=ceil(K*rand); + e2=ceil(K*rand); + while (e2==e1) + e2=ceil(K*rand); + end + a=i(e1); b=j(e1); + c=i(e2); d=j(e2); + + if all(a~=[c d]) && all(b~=[c d]) + break %all four vertices must be different + end + end + + %rewiring condition + if ~(R(a,d) || R(c,b)) + %lattice condition + if (D(a,b)*R(a,b)+D(c,d)*R(c,d))>=(D(a,d)*R(a,b)+D(c,b)*R(c,d)) + %connectedness condition + if ~(any([R(a,c) R(d,b) R(d,c)]) && any([R(c,a) R(b,d) R(b,a)])) + P=R([a c],:); + P(1,b)=0; P(1,d)=1; + P(2,d)=0; P(2,b)=1; + PN=P; + PN(1,a)=1; PN(2,c)=1; + + while 1 + P(1,:)=any(R(P(1,:)~=0,:),1); + P(2,:)=any(R(P(2,:)~=0,:),1); + P=P.*(~PN); + PN=PN+P; + if ~all(any(P,2)) + rewire=0; + break + elseif any(PN(1,[b c])) && any(PN(2,[d a])) + break + end + end + end %connectedness testing + + if rewire %reassign edges + R(a,d)=R(a,b); R(a,b)=0; + R(c,b)=R(c,d); R(c,d)=0; + + j(e1) = d; %reassign edge indices + j(e2) = b; + eff = eff+1; + break; + end %edge reassignment + end %lattice condition + end %rewiring condition + att=att+1; + end %while not rewired +end %iterations + +% lattice in node order used for latticization +Rrp = R; +% reverse random permutation of nodes +[~,ind_rp_reverse] = sort(ind_rp); +Rlatt = Rrp(ind_rp_reverse,ind_rp_reverse); \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/latmio_und.m b/DefaultData/2019_03_03_BCT/latmio_und.m new file mode 100755 index 0000000..30f3804 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/latmio_und.m @@ -0,0 +1,109 @@ +function [Rlatt,Rrp,ind_rp,eff] = latmio_und(R,ITER,D) +%LATMIO_UND Lattice with preserved degree distribution +% +% [Rlatt,Rrp,ind_rp,eff] = latmio_und(R,ITER,D); +% +% This function "latticizes" an undirected network, while preserving the +% degree distribution. The function does not preserve the strength +% distribution in weighted networks. +% +% Input: R, undirected (binary/weighted) connection matrix +% ITER, rewiring parameter +% (each edge is rewired approximately ITER times) +% D, distance-to-diagonal matrix +% +% Output: Rlatt, latticized network in original node ordering +% Rrp, latticized network in node ordering used for +% latticization +% ind_rp, node ordering used for latticization +% eff, number of actual rewirings carried out +% +% References: Maslov and Sneppen (2002) Science 296:910 +% Sporns and Zwi (2004) Neuroinformatics 2:145 +% +% 2007-2012 +% Mika Rubinov, UNSW +% Jonathan Power, WUSTL +% Olaf Sporns, IU + +% Modification History: +% Jun 2007: Original (Mika Rubinov) +% Apr 2008: Edge c-d is flipped with 50% probability, allowing to explore +% all potential rewirings (Jonathan Power) +% Feb 2012: limit on number of attempts, distance-to-diagonal as input, +% count number of successful rewirings (Olaf Sporns) +% Feb 2012: permute node ordering on each run, to ensure lattices are +% shuffled across mutliple runs (Olaf Sporns) + +n=size(R,1); + +% randomly reorder matrix +ind_rp = randperm(n); +R = R(ind_rp,ind_rp); + +% create 'distance to diagonal' matrix +if nargin<3 %if D is not specified by user + D=zeros(n); + u=[0 min([mod(1:n-1,n);mod(n-1:-1:1,n)])]; + for v=1:ceil(n/2) + D(n-v+1,:)=u([v+1:n 1:v]); + D(v,:)=D(n-v+1,n:-1:1); + end +end +%end create + +[i,j]=find(tril(R)); +K=length(i); +ITER=K*ITER; + +% maximal number of rewiring attempts per 'iter' +maxAttempts= round(n*K/(n*(n-1)/2)); +% actual number of successful rewirings +eff = 0; + +for iter=1:ITER + att=0; + while (att<=maxAttempts) %while not rewired + while 1 + e1=ceil(K*rand); + e2=ceil(K*rand); + while (e2==e1) + e2=ceil(K*rand); + end + a=i(e1); b=j(e1); + c=i(e2); d=j(e2); + + if all(a~=[c d]) && all(b~=[c d]) + break %all four vertices must be different + end + end + + if rand>0.5 + i(e2)=d; j(e2)=c; %flip edge c-d with 50% probability + c=i(e2); d=j(e2); %to explore all potential rewirings + end + + %rewiring condition + if ~(R(a,d) || R(c,b)) + %lattice condition + if (D(a,b)*R(a,b)+D(c,d)*R(c,d))>=(D(a,d)*R(a,b)+D(c,b)*R(c,d)) + R(a,d)=R(a,b); R(a,b)=0; + R(d,a)=R(b,a); R(b,a)=0; + R(c,b)=R(c,d); R(c,d)=0; + R(b,c)=R(d,c); R(d,c)=0; + + j(e1) = d; %reassign edge indices + j(e2) = b; + eff = eff+1; + break; + end %lattice condition + end %rewiring condition + att=att+1; + end %while not rewired +end %iterations + +% lattice in node order used for latticization +Rrp = R; +% reverse random permutation of nodes +[~,ind_rp_reverse] = sort(ind_rp); +Rlatt = Rrp(ind_rp_reverse,ind_rp_reverse); diff --git a/DefaultData/2019_03_03_BCT/latmio_und_connected.m b/DefaultData/2019_03_03_BCT/latmio_und_connected.m new file mode 100755 index 0000000..88e7220 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/latmio_und_connected.m @@ -0,0 +1,136 @@ +function [Rlatt,Rrp,ind_rp,eff] = latmio_und_connected(R,ITER,D) +%LATMIO_UND_CONNECTED Lattice with preserved degree distribution +% +% [Rlatt,Rrp,ind_rp,eff] = latmio_und_connected(R,ITER,D); +% +% This function "latticizes" an undirected network, while preserving the +% degree distribution. The function does not preserve the strength +% distribution in weighted networks. The function also ensures that the +% randomized network maintains connectedness, the ability for every node +% to reach every other node in the network. The input network for this +% function must be connected. +% +% Input: R, undirected (binary/weighted) connection matrix +% ITER, rewiring parameter +% (each edge is rewired approximately ITER times) +% D, distance-to-diagonal matrix +% +% Output: Rlatt, latticized network in original node ordering +% Rrp, latticized network in node ordering used for +% latticization +% ind_rp, node ordering used for latticization +% eff, number of actual rewirings carried out +% +% References: Maslov and Sneppen (2002) Science 296:910 +% Sporns and Zwi (2004) Neuroinformatics 2:145 +% +% 2007-2012 +% Mika Rubinov, UNSW +% Jonathan Power, WUSTL +% Olaf Sporns, IU + +% Modification History: +% Jun 2007: Original (Mika Rubinov) +% Apr 2008: Edge c-d is flipped with 50% probability, allowing to explore +% all potential rewirings (Jonathan Power) +% Feb 2012: limit on number of attempts, distance-to-diagonal as input, +% count number of successful rewirings (Olaf Sporns) +% Feb 2012: permute node ordering on each run, to ensure lattices are +% shuffled across mutliple runs (Olaf Sporns) + +n=size(R,1); + +% randomly reorder matrix +ind_rp = randperm(n); +R = R(ind_rp,ind_rp); + +% create 'distance to diagonal' matrix +if nargin<3 %if D is not specified by user + D=zeros(n); + u=[0 min([mod(1:n-1,n);mod(n-1:-1:1,n)])]; + for v=1:ceil(n/2) + D(n-v+1,:)=u([v+1:n 1:v]); + D(v,:)=D(n-v+1,n:-1:1); + end +end +%end create + +[i,j]=find(tril(R)); +K=length(i); +ITER=K*ITER; + +% maximal number of rewiring attempts per 'iter' +maxAttempts= round(n*K/(n*(n-1)/2)); +% actual number of successful rewirings +eff = 0; + +for iter=1:ITER + att=0; + while (att<=maxAttempts) %while not rewired + rewire=1; + while 1 + e1=ceil(K*rand); + e2=ceil(K*rand); + while (e2==e1) + e2=ceil(K*rand); + end + a=i(e1); b=j(e1); + c=i(e2); d=j(e2); + + if all(a~=[c d]) && all(b~=[c d]) + break %all four vertices must be different + end + end + + if rand>0.5 + i(e2)=d; j(e2)=c; %flip edge c-d with 50% probability + c=i(e2); d=j(e2); %to explore all potential rewirings + end + + %rewiring condition + if ~(R(a,d) || R(c,b)) + %lattice condition + if (D(a,b)*R(a,b)+D(c,d)*R(c,d))>=(D(a,d)*R(a,b)+D(c,b)*R(c,d)) + %connectedness condition + if ~(R(a,c) || R(b,d)) + P=R([a d],:); + P(1,b)=0; P(2,c)=0; + PN=P; + PN(:,d)=1; PN(:,a)=1; + + while 1 + P(1,:)=any(R(P(1,:)~=0,:),1); + P(2,:)=any(R(P(2,:)~=0,:),1); + P=P.*(~PN); + if ~all(any(P,2)) + rewire=0; + break + elseif any(any(P(:,[b c]))) + break + end + PN=PN+P; + end + end %connectedness testing + + if rewire %reassign edges + R(a,d)=R(a,b); R(a,b)=0; + R(d,a)=R(b,a); R(b,a)=0; + R(c,b)=R(c,d); R(c,d)=0; + R(b,c)=R(d,c); R(d,c)=0; + + j(e1) = d; %reassign edge indices + j(e2) = b; + eff = eff+1; + break; + end %edge reassignment + end %lattice condition + end %rewiring condition + att=att+1; + end %while not rewired +end %iterations + +% lattice in node order used for latticization +Rrp = R; +% reverse random permutation of nodes +[~,ind_rp_reverse] = sort(ind_rp); +Rlatt = Rrp(ind_rp_reverse,ind_rp_reverse); diff --git a/DefaultData/2019_03_03_BCT/link_communities.m b/DefaultData/2019_03_03_BCT/link_communities.m new file mode 100755 index 0000000..46c8063 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/link_communities.m @@ -0,0 +1,150 @@ +function M=link_communities(W,type_clustering) +%LINK_COMMUNITIES Optimal overlapping community structure +% +% M = link_communities(W) +% M = link_communities(W,'complete'); +% +% The optimal community structure is a subdivision of the network into +% groups of nodes which have a high number of within-group connections +% and a low number of between group connections. +% +% This algorithm uncovers overlapping community structure via +% hierarchical clustering of network links. This algorith is generalized +% for weighted/directed/fully-connected networks. +% +% Input: W, directed (weighted or binary) connection matrix. +% type_clustering, type of hierarchical clustering (optional) +% 'single' single-linkage (default) +% 'complete' complete-linkage +% +% Output: M, nodal community-affiliation matrix +% binary matrix of size CxN [communities x nodes] +% +% NB: The algorithm can be slow and memory intensive. +% +% Reference: Ahn, Bagrow and Lehmann (2010) Nature 466, 761–764. +% +% Mika Rubinov, U Cambridge, 2014-2015 + +%% initialize + +n=size(W,1); % number of nodes +W(1:n+1:end)=0; +W=W./max(W(:)); % normalize weights + +if ~exist('type_clustering','var') + type_clustering='single'; +end + +%% get node similarity + +W(1:n+1:end) = ( sum(W)/sum(W~=0) + sum(W.')/sum(W.'~=0) )/2; % mean weight on diagonal +No=sum(W.^2,2); % out-norm squared +Ni=sum(W.^2,1); % in-norm squared + +Jo=zeros(n); % weighted in-Jaccard +Ji=zeros(n); % weighted ou-Jaccard +for b=1:n + for c=1:n + Do=W(b,:)*W(c,:).'; + Jo(b,c)=Do./(No(b)+No(c)-Do); + + Di=W(:,b).'*W(:,c); + Ji(b,c)=Di./(Ni(b)+Ni(c)-Di); + end +end + +%% get link similarity + +[A,B]=find( (W|W.') & triu(ones(n),1)); +m=length(A); +Ln=zeros(m,2); % link nodes +Lw=zeros(m,1); % link weights +for i=1:m + Ln(i,:) = [A(i) B(i)]; % link nodes + Lw(i) = (W(A(i),B(i))+W(B(i),A(i)))/2; % link weight +end + +ES=zeros(m,m,'single'); % link similarity +for i=1:m + for j=1:m + if Ln(i,1)==Ln(j,1); a=Ln(i,1); b=Ln(i,2); c=Ln(j,2); + elseif Ln(i,1)==Ln(j,2); a=Ln(i,1); b=Ln(i,2); c=Ln(j,1); + elseif Ln(i,2)==Ln(j,1); a=Ln(i,2); b=Ln(i,1); c=Ln(j,2); + elseif Ln(i,2)==Ln(j,2); a=Ln(i,2); b=Ln(i,1); c=Ln(j,1); + else continue + end + + ES(i,j) = (W(a,b)*W(a,c)*Ji(b,c) + W(b,a)*W(c,a)*Jo(b,c))/2; + end +end +ES(1:m+1:end)=0; + +%% perform hierarchical clustering + +C=zeros(m,m,'single'); % community affiliation matrix +Nc=C; Mc=C; Dc=C; % communities nodes, links and density +U=1:m; % initial community assignments +C(1,:)=U; % as above, in the matrix + +for i=1:m-1; fprintf('hierarchy%8d\n',i) % hierarchy level + + % compute densities + for j=1:length(U) % loop over communities + idx = C(i,:)==U(j); % get link indices + links = sort(Lw(idx)); % sort link weights + nodes = sort(reshape(Ln(idx,:),2*nnz(idx),1)); + nodes = nodes([true;nodes(2:end)~=nodes(1:end-1)]); % get unique nodes + + nc = numel(nodes); % community nodes + mc = sum(links); % community weights + min_mc = sum(links(1:nc-1)); % minimal weight + dc = (mc - min_mc) / (nc.*(nc-1)/2 - min_mc); % community density + + Nc(i,j)=nc; + Mc(i,j)=mc; + Dc(i,j)=dc; + end + + % cluster + C(i+1,:)=C(i,:); % copy current partition + [u1,u2]=find(ES(U,U)==max(max(ES(U,U)))); % on this line MAXs MUST BE MAXs + + V=U(unique(sortrows(sort([u1 u2],2)),'rows')); % get unique links + for j=1:size(V,1) + switch type_clustering + case 'single'; x = max(ES(V(j,:),:),[],1); % max -> single linkage + case 'complete'; x = min(ES(V(j,:),:),[],1); % min -> complete linkage + otherwise; error('Unknown clustering type.'); + end + ES(V(j,:),:) = [x;x]; % assign distances to whole clusters + ES(:,V(j,:)) = [x;x].'; + ES(V(j,1),V(j,1)) = 0; % clear diagonal + ES(V(j,2),V(j,2)) = 0; % clear diagonal + + C(i+1,C(i+1,:)==V(j,2)) = V(j,1); % merge communities + V(V==V(j,2)) = V(j,1); % merge indices + end + + U=unique(C(i+1,:)); % get unique communities + if numel(U)==1 + break; + end +end + +%% + +Dc(isnan(Dc))=0; +[~,i]=max(sum(Dc.*Mc,2)); % get maximal density + +U=unique(C(i,:)); % unique communities +M=zeros(1,n); % nodal affiliations +for j=1:length(U) + M(j,unique( Ln(C(i,:)==U(j),:)) )=1; +end +M=M(sum(M,2)>2,:); + +% M2=zeros(n); % two dimensional nodal affiliation +% for i=1:size(M,1); +% M2=M2+(M(i,:).'*ones(1,n) & ones(n,1)*M(i,:)); +% end diff --git a/DefaultData/2019_03_03_BCT/local_assortativity_wu_sign.m b/DefaultData/2019_03_03_BCT/local_assortativity_wu_sign.m new file mode 100755 index 0000000..0f10967 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/local_assortativity_wu_sign.m @@ -0,0 +1,44 @@ +function [loc_assort_pos,loc_assort_neg] = local_assortativity_wu_sign(W) +%LOCAL_ASSORTATIVITY_WU_SIGN Local Assortativity +% +% [loc_assort_pos,loc_assort_neg] = local_assortativity_wu_sign(W); +% +% Local Assortativity measures the extent to which nodes are connected to +% nodes of similar strength (vs. higher or lower strength). Adapted from +% Thedchanamoorthy et al. (2014)'s formula to allow weighted/signed +% networks (node degree replaced with node strength). Note, output values +% sum to total assortativity. +% +% Inputs: W, undirected connection matrix with positive and +% negative weights +% +% Output: loc_assort_pos, local assortativity from positive weights +% +% loc_assort_neg, local assortativity from negative weights +% +% Reference: Thedchanamoorthy G, Piraveenan M, Kasthuriratna D, +% Senanayake U. Proc Comp Sci (2014) 29:2449-2461. +% +% +% Jeff Spielberg, Boston University + +% Modification History: +% May 2015: Original + +W(1:(size(W,1)+1):end) = 0; +r_pos = assortativity_wei(W.*(W>0),0); +r_neg = assortativity_wei(-W.*(W<0),0); +[str_pos,str_neg] = strengths_und_sign(W); +loc_assort_pos = nan(size(W,1),1); +loc_assort_neg = nan(size(W,1),1); + +for curr_node = 1:size(W,1) + [~,j_pos] = find(W(curr_node,:)>0); + loc_assort_pos(curr_node,1) = sum(abs(str_pos(j_pos)-str_pos(curr_node)))/str_pos(curr_node); + + [~,j_neg] = find(W(curr_node,:)<0); + loc_assort_neg(curr_node,1) = sum(abs(str_neg(j_neg)-str_neg(curr_node)))/str_neg(curr_node); +end + +loc_assort_pos = ((r_pos+1)/size(W,1))-(loc_assort_pos/sum(loc_assort_pos)); +loc_assort_neg = ((r_neg+1)/size(W,1))-(loc_assort_neg/sum(loc_assort_neg)); diff --git a/DefaultData/2019_03_03_BCT/make_motif34lib.m b/DefaultData/2019_03_03_BCT/make_motif34lib.m new file mode 100755 index 0000000..9b2708c --- /dev/null +++ b/DefaultData/2019_03_03_BCT/make_motif34lib.m @@ -0,0 +1,90 @@ +function make_motif34lib +%MAKE_MOTIF34LIB Auxiliary motif library function +% +% make_motif34lib; +% +% This function generates the motif34lib.mat library required for all +% other motif computations. +% +% +% Mika Rubinov, UNSW, 2007-2010 + +%#ok<*ASGLU> + +[M3,M3n,ID3,N3]=motif3generate; +[M4,M4n,ID4,N4]=motif4generate; +save motif34lib; + +function [M,Mn,ID,N]=motif3generate +n=0; +M=false(54,6); %isomorphs +CL=zeros(54,6,'uint8'); %canonical labels (predecessors of IDs) +cl=zeros(1,6,'uint8'); +for i=0:2^6-1 %loop through all subgraphs + m=dec2bin(i); + m=[num2str(zeros(1,6-length(m)), '%d') m]; %#ok + G=str2num ([ ... + '0' ' ' m(3) ' ' m(5) ; + m(1) ' ' '0' ' ' m(6) ; + m(2) ' ' m(4) ' ' '0' ]); %#ok + Ko=sum(G,2); + Ki=sum(G,1).'; + if all(Ko|Ki) %if subgraph weakly-connected + n=n+1; + cl(:)=sortrows([Ko Ki]).'; + CL(n,:)=cl; %assign motif label to isomorph + M(n,:)=G([2:4 6:8]); + end +end +[u1,u2,ID]=unique(CL,'rows'); %convert CLs into motif IDs + +%convert IDs into Sporns & Kotter classification +id_mika= [1 3 4 6 7 8 11]; +id_olaf= -[3 6 1 11 4 7 8]; +for id=1:length(id_mika) + ID(ID==id_mika(id))=id_olaf(id); +end +ID=abs(ID); + +[X,ind]=sortrows(ID); +ID=ID(ind,:); %sort IDs +M=M(ind,:); %sort isomorphs +N=sum(M,2); %number of edges +Mn=uint32(sum(repmat(10.^(5:-1:0),size(M,1),1).*M,2)); %M as a single number + +function [M,Mn,ID,N]=motif4generate +n=0; +M=false(3834,12); %isomorphs +CL=zeros(3834,16,'uint8'); %canonical labels (predecessors of IDs) +cl=zeros(1,16,'uint8'); +for i=0:2^12-1 %loop through all subgraphs + m=dec2bin(i); + m=[num2str(zeros(1,12-length(m)), '%d') m]; %#ok + G=str2num ([ ... + '0' ' ' m(4) ' ' m(7) ' ' m(10) ; + m(1) ' ' '0' ' ' m(8) ' ' m(11) ; + m(2) ' ' m(5) ' ' '0' ' ' m(12) ; + m(3) ' ' m(6) ' ' m(9) ' ' '0' ]); %#ok + Gs=G+G.'; + v=Gs(1,:); + for j=1:2 + v=any(Gs(v~=0,:),1)+v; + end + if v %if subgraph weakly connected + n=n+1; + G2=(G*G)~=0; + Ko=sum(G,2); + Ki=sum(G,1).'; + Ko2=sum(G2,2); + Ki2=sum(G2,1).'; + cl(:)=sortrows([Ki Ko Ki2 Ko2]).'; + CL(n,:)=cl; %assign motif label to isomorph + M(n,:)=G([2:5 7:10 12:15]); + end +end +[u1,u2,ID]=unique(CL,'rows'); %convert CLs into motif IDs +[X,ind]=sortrows(ID); +ID=ID(ind,:); %sort IDs +M=M(ind,:); %sort isomorphs +N=sum(M,2); %number of edges +Mn=uint64(sum(repmat(10.^(11:-1:0),size(M,1),1).*M,2)); %M as a single number \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/makeevenCIJ.m b/DefaultData/2019_03_03_BCT/makeevenCIJ.m new file mode 100755 index 0000000..ae95f12 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/makeevenCIJ.m @@ -0,0 +1,75 @@ +function [CIJ] = makeevenCIJ(N,K,sz_cl) +%MAKEEVENCIJ Synthetic modular small-world network +% +% CIJ = makeevenCIJ(N,K,sz_cl); +% +% This function generates a random, directed network with a specified +% number of fully connected modules linked together by evenly distributed +% remaining random connections. +% +% Inputs: N, number of vertices (must be power of 2) +% K, number of edges +% sz_cl, size of clusters (power of 2) +% +% Outputs: CIJ, connection matrix +% +% Notes: N must be a power of 2. +% A warning is generated if all modules contain more edges than K. +% Cluster size is 2^sz_cl; +% +% +% Olaf Sporns, Indiana University, 2005/2007 + +% compute number of hierarchical levels and adjust cluster size +mx_lvl = floor(log2(N)); +sz_cl = sz_cl-1; + +% make a stupid little template +t = ones(2).*2; + +% check N against number of levels +Nlvl = 2^mx_lvl; +if (Nlvl~=N) + disp('Warning: N must be a power of 2'); +end; +N = Nlvl; + +% create hierarchical template +for lvl=1:mx_lvl-1 + CIJ = ones(2^(lvl+1),2^(lvl+1)); + group1 = 1:size(CIJ,1)/2; + group2 = size(CIJ,1)/2+1:size(CIJ,1); + CIJ(group1,group1) = t; + CIJ(group2,group2) = t; + CIJ = CIJ+ones(size(CIJ,1),size(CIJ,1)); + t = CIJ; +end; +s = size(CIJ,1); +CIJ = CIJ-ones(s,s)-mx_lvl.*eye(s); + +% assign connection probabilities +%CIJp = mx_lvl-CIJ-sz_cl; +%CIJp = (CIJp>0).*CIJp; +CIJp = (CIJ>=(mx_lvl-sz_cl)); + +% determine number of remaining (non-cluster) connections and their +% possible positions +%CIJc = (CIJp==0); +CIJc = (CIJp==1); +remK = K-nnz(CIJc); +if (remK<0) + disp('Warning: K is too small, output matrix contains clusters only'); +end; +[a,b] = find(~(CIJc+eye(N))); + +% assign 'remK' randomly distributed connections +rp = randperm(length(a)); +a = a(rp(1:remK)); +b = b(rp(1:remK)); +for i=1:remK + CIJc(a(i),b(i)) = 1; +end; + +% prepare for output +CIJ = CIJc; + diff --git a/DefaultData/2019_03_03_BCT/makefractalCIJ.m b/DefaultData/2019_03_03_BCT/makefractalCIJ.m new file mode 100755 index 0000000..f410586 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/makefractalCIJ.m @@ -0,0 +1,49 @@ +function [CIJ,K] = makefractalCIJ(mx_lvl,E,sz_cl) +%MAKEFRACTALCIJ Synthetic hierarchical modular network +% +% [CIJ,K] = makefractalCIJ(mx_lvl,E,sz_cl); +% +% This function generates a directed network with a hierarchical modular +% organization. All modules are fully connected and connection density +% decays as 1/(E^n), with n = index of hierarchical level. +% +% Inputs: mx_lvl, number of hierarchical levels, N = 2^mx_lvl +% E, connection density fall-off per level +% sz_cl, size of clusters (power of 2) +% +% Outputs: CIJ, connection matrix +% K, number of connections present in the output CIJ +% +% +% Olaf Sporns, Indiana University, 2005/2007 + +% make a little template +t = ones(2).*2; + +% compute N and cluster size +N = 2^mx_lvl; +sz_cl = sz_cl-1; + +% n = [0 0 0:mx_lvl-3]; + +for lvl=1:mx_lvl-1 + CIJ = ones(2^(lvl+1),2^(lvl+1)); + group1 = 1:size(CIJ,1)/2; + group2 = size(CIJ,1)/2+1:size(CIJ,1); + CIJ(group1,group1) = t; + CIJ(group2,group2) = t; + CIJ = CIJ+ones(size(CIJ,1),size(CIJ,1)); + t = CIJ; +end; +s = size(CIJ,1); +CIJ = CIJ-ones(s,s)-mx_lvl.*eye(s); + +% assign connection probablities +ee = mx_lvl-CIJ-sz_cl; +ee = (ee>0).*ee; +prob = (1./(E.^ee)).*(ones(s,s)-eye(s)); +CIJ = (prob>rand(N)); + +% count connections +K = sum(sum(CIJ)); + diff --git a/DefaultData/2019_03_03_BCT/makelatticeCIJ.m b/DefaultData/2019_03_03_BCT/makelatticeCIJ.m new file mode 100755 index 0000000..ae7e1c5 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/makelatticeCIJ.m @@ -0,0 +1,45 @@ +function [CIJ] = makelatticeCIJ(N,K) +%MAKELATTICECIJ Synthetic lattice network +% +% CIJ = makelatticeCIJ(N,K); +% +% This function generates a directed lattice network without toroidal +% boundary counditions (i.e. no ring-like "wrapping around"). +% +% Inputs: N, number of vertices +% K, number of edges +% +% Outputs: CIJ, connection matrix +% +% Note: The lattice is made by placing connections as close as possible +% to the main diagonal, without wrapping around. No connections are made +% on the main diagonal. In/Outdegree is kept approx. constant at K/N. +% +% +% Olaf Sporns, Indiana University, 2005/2007 + +% initialize +CIJ = zeros(N); +CIJ1 = ones(N); +KK = 0; +cnt = 0; +seq = 1:N-1; + +% fill in +while (KK0) + [i,j] = find(dCIJ); + rp = randperm(length(i)); + for ii=1:overby + CIJ(i(rp(ii)),j(rp(ii))) = 0; + end; +end; diff --git a/DefaultData/2019_03_03_BCT/makerandCIJ_dir.m b/DefaultData/2019_03_03_BCT/makerandCIJ_dir.m new file mode 100755 index 0000000..b1e07bd --- /dev/null +++ b/DefaultData/2019_03_03_BCT/makerandCIJ_dir.m @@ -0,0 +1,24 @@ +function [CIJ] = makerandCIJ_dir(N,K) +%MAKERANDCIJ_DIR Synthetic directed random network +% +% CIJ = makerandCIJ_dir(N,K); +% +% This function generates a directed random network +% +% Inputs: N, number of vertices +% K, number of edges +% +% Output: CIJ, directed random connection matrix +% +% Note: no connections are placed on the main diagonal. +% +% +% Olaf Sporns, Indiana University, 2007/2008 + +ind = ~eye(N); +i = find(ind); +rp = randperm(length(i)); +irp = i(rp); + +CIJ = zeros(N); +CIJ(irp(1:K)) = 1; diff --git a/DefaultData/2019_03_03_BCT/makerandCIJ_und.m b/DefaultData/2019_03_03_BCT/makerandCIJ_und.m new file mode 100755 index 0000000..e03dbf1 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/makerandCIJ_und.m @@ -0,0 +1,25 @@ +function [CIJ] = makerandCIJ_und(N,K) +%MAKERANDCIJ_UND Synthetic directed random network +% +% CIJ = makerandCIJ_und(N,K); +% +% This function generates an undirected random network +% +% Inputs: N, number of vertices +% K, number of edges +% +% Output: CIJ, undirected random connection matrix +% +% Note: no connections are placed on the main diagonal. +% +% +% Olaf Sporns, Indiana University, 2007/2008 + +ind = triu(~eye(N)); +i = find(ind); +rp = randperm(length(i)); +irp = i(rp); + +CIJ = zeros(N); +CIJ(irp(1:K)) = 1; +CIJ = CIJ+CIJ'; % symmetrize diff --git a/DefaultData/2019_03_03_BCT/makerandCIJdegreesfixed.m b/DefaultData/2019_03_03_BCT/makerandCIJdegreesfixed.m new file mode 100755 index 0000000..e163a3d --- /dev/null +++ b/DefaultData/2019_03_03_BCT/makerandCIJdegreesfixed.m @@ -0,0 +1,81 @@ +function [cij,flag] = makerandCIJdegreesfixed(in,out) +%MAKERANDCIJDEGREESFIXED Synthetic directed random network +% +% CIJ = makerandCIJdegreesfixed(N,K); +% +% This function generates a directed random network with a specified +% in-degree and out-degree sequence. The function returns a flag, +% denoting whether the algorithm succeeded or failed. +% +% Inputs: in, indegree vector +% out, outdegree vector +% +% Output: CIJ, binary directed connectivity matrix +% flag, flag=1 if the algorithm succeeded; flag=0 otherwise +% +% +% Notes: Necessary conditions include: +% length(in) = length(out) = n +% sum(in) = sum(out) = k +% in(i), out(i) < n-1 +% in(i) + out(j) < n+2 +% in(i) + out(i) < n +% +% No connections are placed on the main diagonal +% +% +% Aviad Rubinstein, Indiana University 2005/2007 + +% intialize +n = length(in); +k = sum(in); +inInv = zeros(k,1); +outInv = inInv; +iIn = 1; iOut = 1; + +for i = 1:n + inInv(iIn:iIn+in(i) - 1) = i; + outInv(iOut:iOut+out(i) - 1) = i; + iIn = iIn+in(i); + iOut = iOut+out(i); +end + +cij = eye(n); +edges = [outInv(1:k)'; inInv(randperm(k))']; + +% create cij, and check for double edges and self-connections +for i = 1:k + if cij(edges(1,i),edges(2,i)) + warningCounter = 1; + while (1) + switchTo = ceil(k*rand); + if ~(cij(edges(1,i),edges(2,switchTo)) || cij(edges(1,switchTo),edges(2,i))) + cij(edges(1,i),edges(2,switchTo)) = 1; + if switchTo < i + cij(edges(1,switchTo),edges(2,switchTo)) = 0; + cij(edges(1,switchTo),edges(2,i)) = 1; + end + temp = edges(2,i); + edges(2,i) = edges(2,switchTo); + edges(2,switchTo) = temp; + break + end + warningCounter = warningCounter+1; + % If there is a legitimate subtitution, it has a probability of 1/k of being done. + % Thus it is highly unlikely that it will not be done after 2*k^2 attempts. + % This is an indication that the given indegree / outdegree + % vectors may not be possible. + if warningCounter == 2*k^2 + flag = 0; % no valid solution found + return; + end + end + else + cij(edges(1,i),edges(2,i)) = 1; + end +end + +cij = cij - eye(n); + +% a valid solution was found +flag = 1; \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/makeringlatticeCIJ.m b/DefaultData/2019_03_03_BCT/makeringlatticeCIJ.m new file mode 100755 index 0000000..9faf359 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/makeringlatticeCIJ.m @@ -0,0 +1,47 @@ +function [CIJ] = makeringlatticeCIJ(N,K) +%MAKERINGLATTICECIJ Synthetic lattice network +% +% CIJ = makeringlatticeCIJ(N,K); +% +% This function generates a directed lattice network with toroidal +% boundary counditions (i.e. with ring-like "wrapping around"). +% +% Inputs: N, number of vertices +% K, number of edges +% +% Outputs: CIJ, connection matrix +% +% Note: The lattice is made by placing connections as close as possible +% to the main diagonal, with wrapping around. No connections are made +% on the main diagonal. In/Outdegree is kept approx. constant at K/N. +% +% +% Olaf Sporns, Indiana University, 2005/2007 + +% initialize +CIJ = zeros(N); +CIJ1 = ones(N); +KK = 0; +cnt = 0; +seq = 1:N-1; +seq2 = N-1:-1:1; + +% fill in +while (KK0) + [i,j] = find(dCIJ); + rp = randperm(length(i)); + for ii=1:overby + CIJ(i(rp(ii)),j(rp(ii))) = 0; + end; +end; diff --git a/DefaultData/2019_03_03_BCT/maketoeplitzCIJ.m b/DefaultData/2019_03_03_BCT/maketoeplitzCIJ.m new file mode 100755 index 0000000..baf9424 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/maketoeplitzCIJ.m @@ -0,0 +1,29 @@ +function [CIJ] = maketoeplitzCIJ(N,K,s) +%MAKETOEPLITZCIJ A synthetic directed network with Gaussian drop-off of +% connectivity with distance +% +% CIJ = maketoeprandCIJ(N,K,s) +% +% This function generates a directed network with a Gaussian drop-off in +% edge density with increasing distance from the main diagonal. There are +% toroidal boundary counditions (i.e. no ring-like "wrapping around"). +% +% Inputs: N, number of vertices +% K, number of edges +% s, standard deviation of toeplitz +% +% Output: CIJ, connection matrix +% +% Note: no connections are placed on the main diagonal. +% +% +% Olaf Sporns, Indiana University, 2005/2007 + +profile = normpdf(1:N-1,0.5,s); +template = toeplitz([0 profile],[0 profile]); +template = template.*(K./sum(sum(template))); +CIJ = zeros(N); + +while ((sum(sum(CIJ)) ~= K)) + CIJ = (rand(N)tol + error('cannot find eigenvalue of 1. Minimum eigenvalue value is %0.6f. Tolerance was set at %0.6f',aux(index)+1,tol); +end + +w = V(:,index)'; %left-eigen vector associated to eigenvalue of 1. +w = w/sum(w); %rescale of left-eigen vector to the sum of it (hence is now in probabilites form. The inverse of this vector is the return-times vector + +W = repmat(w,n,1); %convert column-vector w to a full matrix W by making copies of w. +I = eye(n,n); %Identity matrix I is computed + +Z = inv(I-P+W); %Fundamental matrix Z is computed + +MFPT = (repmat(diag(Z)',n,1)-Z)./W; % this performs MFPT(i,j)=(Z(j,j)-Z(i,j))/w(j) in a matricial way. Corresponds to theorem 11.16 pag. 459 +% r = 1./w; %as demostrated in theorem 11.15 pag. 455. Each entry r_i is the 'mean-recurrence' or 'return-time' of state i (node i when states represent nodes of a graph) \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/mleme_constraint_model.m b/DefaultData/2019_03_03_BCT/mleme_constraint_model.m new file mode 100755 index 0000000..c666737 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/mleme_constraint_model.m @@ -0,0 +1,284 @@ +function [W0, E0, P0, Delt0] = mleme_constraint_model(samp, W, M, Lo, Li, Lm, opts) +%MLEME_CONSTRAINT_MODEL Unbiased sampling of networks with soft constraints +% +% W0 = mleme_constraint_model(samp, W); +% W0 = mleme_constraint_model(samp, W, M); +% W0 = mleme_constraint_model(samp, W, M, Lo, Li, Lm); +% [W0, E0, P0, Delt0] = mleme_constraint_model(samp, W, M, Lo, Li, Lm, opts); +% +% This function returns an ensemble of unbiasedly sampled networks with +% weighted node-strength and module-weight constraints. These constraints +% are soft in that they are satisfied on average for the full network +% ensemble but not, in general, for each individual network. +% +% Inputs (for a network with n nodes, m modules and c constraints): +% +% samp, Number of networks to sample. +% +% W, (length n) square directed and weighted connectivity +% matrix. All weights must be nonnegative integers. Note that +% real-valued weights may be converted to integers with +% arbitrary precision through rescaling and rounding, e.g. +% W_int = round(10^precision * W_real). +% +% M, (length n) module affiliation vector. This vector is often +% obtained as the output of a community detection algorithm. +% The vector must contain nonnegative integers, with zeros +% specifying nodes which are not part of any community. This +% input may be left empty if there are no module constraints. +% +% Lo, (length n) out-strength constraint logical vector. This +% vector specifies out-strength constraints for each node. +% Alternatively, it is possible to specify 1 to constrain all +% out-strengths or 0 for no constraints. Empty or no input +% results in default behavour (no constraints). +% +% Lo, (length n) in-strength constraint logical vector. This +% vector specifies in-strength constraints for each node. +% Alternatively, it is possible to specify 1 to constrain all +% in-strengths or 0 for no constraints. Empty or no input +% results in default behavour (no constraints). +% +% Lm, (length m) module-weight constraint logical matrix. This +% matrix specifies module-weight constraints for all pairs of +% modules. Alternatively, it is possible to specify +% 2 to constrain all inter-module and intra-module weights, +% 1 to constrain all intra-module weights, or 0 for no +% constraints. Empty or no input results in default behavour +% (no constraints). +% +% opts, optional argument: pass optimization and display options with optimset. +% Default: optimset('MaxFunEvals', 1e6*c, 'MaxIter', 1e6, 'Display', 'iter'); +% +% +% Outputs: +% W0, an ensemble of sampled networks with constraints. +% +% E0, expected weights matrix. +% +% P0, probability matrix. +% +% Delt0, algorithm convergence error. +% +% +% Algorithm: +% Maximum-likelihood estimation of network probability +% distribution by numerical solution of systems of nonlinear +% equations, and sampling of individual networks directly +% from this distribution. +% +% +% Notes: +% Empirical connection weights are +% not preserved. Constraint errors are guaranteed to vanish +% in the limit of the full network ensemble. +% +% +% Examples: +% % get community structure of a weighted network W +% M = community_louvain(W, 2); +% +% % specify node and module constraints +% n = length(W); % number of nodes +% m = max(M); % number of modules +% Lo = true(n, 1); % out-strength constraints +% Li = true(n, 1); % in-strength constraints +% Lm = eye(m); % module-weight constraints +% +% % sample networks with the above constraints +% [W0, E0, P0, Delt0] = mleme_constraint_model(samp, W, M, Lo, Li, Lm); +% +% % equivalent formulation +% [W0, E0, P0, Delt0] = mleme_constraint_model(samp, W, M, 1, 1, 1); +% +% % alternative: sample networks with average weight constraints only +% [W0, E0, P0, Delt0] = mleme_constraint_model(samp, W); +% +% +% References: Squartini and Garlaschelli (2011) New J Phys 13:083001 +% Rubinov (2016) Nat Commun 7:13812 +% +% +% 2016, Mika Rubinov, Janelia HHMI + +% Modification History +% Dec 2016: Original. + +n = length(W); % number of nodes + +if ~exist('M', 'var') || isempty(M) + if exist('Lm', 'var') && any(Lm) + error('Need module affiliation vector for module constraints') + else + M = zeros(n, 1); + end +end + +m = max(M); % number of modules + +if ~isequal(W, int64(W)) || min(W(:))<0 + error('W must only contain nonnegative integers.') +end +if ~isequal(M, int64(M)) || min(M(:))<0 + error('M must only contain nonnegative integers.') +end + +% process node constraints +if ~exist('Lo','var') || isempty(Lo) || isequal(Lo,0) + Lo = false(n, 1); +elseif isequal(Lo, 1) + Lo = true(n, 1); +end +if ~exist('Li','var') + Li = Lo; +elseif isempty(Li) || isequal(Li, 0) + Li = false(n, 1); +elseif isequal(Li, 1) + Li = true(n, 1); +end + +% process module constraints +if ~exist('Lm','var') || isempty(Lm) || isequal(Lm,0) + Lm = false(m); +elseif isequal(Lm, 2) + Lm = true(m); +elseif isequal(Lm, 1) + Lm = diag(true(m, 1)); +end +if any(~M) + m = m + 1; + M(~M) = m; + Lm(m, m) = 0; % add a new row and column for nodes without modules +end + +Lo = logical(Lo(:)); +Li = logical(Li(:)); +Lm = logical(Lm(:)); +ao = numel(Lo); +ai = numel(Li); +am = numel(Lm); +uo = nnz(Lo); +ui = nnz(Li); +um = nnz(Lm); +Mij = bsxfun(@plus, M, (M.'-1)*m); + +f_ex = @(V) system_equations(V, Mij, Lo, Li, Lm, ao, ai, am, uo, ui, um); +f_cx = @(W) system_constraints(W, M, Lo, Li, Lm, uo, ui, um); + +C = f_cx(W); +c = 1 + uo + ui + um; +if ~exist('V','var') + V = mean2(W)/(1+mean2(W))*ones(c,1); +end + +assert(c == numel(C)); +assert(c == numel(V)); + +if ~exist('opts', 'var') || isempty(opts) + opts = optimset('MaxFunEvals', 1e6*c, 'MaxIter', 1e6, 'Display', 'iter'); +end + +V0 = fsolve(@(V) C - f_cx(f_ex(V)), V, opts); + +[E0, P0] = f_ex(V0); +Delt0 = C - f_cx(f_ex(V0)); + +W0 = sample_networks(P0, samp); + +end + + +function CellW0 = sample_networks(P0, samp) + +if ~exist('samp', 'var') + samp = 1; +end + +n = length(P0); + +CellW0 = cell(samp, 1); +for i = 1:samp + W0 = zeros(n); + L0 = ~eye(n); + l0 = nnz(L0); + while l0 + L0(L0) = P0(L0) > rand(l0,1); + W0(L0) = W0(L0) + 1; + l0 = nnz(L0); + end + CellW0{i} = W0; +end + +end + + +function [W, P] = system_equations(V, Mij, Lo, Li, Lm, ao, ai, am, uo, ui, um) + +X = ones(ao, 1); +Y = ones(ai, 1); +Z = ones(am, 1); + +if uo + offset = 1; + X(Lo) = V(offset + (1:uo)); +end +if ui + offset = 1 + uo; + Y(Li) = V(offset + (1:ui)); +end +if um + offset = 1 + uo + ui; + Z(Lm) = V(offset + (1:um)); +end + +P = V(1) .* (X * Y.') .* Z(Mij); % V(1) is the total weight +P(P>1) = 1 - eps; + +W = P ./ (1 - P); +W(1:length(W)+1:end) = 0; + +end + + +function C = system_constraints(W, M, Lo, Li, Lm, uo, ui, um) + +if nargin == 0 + C = @block_density; + return; +end + +if uo + So = sum(W(Lo,:), 2); +else + So = []; +end +if ui + Si = sum(W(:,Li), 1).'; +else + Si = []; +end +if um + Wm = block_density(W, M, Lm); +else + Wm = []; +end + +C = [sum(sum(W)); So; Si; Wm]; + +end + + +function Wm = block_density(W, M, Lwm) + +m = max(M); + +Wm = zeros(m*m, 1); +for u = 1:m + for v = 1:m + Wm(u + (v-1)*m) = sum(sum(W(M==u, M==v))); + end +end + +Wm = Wm(Lwm); + +end diff --git a/DefaultData/2019_03_03_BCT/modularity_dir.m b/DefaultData/2019_03_03_BCT/modularity_dir.m new file mode 100755 index 0000000..acf0fff --- /dev/null +++ b/DefaultData/2019_03_03_BCT/modularity_dir.m @@ -0,0 +1,124 @@ +function [Ci,Q]=modularity_dir(A,gamma) +%MODULARITY_DIR Optimal community structure and modularity +% +% Ci = modularity_dir(W); +% [Ci Q] = modularity_dir(W); +% +% The optimal community structure is a subdivision of the network into +% nonoverlapping groups of nodes in a way that maximizes the number of +% within-group edges, and minimizes the number of between-group edges. +% The modularity is a statistic that quantifies the degree to which the +% network may be subdivided into such clearly delineated groups. +% +% Inputs: +% W, +% directed weighted/binary connection matrix +% gamma, +% resolution parameter (optional) +% gamma>1, detects smaller modules +% 0<=gamma<1, detects larger modules +% gamma=1, classic modularity (default) +% +% Outputs: +% Ci, optimal community structure +% Q, maximized modularity +% +% Note: +% This algorithm is essentially deterministic. The only potential +% source of stochasticity occurs at the iterative finetuning step, in +% the presence of non-unique optimal swaps. However, the present +% implementation always makes the first available optimal swap and +% is therefore deterministic. +% +% References: +% Leicht and Newman (2008) Phys Rev Lett 100:118703. +% Reichardt and Bornholdt (2006) Phys Rev E 74:016110. +% +% 2008-2016 +% Mika Rubinov, UNSW +% Jonathan Power, WUSTL +% Dani Bassett, UCSB +% Xindi Wang, Beijing Normal University +% Roan LaPlante, Martinos Center for Biomedical Imaging + +% Modification History: +% Jul 2008: Original (Mika Rubinov) +% Oct 2008: Positive eigenvalues made insufficient for division (Jonathan Power) +% Dec 2008: Fine-tuning made consistent with Newman's description (Jonathan Power) +% Dec 2008: Fine-tuning vectorized (Mika Rubinov) +% Sep 2010: Node identities permuted (Dani Bassett) +% Dec 2013: Gamma resolution parameter included (Mika Rubinov) +% Dec 2013: Detection of maximum real part of eigenvalues enforced (Mika Rubinov) +% Thanks to Mason Porter and Jack Setford, University of Oxford +% Dec 2015: Single moves during fine-tuning enforced (Xindi Wang) +% Jan 2017: Removed node permutation and updated documentation (Roan LaPlante) + +if ~exist('gamma','var') + gamma = 1; +end + +N=length(A); %number of vertices +% n_perm = randperm(N); %DB: randomly permute order of nodes +% A = A(n_perm,n_perm); %DB: use permuted matrix for subsequent analysis +Ki=sum(A,1); %in-degree +Ko=sum(A,2); %out-degree +m=sum(Ki); %number of edges +b=A-gamma*(Ko*Ki).'/m; +B=b+b.'; %directed modularity matrix +Ci=ones(N,1); %community indices +cn=1; %number of communities +U=[1 0]; %array of unexamined communites + +ind=1:N; +Bg=B; +Ng=N; + +while U(1) %examine community U(1) + [V,D]=eig(Bg); + [~,i1]=max(real(diag(D))); %maximal positive (real part of) eigenvalue of Bg + v1=V(:,i1); %corresponding eigenvector + + S=ones(Ng,1); + S(v1<0)=-1; + q=S.'*Bg*S; %contribution to modularity + + if q>1e-10 %contribution positive: U(1) is divisible + qmax=q; %maximal contribution to modularity + Bg(logical(eye(Ng)))=0; %Bg is modified, to enable fine-tuning + indg=ones(Ng,1); %array of unmoved indices + Sit=S; + while any(indg) %iterative fine-tuning + Qit=qmax-4*Sit.*(Bg*Sit); %this line is equivalent to: + [qmax,imax]=max(Qit.*indg); %for i=1:Ng + Sit(imax)=-Sit(imax); % Sit(i)=-Sit(i); + indg(imax)=nan; % Qit(i)=Sit.'*Bg*Sit; + if qmax>q % Sit(i)=-Sit(i); + q=qmax; %end + S=Sit; + end + end + + if abs(sum(S))==Ng %unsuccessful splitting of U(1) + U(1)=[]; + else + cn=cn+1; + Ci(ind(S==1))=U(1); %split old U(1) into new U(1) and into cn + Ci(ind(S==-1))=cn; + U=[cn U]; %#ok + end + else %contribution nonpositive: U(1) is indivisible + U(1)=[]; + end + + ind=find(Ci==U(1)); %indices of unexamined community U(1) + bg=B(ind,ind); + Bg=bg-diag(sum(bg)); %modularity matrix for U(1) + Ng=length(ind); %number of vertices in U(1) +end + +s=Ci(:,ones(1,N)); %compute modularity +Q=~(s-s.').*B/(2*m); +Q=sum(Q(:)); +% Ci_corrected = zeros(N,1); % DB: initialize Ci_corrected +% Ci_corrected(n_perm) = Ci; % DB: return order of nodes to the order used at the input stage. +% Ci = Ci_corrected; % DB: output corrected community assignments diff --git a/DefaultData/2019_03_03_BCT/modularity_und.m b/DefaultData/2019_03_03_BCT/modularity_und.m new file mode 100755 index 0000000..ad33fe0 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/modularity_und.m @@ -0,0 +1,122 @@ +function [Ci,Q]=modularity_und(A,gamma) +%MODULARITY_UND Optimal community structure and modularity +% +% Ci = modularity_und(W); +% [Ci Q] = modularity_und(W,gamma); +% +% The optimal community structure is a subdivision of the network into +% nonoverlapping groups of nodes in a way that maximizes the number of +% within-group edges, and minimizes the number of between-group edges. +% The modularity is a statistic that quantifies the degree to which the +% network may be subdivided into such clearly delineated groups. +% +% Inputs: +% W, +% undirected weighted/binary connection matrix +% gamma, +% resolution parameter (optional) +% gamma>1, detects smaller modules +% 0<=gamma<1, detects larger modules +% gamma=1, classic modularity (default) +% +% Outputs: +% Ci, optimal community structure +% Q, maximized modularity +% +% Note: +% This algorithm is essentially deterministic. The only potential +% source of stochasticity occurs at the iterative finetuning step, in +% the presence of non-unique optimal swaps. However, the present +% implementation always makes the first available optimal swap and +% is therefore deterministic. +% +% References: +% Newman (2006) -- Phys Rev E 74:036104, PNAS 23:8577-8582. +% Reichardt and Bornholdt (2006) Phys Rev E 74:016110. +% +% 2008-2016 +% Mika Rubinov, UNSW +% Jonathan Power, WUSTL +% Dani Bassett, UCSB +% Xindi Wang, Beijing Normal University +% Roan LaPlante, Martinos Center for Biomedical Imaging + +% Modification History: +% Jul 2008: Original (Mika Rubinov) +% Oct 2008: Positive eigenvalues made insufficient for division (Jonathan Power) +% Dec 2008: Fine-tuning made consistent with Newman's description (Jonathan Power) +% Dec 2008: Fine-tuning vectorized (Mika Rubinov) +% Sep 2010: Node identities permuted (Dani Bassett) +% Dec 2013: Gamma resolution parameter included (Mika Rubinov) +% Dec 2013: Detection of maximum real part of eigenvalues enforced (Mika Rubinov) +% Thanks to Mason Porter and Jack Setford, University of Oxford +% Dec 2015: Single moves during fine-tuning enforced (Xindi Wang) +% Jan 2017: Removed node permutation and updated documentation (Roan LaPlante) + +if ~exist('gamma','var') + gamma = 1; +end + +N=length(A); %number of vertices +% n_perm = randperm(N); %DB: randomly permute order of nodes +% A = A(n_perm,n_perm); %DB: use permuted matrix for subsequent analysis +K=sum(A); %degree +m=sum(K); %number of edges (each undirected edge is counted twice) +B=A-gamma*(K.'*K)/m; %modularity matrix +Ci=ones(N,1); %community indices +cn=1; %number of communities +U=[1 0]; %array of unexamined communites + +ind=1:N; +Bg=B; +Ng=N; + +while U(1) %examine community U(1) + [V,D]=eig(Bg); + [~,i1]=max(real(diag(D))); %maximal positive (real part of) eigenvalue of Bg + v1=V(:,i1); %corresponding eigenvector + + S=ones(Ng,1); + S(v1<0)=-1; + q=S.'*Bg*S; %contribution to modularity + + if q>1e-10 %contribution positive: U(1) is divisible + qmax=q; %maximal contribution to modularity + Bg(logical(eye(Ng)))=0; %Bg is modified, to enable fine-tuning + indg=ones(Ng,1); %array of unmoved indices + Sit=S; + while any(indg) %iterative fine-tuning + Qit=qmax-4*Sit.*(Bg*Sit); %this line is equivalent to: + [qmax,imax]=max(Qit.*indg); %for i=1:Ng + Sit(imax)=-Sit(imax); % Sit(i)=-Sit(i); + indg(imax)=nan; % Qit(i)=Sit.'*Bg*Sit; + if qmax>q % Sit(i)=-Sit(i); + q=qmax; %end + S=Sit; + end + end + + if abs(sum(S))==Ng %unsuccessful splitting of U(1) + U(1)=[]; + else + cn=cn+1; + Ci(ind(S==1))=U(1); %split old U(1) into new U(1) and into cn + Ci(ind(S==-1))=cn; + U=[cn U]; %#ok + end + else %contribution nonpositive: U(1) is indivisible + U(1)=[]; + end + + ind=find(Ci==U(1)); %indices of unexamined community U(1) + bg=B(ind,ind); + Bg=bg-diag(sum(bg)); %modularity matrix for U(1) + Ng=length(ind); %number of vertices in U(1) +end + +s=Ci(:,ones(1,N)); %compute modularity +Q=~(s-s.').*B/m; +Q=sum(Q(:)); +% Ci_corrected = zeros(N,1); % DB: initialize Ci_corrected +% Ci_corrected(n_perm) = Ci; % DB: return order of nodes to the order used at the input stage. +% Ci = Ci_corrected; % DB: output corrected community assignments diff --git a/DefaultData/2019_03_03_BCT/module_degree_zscore.m b/DefaultData/2019_03_03_BCT/module_degree_zscore.m new file mode 100755 index 0000000..4742b7d --- /dev/null +++ b/DefaultData/2019_03_03_BCT/module_degree_zscore.m @@ -0,0 +1,41 @@ +function Z=module_degree_zscore(W,Ci,flag) +%MODULE_DEGREE_ZSCORE Within-module degree z-score +% +% Z=module_degree_zscore(W,Ci,flag); +% +% The within-module degree z-score is a within-module version of degree +% centrality. +% +% Inputs: W, binary/weighted, directed/undirected connection matrix +% Ci, community affiliation vector +% flag, 0, undirected graph (default) +% 1, directed graph: out-degree +% 2, directed graph: in-degree +% 3, directed graph: out-degree and in-degree +% +% Output: Z, within-module degree z-score. +% +% Reference: Guimera R, Amaral L. Nature (2005) 433:895-900. +% +% +% Mika Rubinov, UNSW, 2008-2010 + +if ~exist('flag','var') + flag=0; +end + +switch flag + case 0 % no action required + case 1 % no action required + case 2; W=W.'; + case 3; W=W+W.'; +end + +n=length(W); %number of vertices +Z=zeros(n,1); +for i=1:max(Ci) + Koi=sum(W(Ci==i,Ci==i),2); + Z(Ci==i)=(Koi-mean(Koi))./std(Koi); +end + +Z(isnan(Z))=0; \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/motif3funct_bin.m b/DefaultData/2019_03_03_BCT/motif3funct_bin.m new file mode 100755 index 0000000..74dafe7 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/motif3funct_bin.m @@ -0,0 +1,78 @@ +function [f,F]=motif3funct_bin(A) +%MOTIF3FUNCT_BIN Frequency of functional class-3 motifs +% +% [f,F] = motif3funct_bin(A); +% +% *Structural motifs* are patterns of local connectivity in complex +% networks. In contrast, *functional motifs* are all possible subsets of +% patterns of local connectivity embedded within structural motifs. Such +% patterns are particularly diverse in directed networks. The motif +% frequency of occurrence around an individual node is known as the motif +% fingerprint of that node. The total motif frequency of occurrence in +% the whole network is correspondingly known as the motif fingerprint of +% the network. +% +% Input: A, binary directed connection matrix +% +% Output: F, node motif frequency fingerprint +% f, network motif frequency fingerprint +% +% Notes: +% 1. The function find_motif34.m outputs the motif legend. +% 2. There is a source of possible confusion in motif terminology. +% Motifs ("structural" and "functional") are most frequently +% considered only in the context of anatomical brain networks +% (Sporns and Kötter, 2004). On the other hand, motifs are not +% commonly studied in undirected networks, due to the paucity of +% local undirected connectivity patterns. +% +% References: Milo et al. (2002) Science 298:824-827 +% Sporns O, Kötter R (2004) PLoS Biol 2: e369 +% +% +% Mika Rubinov, UNSW/U Cambridge, 2007-2015 + +% Modification History: +% 2007: Original +% 2015: Improved documentation + +persistent M3 ID3 N3 +if isempty(N3) + load motif34lib M3 ID3 N3 %load motif data +end + +n=length(A); %number of vertices in A +f=zeros(13,1); %motif count for whole graph +F=zeros(13,n); %frequency + +A=1*(A~=0); %adjacency matrix +As=A|A.'; %symmetrized adjacency + +for u=1:n-2 %loop u 1:n-2 + V1=[false(1,u) As(u,u+1:n)]; %v1: neibs of u (>u) + for v1=find(V1) + V2=[false(1,u) As(v1,u+1:n)]; %v2: all neibs of v1 (>u) + V2(V1)=0; %not already in V1 + V2=([false(1,v1) As(u,v1+1:n)])|V2; %and all neibs of u (>v1) + for v2=find(V2) + a=[A(v1,u);A(v2,u);A(u,v1);A(v2,v1);A(u,v2);A(v1,v2)]; + ind=(M3*a)==N3; %find all contained isomorphs + id=ID3(ind); + + [idu,j]=unique(id); %unique motif occurences + j=[0;j]; %#ok + mu=length(idu); %number of unique motifs + f2=zeros(mu,1); + + for h=1:mu %for each unique motif + f2(h)=j(h+1)-j(h); %and frequencies + end + + %then add to cumulative count + f(idu)=f(idu)+f2; + if nargout==2 + F(idu,[u v1 v2])=F(idu,[u v1 v2])+[f2 f2 f2]; + end + end + end +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/motif3funct_wei.m b/DefaultData/2019_03_03_BCT/motif3funct_wei.m new file mode 100755 index 0000000..bab969d --- /dev/null +++ b/DefaultData/2019_03_03_BCT/motif3funct_wei.m @@ -0,0 +1,101 @@ +function [I,Q,F]=motif3funct_wei(W) +%MOTIF3FUNCT_WEI Intensity and coherence of functional class-3 motifs +% +% [I,Q,F] = motif3funct_wei(W); +% +% *Structural motifs* are patterns of local connectivity in complex +% networks. In contrast, *functional motifs* are all possible subsets of +% patterns of local connectivity embedded within structural motifs. Such +% patterns are particularly diverse in directed networks. The motif +% frequency of occurrence around an individual node is known as the motif +% fingerprint of that node. The motif intensity and coherence are +% weighted generalizations of the motif frequency. The motif +% intensity is equivalent to the geometric mean of weights of links +% comprising each motif. The motif coherence is equivalent to the ratio +% of geometric and arithmetic means of weights of links comprising each +% motif. +% +% Input: W, weighted directed connection matrix +% (all weights must be between 0 and 1) +% +% Output: I, node motif intensity fingerprint +% Q, node motif coherence fingerprint +% F, node motif frequency fingerprint +% +% Notes: +% 1. The function find_motif34.m outputs the motif legend. +% 2. Average intensity and coherence are given by I./F and Q./F +% 3. All weights must be between 0 and 1. This may be achieved using +% the weight_conversion.m function, as follows: +% W_nrm = weight_conversion(W, 'normalize'); +% 4. There is a source of possible confusion in motif terminology. +% Motifs ("structural" and "functional") are most frequently +% considered only in the context of anatomical brain networks +% (Sporns and Kötter, 2004). On the other hand, motifs are not +% commonly studied in undirected networks, due to the paucity of +% local undirected connectivity patterns. +% +% References: Onnela et al. (2005), Phys Rev E 71:065103 +% Milo et al. (2002) Science 298:824-827 +% Sporns O, Kötter R (2004) PLoS Biol 2: e369 +% +% +% Mika Rubinov, UNSW/U Cambridge, 2007-2015 + +% Modification History: +% 2007: Original +% 2015: Improved documentation + +persistent M3 ID3 N3 +if isempty(N3) + load motif34lib M3 ID3 N3 %load motif data +end + +n=length(W); %number of vertices in W +I=zeros(13,n); %intensity +Q=zeros(13,n); %coherence +F=zeros(13,n); %frequency + +A=1*(W~=0); %adjacency matrix +As=A|A.'; %symmetrized adjacency + +for u=1:n-2 %loop u 1:n-2 + V1=[false(1,u) As(u,u+1:n)]; %v1: neibs of u (>u) + for v1=find(V1) + V2=[false(1,u) As(v1,u+1:n)]; %v2: all neibs of v1 (>u) + V2(V1)=0; %not already in V1 + V2=([false(1,v1) As(u,v1+1:n)])|V2; %and all neibs of u (>v1) + for v2=find(V2) + w=[W(v1,u) W(v2,u) W(u,v1) W(v2,v1) W(u,v2) W(v1,v2)]; + a=[A(v1,u);A(v2,u);A(u,v1);A(v2,v1);A(u,v2);A(v1,v2)]; + ind=(M3*a)==N3; %find all contained isomorphs + m=sum(ind); %number of isomorphs + + M=M3(ind,:).*repmat(w,m,1); + id=ID3(ind); + l=N3(ind); + + x=sum(M,2)./l; %arithmetic mean + M(M==0)=1; %enable geometric mean + i=prod(M,2).^(1./l); %intensity + q=i./x; %coherence + + [idu,j]=unique(id); %unique motif occurences + j=[0;j]; %#ok + mu=length(idu); %number of unique motifs + i2=zeros(mu,1); + q2=i2; f2=i2; + + for h=1:mu %for each unique motif + i2(h)=sum(i(j(h)+1:j(h+1))); %sum all intensities, + q2(h)=sum(q(j(h)+1:j(h+1))); %coherences + f2(h)=j(h+1)-j(h); %and frequencies + end + + %then add to cumulative count + I(idu,[u v1 v2])=I(idu,[u v1 v2])+[i2 i2 i2]; + Q(idu,[u v1 v2])=Q(idu,[u v1 v2])+[q2 q2 q2]; + F(idu,[u v1 v2])=F(idu,[u v1 v2])+[f2 f2 f2]; + end + end +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/motif3struct_bin.m b/DefaultData/2019_03_03_BCT/motif3struct_bin.m new file mode 100755 index 0000000..3653a73 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/motif3struct_bin.m @@ -0,0 +1,56 @@ +function [f,F]=motif3struct_bin(A) +%MOTIF3STRUCT_BIN Frequency of structural class-3 motifs +% +% [f,F] = motif3struct_bin(A); +% +% Structural motifs are patterns of local connectivity in complex +% networks. Such patterns are particularly diverse in directed networks. +% The motif frequency of occurrence around an individual node is known as +% the motif fingerprint of that node. The total motif frequency of +% occurrence in the whole network is correspondingly known as the +% motif fingerprint of the network. +% +% Input: A, binary directed connection matrix +% +% Output: F, node motif frequency fingerprint +% f, network motif frequency fingerprint +% +% Note: The function find_motif34.m outputs the motif legend. +% +% References: Milo et al. (2002) Science 298:824-827 +% Sporns O, Kötter R (2004) PLoS Biol 2: e369 +% +% +% Mika Rubinov, UNSW/U Cambridge, 2007-2015 + +% Modification History: +% 2007: Original +% 2015: Improved documentation + +persistent M3n ID3 +if isempty(ID3) + load motif34lib M3n ID3 %load motif data +end + +n=length(A); %number of vertices in A +F=zeros(13,n); %motif count of each vertex +f=zeros(13,1); %motif count for whole graph +As=A|A.'; %symmetrized adjacency matrix + + +for u=1:n-2 %loop u 1:n-2 + V1=[false(1,u) As(u,u+1:n)]; %v1: neibs of u (>u) + for v1=find(V1) + V2=[false(1,u) As(v1,u+1:n)]; %v2: all neibs of v1 (>u) + V2(V1)=0; %not already in V1 + V2=([false(1,v1) As(u,v1+1:n)])|V2; %and all neibs of u (>v1) + for v2=find(V2) + + s=uint32(sum(10.^(5:-1:0).*[A(v1,u) A(v2,u) A(u,v1)... + A(v2,v1) A(u,v2) A(v1,v2)])); + ind=ID3(s==M3n); + if nargout==2; F(ind,[u v1 v2])=F(ind,[u v1 v2])+1; end + f(ind)=f(ind)+1; + end + end +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/motif3struct_wei.m b/DefaultData/2019_03_03_BCT/motif3struct_wei.m new file mode 100755 index 0000000..7d72068 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/motif3struct_wei.m @@ -0,0 +1,80 @@ +function [I,Q,F]=motif3struct_wei(W) +%MOTIF3STRUCT_WEI Intensity and coherence of structural class-3 motifs +% +% [I,Q,F] = motif3struct_wei(W); +% +% Structural motifs are patterns of local connectivity in complex +% networks. Such patterns are particularly diverse in directed networks. +% The motif frequency of occurrence around an individual node is known as +% the motif fingerprint of that node. The motif intensity and coherence +% are weighted generalizations of the motif frequency. The motif +% intensity is equivalent to the geometric mean of weights of links +% comprising each motif. The motif coherence is equivalent to the ratio +% of geometric and arithmetic means of weights of links comprising each +% motif. +% +% Input: W, weighted directed connection matrix +% (all weights must be between 0 and 1) +% +% Output: I, node motif intensity fingerprint +% Q, node motif coherence fingerprint +% F, node motif frequency fingerprint +% +% Notes: +% 1. The function find_motif34.m outputs the motif legend. +% 2. Average intensity and coherence are given by I./F and Q./F +% 3. All weights must be between 0 and 1. This may be achieved using +% the weight_conversion.m function, as follows: +% W_nrm = weight_conversion(W, 'normalize'); +% +% References: Onnela et al. (2005), Phys Rev E 71:065103 +% Milo et al. (2002) Science 298:824-827 +% Sporns O, Kötter R (2004) PLoS Biol 2: e369% +% +% +% Mika Rubinov, UNSW/U Cambridge, 2007-2015 + +% Modification History: +% 2007: Original +% 2015: Improved documentation + +persistent M3 M3n ID3 N3 +if isempty(N3) + load motif34lib M3 M3n ID3 N3 %load motif data +end + +n=length(W); %number of vertices in W +I=zeros(13,n); %intensity +Q=zeros(13,n); %coherence +F=zeros(13,n); %frequency + +A=1*(W~=0); %adjacency matrix +As=A|A.'; %symmetrized adjacency + +for u=1:n-2 %loop u 1:n-2 + V1=[false(1,u) As(u,u+1:n)]; %v1: neibs of u (>u) + for v1=find(V1) + V2=[false(1,u) As(v1,u+1:n)]; %v2: all neibs of v1 (>u) + V2(V1)=0; %not already in V1 + V2=([false(1,v1) As(u,v1+1:n)])|V2; %and all neibs of u (>v1) + for v2=find(V2) + w=[W(v1,u) W(v2,u) W(u,v1) W(v2,v1) W(u,v2) W(v1,v2)]; + s=uint32(sum(10.^(5:-1:0).*[A(v1,u) A(v2,u) A(u,v1)... + A(v2,v1) A(u,v2) A(v1,v2)])); + ind=(s==M3n); + + M=w.*M3(ind,:); + id=ID3(ind); + l=N3(ind); + x=sum(M,2)/l; %arithmetic mean + M(M==0)=1; %enable geometric mean + i=prod(M,2)^(1/l); %intensity + q=i/x; %coherence + + %then add to cumulative count + I(id,[u v1 v2])=I(id,[u v1 v2])+[i i i]; + Q(id,[u v1 v2])=Q(id,[u v1 v2])+[q q q]; + F(id,[u v1 v2])=F(id,[u v1 v2])+[1 1 1]; + end + end +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/motif4funct_bin.m b/DefaultData/2019_03_03_BCT/motif4funct_bin.m new file mode 100755 index 0000000..34b4e4c --- /dev/null +++ b/DefaultData/2019_03_03_BCT/motif4funct_bin.m @@ -0,0 +1,88 @@ +function [f,F]=motif4funct_bin(A) +%MOTIF4FUNCT_BIN Frequency of functional class-4 motifs +% +% [f,F] = motif4funct_bin(A); +% +% *Structural motifs* are patterns of local connectivity in complex +% networks. In contrast, *functional motifs* are all possible subsets of +% patterns of local connectivity embedded within structural motifs. Such +% patterns are particularly diverse in directed networks. The motif +% frequency of occurrence around an individual node is known as the motif +% fingerprint of that node. The total motif frequency of occurrence in +% the whole network is correspondingly known as the motif fingerprint of +% the network. +% +% Input: A, binary directed connection matrix +% +% Output: F, node motif frequency fingerprint +% f, network motif frequency fingerprint +% +% Notes: +% 1. The function find_motif34.m outputs the motif legend. +% 2. There is a source of possible confusion in motif terminology. +% Motifs ("structural" and "functional") are most frequently +% considered only in the context of anatomical brain networks +% (Sporns and Kötter, 2004). On the other hand, motifs are not +% commonly studied in undirected networks, due to the paucity of +% local undirected connectivity patterns. +% +% References: Milo et al. (2002) Science 298:824-827 +% Sporns O, Kötter R (2004) PLoS Biol 2: e369 +% +% +% Mika Rubinov, UNSW/U Cambridge, 2007-2015 + +% Modification History: +% 2007: Original +% 2015: Improved documentation + +persistent M4 ID4 N4 +if isempty(N4) + load motif34lib M4 ID4 N4 %load motif data +end + +n=length(A); %number of vertices in A +f=zeros(199,1); +F=zeros(199,n); %frequency + +A=1*(A~=0); %adjacency matrix +As=A|A.'; %symmetrized adjacency + +for u=1:n-3 %loop u 1:n-2 + V1=[false(1,u) As(u,u+1:n)]; %v1: neibs of u (>u) + for v1=find(V1) + V2=[false(1,u) As(v1,u+1:n)]; %v2: all neibs of v1 (>u) + V2(V1)=0; %not already in V1 + V2=V2|([false(1,v1) As(u,v1+1:n)]); %and all neibs of u (>v1) + for v2=find(V2) + vz=max(v1,v2); %vz: largest rank node + V3=([false(1,u) As(v2,u+1:n)]); %v3: all neibs of v2 (>u) + V3(V2)=0; %not already in V1&V2 + V3=V3|([false(1,v2) As(v1,v2+1:n)]);%and all neibs of v1 (>v2) + V3(V1)=0; %not already in V1 + V3=V3|([false(1,vz) As(u,vz+1:n)]); %and all neibs of u (>vz) + for v3=find(V3) + + a=[A(v1,u);A(v2,u);A(v3,u);A(u,v1);A(v2,v1);A(v3,v1);... + A(u,v2);A(v1,v2);A(v3,v2);A(u,v3);A(v1,v3);A(v2,v3)]; + ind=(M4*a)==N4; %find all contained isomorphs + id=ID4(ind); + + [idu,j]=unique(id); %unique motif occurences + j=[0;j]; %#ok + mu=length(idu); %number of unique motifs + f2=zeros(mu,1); + + for h=1:mu %for each unique motif + f2(h)=j(h+1)-j(h); %and frequencies + end + + %then add to cumulative count + f(idu)=f(idu)+f2; + if nargout==2 + F(idu,[u v1 v2 v3])=F(idu,[u v1 v2 v3])+[f2 f2 f2 f2]; + end + end + end + end +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/motif4funct_wei.m b/DefaultData/2019_03_03_BCT/motif4funct_wei.m new file mode 100755 index 0000000..c55e102 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/motif4funct_wei.m @@ -0,0 +1,111 @@ +function [I,Q,F]=motif4funct_wei(W) +%MOTIF4FUNCT_WEI Intensity and coherence of functional class-4 motifs +% +% [I,Q,F] = motif4funct_wei(W); +% +% *Structural motifs* are patterns of local connectivity in complex +% networks. In contrast, *functional motifs* are all possible subsets of +% patterns of local connectivity embedded within structural motifs. Such +% patterns are particularly diverse in directed networks. The motif +% frequency of occurrence around an individual node is known as the motif +% fingerprint of that node. The motif intensity and coherence are +% weighted generalizations of the motif frequency. The motif +% intensity is equivalent to the geometric mean of weights of links +% comprising each motif. The motif coherence is equivalent to the ratio +% of geometric and arithmetic means of weights of links comprising each +% motif. +% +% Input: W, weighted directed connection matrix +% (all weights must be between 0 and 1) +% +% Output: I, node motif intensity fingerprint +% Q, node motif coherence fingerprint +% F, node motif frequency fingerprint +% +% Notes: +% 1. The function find_motif34.m outputs the motif legend. +% 2. Average intensity and coherence are given by I./F and Q./F +% 3. All weights must be between 0 and 1. This may be achieved using +% the weight_conversion.m function, as follows: +% W_nrm = weight_conversion(W, 'normalize'); +% 4. There is a source of possible confusion in motif terminology. +% Motifs ("structural" and "functional") are most frequently +% considered only in the context of anatomical brain networks +% (Sporns and Kötter, 2004). On the other hand, motifs are not +% commonly studied in undirected networks, due to the paucity of +% local undirected connectivity patterns. +% +% References: Onnela et al. (2005), Phys Rev E 71:065103 +% Milo et al. (2002) Science 298:824-827 +% Sporns O, Kötter R (2004) PLoS Biol 2: e369% +% +% +% Mika Rubinov, UNSW/U Cambridge, 2007-2015 + +% Modification History: +% 2007: Original +% 2015: Improved documentation + +persistent M4 ID4 N4 +if isempty(N4) + load motif34lib M4 ID4 N4 %load motif data +end + +n=length(W); %number of vertices in W +I=zeros(199,n); %intensity +Q=zeros(199,n); %coherence +F=zeros(199,n); %frequency + +A=1*(W~=0); %adjacency matrix +As=A|A.'; %symmetrized adjacency + +for u=1:n-3 %loop u 1:n-2 + V1=[false(1,u) As(u,u+1:n)]; %v1: neibs of u (>u) + for v1=find(V1) + V2=[false(1,u) As(v1,u+1:n)]; %v2: all neibs of v1 (>u) + V2(V1)=0; %not already in V1 + V2=V2|([false(1,v1) As(u,v1+1:n)]); %and all neibs of u (>v1) + for v2=find(V2) + vz=max(v1,v2); %vz: largest rank node + V3=([false(1,u) As(v2,u+1:n)]); %v3: all neibs of v2 (>u) + V3(V2)=0; %not already in V1&V2 + V3=V3|([false(1,v2) As(v1,v2+1:n)]);%and all neibs of v1 (>v2) + V3(V1)=0; %not already in V1 + V3=V3|([false(1,vz) As(u,vz+1:n)]); %and all neibs of u (>vz) + for v3=find(V3) + + w=[W(v1,u) W(v2,u) W(v3,u) W(u,v1) W(v2,v1) W(v3,v1)... + W(u,v2) W(v1,v2) W(v3,v2) W(u,v3) W(v1,v3) W(v2,v3)]; + a=[A(v1,u);A(v2,u);A(v3,u);A(u,v1);A(v2,v1);A(v3,v1);... + A(u,v2);A(v1,v2);A(v3,v2);A(u,v3);A(v1,v3);A(v2,v3)]; + ind=(M4*a)==N4; %find all contained isomorphs + m=sum(ind); %number of isomorphs + + M=M4(ind,:).*repmat(w,m,1); + id=ID4(ind); + l=N4(ind); + x=sum(M,2)./l; %arithmetic mean + M(M==0)=1; %enable geometric mean + i=prod(M,2).^(1./l); %intensity + q=i./x; %coherence + + [idu,j]=unique(id); %unique motif occurences + j=[0;j]; %#ok + mu=length(idu); %number of unique motifs + i2=zeros(mu,1); + q2=i2; f2=i2; + + for h=1:mu %for each unique motif + i2(h)=sum(i(j(h)+1:j(h+1))); %sum all intensities, + q2(h)=sum(q(j(h)+1:j(h+1))); %coherences + f2(h)=j(h+1)-j(h); %and frequencies + end + + %then add to cumulative count + I(idu,[u v1 v2 v3])=I(idu,[u v1 v2 v3])+[i2 i2 i2 i2]; + Q(idu,[u v1 v2 v3])=Q(idu,[u v1 v2 v3])+[q2 q2 q2 q2]; + F(idu,[u v1 v2 v3])=F(idu,[u v1 v2 v3])+[f2 f2 f2 f2]; + end + end + end +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/motif4struct_bin.m b/DefaultData/2019_03_03_BCT/motif4struct_bin.m new file mode 100755 index 0000000..5e3bd7a --- /dev/null +++ b/DefaultData/2019_03_03_BCT/motif4struct_bin.m @@ -0,0 +1,65 @@ +function [f,F]=motif4struct_bin(A) +%MOTIF4STRUCT_BIN Frequency of structural class-4 motifs +% +% [f,F] = motif4struct_bin(A); +% +% Structural motifs are patterns of local connectivity in complex +% networks. Such patterns are particularly diverse in directed networks. +% The motif frequency of occurrence around an individual node is known as +% the motif fingerprint of that node. The total motif frequency of +% occurrence in the whole network is correspondingly known as the +% motif fingerprint of the network. +% +% Input: A, binary directed connection matrix +% +% Output: F, node motif frequency fingerprint +% f, network motif frequency fingerprint +% +% Note: The function find_motif34.m outputs the motif legend. +% +% References: Milo et al. (2002) Science 298:824-827 +% Sporns O, Kötter R (2004) PLoS Biol 2: e369 +% +% +% Mika Rubinov, UNSW/U Cambridge, 2007-2015 + +% Modification History: +% 2007: Original +% 2015: Improved documentation + + +persistent M4n ID4 +if isempty(ID4) + load motif34lib M4n ID4 %load motif data +end + +n=length(A); %number of vertices in A +F=zeros(199,n); %motif count of each vertex +f=zeros(199,1); %motif count for whole graph +As=A|A.'; %symmetric adjacency matrix + +for u=1:n-3 %loop u 1:n-2 + V1=[false(1,u) As(u,u+1:n)]; %v1: neibs of u (>u) + for v1=find(V1) + V2=[false(1,u) As(v1,u+1:n)]; %v2: all neibs of v1 (>u) + V2(V1)=0; %not already in V1 + V2=V2|([false(1,v1) As(u,v1+1:n)]); %and all neibs of u (>v1) + for v2=find(V2) + vz=max(v1,v2); %vz: largest rank node + V3=([false(1,u) As(v2,u+1:n)]); %v3: all neibs of v2 (>u) + V3(V2)=0; %not already in V1&V2 + V3=V3|([false(1,v2) As(v1,v2+1:n)]);%and all neibs of v1 (>v2) + V3(V1)=0; %not already in V1 + V3=V3|([false(1,vz) As(u,vz+1:n)]); %and all neibs of u (>vz) + for v3=find(V3) + + s=uint64(sum(10.^(11:-1:0).*[A(v1,u) A(v2,u) A(v3,u)... + A(u,v1) A(v2,v1) A(v3,v1) A(u,v2) A(v1,v2)... + A(v3,v2) A(u,v3) A(v1,v3) A(v2,v3)])); + ind=ID4(s==M4n); + if nargout==2; F(ind,[u v1 v2 v3])=F(ind,[u v1 v2 v3])+1; end + f(ind)=f(ind)+1; + end + end + end +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/motif4struct_wei.m b/DefaultData/2019_03_03_BCT/motif4struct_wei.m new file mode 100755 index 0000000..2083596 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/motif4struct_wei.m @@ -0,0 +1,91 @@ +function [I,Q,F]=motif4struct_wei(W) +%MOTIF4STRUCT_WEI Intensity and coherence of structural class-4 motifs +% +% [I,Q,F] = motif4struct_wei(W); +% +% Structural motifs are patterns of local connectivity in complex +% networks. Such patterns are particularly diverse in directed networks. +% The motif frequency of occurrence around an individual node is known as +% the motif fingerprint of that node. The motif intensity and coherence +% are weighted generalizations of the motif frequency. The motif +% intensity is equivalent to the geometric mean of weights of links +% comprising each motif. The motif coherence is equivalent to the ratio +% of geometric and arithmetic means of weights of links comprising each +% motif. +% +% Input: W, weighted directed connection matrix +% (all weights must be between 0 and 1) +% +% Output: I, node motif intensity fingerprint +% Q, node motif coherence fingerprint +% F, node motif frequency fingerprint +% +% Notes: +% 1. The function find_motif34.m outputs the motif legend. +% 2. Average intensity and coherence are given by I./F and Q./F +% 3. All weights must be between 0 and 1. This may be achieved using +% the weight_conversion.m function, as follows: +% W_nrm = weight_conversion(W, 'normalize'); +% +% References: Onnela et al. (2005), Phys Rev E 71:065103 +% Milo et al. (2002) Science 298:824-827 +% Sporns O, Kötter R (2004) PLoS Biol 2: e369% +% +% +% Mika Rubinov, UNSW/U Cambridge, 2007-2015 + +% Modification History: +% 2007: Original +% 2015: Improved documentation + +persistent M4 M4n ID4 N4 +if isempty(N4) + load motif34lib M4 M4n ID4 N4 %load motif data +end + +n=length(W); %number of vertices in W +I=zeros(199,n); %intensity +Q=zeros(199,n); %coherence +F=zeros(199,n); %frequency + +A=1*(W~=0); %adjacency matrix +As=A|A.'; %symmetrized adjacency + +for u=1:n-3 %loop u 1:n-2 + V1=[false(1,u) As(u,u+1:n)]; %v1: neibs of u (>u) + for v1=find(V1) + V2=[false(1,u) As(v1,u+1:n)]; %v2: all neibs of v1 (>u) + V2(V1)=0; %not already in V1 + V2=V2|([false(1,v1) As(u,v1+1:n)]); %and all neibs of u (>v1) + for v2=find(V2) + vz=max(v1,v2); %vz: largest rank node + V3=([false(1,u) As(v2,u+1:n)]); %v3: all neibs of v2 (>u) + V3(V2)=0; %not already in V1&V2 + V3=V3|([false(1,v2) As(v1,v2+1:n)]);%and all neibs of v1 (>v2) + V3(V1)=0; %not already in V1 + V3=V3|([false(1,vz) As(u,vz+1:n)]); %and all neibs of u (>vz) + for v3=find(V3) + + w=[W(v1,u) W(v2,u) W(v3,u) W(u,v1) W(v2,v1) W(v3,v1)... + W(u,v2) W(v1,v2) W(v3,v2) W(u,v3) W(v1,v3) W(v2,v3)]; + s=uint64(sum(10.^(11:-1:0).*[A(v1,u) A(v2,u) A(v3,u)... + A(u,v1) A(v2,v1) A(v3,v1) A(u,v2) A(v1,v2)... + A(v3,v2) A(u,v3) A(v1,v3) A(v2,v3)])); + ind=(s==M4n); + + M=w.*M4(ind,:); + id=ID4(ind); + l=N4(ind); + x=sum(M,2)/l; %arithmetic mean + M(M==0)=1; %enable geometric mean + i=prod(M,2)^(1/l); %intensity + q=i/x; %coherence + + %then add to cumulative count + I(id,[u v1 v2 v3])=I(id,[u v1 v2 v3])+[i i i i]; + Q(id,[u v1 v2 v3])=Q(id,[u v1 v2 v3])+[q q q q]; + F(id,[u v1 v2 v3])=F(id,[u v1 v2 v3])+[1 1 1 1]; + end + end + end +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/navigation_wu.m b/DefaultData/2019_03_03_BCT/navigation_wu.m new file mode 100755 index 0000000..f3a1cae --- /dev/null +++ b/DefaultData/2019_03_03_BCT/navigation_wu.m @@ -0,0 +1,112 @@ +function [sr, PL_bin, PL_wei, PL_dis, paths] = navigation_wu(L, D, max_hops) + +% Navigation of connectivity length matrix L guided by nodal distance D +% +% % Navigation +% [sr, PL_bin, PL_wei] = navigation_wu(L,D); +% % Binary shortest path length +% sp_PL_bin = distance_bin(L); +% % Weighted shortest path length +% sp_PL_wei = distance_wei_floyd(L); +% % Binary efficiency ratio +% er_bin = mean(mean(sp_PL_bin./PL_bin)); +% % Weighted efficiency ratio +% er_wei = mean(mean(sp_PL_wei./PL_wei)); +% +% *** Inputs: +% +% L - Weighted/unweighted directed/undirected NxN SC matrix of connection *lengths* +% L(i,j) is the strength-to-length remapping of the connection weight +% between i and j. L(i,j) = 0 denotes the lack of a connection between i +% and j. +% +% D - Symmetric NxN nodal distance matrix (e.g., Euclidean distance between node centroids) +% +% max_hops (optional) - Limits the maximum number of hops of navigation +% paths +% +% *** Outputs: +% +% sr - The success ratio (scalar) is the proportion of node pairs +% successfully reached by navigation. +% +% PL_bin - NxN matrix of binary navigation path length (i.e., number of hops in +% navigation paths). Infinte values indicate failed navigation paths. +% +% PL_wei - NxN matrix of weighted navigation path length (i.e., sum of connection +% weights--as defined by C--along navigaiton path). Infinte values indicate failed navigation paths. +% +% PL_dis - NxN matrix of distance-based navigation path length (i.e., sum of connection +% distances--as defined by D--along navigaiton path). Infinte values indicate failed navigation paths. +% +% paths - NxN cell of nodes comprising navigation paths. +% +% *** Reference: Seguin et al. (2018) PNAS. +% +% Caio Seguin, University of Melbourne, 2017 + + if nargin == 2 + max_hops = length(L); + end + + N = size(L, 1); + paths = cell(N); + PL_bin = zeros(N); + PL_wei = zeros(N); + PL_dis = zeros(N); + + for i = 1:N + for j = 1:N + if (i ~= j) + + curr_node = i; + last_node = curr_node; + target = j; + paths{i,j} = curr_node; + + pl_bin = 0; + pl_wei = 0; + pl_dis = 0; + + while (curr_node ~= target) + + neighbors = find(L(curr_node,:) ~= 0); + + [~, min_index] = min(D(target, neighbors)); + + next_node = neighbors(min_index); + + if isempty(next_node) || next_node == last_node || pl_bin > max_hops + + pl_bin = Inf; + pl_wei = Inf; + pl_dis = Inf; + break; + + end + + paths{i,j} = [paths{i,j} next_node]; + pl_bin = pl_bin + 1; + pl_wei = L(curr_node, next_node) + pl_wei; + pl_dis = D(curr_node, next_node) + pl_dis; + + last_node = curr_node; + curr_node = next_node; + + end + + PL_bin(i,j) = pl_bin; + PL_wei(i,j) = pl_wei; + PL_dis(i,j) = pl_dis; + + end + end + end + + PL_bin(1:N+1:end) = Inf; + PL_wei(1:N+1:end) = Inf; + PL_dis(1:N+1:end) = Inf; + + sr = 1 - (length(find(PL_bin == Inf)) - N)/(N*N - N); + +end diff --git a/DefaultData/2019_03_03_BCT/null_model_dir_sign.m b/DefaultData/2019_03_03_BCT/null_model_dir_sign.m new file mode 100755 index 0000000..2332f30 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/null_model_dir_sign.m @@ -0,0 +1,188 @@ +function [W0, R] = null_model_dir_sign(W,bin_swaps,wei_freq) +%NULL_MODEL_DIR_SIGN Directed random graphs with preserved weight, +% degree and strength distributions +% +% W0 = null_model_dir_sign(W); +% W0 = null_model_dir_sign(W,bin_swaps); +% W0 = null_model_dir_sign(W,bin_swaps,wei_freq); +% [W0 R] = null_model_dir_sign(W,bin_swaps,wei_freq); +% +% This function randomizes an directed network with positive and +% negative weights, while preserving the degree and strength +% distributions. This function calls randmio_dir_signed.m +% +% Inputs: W, Directed weighted connection matrix +% bin_swaps, Average number of swaps of each edge in binary randomization. +% bin_swap=5 is the default (each edge rewired 5 times) +% bin_swap=0 implies no binary randomization +% wei_freq, Frequency of weight sorting in weighted randomization +% wei_freq must be in the range of: 0 < wei_freq <= 1 +% wei_freq=1 implies that weights are sorted at each step +% (default in older [<2011] versions of MATLAB) +% wei_freq=0.1 implies that weights are sorted at each 10th step +% (faster, default in newer versions of MATLAB) +% +% Output: W0, Randomized weighted connection matrix +% R, Correlation coefficients between strength sequences +% of input and output connection matrices +% +% Notes: +% The value of bin_swaps is ignored when binary topology is fully +% connected (e.g. when the network has no negative weights). +% Randomization may be better (and execution time will be slower) for +% higher values of bin_swaps and wei_freq. Higher values of bin_swaps may +% enable a more random binary organization, and higher values of wei_freq +% may enable a more accurate conservation of strength sequences. +% R are the correlation coefficients between positive and negative +% in-strength and out-strength sequences of input and output connection +% matrices and are used to evaluate the accuracy with which strengths +% were preserved. Note that correlation coefficients may be a rough +% measure of strength-sequence accuracy and one could implement more +% formal tests (such as the Kolmogorov-Smirnov test) if desired. +% +% Example usage: +% +% %Create random directed weights matrix +% +% W=randn(100); +% +% %Compute one instance of null model (slow execution time): +% %bin_swaps=5, rewire each binary edge 5 times on average +% %wei_freq=1, sort all edges at every step +% +% tic; [W0_slow R_slow]=null_model_dir_sign(W,5,1); R_slow, toc +% +% R_slow = +% 0.9795 0.9724 0.9772 0.9773 +% Elapsed time is 3.485388 seconds. +% +% %Compute another instance of of null model (fast execution time): +% %bin_swaps=5, rewire each binary edge 5 times on average +% %wei_freq=0.1, sort all edges at every 10th step (10=1/0.1) +% +% tic; [W0_fast R_fast]=null_model_dir_sign(W,5,0.1); R_fast, toc +% +% R_fast = +% 0.9655 0.9652 0.9717 0.9804 +% Elapsed time is 0.763831 seconds. +% +% +% Reference: Rubinov and Sporns (2011) Neuroimage 56:2068-79 +% +% +% 2011-2015, Mika Rubinov, U Cambridge + +% Modification History +% Mar 2011: Original. +% Sep 2012: Edge-sorting acceleration. +% Dec 2015: Enforce preservation of negative degrees in sparse +% networks with negative weights (thanks to Andrew Zalesky). + +%#ok<*ASGLU> + +if ~exist('bin_swaps','var') + bin_swaps=5; +end +if ~exist('wei_freq','var') + if nargin('randperm')==1 + wei_freq=1; + else + wei_freq=0.1; + end +end + +if wei_freq<=0 || wei_freq>1 + error('wei_freq must be in the range of: 0 < wei_freq <= 1.') +end +if wei_freq && wei_freq<1 && nargin('randperm')==1 + warning('wei_freq may only equal 1 in older (<2011) versions of MATLAB.') + wei_freq=1; +end + +n=size(W,1); %number of nodes +W(1:n+1:end)=0; %clear diagonal +Ap = W>0; %positive adjacency matrix +An = W<0; %negative adjacency matrix + +if nnz(Ap)<(n*(n-1)) %if Ap is not full + W_r = randmio_dir_signed(W,bin_swaps); + Ap_r = W_r>0; + An_r = W_r<0; +else + Ap_r = Ap; + An_r = An; +end + +W0=zeros(n); %null model network +for s=[1 -1] + switch s %switch sign (positive/negative) + case 1 + Si=sum(W.*Ap,1).'; %positive in-strength + So=sum(W.*Ap,2); %positive out-strength + Wv=sort(W(Ap)); %sorted weights vector + [I, J]=find(Ap_r); %weights indices + Lij=n*(J-1)+I; %linear weights indices + case -1 + Si=sum(-W.*An,1).'; %negative in-strength + So=sum(-W.*An,2); %negative out-strength + Wv=sort(-W(An)); %sorted weights vector + [I, J]=find(An_r); %weights indices + Lij=n*(J-1)+I; %linear weights indices + end + + P=(So*Si.'); %expected weights matrix + + if wei_freq==1 + for m=numel(Wv):-1:1 %iteratively explore all weights + [dum, Oind]=sort(P(Lij)); %get indices of Lij that sort P + r=ceil(rand*m); + o=Oind(r); %choose random index of sorted expected weight + W0(Lij(o)) = s*Wv(r); %assign corresponding sorted weight at this index + + f = 1 - Wv(r)/So(I(o)); %readjust expected weight probabilities for node I(o) + P(I(o),:) = P(I(o),:)*f; %[1 - Wv(r)/S(I(o)) = (S(I(o)) - Wv(r))/S(I(o))] + f = 1 - Wv(r)/Si(J(o)); %readjust expected weight probabilities for node J(o) + P(:,J(o)) = P(:,J(o))*f; %[1 - Wv(r)/S(J(o)) = (S(J(o)) - Wv(r))/S(J(o))] + + So(I(o)) = So(I(o)) - Wv(r); %readjust in-strength of node I(o) + Si(J(o)) = Si(J(o)) - Wv(r); %readjust out-strength of node J(o) + Lij(o)=[]; %remove current index from further consideration + I(o)=[]; + J(o)=[]; + Wv(r)=[]; %remove current weight from further consideration + end + else + wei_period = round(1/wei_freq); %convert frequency to period + for m=numel(Wv):-wei_period:1 %iteratively explore at the given period + [dum, Oind]=sort(P(Lij)); %get indices of Lij that sort P + R=randperm(m,min(m,wei_period)).'; + + O=Oind(R); %choose random index of sorted expected weight + W0(Lij(O)) = s*Wv(R); %assign corresponding sorted weight at this index + + WAi = accumarray(I(O),Wv(R),[n,1]); + Iu = any(WAi,2); + F = 1 - WAi(Iu)./So(Iu); %readjust expected weight probabilities for node I(o) + P(Iu,:) = P(Iu,:).*F(:,ones(1,n)); %[1 - Wv(r)/S(I(o)) = (S(I(o)) - Wv(r))/S(I(o))] + So(Iu) = So(Iu) - WAi(Iu); %readjust in-strength of node I(o) + + WAj = accumarray(J(O),Wv(R),[n,1]); + Ju = any(WAj,2); + F = 1 - WAj(Ju)./Si(Ju); %readjust expected weight probabilities for node J(o) + P(:,Ju) = P(:,Ju).*F(:,ones(1,n)).'; %[1 - Wv(r)/S(J(o)) = (S(J(o)) - Wv(r))/S(J(o))] + Si(Ju) = Si(Ju) - WAj(Ju); %readjust out-strength of node J(o) + + O=Oind(R); + Lij(O)=[]; %remove current index from further consideration + I(O)=[]; + J(O)=[]; + Wv(R)=[]; %remove current weight from further consideration + end + end +end + +rpos_in=corrcoef(sum( W.*(W>0),1), sum( W0.*(W0>0),1) ); +rpos_ou=corrcoef(sum( W.*(W>0),2), sum( W0.*(W0>0),2) ); +rneg_in=corrcoef(sum(-W.*(W<0),1), sum(-W0.*(W0<0),1) ); +rneg_ou=corrcoef(sum(-W.*(W<0),2), sum(-W0.*(W0<0),2) ); +R=[rpos_in(2) rpos_ou(2) rneg_in(2) rneg_ou(2)]; diff --git a/DefaultData/2019_03_03_BCT/null_model_und_sign.m b/DefaultData/2019_03_03_BCT/null_model_und_sign.m new file mode 100755 index 0000000..7653df1 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/null_model_und_sign.m @@ -0,0 +1,181 @@ +function [W0,R] = null_model_und_sign(W,bin_swaps,wei_freq) +%NULL_MODEL_UND_SIGN Random graphs with preserved weight, degree and +% strength distributions +% +% W0 = null_model_und_sign(W); +% W0 = null_model_und_sign(W,bin_swaps); +% W0 = null_model_und_sign(W,bin_swaps,wei_freq); +% [W0 R] = null_model_und_sign(W,bin_swaps,wei_freq); +% +% This function randomizes an undirected network with positive and +% negative weights, while preserving the degree and strength +% distributions. This function calls randmio_und_signed.m +% +% Inputs: W, Undirected weighted connection matrix +% bin_swaps, Average number of swaps of each edge in binary randomization. +% bin_swap=5 is the default (each edge rewired 5 times) +% bin_swap=0 implies no binary randomization +% wei_freq, Frequency of weight sorting in weighted randomization +% wei_freq must be in the range of: 0 < wei_freq <= 1 +% wei_freq=1 implies that weights are resorted at each step +% (default in older [<2011] versions of MATLAB) +% wei_freq=0.1 implies that weights are sorted at each 10th step +% (faster, default in newer versions of Matlab) +% +% Output: W0, Randomized weighted connection matrix +% R, Correlation coefficient between strength sequences +% of input and output connection matrices +% +% Notes: +% The value of bin_swaps is ignored when binary topology is fully +% connected (e.g. when the network has no negative weights). +% Randomization may be better (and execution time will be slower) for +% higher values of bin_swaps and wei_freq. Higher values of bin_swaps may +% enable a more random binary organization, and higher values of wei_freq +% may enable a more accurate conservation of strength sequences. +% R are the correlation coefficients between positive and negative +% strength sequences of input and output connection matrices and are +% used to evaluate the accuracy with which strengths were preserved. Note +% that correlation coefficients may be a rough measure of +% strength-sequence accuracy and one could implement more formal tests +% (such as the Kolmogorov-Smirnov test) if desired. +% +% Example usage: +% +% %Create random weights matrix +% +% W=tril(randn(100),-1); W=W+W.'; +% +% %Compute one instance of null model (slow execution time): +% %bin_swaps=5, rewire each binary edge 5 times on average +% %wei_freq=1, sort all edges at every step +% +% tic; [W0_slow R_slow]=null_model_und_sign(W,5,1); R_slow, toc +% +% R_slow = +% 0.9720 0.9746 +% Elapsed time is 2.112715 seconds. +% +% %Compute another instance of of null model (fast execution time): +% %bin_swaps=5, rewire each binary edge 5 times on average +% %wei_freq=0.1, sort all edges at every 10th step (10=1/0.1) +% +% tic; [W0_fast R_fast]=null_model_und_sign(W,5,0.1); R_fast, toc +% +% R_fast = +% 0.9623 0.9789 +% Elapsed time is 0.584797 seconds. +% +% +% Reference: Rubinov and Sporns (2011) Neuroimage 56:2068-79 +% +% +% 2011-2015, Mika Rubinov, U Cambridge + +% Modification History +% Mar 2011: Original +% Sep 2012: Edge-sorting acceleration +% Dec 2015: Enforce preservation of negative degrees in sparse +% networks with negative weights (thanks to Andrew Zalesky). + +%#ok<*ASGLU> + +if ~exist('bin_swaps','var') + bin_swaps=5; +end +if ~exist('wei_freq','var') + if nargin('randperm')==1 + wei_freq=1; + else + wei_freq=0.1; + end +end + +if wei_freq<=0 || wei_freq>1 + error('wei_freq must be in the range of: 0 < wei_freq <= 1.') +end +if wei_freq && wei_freq<1 && nargin('randperm')==1 + warning('wei_freq may only equal 1 in older (<2011) versions of MATLAB.') + wei_freq=1; +end + +n=size(W,1); %number of nodes +W(1:n+1:end)=0; %clear diagonal +Ap = W>0; %positive adjacency matrix +An = W<0; %negative adjacency matrix + +if nnz(Ap)<(n*(n-1)) %if Ap is not full + W_r = randmio_und_signed(W,bin_swaps); + Ap_r = W_r>0; + An_r = W_r<0; +else + Ap_r = Ap; + An_r = An; +end + +W0=zeros(n); %null model network +for s=[1 -1] + switch s %switch sign (positive/negative) + case 1 + S=sum(W.*Ap,2); %positive strength + Wv=sort(W(triu(Ap))); %sorted weights vector + [I,J]=find(triu(Ap_r)); %weights indices + Lij=n*(J-1)+I; %linear weights indices + case -1 + S=sum(-W.*An,2); %negative strength + Wv=sort(-W(triu(An))); %sorted weights vector + [I,J]=find(triu(An_r)); %weights indices + Lij=n*(J-1)+I; %linear weights indices + end + + P=(S*S.'); %expected weights matrix + + if wei_freq==1 + for m=numel(Wv):-1:1 %iteratively explore all weights + [dum,Oind]=sort(P(Lij)); %get indices of Lij that sort P + r=ceil(rand*m); + o=Oind(r); %choose random index of sorted expected weight + W0(Lij(o)) = s*Wv(r); %assign corresponding sorted weight at this index + + f = 1 - Wv(r)/S(I(o)); %readjust expected weight probabilities for node I(o) + P(I(o),:) = P(I(o),:)*f; %[1 - Wv(r)/S(I(o)) = (S(I(o)) - Wv(r))/S(I(o))] + P(:,I(o)) = P(:,I(o))*f; + f = 1 - Wv(r)/S(J(o)); %readjust expected weight probabilities for node J(o) + P(J(o),:) = P(J(o),:)*f; %[1 - Wv(r)/S(J(o)) = (S(J(o)) - Wv(r))/S(J(o))] + P(:,J(o)) = P(:,J(o))*f; + + S([I(o) J(o)]) = S([I(o) J(o)])-Wv(r); %readjust strengths of nodes I(o) and J(o) + Lij(o)=[]; %remove current index from further consideration + I(o)=[]; + J(o)=[]; + Wv(r)=[]; %remove current weight from further consideration + end + else + wei_period = round(1/wei_freq); %convert frequency to period + for m=numel(Wv):-wei_period:1 %iteratively explore at the given period + [dum,Oind]=sort(P(Lij)); %get indices of Lij that sort P + R=randperm(m,min(m,wei_period)).'; + O = Oind(R); + W0(Lij(O)) = s*Wv(R); %assign corresponding sorted weight at this index + + WA = accumarray([I(O);J(O)],Wv([R;R]),[n,1]); %cumulative weight + IJu = any(WA,2); + F = 1-WA(IJu)./S(IJu); + F = F(:,ones(1,n)); %readjust expected weight probabilities for node I(o) + P(IJu,:) = P(IJu,:).*F; %[1 - Wv(r)/S(I(o)) = (S(I(o)) - Wv(r))/S(I(o))] + P(:,IJu) = P(:,IJu).*F.'; + S(IJu) = S(IJu)-WA(IJu); %re-adjust strengths of nodes I(o) and J(o) + + O=Oind(R); + Lij(O)=[]; %remove current index from further consideration + I(O)=[]; + J(O)=[]; + Wv(R)=[]; %remove current weight from further consideration + end + end +end +W0=W0+W0.'; + +rpos=corrcoef(sum( W.*(W>0)),sum( W0.*(W0>0))); +rneg=corrcoef(sum(-W.*(W<0)),sum(-W0.*(W0<0))); +R=[rpos(2) rneg(2)]; diff --git a/DefaultData/2019_03_03_BCT/pagerank_centrality.m b/DefaultData/2019_03_03_BCT/pagerank_centrality.m new file mode 100755 index 0000000..94a76d2 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/pagerank_centrality.m @@ -0,0 +1,57 @@ +function r = pagerank_centrality(A, d, falff) +%PAGERANK_CENTRALITY PageRank centrality +% +% r = pagerank_centrality(A, d, falff) +% +% The PageRank centrality is a variant of eigenvector centrality. This +% function computes the PageRank centrality of each vertex in a graph. +% +% Formally, PageRank is defined as the stationary distribution achieved +% by instantiating a Markov chain on a graph. The PageRank centrality of +% a given vertex, then, is proportional to the number of steps (or amount +% of time) spent at that vertex as a result of such a process. +% +% The PageRank index gets modified by the addition of a damping factor, +% d. In terms of a Markov chain, the damping factor specifies the +% fraction of the time that a random walker will transition to one of its +% current state's neighbors. The remaining fraction of the time the +% walker is restarted at a random vertex. A common value for the damping +% factor is d = 0.85. +% +% Inputs: A, adjacency matrix +% d, damping factor +% falff, initial page rank probability (non-negative) +% +% Outputs: r, vectors of page rankings +% +% Note: The algorithm will work well for smaller matrices (number of +% nodes around 1000 or less) +% +% References: +% +% [1]. GeneRank: Using search engine technology for the analysis of +% microarray experiments, by Julie L. Morrison, Rainer Breitling, Desmond +% J. Higham and David R. Gilbert, BMC Bioinformatics, 6:233, 2005. +% [2]. Boldi P, Santini M, Vigna S (2009) PageRank: Functional +% dependencies. ACM Trans Inf Syst 27, 1-23. +% +% Xi-Nian Zuo, Institute of Psychology, Chinese Academy of Sciences, 2011 +% Rick Betzel, Indiana University, 2012 + +N = size(A,1); +if nargin < 3 + norm_falff = ones(N,1)/N; +else + falff = abs(falff); + norm_falff = falff/sum(falff); +end + +deg = sum(A); +ind = (deg == 0); +deg(ind) = 1; +D1 = zeros(N); +D1(1:(N+1):end) = 1./deg; +B = eye(N) - d*(A*D1); +b = (1-d)*norm_falff; +r = B\b; +r = r/sum(r); \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/participation_coef.m b/DefaultData/2019_03_03_BCT/participation_coef.m new file mode 100755 index 0000000..0ae4eb3 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/participation_coef.m @@ -0,0 +1,49 @@ +function P=participation_coef(W,Ci,flag) +%PARTICIPATION_COEF Participation coefficient +% +% P = participation_coef(W,Ci); +% +% Participation coefficient is a measure of diversity of intermodular +% connections of individual nodes. +% +% Inputs: W, binary/weighted, directed/undirected connection matrix +% Ci, community affiliation vector +% flag, 0, undirected graph (default) +% 1, directed graph: out-degree +% 2, directed graph: in-degree +% +% Output: P, participation coefficient +% +% Reference: Guimera R, Amaral L. Nature (2005) 433:895-900. +% +% +% 2008-2015 +% Mika Rubinov, UNSW/U Cambridge +% Alex Fornito, University of Melbourne + +% Modification History: +% Jul 2008: Original (Mika Rubinov) +% Mar 2011: Weighted-network bug fixes (Alex Fornito) +% Jan 2015: Generalized for in- and out-degree (Mika Rubinov) + +if ~exist('flag','var') + flag=0; +end + +switch flag + case 0 % no action required + case 1 % no action required + case 2; W=W.'; +end + +n=length(W); %number of vertices +Ko=sum(W,2); %degree +Gc=(W~=0)*diag(Ci); %neighbor community affiliation +Kc2=zeros(n,1); %community-specific neighbors + +for i=1:max(Ci) + Kc2=Kc2+(sum(W.*(Gc==i),2).^2); +end + +P=ones(n,1)-Kc2./(Ko.^2); +P(~Ko)=0; %P=0 if for nodes with no (out)neighbors diff --git a/DefaultData/2019_03_03_BCT/participation_coef_sign.m b/DefaultData/2019_03_03_BCT/participation_coef_sign.m new file mode 100755 index 0000000..7568efa --- /dev/null +++ b/DefaultData/2019_03_03_BCT/participation_coef_sign.m @@ -0,0 +1,47 @@ +function [Ppos,Pneg]=participation_coef_sign(W,Ci) +%PARTICIPATION_COEF_SIGN Participation coefficient +% +% [Ppos Pneg] = participation_coef_sign(W,Ci); +% +% Participation coefficient is a measure of diversity of intermodular +% connections of individual nodes. +% +% Inputs: W, undirected connection matrix with positive and +% negative weights +% +% Ci, community affiliation vector +% +% Output: Ppos, participation coefficient from positive weights +% +% Pneg, participation coefficient from negative weights +% +% Reference: Guimera R, Amaral L. Nature (2005) 433:895-900. +% +% +% 2011, Mika Rubinov, UNSW + +% Modification History: +% Mar 2011: Original +% Sep 2012: Fixed treatment of nodes with no negative strength +% (thanks to Alex Fornito and Martin Monti) + + +n=length(W); %number of vertices + +Ppos = pcoef( W.*(W>0)); +Pneg = pcoef(-W.*(W<0)); + + function P=pcoef(W_) + S = sum(W_,2); %strength + Gc = (W_~=0)*diag(Ci); %neighbor community affiliation + Sc2 = zeros(n,1); %community-specific neighbors + + for i = 1:max(Ci) + Sc2 = Sc2 + (sum(W_.*(Gc==i),2).^2); + end + + P = ones(n,1) - Sc2./(S.^2); + P(isnan(P)) = 0; + P(~P) = 0; %p_ind=0 if no (out)neighbors + end +end diff --git a/DefaultData/2019_03_03_BCT/partition_distance.m b/DefaultData/2019_03_03_BCT/partition_distance.m new file mode 100755 index 0000000..6c4fadf --- /dev/null +++ b/DefaultData/2019_03_03_BCT/partition_distance.m @@ -0,0 +1,97 @@ +function [VIn, MIn] = partition_distance(Cx, Cy) +%PARTITION_DISTANCE Distance or similarity between community partitions +% +% This function quantifies information-theoretic distance (normalized +% variation of information) or similarity (normalized mutual information) +% between community partitions. +% +% VIn = partition_distance(Cx); +% VIn = partition_distance(Cx, Cy); +% [VIn, MIn] = partition_distance(Cx, Cy); +% +% Inputs: +% Cx, +% Community partition vector or matrix of n rows and p columns, +% n is the number of network nodes, and p is the number of input +% community partitions (in the case of vector input p=1). +% +% Cy (optional argument), +% Community partition vector or matrix of n rows and q columns. n +% is the number of nodes (must be equal to the number of nodes in +% Cq) and q is the number of input community partitions (may be +% different to the number of nodes in Cq). This argument may be +% omitted, in which case, the partition distance is computed +% between all pairwise partitions of Cx. +% +% Outputs: +% VIn, +% Normalized variation of information ([p, q] matrix) +% +% MIn, +% Normalized mutual information ([p, q] matrix) +% +% Notes: +% Mathematical definitions. +% +% VIn = [H(X) + H(Y) - 2MI(X, Y)]/log(n) +% MIn = 2MI(X, Y) / [H(X) + H(Y)] +% +% where H is the entropy and MI is the mutual information +% +% +% Reference: Meila M (2007) J Multivar Anal 98, 873-895. +% +% +% 2011-2017, Mika Rubinov, UNSW, Janelia HHMI + +% Modification History: +% Mar 2011: Original +% Jan 2017: Added computation between input matrices. + +s = (nargin==1); +if s + Cy = Cx; + d = 10.^ceil(log10(double(1 + max( Cx(:)) ))); +else + d = 10.^ceil(log10(double(1 + max([Cx(:);Cy(:)]) ))); +end + +if ~isequal([Cx(:);Cy(:)], int64([Cx(:);Cy(:)])) || min([Cx(:);Cy(:)])<=0 + error('Input partitions must contain only positive integers.') +end + +[n, p] = size(Cx); +HX = zeros(p, 1); +for i = 1:p + Px = nonzeros(accumarray(Cx(:, i), 1)) / n; % P(x) + HX(i) = - sum(Px .* log(Px)); % H(x) +end + +if s + q = p; + HY = HX; +else + [n_, q] = size(Cy); + assert(n == n_); + HY = zeros(q, 1); + for j = 1:q + Py = nonzeros(accumarray(Cy(:, j), 1)) / n; % P(y) + HY(j) = - sum(Py .* log(Py)); % H(y) + end +end + +VIn = zeros(p, q); +MIn = zeros(p, q); +for i = 1:p + j_idx = (s * (i - 1) + 1):q; + for j = j_idx + Pxy = nonzeros(accumarray(d*Cx(:, i) + Cy(:, j), 1)) / n; % P(x,y) + Hxy = -sum(Pxy .* log(Pxy)); % H(x,y) + VIn(i, j) = (2 * Hxy - HX(i) - HY(j)) / log(n); % VIn + MIn(i, j) = 2 * (HX(i) + HY(j) - Hxy) / (HX(i) + HY(j)); % MIn + end + if s + VIn(j_idx, i) = VIn(i, j_idx); + MIn(j_idx, i) = MIn(i, j_idx); + end +end diff --git a/DefaultData/2019_03_03_BCT/path_transitivity.m b/DefaultData/2019_03_03_BCT/path_transitivity.m new file mode 100755 index 0000000..4cbe692 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/path_transitivity.m @@ -0,0 +1,84 @@ +function T=path_transitivity(W,transform) +% PATH_TRANSITIVITY Transitivity based on shortest paths +% +% T = path_transitivity(W,transform) +% +% This function computes the density of local detours (triangles) that +% are available along the shortest-paths between all pairs of nodes. +% +% Inputs: +% +% W, +% unweighted/weighted undirected connection *weight* OR *length* +% matrix. +% +% +% transform, +% If the input matrix is a connection *weight* matrix, specify a +% transform that map input connection weights to connection +% lengths. Two transforms are available. +% 'log' -> l_ij = -log(w_ij) +% 'inv' -> l_ij = 1/w_ij +% +% If the input matrix is a connection *length* matrix, do not +% specify a transform (or specify an empty transform argument). +% +% +% Output: +% +% T, +% matrix of pairwise path transitivity. +% +% +% Olaf Sporns, Andrea Avena-Koenigsberger and Joaquin Goñi, IU Bloomington, 2014 +% +% References: Goñi et al (2014) PNAS doi: 10.1073/pnas.131552911 +% + +if ~exist('transform','var') + transform = []; +end + +n=length(W); +m=zeros(n,n); +T=zeros(n,n); + +for i=1:n-1 + for j=i+1:n + x=0; + y=0; + z=0; + for k=1:n + if W(i,k)~=0 && W(j,k)~=0 && k~=i && k~=j + x=x+W(i,k)+W(j,k); + end + if k~=j + y=y+W(i,k); + end + if k~=i + z=z+W(j,k); + end + end + m(i,j)=x/(y+z); + end +end +m=m+m'; + +[~,hops,Pmat] = distance_wei_floyd(W,transform); + +% --- path transitivity ---%% +for i=1:n-1 + for j=i+1:n + x=0; + path = retrieve_shortest_path(i,j,hops,Pmat); + K=length(path); + + for t=1:K-1 + for l=t+1:K + x=x+m(path(t),path(l)); + end + end + T(i,j)=2*x/(K*(K-1)); + end +end +T=T+T'; diff --git a/DefaultData/2019_03_03_BCT/predict_fc.m b/DefaultData/2019_03_03_BCT/predict_fc.m new file mode 100755 index 0000000..d7fcc74 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/predict_fc.m @@ -0,0 +1,228 @@ +function [FCpre,FCcorr,beta,pred_data,R] = predict_fc(SC,FC,ED,pred_var,model) +% PREDICT_FC Prediction of functional connectivity from structural connectivity +% +% [FCpre,FCcorr,beta,pred_data,R] = predict_fc(SC,FC,ED,pred_var,model) +% [FCpre,FCcorr,beta] = predict_fc(SC,FC,[],{'SPLwei_log','SIwei_log'},'quadratic') +% +% This function computes regression coefficients to predict FC from +% structural-based measures that are used as predictor variables. +% +% Inputs: +% +% SC, +% Weighted/unweighted undirected NxN Structural Connectivity matrix. +% +% FC, +% Functional connections. FC can be a NxN symmetric matrix or an +% ((N*(N-1))/2) x 1 vector containing the upper triangular +% elements of the square FC matrix (excluding diagonal elements). +% +% ED, +% Euclidean distance matrix or upper triangular vector of the +% matrix (optional) +% +% pred_var, +% Set of M predictors. These can be given as an KxM array where +% K = ((N*(N-1))/2) and M is the number of predictors. +% Alternatively, pred_var can be a cell with the names of network +% measures to be used as predictors. Accepted network measure +% names are: +% SPLbin - Shortest-path length (binary) +% SPLwei_inv - Shortest-path length computed with an inv transform +% SPLwei_log - Shortest-path length computed with a log transform +% SPLdist - Shortest-path length computed with no transform +% SIbin - Search Information of binary shortest-paths +% SIwei_inv - Search Information of shortest-paths computed with an inv transform +% SIwei_log - Search Information of shortest-paths computed with a log transform +% SIdist - Search Information of shortest-paths computed with no transform +% T - Path Transitivity +% deltaMFPT - Column-wise z-scored mean first passage time +% neighOverlap - Neighborhood Overlap +% MI - Matching Index +% +% If no predictors are specified, the defaults are {'SPLwei_log', 'SIwei_log'}. +% +% model, +% Specifies the order of the regression model used within +% matlab's function regstats.m. 'model' can be any option +% accepted by matlab's regstats.m function (e.g. 'linear', +% 'interaction', 'quadratic', etc.) If no model is specified, +% 'linear' is the default. +% +% Output: +% +% FCpre, +% Predicted NxN Functional Connectivity matrix +% +% FCcorr, +% Pearson Correlation between PCpred and FC +% +% beta, +% Regression Coefficients +% +% pred_data, +% KxM array of predictors. +% +% R, +% Output from regstats.m (e.g. 'beta', 'yhat', 'rsquare', +% 'adjrsquare', 'tstat', 'r', 'mse', 'standres'). +% +% +% References: Goñi et al (2014) PNAS, 833–838, doi: 10.1073/pnas.1315529111 +% +% +% Andrea Avena-Koenigsberger and Joaquin Goñi, IU Bloomington, 2014 + +% Modification history +% 2012: Original +% 2016: Added more predictors and possibility of accepting predictor +% names as input. + +pred_names = {'SPLbin','SPLwei_inv','SPLwei_log','SPLdist','SIbin',... + 'SIwei_inv','SIwei_log','SIdist','T','deltaMFPT','neighOverlap','MI'}; + +N = size(SC,1); +indx = find(triu(ones(N),1)); + +% select model +if ~exist('model','var') + model = 'linear'; +end + +if ~exist('pred_var','var') && ~isempty(ED) + pred_var = {'ED','SPLwei_log','SI','T'}; + flag_var_names = true; + flag_ED = true; +elseif ~exist('pred_var','var') && isempty(ED) + pred_var = {'SPLwei_log','SI','T'}; + flag_var_names = true; +elseif exist('pred_var','var') && ~isnumeric(pred_var) && ~isempty(ED) + flag_var_names = true; + flag_ED = true; +elseif exist('pred_var','var') && ~isnumeric(pred_var) && isempty(ED) + flag_var_names = true; + flag_ED = false; +elseif exist('pred_var','var') && isnumeric(pred_var) && ~isempty(ED) + flag_var_names = false; + flag_ED = true; +elseif exist('pred_var','var') && isnumeric(pred_var) && isempty(ED) + flag_var_names = false; + flag_ED = false; +else + err_str = '"pred_var" must be an KxM array of M predictors, or any of the following graph-measure names:'; + s1 = sprintf('SPLbin - Shortest-path length (binary) \n'); + s2 = sprintf('SPLwei_inv - Shortest-path length computed with an inv transform \n'); + s3 = sprintf('SPLwei_log - Shortest-path length computed with a log transform \n'); + s4 = sprintf('SPLdist - Shortest-path length computed with no transform \n'); + s5 = sprintf('SIbin - Search Information of binary shortest-paths \n'); + s6 = sprintf('SIwei_inv - Search Information of shortest-paths computed with an inv transform \n'); + s7 = sprintf('SIwei_log - Search Information of shortest-paths computed with a log transform \n'); + s8 = sprintf('SIdist - Search Information of shortest-paths computed with no transform \n'); + s9 = sprintf('T - Path Transitivity \n'); + s10 = sprintf('deltaMFPT - Column-wise z-scored mean first passage time \n'); + s11 = sprintf('neighOverlap - Neighborhood Overlap \n'); + s12 = sprintf('MI - Matching Index \n'); + error('%s \n %s %s %s %s %s %s %s %s %s %s %s %s',err_str,s1,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11,s12); +end + +if flag_ED + [n1,n2] = size(ED); + if n1 == n2 && n1 == N + % square ED matrix + pred_data = ED(indx); + elseif n1 == length(indx) && n2 == 1 + % ED is already an upper-triangle vector + pred_data = ED; + else + error('ED must be square matrix or a vector containing the upper triangle of the square ED matrix \n') + end +else + pred_data = []; +end + + +if flag_var_names + fprintf('\n----------------------'); + fprintf('\n Selected predictors: \n'); + ind2start = size(pred_data,2); + pred_data = [pred_data,zeros(length(indx),length(pred_var))]; + + for v = 1:length(pred_var) + var_ind = find(strcmp(pred_var{v},pred_names)); + switch var_ind + + case 1 %SPLbin + fprintf('Shortest-path length (binary) \n\n'); + data = distance_wei_floyd(double(SC>0)); + case 2 %SPLwei_inv + fprintf('Shortest-path length computed with an inv transform \n'); + data = distance_wei_floyd(SC,'inv'); + case 3 %SPLwei_log + fprintf('Shortest-path length computed with a log transform \n'); + data = distance_wei_floyd(SC,'log'); + case 4 %SPLdist + fprintf('Shortest-path length computed with no transform \n'); + data = distance_wei_floyd(SC); + case 5 %SIbin + fprintf('Search Information of binary shortest-paths \n'); + data = search_information(double(SC>0)); + data = data + data'; + case 6 %SIwei_inv + fprintf('Search Information of shortest-paths computed with an inv transform \n'); + data = search_information(SC,'inv'); + data = data + data'; + case 7 %SIwei_log + fprintf('Search Information of shortest-paths computed with a log transform \n'); + data = search_information(SC,'log'); + data = data + data'; + case 8 %SIdist + fprintf('Search Information of shortest-paths computed with no transform \n'); + data = search_information(SC); + data = data + data'; + case 9 %T + fprintf('Path Transitivity \n'); + data = path_transitivity(double(SC>0)); + case 10 %deltaMFPT + fprintf('Column-wise z-scored mean first passage time \n'); + mfpt = mean_first_passage_time(SC); + deltamfpt = zscore(mfpt,[],1); + data = deltamfpt+deltamfpt'; + case 11 %neighOverlap + fprintf('Neighborhood Overlap \n'); + data = double(SC>0) * double(SC>0)'; + case 12 %MI + fprintf('Matching Index \n'); + data = matching_ind(SC); + otherwise + error('This is not an accepted predictor. See list of available predictors \n') + end + pred_data(:,ind2start+v) = data(indx); + end +else + if size(pred_var,1) == length(indx) + pred_data = [pred_data,pred_var]; + else + error('Custom predictors must be provided as KxM array of M predictors \n'); + end +end + + +[n1,n2] = size(FC); +if n1 == n2 && n1 == N + % square FC matrix + responses = FC(indx); +elseif n1 == length(indx) && n2 == 1 + % FC is already an upper-triangle vector + responses = FC; +else + error('FC must be square matrix or a vector containing the upper triangle (no diagonal elements) of the square FC matrix \n') +end + +% run multilinear model +R = regstats(responses,pred_data,model,{'beta','yhat','rsquare','adjrsquare','tstat','r','mse','standres'}); +beta = R.beta; +FCpre = zeros(size(SC)); +FCpre(indx) = R.yhat; +FCpre = FCpre+FCpre'; +FCcorr = corr(responses,FCpre(indx)); + diff --git a/DefaultData/2019_03_03_BCT/quasi_idempotence.m b/DefaultData/2019_03_03_BCT/quasi_idempotence.m new file mode 100755 index 0000000..591f4d1 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/quasi_idempotence.m @@ -0,0 +1,110 @@ +function [XN,IOTA,EPS,U]=quasi_idempotence(X,K) +%QUASI_IDEMPOTENCE Connection matrix quasi-idempotence +% +% [XN,IOTA,EPS,U]=quasi_idempotence(X) +% [XN,IOTA,EPS,U]=quasi_idempotence(X,K) +% +% The degree of quasi-idempotence of a matrix represents how close it +% is to being idempotent, i.e. invariant to squaring. In turn, this +% reflects how closely related the edges in the graph it corresponds to +% are to the sums of all triangles between the corresponding nodes, +% spanning the entirety of the network. This probes a form of +% self-similarity, intended not as between nodes and modules, but +% between as edges and triangles (or more generally paths). +% Networks wherein the edge strengths are weakly related to the +% nodal strengths have low quasi-idempotence, and networks wherein the +% edge strengths are significantly influenced by the strengths of the +% corresponding nodes have high quasi-idempotence. In other words the +% degree of quasi-idempotence may be viewed as a measure of +% "collectivity", meaning how strongly individual nodes participate +% in collective dynamics. +% +% Inputs: +% X, +% undirected weighted/binary connection matrix with +% non-negative weights, +% K, +% number of iterations (optional), +% K0)=0; % null the diagonal in the initial matrix +X=X/norm(X); % normalize to unit norm +XN=X; +mask=triu(ones(N),1)>0; % create mask for superdiagonal elements +U=0; % initialize iterations counter +IOTA=[]; % this vector will contain the correlation coefficients +EPS=inf; % and this will contain the errors +if isinf(K) + while EPS(end)>eps('double') % iterate until error below precision + U=U+1; % increase iteration counter + XN_hat=XN; % save the initial matrix + XN=XN^2; % square the matrix + XN=XN/norm(XN); % normalize it again (for numerical reasons) + IOTA(end+1)=corr(X(mask),XN(mask)); % calculate correlation coefficient + EPS(end+1)=norm(XN_hat-XN); % calculate error + end +else + while U2011) version of MATLAB.') +end + +R = double(W); % sign function requires double input +n = size(R,1); +ITER=ITER*n*(n-1); + +% maximal number of rewiring attempts per 'iter' +maxAttempts=n; +% actual number of successful rewirings +eff = 0; + +for iter=1:ITER + att=0; + while (att<=maxAttempts) %while not rewired + %select four distinct vertices + nodes = randperm(n,4); + a = nodes(1); + b = nodes(2); + c = nodes(3); + d = nodes(4); + + r0_ab = R(a,b); + r0_cd = R(c,d); + r0_ad = R(a,d); + r0_cb = R(c,b); + + %rewiring condition + if (sign(r0_ab)==sign(r0_cd)) && ... + (sign(r0_ad)==sign(r0_cb)) && ... + (sign(r0_ab)~=sign(r0_ad)) + + R(a,d)=r0_ab; R(a,b)=r0_ad; + R(c,b)=r0_cd; R(c,d)=r0_cb; + + eff = eff+1; + break; + end %rewiring condition + att=att+1; + end %while not rewired +end %iterations \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/randmio_und.m b/DefaultData/2019_03_03_BCT/randmio_und.m new file mode 100755 index 0000000..91ef5e1 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/randmio_und.m @@ -0,0 +1,80 @@ +function [R,eff]=randmio_und(R, ITER) +%RANDMIO_UND Random graph with preserved degree distribution +% +% R = randmio_und(W,ITER); +% [R eff]=randmio_und(W, ITER); +% +% This function randomizes an undirected network, while preserving the +% degree distribution. The function does not preserve the strength +% distribution in weighted networks. +% +% Input: W, undirected (binary/weighted) connection matrix +% ITER, rewiring parameter +% (each edge is rewired approximately ITER times) +% +% Output: R, randomized network +% eff, number of actual rewirings carried out +% +% References: Maslov and Sneppen (2002) Science 296:910 +% +% +% 2007-2012 +% Mika Rubinov, UNSW +% Jonathan Power, WUSTL +% Olaf Sporns, IU + +% Modification History: +% Jun 2007: Original (Mika Rubinov) +% Apr 2008: Edge c-d is flipped with 50% probability, allowing to explore +% all potential rewirings (Jonathan Power) +% Mar 2012: Limit number of rewiring attempts, count number of successful +% rewirings (Olaf Sporns) + + +n=size(R,1); +[i,j]=find(tril(R)); +K=length(i); +ITER=K*ITER; + +% maximal number of rewiring attempts per 'iter' +maxAttempts= round(n*K/(n*(n-1))); +% actual number of successful rewirings +eff = 0; + +for iter=1:ITER + att=0; + while (att<=maxAttempts) %while not rewired + while 1 + e1=ceil(K*rand); + e2=ceil(K*rand); + while (e2==e1) + e2=ceil(K*rand); + end + a=i(e1); b=j(e1); + c=i(e2); d=j(e2); + + if all(a~=[c d]) && all(b~=[c d]) + break %all four vertices must be different + end + end + + if rand>0.5 + i(e2)=d; j(e2)=c; %flip edge c-d with 50% probability + c=i(e2); d=j(e2); %to explore all potential rewirings + end + + %rewiring condition + if ~(R(a,d) || R(c,b)) + R(a,d)=R(a,b); R(a,b)=0; + R(d,a)=R(b,a); R(b,a)=0; + R(c,b)=R(c,d); R(c,d)=0; + R(b,c)=R(d,c); R(d,c)=0; + + j(e1) = d; %reassign edge indices + j(e2) = b; + eff = eff+1; + break; + end %rewiring condition + att=att+1; + end %while not rewired +end %iterations \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/randmio_und_connected.m b/DefaultData/2019_03_03_BCT/randmio_und_connected.m new file mode 100755 index 0000000..8d146f5 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/randmio_und_connected.m @@ -0,0 +1,107 @@ +function [R,eff] = randmio_und_connected(R, ITER) +%RANDMIO_UND_CONNECTED Random graph with preserved degree distribution +% +% R = randmio_und_connected(W,ITER); +% [R eff] = randmio_und_connected(W, ITER); +% +% This function randomizes an undirected network, while preserving the +% degree distribution. The function does not preserve the strength +% distribution in weighted networks. The function also ensures that the +% randomized network maintains connectedness, the ability for every node +% to reach every other node in the network. The input network for this +% function must be connected. +% +% Input: W, undirected (binary/weighted) connection matrix +% ITER, rewiring parameter +% (each edge is rewired approximately ITER times) +% +% Output: R, randomized network +% eff, number of actual rewirings carried out +% +% References: Maslov and Sneppen (2002) Science 296:910 +% +% +% 2007-2012 +% Mika Rubinov, UNSW +% Jonathan Power, WUSTL +% Olaf Sporns, IU + +% Modification History: +% Jun 2007: Original (Mika Rubinov) +% Apr 2008: Edge c-d is flipped with 50% probability, allowing to explore +% all potential rewirings (Jonathan Power) +% Mar 2012: Limit number of rewiring attempts, count number of successful +% rewirings (Olaf Sporns) + + +n=size(R,1); +[i,j]=find(tril(R)); +K=length(i); +ITER=K*ITER; + +% maximal number of rewiring attempts per 'iter' +maxAttempts= round(n*K/(n*(n-1))); +% actual number of successful rewirings +eff = 0; + +for iter=1:ITER + att=0; + while (att<=maxAttempts) %while not rewired + rewire=1; + while 1 + e1=ceil(K*rand); + e2=ceil(K*rand); + while (e2==e1) + e2=ceil(K*rand); + end + a=i(e1); b=j(e1); + c=i(e2); d=j(e2); + + if all(a~=[c d]) && all(b~=[c d]) + break %all four vertices must be different + end + end + + if rand>0.5 + i(e2)=d; j(e2)=c; %flip edge c-d with 50% probability + c=i(e2); d=j(e2); %to explore all potential rewirings + end + + %rewiring condition + if ~(R(a,d) || R(c,b)) + %connectedness condition + if ~(R(a,c) || R(b,d)) + P=R([a d],:); + P(1,b)=0; P(2,c)=0; + PN=P; + PN(:,d)=1; PN(:,a)=1; + + while 1 + P(1,:)=any(R(P(1,:)~=0,:),1); + P(2,:)=any(R(P(2,:)~=0,:),1); + P=P.*(~PN); + if ~all(any(P,2)) + rewire=0; + break + elseif any(any(P(:,[b c]))) + break + end + PN=PN+P; + end + end %connectedness testing + + if rewire %reassign edges + R(a,d)=R(a,b); R(a,b)=0; + R(d,a)=R(b,a); R(b,a)=0; + R(c,b)=R(c,d); R(c,d)=0; + R(b,c)=R(d,c); R(d,c)=0; + + j(e1) = d; %reassign edge indices + j(e2) = b; + eff = eff+1; + break; + end %edge reassignment + end %rewiring condition + att=att+1; + end %while not rewired +end %iterations \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/randmio_und_signed.m b/DefaultData/2019_03_03_BCT/randmio_und_signed.m new file mode 100755 index 0000000..31bd1be --- /dev/null +++ b/DefaultData/2019_03_03_BCT/randmio_und_signed.m @@ -0,0 +1,80 @@ +function [R,eff] = randmio_und_signed(W, ITER) +% RANDMIO_UND_SIGNED Random graph with preserved signed degree distribution +% +% R = randmio_und_signed(W,ITER); +% [R,eff] = randmio_und_signed(W,ITER); +% +% This function randomizes an undirected network with positively and +% negatively signed connections, while preserving the positively and +% negatively signed degree distribution. The function does not preserve +% the strength distribution in weighted networks. +% +% Input: W, undirected (binary/weighted) connection matrix +% ITER, rewiring parameter +% (each edge is rewired approximately ITER times) +% +% Output: R, randomized network +% eff, number of actual rewirings carried out +% +% Reference: Maslov and Sneppen (2002) Science 296:910 +% +% +% 2011-2015 +% Dani Bassett, UCSB +% Olaf Sporns, Indiana U +% Mika Rubinov, U Cambridge + +% Modification History: +% Mar 2011: Original (Dani Bassett, based on randmio_und.m) +% Mar 2012: Limit number of rewiring attempts, +% count number of successful rewirings (Olaf Sporns) +% Dec 2015: Rewritten the core of the rewiring algorithm to allow +% unbiased exploration of all network configurations. The new +% algorithm allows positive-positive/negative-negative +% rewirings, in addition to the previous positive-positive/0-0 +% and negative-negative/0-0 rewirings (Mika Rubinov). + +if nargin('randperm')==1 + warning('This function requires a recent (>2011) version of MATLAB.') +end + +R = double(W); % sign function requires double input +n = size(R,1); +ITER = ITER*n*(n-1)/2; + +% maximal number of rewiring attempts per 'iter' +maxAttempts = round(n/2); +% actual number of successful rewirings +eff = 0; + +for iter=1:ITER + att=0; + while (att<=maxAttempts) %while not rewired + %select four distinct vertices + nodes = randperm(n,4); + a = nodes(1); + b = nodes(2); + c = nodes(3); + d = nodes(4); + + r0_ab = R(a,b); + r0_cd = R(c,d); + r0_ad = R(a,d); + r0_cb = R(c,b); + + %rewiring condition + if (sign(r0_ab)==sign(r0_cd)) && ... + (sign(r0_ad)==sign(r0_cb)) && ... + (sign(r0_ab)~=sign(r0_ad)) + + R(a,d)=r0_ab; R(a,b)=r0_ad; + R(d,a)=r0_ab; R(b,a)=r0_ad; + R(c,b)=r0_cd; R(c,d)=r0_cb; + R(b,c)=r0_cd; R(d,c)=r0_cb; + + eff = eff+1; + break; + end %rewiring condition + att=att+1; + end %while not rewired +end %iterations \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/randomize_graph_partial_und.m b/DefaultData/2019_03_03_BCT/randomize_graph_partial_und.m new file mode 100755 index 0000000..c8216c0 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/randomize_graph_partial_und.m @@ -0,0 +1,53 @@ +function A = randomize_graph_partial_und(A,B,maxswap) +% RANDOMIZE_GRAPH_PARTIAL_UND Swap edges with preserved degree sequence +% +% A = RANDOMIZE_GRAPH_PARTIAL_UND(A,B,MAXSWAP) takes adjacency matrices A +% and B and attempts to randomize matrix A by performing MAXSWAP +% rewirings. The rewirings will avoid any spots where matrix B is +% nonzero. +% +% Inputs: A, undirected adjacency matrix +% B, edges to avoid +% MAXSWAP, number of rewirings +% +% Outputs: A, randomized matrix +% +% Richard Betzel, Indiana University, 2013 +% +% Notes: +% 1. Based on the script randmio_und.m. +% 2. Graph may become disconnected as a result of rewiring. Always +% important to check. +% 3. A can be weighted, though the weighted degree sequence will not be +% preserved. +% + +[i,j] = find(triu(A,1)); +m = length(i); +nswap = 0; +while nswap < maxswap + while 1 + e1 = randi(m); e2 = randi(m); + while e2 == e1 + e2 = randi(m); + end + a = i(e1); b = j(e1); + c = i(e2); d = j(e2); + if all(a~=[c,d]) && all(b~=[c,d]) + break + end + end + if rand > 0.5 + i(e2) = d; j(e2) = c; + c = i(e2); d = j(e2); + end + if ~(A(a,d) || A(c,b) || B(a,d) || B(c,b)) + A(a,d) = A(a,b); A(a,b) = 0; + A(d,a) = A(b,a); A(b,a) = 0; + A(c,b) = A(c,d); A(c,d) = 0; + A(b,c) = A(d,c); A(d,c) = 0; + j(e1) = d; + j(e2) = b; + nswap = nswap + 1; + end +end diff --git a/DefaultData/2019_03_03_BCT/randomizer_bin_und.m b/DefaultData/2019_03_03_BCT/randomizer_bin_und.m new file mode 100755 index 0000000..ef8d9f4 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/randomizer_bin_und.m @@ -0,0 +1,140 @@ +function [R] = randomizer_bin_und(R,alpha) +%RANDOMIZER_BIN_UND Random graph with preserved in/out degree distribution +% +% R = randomizer_bin_und(A,alpha); +% +% This function randomizes a binary undirected network, while preserving +% the degree distribution. The function directly searches for rewirable +% edge pairs (rather than trying to rewire edge pairs at random), and +% hence avoids long loops and works especially well in dense matrices. +% +% Inputs: A, binary undirected connection matrix +% alpha, fraction of edges to rewire +% +% Outputs: R, randomized network +% +% References: Maslov and Sneppen (2002) Science 296:910 +% +% +% Jonathan Power, WUSTL. 3/1/10. + +%#ok<*ASGLU> + +% make binary +R=ceil(R); + +% ensure that matrix is binary +if (max(R(:))~=1) || (min(R(:))~=0) + error('Matrix should be binary'); +end + +% ensure that matrix is undirected +if ~isequal(R,R.') + error('Matrix should be undirected'); +end + +% find how many edges are possible in the network +[a,b]=size(R); +numpossibleedges=((a*a)-a)/2; + +% excise the diagonal and replace it with 9999 +savediag=R.*(eye(size(R,1))); +R=R.*(~eye(size(R,1))); +R=R+(eye(size(R,1)).*9999); + +% if there are more edges than non-edges we invert the matrix to reduce +% computation time, then revert at the end of the script +inverted=0; +[i,j]=find(triu(R,1)); +K=size(i,1); +if K>(numpossibleedges/2) + inverted=1; + R=double(~R); + R=R.*(~eye(size(R,1))); + R=R+(eye(size(R,1)).*9999); +end + +% find edges +[i,j]=find(triu(R,1)); +% K=size(i,1); + +% exclude fully connected nodes. will replace later +fullnode=find((sum(triu(R,1),1)+(sum(triu(R,1),2))')==(a-1)); +if ~isempty(fullnode) + R(fullnode,:)=0; R(:,fullnode)=0; + R=R.*(~eye(size(R,1))); + R=R+(eye(size(R,1)).*9999); +end + +% find the edges +[i,j]=find(triu(R,1)); +K=size(i,1); + +if (isempty(K) || K==(numpossibleedges) || (K==numpossibleedges-1)) + fprintf('No possible randomization.\n') +else + for iter=1:K % for every edge + if rand<=alpha % rewire ~alpha% of edges + + % this is the chosen edge + a=i(iter); + b=j(iter); + + % for selected edge, see where each end can connect to + alliholes=find(R(:,i(iter))==0); + alljholes=find(R(:,j(iter))==0); + + % we can only use edges with connection to neither node + iintersect=intersect(alliholes,alljholes); + + % find which of these nodes are connected + [ii,jj]=find(R(iintersect,iintersect)==1); + + % if there an edge to switch + if ~isempty(ii) + + % choose one randomly + nummates=size(ii,1); + mate=ceil(rand*nummates); + + % randomly orient the second edge + if rand<0.5 + c=iintersect(ii(mate)); + d=iintersect(jj(mate)); + else + d=iintersect(ii(mate)); + c=iintersect(jj(mate)); + end + + % make the changes in the matrix + R(a,b)=0; R(c,d)=0; + R(b,a)=0; R(d,c)=0; + R(a,c)=1; R(b,d)=1; + R(c,a)=1; R(d,b)=1; + + % update the edge index + for m=1:K + if ((i(m)==d) && (j(m)==c)) + j(iter)=c; j(m)=b; + elseif ((i(m)==c) && (j(m)==d)) + j(iter)=c; i(m)=b; + end + end + end % rewiring + end % if randtol + inside = 0; + else + inside = 1; + end + end + + % Find nodes inside the box, edges crossing the boundary + + L = find(XYn(:,1)>(randx-deltaL) & XYn(:,1)<(randx+deltaL) ... + & XYn(:,2)>(randy-deltaL) & XYn(:,2)<(randy+deltaL)); + + if ~isempty(L) == 1 + nPartitions = nPartitions+1; + % count edges crossing the boundary of the box + E(nPartitions,1) = sum(sum(A(L,setdiff(1:M,L)))); + % count nodes inside of the box + N(nPartitions,1) = numel(L); + + end + +end + +return; \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/rentian_scaling_3d.m b/DefaultData/2019_03_03_BCT/rentian_scaling_3d.m new file mode 100755 index 0000000..0016105 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/rentian_scaling_3d.m @@ -0,0 +1,209 @@ +function [N,E] = rentian_scaling_3d(A,XYZ,n,tol) +% RENTIAN_SCALING_3D Rentian scaling for networks embedded in three dimensions. +% +% [N,E] = rentian_scaling_3d(A,XYZ,n,tol) +% +% Physical Rentian scaling (or more simply Rentian scaling) is a property +% of systems that are cost-efficiently embedded into physical space. It is +% what is called a "topo-physical" property because it combines information +% regarding the topological organization of the graph with information +% about the physical placement of connections. Rentian scaling is present +% in very large scale integrated circuits, the C. elegans neuronal network, +% and morphometric and diffusion-based graphs of human anatomical networks. +% Rentian scaling is determined by partitioning the system into cubes, +% counting the number of nodes inside of each cube (N), and the number of +% edges traversing the boundary of each cube (E). If the system displays +% Rentian scaling, these two variables N and E will scale with one another +% in loglog space. The Rent's exponent is given by the slope of log10(E) +% vs. log10(N), and can be reported alone or can be compared to the +% theoretical minimum Rent's exponent to determine how cost efficiently the +% network has been embedded into physical space. Note: if a system displays +% Rentian scaling, it does not automatically mean that the system is +% cost-efficiently embedded (although it does suggest that). Validation +% occurs when comparing to the theoretical minimum Rent's exponent for that +% system. +% +% INPUTS: +% +% A: MxM adjacency matrix. +% Must be unweighted, binary, and symmetric. +% XYZ: Matrix of node placement coordinates. +% Must be in the form of an Mx3 matrix [x y z], where M is +% the number of nodes and x, y, z are column vectors of node +% coordinates. +% n: Number of partitions to compute. Each partition is a data +% point. You want a large enough number to adequately +% estimate the Rent's exponent. +% tol: This should be a small value (for example 1e-6). +% In order to mitigate the effects of boundary conditions due +% to the finite size of the network, we only allow partitions +% that are contained within the boundary of the network. This +% is achieved by first computing the volume of the convex +% hull of the node coordinates (V). We then ensure that the +% volume of the convex hull computed on the original node +% coordinates plus the coordinates of the randomly generated +% partition (Vnew) is within a given tolerance of the +% original (i.e. check abs(V - Vnew) < tol). Thus tol, should +% be a small value in order to make sure the partitions are +% contained largely within the boundary of the network, and +% thus the number of nodes and edges within the box are not +% skewed by finite size effects. +% +% OUTPUTS: +% +% N: nx1 vector of the number of nodes in each of the n partitions. +% E: nx1 vector of the number of edges crossing the boundary of +% each partition. +% +% Subsequent Analysis: +% +% Rentian scaling plots are created by: figure; loglog(E,N,'*'); +% +% To determine the Rent's exponent, p, we need to determine +% the slope of E vs. N in loglog space, which is the Rent's +% exponent. There are many ways of doing this with more or less +% statistical rigor. Robustfit in MATLAB is one such option: +% +% [b,stats] = robustfit(log10(N),log10(E)) +% +% Then the Rent's exponent is b(1,2) and the standard error of the +% estimation is given by stats.se(1,2). +% +% Note: n=5000 was used in Bassett et al. 2010 in PLoS CB. +% +% Reference: +% Danielle S. Bassett, Daniel L. Greenfield, Andreas Meyer-Lindenberg, +% Daniel R. Weinberger, Simon W. Moore, Edward T. Bullmore. Efficient +% physical embedding of topologically complex information processing +% networks in brains and computer circuits. PLoS Comput Biol, 2010, +% 6(4):e1000748. +% +% Modification History: +% +% 2010: Original (Dani Bassett) +% Dec 2016: Updated code so that both partition centers and partition +% sizes are chosen at random. Also added in a constraint on +% partition placement that prevents boxes from being located +% outside the edges of the network. This helps prevent skewed +% results due to boundary effects arising from the finite size +% of the network. (Lia Papadopoulos) +% + + +% determine the number of nodes in the system +M = numel(XYZ(:,1)); + +% rescale coordinates so that they are all greater than unity +XYZn = XYZ - repmat(min(XYZ)-1,M,1); + +% compute the area of convex hull (i.e. are of the boundary) of the network +[~,V] = convhull(XYZn(:,1),XYZn(:,2),XYZn(:,3)); + +% min and max network coordinates +xmin = min(XYZn(:,1)); +xmax = max(XYZn(:,1)); +ymin = min(XYZn(:,2)); +ymax = max(XYZn(:,2)); +zmin = min(XYZn(:,3)); +zmax = max(XYZn(:,3)); + +% initialize vectors of number of nodes in box and number of edges crossing +% box +N = zeros(n,1); +E = zeros(n,1); + +% create partitions, and count the number of nodes inside the partition (N) +% and the number of edges traversing the boundary of the partition (E) +nPartitions = 0; + +% create partitions, and count the number of nodes inside the partition (N) +% and the number of edges traversing the boundary of the partition (E) + +while nPartitions<(n+1) + + % variable to check if partition center is within network boundary + % OK if inside == 1 + inside = 0; + + while inside == 0 + + % pick a random (x,y,z) coordinate to be the center of the box + randx = xmin+(xmax-xmin)*rand(1); + randy = ymin+(ymax-ymin)*rand(1); + randz = zmin+(zmax-zmin)*rand(1); + + % make sure the point is inside the convex hull of the network + newCoords = [XYZn; [randx randy randz]]; + [~,Vnew] = convhull(newCoords(:,1),newCoords(:,2),newCoords(:,3)); + + % if the old convex hull area and new convex hull area are equal + % then the box center must be inside the network boundary. + + if isequal(V,Vnew)==0 + inside = 0; + else + inside = 1; + end + + end + + % determine the approximate maximum distance the box can extend, given + % the center point and the bounds of the network + deltaX = min(abs(xmax-randx),abs(xmin-randx)); + deltaY = min(abs(ymax-randy),abs(ymin-randy)); + deltaZ = min(abs(zmax-randz),abs(zmin-randz)); + deltaLmin = min([deltaX deltaY deltaZ]); + + % variable to check if partition is within network boundary + % OK if inside == 1 + inside = 0; + + while inside == 0 + + % pick a random (side length)/2 that is between 0 and the + % max possible + deltaL = deltaLmin*rand(1); + + % (x,y,z) coordinates for corners of box + boxCoords = [randx - deltaL randy - deltaL randz - deltaL; ... + randx - deltaL randy - deltaL randz + deltaL; ... + randx - deltaL randy + deltaL randz - deltaL; ... + randx - deltaL randy + deltaL randz + deltaL; ... + randx + deltaL randy - deltaL randz - deltaL;... + randx + deltaL randy - deltaL randz + deltaL; ... + randx + deltaL randy + deltaL randz - deltaL; ... + randx + deltaL randy + deltaL randz + deltaL]; + + % check if all corners of box are inside the convex hull of the + % network + newCoords = [XYZn; boxCoords]; + [~,Vnew] = convhull(newCoords(:,1),newCoords(:,2),newCoords(:,3)); + + % make sure the new convex hull that includes the partition corners + % is within a certain tolerance of the original convex hull area. + + if abs(V-Vnew)>tol + inside = 0; + else + inside = 1; + end + end + + % Find nodes inside the box, edges crossing the boundary + + L = find(XYZn(:,1)>(randx-deltaL) & XYZn(:,1)<(randx+deltaL) ... + & XYZn(:,2)>(randy-deltaL) & XYZn(:,2)<(randy+deltaL) ... + & XYZn(:,3)>(randz-deltaL) & XYZn(:,3)<(randz+deltaL)); + + if ~isempty(L) == 1 + nPartitions = nPartitions+1; + % count edges crossing the boundary of the cube + E(nPartitions,1) = sum(sum(A(L,setdiff(1:M,L)))); + % count nodes inside of the cube + N(nPartitions,1) = numel(L); + + end + +end + +return; \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/reorderMAT.m b/DefaultData/2019_03_03_BCT/reorderMAT.m new file mode 100755 index 0000000..8601e40 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/reorderMAT.m @@ -0,0 +1,64 @@ +function [MATreordered,MATindices,MATcost] = reorderMAT(MAT,H,cost) +%REORDERMAT Reorder matrix for visualization +% +% [MATreordered,MATindices,MATcost] = reorderMAT(MAT,H,cost); +% +% This function reorders the connectivity matrix in order to place more +% edges closer to the diagonal. This often helps in displaying community +% structure, clusters, etc. +% +% Inputs: MAT, connection matrix +% H, number of reordering attempts +% cost, 'line' or 'circ', for shape of lattice +% (linear or ring lattice) +% +% MATreordered reordered connection matrix +% MATindices reordered indices +% MATcost cost of reordered matrix +% +% +% Olaf Sporns, Indiana University + + +N = length(MAT); +diagMAT = diag(diag(MAT)); +MAT = MAT-diagMAT; + +% generate cost function +if strcmp(cost,'line') + profil = fliplr(normpdf(1:N,0,N/2)); +end; +if strcmp(cost,'circ') + profil = fliplr(normpdf(1:N,N/2,N/4)); +end; +COST = toeplitz(profil,profil); + +% initialize lowCOST +lowMATcost = sum(sum(COST.*MAT)); + +% keep track of starting configuration +MATstart = MAT; +starta = 1:N; + +% reorder +for h=1:H + a = 1:N; + % choose two positions at random and flip them + r = randperm(N); + a(r(1)) = r(2); + a(r(2)) = r(1); + MATcostnew = sum(sum(MAT(a,a).*COST)); + if (MATcostnew < lowMATcost) + MAT = MAT(a,a); + r2 = starta(r(2)); + r1 = starta(r(1)); + starta(r(1)) = r2; + starta(r(2)) = r1; + lowMATcost = MATcostnew; + end; +end; % h + +MATreordered = MATstart(starta,starta) + diagMAT(starta,starta); +MATindices = starta; +MATcost = lowMATcost; + diff --git a/DefaultData/2019_03_03_BCT/reorder_matrix.m b/DefaultData/2019_03_03_BCT/reorder_matrix.m new file mode 100755 index 0000000..8bab68b --- /dev/null +++ b/DefaultData/2019_03_03_BCT/reorder_matrix.m @@ -0,0 +1,105 @@ +function [Mreordered,Mindices,cost] = reorder_matrix(M1,cost,flag) +% REORDER_MATRIX Matrix reordering for visualization +% +% [Mreordered,Mindices,cost] = reorder_matrix(M1,cost,flag) +% +% This function rearranges the nodes in matrix M1 such that the matrix +% elements are squeezed along the main diagonal. The function uses a +% version of simulated annealing. +% +% Inputs: M1 = connection matrix (weighted or binary, +% directed or undirected) +% cost = 'line' or 'circ', for shape of lattice +% cost (linear or ring lattice) +% +% Mreordered = reordered connection matrix +% Mindices = reordered indices +% cost = distance between M1 and Mreordered +% +% Note that in general, the outcome will depend on the initial condition +% (the setting of the random number seed). Also, there is no good way to +% determine optimal annealing parameters in advance - these paramters +% will need to be adjusted "by hand" (particularly H, Texp, and T0). +% For large and/or dense matrices, it is highly recommended to perform +% exploratory runs varying the settings of 'H' and 'Texp' and then select +% the best values. +% +% Based on extensive testing, it appears that T0 and Hbrk can remain +% unchanged in most cases. Texp may be varied from 1-1/H to 1-10/H, for +% example. H is the most important parameter - set to larger values as +% the problem size increases. It is advisable to run this function +% multiple times and select the solution(s) with the lowest 'cost'. +% +% Setting 'Texp' to zero cancels annealing and uses a greedy algorithm +% instead. +% +% Yusuke Adachi, University of Tokyo 2010 +% Olaf Sporns, Indiana University 2010 + +N = size(M1,1); + +% generate cost function +if (strcmp(cost,'line')) + profil = fliplr(normpdf(1:N,0,N/2)); +end; +if (strcmp(cost,'circ')) + profil = fliplr(normpdf(1:N,N/2,N/4)); +end; +COST = (toeplitz(profil,profil).*~eye(N)); +COST = COST./sum(sum(COST)); + +% establish maxcost, lowcost, mincost +maxcost = sum(sort(COST(:)).*(sort(M1(:)))); +lowcost = sum(sum(M1.*COST))/maxcost; +mincost = lowcost; + +% initialize +anew = 1:N; +amin = 1:N; +h = 0; hcnt = 0; + +% set annealing parameters +% H determines the maximal number of steps +% Texp determines the steepness of the temperature gradient +% T0 sets the initial temperature (and scales the energy term) +% Hbrk sets a break point for the simulation (if no further improvement) +H = 1e04; Texp = 1-10/H; T0 = 1e-03; Hbrk = H/10; +%Texp = 0; + +while hHbrk) + break; + end; + % current temperature + T = T0*Texp^h; + % choose two positions at random and flip them + atmp = anew; + %r = randperm(N); % slower + r = ceil(rand(1,2).*N); + atmp(r(1)) = anew(r(2)); + atmp(r(2)) = anew(r(1)); + costnew = sum(sum(M1(atmp,atmp).*COST))/maxcost; + % annealing + if (costnew < lowcost) || (rand < exp(-(costnew-lowcost)/T)) + anew = atmp; + lowcost = costnew; + % is this a new absolute best? + if (lowcost0)); +% Gprob_SPL = mean(rob_SPL(~eye(N)>0)); +% +% +% Reference: Goñi J, et al (2013) PLoS ONE +% +% +% Joaquin Goñi, IU Bloomington, 2012 + + +N = size(adj,1); +EYE = logical(eye(N,N)); + +flagResources = ~isnan(lambda); + +if flagResources + if lambda<=0 || lambda>=1 + error('p_req_values must be non-zero probabilities') + end + z = zeros(N,N); +end +if nargin<4 + SPL = distance_wei_floyd(adj); +end +if nargin<5 + M = diag(sum(adj,2))\adj; +end + +Lvalues = unique(SPL(:)); +Lvalues = Lvalues(~(Lvalues==0)); + +prob_SPL = zeros(N,N); % a priori zero probability of going through SPL among nodes + +for indexSPL=1:length(Lvalues) %for each possible value of SPL for current component + SPLvalue = Lvalues(indexSPL); + [~, hcols] = find(SPL==SPLvalue); + hvector = unique(hcols); clear hrows hcols + entries = SPL==SPLvalue; + + if flagResources % compute Eres + [prob_aux,z_aux] = prob_first_particle_arrival(M,SPLvalue,hvector,lambda); + else % not compute Eres + [prob_aux] = prob_first_particle_arrival(M,SPLvalue,hvector,[]); + end + + prob_aux(~entries) = 0; + prob_SPL = prob_SPL + prob_aux; + + if flagResources + z_aux(~entries) = 0; + z = z + z_aux; + end +end + +prob_SPL(EYE) = 0; + +if flagResources + z(prob_SPL==1) = 1; + Eres = 1./z; + Eres(EYE) = 0; +else + Eres = nan; +end + +function [prob,resources] = prob_first_particle_arrival(M,L,hvector,lambda) + +N = size(M,1); +prob = zeros(N,N); + +if nargin<4 + hvector=1:N; +end + +flagResources = ~isnan(lambda); + +if flagResources + if lambda<=0 || lambda>=1 + error('p_req_values must be non-zero probabilities') + end + resources = zeros(N,N); +end + +for hindex=1:length(hvector) %for each destination node h + h = hvector(hindex); + B_h = M; + B_h(h,:) = 0; B_h(h,h) = 1; % h becomes absorbant state. + + B_h_L = B_h^L; + + term = 1-B_h_L(:,h); + + prob(:,h)= 1-term; + + if flagResources + resources(:,h) = repmat(log(1-lambda),N,1)./repmat(log(term),1); + end +end diff --git a/DefaultData/2019_03_03_BCT/retrieve_shortest_path.m b/DefaultData/2019_03_03_BCT/retrieve_shortest_path.m new file mode 100755 index 0000000..1213597 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/retrieve_shortest_path.m @@ -0,0 +1,37 @@ +function path = retrieve_shortest_path(s,t,hops,Pmat) +% RETRIEVE_SHORTEST_PATH Retrieval of shortest path +% +% This function finds the sequence of nodes that comprise the shortest +% path between a given source and target node. +% +% Inputs: +% s, +% Source node: i.e. node where the shortest path begins. +% t, +% Target node: i.e. node where the shortest path ends. +% hops, +% Number of edges in the path. This matrix may be obtained as the +% second output argument of the function "distance_wei_floyd.m". +% Pmat, +% Pmat is a matrix whose elements {k,t} indicate the next node in +% the shortest path between k and t. This matrix may be obtained +% as the third output of the function "distance_wei_floyd.m" +% +% Output: +% path, +% Nodes comprising the shortest path between nodes s and t. +% +% +% Andrea Avena-Koenigsberger and Joaquin Goñi, IU, 2012 + +path_length = hops(s,t); +if path_length ~= 0 + path = nan(path_length+1,1); + path(1) = s; + for ind = 2:length(path) + s = Pmat(s,t); + path(ind) = s; + end +else + path = []; +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/rich_club_bd.m b/DefaultData/2019_03_03_BCT/rich_club_bd.m new file mode 100755 index 0000000..80af3fc --- /dev/null +++ b/DefaultData/2019_03_03_BCT/rich_club_bd.m @@ -0,0 +1,53 @@ +function [R,Nk,Ek] = rich_club_bd(CIJ,varargin) +%RICH_CLUB_BD Rich club coefficients (binary directed graph) +% +% R = rich_club_bd(CIJ) +% [R,Nk,Ek] = rich_club_bd(CIJ,klevel) +% +% The rich club coefficient, R, at level k is the fraction of edges that +% connect nodes of degree k or higher out of the maximum number of edges +% that such nodes might share. +% +% Input: CIJ, connection matrix, binary and directed +% klevel, optional input argument. klevel sets the +% maximum level at which the rich club +% coefficient will be calculated. If klevel is +% not included the the maximum level will be +% set to the maximum degree of CIJ. +% +% Output: R, vector of rich-club coefficients for levels +% 1 to klevel. +% Nk, number of nodes with degree>k +% Ek, number of edges remaining in subgraph with +% degree>k +% +% Reference: Colizza et al. (2006) Nat. Phys. 2:110. +% +% Martijn van den Heuvel, University Medical Center Utrecht, 2011 + +N = size(CIJ,1); %#ok + +% definition of "degree" as used for RC coefficients +% degree is taken to be the sum of incoming and outgoing connectons +[~,~,degree] = degrees_dir(CIJ); + +if nargin == 1 + klevel = max(degree); +elseif nargin == 2 + klevel = varargin{1}; +elseif nargin > 2 + error('number of inputs incorrect. Should be [CIJ], or [CIJ, klevel]') +end + +R = zeros(1,klevel); +Nk = zeros(1,klevel); +Ek = zeros(1,klevel); +for k = 1:klevel + SmallNodes=find(degree<=k); %get 'small nodes' with degree <=k + subCIJ=CIJ; %extract subnetwork of nodes >k by removing nodes <=k of CIJ + subCIJ(SmallNodes,:)=[]; %remove rows + subCIJ(:,SmallNodes)=[]; %remove columns + Nk(k)=size(subCIJ,2); %number of nodes with degree >k + Ek(k)=sum(subCIJ(:)); %total number of connections in subgraph + R(k)=Ek(k)/(Nk(k)*(Nk(k)-1)); %unweighted rich-club coefficient +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/rich_club_bu.m b/DefaultData/2019_03_03_BCT/rich_club_bu.m new file mode 100755 index 0000000..e7e7651 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/rich_club_bu.m @@ -0,0 +1,49 @@ +function [R,Nk,Ek] = rich_club_bu(CIJ,varargin) +%RICH_CLUB_BU Rich club coefficients (binary undirected graph) +% +% R = rich_club_bu(CIJ) +% [R,Nk,Ek] = rich_club_bu(CIJ,klevel) +% +% The rich club coefficient, R, at level k is the fraction of edges that +% connect nodes of degree k or higher out of the maximum number of edges +% that such nodes might share. +% +% Input: CIJ, connection matrix, binary and undirected +% klevel, optional input argument. klevel sets the +% maximum level at which the rich club +% coefficient will be calculated. If klevel is +% not included the the maximum level will be +% set to the maximum degree of CIJ. +% +% Output: R, vector of rich-club coefficients for levels +% 1 to klevel. +% Nk, number of nodes with degree>k +% Ek, number of edges remaining in subgraph with +% degree>k +% +% Reference: Colizza et al. (2006) Nat. Phys. 2:110. +% +% Martijn van den Heuvel, University Medical Center Utrecht, 2011 + +Degree = sum(CIJ); %compute degree of each node + +if nargin == 1 + klevel = max(Degree); +elseif nargin == 2 + klevel = varargin{1}; +elseif nargin > 2 + error('number of inputs incorrect. Should be [CIJ], or [CIJ, klevel]') +end + +R = zeros(1,klevel); +Nk = zeros(1,klevel); +Ek = zeros(1,klevel); +for k = 1:klevel + SmallNodes=find(Degree<=k); %get 'small nodes' with degree <=k + subCIJ=CIJ; %extract subnetwork of nodes >k by removing nodes <=k of CIJ + subCIJ(SmallNodes,:)=[]; %remove rows + subCIJ(:,SmallNodes)=[]; %remove columns + Nk(k)=size(subCIJ,2); %number of nodes with degree >k + Ek(k)=sum(subCIJ(:)); %total number of connections in subgraph + R(k)=Ek(k)/(Nk(k)*(Nk(k)-1)); %unweighted rich-club coefficient +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/rich_club_wd.m b/DefaultData/2019_03_03_BCT/rich_club_wd.m new file mode 100755 index 0000000..679c932 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/rich_club_wd.m @@ -0,0 +1,75 @@ +function [Rw] = rich_club_wd(CIJ,varargin) +%RICH_CLUB_WD Rich club coefficients curve (weighted directed graph) +% +% Rw = rich_club_wd(CIJ,varargin) +% +% The weighted rich club coefficient, Rw, at level k is the fraction of +% edge weights that connect nodes of degree k or higher out of the +% maximum edge weights that such nodes might share. +% +% Inputs: +% CIJ: weighted directed connection matrix +% +% k-level: (optional) max level of RC(k). +% (by default k-level quals the maximal degree of CIJ) +% +% Output: +% Rw: rich-club curve +% +% +% References: +% T Opsahl et al. Phys Rev Lett, 2008, 101(16) +% M van den Heuvel, O Sporns, J Neurosci 2011 31(44) +% +% Martijn van den Heuvel, University Medical Center Utrecht, 2011 + +% Modification History: +% 2011: Original +% 2015: Expanded documentation (Mika Rubinov) + + +NofNodes = size(CIJ,2); %#ok %number of nodes +NodeDegree = sum((CIJ~=0))+sum((CIJ'~=0)); %define degree of each node (indegree + outdegree) + +%define to which level rc should be computed +if size(varargin,2)==1 + klevel = varargin{1}; +elseif isempty(varargin) + klevel = max(NodeDegree); +else + error('number of inputs incorrect. Should be [CIJ], or [CIJ, klevel]') +end + + +%wrank contains the ranked weights of the network, with strongest connections on top + +wrank = sort(CIJ(:), 'descend'); + +%loop over all possible k-levels +for kk = 1:klevel + + SmallNodes=find(NodeDegree + continue + end + + %remove small nodes with NodeDegreer + Wr = sum(CutoutCIJ(:)); + + %total number of connections in subset E>r + Er = length(find(CutoutCIJ~=0)); + + %E>r number of connections with max weight in network + wrank_r = wrank(1:1:Er); + + %weighted rich-club coefficient + Rw(kk)=Wr / sum(wrank_r); + +end diff --git a/DefaultData/2019_03_03_BCT/rich_club_wu.m b/DefaultData/2019_03_03_BCT/rich_club_wu.m new file mode 100755 index 0000000..c6523af --- /dev/null +++ b/DefaultData/2019_03_03_BCT/rich_club_wu.m @@ -0,0 +1,72 @@ +function [Rw] = rich_club_wu(CIJ,varargin) +%RICH_CLUB_WU Rich club coefficients curve (weighted undirected graph) +% +% Rw = rich_club_wu(CIJ,varargin) % rich club curve for weighted graph +% +% The weighted rich club coefficient, Rw, at level k is the fraction of +% edge weights that connect nodes of degree k or higher out of the +% maximum edge weights that such nodes might share. +% +% Inputs: +% CIJ: weighted directed connection matrix +% +% k-level: (optional) max level of RC(k). +% (by default k-level quals the maximal degree of CIJ) +% +% Output: +% Rw: rich-club curve +% +% +% References: +% T Opsahl et al. Phys Rev Lett, 2008, 101(16) +% M van den Heuvel, O Sporns, J Neurosci 2011 31(44) +% +% Martijn van den Heuvel, University Medical Center Utrecht, 2011 + +% Modification History: +% 2011: Original +% 2015: Expanded documentation (Mika Rubinov) + + +NofNodes = size(CIJ,2); %#ok %number of nodes +NodeDegree = sum((CIJ~=0)); %define degree of each node + +%define to which level rc should be computed +if size(varargin,2)==1 + klevel = varargin{1}; +elseif isempty(varargin) + klevel = max(NodeDegree); +else + error('number of inputs incorrect. Should be [CIJ], or [CIJ, klevel]') +end + +%wrank contains the ranked weights of the network, with strongest connections on top +wrank = sort(CIJ(:), 'descend'); + +%loop over all possible k-levels +for kk = 1:klevel + + SmallNodes=find(NodeDegree + continue + end + + %remove small nodes with NodeDegreer + Wr = sum(CutoutCIJ(:)); + + %total number of connections in subset E>r + Er = length(find(CutoutCIJ~=0)); + + %E>r number of connections with max weight in network + wrank_r = wrank(1:1:Er); + + %weighted rich-club coefficient + Rw(kk)=Wr / sum(wrank_r); +end diff --git a/DefaultData/2019_03_03_BCT/rout_efficiency.m b/DefaultData/2019_03_03_BCT/rout_efficiency.m new file mode 100755 index 0000000..1ad04bd --- /dev/null +++ b/DefaultData/2019_03_03_BCT/rout_efficiency.m @@ -0,0 +1,87 @@ +function [GErout,Erout,Eloc] = rout_efficiency(D,transform) +% ROUT_EFFICIENCY Mean, pair-wise and local routing efficiency +% +% [GErout,Erout,Eloc] = rout_efficiency(D,transform); +% +% The routing efficiency is the average of inverse shortest path length. +% +% The local routing efficiency of a node u is the routing efficiency +% computed on the subgraph formed by the neighborhood of node u +% (excluding node u). +% +% +% Inputs: +% +% D, +% Weighted/unweighted directed/undirected +% connection *weight* OR *length* matrix. +% +% transform, +% If the input matrix is a connection *weight* matrix, specify a +% transform that map input connection weights to connection +% lengths. Two transforms are available. +% 'log' -> l_ij = -log(w_ij) +% 'inv' -> l_ij = 1/w_ij +% +% If the input matrix is a connection *length* matrix, do not +% specify a transform (or specify an empty transform argument). +% +% +% Outputs: +% +% GErout, +% Mean global routing efficiency (scalar). +% +% Erout, +% Pair-wise routing efficiency (matrix). +% +% Eloc, +% Local efficiency (vector) +% +% +% Note: +% +% The input matrix may be either a connection weight matrix, or a +% connection length matrix. The connection length matrix is typically +% obtained with a mapping from weight to length, such that higher +% weights are mapped to shorter lengths (see above). +% +% +% Algorithm: Floyd–Warshall Algorithm +% +% +% References: +% Latora and Marchiori (2001) Phys Rev Lett +% Goñi et al (2013) PLoS ONE +% Avena-Koenigsberger et al (2016) Brain Structure and Function +% +% +% Andrea Avena-Koenigsberger and Joaquin Goñi, IU Bloomington, 2012 +% + +% Modification history +% 2012 - original +% 2016 - included comutation of local efficiency +% 2016 - included transform variable that maps strengths onto distances + + +if ~exist('transform','var') + transform = []; +end + +n=length(D); % number of nodes + +Erout = distance_wei_floyd(D,transform); % pair-wise routing efficiency +Erout = 1./Erout; +Erout(eye(n)>0) = 0; +GErout = sum(Erout(~eye(n)>0))/(n^2-n); % global routing efficiency + +if nargout == 3 + Eloc = zeros(n,1); + for u = 1:n + Gu = find(D(u,:) | D(:,u).'); % u's neighbors + nGu = length(Gu); + e = distance_wei_floyd(D(Gu,Gu),transform); + Eloc(u) = sum(sum(1./e(~eye(nGu)>0)))/nGu; % efficiency of subgraph Gu + end +end diff --git a/DefaultData/2019_03_03_BCT/score_wu.m b/DefaultData/2019_03_03_BCT/score_wu.m new file mode 100755 index 0000000..08b926c --- /dev/null +++ b/DefaultData/2019_03_03_BCT/score_wu.m @@ -0,0 +1,40 @@ +function [CIJscore,sn] = score_wu(CIJ,s) +%SCORE_WU S-score +% +% [CIJscore,sn] = score_wu(CIJ,s); +% +% The s-core is the largest subnetwork comprising nodes of strength at +% least s. This function computes the s-core for a given weighted +% undirected connection matrix. Computation is analogous to the more +% widely used k-core, but is based on node strengths instead of node +% degrees. +% +% input: CIJ, connection/adjacency matrix (weighted, undirected) +% s, level of s-core. Note: s can take on any fractional value +% +% output: CIJscore, connection matrix of the s-core. This matrix +% contains only nodes with a strength of at least s. +% sn, size of s-score +% +% Olaf Sporns, Indiana University, 2007/2008/2010/2012 + +while 1 + + % get strengths of matrix + [str] = strengths_und(CIJ); + + % find nodes with strength 0)); + + % if none found -> stop + if (isempty(ff)) break; end; %#ok + + % peel found nodes + CIJ(ff,:) = 0; + CIJ(:,ff) = 0; + +end; + +CIJscore = CIJ; +sn = sum(str>0); + diff --git a/DefaultData/2019_03_03_BCT/search_information.m b/DefaultData/2019_03_03_BCT/search_information.m new file mode 100755 index 0000000..7791473 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/search_information.m @@ -0,0 +1,117 @@ +function SI = search_information(W, L, has_memory) +% SEARCH_INFORMATION Search information +% +% SI = search_information(W, L,has_memory) +% +% Computes the amount of information (measured in bits) that a random +% walker needs to follow the shortest path between a given pair of nodes. +% +% Inputs: +% +% W +% Weighted/unweighted directed/undirected +% connection weight matrix. +% +% L +% Weighted/unweighted directed/undirected +% connection length matrix. +% +% has_memory, +% This flag defines whether or not the random walker "remembers" +% its previous step, which has the effect of reducing the amount +% of information needed to find the next state. If this flag is +% not set, the walker has no memory by default. +% +% +% Outputs: +% +% SI, +% pair-wise search information (matrix). Note that SI(i,j) may be +% different from SI(j,i), hense, SI is not a symmetric matrix +% even when adj is symmetric. +% +% +% References: Rosvall et al. (2005) Phys Rev Lett 94, 028701 +% Goñi et al (2014) PNAS doi: 10.1073/pnas.131552911 +% +% +% Andrea Avena-Koenigsberger and Joaquin Goñi, IU Bloomington, 2014 +% Caio Seguin, University of Melbourne, 2019 + +% Modification history +% 2014 - original +% 2016 - included SPL transform option and generalized for +% symmetric/asymmetric networks +% 2019 - modified to make user directly specify weight-to-length transformations + + +if ~exist('has_memory','var') + has_memory = false; +end + +N = size(W,1); + +if issymmetric(W) + flag_triu = true; +else + flag_triu = false; +end + +T = diag(sum(W,2))\W; +[~,hops,Pmat] = distance_wei_floyd(L,[]); % Compute shortest paths based on L + +SI = zeros(N,N); +SI(eye(N)>0) = nan; + +for i = 1:N + for j = 1:N + if (j > i && flag_triu) || (~flag_triu && i ~= j) + path = retrieve_shortest_path(i,j,hops,Pmat); + lp = length(path); + if flag_triu + if ~isempty(path) + pr_step_ff = nan(1,lp-1); + pr_step_bk = nan(1,lp-1); + if has_memory + pr_step_ff(1) = T(path(1),path(2)); + pr_step_bk(lp-1) = T(path(lp),path(lp-1)); + for z=2:lp-1 + pr_step_ff(z) = T(path(z),path(z+1))/(1 - T(path(z-1),path(z))); + pr_step_bk(lp-z) = T(path(lp-z+1),path(lp-z))/(1 - T(path(lp-z+2),path(lp-z+1))); + end + else + for z=1:length(path)-1 + pr_step_ff(z) = T(path(z),path(z+1)); + pr_step_bk(z) = T(path(z+1),path(z)); + end + end + prob_sp_ff = prod(pr_step_ff); + prob_sp_bk = prod(pr_step_bk); + SI(i,j) = -log2(prob_sp_ff); + SI(j,i) = -log2(prob_sp_bk); + else + SI(i,j) = inf; + SI(j,i) = inf; + end + else + if ~isempty(path) + pr_step_ff = nan(1,lp-1); + if has_memory + pr_step_ff(1) = T(path(1),path(2)); + for z=2:lp-1 + pr_step_ff(z) = T(path(z),path(z+1))/(1 - T(path(z-1),path(z))); + end + else + for z=1:length(path)-1 + pr_step_ff(z) = T(path(z),path(z+1)); + end + end + prob_sp_ff = prod(pr_step_ff); + SI(i,j) = -log2(prob_sp_ff); + else + SI(i,j) = inf; + end + end + end + end +end diff --git a/DefaultData/2019_03_03_BCT/strengths_dir.m b/DefaultData/2019_03_03_BCT/strengths_dir.m new file mode 100755 index 0000000..ac4726e --- /dev/null +++ b/DefaultData/2019_03_03_BCT/strengths_dir.m @@ -0,0 +1,27 @@ +function [is,os,str] = strengths_dir(CIJ) +%STRENGTHS_DIR In-strength and out-strength +% +% [is,os,str] = strengths_dir(CIJ); +% +% Node strength is the sum of weights of links connected to the node. The +% instrength is the sum of inward link weights and the outstrength is the +% sum of outward link weights. +% +% Input: CIJ, directed weighted connection matrix +% +% Output: is, node instrength +% os, node outstrength +% str, node strength (instrength + outstrength) +% +% Notes: Inputs are assumed to be on the columns of the CIJ matrix. +% +% +% Olaf Sporns, Indiana University, 2002/2006/2008 + + +% compute strengths +is = sum(CIJ,1); % instrength = column sum of CIJ +os = sum(CIJ,2)'; % outstrength = row sum of CIJ +str = is+os; % strength = instrength+outstrength + + diff --git a/DefaultData/2019_03_03_BCT/strengths_und.m b/DefaultData/2019_03_03_BCT/strengths_und.m new file mode 100755 index 0000000..4eb844a --- /dev/null +++ b/DefaultData/2019_03_03_BCT/strengths_und.m @@ -0,0 +1,18 @@ +function [str] = strengths_und(CIJ) +%STRENGTHS_UND Strength +% +% str = strengths_und(CIJ); +% +% Node strength is the sum of weights of links connected to the node. +% +% Input: CIJ, undirected weighted connection matrix +% +% Output: str, node strength +% +% +% Olaf Sporns, Indiana University, 2002/2006/2008 + +% compute strengths +str = sum(CIJ); % strength + + diff --git a/DefaultData/2019_03_03_BCT/strengths_und_sign.m b/DefaultData/2019_03_03_BCT/strengths_und_sign.m new file mode 100755 index 0000000..9d3d66a --- /dev/null +++ b/DefaultData/2019_03_03_BCT/strengths_und_sign.m @@ -0,0 +1,30 @@ +function [Spos,Sneg,vpos,vneg] = strengths_und_sign(W) +%STRENGTHS_UND_SIGN Strength and weight +% +% [Spos Sneg] = strengths_und_sign(W); +% [Spos Sneg vpos vneg] = strengths_und_sign(W); +% +% Node strength is the sum of weights of links connected to the node. +% +% Inputs: W, undirected connection matrix with positive +% and negative weights +% +% Output: Spos/Sneg, nodal strength of positive/negative weights +% vpos/vneg, total positive/negative weight +% +% +% 2011, Mika Rubinov, UNSW + +% Modification History: +% Mar 2011: Original + + +n = length(W); %number of nodes +W(1:n+1:end) = 0; %clear diagonal +Spos = sum( W.*(W>0)); %positive strengths +Sneg = sum(-W.*(W<0)); %negative strengths + +if nargout>2 + vpos = sum(Spos); %positive weight + vneg = sum(Sneg); %negative weight +end \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/subgraph_centrality.m b/DefaultData/2019_03_03_BCT/subgraph_centrality.m new file mode 100755 index 0000000..f753098 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/subgraph_centrality.m @@ -0,0 +1,24 @@ +function Cs = subgraph_centrality(CIJ) +% SUBGRAPH_CENTRALITY Subgraph centrality of a network +% +% Cs = subgraph_centrality(CIJ) +% +% The subgraph centrality of a node is a weighted sum of closed walks of +% different lengths in the network starting and ending at the node. This +% function returns a vector of subgraph centralities for each node of the +% network. +% +% Inputs: CIJ, adjacency matrix (binary) +% +% Outputs: Cs, subgraph centrality +% +% Reference: Estrada and Rodriguez-Velasquez (2005) Phys Rev E 71, 056103 +% Estrada and Higham (2010) SIAM Rev 52, 696. +% +% Xi-Nian Zuo, Chinese Academy of Sciences, 2010 +% Rick Betzel, Indiana University, 2012 + +[V,lambda] = eig(CIJ); % Compute the eigenvectors and +lambda = diag(lambda); % eigenvalues. +V2 = V.^2; % Matrix of squares of the eigenvectors elements. +Cs = real(V2 * exp(lambda)); % Compute eigenvector centrality. Lop off imaginary part remaining due to precision error. \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/threshold_absolute.m b/DefaultData/2019_03_03_BCT/threshold_absolute.m new file mode 100755 index 0000000..e95bc06 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/threshold_absolute.m @@ -0,0 +1,19 @@ +function W = threshold_absolute(W, thr) +% THRESHOLD_ABSOLUTE Absolute thresholding +% +% W_thr = threshold_absolute(W, thr); +% +% This function thresholds the connectivity matrix by absolute weight +% magnitude. All weights below the given threshold, and all weights +% on the main diagonal (self-self connections) are set to 0. +% +% Inputs: W weighted or binary connectivity matrix +% thr weight treshold +% +% Output: W_thr thresholded connectivity matrix +% +% +% Mika Rubinov, UNSW, 2009-2010 + +W(1:size(W,1)+1:end)=0; %clear diagonal +W(Wj edge pairs (these do not generate triangles). The number of +% false pairs is the main diagonal of A^2. Thus the maximum possible +% number of triangles = (2 edges)*([ALL PAIRS] - [FALSE PAIRS]) +% = 2 * (K(K-1)/2 - diag(A^2)) +% = K(K-1) - 2(diag(A^2)) + +S = A+A.'; % symmetrized input graph +K = sum(S,2); % total degree (in + out) +cyc3 = diag(S^3)/2; % number of 3-cycles (ie. directed triangles) +CYC3 = K.*(K-1)-2*diag(A^2); % number of all possible 3-cycles +T = sum(cyc3)./sum(CYC3); % transitivity diff --git a/DefaultData/2019_03_03_BCT/transitivity_bu.m b/DefaultData/2019_03_03_BCT/transitivity_bu.m new file mode 100755 index 0000000..683b817 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/transitivity_bu.m @@ -0,0 +1,20 @@ +function [C_tri]=transitivity_bu(A) +%TRANSITIVITY_BU Transitivity +% +% T = transitivity_bu(A); +% +% Transitivity is the ratio of 'triangles to triplets' in the network. +% (A classical version of the clustering coefficient). +% +% Input: A binary undirected connection matrix +% +% Output: T transitivity scalar +% +% Reference: e.g. Humphries et al. (2008) Plos ONE 3: e0002051 +% +% +% Alexandros Goulas, Maastricht University, 2010 + + C_tri = trace(A^3) / (sum(sum(A^2)) - trace(A^2)); + +return; \ No newline at end of file diff --git a/DefaultData/2019_03_03_BCT/transitivity_wd.m b/DefaultData/2019_03_03_BCT/transitivity_wd.m new file mode 100755 index 0000000..6809956 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/transitivity_wd.m @@ -0,0 +1,47 @@ +function T=transitivity_wd(W) +%TRANSITIVITY_WD Transitivity +% +% T = transitivity_wd(W); +% +% Transitivity is the ratio of 'triangles to triplets' in the network. +% (A classical version of the clustering coefficient). +% +% Input: W weighted directed connection matrix +% +% Output: T transitivity scalar +% +% Note: All weights must be between 0 and 1. +% This may be achieved using the weight_conversion.m function, +% W_nrm = weight_conversion(W, 'normalize'); +% +% Reference: Rubinov M, Sporns O (2010) NeuroImage 52:1059-69 +% based on Fagiolo (2007) Phys Rev E 76:026107. +% +% +% Contributors: +% Mika Rubinov, UNSW/University of Cambridge +% Christoph Schmidt, Friedrich Schiller University Jena +% Andrew Zalesky, University of Melbourne +% 2007-2015 + +% Modification history: +% 2007: original (MR) +% 2013, 2015: removed tests for absence of nodewise 3-cycles (CS,AZ) +% 2015: Expanded documentation + + +% Methodological note (also see note for clustering_coef_bd) +% The weighted modification is as follows: +% - The numerator: adjacency matrix is replaced with weights matrix ^ 1/3 +% - The denominator: no changes from the binary version +% +% The above reduces to symmetric and/or binary versions of the clustering +% coefficient for respective graphs. + +A = W~=0; % adjacency matrix +S = W.^(1/3)+(W.').^(1/3); % symmetrized weights matrix ^1/3 +K = sum(A+A.',2); % total degree (in + out) +cyc3 = diag(S^3)/2; % number of 3-cycles (ie. directed triangles) +CYC3 = K.*(K-1)-2*diag(A^2); % number of all possible 3-cycles +T = sum(cyc3)./sum(CYC3); % transitivity + diff --git a/DefaultData/2019_03_03_BCT/transitivity_wu.m b/DefaultData/2019_03_03_BCT/transitivity_wu.m new file mode 100755 index 0000000..232d7be --- /dev/null +++ b/DefaultData/2019_03_03_BCT/transitivity_wu.m @@ -0,0 +1,29 @@ +function T=transitivity_wu(W) +%TRANSITIVITY_WU Transitivity +% +% T = transitivity_wu(W); +% +% Transitivity is the ratio of 'triangles to triplets' in the network. +% (A classical version of the clustering coefficient). +% +% Input: W weighted undirected connection matrix +% +% Output: T transitivity scalar +% +% Note: All weights must be between 0 and 1. +% This may be achieved using the weight_conversion.m function, +% W_nrm = weight_conversion(W, 'normalize'); +% +% Reference: Rubinov M, Sporns O (2010) NeuroImage 52:1059-69 +% based on Onnela et al. (2005) Phys Rev E 71:065103 +% +% +% Mika Rubinov, UNSW/U Cambridge, 2010-2015 + +% Modification history: +% 2010: Original +% 2015: Expanded documentation + +K = sum(W~=0,2); +cyc3 = diag((W.^(1/3))^3); +T = sum(cyc3)./sum((K.*(K-1))); %transitivity diff --git a/DefaultData/2019_03_03_BCT/weight_conversion.m b/DefaultData/2019_03_03_BCT/weight_conversion.m new file mode 100755 index 0000000..f6b93f0 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/weight_conversion.m @@ -0,0 +1,88 @@ +function W = weight_conversion(W, wcm) +% WEIGHT_CONVERSION Conversion of weights in input matrix +% +% W_bin = weight_conversion(W, 'binarize'); +% W_nrm = weight_conversion(W, 'normalize'); +% L = weight_conversion(W, 'lengths'); +% W_fix = weight_conversion(W, 'autofix'); +% +% This function may either binarize an input weighted connection matrix, +% normalize an input weighted connection matrix, convert an input +% weighted connection matrix to a weighted connection-length matrix, or +% fix common connection problems in binary or weighted connection matrices. +% +% Binarization converts all present connection weights to 1. +% +% Normalization rescales all weight magnitudes to the range [0,1] and +% should be done prior to computing some weighted measures, such as the +% weighted clustering coefficient. +% +% Conversion of connection weights to connection lengths is needed +% prior to computation of weighted distance-based measures, such as +% distance and betweenness centrality. In a weighted connection network, +% higher weights are naturally interpreted as shorter lengths. The +% connection-lengths matrix here is defined as the inverse of the +% connection-weights matrix. +% +% Autofix removes all Inf and NaN values, remove all self connections +% (sets all weights on the main diagonal to 0), ensures that symmetric matrices +% are exactly symmetric (by correcting for round-off error), and ensures that +% binary matrices are exactly binary (by correcting for round-off error). +% +% Inputs: W binary or weighted connectivity matrix +% wcm weight-conversion command - possible values: +% 'binarize' binarize weights +% 'normalize' normalize weights + +% 'lengths' convert weights to lengths +% 'autofix' fixes common weights problems +% +% Output: W_ output connectivity matrix +% +% +% Mika Rubinov, U Cambridge, 2012 + +% Modification History: +% Sep 2012: Original +% Jan 2015: Added autofix feature. +% Jan 2017: Corrected bug in autofix (thanks to Jeff Spielberg) + +switch wcm + case 'binarize' + W=double(W~=0); % binarize + case 'normalize' + W=W./max(abs(W(:))); % rescale by maximal weight + case 'lengths' + E=find(W); + W(E)=1./W(E); % invert weights + case 'autofix' + % clear diagonal + n = length(W); + W(1:n+1:end)=0; + + % remove Infs and NaNs + idx = isnan(W) | isinf(W); + if any(any(idx)) + W(idx)=0; + end + + % ensure exact binariness + U = unique(W); + if nnz(U) > 1 + idx_0 = abs(W ) < 1e-10; + idx_1 = abs(W-1) < 1e-10; + if all(all(idx_0 | idx_1)) + W(idx_0)=0; + W(idx_1)=1; + end + end + + % ensure exact symmetry + if ~isequal(W,W.') + if max(max(abs(W-W.'))) < 1e-10 + W=(W+W).'/2; + end + end + otherwise + error('Unknown weight-conversion command.') +end diff --git a/DefaultData/2019_03_03_BCT/writetoPAJ.m b/DefaultData/2019_03_03_BCT/writetoPAJ.m new file mode 100755 index 0000000..3827786 --- /dev/null +++ b/DefaultData/2019_03_03_BCT/writetoPAJ.m @@ -0,0 +1,41 @@ +function writetoPAJ(CIJ, fname, arcs) +%WRITETOPAJ Write to Pajek +% +% writetoPAJ(CIJ, fname, arcs); +% +% This function writes a Pajek .net file from a MATLAB matrix +% +% Inputs: CIJ, adjacency matrix +% fname, filename minus .net extension +% arcs, 1 for directed network +% 0 for an undirected network +% +% Chris Honey, Indiana University, 2007 + + +N = size(CIJ,1); +fid = fopen(cat(2,fname,'.net'), 'w'); + +%%%VERTICES +fprintf(fid, '*vertices %6i \r', N); +for i = 1:N + fprintf(fid, '%6i "%6i" \r', [i i]); +end + +%%%ARCS/EDGES +if arcs + fprintf(fid, '*arcs \r'); +else + fprintf(fid, '*edges \r'); +end + +for i = 1:N + for j = 1:N + if CIJ(i,j) ~= 0 + fprintf(fid, '%6i %6i %6f \r', [i j CIJ(i,j)]); + end + end +end + +fclose(fid); + diff --git a/DefaultData/BrainMask.nii b/DefaultData/BrainMask.nii new file mode 100644 index 0000000..541c242 Binary files /dev/null and b/DefaultData/BrainMask.nii differ diff --git a/DefaultData/CerebroMask.nii b/DefaultData/CerebroMask.nii new file mode 100644 index 0000000..b4c36b7 Binary files /dev/null and b/DefaultData/CerebroMask.nii differ diff --git a/DefaultData/Default_mask.nii b/DefaultData/Default_mask.nii new file mode 100644 index 0000000..d7f3981 Binary files /dev/null and b/DefaultData/Default_mask.nii differ diff --git a/DefaultData/Underlay.nii b/DefaultData/Underlay.nii new file mode 100755 index 0000000..23d0901 Binary files /dev/null and b/DefaultData/Underlay.nii differ diff --git a/DefaultData/WhiteMask.nii b/DefaultData/WhiteMask.nii new file mode 100644 index 0000000..4541eed Binary files /dev/null and b/DefaultData/WhiteMask.nii differ diff --git a/DefaultData/brain.mat b/DefaultData/brain.mat new file mode 100755 index 0000000..4023a14 Binary files /dev/null and b/DefaultData/brain.mat differ diff --git a/DefaultData/load_nii.m b/DefaultData/load_nii.m new file mode 100755 index 0000000..42415a5 --- /dev/null +++ b/DefaultData/load_nii.m @@ -0,0 +1,138 @@ +% Load NIFTI or ANALYZE dataset. Support both *.nii and *.hdr/*.img +% file extension. If file extension is not provided, *.hdr/*.img will +% be used as default. +% +% A subset of NIFTI transform is included. For non-orthogonal rotation, +% shearing etc., please use 'reslice_nii.m' to reslice the NIFTI file. +% It will not cause negative effect, as long as you remember not to do +% slice time correction after reslicing the NIFTI file. Output variable +% nii will be in RAS orientation, i.e. X axis from Left to Right, +% Y axis from Posterior to Anterior, and Z axis from Inferior to +% Superior. +% +% Usage: nii = load_nii(filename, [img_idx], [dim5_idx], [dim6_idx], ... +% [dim7_idx], [old_RGB], [tolerance], [preferredForm]) +% +% filename - NIFTI or ANALYZE file name. +% +% img_idx (optional) - a numerical array of 4th dimension indices, +% which is the indices of image scan volume. The number of images +% scan volumes can be obtained from get_nii_frame.m, or simply +% hdr.dime.dim(5). Only the specified volumes will be loaded. +% All available image volumes will be loaded, if it is default or +% empty. +% +% dim5_idx (optional) - a numerical array of 5th dimension indices. +% Only the specified range will be loaded. All available range +% will be loaded, if it is default or empty. +% +% dim6_idx (optional) - a numerical array of 6th dimension indices. +% Only the specified range will be loaded. All available range +% will be loaded, if it is default or empty. +% +% dim7_idx (optional) - a numerical array of 7th dimension indices. +% Only the specified range will be loaded. All available range +% will be loaded, if it is default or empty. +% +% old_RGB (optional) - a scale number to tell difference of new RGB24 +% from old RGB24. New RGB24 uses RGB triple sequentially for each +% voxel, like [R1 G1 B1 R2 G2 B2 ...]. Analyze 6.0 from AnalyzeDirect +% uses old RGB24, in a way like [R1 R2 ... G1 G2 ... B1 B2 ...] for +% each slices. If the image that you view is garbled, try to set +% old_RGB variable to 1 and try again, because it could be in +% old RGB24. It will be set to 0, if it is default or empty. +% +% tolerance (optional) - distortion allowed in the loaded image for any +% non-orthogonal rotation or shearing of NIfTI affine matrix. If +% you set 'tolerance' to 0, it means that you do not allow any +% distortion. If you set 'tolerance' to 1, it means that you do +% not care any distortion. The image will fail to be loaded if it +% can not be tolerated. The tolerance will be set to 0.1 (10%), if +% it is default or empty. +% +% preferredForm (optional) - selects which transformation from voxels +% to RAS coordinates; values are s,q,S,Q. Lower case s,q indicate +% "prefer sform or qform, but use others if preferred not present". +% Upper case indicate the program is forced to use the specificied +% tranform or fail loading. 'preferredForm' will be 's', if it is +% default or empty. - Jeff Gunter +% +% Returned values: +% +% nii structure: +% +% hdr - struct with NIFTI header fields. +% +% filetype - Analyze format .hdr/.img (0); +% NIFTI .hdr/.img (1); +% NIFTI .nii (2) +% +% fileprefix - NIFTI filename without extension. +% +% machine - machine string variable. +% +% img - 3D (or 4D) matrix of NIFTI data. +% +% original - the original header before any affine transform. +% +% Part of this file is copied and modified from: +% http://www.mathworks.com/matlabcentral/fileexchange/1878-mri-analyze-tools +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function nii = load_nii(filename, img_idx, dim5_idx, dim6_idx, dim7_idx, ... + old_RGB, tolerance, preferredForm) + + if ~exist('filename','var') + error('Usage: nii = load_nii(filename, [img_idx], [dim5_idx], [dim6_idx], [dim7_idx], [old_RGB], [tolerance], [preferredForm])'); + end + + if ~exist('img_idx','var') | isempty(img_idx) + img_idx = []; + end + + if ~exist('dim5_idx','var') | isempty(dim5_idx) + dim5_idx = []; + end + + if ~exist('dim6_idx','var') | isempty(dim6_idx) + dim6_idx = []; + end + + if ~exist('dim7_idx','var') | isempty(dim7_idx) + dim7_idx = []; + end + + if ~exist('old_RGB','var') | isempty(old_RGB) + old_RGB = 0; + end + + if ~exist('tolerance','var') | isempty(tolerance) + tolerance = 0.1; % 10 percent + end + + if ~exist('preferredForm','var') | isempty(preferredForm) + preferredForm= 's'; % Jeff + end + + % Read the dataset header + % + [nii.hdr,nii.filetype,nii.fileprefix,nii.machine] = load_nii_hdr(filename); + + % Read the header extension + % +% nii.ext = load_nii_ext(filename); + + % Read the dataset body + % + [nii.img,nii.hdr] = load_nii_img(nii.hdr,nii.filetype,nii.fileprefix, ... + nii.machine,img_idx,dim5_idx,dim6_idx,dim7_idx,old_RGB); + + % Perform some of sform/qform transform + % + nii = xform_nii(nii, tolerance, preferredForm); + + return % load_nii + diff --git a/DefaultData/load_nii_hdr.m b/DefaultData/load_nii_hdr.m new file mode 100755 index 0000000..a915a74 --- /dev/null +++ b/DefaultData/load_nii_hdr.m @@ -0,0 +1,320 @@ +% Load NIFTI dataset header. Support both *.nii and *.hdr/*.img file +% extension. If file extension is not provided, *.hdr/*.img will be +% used as default. +% +% Usage: [hdr, filetype, fileprefix, machine] = load_nii_hdr(filename) +% +% filename - NIFTI file name. +% +% Returned values: +% +% hdr - struct with NIFTI header fields. +% +% filetype - 0 for Analyze format (*.hdr/*.img); +% 1 for NIFTI format in 2 files (*.hdr/*.img); +% 2 for NIFTI format in 1 file (*.nii). +% +% fileprefix - NIFTI file name without extension. +% +% machine - a string, see below for details. The default here is 'ieee-le'. +% +% 'native' or 'n' - local machine format - the default +% 'ieee-le' or 'l' - IEEE floating point with little-endian +% byte ordering +% 'ieee-be' or 'b' - IEEE floating point with big-endian +% byte ordering +% 'vaxd' or 'd' - VAX D floating point and VAX ordering +% 'vaxg' or 'g' - VAX G floating point and VAX ordering +% 'cray' or 'c' - Cray floating point with big-endian +% byte ordering +% 'ieee-le.l64' or 'a' - IEEE floating point with little-endian +% byte ordering and 64 bit long data type +% 'ieee-be.l64' or 's' - IEEE floating point with big-endian byte +% ordering and 64 bit long data type. +% +% Number of scanned images in the file can be obtained by: +% num_scan = hdr.dime.dim(5) +% +% Part of this file is copied and modified from: +% http://www.mathworks.com/matlabcentral/fileexchange/1878-mri-analyze-tools +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function [hdr, filetype, fileprefix, machine] = load_nii_hdr(fileprefix) + + if ~exist('fileprefix','var'), + error('Usage: [hdr, filetype, fileprefix, machine] = load_nii_hdr(filename)'); + end + + machine = 'ieee-le'; + new_ext = 0; + + if findstr('.nii',fileprefix) + new_ext = 1; + fileprefix = strrep(fileprefix,'.nii',''); + end + + if findstr('.hdr',fileprefix) + fileprefix = strrep(fileprefix,'.hdr',''); + end + + if findstr('.img',fileprefix) + fileprefix = strrep(fileprefix,'.img',''); + end + + if new_ext + fn = sprintf('%s.nii',fileprefix); + + if ~exist(fn) + msg = sprintf('Cannot find file "%s.nii".', fileprefix); + error(msg); + end + else + fn = sprintf('%s.hdr',fileprefix); + + if ~exist(fn) + msg = sprintf('Cannot find file "%s.hdr".', fileprefix); + error(msg); + end + end + + fid = fopen(fn,'r',machine); + + if fid < 0, + msg = sprintf('Cannot open file %s.',fn); + error(msg); + else + fseek(fid,0,'bof'); + + if fread(fid,1,'int32') == 348 + hdr = read_header(fid); + fclose(fid); + else + fclose(fid); + + % first try reading the opposite endian to 'machine' + % + switch machine, + case 'ieee-le', machine = 'ieee-be'; + case 'ieee-be', machine = 'ieee-le'; + end + + fid = fopen(fn,'r',machine); + + if fid < 0, + msg = sprintf('Cannot open file %s.',fn); + error(msg); + else + fseek(fid,0,'bof'); + + if fread(fid,1,'int32') ~= 348 + + % Now throw an error + % + msg = sprintf('File "%s" is corrupted.',fn); + error(msg); + end + + hdr = read_header(fid); + fclose(fid); + end + end + end + + if strcmp(hdr.hist.magic, 'n+1') + filetype = 2; + elseif strcmp(hdr.hist.magic, 'ni1') + filetype = 1; + else + filetype = 0; + end + + return % load_nii_hdr + + +%--------------------------------------------------------------------- +function [ dsr ] = read_header(fid) + + % Original header structures + % struct dsr + % { + % struct header_key hk; /* 0 + 40 */ + % struct image_dimension dime; /* 40 + 108 */ + % struct data_history hist; /* 148 + 200 */ + % }; /* total= 348 bytes*/ + + dsr.hk = header_key(fid); + dsr.dime = image_dimension(fid); + dsr.hist = data_history(fid); + + % For Analyze data format + % + if ~strcmp(dsr.hist.magic, 'n+1') & ~strcmp(dsr.hist.magic, 'ni1') + dsr.hist.qform_code = 0; + dsr.hist.sform_code = 0; + end + + return % read_header + + +%--------------------------------------------------------------------- +function [ hk ] = header_key(fid) + + fseek(fid,0,'bof'); + + % Original header structures + % struct header_key /* header key */ + % { /* off + size */ + % int sizeof_hdr /* 0 + 4 */ + % char data_type[10]; /* 4 + 10 */ + % char db_name[18]; /* 14 + 18 */ + % int extents; /* 32 + 4 */ + % short int session_error; /* 36 + 2 */ + % char regular; /* 38 + 1 */ + % char dim_info; % char hkey_un0; /* 39 + 1 */ + % }; /* total=40 bytes */ + % + % int sizeof_header Should be 348. + % char regular Must be 'r' to indicate that all images and + % volumes are the same size. + + v6 = version; + if str2num(v6(1))<6 + directchar = '*char'; + else + directchar = 'uchar=>char'; + end + + hk.sizeof_hdr = fread(fid, 1,'int32')'; % should be 348! + hk.data_type = deblank(fread(fid,10,directchar)'); + hk.db_name = deblank(fread(fid,18,directchar)'); + hk.extents = fread(fid, 1,'int32')'; + hk.session_error = fread(fid, 1,'int16')'; + hk.regular = fread(fid, 1,directchar)'; + hk.dim_info = fread(fid, 1,'uchar')'; + + return % header_key + + +%--------------------------------------------------------------------- +function [ dime ] = image_dimension(fid) + + % Original header structures + % struct image_dimension + % { /* off + size */ + % short int dim[8]; /* 0 + 16 */ + % /* + % dim[0] Number of dimensions in database; usually 4. + % dim[1] Image X dimension; number of *pixels* in an image row. + % dim[2] Image Y dimension; number of *pixel rows* in slice. + % dim[3] Volume Z dimension; number of *slices* in a volume. + % dim[4] Time points; number of volumes in database + % */ + % float intent_p1; % char vox_units[4]; /* 16 + 4 */ + % float intent_p2; % char cal_units[8]; /* 20 + 4 */ + % float intent_p3; % char cal_units[8]; /* 24 + 4 */ + % short int intent_code; % short int unused1; /* 28 + 2 */ + % short int datatype; /* 30 + 2 */ + % short int bitpix; /* 32 + 2 */ + % short int slice_start; % short int dim_un0; /* 34 + 2 */ + % float pixdim[8]; /* 36 + 32 */ + % /* + % pixdim[] specifies the voxel dimensions: + % pixdim[1] - voxel width, mm + % pixdim[2] - voxel height, mm + % pixdim[3] - slice thickness, mm + % pixdim[4] - volume timing, in msec + % ..etc + % */ + % float vox_offset; /* 68 + 4 */ + % float scl_slope; % float roi_scale; /* 72 + 4 */ + % float scl_inter; % float funused1; /* 76 + 4 */ + % short slice_end; % float funused2; /* 80 + 2 */ + % char slice_code; % float funused2; /* 82 + 1 */ + % char xyzt_units; % float funused2; /* 83 + 1 */ + % float cal_max; /* 84 + 4 */ + % float cal_min; /* 88 + 4 */ + % float slice_duration; % int compressed; /* 92 + 4 */ + % float toffset; % int verified; /* 96 + 4 */ + % int glmax; /* 100 + 4 */ + % int glmin; /* 104 + 4 */ + % }; /* total=108 bytes */ + + dime.dim = fread(fid,8,'int16')'; + dime.intent_p1 = fread(fid,1,'float32')'; + dime.intent_p2 = fread(fid,1,'float32')'; + dime.intent_p3 = fread(fid,1,'float32')'; + dime.intent_code = fread(fid,1,'int16')'; + dime.datatype = fread(fid,1,'int16')'; + dime.bitpix = fread(fid,1,'int16')'; + dime.slice_start = fread(fid,1,'int16')'; + dime.pixdim = fread(fid,8,'float32')'; + dime.vox_offset = fread(fid,1,'float32')'; + dime.scl_slope = fread(fid,1,'float32')'; + dime.scl_inter = fread(fid,1,'float32')'; + dime.slice_end = fread(fid,1,'int16')'; + dime.slice_code = fread(fid,1,'uchar')'; + dime.xyzt_units = fread(fid,1,'uchar')'; + dime.cal_max = fread(fid,1,'float32')'; + dime.cal_min = fread(fid,1,'float32')'; + dime.slice_duration = fread(fid,1,'float32')'; + dime.toffset = fread(fid,1,'float32')'; + dime.glmax = fread(fid,1,'int32')'; + dime.glmin = fread(fid,1,'int32')'; + + return % image_dimension + + +%--------------------------------------------------------------------- +function [ hist ] = data_history(fid) + + % Original header structures + % struct data_history + % { /* off + size */ + % char descrip[80]; /* 0 + 80 */ + % char aux_file[24]; /* 80 + 24 */ + % short int qform_code; /* 104 + 2 */ + % short int sform_code; /* 106 + 2 */ + % float quatern_b; /* 108 + 4 */ + % float quatern_c; /* 112 + 4 */ + % float quatern_d; /* 116 + 4 */ + % float qoffset_x; /* 120 + 4 */ + % float qoffset_y; /* 124 + 4 */ + % float qoffset_z; /* 128 + 4 */ + % float srow_x[4]; /* 132 + 16 */ + % float srow_y[4]; /* 148 + 16 */ + % float srow_z[4]; /* 164 + 16 */ + % char intent_name[16]; /* 180 + 16 */ + % char magic[4]; % int smin; /* 196 + 4 */ + % }; /* total=200 bytes */ + + v6 = version; + if str2num(v6(1))<6 + directchar = '*char'; + else + directchar = 'uchar=>char'; + end + + hist.descrip = deblank(fread(fid,80,directchar)'); + hist.aux_file = deblank(fread(fid,24,directchar)'); + hist.qform_code = fread(fid,1,'int16')'; + hist.sform_code = fread(fid,1,'int16')'; + hist.quatern_b = fread(fid,1,'float32')'; + hist.quatern_c = fread(fid,1,'float32')'; + hist.quatern_d = fread(fid,1,'float32')'; + hist.qoffset_x = fread(fid,1,'float32')'; + hist.qoffset_y = fread(fid,1,'float32')'; + hist.qoffset_z = fread(fid,1,'float32')'; + hist.srow_x = fread(fid,4,'float32')'; + hist.srow_y = fread(fid,4,'float32')'; + hist.srow_z = fread(fid,4,'float32')'; + hist.intent_name = deblank(fread(fid,16,directchar)'); + hist.magic = deblank(fread(fid,4,directchar)'); + + fseek(fid,253,'bof'); + hist.originator = fread(fid, 5,'int16')'; + + return % data_history + diff --git a/DefaultData/load_nii_img.m b/DefaultData/load_nii_img.m new file mode 100755 index 0000000..e8d6e5f --- /dev/null +++ b/DefaultData/load_nii_img.m @@ -0,0 +1,386 @@ +% internal function + +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) + +function [img,hdr] = load_nii_img(hdr,filetype,fileprefix,machine,img_idx,dim5_idx,dim6_idx,dim7_idx,old_RGB) + + if ~exist('hdr','var') | ~exist('filetype','var') | ~exist('fileprefix','var') | ~exist('machine','var') + error('Usage: [img,hdr] = load_nii_img(hdr,filetype,fileprefix,machine,[img_idx],[dim5_idx],[dim6_idx],[dim7_idx],[old_RGB]);'); + end + + if ~exist('img_idx','var') | isempty(img_idx) | hdr.dime.dim(5)<1 + img_idx = []; + end + + if ~exist('dim5_idx','var') | isempty(dim5_idx) | hdr.dime.dim(6)<1 + dim5_idx = []; + end + + if ~exist('dim6_idx','var') | isempty(dim6_idx) | hdr.dime.dim(7)<1 + dim6_idx = []; + end + + if ~exist('dim7_idx','var') | isempty(dim7_idx) | hdr.dime.dim(8)<1 + dim7_idx = []; + end + + if ~exist('old_RGB','var') | isempty(old_RGB) + old_RGB = 0; + end + + % check img_idx + % + if ~isempty(img_idx) & ~isnumeric(img_idx) + error('"img_idx" should be a numerical array.'); + end + + if length(unique(img_idx)) ~= length(img_idx) + error('Duplicate image index in "img_idx"'); + end + + if ~isempty(img_idx) & (min(img_idx) < 1 | max(img_idx) > hdr.dime.dim(5)) + max_range = hdr.dime.dim(5); + + if max_range == 1 + error(['"img_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"img_idx" should be an integer within the range of [' range '].']); + end + end + + % check dim5_idx + % + if ~isempty(dim5_idx) & ~isnumeric(dim5_idx) + error('"dim5_idx" should be a numerical array.'); + end + + if length(unique(dim5_idx)) ~= length(dim5_idx) + error('Duplicate index in "dim5_idx"'); + end + + if ~isempty(dim5_idx) & (min(dim5_idx) < 1 | max(dim5_idx) > hdr.dime.dim(6)) + max_range = hdr.dime.dim(6); + + if max_range == 1 + error(['"dim5_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"dim5_idx" should be an integer within the range of [' range '].']); + end + end + + % check dim6_idx + % + if ~isempty(dim6_idx) & ~isnumeric(dim6_idx) + error('"dim6_idx" should be a numerical array.'); + end + + if length(unique(dim6_idx)) ~= length(dim6_idx) + error('Duplicate index in "dim6_idx"'); + end + + if ~isempty(dim6_idx) & (min(dim6_idx) < 1 | max(dim6_idx) > hdr.dime.dim(7)) + max_range = hdr.dime.dim(7); + + if max_range == 1 + error(['"dim6_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"dim6_idx" should be an integer within the range of [' range '].']); + end + end + + % check dim7_idx + % + if ~isempty(dim7_idx) & ~isnumeric(dim7_idx) + error('"dim7_idx" should be a numerical array.'); + end + + if length(unique(dim7_idx)) ~= length(dim7_idx) + error('Duplicate index in "dim7_idx"'); + end + + if ~isempty(dim7_idx) & (min(dim7_idx) < 1 | max(dim7_idx) > hdr.dime.dim(8)) + max_range = hdr.dime.dim(8); + + if max_range == 1 + error(['"dim7_idx" should be 1.']); + else + range = ['1 ' num2str(max_range)]; + error(['"dim7_idx" should be an integer within the range of [' range '].']); + end + end + + [img,hdr] = read_image(hdr,filetype,fileprefix,machine,img_idx,dim5_idx,dim6_idx,dim7_idx,old_RGB); + + return % load_nii_img + + +%--------------------------------------------------------------------- +function [img,hdr] = read_image(hdr,filetype,fileprefix,machine,img_idx,dim5_idx,dim6_idx,dim7_idx,old_RGB) + + switch filetype + case {0, 1} + fn = [fileprefix '.img']; + case 2 + fn = [fileprefix '.nii']; + end + + fid = fopen(fn,'r',machine); + + if fid < 0, + msg = sprintf('Cannot open file %s.',fn); + error(msg); + end + + % Set bitpix according to datatype + % + % /*Acceptable values for datatype are*/ + % + % 0 None (Unknown bit per voxel) % DT_NONE, DT_UNKNOWN + % 1 Binary (ubit1, bitpix=1) % DT_BINARY + % 2 Unsigned char (uchar or uint8, bitpix=8) % DT_UINT8, NIFTI_TYPE_UINT8 + % 4 Signed short (int16, bitpix=16) % DT_INT16, NIFTI_TYPE_INT16 + % 8 Signed integer (int32, bitpix=32) % DT_INT32, NIFTI_TYPE_INT32 + % 16 Floating point (single or float32, bitpix=32) % DT_FLOAT32, NIFTI_TYPE_FLOAT32 + % 32 Complex, 2 float32 (Use float32, bitpix=64) % DT_COMPLEX64, NIFTI_TYPE_COMPLEX64 + % 64 Double precision (double or float64, bitpix=64) % DT_FLOAT64, NIFTI_TYPE_FLOAT64 + % 128 uint8 RGB (Use uint8, bitpix=24) % DT_RGB24, NIFTI_TYPE_RGB24 + % 256 Signed char (schar or int8, bitpix=8) % DT_INT8, NIFTI_TYPE_INT8 + % 511 Single RGB (Use float32, bitpix=96) % DT_RGB96, NIFTI_TYPE_RGB96 + % 512 Unsigned short (uint16, bitpix=16) % DT_UNINT16, NIFTI_TYPE_UNINT16 + % 768 Unsigned integer (uint32, bitpix=32) % DT_UNINT32, NIFTI_TYPE_UNINT32 + % 1024 Signed long long (int64, bitpix=64) % DT_INT64, NIFTI_TYPE_INT64 + % 1280 Unsigned long long (uint64, bitpix=64) % DT_UINT64, NIFTI_TYPE_UINT64 + % 1536 Long double, float128 (Unsupported, bitpix=128) % DT_FLOAT128, NIFTI_TYPE_FLOAT128 + % 1792 Complex128, 2 float64 (Use float64, bitpix=128) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 + % 2048 Complex256, 2 float128 (Unsupported, bitpix=256) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 + % + switch hdr.dime.datatype + case 1, + hdr.dime.bitpix = 1; precision = 'ubit1'; + case 2, + hdr.dime.bitpix = 8; precision = 'uint8'; + case 4, + hdr.dime.bitpix = 16; precision = 'int16'; + case 8, + hdr.dime.bitpix = 32; precision = 'int32'; + case 16, + hdr.dime.bitpix = 32; precision = 'float32'; + case 32, + hdr.dime.bitpix = 64; precision = 'float32'; + case 64, + hdr.dime.bitpix = 64; precision = 'float64'; + case 128, + hdr.dime.bitpix = 24; precision = 'uint8'; + case 256 + hdr.dime.bitpix = 8; precision = 'int8'; + case 511 + hdr.dime.bitpix = 96; precision = 'float32'; + case 512 + hdr.dime.bitpix = 16; precision = 'uint16'; + case 768 + hdr.dime.bitpix = 32; precision = 'uint32'; + case 1024 + hdr.dime.bitpix = 64; precision = 'int64'; + case 1280 + hdr.dime.bitpix = 64; precision = 'uint64'; + case 1792, + hdr.dime.bitpix = 128; precision = 'float64'; + otherwise + error('This datatype is not supported'); + end + + hdr.dime.dim(find(hdr.dime.dim < 1)) = 1; + + % move pointer to the start of image block + % + switch filetype + case {0, 1} + fseek(fid, 0, 'bof'); + case 2 + fseek(fid, hdr.dime.vox_offset, 'bof'); + end + + % Load whole image block for old Analyze format or binary image; + % otherwise, load images that are specified in img_idx, dim5_idx, + % dim6_idx, and dim7_idx + % + % For binary image, we have to read all because pos can not be + % seeked in bit and can not be calculated the way below. + % + if hdr.dime.datatype == 1 | isequal(hdr.dime.dim(5:8),ones(1,4)) | ... + (isempty(img_idx) & isempty(dim5_idx) & isempty(dim6_idx) & isempty(dim7_idx)) + + % For each frame, precision of value will be read + % in img_siz times, where img_siz is only the + % dimension size of an image, not the byte storage + % size of an image. + % + img_siz = prod(hdr.dime.dim(2:8)); + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + img_siz = img_siz * 2; + end + + %MPH: For RGB24, voxel values include 3 separate color planes + % + if hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + img_siz = img_siz * 3; + end + + img = fread(fid, img_siz, sprintf('*%s',precision)); + + d1 = hdr.dime.dim(2); + d2 = hdr.dime.dim(3); + d3 = hdr.dime.dim(4); + d4 = hdr.dime.dim(5); + d5 = hdr.dime.dim(6); + d6 = hdr.dime.dim(7); + d7 = hdr.dime.dim(8); + + if isempty(img_idx) + img_idx = 1:d4; + end + + if isempty(dim5_idx) + dim5_idx = 1:d5; + end + + if isempty(dim6_idx) + dim6_idx = 1:d6; + end + + if isempty(dim7_idx) + dim7_idx = 1:d7; + end + else + + img = []; + + d1 = hdr.dime.dim(2); + d2 = hdr.dime.dim(3); + d3 = hdr.dime.dim(4); + d4 = hdr.dime.dim(5); + d5 = hdr.dime.dim(6); + d6 = hdr.dime.dim(7); + d7 = hdr.dime.dim(8); + + if isempty(img_idx) + img_idx = 1:d4; + end + + if isempty(dim5_idx) + dim5_idx = 1:d5; + end + + if isempty(dim6_idx) + dim6_idx = 1:d6; + end + + if isempty(dim7_idx) + dim7_idx = 1:d7; + end + + for i7=1:length(dim7_idx) + for i6=1:length(dim6_idx) + for i5=1:length(dim5_idx) + for t=1:length(img_idx) + + % Position is seeked in bytes. To convert dimension size + % to byte storage size, hdr.dime.bitpix/8 will be + % applied. + % + pos = sub2ind([d1 d2 d3 d4 d5 d6 d7], 1, 1, 1, ... + img_idx(t), dim5_idx(i5),dim6_idx(i6),dim7_idx(i7)) -1; + pos = pos * hdr.dime.bitpix/8; + + img_siz = prod(hdr.dime.dim(2:4)); + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + img_siz = img_siz * 2; + end + + %MPH: For RGB24, voxel values include 3 separate color planes + % + if hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + img_siz = img_siz * 3; + end + + if filetype == 2 + fseek(fid, pos + hdr.dime.vox_offset, 'bof'); + else + fseek(fid, pos, 'bof'); + end + + % For each frame, fread will read precision of value + % in img_siz times + % + img = [img fread(fid, img_siz, sprintf('*%s',precision))]; + end + end + end + end + end + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + img = reshape(img, [2, length(img)/2]); + img = complex(img(1,:)', img(2,:)'); + end + + fclose(fid); + + % Update the global min and max values + % + hdr.dime.glmax = double(max(img(:))); + hdr.dime.glmin = double(min(img(:))); + + % old_RGB treat RGB slice by slice, now it is treated voxel by voxel + % + if old_RGB & hdr.dime.datatype == 128 & hdr.dime.bitpix == 24 + % remove squeeze + img = (reshape(img, [hdr.dime.dim(2:3) 3 hdr.dime.dim(4) length(img_idx) length(dim5_idx) length(dim6_idx) length(dim7_idx)])); + img = permute(img, [1 2 4 3 5 6 7 8]); + elseif hdr.dime.datatype == 128 & hdr.dime.bitpix == 24 + % remove squeeze + img = (reshape(img, [3 hdr.dime.dim(2:4) length(img_idx) length(dim5_idx) length(dim6_idx) length(dim7_idx)])); + img = permute(img, [2 3 4 1 5 6 7 8]); + elseif hdr.dime.datatype == 511 & hdr.dime.bitpix == 96 + img = double(img(:)); + img = (img - min(img))/(max(img) - min(img)); + % remove squeeze + img = (reshape(img, [3 hdr.dime.dim(2:4) length(img_idx) length(dim5_idx) length(dim6_idx) length(dim7_idx)])); + img = permute(img, [2 3 4 1 5 6 7 8]); + else + % remove squeeze + img = (reshape(img, [hdr.dime.dim(2:4) length(img_idx) length(dim5_idx) length(dim6_idx) length(dim7_idx)])); + end + + if ~isempty(img_idx) + hdr.dime.dim(5) = length(img_idx); + end + + if ~isempty(dim5_idx) + hdr.dime.dim(6) = length(dim5_idx); + end + + if ~isempty(dim6_idx) + hdr.dime.dim(7) = length(dim6_idx); + end + + if ~isempty(dim7_idx) + hdr.dime.dim(8) = length(dim7_idx); + end + + return % read_image + diff --git a/DefaultData/make_nii.m b/DefaultData/make_nii.m new file mode 100755 index 0000000..b89fe66 --- /dev/null +++ b/DefaultData/make_nii.m @@ -0,0 +1,243 @@ +% Make NIfTI structure specified by an N-D matrix. Usually, N is 3 for +% 3D matrix [x y z], or 4 for 4D matrix with time series [x y z t]. +% However, NIfTI allows a maximum of 7D matrix. For RGB24 datatype, an +% extra dimension for RGB should be inserted immediately after [x y z]. +% Optional parameters can also be included, such as: voxel_size, +% origin, datatype, and description. +% +% Once the NIfTI structure is made, it can be saved into NIfTI file +% using "save_nii" command (for more detail, type: help save_nii). +% +% Usage: nii = make_nii(img, [voxel_size], [origin], [datatype], ... +% [description]) +% +% Where: +% +% img: Usually, img is a 3D matrix [x y z], or a 4D +% matrix with time series [x y z t]. However, +% NIfTI allows a maximum of 7D matrix. For RGB +% datatype, an extra dimension for RGB should +% be inserted immediately after [x y z]. +% +% voxel_size (optional): Voxel size in millimeter for each +% dimension. Default is [1 1 1]. +% +% origin (optional): The AC origin. Default is [0 0 0]. +% +% datatype (optional): Storage data type: +% 2 - uint8, 4 - int16, 8 - int32, 16 - float32, +% 32 - complex64, 64 - float64, 128 - RGB24, +% 256 - int8, 512 - uint16, 768 - uint32, +% 1792 - complex128 +% Default will use the data type of 'img' matrix +% +% description (optional): Description of data. Default is ''. +% +% e.g.: +% origin = [33 44 13]; datatype = 64; +% nii = make_nii(img, [], origin, datatype); % default voxel_size +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function nii = make_nii(varargin) + + nii.img = varargin{1}; + dims = size(nii.img); + dims = [length(dims) dims ones(1,8)]; + dims = dims(1:8); + + voxel_size = [0 ones(1,7)]; + origin = zeros(1,5); + descrip = ''; + + switch class(nii.img) + case 'uint8' + datatype = 2; + case 'int16' + datatype = 4; + case 'int32' + datatype = 8; + case 'single' + datatype = 16; + case 'double' + datatype = 64; + case 'int8' + datatype = 256; + case 'uint16' + datatype = 512; + case 'uint32' + datatype = 768; + otherwise + error('Datatype is not supported by make_nii.'); + end + + if nargin > 1 & ~isempty(varargin{2}) + voxel_size(2:4) = double(varargin{2}); + end + + if nargin > 2 & ~isempty(varargin{3}) + origin(1:3) = double(varargin{3}); + end + + if nargin > 3 & ~isempty(varargin{4}) + datatype = double(varargin{4}); + end + + if nargin > 4 & ~isempty(varargin{5}) + descrip = varargin{5}; + end + + if datatype == 128 + if ndims(nii.img) > 8 + error('NIfTI only allows a maximum of 7 Dimension matrix.'); + end + + dims(1) = dims(1)-1; + dims(5:8) = [dims(6:8) 1]; + + else + if ndims(nii.img) > 7 + error('NIfTI only allows a maximum of 7 Dimension matrix.'); + end + end + + maxval = round(double(max(nii.img(:)))); + minval = round(double(min(nii.img(:)))); + + nii.hdr = make_header(dims, voxel_size, origin, datatype, ... + descrip, maxval, minval); + + switch nii.hdr.dime.datatype + case 2 + nii.img = uint8(nii.img); + case 4 + nii.img = int16(nii.img); + case 8 + nii.img = int32(nii.img); + case 16 + nii.img = single(nii.img); + case 32 + nii.img = single(nii.img); + case 64 + nii.img = double(nii.img); + case 128 + nii.img = uint8(nii.img); + case 256 + nii.img = int8(nii.img); + case 512 + nii.img = uint16(nii.img); + case 768 + nii.img = uint32(nii.img); + case 1792 + nii.img = double(nii.img); + otherwise + error('Datatype is not supported by make_nii.'); + end + + return; % make_nii + + +%--------------------------------------------------------------------- +function hdr = make_header(dims, voxel_size, origin, datatype, ... + descrip, maxval, minval) + + hdr.hk = header_key; + hdr.dime = image_dimension(dims, voxel_size, datatype, maxval, minval); + hdr.hist = data_history(origin, descrip); + + return; % make_header + + +%--------------------------------------------------------------------- +function hk = header_key + + hk.sizeof_hdr = 348; % must be 348! + hk.data_type = ''; + hk.db_name = ''; + hk.extents = 0; + hk.session_error = 0; + hk.regular = 'r'; + hk.dim_info = 0; + + return; % header_key + + +%--------------------------------------------------------------------- +function dime = image_dimension(dims, voxel_size, datatype, maxval, minval) + + dime.dim = dims; + dime.intent_p1 = 0; + dime.intent_p2 = 0; + dime.intent_p3 = 0; + dime.intent_code = 0; + dime.datatype = datatype; + + switch dime.datatype + case 2, + dime.bitpix = 8; precision = 'uint8'; + case 4, + dime.bitpix = 16; precision = 'int16'; + case 8, + dime.bitpix = 32; precision = 'int32'; + case 16, + dime.bitpix = 32; precision = 'float32'; + case 32, + dime.bitpix = 64; precision = 'float32'; + case 64, + dime.bitpix = 64; precision = 'float64'; + case 128, + dime.bitpix = 24; precision = 'uint8'; + case 256 + dime.bitpix = 8; precision = 'int8'; + case 512 + dime.bitpix = 16; precision = 'uint16'; + case 768 + dime.bitpix = 32; precision = 'uint32'; + case 1792, + dime.bitpix = 128; precision = 'float64'; + otherwise + error('Datatype is not supported by make_nii.'); + end + + dime.slice_start = 0; + dime.pixdim = voxel_size; + dime.vox_offset = 0; + dime.scl_slope = 0; + dime.scl_inter = 0; + dime.slice_end = 0; + dime.slice_code = 0; + dime.xyzt_units = 0; + dime.cal_max = 0; + dime.cal_min = 0; + dime.slice_duration = 0; + dime.toffset = 0; + dime.glmax = maxval; + dime.glmin = minval; + + return; % image_dimension + + +%--------------------------------------------------------------------- +function hist = data_history(origin, descrip) + + hist.descrip = descrip; + hist.aux_file = 'none'; + hist.qform_code = 0; + hist.sform_code = 0; + hist.quatern_b = 0; + hist.quatern_c = 0; + hist.quatern_d = 0; + hist.qoffset_x = 0; + hist.qoffset_y = 0; + hist.qoffset_z = 0; + hist.srow_x = zeros(1,4); + hist.srow_y = zeros(1,4); + hist.srow_z = zeros(1,4); + hist.intent_name = ''; + hist.magic = ''; + hist.originator = origin; + + return; % data_history + diff --git a/DefaultData/save_nii.m b/DefaultData/save_nii.m new file mode 100755 index 0000000..c96ee57 --- /dev/null +++ b/DefaultData/save_nii.m @@ -0,0 +1,233 @@ +% Save NIFTI dataset. Support both *.nii and *.hdr/*.img file extension. +% If file extension is not provided, *.hdr/*.img will be used as default. +% +% Usage: save_nii(nii, filename, [old_RGB]) +% +% nii.hdr - struct with NIFTI header fields (from load_nii.m or make_nii.m) +% +% nii.img - 3D (or 4D) matrix of NIFTI data. +% +% filename - NIFTI file name. +% +% old_RGB - an optional boolean variable to handle special RGB data +% sequence [R1 R2 ... G1 G2 ... B1 B2 ...] that is used only by +% AnalyzeDirect (Analyze Software). Since both NIfTI and Analyze +% file format use RGB triple [R1 G1 B1 R2 G2 B2 ...] sequentially +% for each voxel, this variable is set to FALSE by default. If you +% would like the saved image only to be opened by AnalyzeDirect +% Software, set old_RGB to TRUE (or 1). It will be set to 0, if it +% is default or empty. +% +% Tip: to change the data type, set nii.hdr.dime.datatype, +% and nii.hdr.dime.bitpix to: +% +% 0 None (Unknown bit per voxel) % DT_NONE, DT_UNKNOWN +% 1 Binary (ubit1, bitpix=1) % DT_BINARY +% 2 Unsigned char (uchar or uint8, bitpix=8) % DT_UINT8, NIFTI_TYPE_UINT8 +% 4 Signed short (int16, bitpix=16) % DT_INT16, NIFTI_TYPE_INT16 +% 8 Signed integer (int32, bitpix=32) % DT_INT32, NIFTI_TYPE_INT32 +% 16 Floating point (single or float32, bitpix=32) % DT_FLOAT32, NIFTI_TYPE_FLOAT32 +% 32 Complex, 2 float32 (Use float32, bitpix=64) % DT_COMPLEX64, NIFTI_TYPE_COMPLEX64 +% 64 Double precision (double or float64, bitpix=64) % DT_FLOAT64, NIFTI_TYPE_FLOAT64 +% 128 Red-Green-Blue (Use uint8, bitpix=24) % DT_RGB24, NIFTI_TYPE_RGB24 +% 256 Signed char (schar or int8, bitpix=8) % DT_INT8, NIFTI_TYPE_INT8 +% 512 Unsigned short (uint16, bitpix=16) % DT_UNINT16, NIFTI_TYPE_UNINT16 +% 768 Unsigned integer (uint32, bitpix=32) % DT_UNINT32, NIFTI_TYPE_UNINT32 +% 1024 Signed long long (int64, bitpix=64) % DT_INT64, NIFTI_TYPE_INT64 +% 1280 Unsigned long long (uint64, bitpix=64) % DT_UINT64, NIFTI_TYPE_UINT64 +% 1536 Long double, float128 (Unsupported, bitpix=128) % DT_FLOAT128, NIFTI_TYPE_FLOAT128 +% 1792 Complex128, 2 float64 (Use float64, bitpix=128) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 +% 2048 Complex256, 2 float128 (Unsupported, bitpix=256) % DT_COMPLEX128, NIFTI_TYPE_COMPLEX128 +% +% Part of this file is copied and modified from: +% http://www.mathworks.com/matlabcentral/fileexchange/1878-mri-analyze-tools +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% - "old_RGB" related codes in "save_nii.m" are added by Mike Harms (2006.06.28) +% +function save_nii(nii, fileprefix, old_RGB) + + if ~exist('nii','var') | isempty(nii) | ~isfield(nii,'hdr') | ... + ~isfield(nii,'img') | ~exist('fileprefix','var') | isempty(fileprefix) + + error('Usage: save_nii(nii, filename, [old_RGB])'); + end + + if isfield(nii,'untouch') & nii.untouch == 1 + error('Usage: please use ''save_untouch_nii.m'' for the untouched structure.'); + end + + if ~exist('old_RGB','var') | isempty(old_RGB) + old_RGB = 0; + end + + filetype = 1; + + % Note: fileprefix is actually the filename you want to save + % + if findstr('.nii',fileprefix) + filetype = 2; + fileprefix = strrep(fileprefix,'.nii',''); + end + + if findstr('.hdr',fileprefix) + fileprefix = strrep(fileprefix,'.hdr',''); + end + + if findstr('.img',fileprefix) + fileprefix = strrep(fileprefix,'.img',''); + end + + write_nii(nii, filetype, fileprefix, old_RGB); + + if filetype == 1 + + % So earlier versions of SPM can also open it with correct originator + % + M=[[diag(nii.hdr.dime.pixdim(2:4)) -[nii.hdr.hist.originator(1:3).*nii.hdr.dime.pixdim(2:4)]'];[0 0 0 1]]; + save([fileprefix '.mat'], 'M'); + end + + return % save_nii + + +%----------------------------------------------------------------------------------- +function write_nii(nii, filetype, fileprefix, old_RGB) + + hdr = nii.hdr; + + if isfield(nii,'ext') & ~isempty(nii.ext) + ext = nii.ext; + [ext, esize_total] = verify_nii_ext(ext); + else + ext = []; + end + + switch double(hdr.dime.datatype), + case 1, + hdr.dime.bitpix = int16(1 ); precision = 'ubit1'; + case 2, + hdr.dime.bitpix = int16(8 ); precision = 'uint8'; + case 4, + hdr.dime.bitpix = int16(16); precision = 'int16'; + case 8, + hdr.dime.bitpix = int16(32); precision = 'int32'; + case 16, + hdr.dime.bitpix = int16(32); precision = 'float32'; + case 32, + hdr.dime.bitpix = int16(64); precision = 'float32'; + case 64, + hdr.dime.bitpix = int16(64); precision = 'float64'; + case 128, + hdr.dime.bitpix = int16(24); precision = 'uint8'; + case 256 + hdr.dime.bitpix = int16(8 ); precision = 'int8'; + case 512 + hdr.dime.bitpix = int16(16); precision = 'uint16'; + case 768 + hdr.dime.bitpix = int16(32); precision = 'uint32'; + case 1024 + hdr.dime.bitpix = int16(64); precision = 'int64'; + case 1280 + hdr.dime.bitpix = int16(64); precision = 'uint64'; + case 1792, + hdr.dime.bitpix = int16(128); precision = 'float64'; + otherwise + error('This datatype is not supported'); + end + + hdr.dime.glmax = round(double(max(nii.img(:)))); + hdr.dime.glmin = round(double(min(nii.img(:)))); + + if filetype == 2 + fid = fopen(sprintf('%s.nii',fileprefix),'w'); + + if fid < 0, + msg = sprintf('Cannot open file %s.nii.',fileprefix); + error(msg); + end + + hdr.dime.vox_offset = 352; + + if ~isempty(ext) + hdr.dime.vox_offset = hdr.dime.vox_offset + esize_total; + end + + hdr.hist.magic = 'n+1'; + save_nii_hdr(hdr, fid); + + if ~isempty(ext) + save_nii_ext(ext, fid); + end + else + fid = fopen(sprintf('%s.hdr',fileprefix),'w'); + + if fid < 0, + msg = sprintf('Cannot open file %s.hdr.',fileprefix); + error(msg); + end + + hdr.dime.vox_offset = 0; + hdr.hist.magic = 'ni1'; + save_nii_hdr(hdr, fid); + + if ~isempty(ext) + save_nii_ext(ext, fid); + end + + fclose(fid); + fid = fopen(sprintf('%s.img',fileprefix),'w'); + end + + ScanDim = double(hdr.dime.dim(5)); % t + SliceDim = double(hdr.dime.dim(4)); % z + RowDim = double(hdr.dime.dim(3)); % y + PixelDim = double(hdr.dime.dim(2)); % x + SliceSz = double(hdr.dime.pixdim(4)); + RowSz = double(hdr.dime.pixdim(3)); + PixelSz = double(hdr.dime.pixdim(2)); + + x = 1:PixelDim; + + if filetype == 2 & isempty(ext) + skip_bytes = double(hdr.dime.vox_offset) - 348; + else + skip_bytes = 0; + end + + if double(hdr.dime.datatype) == 128 + + % RGB planes are expected to be in the 4th dimension of nii.img + % + if(size(nii.img,4)~=3) + error(['The NII structure does not appear to have 3 RGB color planes in the 4th dimension']); + end + + if old_RGB + nii.img = permute(nii.img, [1 2 4 3 5 6 7 8]); + else + nii.img = permute(nii.img, [4 1 2 3 5 6 7 8]); + end + end + + % For complex float32 or complex float64, voxel values + % include [real, imag] + % + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 + real_img = real(nii.img(:))'; + nii.img = imag(nii.img(:))'; + nii.img = [real_img; nii.img]; + end + + if skip_bytes + fwrite(fid, ones(1,skip_bytes), 'uint8'); + end + + fwrite(fid, nii.img, precision); +% fwrite(fid, nii.img, precision, skip_bytes); % error using skip + fclose(fid); + + return; % write_nii + diff --git a/DefaultData/save_nii_ext.m b/DefaultData/save_nii_ext.m new file mode 100755 index 0000000..4788649 --- /dev/null +++ b/DefaultData/save_nii_ext.m @@ -0,0 +1,38 @@ +% Save NIFTI header extension. +% +% Usage: save_nii_ext(ext, fid) +% +% ext - struct with NIFTI header extension fields. +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function save_nii_ext(ext, fid) + + if ~exist('ext','var') | ~exist('fid','var') + error('Usage: save_nii_ext(ext, fid)'); + end + + if ~isfield(ext,'extension') | ~isfield(ext,'section') | ~isfield(ext,'num_ext') + error('Wrong header extension'); + end + + write_ext(ext, fid); + + return; % save_nii_ext + + +%--------------------------------------------------------------------- +function write_ext(ext, fid) + + fwrite(fid, ext.extension, 'uchar'); + + for i=1:ext.num_ext + fwrite(fid, ext.section(i).esize, 'int32'); + fwrite(fid, ext.section(i).ecode, 'int32'); + fwrite(fid, ext.section(i).edata, 'uchar'); + end + + return; % write_ext + diff --git a/DefaultData/save_nii_hdr.m b/DefaultData/save_nii_hdr.m new file mode 100755 index 0000000..6cc34bb --- /dev/null +++ b/DefaultData/save_nii_hdr.m @@ -0,0 +1,239 @@ +% Save NIFTI dataset header. Support both *.nii and *.hdr/*.img file +% extension. +% +% Usage: save_nii_hdr(hdr, fid) +% +% hdr - struct with NIFTI header fields. +% +% fileprefix - NIFTI file name without extension. +% +% Part of this file is copied and modified from: +% http://www.mathworks.com/matlabcentral/fileexchange/1878-mri-analyze-tools +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function save_nii_hdr(hdr, fid) + + if ~exist('hdr','var') | ~exist('fid','var') + error('Usage: save_nii_hdr(hdr, fid)'); + end + + if ~isequal(hdr.hk.sizeof_hdr,348), + error('hdr.hk.sizeof_hdr must be 348.'); + end + + if hdr.hist.qform_code == 0 & hdr.hist.sform_code == 0 + hdr.hist.sform_code = 1; + hdr.hist.srow_x(1) = hdr.dime.pixdim(2); + hdr.hist.srow_x(2) = 0; + hdr.hist.srow_x(3) = 0; + hdr.hist.srow_y(1) = 0; + hdr.hist.srow_y(2) = hdr.dime.pixdim(3); + hdr.hist.srow_y(3) = 0; + hdr.hist.srow_z(1) = 0; + hdr.hist.srow_z(2) = 0; + hdr.hist.srow_z(3) = hdr.dime.pixdim(4); + hdr.hist.srow_x(4) = (1-hdr.hist.originator(1))*hdr.dime.pixdim(2); + hdr.hist.srow_y(4) = (1-hdr.hist.originator(2))*hdr.dime.pixdim(3); + hdr.hist.srow_z(4) = (1-hdr.hist.originator(3))*hdr.dime.pixdim(4); + end + + write_header(hdr, fid); + + return; % save_nii_hdr + + +%--------------------------------------------------------------------- +function write_header(hdr, fid) + + % Original header structures + % struct dsr /* dsr = hdr */ + % { + % struct header_key hk; /* 0 + 40 */ + % struct image_dimension dime; /* 40 + 108 */ + % struct data_history hist; /* 148 + 200 */ + % }; /* total= 348 bytes*/ + + header_key(fid, hdr.hk); + image_dimension(fid, hdr.dime); + data_history(fid, hdr.hist); + + % check the file size is 348 bytes + % + fbytes = ftell(fid); + + if ~isequal(fbytes,348), + msg = sprintf('Header size is not 348 bytes.'); + warning(msg); + end + + return; % write_header + + +%--------------------------------------------------------------------- +function header_key(fid, hk) + + fseek(fid,0,'bof'); + + % Original header structures + % struct header_key /* header key */ + % { /* off + size */ + % int sizeof_hdr /* 0 + 4 */ + % char data_type[10]; /* 4 + 10 */ + % char db_name[18]; /* 14 + 18 */ + % int extents; /* 32 + 4 */ + % short int session_error; /* 36 + 2 */ + % char regular; /* 38 + 1 */ + % char dim_info; % char hkey_un0; /* 39 + 1 */ + % }; /* total=40 bytes */ + + fwrite(fid, hk.sizeof_hdr(1), 'int32'); % must be 348. + + % data_type = sprintf('%-10s',hk.data_type); % ensure it is 10 chars from left + % fwrite(fid, data_type(1:10), 'uchar'); + pad = zeros(1, 10-length(hk.data_type)); + hk.data_type = [hk.data_type char(pad)]; + fwrite(fid, hk.data_type(1:10), 'uchar'); + + % db_name = sprintf('%-18s', hk.db_name); % ensure it is 18 chars from left + % fwrite(fid, db_name(1:18), 'uchar'); + pad = zeros(1, 18-length(hk.db_name)); + hk.db_name = [hk.db_name char(pad)]; + fwrite(fid, hk.db_name(1:18), 'uchar'); + + fwrite(fid, hk.extents(1), 'int32'); + fwrite(fid, hk.session_error(1), 'int16'); + fwrite(fid, hk.regular(1), 'uchar'); % might be uint8 + + % fwrite(fid, hk.hkey_un0(1), 'uchar'); + % fwrite(fid, hk.hkey_un0(1), 'uint8'); + fwrite(fid, hk.dim_info(1), 'uchar'); + + return; % header_key + + +%--------------------------------------------------------------------- +function image_dimension(fid, dime) + + % Original header structures + % struct image_dimension + % { /* off + size */ + % short int dim[8]; /* 0 + 16 */ + % float intent_p1; % char vox_units[4]; /* 16 + 4 */ + % float intent_p2; % char cal_units[8]; /* 20 + 4 */ + % float intent_p3; % char cal_units[8]; /* 24 + 4 */ + % short int intent_code; % short int unused1; /* 28 + 2 */ + % short int datatype; /* 30 + 2 */ + % short int bitpix; /* 32 + 2 */ + % short int slice_start; % short int dim_un0; /* 34 + 2 */ + % float pixdim[8]; /* 36 + 32 */ + % /* + % pixdim[] specifies the voxel dimensions: + % pixdim[1] - voxel width + % pixdim[2] - voxel height + % pixdim[3] - interslice distance + % pixdim[4] - volume timing, in msec + % ..etc + % */ + % float vox_offset; /* 68 + 4 */ + % float scl_slope; % float roi_scale; /* 72 + 4 */ + % float scl_inter; % float funused1; /* 76 + 4 */ + % short slice_end; % float funused2; /* 80 + 2 */ + % char slice_code; % float funused2; /* 82 + 1 */ + % char xyzt_units; % float funused2; /* 83 + 1 */ + % float cal_max; /* 84 + 4 */ + % float cal_min; /* 88 + 4 */ + % float slice_duration; % int compressed; /* 92 + 4 */ + % float toffset; % int verified; /* 96 + 4 */ + % int glmax; /* 100 + 4 */ + % int glmin; /* 104 + 4 */ + % }; /* total=108 bytes */ + + fwrite(fid, dime.dim(1:8), 'int16'); + fwrite(fid, dime.intent_p1(1), 'float32'); + fwrite(fid, dime.intent_p2(1), 'float32'); + fwrite(fid, dime.intent_p3(1), 'float32'); + fwrite(fid, dime.intent_code(1), 'int16'); + fwrite(fid, dime.datatype(1), 'int16'); + fwrite(fid, dime.bitpix(1), 'int16'); + fwrite(fid, dime.slice_start(1), 'int16'); + fwrite(fid, dime.pixdim(1:8), 'float32'); + fwrite(fid, dime.vox_offset(1), 'float32'); + fwrite(fid, dime.scl_slope(1), 'float32'); + fwrite(fid, dime.scl_inter(1), 'float32'); + fwrite(fid, dime.slice_end(1), 'int16'); + fwrite(fid, dime.slice_code(1), 'uchar'); + fwrite(fid, dime.xyzt_units(1), 'uchar'); + fwrite(fid, dime.cal_max(1), 'float32'); + fwrite(fid, dime.cal_min(1), 'float32'); + fwrite(fid, dime.slice_duration(1), 'float32'); + fwrite(fid, dime.toffset(1), 'float32'); + fwrite(fid, dime.glmax(1), 'int32'); + fwrite(fid, dime.glmin(1), 'int32'); + + return; % image_dimension + + +%--------------------------------------------------------------------- +function data_history(fid, hist) + + % Original header structures + %struct data_history + % { /* off + size */ + % char descrip[80]; /* 0 + 80 */ + % char aux_file[24]; /* 80 + 24 */ + % short int qform_code; /* 104 + 2 */ + % short int sform_code; /* 106 + 2 */ + % float quatern_b; /* 108 + 4 */ + % float quatern_c; /* 112 + 4 */ + % float quatern_d; /* 116 + 4 */ + % float qoffset_x; /* 120 + 4 */ + % float qoffset_y; /* 124 + 4 */ + % float qoffset_z; /* 128 + 4 */ + % float srow_x[4]; /* 132 + 16 */ + % float srow_y[4]; /* 148 + 16 */ + % float srow_z[4]; /* 164 + 16 */ + % char intent_name[16]; /* 180 + 16 */ + % char magic[4]; % int smin; /* 196 + 4 */ + % }; /* total=200 bytes */ + + % descrip = sprintf('%-80s', hist.descrip); % 80 chars from left + % fwrite(fid, descrip(1:80), 'uchar'); + pad = zeros(1, 80-length(hist.descrip)); + hist.descrip = [hist.descrip char(pad)]; + fwrite(fid, hist.descrip(1:80), 'uchar'); + + % aux_file = sprintf('%-24s', hist.aux_file); % 24 chars from left + % fwrite(fid, aux_file(1:24), 'uchar'); + pad = zeros(1, 24-length(hist.aux_file)); + hist.aux_file = [hist.aux_file char(pad)]; + fwrite(fid, hist.aux_file(1:24), 'uchar'); + + fwrite(fid, hist.qform_code, 'int16'); + fwrite(fid, hist.sform_code, 'int16'); + fwrite(fid, hist.quatern_b, 'float32'); + fwrite(fid, hist.quatern_c, 'float32'); + fwrite(fid, hist.quatern_d, 'float32'); + fwrite(fid, hist.qoffset_x, 'float32'); + fwrite(fid, hist.qoffset_y, 'float32'); + fwrite(fid, hist.qoffset_z, 'float32'); + fwrite(fid, hist.srow_x(1:4), 'float32'); + fwrite(fid, hist.srow_y(1:4), 'float32'); + fwrite(fid, hist.srow_z(1:4), 'float32'); + + % intent_name = sprintf('%-16s', hist.intent_name); % 16 chars from left + % fwrite(fid, intent_name(1:16), 'uchar'); + pad = zeros(1, 16-length(hist.intent_name)); + hist.intent_name = [hist.intent_name char(pad)]; + fwrite(fid, hist.intent_name(1:16), 'uchar'); + + % magic = sprintf('%-4s', hist.magic); % 4 chars from left + % fwrite(fid, magic(1:4), 'uchar'); + pad = zeros(1, 4-length(hist.magic)); + hist.magic = [hist.magic char(pad)]; + fwrite(fid, hist.magic(1:4), 'uchar'); + + return; % data_history + diff --git a/DefaultData/xform_nii.m b/DefaultData/xform_nii.m new file mode 100755 index 0000000..21d82cd --- /dev/null +++ b/DefaultData/xform_nii.m @@ -0,0 +1,521 @@ +% internal function + +% 'xform_nii.m' is an internal function called by "load_nii.m", so +% you do not need run this program by yourself. It does simplified +% NIfTI sform/qform affine transform, and supports some of the +% affine transforms, including translation, reflection, and +% orthogonal rotation (N*90 degree). +% +% For other affine transforms, e.g. any degree rotation, shearing +% etc. you will have to use the included 'reslice_nii.m' program +% to reslice the image volume. 'reslice_nii.m' is not called by +% any other program, and you have to run 'reslice_nii.m' explicitly +% for those NIfTI files that you want to reslice them. +% +% Since 'xform_nii.m' does not involve any interpolation or any +% slice change, the original image volume is supposed to be +% untouched, although it is translated, reflected, or even +% orthogonally rotated, based on the affine matrix in the +% NIfTI header. +% +% However, the affine matrix in the header of a lot NIfTI files +% contain slightly non-orthogonal rotation. Therefore, optional +% input parameter 'tolerance' is used to allow some distortion +% in the loaded image for any non-orthogonal rotation or shearing +% of NIfTI affine matrix. If you set 'tolerance' to 0, it means +% that you do not allow any distortion. If you set 'tolerance' to +% 1, it means that you do not care any distortion. The image will +% fail to be loaded if it can not be tolerated. The tolerance will +% be set to 0.1 (10%), if it is default or empty. +% +% Because 'reslice_nii.m' has to perform 3D interpolation, it can +% be slow depending on image size and affine matrix in the header. +% +% After you perform the affine transform, the 'nii' structure +% generated from 'xform_nii.m' or new NIfTI file created from +% 'reslice_nii.m' will be in RAS orientation, i.e. X axis from +% Left to Right, Y axis from Posterior to Anterior, and Z axis +% from Inferior to Superior. +% +% NOTE: This function should be called immediately after load_nii. +% +% Usage: [ nii ] = xform_nii(nii, [tolerance], [preferredForm]) +% +% nii - NIFTI structure (returned from load_nii) +% +% tolerance (optional) - distortion allowed for non-orthogonal rotation +% or shearing in NIfTI affine matrix. It will be set to 0.1 (10%), +% if it is default or empty. +% +% preferredForm (optional) - selects which transformation from voxels +% to RAS coordinates; values are s,q,S,Q. Lower case s,q indicate +% "prefer sform or qform, but use others if preferred not present". +% Upper case indicate the program is forced to use the specificied +% tranform or fail loading. 'preferredForm' will be 's', if it is +% default or empty. - Jeff Gunter +% +% NIFTI data format can be found on: http://nifti.nimh.nih.gov +% +% - Jimmy Shen (jimmy@rotman-baycrest.on.ca) +% +function nii = xform_nii(nii, tolerance, preferredForm) + + % save a copy of the header as it was loaded. This is the + % header before any sform, qform manipulation is done. + % + nii.original.hdr = nii.hdr; + + if ~exist('tolerance','var') | isempty(tolerance) + tolerance = 0.1; + elseif(tolerance<=0) + tolerance = eps; + end + + if ~exist('preferredForm','var') | isempty(preferredForm) + preferredForm= 's'; % Jeff + end + + % if scl_slope field is nonzero, then each voxel value in the + % dataset should be scaled as: y = scl_slope * x + scl_inter + % I bring it here because hdr will be modified by change_hdr. + % + if nii.hdr.dime.scl_slope ~= 0 & ... + ismember(nii.hdr.dime.datatype, [2,4,8,16,64,256,512,768]) & ... + (nii.hdr.dime.scl_slope ~= 1 | nii.hdr.dime.scl_inter ~= 0) + + nii.img = ... + nii.hdr.dime.scl_slope * double(nii.img) + nii.hdr.dime.scl_inter; + + if nii.hdr.dime.datatype == 64 + + nii.hdr.dime.datatype = 64; + nii.hdr.dime.bitpix = 64; + else + nii.img = single(nii.img); + + nii.hdr.dime.datatype = 16; + nii.hdr.dime.bitpix = 32; + end + + nii.hdr.dime.glmax = max(double(nii.img(:))); + nii.hdr.dime.glmin = min(double(nii.img(:))); + + % set scale to non-use, because it is applied in xform_nii + % + nii.hdr.dime.scl_slope = 0; + + end + + % However, the scaling is to be ignored if datatype is DT_RGB24. + + % If datatype is a complex type, then the scaling is to be applied + % to both the real and imaginary parts. + % + if nii.hdr.dime.scl_slope ~= 0 & ... + ismember(nii.hdr.dime.datatype, [32,1792]) + + nii.img = ... + nii.hdr.dime.scl_slope * double(nii.img) + nii.hdr.dime.scl_inter; + + if nii.hdr.dime.datatype == 32 + nii.img = single(nii.img); + end + + nii.hdr.dime.glmax = max(double(nii.img(:))); + nii.hdr.dime.glmin = min(double(nii.img(:))); + + % set scale to non-use, because it is applied in xform_nii + % + nii.hdr.dime.scl_slope = 0; + + end + + % There is no need for this program to transform Analyze data + % + if nii.filetype == 0 & exist([nii.fileprefix '.mat'],'file') + load([nii.fileprefix '.mat']); % old SPM affine matrix + R=M(1:3,1:3); + T=M(1:3,4); + T=R*ones(3,1)+T; + M(1:3,4)=T; + nii.hdr.hist.qform_code=0; + nii.hdr.hist.sform_code=1; + nii.hdr.hist.srow_x=M(1,:); + nii.hdr.hist.srow_y=M(2,:); + nii.hdr.hist.srow_z=M(3,:); + elseif nii.filetype == 0 + nii.hdr.hist.rot_orient = []; + nii.hdr.hist.flip_orient = []; + return; % no sform/qform for Analyze format + end + + hdr = nii.hdr; + + [hdr,orient]=change_hdr(hdr,tolerance,preferredForm); + + % flip and/or rotate image data + % + if ~isequal(orient, [1 2 3]) + + old_dim = hdr.dime.dim([2:4]); + + % More than 1 time frame + % + if ndims(nii.img) > 3 + pattern = 1:prod(old_dim); + else + pattern = []; + end + + if ~isempty(pattern) + pattern = reshape(pattern, old_dim); + end + + % calculate for rotation after flip + % + rot_orient = mod(orient + 2, 3) + 1; + + % do flip: + % + flip_orient = orient - rot_orient; + + for i = 1:3 + if flip_orient(i) + if ~isempty(pattern) + pattern = flipdim(pattern, i); + else + nii.img = flipdim(nii.img, i); + end + end + end + + % get index of orient (rotate inversely) + % + [tmp rot_orient] = sort(rot_orient); + + new_dim = old_dim; + new_dim = new_dim(rot_orient); + hdr.dime.dim([2:4]) = new_dim; + + new_pixdim = hdr.dime.pixdim([2:4]); + new_pixdim = new_pixdim(rot_orient); + hdr.dime.pixdim([2:4]) = new_pixdim; + + % re-calculate originator + % + tmp = hdr.hist.originator([1:3]); + tmp = tmp(rot_orient); + flip_orient = flip_orient(rot_orient); + + for i = 1:3 + if flip_orient(i) & ~isequal(tmp(i), 0) + tmp(i) = new_dim(i) - tmp(i) + 1; + end + end + + hdr.hist.originator([1:3]) = tmp; + hdr.hist.rot_orient = rot_orient; + hdr.hist.flip_orient = flip_orient; + + % do rotation: + % + if ~isempty(pattern) + pattern = permute(pattern, rot_orient); + pattern = pattern(:); + + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 | ... + hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + + tmp = reshape(nii.img(:,:,:,1), [prod(new_dim) hdr.dime.dim(5:8)]); + tmp = tmp(pattern, :); + nii.img(:,:,:,1) = reshape(tmp, [new_dim hdr.dime.dim(5:8)]); + + tmp = reshape(nii.img(:,:,:,2), [prod(new_dim) hdr.dime.dim(5:8)]); + tmp = tmp(pattern, :); + nii.img(:,:,:,2) = reshape(tmp, [new_dim hdr.dime.dim(5:8)]); + + if hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + tmp = reshape(nii.img(:,:,:,3), [prod(new_dim) hdr.dime.dim(5:8)]); + tmp = tmp(pattern, :); + nii.img(:,:,:,3) = reshape(tmp, [new_dim hdr.dime.dim(5:8)]); + end + + else + nii.img = reshape(nii.img, [prod(new_dim) hdr.dime.dim(5:8)]); + nii.img = nii.img(pattern, :); + nii.img = reshape(nii.img, [new_dim hdr.dime.dim(5:8)]); + end + else + if hdr.dime.datatype == 32 | hdr.dime.datatype == 1792 | ... + hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + + nii.img(:,:,:,1) = permute(nii.img(:,:,:,1), rot_orient); + nii.img(:,:,:,2) = permute(nii.img(:,:,:,2), rot_orient); + + if hdr.dime.datatype == 128 | hdr.dime.datatype == 511 + nii.img(:,:,:,3) = permute(nii.img(:,:,:,3), rot_orient); + end + else + nii.img = permute(nii.img, rot_orient); + end + end + else + hdr.hist.rot_orient = []; + hdr.hist.flip_orient = []; + end + + nii.hdr = hdr; + + return; % xform_nii + + +%----------------------------------------------------------------------- +function [hdr, orient] = change_hdr(hdr, tolerance, preferredForm) + + orient = [1 2 3]; + affine_transform = 1; + + % NIFTI can have both sform and qform transform. This program + % will check sform_code prior to qform_code by default. + % + % If user specifys "preferredForm", user can then choose the + % priority. - Jeff + % + useForm=[]; % Jeff + + if isequal(preferredForm,'S') + if isequal(hdr.hist.sform_code,0) + error('User requires sform, sform not set in header'); + else + useForm='s'; + end + end % Jeff + + if isequal(preferredForm,'Q') + if isequal(hdr.hist.qform_code,0) + error('User requires qform, qform not set in header'); + else + useForm='q'; + end + end % Jeff + + if isequal(preferredForm,'s') + if hdr.hist.sform_code > 0 + useForm='s'; + elseif hdr.hist.qform_code > 0 + useForm='q'; + end + end % Jeff + + if isequal(preferredForm,'q') + if hdr.hist.qform_code > 0 + useForm='q'; + elseif hdr.hist.sform_code > 0 + useForm='s'; + end + end % Jeff + + if isequal(useForm,'s') + R = [hdr.hist.srow_x(1:3) + hdr.hist.srow_y(1:3) + hdr.hist.srow_z(1:3)]; + + T = [hdr.hist.srow_x(4) + hdr.hist.srow_y(4) + hdr.hist.srow_z(4)]; + + if det(R) == 0 | ~isequal(R(find(R)), sum(R)') + hdr.hist.old_affine = [ [R;[0 0 0]] [T;1] ]; + R_sort = sort(abs(R(:))); + R( find( abs(R) < tolerance*min(R_sort(end-2:end)) ) ) = 0; + hdr.hist.new_affine = [ [R;[0 0 0]] [T;1] ]; + + if det(R) == 0 | ~isequal(R(find(R)), sum(R)') + msg = [char(10) char(10) ' Non-orthogonal rotation or shearing ']; + msg = [msg 'found inside the affine matrix' char(10)]; + msg = [msg ' in this NIfTI file. You have 3 options:' char(10) char(10)]; + msg = [msg ' 1. Using included ''reslice_nii.m'' program to reslice the NIfTI' char(10)]; + msg = [msg ' file. I strongly recommand this, because it will not cause' char(10)]; + msg = [msg ' negative effect, as long as you remember not to do slice' char(10)]; + msg = [msg ' time correction after using ''reslice_nii.m''.' char(10) char(10)]; + msg = [msg ' 2. Using included ''load_untouch_nii.m'' program to load image' char(10)]; + msg = [msg ' without applying any affine geometric transformation or' char(10)]; + msg = [msg ' voxel intensity scaling. This is only for people who want' char(10)]; + msg = [msg ' to do some image processing regardless of image orientation' char(10)]; + msg = [msg ' and to save data back with the same NIfTI header.' char(10) char(10)]; + msg = [msg ' 3. Increasing the tolerance to allow more distortion in loaded' char(10)]; + msg = [msg ' image, but I don''t suggest this.' char(10) char(10)]; + msg = [msg ' To get help, please type:' char(10) char(10) ' help reslice_nii.m' char(10)]; + msg = [msg ' help load_untouch_nii.m' char(10) ' help load_nii.m']; + error(msg); + end + end + + elseif isequal(useForm,'q') + b = hdr.hist.quatern_b; + c = hdr.hist.quatern_c; + d = hdr.hist.quatern_d; + + if 1.0-(b*b+c*c+d*d) < 0 + if abs(1.0-(b*b+c*c+d*d)) < 1e-5 + a = 0; + else + error('Incorrect quaternion values in this NIFTI data.'); + end + else + a = sqrt(1.0-(b*b+c*c+d*d)); + end + + qfac = hdr.dime.pixdim(1); + if qfac==0, qfac = 1; end + i = hdr.dime.pixdim(2); + j = hdr.dime.pixdim(3); + k = qfac * hdr.dime.pixdim(4); + + R = [a*a+b*b-c*c-d*d 2*b*c-2*a*d 2*b*d+2*a*c + 2*b*c+2*a*d a*a+c*c-b*b-d*d 2*c*d-2*a*b + 2*b*d-2*a*c 2*c*d+2*a*b a*a+d*d-c*c-b*b]; + + T = [hdr.hist.qoffset_x + hdr.hist.qoffset_y + hdr.hist.qoffset_z]; + + % qforms are expected to generate rotation matrices R which are + % det(R) = 1; we'll make sure that happens. + % + % now we make the same checks as were done above for sform data + % BUT we do it on a transform that is in terms of voxels not mm; + % after we figure out the angles and squash them to closest + % rectilinear direction. After that, the voxel sizes are then + % added. + % + % This part is modified by Jeff Gunter. + % + if det(R) == 0 | ~isequal(R(find(R)), sum(R)') + + % det(R) == 0 is not a common trigger for this --- + % R(find(R)) is a list of non-zero elements in R; if that + % is straight (not oblique) then it should be the same as + % columnwise summation. Could just as well have checked the + % lengths of R(find(R)) and sum(R)' (which should be 3) + % + hdr.hist.old_affine = [ [R * diag([i j k]);[0 0 0]] [T;1] ]; + R_sort = sort(abs(R(:))); + R( find( abs(R) < tolerance*min(R_sort(end-2:end)) ) ) = 0; + R = R * diag([i j k]); + hdr.hist.new_affine = [ [R;[0 0 0]] [T;1] ]; + + if det(R) == 0 | ~isequal(R(find(R)), sum(R)') + msg = [char(10) char(10) ' Non-orthogonal rotation or shearing ']; + msg = [msg 'found inside the affine matrix' char(10)]; + msg = [msg ' in this NIfTI file. You have 3 options:' char(10) char(10)]; + msg = [msg ' 1. Using included ''reslice_nii.m'' program to reslice the NIfTI' char(10)]; + msg = [msg ' file. I strongly recommand this, because it will not cause' char(10)]; + msg = [msg ' negative effect, as long as you remember not to do slice' char(10)]; + msg = [msg ' time correction after using ''reslice_nii.m''.' char(10) char(10)]; + msg = [msg ' 2. Using included ''load_untouch_nii.m'' program to load image' char(10)]; + msg = [msg ' without applying any affine geometric transformation or' char(10)]; + msg = [msg ' voxel intensity scaling. This is only for people who want' char(10)]; + msg = [msg ' to do some image processing regardless of image orientation' char(10)]; + msg = [msg ' and to save data back with the same NIfTI header.' char(10) char(10)]; + msg = [msg ' 3. Increasing the tolerance to allow more distortion in loaded' char(10)]; + msg = [msg ' image, but I don''t suggest this.' char(10) char(10)]; + msg = [msg ' To get help, please type:' char(10) char(10) ' help reslice_nii.m' char(10)]; + msg = [msg ' help load_untouch_nii.m' char(10) ' help load_nii.m']; + error(msg); + end + + else + R = R * diag([i j k]); + end % 1st det(R) + + else + affine_transform = 0; % no sform or qform transform + end + + if affine_transform == 1 + voxel_size = abs(sum(R,1)); + inv_R = inv(R); + originator = inv_R*(-T)+1; + orient = get_orient(inv_R); + + % modify pixdim and originator + % + hdr.dime.pixdim(2:4) = voxel_size; + hdr.hist.originator(1:3) = originator; + + % set sform or qform to non-use, because they have been + % applied in xform_nii + % + hdr.hist.qform_code = 0; + hdr.hist.sform_code = 0; + end + + % apply space_unit to pixdim if not 1 (mm) + % + space_unit = get_units(hdr); + + if space_unit ~= 1 + hdr.dime.pixdim(2:4) = hdr.dime.pixdim(2:4) * space_unit; + + % set space_unit of xyzt_units to millimeter, because + % voxel_size has been re-scaled + % + hdr.dime.xyzt_units = char(bitset(hdr.dime.xyzt_units,1,0)); + hdr.dime.xyzt_units = char(bitset(hdr.dime.xyzt_units,2,1)); + hdr.dime.xyzt_units = char(bitset(hdr.dime.xyzt_units,3,0)); + end + + hdr.dime.pixdim = abs(hdr.dime.pixdim); + + return; % change_hdr + + +%----------------------------------------------------------------------- +function orient = get_orient(R) + + orient = []; + + for i = 1:3 + switch find(R(i,:)) * sign(sum(R(i,:))) + case 1 + orient = [orient 1]; % Left to Right + case 2 + orient = [orient 2]; % Posterior to Anterior + case 3 + orient = [orient 3]; % Inferior to Superior + case -1 + orient = [orient 4]; % Right to Left + case -2 + orient = [orient 5]; % Anterior to Posterior + case -3 + orient = [orient 6]; % Superior to Inferior + end + end + + return; % get_orient + + +%----------------------------------------------------------------------- +function [space_unit, time_unit] = get_units(hdr) + + switch bitand(hdr.dime.xyzt_units, 7) % mask with 0x07 + case 1 + space_unit = 1e+3; % meter, m + case 3 + space_unit = 1e-3; % micrometer, um + otherwise + space_unit = 1; % millimeter, mm + end + + switch bitand(hdr.dime.xyzt_units, 56) % mask with 0x38 + case 16 + time_unit = 1e-3; % millisecond, ms + case 24 + time_unit = 1e-6; % microsecond, us + otherwise + time_unit = 1; % second, s + end + + return; % get_units +