Multiplying a sparse matrix by an array without copying - arrays

I have a 1d array (vector of size M), a pretty large one, and I definitely don't want to be copying it in memory. I also have a sparse matrix of window N (arbitrary size, basically all elements except the diagonal & N pseudo diagonals are zero).
I want to multiply this sparse matrix by the vector, without having to copy the vector in memory. What's the easiest and most efficient way of doing this? There has to be a neat solution, but I don't know the proper literature and I'm not educated enough to figure this out.
There is a solution for N=1 (where matrix is: a on the diagonal, and b on two closest pseudo diagonals). The solution looks smth like this (e.g., in python):
tmp2 = array[0]
i = 1
while (i < len(array) - 1):
tmp1 = b * (array[i - 1] + array[i + 1]) + a * array[i]
array[i - 1] = tmp2
i += 1
tmp2 = b * (array[i - 1] + array[i + 1]) + a * array[i]
array[i - 1] = tmp1
i += 1
But I can't manage to generalize this for an arbitrary N.
Notes: I absolutely don't want to be copying the size M vector in memory. However, using a temporary array of size 2N+1 is ok, since M >> N. I'm looking for an actual algorithm description, not a smart custom library that does the job.
Thanks in advance!

Consider the matrix
[
1 2 3 0 0 0
2 1 2 4 0 0
3 2 1 2 5 0
0 7 2 1 2 6
0 0 8 2 1 2
0 0 0 9 2 1
]
and the vector v [1,2,3,4,5,6]
For each row, below the involves coeff of v:
[1,2,3]
[1,2,3,4]
[1,2,3,4,5]
[ 2,3,4,5,6]
[ 3,4,5,6]
[ 4,5,6]
As you have noticed, you just need to keep track of a window of v.
That window is originally [1,2,3,4,5] (for i = 0, 1, 2)
Then you shift that window to the right every i (and eventually truncate it for the last rows not to be out of bounds of v...)
Now notice that when you shift to the right, you only need to know the next value from v, and as long as you have not dirtied that value (by writing to v) your new window is valid.
For row i, window is [i-n;i+n] and the coeff which will be modified is v[i]. For the next window, you need to know v[i+n+1] which has not been dirtied. So all good.
So algo be like
window = circularbuffer(2n+1) //you push to the right, and if length > 2n+1, you remove first elem
for i = 0; i<v.size()
v[i] = prod(row_i, window) // only for the row_i coeffs...
if i >= n && < M-3
window.push(v[i+n+1])
else if i>= M-3
window.shift() // just remove the first value
const N = 2
const M_SIZE = 10
function toString(M){
return M.map(x=>x.join(' ')).join('\n')
}
const { M, v } = (_ => {
const M = Array(M_SIZE).fill(0).map(x=>Array(M_SIZE).fill(0))
let z = 1
for(let i = 0; i<M_SIZE; ++i){
for(let j = -N; j<=N; ++j){
if(i+j >= 0 && i+j <M_SIZE){
M[i][i+j] = (z++ % (N*2))+1
}
}
}
const v = Array(M.length).fill(0).map((x,i)=>i)
return { M, v}
})()
function classic(M, v){
return M.map(r => r.reduce((acc, x, j) => acc + v[j]*x, 0))
}
function inplace(M, v){
// captn inefficiency
const circBuf = (init => {
let buf = init
return {
push (x) {
buf.push(x)
buf.shift()
},
shift() {
buf.shift()
},
at (i) { return buf[i] },
toString() {
return buf.join(' ')
}
}
})(v.slice(0, 2 * N + 1))
const sparseProd = (row, buf) => {
let s = 0
row.forEach((x, j) => s += x * buf.at(j))
return s
}
const sparseRows = M.map(r => r.filter(x => x !== 0))
sparseRows.forEach((row, i) => {
v[i] = sparseProd(row, circBuf)
if (i >= sparseRows.length - 3 ) {
circBuf.shift()
} else {
if (i >= N) {
circBuf.push(v[i + N + 1])
}
}
})
}
console.log('classic prod', classic(M, v))
inplace(M, v)
console.log('inplace prod', v)

So I ended up doing something like this. It seems like a generalization of what was done for the N=1 case.
In general, my weights are basically the non-zero components of the central row in my sparse matrix. I.e. if the matrix looks like this (as was noted in the comments, it's usually symmetric, but not necessarily):
| a b c 0 0 ... 0 0 0 0 0 |
| b a b c 0 ... 0 0 0 0 0 |
| c b a b c ... 0 0 0 0 0 |
| 0 c b a b ... 0 0 0 0 0 |
| 0 0 c b a ... 0 0 0 0 0 |
| ... ... ... |
| 0 0 0 0 0 ... a b c 0 0 |
| 0 0 0 0 0 ... b a b c 0 |
| 0 0 0 0 0 ... c b a b c |
| 0 0 0 0 0 ... 0 c b a b |
| 0 0 0 0 0 ... 0 0 c b a |
then the weights vector is simply [c, b, a, b, c] (i.e., N = 2).
So for the general case where N = ntimes I ended doing something like this:
def sparse_multiply(array, weights):
ntimes = (len(weights) - 1) / 2
# reduced dot product
def product(a_, i_, w_):
dot = 0.0
for k, j in enumerate(range(i_ - ntimes, i_ + ntimes + 1)):
if (j >= 0 and j < len(a_)):
dot += a_[j] * w_[k]
return dot
tmp = np.zeros(ntimes + 1)
for i in range(ntimes):
tmp[i] = array[i]
i = ntimes
while (i <= len(array)):
for t in range(-1, ntimes):
tmp[t] = product(array, i, w)
array[i - ntimes] = tmp[t + 1]
i += 1
return array
The only sacrifice you make, is the temporary array of size O(N), which is fine, because as I said, N << M.
Yes, yes, I know some of the operations (like the reduced dot product) could have been done with some python magic. But my point was to transfer this into old school C/Fortran, so that wouldn't help much.
Applications
Actually the application I was interested was to apply a gaussian filter: a_i = 0.5 * a_i + 0.25 * (a_{i-1} + a_{i+1}) to an array N times without having to do N passes and without having to copy the whole array.
So what you can do, is you can raise the sparse matrix of 0.5 on diagonals, and 0.25 on pseudo diagonals to the Nth power, and you will end up with the weights vector and a matrix that looks like the one I showed earlier (but with N nonzero pseudo diagonals). Then you can apply these weights to the array using the method above, so that you don't modify a_i before having to use it for other components, but at the same time get away without copying the whole array.
PS. Unfortunately I did not quite follow #grodzi's answer. A bit more explanation would certainly help.

Related

How do you invert euclidean (transform and rotation only) matrices in C?

How do you invert 4x3 matrices that are only translation and rotation, no scale? The sort of thing you would use to do an OpenGL Matrix inverse (just without scaling)?
Assuming your TypeMatrix3x4 is a [3][4] matrix, and you are only transforming a 1:1 scale, rotation and translation matrix, the following code seems to work -
This transposes the rotation matrix and applies the inverse of the translation.
TypeMatrix3x4 InvertHmdMatrix34( TypeMatrix3x4 mtoinv )
{
int i, j;
TypeMatrix3x4 out = { 0 };
for( i = 0; i < 3; i++ )
for( j = 0; j < 3; j++ )
out.m[j][i] = mtoinv.m[i][j];
for ( i = 0; i < 3; i++ )
{
out.m[i][3] = 0;
for( j = 0; j < 3; j++ )
out.m[i][3] += out.m[i][j] * -mtoinv.m[j][3];
}
return out;
}
You can solve that for any 3 dimensional affine transformation whose 3x3 transformation matrix is invertible. This allows you to include scaling and non conformant applications. The only requirement is for the 3x3 matrix to be invertible.
Simply extend your 3x4 matrix to 4x4 by adding a row all zeros except the last element, and invert that matrix. For example, as shown below:
[[a b c d] [[x] [[x']
[e f g h] * [y] = [y']
[i j k l] [z] [z']
[0 0 0 1]] [1]] [1 ]] (added row)
It's easy to see that this 4x4 matrix, applied to your vector produces exactly the same vector as before the extension.
If you get the inverse of that matrix, you'll have:
[[A B C D] [[x'] [[x]
[E F G H] * [y'] = [y]
[I J K L] [z'] [z]
[0 0 0 1]] [1 ] [1]]
It's easy to see that it this works in one direction, it needs to be in the reverse direction, if A is the image of B, then B will be the inverse throug the inverse transformation, the only requisite is the matrix to be invertible.
More on... if you have a list of vectors you want to process, you can apply Gauss elimination method to an extended matrix of the form:
[[a b c d x0' x1' x2' ... xn']
[e f g h y0' y1' y2' ... yn']
[i j k l z0' z1' z2' ... zn']
[0 0 0 1 1 1 1 ... 1 ]]
to obtain the inverses of all the vectors you do the Gauss elimination vector to get from above:
[[1 0 0 0 x0 x1 x2 ... xn ]
[0 1 0 0 y0 y1 y2 ... yn ]
[0 0 1 0 z0 z1 z2 ... zn ]
[0 0 0 1 1 1 1 ... 1 ]]
and you will solve n problems in one shot, because the column vectors above will be the ones, that once transformed produce the former ones.
You can get a simple implementation I wrote to teach my son about linear algebra of Gauss/Jordan elimination method here. It's opensource (BSD license) and you can modify/adapt it to your needs. This method uses the last approach, and you can use it out of the box by trying the sist_lin program.
If you want the inverse transformation, put the following contents in the matrix, and apply Gauss elimination to:
a b c d 1 0 0 0
e f g h 0 1 0 0
i j k l 0 0 1 0
0 0 0 1 0 0 0 1
as input to sist_lin and you get:
1 0 0 0 A B C D <-- these are the coefs of the
0 1 0 0 E F G H inverse transformation
0 0 1 0 I J K L
0 0 0 1 0 0 0 1
you will have:
a * x + b * y + c * z + d = X
e * x + f * y + g * z + h = Y
i * x + j * y + k * z + l = Z
0 * x + 0 * y + 0 * z + 1 = 1
and
A * X + B * Y + C * Z + D = x
E * X + F * Y + G * Z + H = y
I * X + J * Y + K * Z + L = z
0 * X + 0 * Y + 0 * Z + 1 = 1

How to find the number of times a group of a specific value is present in an array?

I have a 1 by 1000 (1 row by 1000 columns) matrix that contain only 0 and 1 as their elements. How can I find how many times 1 is repeated 3 times consecutively.
If there are more than 3 ones then it is necessary to reset the counting. So 4 would be 3+1 and it counts as only one instance of 3 consecutive 1s but 6 would be 3+3 so it counts as two instances of having 3 consecutive 1s.
This approach finds the differences between when A goes from 0 to 1 (rising edge) and from 1 to 0 (falling edge). This gives the lengths of consecutive 1s in each block. Then divide these numbers by 3 and round down to get the number of runs of 3.
Padding A with a 0 at the start and end just ensures we have a rising edge at the start if A starts with a 1, and we have a falling edge at the end if A ends with a 1.
A = round(rand(1,1000));
% padding with a 0 at the start and end will make this simpler
B = [0,A,0];
rising_edges = ~B(1:end-1) & B(2:end);
falling_edges = B(1:end-1) & ~B(2:end);
lengths_of_ones = find(falling_edges) - find(rising_edges);
N = sum(floor(lengths_of_ones / 3));
Or in a much less readable 2 lines:
A = round(rand(1,1000));
B = [0,A,0];
N = sum(floor((find(B(1:end-1) & ~B(2:end)) - find(~B(1:end-1) & B(2:end))) / 3));
You can define your custom functions like below
v = randi([0,1],1,1000);
% get runs in cell array
function C = runs(v)
C{1} = v(1);
for k = 2:length(v)
if v(k) == C{end}(end)
C{end} = [C{end},v(k)];
else
C{end+1} = v(k);
end
end
end
% count times of 3 consecutive 1s
function y = count(x)
if all(x)
y = floor(length(x)/3);
else
y = 0;
end
end
sum(cellfun(#count,runs(v)))
Here is another vectorized way:
% input
n = 3;
a = [1 1 1 1 0 0 1 1 1 0 0 0 1 1 1 1 1 0 1 1 1 1 1 1 1]
% x x x x x = 5
% output
a0 = [a 0];
b = cumsum( a0 ) % cumsum
c = diff( [0 b( ~( diff(a0) + 1 ) ) ] ) % number of ones within group
countsOf3 = sum( floor( c/n ) ) % groups of 3
You like it messy? Here is a one-liner:
countsOf3 = sum(floor(diff([0 getfield(cumsum([a 0]),{~(diff([a 0])+1)})])/n))

Create a matrix with a diagonal and left-diagonal of all 1s in MATLAB

I would like to create a square matrix of size n x n where the diagonal elements as well as the left-diagonal are all equal to 1. The rest of the elements are equal to 0.
For example, this would be the expected result if the matrix was 5 x 5:
1 0 0 0 0
1 1 0 0 0
0 1 1 0 0
0 0 1 1 0
0 0 0 1 1
How could I do this in MATLAB?
Trivial using the tril function:
tril(ones(n),0) - tril(ones(n),-2)
And if you wanted a thicker line of 1s just adjust that -2:
n = 10;
m = 4;
tril(ones(n),0) - tril(ones(n),-m)
If you prefer to use diag like excaza suggested then try
diag(ones(n,1)) + diag(ones(n-1,1),-1)
but you can't control the 'thickness' of the stripe this way. However, for a thickness of 2, it might perform better. You'd have to test it though.
You can also use spdiags too to create that matrix:
n = 5;
v = ones(n,1);
d = full(spdiags([v v], [-1 0], n, n));
We get:
>> d
d =
1 0 0 0 0
1 1 0 0 0
0 1 1 0 0
0 0 1 1 0
0 0 0 1 1
The first two lines define the desired size of the matrix, assuming a square n x n as well as a vector of all ones that is of length n x 1. We then call spdiags to define where along the diagonal of this matrix this vector will be populating. We want to define the main diagonal to have all ones as well as the diagonal to the left of the main diagonal, or -1 away from the main diagonal. spdiags will adjust the total number of elements for the diagonal away from the main to compensate.
We also ensure that the output is of size n x n, but this matrix is actually sparse . We need to convert the matrix to full to complete the result.,
With a bit of indices juggling, you can also do this:
N = 5;
ind = repelem(1:N, 2); % [1 1 2 2 3 3 ... N N]
M = full(sparse(ind(2:end), ind(1:end-1), 1))
Simple approach using linear indexing:
n = 5;
M = eye(n);
M(2:n+1:end) = 1;
This can also be done with bsxfun:
n = 5; %// matrix size
d = [0 -1]; %// diagonals you want set to 1
M = double(ismember(bsxfun(#minus, 1:n, (1:n).'), d));
For example, to obtain a 5x5 matrix with the main diagonal and the two diagonals below set to 1, define n=5 and d = [0 -1 -2], which gives
M =
1 0 0 0 0
1 1 0 0 0
1 1 1 0 0
0 1 1 1 0
0 0 1 1 1

How can I generate this matrix (containing only 0s and ±1s)?

I would like to generate matrix of size (n(n-1)/2, n) that looks like this (n=5 in this case):
-1 1 0 0 0
-1 0 1 0 0
-1 0 0 1 0
-1 0 0 0 1
0 -1 1 0 0
0 -1 0 1 0
0 -1 0 0 1
0 0 -1 1 0
0 0 -1 0 1
0 0 0 -1 1
This is what I, quickly, came up with:
G = [];
for i = 1:n-1;
for j = i+1:n
v = sparse(1,i,-1,1,n);
w = sparse(1,j,1,1,n);
vw = v+w;
G = [G; vw];
end
end
G = full(G);
It works, but is there a faster/cleaner way of doing it?
Use nchoosek to generate the indices of the columns that will be nonzero:
n = 5; %// number of columns
ind = nchoosek(1:n,2); %// ind(:,1): columns with "-1". ind(:,2): with "1".
m = size(ind,1);
rows = (1:m).'; %'// row indices
G = zeros(m,n);
G(rows + m*(ind(:,1)-1)) = -1;
G(rows + m*(ind(:,2)-1)) = 1;
You have two nested loops, which leads to O(N^2) complexity of non-vectorized operations, which is too much for this task. Take a look that your matrix actually has a rectursive pattern:
G(n+1) = [ -1 I(n)]
[ 0 G(n)];
where I(n) is identity matrix of size n. That's how you can express this pattern in matlab:
function G = mat(n)
% Treat original call as G(n+1)
n = n - 1;
% Non-recursive branch for trivial case
if n == 1
G = [-1 1];
return;
end
RT = eye(n); % Right-top: I(n)
LT = repmat(-1, n, 1); % Left-top: -1
RB = mat(n); % Right-bottom: G(n), recursive
LB = zeros(size(RB, 1), 1); % Left-bottom: 0
G = [LT RT; LB RB];
end
And it gives us O(N) complexity of non-vectorized operations. It probably will waste some memory during recursion and matrix composition if Matlab is not smart enought to factor these out. If it is critical, you may unroll recursion into loop and iteratively fill up corresponding places in your original pre-allocated matrix.

map a matrix with another matrix

I have a question to the mapping of a matrix with another matrix which contains only 1 and 0.
Here an example of my problem: A is the matrix with doubles
A = [ 1 4 3;
2 3 4;
4 3 1;
4 5 5;
1 2 1];
B is a matrix with ones and zeros:
B = [ 0 0 0;
0 0 0;
1 1 1;
1 1 1;
0 0 0];
I want to achieve a matrix C which is the result of A mapped by B, just like that:
C = [ 0 0 0;
0 0 0;
4 3 1;
4 5 5;
0 0 0];
I tried B as a logical array and as a matrix. Both lead to the same error:
"Subscript indices must either be real positive integers or logicals."
Just multiply A and B element-wise:
C = A.*B
I like Dan's solution, but this would be another way:
C = zeros(size(A));
C(B==1) = A(B==1);

Resources