Creating a weight adjacency matrix - matlab

I need to assign weights to edges of a graph, from the following papers:
"Fast linear iterations for distributed averaging" by L. Xiao and S. Boyd
"Convex Optimization of Graph Laplacian Eigenvalues" by S. Boyd
I have the adjacency matrix for my graph (a 50 by 50 matrix), with 512 non-zero values.
I also have a 256 by 1 vector with the optimal weights.
For the software I'm using, I need a 50 by 50 matrix with the weight of edge (i,j) in the relevant position of the adjacency matrix (and with the opposite sign for edge (j,i)).
My attempt is below, but I can't get it working.
function weights = construct_weight_mtx(weight_list, Adj)
weights = zeros(size(Adj));
positions = find(Adj);
for i=1:length(positions)/2
if Adj(i) == 1
weights(i) = weight_list(i);
end
end
weights = weights - weights';
find(Adj) == find(weights);
end

You're finding the nonzero positions in the original adjacency matrix, but you're finding all of them. To get around this, you then take only the first half of those positions.
for i=1:length(positions)/2 ...
Unfortunately, this takes the indices from complete columns rather than just the positions below the diagonal. So if your matrix was all 1's, you'd be taking:
1 1 1 0 0 ...
1 1 1 0 0 ...
1 1 1 0 0 ...
...
instead of:
1 0 0 0 0 ...
1 1 0 0 0 ...
1 1 1 0 0 ...
...
To take the correct values, we just take the lower triangular portion of Adj and then find the nonzero positions of that:
positions = find(tril(Adj));
Now we have only the 256 positions below the diagonal and we can loop over all of the positions. Next, we need to fix the assignment in the loop:
for i=1:length(positions)
if Adj(i) == 1 %// we already know Adj(i) == 1 for all indices in positions
weights(i) = weight_list(i); %// we need to update weights(positions(i))
end
end
So this becomes:
for i=1:length(positions)
weights(positions(i)) = weight_list(i);
end
But if all we're doing is assigning 256 values to 256 positions, we can do that without a for loop:
weights(position) = weight_list;
Note that the elements of weight_list must be in the proper order with the nonzero elements of the lower-triangular portion ordered by columns.
Completed code:
function weights = construct_weight_mtx(weight_list, Adj)
weights = zeros(size(Adj));
positions = find(tril(Adj));
weights(positions) = weight_list;
weights = weights - weights.'; %// ' is complex conjugate; not a big deal here, but something to know
find(Adj) == find(weights); %// Not sure what this is meant to do; maybe an assert?
end

Related

Finding equal rows in Matlab

I have a matrix suppX in Matlab with size GxN and a matrix A with size MxN. I would like your help to construct a matrix Xresponse with size GxM with Xresponse(g,m)=1 if the row A(m,:) is equal to the row suppX(g,:) and zero otherwise.
Let me explain better with an example.
suppX=[1 2 3 4;
5 6 7 8;
9 10 11 12]; %GxN
A=[1 2 3 4;
1 2 3 4;
9 10 11 12;
1 2 3 4]; %MxN
Xresponse=[1 1 0 1;
0 0 0 0;
0 0 1 0]; %GxM
I have written a code that does what I want.
Xresponsemy=zeros(size(suppX,1), size(A,1));
for x=1:size(suppX,1)
Xresponsemy(x,:)=ismember(A, suppX(x,:), 'rows').';
end
My code uses a loop. I would like to avoid this because in my real case this piece of code is part of another big loop. Do you have suggestions without looping?
One way to do this would be to treat each matrix as vectors in N dimensional space and you can find the L2 norm (or the Euclidean distance) of each vector. After, check if the distance is 0. If it is, then you have a match. Specifically, you can create a matrix such that element (i,j) in this matrix calculates the distance between row i in one matrix to row j in the other matrix.
You can treat your problem by modifying the distance matrix that results from this problem such that 1 means the two vectors completely similar and 0 otherwise.
This post should be of interest: Efficiently compute pairwise squared Euclidean distance in Matlab.
I would specifically look at the answer by Shai Bagon that uses matrix multiplication and broadcasting. You would then modify it so that you find distances that would be equal to 0:
nA = sum(A.^2, 2); % norm of A's elements
nB = sum(suppX.^2, 2); % norm of B's elements
Xresponse = bsxfun(#plus, nB, nA.') - 2 * suppX * A.';
Xresponse = Xresponse == 0;
We get:
Xresponse =
3×4 logical array
1 1 0 1
0 0 0 0
0 0 1 0
Note on floating-point efficiency
Because you are using ismember in your implementation, it's implicit to me that you expect all values to be integer. In this case, you can very much compare directly with the zero distance without loss of accuracy. If you intend to move to floating-point, you should always compare with some small threshold instead of 0, like Xresponse = Xresponse <= 1e-10; or something to that effect. I don't believe that is needed for your scenario.
Here's an alternative to #rayryeng's answer: reduce each row of the two matrices to a unique identifier using the third output of unique with the 'rows' input flag, and then compare the identifiers with singleton expansion (broadcast) using bsxfun:
[~, ~, w] = unique([A; suppX], 'rows');
Xresponse = bsxfun(#eq, w(1:size(A,1)).', w(size(A,1)+1:end));

MATLAB manual error?

I read interesting article about correct memory usage in MATLAB. Here it is: Link at official website
And here I see example:
If your data contains many zeros, consider using sparse arrays, which
store only nonzero elements. The following example compares the space
required for storage of an array of mainly zeros:
A = diag(1e3,1e3); % Full matrix with ones on the diagonal
As = sparse(A) % Sparse matrix with only nonzero elements
I tried to implement it in my code and find interesting moment:
A = diag(1e3,1e3) does not create matrix with ones on the diagonal! It creates matrix of zeros with only one nonzero element:
clear A
A = diag(1e3,1e3);
find(A);
ans =
1001001
A(1001001)
ans =
1000
Ok. I read about diag function in help and see this:
D = diag(v) returns a square diagonal matrix with the elements of
vector v on the main diagonal.
Ok! So it really doesn't create diagonal matrix if v consist of 1 element! Is it mistake at help?
BUT. One more question: why it works this way?
diag(5,5)
ans =
0 0 0 0 0 5
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
I expect to get matrix 5x5 with 5 value at (1,1) or (5,5). Why it creates 6x6 matrix and why 5 is a (1,6) element?
Some time ago they fix documentation:
Manual: diag
you are using the 2nd overloaded version of diag:
D = diag(v,k) places the elements of vector v on the kth diagonal. k=0 represents the main diagonal, k>0 is above the main diagonal, and k<0 is below the main diagonal.
So your command A = diag(5,5) will construct a matrix where the diagonal elements of 5th diagonal above the main diagonal will be equal to the vector [5]. Thus the resulting value where only A(1,6) has a value.
If you want to have a 1e3x1e3 Matrix with ones on the diagonal try
A = diag(ones(1,1e3));
The article is incorrect.
A = diag(1e3,1e3);
does not produce a matrix with ones on the diagonal. The code should instead read:
A = eye(1e3,1e3);
Now reading your question again, I understood it really and have to rewrite my answer. You are refering to this part of the documentation:
A = diag(1e3,1e3); % Full matrix with ones on the diagonal
As = sparse(A) % Sparse matrix with only nonzero elements
whos
Name Size Bytes Class
A 1001x1001 8016008 double array
As 1001x1001 4020 double array (sparse)
That example is definitely wrong, probably it should be:
A=eye(1e3,1e3)
As=sparse(A);
Which creates a 1000x1000 matrix with ones on the main diagonal.
The bug is reported to mathworks

Hash function for binary matrices (Matlab)

I am working with big binary 2D matrices that are stored in a vector and every time a new matrix is obtained it is added to this vector, that can reach sizes of about 500 or 1000 elements. What I ask is if there is a more efficient way to store this matrices, maybe with a hash function. When there is a coincidence of two elements in the vector what I need is their position in the vector, not the matrix itself. I am working with Matlab.
this is executed after a new matrix is obtained:
states = [states new_state];
for i = 1:size(states,3)-1
if isequal(states(:,:,end), states(:,:,i))
found = 1;
num = size(states,3) - i;
break
end
end
matrices are binary:
new_state = [1 0 0 0; 0 0 0 1; 1 1 0 1; 1 1 0 0];

Matlab 2-D density plot

I am trying to do a density plot for a data containing two columns with different ranges. The RMSD column is [0-2] and Angle is [0-200] ranges.
My data in the file is like this:
0.0225370 37.088
0.1049553 35.309
0.0710002 33.993
0.0866880 34.708
0.0912664 33.011
0.0932054 33.191
0.1083590 37.276
0.1104145 34.882
0.1027977 34.341
0.0896688 35.991
0.1047578 36.457
0.1215936 38.914
0.1105484 35.051
0.0974138 35.533
0.1390955 33.601
0.1333878 32.133
0.0933365 35.714
0.1200465 33.038
0.1155794 33.694
0.1125247 34.522
0.1181806 37.890
0.1291700 38.871
I want both x and y axis to be binned 1/10th of the range
The 0 of both the axis to be starting in the same
Print the number of elements in each grid of the matrix like this and make a density plot based on these number of elements
0 0.1 0.2 (RMSD)
0 0 1 3
20 2 0 4
40 1 0 5
60 0 0 2
(Angle)
I can find ways to do 1-D binning but then I am stumped about how to make a density plot from those values and havent even dared to attempt2-D binning + plotting.
Thanks for the help
I think you want hist3. Assuming you want to specifty bin edges (not bin centers), use
result = hist3(data, 'Edges', {[0 .1 .2], [0 20 40 60]}).';
where data denotes your data.
From the linked documentation:
hist3(X,'Edges',edges), where edges is a two-element cell array of numeric vectors with monotonically non-decreasing values, uses a 2-D grid of bins with edges at edges{1} in the first dimension and at edges{2} in the second. The (i,j)th bin includes the value X(k,:) if
edges{1}(i) <= X(k,1) < edges{1}(i+1)
edges{2}(j) <= X(k,2) < edges{2}(j+1)
With your example data this gives
result =
0 0 0
8 14 0
0 0 0
0 0 0
For those who don't have Statistics and Machine Learning Toolbox to run bivariate histogram (hist3), it may be more practical using an alternative to solve 2-D hist problem. The following function generates the same output
function N = hist3_alt(x,y,edgesX,edgesY)
N = zeros(length(edgesY)-1,length(edgesX)-1);
[~,~,binX] = histcounts(x,edgesX);
for ii=1:numel(edgesX)-1
N(:,ii) = (histcounts(y(binX==ii),edgesY))';
end
It's simple and efficient. Then you could run the function like this:
N = hist3_alt(x,y,[0:0.1:2],[0:20:200])

How to make a general case of inserting ones in any type of matrix, in the non-principal diagonal

The title might be confusing, here's a particular example to explain myself. Also, I'm not sure how do you call the diagonal that starts in (1,2) and goes onward: (2,3) ; (3,4) and so on. Non-principal, non-main diagonal, not sure at all.
3x3 case
-1 1 0
-1 0 1
0 -1 1
4x4 case
-1 1 0 0
-1 0 1 0
-1 0 0 1
0 -1 1 0
0 -1 0 1
0 0 -1 1
So if the original matrix was a 4x4 (or any other size), I am able to make a matrix the size of the second example. I now have to insert the -1 and 1's in this fashion. This means n-1 number of -1's inserted if j=1, and then, a n-1 number of ones in the non-principal diagonal. When this is done, it's the same but for j=2 and the next non-principal diagonal, and so on.
Thing is, I'm thinking all the time about loops, and too many cases arise, because what I want is to be able to do this for any possible dimension, not for a particular case.
But then I saw this post Obtaining opposite diagonal of a matrix in Matlab
With this answer: A(s:s-1:end-1)
And it seems like a much cleaner way of doing it, since my own way (not finished since I'm not able to figure all the cases) has too many conditions. With a sentence like that, I could choose the diagonal, insert ones, and do it as many times as required, depending of the n dimension.
This leaves the problem of inserting the -1's, but I guess I could manage something.
It seems to mee that you want to obtain the following matrix B of size n × (n-1)*n/2
n = 4;
idx = fliplr(fullfact([n n]));
idx(diff(idx')<=0,:) = [];
m = size(idx,1);
B = zeros(m,n);
B(sub2ind(size(B),1:m,idx(:,1)')) = -1;
B(sub2ind(size(B),1:m,idx(:,2)')) = 1;
Approach #1
Here's a vectorized approach that has more memory requirements than a non-vectorized or for-loop based one. So, it could be tried out for small to medium sized datasizes.
The basic idea is this. For n=4 as an example, we take
-1 1 0 0
-1 0 1 0
-1 0 0 1
as the basic building block, replicate it n-1 i.e. 3 times and then remove the rows that aren't supposed to be part of the final output as per the requirements of the problem. Because of this very nature, this solution has more memory requirements, as we need to remove rows 6,8,9 for n = 4 case. But this gives us the opportunity to work with everything in one go.
N = n-1; %// minus 1 of the datasize, n
blksz = N*(N+1); %// number of elements in a (n-1)*n blocksize that is replicated
b1 = [-1*ones(N,1) eye(N)] %// Create that special starting (n-1)*n block
idx1 = find(b1~=0) %// find non zero elements for the starting block
idx2 = bsxfun(#plus,idx1,[0:N-1]*(blksz+N)) %// non zero elements for all blocks
b1nzr = repmat(b1(b1~=0),[1 N]) %// elements for all blocks
vald_ind = bsxfun(#le,idx2,[1:N]*blksz) %// positions of valid elements all blocks
mat1 = zeros(N,blksz) %// create an array for all blocks
mat1(idx2(vald_ind)) = b1nzr(vald_ind) %// put right elements into right places
%// reshape into a 3D array, join/concatenate along dim3
out = reshape(permute(reshape(mat1,N,N+1,[]),[1 3 2]),N*N,[])
%// remove rows that are not entertained according to the requirements of problem
out = out(any(out==1,2),:)
Approach #2
Here's a loop based code that could be easier to get a hold on if you have to explain it to yourself or just people and most importantly scales up pretty well on performance criteria across varying datasizes.
start_block = [-1*ones(n-1,1) eye(n-1)] %// Create that special starting (n-1)*n block
%// Find starting and ending row indices for each shifted block to be repeated
ends = cumsum([n-1:-1:1])
starts = [1 ends(1:end-1)+1]
out = zeros(sum(1:n-1),n) %// setup all zeros array to store output
for k1 = 1:n-1
%// Put elements from shifted portion of start_block for creating the output
out(starts(k1):ends(k1),k1:end) = start_block(1:n-k1,1:n-k1+1)
end
With n=4, the output -
out =
-1 1 0 0
-1 0 1 0
-1 0 0 1
0 -1 1 0
0 -1 0 1
0 0 -1 1
I don't know if I understood properly, but is this what you are looking for:
M=rand(5);
k=1; % this is to select the k-th diagonal
D=diag(ones(1,size(M,2)-abs(k)), k);
M(D==1)=-1;
M =
0.9834 -1.0000 0.8402 0.6310 0.0128
0.8963 0.1271 -1.0000 0.3164 0.6054
0.8657 0.6546 0.3788 -1.0000 0.5765
0.8010 0.8640 0.2682 0.4987 -1.0000
0.5550 0.2746 0.1529 0.7386 0.6550