Multiplying a 4D matrix by a vector, and collapsing 1 dimension - matlab

I have a question regarding the multiplication of a 4-dimensional object by a 1 dimensional object.
Effectively, I have a 4D object of sizes (15,15,3,5).
I want to multiply out the 4th dimension by using a 5x1 vector, collapsing the last dimension to 1. Then I want to use squeeze to get a (15,15,3) sized object, again multiplying it by a 3x1 vector, leaving me with a 15x15 matrix.
I can do this in a loop, but that is quite costly. Can anyone give me suggestions how to do this without a loop?
For now the loop:
expectationCalc = reshape(mValueFunction(age+1, :, :, :, :), nGridAssets, nGridHumanCapital, nNetInterestRate, nShockstoHumanCapital);
for i = 1:nGridAssets
for j = 1:nGridHumanCapital
expectation(i,j) = mTransitionNetInterestRate(nNetIntRate, :)*(squeeze(expectationCalc(i,j,:,:))*mTransitionShockHumanCapital(ShockHcapital, :)');
end
end

If you reshape your 4D matrix to a 2D matrix, where the 2nd dimension is the one you want to reduce by dot product, and the 1st dimension contains all other dimensions, then you can apply a regular matrix multiplication. The result can then be reshaped to the original size (minus one dimension):
% Input data
M = randn(15,15,3,5);
v1 = randn(5,1);
v2 = randn(3,1);
% 1st multiplication
sz = size(M);
M = reshape(M,[],sz(end));
M = M * v1;
sz(end) = []; % We no longer have that last dimension
M = reshape(M,sz);
% 2nd multiplication
M = reshape(M,[],sz(end));
M = M * v2;
sz(end) = []; % We no longer have that last dimension
M = reshape(M,sz);

Related

MATLAB: Multiply 2D matrix with 3D matrix within cell arrays

I have a constant 2D double matrix mat1. I also have a 2D cell array mat2 where every cell contains a 2D or 3D double matrix. These double matrices have the same number of rows and columns as mat1. I need to dot multiply (.*) mat1 with every slice of each double matrix within mat2. The result needs to be another cell array results with the same size as mat2, whereby the contatining double matrices must equal the double matrices of mat2 in terms of size.
Here's my code to generate mat1 and mat2 for illustrating purposes. I am struggling at the point where the multiplication should take place.
rowCells = 5;
colCells = 3;
rowTimeSeries = 300;
colTimeSeries = 5;
slices = [1;10];
% Create 2D double matrix
mat1 = rand(rowTimeSeries, colTimeSeries);
% Create 2D cell matrix comprisiong 2D and/or 3D double matrices
mat2 = cell(rowCells,colCells);
for c = 1:colCells
for r = 1:rowCells
slice = randsample(slices, 1, true);
mat2{r,c} = rand(rowTimeSeries, colTimeSeries, slice);
end
end
% Multiply (.*) mat1 with mat2 (every slice)
results = cell(rowCells,colCells);
for c = 1:colCells
for r = 1:rowCells
results{r,c} = ... % I am struggling here!!!
end
end
You could use bsxfun to remove the need for your custom function multiply2D3D, it works in a similar way! Updated code:
results = cell(rowCells,colCells);
for c = 1:colCells
for r = 1:rowCells
results{r,c} = bsxfun(#times, mat1, mat2{r,c});
end
end
This will work for 2D and 3D matrices where the number of rows and cols is the same in each of your "slices", so it should work in your case.
You also don't need to loop over the rows and the columns of your cell array separately. This loop has the same number of iterations, but it is one loop not two, so the code is a little more streamlined:
results = cell(size(mat2));
for n = 1:numel(mat2) % Loop over every element of mat2. numel(mat2) = rowCells*colCells
results{n} = bsxfun(#times, mat1, mat2{n});
end
I had almost the exact same answer as Wolfie but he beat me to it.
Anyway, here is a one liner that I think is slightly nicer:
nR = rowCells; % Number of Rows
nC = colCells; % Number of Cols
results = arrayfun(#(I) bsxfun(#times, mat1, mat2{I}), reshape(1:nR*nC,[],nC), 'un',0);
This uses arrayfun to perform the loop indexing and bsxfun for the multiplications.
A few advantages
1) Specifying 'UniformOutput' ('un') in arrayfun returns a cell array so the results variable is also a cell array and doesn't need to be initialised (in contrast to using loops).
2) The dimensions of the indexes determine the dimensions of results at the output, so they can match what you like.
3) The single line can be used directly as an input argument to a function.
Disadvantage
1) Can run slower than using for loops as Wolfie pointed out in the comments.
One solution I came up with is to outsource the multiplication of a 2D with a 3D matrix into a function. However, I am curious to know whether this is the most efficient way to solve this problem?
rowCells = 5;
colCells = 3;
rowTimeSeries = 300;
colTimeSeries = 5;
slices = [1;10];
% Create 2D double matrix
mat1 = rand(rowTimeSeries, colTimeSeries);
% Create 2D cell matrix comprisiong 2D and/or 3D double matrices
mat2 = cell(rowCells,colCells);
for c = 1:colCells
for r = 1:rowCells
slice = randsample(slices, 1, true);
mat2{r,c} = rand(rowTimeSeries, colTimeSeries, slice);
end
end
% Multiply (.*) mat1 with mat2 (every slice)
results = cell(rowCells,colCells);
for c = 1:colCells
for r = 1:rowCells
results{r,c} = multiply2D3D(mat1, mat2{r,c});
end
end
function vout = multiply2D3D(mat2D, mat3D)
%MULTIPLY2D3D multiplies a 2D double matrix with every slice of a 3D
% double matrix.
%
% INPUTs:
% mat2D:
% 2D double matrix
%
% mat3D:
% 3D double matrix where the third dimension is equal or greater than 1.
%
% OUTPUT:
% vout:
% 3D double matrix with the same size as mat3D. Every slice in vout
% is the result of a multiplication of mat2D with every individual slice
% of mat3D.
[rows, cols, slices] = size(mat3D);
vout = zeros(rows, cols, slices);
for s = 1 : slices
vout(:,:,s) = mat2D .* mat3D(:,:,s);
end
end

How can all dimensions left after the specified one be preserved, without explicitly listing them?

Or equivalently, "what is the equivalent of NumPy's ellipsis indexing in Matlab"
Say I have some high-dimensional array:
x = zeros(3, 4, 5, 6);
I want to write a function that takes an array of size (3, ...), and does some computation. In NumPy, I could write this:
def fun(x):
return x[0]*x[1] + x[2]
However, the equivalent in MATLAB doesn't work, because indexing with one integer flattens the array to 1d
function y = fun_bad(x)
y = x(1)*x(2) + x(3)
I can make this work for up to 3-dimensional arrays with
function y = fun_ok3d(x)
y = x(1,:,:)*x(2,:,:) + x(3,:,:)
If I want this to work for up to 10-dimensional arrays, I can write
function y = fun_ok10d(x)
y = x(1,:,:,:,:,:,:,:,:,:)*x(2,:,:,:,:,:,:,:,:,:) + x(3,:,:,:,:,:,:,:,:,:)
How can I avoid writing stupid numbers of colons here, and just make this work for any dimension? Is there some x(1,...) syntax that implies this?
NumPy can use the ... (Ellipsis) literal in an indexing expression to mean ": as many times as needed", which would solve this problem.
Approach 1: using a comma-separated list with ':'
I don't know a way to specify
: as many times as needed
while preserving shape. But you can specify
: an arbitrary number of times
where that number of times is defined at run-time. With this method you can preserve shape, provided that the number of indices coincides with the number of dimensions.
This is done using a comma-separated list generated from a cell array, and exploiting the fact that the string ':' can be used as an index instead of ::
function y = fun(x)
colons = repmat({':'}, 1, ndims(x)-1); % row cell array containing the string ':'
% repeated the required number of times
y = x(1,colons{:}).*x(2,colons{:}) + x(3,colons{:});
This approach can be easily generalized to indexing along any dimension, not just the first:
function y = fun(x, dim)
% Input argument dim is the dimension along which to index
colons_pre = repmat({':'}, 1, dim-1);
colons_post = repmat({':'}, 1, ndims(x)-dim);
y = x(colons_pre{:}, 1, colons_post{:}) ...
.*x(colons_pre{:}, 2, colons_post{:}) ...
+ x(colons_pre{:}, 3, colons_post{:});
Approach 2: splitting the array
You can split the array along the first dimension using num2cell, and then apply the operation to the resulting subarrays. Of course this uses more memory; and as noted by #Adriaan it is slower.
function y = fun(x)
xs = num2cell(x, [2:ndims(x)]); % x split along the first dimension
y = xs{1}.*xs{2} + xs{3};
Or, for indexing along any dimension:
function y = fun(x, dim)
xs = num2cell(x, [1:dim-1 dim+1:ndims(x)]); % x split along dimension dim
y = xs{1}.*xs{2} + xs{3};
MATLAB flattens all trailing dimensions when using a single colon, so you can use that to get from your N-D array to a 2D array, which you can reshape back into the original N dimensions after the calculation.
Along the first dimension
If you want to use the first dimension you can use a relatively simple and short piece of code:
function y = MyMultiDimensional(x)
x_size = size(x); % Get input size
yflat = x(1,:) .* x(2,:) + x(3,:); % Calculate "flattened" 2D function
y = reshape(yflat, [1 x_size(2:end)]); % Reshape output back to original size
end
Along an arbitrary dimension, now featuring N-D permute.
When you want your function to act along the n-th dimension out of a total of N, you can permute that dimension to the front first:
function y = MyMultiDimensional(x,n)
x_size = size(x); % Get input size
Order = 1:numel(x_size);
Order(n)=[]; % Remove n-th dimension
Order2 = [n, Order]; % Prepend n-th dimension
xPermuted = permute(x,Order2); % permute the n-th dimension to the front
yTmp = xPermuted (1,:) .* xPermuted (2,:) + xPermuted (3,:); % Calculate "flattened" 2D function
y = reshape(yTmp, x_size(Order)); % Reshape output back to original size
end
I timed the results of the two methods of Luis' and my methods:
function timeMultiDim()
x = rand(1e1,1e1,1e1,1e1,1e1,1e1,1e1,1e1);
function y = Luis1(x)
colons = repmat({':'}, 1, ndims(x)-1); % row cell array containing the string ':'
% repeated the required number of times
y = x(1,colons{:}).*x(2,colons{:}) + x(3,colons{:});
end
function y = Luis2(x)
xs = num2cell(x, [2:ndims(x)]); % x split along the first dimension
y = xs{1}.*xs{2} + xs{3};
end
function y = Adriaan(x)
x_size = size(x); % Get input size
yflat = x(1,:) .* x(2,:) + x(3,:); % Calculate "flattened" 2D function
y = reshape(yflat, [1 x_size(2:end)]); % Reshape output back to original size
end
n=1;
function y = Adriaan2(x,n)
x_size = size(x); % Get input size
Order = 1:numel(x_size);
Order(n)=[]; % Remove n-th dimension
Order2 = [n, Order]; % Prepend n-th dimension
xPermuted = permute(x,Order2); % permute the n-th dimension to the front
yTmp = xPermuted (1,:) .* xPermuted (2,:) + xPermuted (3,:); % Calculate "flattened" 2D function
y = reshape(yTmp, x_size(Order)); % Reshape output back to original size
end
t1 = timeit(#() Luis1(x));
t2 = timeit(#() Luis2(x));
t3 = timeit(#() Adriaan(x));
t4 = timeit(#() Adriaan2(x,n));
format long g;
fprintf('Luis 1: %f seconds\n', t1);
fprintf('Luis 2: %f seconds\n', t2);
fprintf('Adriaan 1: %f seconds\n', t3);
fprintf('Adriaan 2: %f seconds\n', t4);
end
Luis 1: 0.698139 seconds
Luis 2: 4.082378 seconds
Adriaan 1: 0.696034 seconds
Adriaan 2: 0.691597 seconds
So, going to a cell is bad, it takes more than 5 times as long, reshape and ':' are barely apart, so that'd come down to preference.

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.

Matlab d-dimensional multipication table?

I'm new to Matlab and I'm trying to solve a problem that involves creating a d dimensional multiplication table where each edge goes from 1 to n. The problem statement says that inputting d = 0 should return the number 1 and d = 1 should return a column vector with the elements 1 to n.
Ideally, I would just create a matrix of 1 to n along d dimensions and then iterate through for each element setting it equal to the product of the indices, but I don't know how to create the d dimensional matrix.
Can anyone help me with this problem?
You can create the table with repeated use of bsxfun. At each iteration, the vector 1,2,...,n is shifted to a new dimension and multiplied (with singleton expansion) by the previous result.
%// Data
d = 3;
n = 10;
%// Computations
vector = (1:n).'; %// first dimension: column vector
result = 1; %// initialization
for n = 1:d
result = bsxfun(#times, result, vector); %// new dimension
vector = shiftdim(vector,-1); %// shift to the next dimension
end

Matlab - Generating random coordinates for a matrix

I need to create a list (of size n) of random, non-repeating set of coordinates on a matrix of predefined size.
Is there a fast way to generate this in Matlab?
My initial idea was to create a list of size n with permutations the size of (width x length) and to translate them back to Row and Col values, but it seems to me too much.
Thanks,
Guy
You can use randperm to generate a linear index, and convert it to [row,col] if needed using ind2sub.
x = rand(7,9);
n = 20;
ndx = randperm(numel(x), n);
[row,col] = ind2sub(size(x), ndx);
As long as n is less than the number of elements in the matrix this is simple:
% A is the matrix to be sampled
% N is the number of coordinate pairs you want
numInMat = numel(A);
% sample from 1:N without replacement
ind = randperm(numInMat, N);
% convert ind to Row,Col pairs
[r, c] = ind2sub( size(A), ind )
Your idea is a good one, although you don't even have to convert your linear indices back to row and col indices, you can do linear indexing directly into a 2D array.
idx = randperm(prod(size(data)))
where data is your matrix. This will generate a vector of random integers between 1 and prod(size(data)), i.e. one index for each element.
e.g.
n = 3;
data = magic(n);
idx = randperm(prod(size(data)));
reshape(data(idx), size(data)) %this gives you your randomly indexed data matrix back