Create a matrix blockwise attaching blocks like a stairway MATLAB - arrays

I want to build an Array containing of different blocks in the following way.
Given the block I want to repeat the block n-times so that it looks like this:
A =
1 0 0 -1 0 0 0 0 0 1 0 0
0 1 0 0 -1 0 0 0 0 0 1 0
0 0 1 0 0 -1 0 0 0 0 0 1
and I want the Array look like this, n times repeating the scheme:
newArray =
1 0 0 -1 0 0 0 0 0 1 0 0
0 1 0 0 -1 0 0 0 0 0 1 0
0 0 1 0 0 -1 0 0 0 0 0 1
1 0 0 -1 0 0 0 0 0 1 0 0
0 1 0 0 -1 0 0 0 0 0 1 0
0 0 1 0 0 -1 0 0 0 0 0 1
and so on...
With the free space being zeros, since the final array should be a sparse array either way.
How can I repeat and attach the block without using loops?

I'm assuming that the leftward offset of each block with respect to a pure block-diagonal matrix is the number of rows of A, as in your example.
You can build a matrix t that 2D-convolved with A gives the result, as follows:
A = [1 2 3 4; 5 6 7 8]; % data matrix
n = 3; % number of repetitions
[r, c] = size(A);
d = c-r;
t = zeros(r*(n-1)+1, d*(n-1)+1);
t(1:(r*(n-1)+1)*d+r:end) = 1;
result = conv2(t,A);
This gives
A =
1 2 3 4
5 6 7 8
result =
1 2 3 4 0 0 0 0
5 6 7 8 0 0 0 0
0 0 1 2 3 4 0 0
0 0 5 6 7 8 0 0
0 0 0 0 1 2 3 4
0 0 0 0 5 6 7 8

Here is a solution using kron:
n = 5; % number of repetitions
v = 3; % overlapping
s = size(A);
B = A(:,1:s(2)-v)
C = zeros(s(1),s(2)-v);
C(:,end-v+1:end) = A(:,end-v+1:end);
result = kron(eye(n) , B);
result(end,end+v)=0;
result(:,v+1:end) = result(:,v+1:end) + kron(eye(n) , C);
When the matrix size is large you can use sparse matrix:
n = 5;
v = 3;
s = size(A);
B = sparse(A(:,1:s(2)-v));
C = sparse(s(1),s(2)-v);
C(:,end-v+1:end) = A(:,end-v+1:end);
result = kron(eye(n) , B);
result(end,end+v) = 0;
result(:,v+1:end) = result(:,v+1:end) + kron(eye(n) , C);

Related

Vectorisation of code for insertion of 2x2 matrix along the diagonal of a large matrix

I am trying to do an elementwise insertion of a small matrix (2x2) along the diagonal of a large matrix (10x10 say). Overlapping values are added, and the small matrix is only inserted where it can fit fully inside the large matrix.
I have achieved this using a for loop, but am curious whether the process can be vectorised.
function M = TestDiagonal()
N = 10;
miniM = [1, -1; -1, 1];
M = zeros(N);
for i = 1:N-1
M(i:i+1,i:i+1) = M(i:i+1,i:i+1) + miniM;
end
end
Giving the desired matrix
1 -1 0 0 0 0 0 0 0 0
-1 2 -1 0 0 0 0 0 0 0
0 -1 2 -1 0 0 0 0 0 0
0 0 -1 2 -1 0 0 0 0 0
0 0 0 -1 2 -1 0 0 0 0
0 0 0 0 -1 2 -1 0 0 0
0 0 0 0 0 -1 2 -1 0 0
0 0 0 0 0 0 -1 2 -1 0
0 0 0 0 0 0 0 -1 2 -1
0 0 0 0 0 0 0 0 -1 1
In the general case, the input will always be square, but can take any size. The step dimension will always be equal to 1.
Just use 2D convolution (see conv2).
2×2 case, step 1 along each dimension
M = conv2(eye(N-1), miniM);
m×m case, step 1 along each dimension
M = conv2(eye(N-size(miniM-1)+1), miniM);
m×n case, arbitrary steps along each dimension
In this case the steps need to be defined:
step = [2 1]; % desired step along each dimension
and it makes more sense to define a desired number of repetitions R, rather than the final size (N), because the latter may not be achievable with full repetitions of miniM:
R = 4; % desired number of repetitions
Then:
M = conv2(full(sparse(1:step(1):step(1)*R, 1:step(2):step(2)*R, 1)), miniM);
Example:
>> miniM = [10 20 30; 40 50 60];
>> R = 4;
>> step = [1 2];
>> M = conv2(full(sparse(1:step(1):step(1)*R, 1:step(2):step(2)*R, 1)), miniM)
M =
10 20 30 0 0 0 0 0 0
40 50 70 20 30 0 0 0 0
0 0 40 50 70 20 30 0 0
0 0 0 0 40 50 70 20 30
0 0 0 0 0 0 40 50 60

Find regions of contiguous zeros in a binary array

I have
x=[ 1 1 1 1 0 0 1 1 1 0 1 0 0 0 0 0 0 1 0 1]
I want to find all the regions that have more than 5 zeros in a row. I want to find the index where it starts and where it stops.
In this case I want this: c=[12 18]. I can do it using for loops but I wonder if there is any better way, at least to find if there are some regions where this 'mask' ( mask=[0 0 0 0 0] ) appears.
A convolution based approach:
n = 5;
x = [0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 1 1 0 1 0];
end_idx = find(diff(conv(~x, ones(1,n))==n)==-1)
start_idx = find(diff(conv(~x, ones(1,n))==n)==1) - n + 2
returning
end_idx =
6 14 25
start_idx =
1 9 20
Note that this part is common to both lines: diff(conv(~x, ones(1,n))==n) so it would be more efficient to pull it out:
kernel = ones(1,n);
convolved = diff(conv(~x, kernel)==n);
end_idx = find(convolved==-1)
start_idx = find(convolved==1) - n + 2
You can use regexp this way:
convert the array into a string
remove the blanks
use regexp to find the sequence of 0
A possible implementation could be:
x=[ 1 1 1 1 0 0 1 1 1 0 1 0 0 0 0 0 0 1 0 1]
% Convert the array to string and remove the blanks
str=strrep(num2str(x),' ','')
% Find the occurrences
[start_idx,end_idx]=regexp(str,'0{6,}')
This gives:
start_idx = 12
end_idx = 17
where x(start_idx) is the first element of the sequence and x(end_idx) is the last one
Applied to a more long sequence, start_idx and end_idx results being arrays:
x=[0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 1 1 0 1 0]
start_idx =
1 9 20
end_idx =
6 14 25

how to create this matrix without using for loop?

I want to create a N*N matrix A.
when n = 4
2 0 -2 0
2 0 2 0
0 2 0 -2
0 2 0 2
when n = 8
2 0 0 0 -2 0 0 0
2 0 0 0 2 0 0 0
0 2 0 0 0 -2 0 0
0 2 0 0 0 2 0 0
0 0 2 0 0 0 -2 0
0 0 2 0 0 0 2 0
0 0 0 2 0 0 0 -2
0 0 0 2 0 0 0 2
I can create this using nested for loop, but how to achieve it more efficiently? Are there any methods without for loop?
Thanks
Here's one way with bsxfun -
A = zeros(n);
idx = bsxfun(#plus,[0:(n/2)-1]*((n+3)-1),[1:2].');
A(idx) = 2;
A(idx+numel(A)/2) = -2;
Sample runs -
Case #1 :
>> n = 4;
>> A
A =
2 0 -2 0
2 0 -2 0
0 2 0 -2
0 2 0 -2
Case #2 :
>> n = 8;
>> A
A =
2 0 0 0 -2 0 0 0
2 0 0 0 -2 0 0 0
0 2 0 0 0 -2 0 0
0 2 0 0 0 -2 0 0
0 0 2 0 0 0 -2 0
0 0 2 0 0 0 -2 0
0 0 0 2 0 0 0 -2
0 0 0 2 0 0 0 -2
You can do it like that:
[reshape([repmat([ 2;2;zeros(n,1)],n/2-1,1); 2;2],n,n/2) ...
reshape([repmat([-2;2;zeros(n,1)],n/2-1,1);-2;2],n,n/2) ]
This only will work, if n is a power of two, obviously.
[EDIT]
It might be faster to use
[reshape([repmat([ 2;2;zeros(n,1)],n/2-1,1); 2;2; ...
repmat([-2;2;zeros(n,1)],n/2-1,1);-2;2] ,n,n) ]
[EDIT2]
This is only a good idea, if you have n of moderate size. If you need really big matrices, you should use sparse matrices. In this case a loop is what you want.

The fastest way to set up sparse matrix in matlab

I am working with iterative methods, and thus with large sparse matrices.
For instance, I want to set up a matrix like this:
1 1 0 0 1 0 0 0 0 0
1 1 1 0 0 1 0 0 0 0
0 1 1 1 0 0 1 0 0 0
0 0 1 1 1 0 0 1 0 0
1 0 0 1 1 1 0 0 1 0
0 1 0 0 1 1 1 0 0 1
So that only certain diagonals are non-zero. In my programming, I will be working with much larger matrix sizes, but Idea is the same: Only a few diagonals are non-zero, all other entries are zeros.
I know, how to do it in for loop, but it seems to be not effective, if the matrix size is large. Also I work with symmetric matrices.
I would appreciate, if you provide me a code for my sample matrix along with description.
You want spdiags:
m = 6; %// number of rows
n = 10; %// number of columns
diags = [-4 -1 0 1 4]; %// diagonals to be filled
A = spdiags(ones(min(m,n), numel(diags)), diags, m, n);
This gives:
>> full(A)
ans =
1 1 0 0 1 0 0 0 0 0
1 1 1 0 0 1 0 0 0 0
0 1 1 1 0 0 1 0 0 0
0 0 1 1 1 0 0 1 0 0
1 0 0 1 1 1 0 0 1 0
0 1 0 0 1 1 1 0 0 1

Fill odd sequences between ones in binary vector with value

I'm looking for a vectorized solution for this problem :
Let A a vector (great size : > 10000) of 0 and 1.
Ex :
A = [0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 0 1 0 0 1 0 1 etc]
I want to replace the 0 between the 1's (of odd ranks) by 2
i.e. to produce :
B = [0 0 0 1 2 2 2 2 2 1 0 0 0 1 2 2 1 0 0 1 2 1 etc]
Thanks for your help
It can be done quite easily with cumsum and mod:
A = [0 0 0 1 0 0 0 0 0 1 0 0 0 1 0 0 1 0 0 1 0 1]
Short answer
A( mod(cumsum(A),2) & ~A ) = 2
A =
0 0 0 1 2 2 2 2 2 1 0 0 0 1 2 2 1 0 0 1 2 1
You requested to fill the islands of odd rank, but by changing mod(... to ~mod(... you can easily fill also the islands of even rank.
Explanation/Old answer:
mask1 = logical(A);
mask2 = logical(mod(cumsum(A),2))
out = zeros(size(A));
out(mask2) = 2
out(mask1) = 1
try using cumsum
cs = cumsum( A );
B = 2*( mod(cs,2)== 1 );
B(A==1) = 1;

Resources