Efficient computation of Euclidean distance between cell arrays - matlab

I have an a-by-b cell array, C. In each element, there is a float array.
I now want to create a new symmetric matrix M. Each element (i, j) in M is to be set to the sum of the Euclidean distances of all the respective float arrays in C.
For example, to find M(i,j), I would take the set of b float arrays in C along row i, and the set of b float arrays in C along row j, find the Euclidean distance between each array across the two sets, and then sum up the b x b values. C{i,j} is a column vector. All columns are the same length.
Below is my "brute force" implementation of this:
for i=1:a
for j=1:a
dist_sum = 0;
for k=1:b
for l=1:b
dist = sqrt(sum((C{i, k} - C{j, l}) .^ 2));
dist_sum = dist_sum + dist;
end
end
M(j, i) = dist_sum;
M(i, j) = dist_sum;
end
end
My question: Is there a more efficient way of doing this using matrix operations, without having to explicitly compute each Euclidean distance in turn?

It would be better to use a 3D array, instead of a 2D cell array of equal-size column vectors.
If you have a cell array: first convert into a 3D array (D in my code); then it's easy to compute distances with bsxfun; and finally apply sum:
D = permute(C, [3 1 2]);
D = reshape(cat(2, D{:}), [], size(C,1), size(C,2)); %// 3D array
dist = sqrt(sum(bsxfun(#minus, D, permute(D, [1 4 5 2 3])).^2)); %// distances
M = squeeze(sum(sum(dist, 3), 5)); %// sum of distances
Example: with
>> C = {[1; 2], [30; 40], [0; 1]; [5; 7] [19; 17] [4; 5]}; %// a is 2, b is 3
the result of both your code and mine is
M =
196.8391 182.8791
182.8791 77.3002

Before Calculating Euclidean Distance:
Can convert the cell array to matrix by using cell2mat... then u can use following methods..
Method 1:
G = rand(1, 72);
G2 = rand(1, 72);
D = sqrt(sum((G - G2) .^ 2));
Method 2:
V = G - G2;
D = sqrt(V * V');
Method 3:
D = norm(G - G2);
Method 4:
D = pdist2(G,G2);

I would suggest converting the matrix elements to a vector using (:) and then using distance2curve.m function file from Matlab File Exchange to find the minimum/Euclidean distance between the two arrays.
Let's say the two cell arrays are A and B with matrices containing the row and column indices for each cell array denoted as 'indA' and 'indB', where each row in 'indA' and 'indB' contains the row element and column elements of 'A' and 'B', respectively. Now use the above function as:
[M, distance, t] = distance2curve(indA(:, :), indB(:, :))
The output variable M should contain the minimum/Euclidean distance between the two arrays that you are looking for.

Related

Generalise indexing of a multi-dimensional array in Matlab

I want to generalise to any n the Matlab code below.
Let A be an n-dimensional array:
clear
rng default
n=4;
A=randn(n,n,n,n);
n=5;
A=randn(n,n,n,n,n);
Note that A is composed of n^(n-2) 2-dimensional matrices, each of size nxn.
For example, when n=4 these matrices are A(:,:,1,1),...,A(:,:,4,1),A(:,:,1,2),...,A(:,:,4,4).
Suppose I'm interested in a code which:
1) deletes the last column and row in each of the n^(n-2) 2-dimensional matrices
%when n=4
A(n,:,:,:)=[];
A(:,n,:,:)=[];
%when n=5
A(n,:,:,:,:)=[];
A(:,n,:,:,:)=[];
2) deletes the 2-dimensional matrices with the 3-th,4-th,5-th,n-th index equal to n.
%when n=4
A(:,:,n,:)=[];
A(:,:,:,n)=[];
%when n=5
A(:,:,n,:,:)=[];
A(:,:,:,n,:)=[];
A(:,:,:,:,n)=[];
Question: could you help me to generalise the code above to any n? I cannot see how to proceed.
You can index your matrix with a cell containing multiple elements. Each element will be interpreted as a new index (more information here):
%Example 1: A(:,:,1:3,1:3,1:3}
%elements per dimension
n = 4;
%number of dimension
d = 5;
%random matrix
repdim = repmat({n},d,1)
A = rand(repdim{:});
%We want A(:,:,1:3,1:3,1:3}, so we create c = {1:3,1:3,1:3}
c = repmat({1:n-1},d-2,1);
%Get the new matrix
A = A(:,:,c{:});
%Example 2: A(1:3,1:3,:,:,:}
%elements per dimension
n = 4;
%number of dimension
d = 5;
%random matrix
repdim = repmat({n},d,1)
A = rand(repdim{:});
%We want A(1:3,1:3,:,:,:}, so we create c1 = {1:3,1:3} and c2 = {':',':',':'}
c1 = repmat({1:n-1},2,1);
c2 = repmat({':'},d-2,1); %thanks to #LuisMendo for the suggestion.
%Get the new matrix
A = A(c1{:},c2{:});

3d matrix: how to use (row, column) pairs with 3rd dimension wildcard in MATLAB?

I have a 3 dimensional matrix, and a list of (row, column) pairs. I would like to extract the 2 dimensional matrix that corresponds to the elements in those positions, projected through the depth of the matrix. For instance, suppose,
>> a = rand(4, 3, 2)
a(:,:,1) =
0.5234 0.7057 0.0282
0.6173 0.2980 0.9041
0.7337 0.9380 0.9639
0.0591 0.8765 0.1693
a(:,:,2) =
0.8803 0.2094 0.5841
0.7151 0.9174 0.6203
0.7914 0.7674 0.6194
0.2009 0.2542 0.3600
>> rows = [1 4 2 1];
>> cols = [1 2 1 3];
What I'd like to get is,
0.5234 0.8765 0.6173 0.0282
0.8803 0.2542 0.7151 0.5841
maybe with some permutation of dimensions. Also, although this example has the wildcard in the last dimension, I also have cases where it's in the first or second.
I naively tried a(rows, cols, :) and got a 3d matrix where the diagonal plane is what I want. I also found sub2ind, which will extract the desired elements from the a(:,:,1) plane. I could work with one of these to get to what I want, but I'm wondering is there a more canonical, elegant, or efficient method that I'm missing?
Update
This was the solution I used, based on the answer posted below,
sz = size(a);
subs = [repmat(rows, [1, sz(3)]);
repmat(cols, [1, sz(3)]);
repelem([1:sz(3)], length(rows))];
result = a(sub2ind(sz, subs(1,:), subs(2,:), subs(3,:)));
sub2ind is pretty much what you have to use here to convert your subscripts into linear indices (apart from manually computing the linear indices yourself). You can do something like the following which will convert the rows and cols to a linear index (in a 2D slice) and then it adds an offset (equal to the number of elements in a 2D slice) to these indices to sample all elements in the third dimension.
sz = size(a);
inds = sub2ind(sz(1:2), rows, cols);
inds = bsxfun(#plus, inds, (0:(sz(3)-1)).' * prod(sz(1:2)));
result = a(inds);
And to actually compute the linear indices yourself
inds = (cols - 1) * sz(1) + rows;
inds = bsxfun(#plus, inds, (0:(sz(3) - 1)).' * prod(sz(1:2)));
result = a(inds);
Another option would be to permute your initial matrix to bring the third dimension to the first dimension, reshape it to a 2D matrix, and then use the linear index as the second subscript
% Create a new temporary matrix
anew = reshape(permute(a, [3, 1, 2]), size(a, 3), []);
% Grab all rows (the 3rd dimension) and compute the columns to grab
result = anew(:, (cols - 1) * size(a, 1) + rows);

Linear combination of the Slices in a 3D

I have a 3D matrix sized (x,y,N) and a 2D matrix sized (N,N).
I would like to manipulate the two in a way that each column in the 2D matrix has the coefficients for a linear combination of the 2D sized- (x, y) slices in the 3D matrix. And I would like to do this for all N columns in the 2D matrix.
Schematically,
Currently the code looks like:
A = zeros(numel(x_axis), numel(y_axis), N);
B = zeros(numel(x_axis), numel(y_axis), N);
C = zeros(N, N)
for i = 1 : N
for j = 1 : N
A(:,:,i) = A(:,:,i) + B(:,:,j) * C(j,i);
end
end
But it is quite slow. Is there any way to speed up the MATLAB code by vectorizing?
If I understand your problem well, then this should work:
[p,q,N] = size(B);
A = reshape( reshape(B, [p*q, N]) * C, [p, q, N]);
edit: Cleaner version suggested by Suever:
A = reshape(reshape(B, [], size(B, 3)) * C, size(B))
Generalization to the R-D case:
A = reshape(reshape(B, [], size(B, ndims(B))) * C, size(B))
You can use bsxfun which will calculate this very quickly for you. We have to use permute to re-arrange C a little bit to ensure that it has conformant dimensions for using bsxfun and then we perform the summation along the third dimension of the resulting output and apply squeeze to remove the singleton third dimension.
A = squeeze(sum(bsxfun(#times, B, permute(C, [3 4 1 2])), 3))

Multidimensional Matrix Multiplication

I'm wondering if it is possible to perform a multidimensional matrix multiplication without resorting to a for-loop. Given the N-by-P matrix A and the N-by-M-by-P matrix B, I want to compute the M-dimensional vector y, defined element-wise as
y(j) = sum_(i = 1,...,N) sum_(k = 1,...,P) A(i,k)*B(i,j,k)
You can linearize A into a row vector, then reshape and permute the array B as a matrix, so that the desired result is just matrix multiplication:
M = 5;
N = 6;
P = 8;
A = rand(N,P);
B = rand(N,M,P);
result = A(:).'*reshape(permute(B, [1 3 2]), [], M);
Or reshape matrix A so that its dimensions are aligned with those of B, use bsxfun to multiply with singleton-expansion, and sum over the two desired dimensions:
result = sum(sum(bsxfun(#times, reshape(A, N, 1, P), B), 1), 3);

Linear index of the maximum of a multi-dimensional matrix - MATLAB

Let's say I have a 3-dimensional matrix and have computed the max along the second dimension, and want to get the linear indices of the max values. However, the max-function only returns the subscripts along one dimension.
A = randn([5,5,5]); % Generate random matrix
[M, Ind] = max(A,[],2); % Take the max along dimension 2
How do I transfer the index to linear indexing, such that
M == A(Ind)
becomes true?
My intention for this problem is that I have two multi-dimensional matrices and need to compute the max in the first one. Then, I want to access the values in the second matrix at exactly those positions where I found a max in the first one.
One way is to use sub2ind:
A = randn([5,5,5]);
[M, col] = max(A,[],2);
[m,n,o] = size(A);
dim1 = mod((0:m*o-1)', m)+1;
dim2 = col(:);
dim3 = ceil((1:m*o)/m)';
ind = sub2ind(size(A), dim1, dim2, dim3)
verify it works with
isequal(M(:), A(ind))
to get them to have the same shape as M:
reshape(ind, m, 1, o)
Create the indices for the other dimensions.
In dim 1 the index needs to change fastest: [1,2,...,size(A,1)] and this size(A,3) times:
idx1 = repmat((1:size(A,1))',size(A,3),1);
In dim 2 the index is given by Ind.
In dim 3 the index need to change slowest: [1,1,...,1] for size(A,1) times and then [2,2,...,2] and so on until size(A,3).
idx3 = ones(size(A,1),1)*(1:size(A,3));
Access single values:
M_ = A(sub2ind(size(A),idx1(:),Ind(:),idx3(:)));
Compare:
M(:) == M_
3-dimensional case:
[m, n, p] = size(A);
[M, Ind] = max(A,[],2);
LinInd = bsxfun(#plus, (1:m).', (0:p-1)*m*n); %'//
LinInd = LinInd(:) + (Ind(:)-1)*m;
The desired linear index is LinInd. This produces
A(LinInd) == M(:)
with all true entries (note you need (:) on the right-hand side so that the comparison makes sense).
General multi-dimensonal case:
d = 3; %// dimension along which max will be computed
s = size(A);
sLow = prod(s(1:d-1));
sHigh = prod(s(d+1:end));
[M, Ind] = max(A,[],d);
LinInd = bsxfun(#plus, (1:sLow).', (0:sHigh-1)*sLow*s(d)); %'//
LinInd = LinInd(:) + (Ind(:)-1)*sLow;
Let's suppose A and B are the two matrices you have and you need to get max indices from A and use those indices to index into B for the desired output. One approach to achieve the same could be like this -
%// Your code to get Ind
A = randn([5,5,5]); % Generate random matrix
[M, Ind] = max(A,[],2); % Take the max along dimension 2
%// ------- Solution code -------------
%// Get the size of A
[n1,n2,n3] = size(A)
%// Linear indices corresponding to column and third dimension indices
col_dim3_lin_idx = bsxfun(#plus,(Ind-1)*n1,permute([0:n3-1]*n1*n2,[1 3 2]))
%// Finally get the overall linear indices
linear_index = bsxfun(#plus,col_dim3_lin_idx,[1:n1]') %//'
%// Get the corresponding elements from B
out = B(linear_index)
Slightly different way to have the desired linear indices as a 2D array would be like this -
[n1,n2,n3] = size(A) %// Get the size of A
idx = bsxfun(#plus,bsxfun(#plus,squeeze((Ind-1)*n1),[0:n3-1]*n1*n2),[1:n1]')
idx(:) would be the column vector of linear indices with this new approach, which you can index into B i.e. B(idx(:)) to have the desired output as a column vector.