Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extend a 2D-matrix to a 3D-matrix by multiplying with a vector [duplicate]

Here what I would like to achieve:

I have a matrix C

C=[1 2 3; 4 5 6; 7 8 9];

And a vector a

a=[1 2];

I would like to make such an operation, that each element of the a vector is multiplied with C ( scalar multiplication ) and out comes a 3-dimensional array D:

(:,:,1) =

     1     2     3
     4     5     6
     7     8     9


(:,:,2) =

     2     4     6
     8    10    12
    14    16    18

It would certainly work with a loop, but, since I'll need this operation on multiple occasions, a oneliner would be a great saver.

like image 723
Artem Moskalew Avatar asked Feb 22 '16 13:02

Artem Moskalew


5 Answers

This is a beautiful example for the use of bsxfun and reshape. While @thewaywewalks proposes first calling bsxfun and reshaping the result, I'd suggest the opposite. This makes one of the key concepts of bsxfun - the singleton dimension expansion - more clear:

out = bsxfun(@times,C,reshape(a,1,1,[]))

ans(:,:,1) =

     1     2     3
     4     5     6
     7     8     9


ans(:,:,2) =

     2     4     6
     8    10    12
    14    16    18

With reshape(a,1,1,[]), you make a be in the third dimension. If you now apply bsxfun, it will multiply the matrix C with each element of a.

like image 190
hbaderts Avatar answered Nov 02 '22 11:11

hbaderts


Some reshape'ing and some bsxfun will do:

out = reshape(bsxfun(@mtimes, C(:), a(:).'), [size(C),numel(a)] )

As suggested in hbaderts answer one could also use bsxfun's capability of dimension expansion, and provide a permuted vector of factors:

out = bsxfun(@mtimes,C,permute(a,[3,1,2]))

out(:,:,1) =

     1     2     3
     4     5     6
     7     8     9


out(:,:,2) =

     2     4     6
     8    10    12
    14    16    18
like image 44
Robert Seifert Avatar answered Nov 02 '22 11:11

Robert Seifert


I have another method for the benchmark compare... IMO it's the neatest way, at least for the syntax/readability term:

out = reshape(kron(a,C),[size(C),numel(a)]);

out(:,:,1) =

     1     2     3
     4     5     6
     7     8     9


out(:,:,2) =

     2     4     6
     8    10    12
    14    16    18
like image 6
Adiel Avatar answered Nov 02 '22 10:11

Adiel


Another possibility is to use matrix multiplication of C as a column vector times a as a row vector (this gives all element-wise products), and then reshape the result:

out = reshape(C(:)*a, size(C,1), size(C,2), numel(a));
like image 4
Luis Mendo Avatar answered Nov 02 '22 10:11

Luis Mendo


EDIT (BENCHMARKING): Since several solutions (including mine below) have been suggested, here is some rough benchmarking to compare the different solutions, using larger arrays:

a=1:10;
N=1000; timers=zeros(N,6);
for ii=1:N; C=rand(400);
  tic; out = repmat(C,[1,1,numel(a)]).*reshape(repelem(a,size(C,1),size(C,2)),[size(C),numel(a)]); timers(ii,1)=toc;
  tic; out = bsxfun(@times,C,reshape(a,1,1,[])); timers(ii,2)=toc;
  tic; out = reshape(C(:)*a, size(C,1), size(C,2), numel(a)); timers(ii,3)=toc;
  tic; out = bsxfun(@mtimes,C,permute(a,[3,1,2])); timers(ii,4)=toc;
  tic; out = reshape(bsxfun(@mtimes, C(:), a(:).'), [size(C),numel(a)] ); timers(ii,5)=toc; 
  tic; out = reshape(kron(a,C),[size(C),numel(a)]); timers(ii,6)=toc;
end;

mean(timers)

ans =

    0.0080863    0.0032406    0.0041718     0.015166    0.0074462    0.0033051

... suggesting that @hbaderts solution is fastest, then @Adiel's, then @Luis Mendo's, then @thewaywewalk's (1), then mine, then @thewaywewalk's (2).

My solution:

Another option, using repmat and reshape (no bsxfun):

out = repmat(C,[1,1,numel(a)]).*reshape(repelem(a,size(C,1),size(C,2)),[size(C),numel(a)])

out(:,:,1) =

 1     2     3
 4     5     6
 7     8     9

out(:,:,2) =

 2     4     6
 8    10    12
14    16    18

This is the element-wise multiplication of two arrays. The first is your original matrix C repeated numel(a) times in the third dimension:

repmat(C,[1,1,numel(a)])

ans(:,:,1) =

 1     2     3
 4     5     6
 7     8     9

ans(:,:,2) =

 1     2     3
 4     5     6
 7     8     9

The second is the same size as the first, with each slice containing the corresponding element of a:

reshape(repelem(a,size(C,1),size(C,2)),[size(C),numel(a)])

ans(:,:,1) =

 1     1     1
 1     1     1
 1     1     1

ans(:,:,2) =

 2     2     2
 2     2     2
 2     2     2
like image 4
Geoff Avatar answered Nov 02 '22 11:11

Geoff