Matrix to generate finite difference - matlab

I'm implementing a finite difference scheme for a 2D PDE problem. I wish to avoid using a loop to generate the finite differences. For instance to generate a 2nd order central difference of u(x,y)_xx, I can multiply u(x,y) by the following:
Is there a nice matrix representation for u_xy = (u_{i+1,j+1} + u_{i-1,j-1} - u_{i-1,j+1} - u_{i+1,j-1})/(4dxdy)? It's a harder problem to code as it's in 2D - I'd like to multiply some matrix by u(x,y) to avoid looping. Many thanks!

If your points are stored in a N-by-N matrix then, as you said, left multiplying by your finite difference matrix gives an approximation to the second derivative with respect to u_{xx}. Right-multiplying by the transpose of the finite difference matrix is equivalent to an approximation u_{yy}. You can get an approximation to the mixed derivative u_{xy} by left-multiplying and right-multiplying by e.g. a central difference matrix
delta_2x =
0 1 0 0 0
-1 0 1 0 0
0 -1 0 1 0
0 0 -1 0 1
0 0 0 -1 0
(then divide by the factor 4*Dx*Dy), so something like
U_xy = 1/(4*Dx*Dy) * delta_2x * U_matrix * delta_2x';
If you cast a matrix as a N^2 vector
U_vec = U_matrix(:);
then these operators can be expressed using a Kronecker product, implemented in MATLAB as kron: We have
A*X*B = kron(B',A)*X(:);
so for your finite difference matrices
U_xy_vec = 1/(4*Dx*Dy)*(kron(delta_2x,delta_2x)*U_vec);
If instead you have an N-by-M matrix U_mat, then left matrix multiplication is equivalent to kron(eye(M),delta_2x_N) and right multiplication to kron(delta_2y_M,eye(N)), where delta_2y_M (delta_2x_N) is the M-by-M (N-by-N) central difference matrix, so the operation is
U_xy_vec = 1/(4*Dx*Dy) * kron(delta_2y_M,delta_2y_N)*U_vec;
Here is an MATLAB code example:
N = 20;
M = 30;
Dx = 1/N;
Dy = 1/M;
[Y,X] = meshgrid((1:(M))./(M+1),(1:(N))/(N+1));
% Example solution and mixed derivative (chosen for 0 BCs)
U_mat = sin(2*pi*X).*(sin(2*pi*Y.^2));
U_xy = 8*pi^2*Y.*cos(2*pi*X).*cos(2*pi*Y.^2);
% Centred finite difference matrices
delta_x_N = 1/(2*Dx)*(diag(ones(N-1,1),1) - diag(ones(N-1,1),-1));
delta_y_M = 1/(2*Dy)*(diag(ones(M-1,1),1) - diag(ones(M-1,1),-1));
% Cast U as a vector
U_vec = U_mat(:);
% Mixed derivative operator
A = kron(delta_y_M,delta_x_N);
U_xy_num = A*U_vec;
U_xy_matrix = reshape(U_xy_num,N,M);
subplot(1,2,1)
contourf(X,Y,U_xy_matrix)
colorbar
title 'Numeric U_{xy}'
subplot(1,2,2)
contourf(X,Y,U_xy)
colorbar
title 'Analytic U_{xy}'

You can obviously create the matrix yourself, but in Matlab there is tridiag for this purpose.
For example
>> full(gallery('tridiag',5,-1,2,-1))
ans =
2 -1 0 0 0
-1 2 -1 0 0
0 -1 2 -1 0
0 0 -1 2 -1
0 0 0 -1 2

Using sparse functionality available in MATLAB to generate finite difference approximation matrix is a good option.. It saves lot (indeed very much) of memory...

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));

Creating a weight adjacency matrix

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

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])