Writing a loop that works for any value of N - matlab

I have a code for a 1D heat equation. Im trying to format a for loop so that the A matrix will follow a certain pattern of 1 -2 1 down the entire diagonal of a matrix that could be infinite. The pattern starts to take shape when I mess around with the initialized count at the beginning of the for loop but this changes the size of the matrix which fails the rest of the code.
My current code is below. The commented A matrix edits are what it should be.
N = 5;
%A(2,1:3) = [1 -2 1];
%A(3,2:4) = [1 -2 1];
%A(4,3:5) = [1 -2 1];
%A(5,4:6) = [1 -2 1];
A = zeros(N+1,N+1);
A(1,1) = 1;
for count=N:N+1
A(count+1,count:count+2) = [1 -2 1];
end
A(N+1,N+1) = 1;

In Matlab you can often avoid loops. In this case you can get the desired result with 2D convolution:
>> N = 6;
>> A = [1 zeros(1,N-1); conv2(eye(N-2), [1 -2 1]); zeros(1,N-1) 1]
A =
1 0 0 0 0 0
1 -2 1 0 0 0
0 1 -2 1 0 0
0 0 1 -2 1 0
0 0 0 1 -2 1
0 0 0 0 0 1
Or, depending on what you want,
>> A = conv2(eye(N), [1 -2 1], 'same')
A =
-2 1 0 0 0 0
1 -2 1 0 0 0
0 1 -2 1 0 0
0 0 1 -2 1 0
0 0 0 1 -2 1
0 0 0 0 1 -2

There are many simple ways of creating this matrix.
Your loop can be amended as follows:
N = 5;
A = zeros(N+1,N+1);
A(1,1) = 1;
for row = 2:N
A(row, row-1:row+1) = [1 -2 1];
end
A(N+1,N+1) = 1;
I've renamed count to row, we're indexing each row (from 2 to N, skipping the first and last rows), then finding with row-1:row+1 the three indices for that row that you want to address.
Directly indexing the diagonal and off-diagonal elements. Diagonal elements for an NxN matrix are 1:N+1:end. This is obviously more complex, I'd prefer the loop:
N = 6;
A = zeros(N,N);
A(1:N+1:end) = -2;
A(2:N+1:end-2*N) = 1; % skip last row
A(2*N+2:N+1:end) = 1; % skip first row
A(1,1) = 1;
A(N,N) = 1;
Using diag. We need to special-case the first and last rows:
N = 6;
A = diag(-2*ones(N,1),0) + diag(ones(N-1,1),1) + diag(ones(N-1,1),-1);
A(1,1:2) = [1,0];
A(end,end-1:end) = [0,1];

Related

How to leave only the elements that appear once per specific row x column per page?

I have a 3D array. I need to remove any elements that are in the same row, column position but on the next page (3rd dimension), and only use the first occurrence at that position. So if all pages were to multiply the result would be 0.
Since the 3D array may be of any size, I can't hard code solutions like isMember. I also can't use unique because elements can be the same, just not share the same position.
For example, input:
A(:,:,1) = [ 1 0 2];
A(:,:,2) = [ 1 1 0];
A(:,:,3) = [ 0 1 0];
the desired output is:
A(:,:,1) = [ 1 0 2];
A(:,:,2) = [ 0 1 0];
A(:,:,3) = [ 0 0 0];
How can I accomplish this?
Not the most elegant, but at least it works.
A(:,:,1) = [ 1 0 2 ];
A(:,:,2) = [ 1 1 0 ];
A(:,:,3) = [ 0 1 0 ];
for ii = 1:size(A,1)
for jj = 1:size(A,2)
unique_el = unique(A(ii, jj, :)); % Grab unique elements
for kk = 1:numel(unique_el)
idx = find(A(ii,jj,:) == kk); % Contains indices of unique elements
if numel(idx) > 1 % If an element occurs more than once
A(ii, jj, idx(2:end)) = 0; % Set to 0
end
end
end
end
A
A(:,:,1) =
1 0 2
A(:,:,2) =
0 1 0
A(:,:,3) =
0 0 0
I loop over the first two dimensions of A (rows and columns), find any unique elements which occur on a certain row and column location through the third dimensions (pages). Then set all occurrences of a unique element after the first to 0.
Given a more elaborate 3D matrix this still works:
A(:,:,1) = [1 0 2 0; 2 1 3 0];
A(:,:,2) = [1 1 0 0; 2 2 1 0];
A(:,:,3) = [0 1 1 3; 1 2 2 4];
A(:,:,1) =
1 0 2 0
2 1 3 0
A(:,:,2) =
0 1 0 0
0 2 1 0
A(:,:,3) =
0 0 1 3
1 0 2 4
If you want the first non-zero element and discard any element occurring afterwards, simply get rid of the unique() call:
A(:,:,1) = [1 0 2 0; 2 1 3 0];
A(:,:,2) = [1 1 0 0; 2 2 1 0];
A(:,:,3) = [0 1 1 3; 1 2 2 4];
for ii = 1:size(A,1)
for jj = 1:size(A,2)
idx = find(A(ii,jj,:) ~= 0); % Contains indices of nonzero elements
if numel(idx) > 1 % If more than one element
A(ii, jj, idx(2:end)) = 0; % Set rest to 0
end
end
end
A(:,:,1) =
1 0 2 0
2 1 3 0
A(:,:,2) =
0 1 0 0
0 0 0 0
A(:,:,3) =
0 0 0 3
0 0 0 4
My solution assumes, that, for a given "position", EVERY value after the first occurence of any value is cleared. Some of the MATLAB regulars around here had some discussions on that, from there comes the "extended" example as also used in Adriaan's answer.
I use permute and reshape to rearrange the input, so that we have all "positions" as "page" columns in a 2D array. Then, we can use arrayfun to find the proper indices of the first occurence of a non-zero value (kudos to LuisMendo's answer here). Using this approach again, we find all indices to be set to 0.
Let's have a look at the following code:
A(:,:,1) = [1 0 2 0; 2 1 3 0];
A(:,:,2) = [1 1 0 0; 2 2 1 0];
A(:,:,3) = [0 1 1 3; 1 2 2 4]
[m, n, o] = size(A);
B = reshape(permute(A, [3 1 2]), o, m*n);
idx = arrayfun(#(x) find(B(:, x), 1, 'first'), 1:size(B, 2));
idx = arrayfun(#(x) find(B(idx(x)+1:end, x)) + idx(x) + 3*(x-1), 1:size(B, 2), 'UniformOutput', false);
idx = vertcat(idx{:});
B(idx) = 0;
B = permute(reshape(B, o, m , n), [2, 3, 1])
Definitely, it makes sense to have a look at the intermediate outputs to understand the functioning of my approach. (Of course, some lines can be combined, but I wanted to keep a certain degree of readability.)
And, here's the output:
A =
ans(:,:,1) =
1 0 2 0
2 1 3 0
ans(:,:,2) =
1 1 0 0
2 2 1 0
ans(:,:,3) =
0 1 1 3
1 2 2 4
B =
ans(:,:,1) =
1 0 2 0
2 1 3 0
ans(:,:,2) =
0 1 0 0
0 0 0 0
ans(:,:,3) =
0 0 0 3
0 0 0 4
As you can see, it's identical to Adriaan's second version.
Hope that helps!
A vectorized solution. You can use the second output of max to find the index of the first occurence of a nonzero value along the third dimension and then use sub2ind to convert that to linear index.
A(:,:,1) = [ 1 0 2];
A(:,:,2) = [ 1 1 0];
A(:,:,3) = [ 0 1 0];
[~, mi] =max(logical(A) ,[], 3);
sz=size(A) ;
[x, y] =ndgrid(1:sz(1),1:sz(2));
idx=sub2ind( sz, x,y,mi);
result=zeros(sz) ;
result(idx) =A(idx);

Perform "outer product" of 2-D matrix and return a 3-D array in MATLAB

I would like to do an operation on a 2-D matrix which somehow looks like the outer product of a vector. I already have written some codes for this task, but it is pretty slow, so I would like to know if there is anything I can do to accelerate it.
I would like to show the code I wrote first, followed by an example to illustrate the task I wanted to do.
My code, version row-by-row
function B = outer2D(A)
B = zeros(size(A,1),size(A,2),size(A,2)); %Pre-allocate the output array
for J = 1 : size(A,1)
B(J,:,:) = transpose(A(J,:))*A(J,:); %Perform outer product on each row of A and assign to the J-th layer of B
end
end
Using the matrix A = randn(30000,20) as the input for testing, it spends 0.317 sec.
My code, version page-by-page
function B = outer2D(A)
B = zeros(size(A,1),size(A,2),size(A,2)); %Pre-allocate the output array
for J = 1 : size(A,2)
B(:,:,J) = repmat(A(:,J),1,size(A,2)).*A; %Evaluate B page-by-page
end
end
Using the matrix A = randn(30000,20) as the input for testing, it spends 0.146 sec.
Example 1
A = [3 0; 1 1; 1 0; -1 1; 0 -2]; %A is the input matrix.
B = outer2D(A);
disp(B)
Then I would expect
(:,:,1) =
9 0
1 1
1 0
1 -1
0 0
(:,:,2) =
0 0
1 1
0 0
-1 1
0 4
The first row of B, [9 0; 0 0], is the outer product of [3 0],
i.e. [3; 0]*[3 0] = [9 0; 0 0].
The second row of B, [1 1; 1 1], is the outer product of [1 1],
i.e. [1; 1]*[1 1] = [1 1; 1 1].
The third row of B, [1 0; 0 0], is the outer product of [1 0],
i.e. [1; 0]*[1 0] = [1 0; 0 0].
And the same for the remaining rows.
Example 2
A =
0 -1 -2
0 1 0
-3 0 2
0 0 0
1 0 0
B = outer2D(A)
disp(B)
Then, similar to the example 1, the expected output is
(:,:,1) =
0 0 0
0 0 0
9 0 -6
0 0 0
1 0 0
(:,:,2) =
0 1 2
0 1 0
0 0 0
0 0 0
0 0 0
(:,:,3) =
0 2 4
0 0 0
-6 0 4
0 0 0
0 0 0
Because the real input in my project is like in the size of 30000 × 2000 and this task is to be performed for many times. So the acceleration of this task is quite essential for me.
I am thinking of eliminating the for-loop in the function. May I have some opinions on this problem?
With auto expansion:
function B = outer2D(A)
B=permute(permute(A,[3 1 2]).*A',[2 3 1]);
end
Without auto expansion:
function B = outer2Dold(A)
B=permute(bsxfun(#times,permute(A,[3 1 2]),A'),[2 3 1]);
end
Outer products are not possible in the matlab language.

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

Permute elements within rows of matrix

I have a matrix A
A = [0 0 0 0 1; 0 0 0 0 2; 0 1 2 3 4];
and I would like to randomly permute the elements within each row. For example, matrix A2
A2 = [1 0 0 0 0; 0 0 0 2 0; 4 1 3 2 0]; % example of desired output
I can do this with a vector:
Av = [0 1 2 3 4];
Bv = Av(randperm(5));
But I am unsure how to do this a row at time for a matrix and to only permute the elements within a given row. Is this possible to do? I could construct a matrix from many permuted vectors, but I would prefer not to do it this way.
Thanks.
You can use sort on a random array of any size (which is what randperm does). After that, all you need to do is some index-trickery to properly reshuffle the array
A = [0 0 0 0 1; 0 0 0 0 2; 0 1 2 3 4];
[nRows,nCols] = size(A);
[~,idx] = sort(rand(nRows,nCols),2);
%# convert column indices into linear indices
idx = (idx-1)*nRows + ndgrid(1:nRows,1:nCols);
%# rearrange A
B = A;
B(:) = B(idx)
B =
0 0 1 0 0
0 2 0 0 0
2 1 3 4 0

MATLAB Expanding A Matrix with Zeros

I need a matrix of nxn, where the first pxp of it contains ones and rest are zeros. I can do it with traversing the cells, so I'm not asking a way to do it. I'm looking for "the MATLAB way" to do it, using built-in functions and avoiding loops etc.
To be more clear;
let n=4 and p=2,
then the expected result is:
1 1 0 0
1 1 0 0
0 0 0 0
0 0 0 0
There are possibly more than one elegant solution to do it, so I will accept the answer with the shortest and most readable one.
P.S. The question title looks a bit irrelevant: I put that title because my initial approach would be creating a pxp matrix with ones, then expanding it to nxn with zeros.
The answer is creating a matrix of zeroes, and then setting part of it to 1 using indexing:
For example:
n = 4;
p = 2;
x = zeros(n,n);
x(1:p,1:p) = 1;
If you insist on expanding, you can use:
padarray( zeros(p,p)+1 , [n-p n-p], 0, 'post')
Another way to expand the matrix with zeros:
>> p = 2; n = 4;
>> M = ones(p,p)
M =
1 1
1 1
>> M(n,n) = 0
M =
1 1 0 0
1 1 0 0
0 0 0 0
0 0 0 0
You can create the matrix easily by concatenating horizontally and vertically:
n = 4;
p = 2;
MyMatrix = [ ones(p), zeros(p, n-p); zeros(n-p, n) ];
>> p = 2; n = 4;
>> a = [ones(p, 1); zeros(n - p, 1)]
a =
1
1
0
0
>> A = a * a'
A =
1 1 0 0
1 1 0 0
0 0 0 0
0 0 0 0