I would like to get a more compact, and efficient, algorithm for what I am doing. My task is to take the kron product of a matrix with the identity multiple times.
Right now I am doing something that, while it works, I cannot scale it up.
import scipy.sparse as sp
from scipy.sparse import csr_matrix
A=[[0., 0.],
[1., 0.]]
Id=sp.identity(2)
A_1=csr_matrix(sp.kron(A,sp.kron(Id,sp.kron(Id,Id))))
A_2=csr_matrix(sp.kron(Id,sp.kron(A,sp.kron(Id,Id))))
A_3=csr_matrix(sp.kron(Id,sp.kron(Id,sp.kron(A,Id))))
A_4=csr_matrix(sp.kron(Id,sp.kron(Id,sp.kron(Id,A))))
Notice how for each A_i, the A matrix is moved one space to the right. While this works, at some point if I need to do it for the A_20, it would require writing down an extremely long sequence of sp.kron.
That is why I would like to get something like:
n_q=20
A_list=[]
for i in range(i,n_q):
A_i=csr_matrix(sp.kron(Id,...sp.kron(A...,sp.kron(Id,Id))))
A_list.append(A_i)
Does anybody know if something like this is possible?
Here's an trial idea.
Define a function to chain some kron calls from a list of matrices:
In [50]: def foo(alist):
...: M = alist[0]
...: for a in alist[1:]:
...: M = sparse.kron(M,a)
...: return M
...:
Your two matrices:
In [51]: A=sparse.csr_matrix([[0., 0.],
...: [1., 0.]])
...: Id=sparse.identity(2)
Make a list, in this example, 3 id, and 2 after A:
In [52]: alist = [Id]*3+[A]+[Id]*2
In [53]: alist
Out[53]:
[<2x2 sparse matrix of type '<class 'numpy.float64'>'
with 2 stored elements (1 diagonals) in DIAgonal format>,
<2x2 sparse matrix of type '<class 'numpy.float64'>'
with 2 stored elements (1 diagonals) in DIAgonal format>,
<2x2 sparse matrix of type '<class 'numpy.float64'>'
with 2 stored elements (1 diagonals) in DIAgonal format>,
<2x2 sparse matrix of type '<class 'numpy.float64'>'
with 1 stored elements in Compressed Sparse Row format>,
<2x2 sparse matrix of type '<class 'numpy.float64'>'
with 2 stored elements (1 diagonals) in DIAgonal format>,
<2x2 sparse matrix of type '<class 'numpy.float64'>'
with 2 stored elements (1 diagonals) in DIAgonal format>]
And pass it to foo
:
In [54]: M = foo(alist)
In [55]: M
Out[55]:
<64x64 sparse matrix of type '<class 'numpy.float64'>'
with 512 stored elements (blocksize = 2x2) in Block Sparse Row format>
Then it's just a matter of iterating over the possible lists of Id/A mixes, with different numbers of before and after.
It saves typing if not speed.
In numpy the main way to make 'iteration' more efficient is to move it from python to compiled code, that is, by using the builtin whole-array methods. Or by using compilers like numba
.
Scipy sparse works on top of numpy, with most of the matrix attributes stored as arrays. There's limited amount of compiled code (cython?), mainly for matrix multiplication (a key sparse calculation), and conversion between formats.
You have to iteration levels, the different A_i
sequences, and kron
chaining within each.
I don't know enough of how kron
works to offhand suggest improvements on that. I suppose you could precompute some chained sequences, say for 2 and 4 I_d
in a row. So in effect the above example could be written as
sparse.kron(Id_3, sparse.kron(A, Id_2))
In fact let's try that
Chain 2 and 3 Id matrices:
In [68]: Id2 = sparse.kron(Id,Id); Id3 = sparse.kron(Id,Id2)
In [69]: Id3
Out[69]:
<8x8 sparse matrix of type '<class 'numpy.float64'>'
with 32 stored elements (blocksize = 4x4) in Block Sparse Row format>
In [70]: Id3.A
Out[70]:
array([[1., 0., 0., 0., 0., 0., 0., 0.],
[0., 1., 0., 0., 0., 0., 0., 0.],
[0., 0., 1., 0., 0., 0., 0., 0.],
[0., 0., 0., 1., 0., 0., 0., 0.],
[0., 0., 0., 0., 1., 0., 0., 0.],
[0., 0., 0., 0., 0., 1., 0., 0.],
[0., 0., 0., 0., 0., 0., 1., 0.],
[0., 0., 0., 0., 0., 0., 0., 1.]])
Actually we don't need to use kron to get that. It's just a eye
matrix for a desired size.
Now kron
3 matrices:
In [71]: M1 = sparse.kron(Id3,sparse.kron(A,Id2))
In [72]: M1
Out[72]:
<64x64 sparse matrix of type '<class 'numpy.float64'>'
with 512 stored elements in COOrdinate format>
and compare that the result of the full blown iteration:
In [73]: np.allclose(M1.A, foo(alist).A)
Out[73]: True
That's what I had in mind when first suggesting examining the kron sequence for patterns.
In fact using eye
directly:
In [92]: M2 = sparse.kron(sparse.eye(8),sparse.kron(A,sparse.eye(4)))
In [93]: M2
Out[93]:
<64x64 sparse matrix of type '<class 'numpy.float64'>'
with 32 stored elements in COOrdinate format>
In [94]: np.allclose(M2.A, foo(alist).A)
Out[94]: True