29 views (last 30 days)

Show older comments

I am trying to change a column vector

p=[1;3;5]

into a square matrix P=[1,3;3,5]

Howevery I only manage to change the column vector p into a square matrix with element [1,0;3,5]

with the following code

p=[1;3;5]

n=2

P = zeros(n);

P(triu(ones(n)) == 1) = p;

P

John D'Errico
on 16 May 2020

Edited: John D'Errico
on 16 May 2020

This is something that I've sometimes been surprised that is not already a function in MATLAB, thus, to create a symmetric matrix from only the list of the unique triangle of elements. And there will be various ways to solve the problem. Even a carefully constructed double loop would suffice, though that would not appeal to my purist nature. (You can also do it using a single loop, with careful code.)

For example, one could do this as:

function M = buildSymmetric(V,n)

% uses nested loops to create an nxn symmetric matrix from the lower triangle elements as a list

if length(V) ~= (n*(n+1)/2))

error('V is not of a length consistent with n')

end

M = zeros(n,n); % Note the importance of preallocating M

ind = 0;

for cind = 1:n

for rind = cind:n

ind = ind + 1;

M(rind,cind) = V(ind);

if rind > cind

M(cind,rind) = V(ind);

end

end

end

end

Did it work? Of course.

V = 1:10; % this is just as an example of a 10 element vector for a test case

P = buildSymmetric(V,4)

P =

1 2 3 4

2 5 6 7

3 6 8 9

4 7 9 10

It is reasonably efficient, since the MATLAB parser does make loops reasonably efficient these days. Personally, I'd prefer a non-looped solution. The next, classic solution is to create the LOWER trianglular matrix, as you already know how to do (though your code created the upper triangle. The difference is irrelevant.) In fact, I might not be totally surprised if you may be effectively using code I wrote in some answer long, long ago, from a galaxy far, far away.

function M = buildSymmetric(V,n)

% create an nxn symmetric matrix from the lower triangle elements as a list

if numel(V) ~= n*(n+1)/2

error('V is not of a length consistent with n')

end

M = zeros(n,n);

% stuff the lower triangle elements

M(tril(ones(n)) == 1) = V;

% symmetrize the matrix, by adding it to its transpose, then reduce the diagonal

M = M + M.' - diag(diag(M));

end

That final line of code just adds the triangular matrix to its NON-conjugate transpose, which essentially doubles the diagonal elements. Then it subtracts off those diagonal elements to return them to what they should have been.

buildSymmetric(1:6,3)

ans =

1 2 3

2 4 5

3 5 6

buildSymmetric([1 3 5],2)

ans =

1 3

3 5

Honestly, I don't terribly love that solution. Why not? Party because it forces MATLAB to perform a spurious set of n^2 additions, most of which are an add between a number and zero. Those adds don't bother me too much, as they are just CPU cycles, while superfluous, they are not a true bug. However, as well, it essentially multiplies the diagonal elements by 2, and then subtracts off the diagonal element. I could also have divided those elements by 2.

Why is that a bug?

If one of those diagonal elements was really large, doubling the number could cause an overflow, resulting in an inf. I would see that as a bug. It would be a bug that was extremely rare to trip over, but still a significant bug. And I don't like code that will one day fail, even if that is only a rare event.

realmax

ans =

1.7977e+308

buildSymmetric([realmax,1,3],2)

ans =

Inf 1

1 3

So, while I imagine that answer might be acceptable, it is not something that makes me feel good in a MATLAB sense, at least as I think of it now. I can live with the adds to zeros, but a bug just bugs the heck out of me.

function M = buildSymmetric(V,n)

% create an nxn symmetric matrix from the lower triangle elements as a list

if numel(V) ~= n*(n+1)/2

error('V is not of a length consistent with n')

end

% extract the purely diagonal elements from the list

ind = 1 + [0,cumsum(n:-1:2)];

D = V(ind);

V(ind) = [];

M = zeros(n);

if n > 1

% stuff the strictly lower triangle elements

M(logical(tril(ones(n),-1))) = V;

% adds to get the upper triangle

M = M + M.';

end

% stuff the main diagonal without doubling those elements.

% This also just inserts the diagonal elements where they belong.

M(1 + (n+1)*(0:n-1)) = D;

end

This last version also works as we can see. It still used adds with zeros to get the upper triangle correct. It no longer doubles the diagonal elements though, so it is corrrect for all sizes of matrices, even if one of the diagonals would have otherwise caused an overflow.

>> buildSymmetric(2,1)

ans =

2

buildSymmetric([realmax,1,3],2)

ans =

1.7977e+308 1

1 3

>> buildSymmetric(1:6,3)

ans =

1 2 3

2 4 5

3 5 6

>> buildSymmetric(1:10,4)

ans =

1 2 3 4

2 5 6 7

3 6 8 9

4 7 9 10

So as you see, that version always works, creating a symmetric matrix from the list of triangular elements as a vector, even if some of those numbers would have cause an overflow otherwise.

Even more professionally written code yet would not need the number n as an argument, but would be able to extract that from the length of V. And one of the most important feature of professionally written code would be good help built into the help block. The help I've written here is just my personal style to write help, but it should be sufficient.

function M = buildSymmetric(V)

% buildSymmetric: nxn symmetric matrix from the list of lower triangle elements

%

% arguments: (input)

% V - a vector of elements from a triangle of a matrix,

% used to then create the corresponding symmetric matrix.

%

% numel(V) MUST be a triangle number, thus a number that can

% be written as n*(n+1)/2

%

% arguments: (output)

% M - a symmetric matrix of size nxn, containing the elements

% of V in proper order.

%

% Examples:

% buildSymmetric([11 12 22])

% ans =

% 11 12

% 12 22

%

% buildSymmetric([11 12 13 14 22 23 24 33 34 44])

% ans =

% 11 12 13 14

% 12 22 23 24

% 13 23 33 34

% 14 24 34 44

%

% Author: John D'Errico

% Date: 5/16/2020

nV = numel(V);

n = round(sqrt(2*nV + 1/4) - 1/2);

if numel(V) ~= n*(n+1)/2

error('numel(V) is not a triangle number, so cannot create a symmetric matrix')

end

% extract the purely diagonal elements from the list

ind = 1 + [0,cumsum(n:-1:2)];

D = V(ind);

V(ind) = [];

% Preallocate M

M = zeros(n);

% When n > 1, we have a matrix, as opposed to a scalar result,

% so we need to build the lower/upper triangles

if n > 1

% stuff the strictly lower triangle elements

M(logical(tril(ones(n),-1))) = V;

% adds to get the upper triangle (sigh - those superfluous adds, I know)

% I could probably write this next line differently with some thought invested.

M = M + M.';

end

% stuff the main diagonal without doubling those elements.

% This also just inserts the diagonal elements where they belong.

M(1 + (n+1)*(0:n-1)) = D;

end

Testing this final version, we see now all test cases work including a case where an overflow could have happened, and n does not need to provided, since that was easily extractable information. When the length of V was not the consistent in size with a triangular number, the code fails with an error of course, explaining why it failed.

>> buildSymmetric(1:5)

Error using buildSymmetric (line 7)

numel(V) is not a triangle number, so cannot create a symmetric matrix

>> buildSymmetric(5)

ans =

5

>> buildSymmetric([realmax,1,3])

ans =

1.7977e+308 1

1 3

>> buildSymmetric(1:6)

ans =

1 2 3

2 4 5

3 5 6

>> buildSymmetric([11 12 13 14 22 23 24 33 34 44])

ans =

11 12 13 14

12 22 23 24

13 23 33 34

14 24 34 44

This final code is longer. It would probably fail miserably in a Cody contest, because the code is the longest code written. But a good Cody score is not always a true measure of good code. :(

Perhaps most important is to understand why the code was written as it was in the end, and how I used MATLAB to accomplish the end goal in a reasonable way that lacks bugs, even when they might be really, really rare bugs.

John D'Errico
on 18 May 2020

Fabio Freschi
on 16 May 2020

Not as elegant as it could be but it works, especially for small matrices

% matrix dimension

n = 3;

% your entries (here dummy values)

p = rand(n*(n+1)/2,1);

% get the indices of the position into the matrix. I understand it is the triangular upper part

[ir,jc] = find(triu(ones(n)));

% create the upper triangular matrix

Pup = accumarray([ir,jc],p,[n,n]);

% now make the symmetric matrix

P = (Pup+Pup.').*.5*eye(n)

Mehmed Saad
on 16 May 2020

Here is one with for loop

function P = Using_for_loop(p,n)

P = zeros(n);

[I,J]=ind2sub(size(P),1:numel(P));

for ii =1:n

sz = length(P(I==ii & J>ii-1));

P(I==ii & J>ii-1) = p(1:sz);

P(J==ii & I>ii-1) = p(1:sz);

p(1:sz) = [];

end

end

Now call it

p = [11;12;13;14;22;23;24;33;34;44];

n = 4;

P = Using_for_loop(p,n)

P =

11 12 13 14

12 22 23 24

13 23 33 34

14 24 34 44

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!