Writing an adjacency matrix for a graph [duplicate] - matlab

Consider a set of points arranged on a grid of size N-by-M.
I am trying to build the adjacency matrix such that
neighboring points are connected.
For example, in a 3x3 grid with a graph:
1-2-3
| | |
4-5-6
| | |
7-8-9
We should have the corresponding adjacency matrix:
+---+------------------------------------------------------+
| | 1 2 3 4 5 6 7 8 9 |
+---+------------------------------------------------------+
| 1 | 0 1 0 1 0 0 0 0 0 |
| 2 | 1 0 1 0 1 0 0 0 0 |
| 3 | 0 1 0 0 0 1 0 0 0 |
| 4 | 1 0 0 0 1 0 1 0 0 |
| 5 | 0 1 0 1 0 1 0 1 0 |
| 6 | 0 0 1 0 1 0 0 0 1 |
| 7 | 0 0 0 1 0 0 0 1 0 |
| 8 | 0 0 0 0 1 0 1 0 1 |
| 9 | 0 0 0 0 0 1 0 1 0 |
+---+------------------------------------------------------+
As a bonus, the solution should work for both 4- and 8-connected neighboring points, that is:
o o o o
o X o vs. o X o
o o o o
This the code that I have so far:
N = 3; M = 3;
adj = zeros(N*M);
for i=1:N
for j=1:M
k = sub2ind([N M],i,j);
if i>1
ii=i-1; jj=j;
adj(k,sub2ind([N M],ii,jj)) = 1;
end
if i<N
ii=i+1; jj=j;
adj(k,sub2ind([N M],ii,jj)) = 1;
end
if j>1
ii=i; jj=j-1;
adj(k,sub2ind([N M],ii,jj)) = 1;
end
if j<M
ii=i; jj=j+1;
adj(k,sub2ind([N M],ii,jj)) = 1;
end
end
end
How can this improved to avoid all the looping?

If you notice, there is a distinct pattern to the adjacency matrices you are creating. Specifically, they are symmetric and banded. You can take advantage of this fact to easily create your matrices using the diag function (or the spdiags function if you want to make a sparse matrix). Here is how you can create the adjacency matrix for each case, using your sample matrix above as an example:
4-connected neighbors:
mat = [1 2 3; 4 5 6; 7 8 9]; % Sample matrix
[r, c] = size(mat); % Get the matrix size
diagVec1 = repmat([ones(c-1, 1); 0], r, 1); % Make the first diagonal vector
% (for horizontal connections)
diagVec1 = diagVec1(1:end-1); % Remove the last value
diagVec2 = ones(c*(r-1), 1); % Make the second diagonal vector
% (for vertical connections)
adj = diag(diagVec1, 1)+diag(diagVec2, c); % Add the diagonals to a zero matrix
adj = adj+adj.'; % Add the matrix to a transposed copy of
% itself to make it symmetric
And you'll get the following matrix:
adj =
0 1 0 1 0 0 0 0 0
1 0 1 0 1 0 0 0 0
0 1 0 0 0 1 0 0 0
1 0 0 0 1 0 1 0 0
0 1 0 1 0 1 0 1 0
0 0 1 0 1 0 0 0 1
0 0 0 1 0 0 0 1 0
0 0 0 0 1 0 1 0 1
0 0 0 0 0 1 0 1 0
8-connected neighbors:
mat = [1 2 3; 4 5 6; 7 8 9]; % Sample matrix
[r, c] = size(mat); % Get the matrix size
diagVec1 = repmat([ones(c-1, 1); 0], r, 1); % Make the first diagonal vector
% (for horizontal connections)
diagVec1 = diagVec1(1:end-1); % Remove the last value
diagVec2 = [0; diagVec1(1:(c*(r-1)))]; % Make the second diagonal vector
% (for anti-diagonal connections)
diagVec3 = ones(c*(r-1), 1); % Make the third diagonal vector
% (for vertical connections)
diagVec4 = diagVec2(2:end-1); % Make the fourth diagonal vector
% (for diagonal connections)
adj = diag(diagVec1, 1)+... % Add the diagonals to a zero matrix
diag(diagVec2, c-1)+...
diag(diagVec3, c)+...
diag(diagVec4, c+1);
adj = adj+adj.'; % Add the matrix to a transposed copy of
% itself to make it symmetric
And you'll get the following matrix:
adj =
0 1 0 1 1 0 0 0 0
1 0 1 1 1 1 0 0 0
0 1 0 0 1 1 0 0 0
1 1 0 0 1 0 1 1 0
1 1 1 1 0 1 1 1 1
0 1 1 0 1 0 0 1 1
0 0 0 1 1 0 0 1 0
0 0 0 1 1 1 1 0 1
0 0 0 0 1 1 0 1 0

Just for fun, here's a solution to construct the adjacency matrix by computing the distance between all pairs of points on the grid (not the most efficient way obviously)
N = 3; M = 3; %# grid size
CONNECTED = 8; %# 4-/8- connected points
%# which distance function
if CONNECTED == 4, distFunc = 'cityblock';
elseif CONNECTED == 8, distFunc = 'chebychev'; end
%# compute adjacency matrix
[X Y] = meshgrid(1:N,1:M);
X = X(:); Y = Y(:);
adj = squareform( pdist([X Y], distFunc) == 1 );
And here's some code to visualize the adjacency matrix and the graph of connected points:
%# plot adjacency matrix
subplot(121), spy(adj)
%# plot connected points on grid
[xx yy] = gplot(adj, [X Y]);
subplot(122), plot(xx, yy, 'ks-', 'MarkerFaceColor','r')
axis([0 N+1 0 M+1])
%# add labels
[X Y] = meshgrid(1:N,1:M);
X = reshape(X',[],1) + 0.1; Y = reshape(Y',[],1) + 0.1;
text(X, Y(end:-1:1), cellstr(num2str((1:N*M)')) )

I just found this question when searching for the same problem. However, none of the provided solutions worked for me because of the problem size which required the use of sparse matrix types. Here is my solution which works on large scale instances:
function W = getAdjacencyMatrix(I)
[m, n] = size(I);
I_size = m*n;
% 1-off diagonal elements
V = repmat([ones(m-1,1); 0],n, 1);
V = V(1:end-1); % remove last zero
% n-off diagonal elements
U = ones(m*(n-1), 1);
% get the upper triangular part of the matrix
W = sparse(1:(I_size-1), 2:I_size, V, I_size, I_size)...
+ sparse(1:(I_size-m),(m+1):I_size, U, I_size, I_size);
% finally make W symmetric
W = W + W';

Just came across this question. I have a nice working m-function (link: sparse_adj_matrix.m) that is quite general.
It can handle 4-connect grid (radius 1 according to L1 norm), 8-connect grid (radius 1 according to L_infty norm).
It can also support 3D (and arbitrarily higher domensional grids).
The function can also connect nodes further than radius = 1.
Here's the signiture of the function:
% Construct sparse adjacency matrix (provides ii and jj indices into the
% matrix)
%
% Usage:
% [ii jj] = sparse_adj_matrix(sz, r, p)
%
% inputs:
% sz - grid size (determine the number of variables n=prod(sz), and the
% geometry/dimensionality)
% r - the radius around each point for which edges are formed
% p - in what p-norm to measure the r-ball, can be 1,2 or 'inf'
%
% outputs
% ii, jj - linear indices into adjacency matrix (for each pair (m,n)
% there is also the pair (n,m))
%
% How to construct the adjacency matrix?
% >> A = sparse(ii, jj, ones(1,numel(ii)), prod(sz), prod(sz));
%
%
% Example:
% >> [ii jj] = sparse_adj_matrix([10 20], 1, inf);
% construct indices for 200x200 adjacency matrix for 8-connect graph over a
% grid of 10x20 nodes.
% To visualize the graph:
% >> [r c]=ndgrid(1:10,1:20);
% >> A = sparse(ii, jj, 1, 200, 200);;
% >> gplot(A, [r(:) c(:)]);

Your current code doesn't seem so bad. One way or another you need to iterate over all neighbor pairs. If you really need to optimize the code, I would suggest:
loop over node indices i, where 1 <= i <= (N*M)
don't use sub2ind() for efficiency, the neighbors of node i are simpy [i-M, i+1, i+M, i-1] in clockwise order
Notice that to get all neighbor pairs of nodes:
you only have to compute the "right" neighbors (i.e. horizontal edges) for nodes i % M != 0 (since Matlab isn't 0-based but 1-based)
you only have to compute "above" neighbors (i.e. vertical edges) for nodes i > M
there is a similar rule for diagonal edges
This would leed to a single loop (but same number of N*M iterations), doesn't call sub2ind(), and has only two if statements in the loop.

For each node in the graph add a connection to the right and one downwards. Check that you don't overreach your grid. Consider the following function that builds the adjacency matrix.
function adj = AdjMatrixLattice4( N, M )
% Size of adjacency matrix
MN = M*N;
adj = zeros(MN,MN);
% number nodes as such
% [1]---[2]-- .. --[M]
% | | |
% [M+1]-[M+2]- .. -[2*M]
% : : :
% [] [] .. [M*N]
for i=1:N
for j=1:N
A = M*(i-1)+j; %Node # for (i,j) node
if(j<N)
B = M*(i-1)+j+1; %Node # for node to the right
adj(A,B) = 1;
adj(B,A) = 1;
end
if(i<M)
B = M*i+j; %Node # for node below
adj(A,B) = 1;
adj(B,A) = 1;
end
end
end
end
Example as above AdjMatrixLattice4(3,3)=
0 1 0 1 0 0 0 0 0
1 0 1 0 1 0 0 0 0
0 1 0 0 0 1 0 0 0
1 0 0 0 1 0 1 0 0
0 1 0 1 0 1 0 1 0
0 0 1 0 1 0 0 0 1
0 0 0 1 0 0 0 1 0
0 0 0 0 1 0 1 0 1
0 0 0 0 0 1 0 1 0

Related

How to get all possible permutation matrices of a matrix

I need to create all possible permutation matrices for a matrix where every permutation matrix contains only one 1 in each column and each row, and 0 in all other places.
For example, below example in (1) is all possible permutation matrices for 2x2 matrix and in (2) is a all possible permutation matrices for 3x3 matrix and so on
So how can I get these matrices of a matrix NxN in MATLAB and store them into one three-dimensional matrix?
Here's my solution, using implicit expansion (tested with Octave 5.2.0 and MATLAB Online):
n = 3;
% Get all permutations of length n
p = perms(1:n);
% Number of permutations
n_p = size(p, 1);
% Set up indices, where to set elements to 1
p = p + (0:n:n^2-1) + (0:n^2:n^2*n_p-1).';
% Set up indices, where to set elements to 1 (for MATLAB R2016a and before)
%p = bsxfun(#plus, bsxfun(#plus, p, (0:n:n^2-1)), (0:n^2:n^2*n_p-1).');
% Initialize 3-dimensional matrix
a = zeros(n, n, n_p);
% Set proper elements to 1
a(p) = 1
The output for n = 3:
a =
ans(:,:,1) =
0 0 1
0 1 0
1 0 0
ans(:,:,2) =
0 1 0
0 0 1
1 0 0
ans(:,:,3) =
0 0 1
1 0 0
0 1 0
ans(:,:,4) =
0 1 0
1 0 0
0 0 1
ans(:,:,5) =
1 0 0
0 0 1
0 1 0
ans(:,:,6) =
1 0 0
0 1 0
0 0 1
Using repelem, perms and reshape:
n = 3; % matrix size
f = factorial(n); % number of permutation
rep = repelem(eye(n),1,1,f) % repeat n! time the diagonal matrix
res = reshape(rep(:,perms(1:n).'),n,n,f) % indexing and reshaping
Where res is:
res =
ans(:,:,1) =
0 0 1
0 1 0
1 0 0
ans(:,:,2) =
0 1 0
0 0 1
1 0 0
ans(:,:,3) =
0 0 1
1 0 0
0 1 0
ans(:,:,4) =
0 1 0
1 0 0
0 0 1
ans(:,:,5) =
1 0 0
0 0 1
0 1 0
ans(:,:,6) =
1 0 0
0 1 0
0 0 1
And according to your comment:
What I need to do is to multiply a matrix i.e Z with all possible
permutation matrices and choose that permutation matrix which
resulting a tr(Y) minimum; where Y is the results of multiplication of
Z with the permutation matrix. I Think I don't need to generate all
permutation matrices and store them in such variable, I can generate
them one by one and get the result of multiplication. Is that possible
?
You're trying to solve the assignment problem, you can use the well known hungarian algorithm to solve this task in polynomial time. No needs to generate a googleplex of permutation matrix.

How to permute elements of a vector by another vector to obtain a matrix of permutations

I want to obtain all the possible permutations of one vector elements by another vector elements. For example one vector is A=[0 0 0 0] and another is B=[1 1]. I want to replace the elements of A by B to obtain all the permutations in a matrix like this [1 1 0 0; 1 0 1 0; 1 0 0 1; 0 1 1 0; 0 1 0 1; 0 0 1 1]. The length of real A is big and I should be able to choose the length of B_max and to obtain all the permutations of A with B=[1], [1 1], [1 1 1],..., B_max.
Thanks a lot
Actually, since A and B are always defined, respectively, as a vector of zeros and a vector of ones, this computation is much easier than you may think. The only constraints you should respect concerns B, which shoud not be empty and it's elements cannot be greater than or equal to the number of elements in A... because after that threshold A will become a vector of ones and calculating its permutations will be just a waste of CPU cycles.
Here is the core function of the script, which undertakes the creation of the unique permutations of 0 and 1 given the target vector X:
function p = uperms(X)
n = numel(X);
k = sum(X);
c = nchoosek(1:n,k);
m = size(c,1);
p = zeros(m,n);
p(repmat((1-m:0)',1,k) + m*c) = 1;
end
And here is the full code:
clear();
clc();
% Define the main parameter: the number of elements in A...
A_len = 4;
% Compute the elements of B accordingly...
B_len = A_len - 1;
B_seq = 1:B_len;
% Compute the possible mixtures of A and B...
X = tril(ones(A_len));
X = X(B_seq,:);
% Compute the unique permutations...
p = [];
for i = B_seq
p = [p; uperms(X(i,:).')];
end
Output for A_len = 4:
p =
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
1 1 0 0
1 0 1 0
1 0 0 1
0 1 1 0
0 1 0 1
0 0 1 1
1 1 1 0
1 1 0 1
1 0 1 1
0 1 1 1

Iteratively and randomly adding ones to a binary vector in matlab

In each iteration I want to add 1 randomly to binary vector,
Let say
iteration = 1,
k = [0 0 0 0 0 0 0 0 0 0]
iteration = 2,
k = [0 0 0 0 1 0 0 0 0 0]
iteration = 3,
k = [0 0 1 0 0 0 0 1 0 0]
, that goes up to length(find(k)) = 5;
Am thinking of for loop but I don't have an idea how to start.
If it's important to have the intermediate vectors (those with 1, 2, ... 4 ones) as well as the final one, you can generate a random permutation and, in your example, use the first 5 indices one at a time:
n = 9; %// number of elements in vector
m = 5; %// max number of 1's in vector
k = zeros(1, n);
disp(k); %// output vector of all 0's
idx = randperm(n);
for p = 1:m
k(idx(p)) = 1;
disp(k);
end
Here's a sample run:
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1
1 0 0 0 0 0 0 0 1
1 0 0 1 0 0 0 0 1
1 0 0 1 1 0 0 0 1
1 1 0 1 1 0 0 0 1
I wouldn't even use a loop. I would generate a random permutation of indices that sample from a vector going from 1 up to the length of k without replacement then just set these locations to 1. randperm suits the task well:
N = 10; %// Length 10 vector
num_vals = 5; %// 5 values to populate
ind = randperm(N, num_vals); %// Generate a vector from 1 to N and sample num_vals values from this vector
k = zeros(1, N); %// Initialize output vector k to zero
k(ind) = 1; %// Set the right values to 1
Here are some sample runs when I run this code a few times:
k =
0 0 1 1 0 1 1 0 0 1
k =
1 0 0 0 1 0 1 1 0 1
k =
1 0 0 0 1 0 1 1 0 1
k =
0 1 1 1 0 0 1 0 0 1
However, if you insist on using a loop, you can generate a vector from 1 up to the desired length, randomly choose an index in this vector then remove this value from the vector. You'd then use this index to set the location of the output:
N = 10; %// Length 10 vector
num_vals = 5; %// 5 values to populate
vec = 1 : N; %// Generate vector from 1 up to N
k = zeros(1, N); %// Initialize output k
%// Repeat the following for as many times as num_vals
for idx = 1 : num_vals
%// Obtain a value from the vector
ind = vec(randi(numel(vec), 1));
%// Remove from the vector
vec(ind) = [];
%// Set location in output to 1
k(ind) = 1;
end
The above code should still give you the desired effect, but I would argue that it's less efficient.

Conditionally replace neighbouring cells

Lets say I have a matrix A:
A =
0 1 0 0
0 0 0 0
0 0 0 1
0 0 0 0
And I want to create a new matrix B of the same dimension where all ones and accompanying neighbours are replaced by the following matrix:
X =
1 1 1
1 2 1
1 1 1
The 2 in matrix X should be placed 'on top' of the 1 values as to get:
B =
1 2 1 0
1 1 2 1
0 0 1 2
0 0 1 1
Values should be added up where elements overlap and matrix X should be 'cut off' in places where it extends the dimensions of matrix A/B The idea is to eventually replace X by a 2d gaussian distribution and matrix A will be large containing many more ones. So it's essential that the code is efficient and fast. This is the code i came up with:
A = [0 1 0 0;0 0 0 0;0 0 0 1;0 0 0 0]
X = [1 1 1;1 2 1;1 1 1]
B = zeros(4,4);
t=1;
indA = find(A==1);
indX = find(X==2);
all = find(X>0);
[iall jall] = ind2sub(size(X),all);
[ia ja] = ind2sub(size(A),indA)
[ix jx] = ind2sub(size(X),indX)
iv = ia-ix
jv = ja-jx
for t=1:numel(iv),
ib = iall+iv(t);
jb = jall+jv(t);
ibjb = [ib(:), jb(:)]
c1 = (ibjb(:,1)>4)|(ibjb(:,1)<1); c2 = (ibjb(:,2)>4)|(ibjb(:,1)<1);
ibjb((c1|c2),:)=[]
isel = ibjb(:,1)-iv(t)
jsel = ibjb(:,2)-jv(t)
B(ibjb(:,1), ibjb(:,2)) = B(ibjb(:,1), ibjb(:,2))+ X(isel, jsel)
t=t+1;
end
Is there a more efficient/faster way (minimizing the loops) to code this function?
What you want is a (2D) convolution. So use conv2:
B = conv2(A, X, 'same');

How to calculate the centroid of a matrix?

I have the following 5x5 Matrix A:
1 0 0 0 0
1 1 1 0 0
1 0 1 0 1
0 0 1 1 1
0 0 0 0 1
I am trying to find the centroid in MATLAB so I can find the scatter matrix with:
Scatter = A*Centroid*A'
If you by centroid mean the "center of mass" for the matrix, you need to account for the placement each '1' has in your matrix. I have done this below by using the meshgrid function:
M =[ 1 0 0 0 0;
1 1 1 0 0;
1 0 1 0 1;
0 0 1 1 1;
0 0 0 0 1];
[rows cols] = size(M);
y = 1:rows;
x = 1:cols;
[X Y] = meshgrid(x,y);
cY = mean(Y(M==1))
cX = mean(X(M==1))
Produces cX=3 and cY=3;
For
M = [1 0 0;
0 0 0;
0 0 1];
the result is cX=2;cY=2, as expected.
The centroid is simply the mean average computed separately for each dimension.
To find the centroid of each of the rows of your matrix A, you can call the mean function:
centroid = mean(A);
The above call to mean operates on rows by default. If you want to get the centroid of the columns of A, then you need to call mean as follows:
centroid = mean(A, 2);