Accelerate Matlab nested for loop with bsxfun - matlab

I have a graph n x n graph W described as its adjacency matrix and a n vector of group labels (integers) of every node.
I need to count the number of links (edges) between nodes in group c and nodes in group d for every pair of groups. Do to this I wrote a nested for loop but I'm sure that this is not the fastest way to compute the matrix that in the code I call mcd, i.e. the matrix that counts the number of edges betweeen group c and d.
Is it possible through the bsxfun to make this operation faster?
function mcd = interlinks(W,ci)
%// W is the adjacency matrix of a simple undirected graph
%// ci are the group labels of every node in the graph, can be from 1 to |C|
n = length(W); %// number of nodes in the graph
m = sum(nonzeros(triu(W))); %// number of edges in the graph
ncomms = length(unique(ci)); %// number of groups of ci
mcd = zeros(ncomms); %// this is the matrix that counts the number of edges between group c and group d, twice the number of it if c==d
for c=1:ncomms
nodesc = find(ci==c); %// nodes in group c
for d=1:ncomms
nodesd = find(ci==d); %// nodes in group d
M = W(nodesc,nodesd); %// submatrix of edges between c and d
mcd(c,d) = sum(sum(M)); %// count of edges between c and d
end
end
%// Divide diagonal half because counted twice
mcd(1:ncomms+1:ncomms*ncomms)=mcd(1:ncomms+1:ncomms*ncomms)/2;
For example in the picture here the adjacency matrix is
W=[0 1 1 0 0 0;
1 0 1 1 0 0;
1 1 0 0 1 1;
0 1 0 0 1 0;
0 0 1 1 0 1;
0 0 1 0 1 0];
the group label vector is ci=[ 1 1 1 2 2 3] and the resulting matrix mcd is:
mcd=[3 2 1;
2 1 1;
1 1 0];
It means for example that group 1 has 3 links with itself, 2 links with group 2 and 1 link with group 3.

How about this?
C = bsxfun(#eq, ci,unique(ci)');
mcd = C*W*C'
mcd(logical(eye(size(mcd)))) = mcd(logical(eye(size(mcd))))./2;
I think it is what you wanted.

IIUC and assuming ci as an sorted array, it seems you are basically doing blockwise summations, but with irregular block sizes. Thus, you can use an approach using cumsum along the rows and columns and then differentiating at the shift positions in ci, which will basically give you blockwise summations.
The implementation would look like this -
%// Get cumulative sums row-wise and column-wise
csums = cumsum(cumsum(W,1),2)
%/ Get IDs of shifts and thus get cumsums at those positions
[~,idx] = unique(ci) %// OR find(diff([ci numel(ci)]))
csums_indexed = csums(idx,idx)
%// Get the blockwise summations by differentiations on csums at shifts
col1 = diff(csums_indexed(:,1),[],1)
row1 = diff(csums_indexed(1,:),[],2)
rest2D = diff(diff(csums_indexed,[],2),[],1)
out = [[csums_indexed(1,1) ; col1] [row1 ; rest2D]]

If you're not opposed to a mex function, you can use my code below.
testing code
n = 2000;
n_labels = 800;
W = rand(n, n);
W = W * W' > .5; % generate symmetric adjacency matrix of logicals
Wd = double(W);
ci = floor(rand(n, 1) * n_labels ) + 1; % generate ids from 1 to 251
[C, IA, IC] = unique(ci);
disp(sprintf('base avg fun time = %g ',timeit(#() interlinks(W, IC))));
disp(sprintf('mex avg fun time = %g ',timeit(#() interlink_mex(W, IC))));
%note this function requires symmetric (function from #aarbelle)
disp(sprintf('bsx avg fun time = %g ',timeit(#() interlinks_bsx(Wd, IC'))));
x1 = interlinks(W, IC);
x2 = interlink_mex(W, IC);
x3 = interlinks_bsx(Wd, IC');
disp(sprintf('norm(x1 - x2) = %g', norm(x1 - x2)));
disp(sprintf('norm(x1 - x3) = %g', norm(x1 - x3)));
testing results
Testing results with these settings:
base avg fun time = 4.94275
mex avg fun time = 0.0373092
bsx avg fun time = 0.126406
norm(x1 - x2) = 0
norm(x1 - x3) = 0
Basically, for small n_labels, the bsx function does very well but you can make it large enough so that the mex function is faster.
c++ code
throw it into some file like interlink_mex.cpp and compile with mex interlink_mex.cpp. You need a c++ compiler on your machine etc...
#include "mex.h"
#include "matrix.h"
#include <math.h>
// Author: Matthew Gunn
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {
if(nrhs != 2)
mexErrMsgTxt("Invalid number of inputs. Shoudl be 2 input argument.");
if(nlhs != 1)
mexErrMsgTxt("Invalid number of outputs. Should be 1 output arguments.");
if(!mxIsLogical(prhs[0])) {
mexErrMsgTxt("First argument should be a logical array (i.e. type logical)");
}
if(!mxIsDouble(prhs[1])) {
mexErrMsgTxt("Second argument should be an array of type double");
}
const mxArray *W = prhs[0];
const mxArray *ci = prhs[1];
size_t W_m = mxGetM(W);
size_t W_n = mxGetN(W);
if(W_m != W_n)
mexErrMsgTxt("Rows and columns of W are not equal");
// size_t ci_m = mxGetM(ci);
size_t ci_n = mxGetNumberOfElements(ci);
mxLogical *W_data = mxGetLogicals(W);
// double *W_data = mxGetPr(W);
double *ci_data = mxGetPr(ci);
size_t *ci_data_size_t = (size_t*) mxCalloc(ci_n, sizeof(size_t));
size_t ncomms = 0;
double intpart;
for(size_t i = 0; i < ci_n; i++) {
double x = ci_data[i];
if(x < 1 || x > 65536 || modf(x, &intpart) != 0.0) {
mexErrMsgTxt("Input ci is not all integers from 1 to a maximum value of 65536 (can edit source code to change this)");
}
size_t xx = (size_t) x;
if(xx > ncomms)
ncomms = xx;
ci_data_size_t[i] = xx - 1;
}
mxArray *mcd = mxCreateDoubleMatrix(ncomms, ncomms, mxREAL);
double *mcd_data = mxGetPr(mcd);
for(size_t i = 0; i < W_n; i++) {
size_t ii = ci_data_size_t[i];
for(size_t j = 0; j < W_n; j++) {
size_t jj = ci_data_size_t[j];
mcd_data[ii + jj * ncomms] += (W_data[i + j * W_m] != 0);
}
}
for(size_t i = 0; i < ncomms * ncomms; i+= ncomms + 1) //go along diagonal
mcd_data[i]/=2; //divide by 2
mxFree(ci_data_size_t);
plhs[0] = mcd;
}

Related

How can I do vectorization for this matlab "for loop"?

I have some matlab code as follow, constructing KNN similarity weight matrix.
[D,I] = pdist2(X, X, 'squaredeuclidean', 'Smallest', k+1);
D = D < threshold;
W = zeros(n, n);
for i=1:size(I,2)
W(I(:,i), i) = D(:,i);
W(i, I(:,i)) = D(:,i)';
end
I want to vectorize the for loop. I have tried
W(I) = D;
but failed to get the correct value.
I add test case here:
n = 5;
D = [
1 1 1 1 1
0 1 1 1 1
0 0 0 0 0
];
I = [
1 2 3 4 5
5 4 5 2 3
3 1 1 1 1
];
There are some undefined variables that makes it hard to check what it is doing, but this should do the same as your for loop:
D,I] = pdist2(X, X, 'squaredeuclidean', 'Smallest', k+1);
D = D < threshold;
W = zeros(n);
% set the diagonal values
W(sub2ind(size(X), I(1, :), I(1, :))) = D(1,:);
% set the other values
W(sub2ind(size(W), I(2, :), 1:size(I, 2))) = D(2, :);
W(sub2ind(size(W), 1:size(I, 2), I(2, :))) = D(2, :).';
I splited the directions, it works now with your test case.
A possible solution:
idx1 = reshape(1:n*n,n,n).';
idx2 = bsxfun(#plus,I,0:n:n*size(I,2)-1);
W=zeros(n,n);
W(idx2) = D;
W(idx1(idx2)) = D;
Here assumed that you repeatedly want to compute D and I so compute idx only one time and use it repeatedly.
n = 5;
idx1 = reshape(1:n*n,n,n).';
%for k = 1 : 1000
%[D,I] = pdist2(X, X, 'squaredeuclidean', 'Smallest', k+1);
%D = D < threshold;
idx2 = bsxfun(#plus,I,0:n:n*size(I,2)-1);
W=zeros(n,n);
W(idx2) = D;
W(idx1(idx2)) = D;
%end
But if n isn't constant and it varies in each iteration it is better to change the way idx1 is computed:
n = 5;
%for k = 1 : 1000
%n = randi([2 10]);%n isn't constant
%[D,I] = pdist2(X, X, 'squaredeuclidean', 'Smallest', k+1);
%D = D < threshold;
idx1 = bsxfun(#plus,(0:n:n^2-1).',1:size(I,2));
idx2 = bsxfun(#plus,I,0:n:n*size(I,2)-1);
W=zeros(n,n);
W(idx2) = D;
W(idx1(idx2)) = D;
%end
You can cut some corners with linear indices but if your matrices are big then you should only take the nonzero components of D. Following copies all values of D
W = zeros(n);
W(reshape(sub2ind([n,n],I,[1;1;1]*[1:n]),1,[])) = reshape(D,1,[]);

MATLAB: Limit from n to infinity for power of a matrix

I am trying to compute lim(n->inf) for D^n, where D is a diagonal matrix:
D = [1.0000 0 0 0; 0 0.6730 0 0; 0 0 0.7600 0; 0 0 0 0.7370]
n = 1
L = limit(D^n,n,inf)
This returns the error:
Undefined function 'limit' for input arguments of type 'double'.
I am sure this should result in most entries except the upper-left entry going to zero, but I need to be able to present this with MATLAB results. Is there something else I need to include in my limit function?
If your problem is to compute the inf-limit of a diagonal matrix, you'd better create your own function and handle manually the possible cases :
function Mlim = get_diag_matrix_inf_limit(M)
% get the diagonal
M_diag = diag(M);
% All possible cases
I_nan = M_diag <= -1;
I_0 = abs(M_diag) < 1;
I_1 = M_diag == 1;
I_inf = M_diag > 1;
% Update diagonal
M_diag(I_nan) = nan;
M_diag(I_0) = 0;
M_diag(I_1) = 1;
M_diag(I_inf) = Inf;
% Generate new diagonal matrix
Mlim = diag(M_diag);
end

Edge/Vertex matrix from triangulation

I have to analyze some STL files with Matlab, and I import them successfully with an STL reader, but this function only returns vertices and faces (triangles).
This is the STL reader I am using, and this is an example STL file, generated by the gmsh tool with gmsh -2 -format stl -bin t4.geo. In case, the code for the STL function is at the end.
mesh = stlread("t4.stl");
Is there a function I can use to obtain the vertex/edge adjacency matrix from such a triangulation?
function [F,V,N] = stlbinary(M)
F = [];
V = [];
N = [];
if length(M) < 84
error('MATLAB:stlread:incorrectFormat', ...
'Incomplete header information in binary STL file.');
end
% Bytes 81-84 are an unsigned 32-bit integer specifying the number of faces
% that follow.
numFaces = typecast(M(81:84),'uint32');
%numFaces = double(numFaces);
if numFaces == 0
warning('MATLAB:stlread:nodata','No data in STL file.');
return
end
T = M(85:end);
F = NaN(numFaces,3);
V = NaN(3*numFaces,3);
N = NaN(numFaces,3);
numRead = 0;
while numRead < numFaces
% Each facet is 50 bytes
% - Three single precision values specifying the face normal vector
% - Three single precision values specifying the first vertex (XYZ)
% - Three single precision values specifying the second vertex (XYZ)
% - Three single precision values specifying the third vertex (XYZ)
% - Two unused bytes
i1 = 50 * numRead + 1;
i2 = i1 + 50 - 1;
facet = T(i1:i2)';
n = typecast(facet(1:12),'single');
v1 = typecast(facet(13:24),'single');
v2 = typecast(facet(25:36),'single');
v3 = typecast(facet(37:48),'single');
n = double(n);
v = double([v1; v2; v3]);
% Figure out where to fit these new vertices, and the face, in the
% larger F and V collections.
fInd = numRead + 1;
vInd1 = 3 * (fInd - 1) + 1;
vInd2 = vInd1 + 3 - 1;
V(vInd1:vInd2,:) = v;
F(fInd,:) = vInd1:vInd2;
N(fInd,:) = n;
numRead = numRead + 1;
end
end
Assuming your faces are in a n-by-3 array F:
% temporary array
T = [F(:,1) F(:,2) ; F(:,1) F(:,3) ; F(:,2) F(:,3)];
% get the edges
E = unique([min(T,[],2), max(T,[],2)],'rows');
% build the adjacency matrix
n = max(E(:,2));
A = sparse(E(:,1), E (:,2), ones(size(E,1),1), n, n);
A = A + A';
NB: sparse arrays are often useful for such adjacency matrix, especially in the large limit.
Best,

Abnormal behavior algorithm implemented in matlab depending of the input

I'm doing a homework assignment for scientific computing, specifically the iterative methods Gauss-Seidel and SOR in matlab, the problem is that for a matrix gives me unexpected results (the solution does not converge) and for another matrix converges.
Heres the code of sor, where:
A: Matrix of the system A * x = b
Xini: array of initial iteration
b: array independent of the system A * x = b
maxiter: Maximum Iterations
tol: Tolerance;
In particular, the SOR method, will receive a sixth parameter called w which corresponds to the relaxation parameter.
Here´s the code for sor method:
function [x2,iter] = sor(A,xIni, b, maxIter, tol,w)
x1 = xIni;
x2 = x1;
iter = 0;
i = 0;
j = 0;
n = size(A, 1);
for iter = 1:maxIter,
for i = 1:n
a = w / A(i,i);
x = 0;
for j = 1:i-1
x = x + (A(i,j) * x2(j));
end
for j = i+1:n
x = x + (A(i,j) * x1(j));
end
x2(i) = (a * (b(i) - x)) + ((1 - w) * x1(i));
end
x1 = x2;
if (norm(b - A * x2) < tol);
break;
end
end
Here´s the code for Gauss-seidel method:
function [x, iter] = Gauss(A, xIni, b, maxIter, tol)
x = xIni;
xnew = x;
iter = 0;
i = 0;
j = 0;
n = size(A,1);
for iter = 1:maxIter,
for i = 1:n
a = 1 / A(i,i);
x1 = 0;
x2 = 0;
for j = 1:i-1
x1 = x1 + (A(i,j) * xnew(j));
end
for j = i+1:n
x2 = x2 + (A(i,j) * x(j));
end
xnew(i) = a * (b(i) - x1 - x2);
end
x= xnew;
if ((norm(A*xnew-b)) <= tol);
break;
end
end
For this input:
A = [1 2 -2; 1 1 1; 2 2 1];
b = [1; 2; 5];
when call the function Gauss-Seidel or sor :
[x, iter] = gauss(A, [0; 0; 0], b, 1000, eps)
[x, iter] = sor(A, [0; 0; 0], b, 1000, eps, 1.5)
the output for gauss is:
x =
1.0e+304 *
1.6024
-1.6030
0.0011
iter =
1000
and for sor is:
x =
NaN
NaN
NaN
iter =
1000
however for the following system is able to find the solution:
A = [ 4 -1 0 -1 0 0;
-1 4 -1 0 -1 0;
0 -1 4 0 0 -1;
-1 0 0 4 -1 0;
0 -1 0 -1 4 -1;
0 0 -1 0 -1 4 ]
b = [1 0 0 0 0 0]'
Solution:
[x, iter] = sor(A, [0; 0; 0], b, 1000, eps, 1.5)
x =
0.2948
0.0932
0.0282
0.0861
0.0497
0.0195
iter =
52
The behavior of the methods depends on the conditioning of both matrices? because I noticed that the second matrix is better conditioned than the first. Any suggestions?
From the wiki article on Gauss-Seidel:
convergence is only guaranteed if the matrix is either diagonally dominant, or symmetric and positive definite
Since SOR is similar to Gauss-Seidel, I expect the same conditions to hold for SOR, but you might want to look that one up.
Your first matrix is definitely not diagonally dominant or symmetric. Your second matrix however, is symmetric and positive definite (because all(A==A.') and all(eig(A)>0)).
If you use Matlab's default method (A\b) as the "real" solution, and you plot the norm of the difference between each iteration and the "real" solution, then you get the two graphs below. It is obvious the first matrix is not ever going to converge, while the second matrix already produces acceptable results after a few iterations.
Always get to know the limitations of your algorithms before applying them in the wild :)

3d patches from a 3d matrix

I have a 3d matrix (3x3x3), and I need to extract 3d patches (2x2x2) and transform them in vectors.
In 2d, simply:
I=randi(5,3,3);
2d_patches=im2col(I,[2 2],'sliding');
What about 3d?
I=randi(5,3,3,3);
3d_patches= ???
im2col just works in 2d. In 3d I should recombine the vectors 1 and 7, 2 and 8, ...
Is there any fast function for this task?
I do not believe that there is any built-in way to do this. If you need it to be fast, it should be fairly simple to write your own mex-function in c and call it from Matlab.
Here is my (quick and dirty) solution:
im3col.c:
#include <mex.h>
void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[] )
{
const mxArray *I = prhs[0];
double *indata = mxGetPr(I);
double *patchSize = mxGetPr(prhs[1]);
const int *size = mxGetDimensions(I);
int J = (int)patchSize[0], K = (int)patchSize[1], H = (int)patchSize[2];
int M = size[0], N = size[1], P = size[2];
int numPatches = (M - J + 1)*(N - K + 1)*(P - H + 1);
int out_rows = J*K*H, out_cols = numPatches;
mxArray *out = mxCreateDoubleMatrix( out_rows, out_cols, mxREAL );
double *outdata = mxGetPr(out);
int patch = 0;
for( int h_offset = 0; h_offset < P-H+1; h_offset++ ){
for( int k_offset = 0; k_offset < N-K+1; k_offset++ ){
for( int j_offset = 0; j_offset < M-J+1; j_offset++ ){
int row = 0;
for( int h = 0; h < H; h++ ){
for( int k = 0; k < K; k++ ){
for( int j = 0; j < J; j++ ){
outdata[patch*out_rows + row] =
indata[ (j_offset+j) + (k_offset+k)*M + (h_offset+h)*M*N ];
++row;
}}}
++patch;
}}}
plhs[0] = out;
}
Compile:
>> mex -O CFLAGS="\$CFLAGS -std=c99 -Wall" im3col.c
Test:
>> A(:,:,1) = [1,4,7;2,5,8;3,6,9]; A(:,:,2) = [10,13,16;11,14,17;12,15,18];
>> B = im3col(A, [2,2,1])
B =
1 2 4 5 10 11 13 14
2 3 5 6 11 12 14 15
4 5 7 8 13 14 16 17
5 6 8 9 14 15 17 18
>> A(:,:,1),A(:,:,2)
ans =
1 4 7
2 5 8
3 6 9
ans =
10 13 16
11 14 17
12 15 18
Here is the other direction:
(It is pretty slow and there is definitely a faster way)
function [img] = patch2im_2d_time(patch, size_img, size_patch, size_skip, border)
Nx = size_img(1);
Ny = size_img(2);
Nt = size_img(5);
psz1 = size_patch(1);
psz2 = size_patch(2);
psz3 = size_patch(3);
%Extract blocks. One could save a lot here.
patches = reshape(patch, [psz1 psz2 psz3 size(patch,2)]);
c = 1;
img2 = zeros(squeeze(size_img));
%Count for each pixel how many times we added smth to it.
add_count = zeros(size_img);
%The first three loops, loop through all the pixels in the image
for d=1:Nt-psz3+1
for j=1:Nx-psz2+1
for i=1:Ny-psz1+1
%Here we get the next patch. The next patch is always
%the patch that has the pixel at i,j,d at its top front corner.
current_patch = patches(:,:,:,c);
%counter for the next patch
c = c + 1;
%In this loop we add the patch values of each pixel in the
%patch to the image. i,j,d is the base. We add the offset
%ii jj and dd to it. This iteration takes psz^3 many
%iterations.
for dd=1:psz3
for ii=1:psz2
for jj=1:psz1
img2(i+ii-1,j+jj-1,d+dd-1) = img2(i+ii-1,j+jj-1,d+dd-1) + current_patch(ii,jj,dd);
add_count(i+ii-1,j+jj-1,d+dd-1) = add_count(i+ii-1,j+jj-1,d+dd-1) + 1;
end
end
end
end
end
end
img = flipud(rot90(img2 ./ add_count,1));
end
Remember that MATLAB uses col major.
%One possible way to use matlab to call im2col and reshape twice
%N = [row, col, num_frames]
[x_height, ~, num_frames] = size(N);
patchSize = 16;
patchTemporal = 10;
N = reshape(N, x_height, []);
N = im2col(N, [patchSize, patchSize], 'distinct');
N = reshape(N, [], num_frames);
N = im2col(N, [patchSize^2, patchTemporal], 'distinct');
% N = [patchSize^2 *patchTemporal x numPatches]
hi guys what about this solution. To obtain 3x3x3 ROIs from I suggest :
blkSize=3; % should be a odd value like 3,5,7,etc
r=floor(blkSize/2);
k=1;
for sliceNo=(r+1):(size(I,3)-r)
img= I(:,:,sliceNo-r:sliceNo+r);
noPix=(size(img,1)-2*r)*(size(img,2)-2*r);
neiblk=zeros(blkSize^3,noPix);
for blk=1:blkSize
neiblk(blkSize^2*(blk-1)+1:blkSize^2*blk,:)=im2col(img(:,:,blk),...
[blkSize,blkSize],'sliding');
end
ROIs(:,noPix*(k-1)+1:noPix*k)=neiblk;
k=k+1;
end