or in the matrix form
$$ A u = f, $$and ($n = 5$ illustration) $$ A = -\frac{1}{h^2}\begin{pmatrix} 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 \end{pmatrix} $$
The matrix is triadiagonal and sparse
(and also Toeplitz: all elements on the diagonal are the same)
In two dimensions, we get equation of the form
$$\frac{4u_{ij} -u_{(i-1)j} - u_{(i+1)j} - u_{i(j-1)}-u_{i(j+1)}}{h^2} = f_{ij},$$
or in the Kronecker product form
where $\Delta_1$ is a 1D Laplacian, and $\otimes$ is a Kronecker product of matrices.
For matrices $A$ and $B$ its Kronecker product is defined as a block matrix of the form
$$
[a_{ij} B]
$$
We can create this matrix using scipy.sparse package
In [5]:
import numpy as np
import scipy as sp
import scipy.sparse
from scipy.sparse import csc_matrix
import matplotlib.pyplot as plt
%matplotlib inline
n = 20
ex = np.ones(n);
lp1 = sp.sparse.spdiags(np.vstack((ex, -2*ex, ex)), [-1, 0, 1], n, n, 'csr');
e = sp.sparse.eye(n)
A = sp.sparse.kron(lp1, e) + sp.sparse.kron(e, lp1)
A = csc_matrix(A)
plt.spy(A, aspect='equal', marker='.', markersize=1)
Out[5]:
In [3]:
#np.vstack((ex, -2*ex, ex)).shape
print ex.shape
We can now solve the system
In [33]:
import numpy as np
import scipy as sp
import scipy.sparse
import scipy.sparse.linalg
from scipy.sparse import csc_matrix, csr_matrix
import matplotlib.pyplot as plt
%matplotlib inline
n = 256
ex = np.ones(n);
lp1 = sp.sparse.spdiags(np.vstack((ex, -2*ex, ex)), [-1, 0, 1], n, n, 'csr');
e = sp.sparse.eye(n)
A = sp.sparse.kron(lp1, e) + sp.sparse.kron(e, lp1)
A = csr_matrix(A) * (n + 1) ** 2
rhs = np.ones(n * n)
sol = sp.sparse.linalg.spsolve(A, rhs)
And plot the solution
In [34]:
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
t = np.linspace(0, 1, n)
x, y = np.meshgrid(t, t)
sol = np.reshape(sol, (n, n))
ax.plot_wireframe(x, y, -sol)
Out[34]:
In [41]:
print A.shape
v256 = (np.max(-sol))
vex = (4 * v256 - v128) / 3
print vex - v256
Let us do a short timing test
In [44]:
import numpy as np
import scipy as sp
import scipy.sparse
import scipy.sparse.linalg
from scipy.sparse import csc_matrix, csr_matrix, coo_matrix
import matplotlib.pyplot as plt
%matplotlib inline
n = 256
ex = np.ones(n);
lp1 = sp.sparse.spdiags(np.vstack((ex, -2*ex, ex)), [-1, 0, 1], n, n, 'csr');
e = sp.sparse.eye(n)
A = sp.sparse.kron(lp1, e) + sp.sparse.kron(e, lp1)
A = csr_matrix(A)
rhs = np.ones(n * n)
B = coo_matrix(A)
%timeit A.dot(rhs)
%timeit B.dot(rhs)
As you see, CSR is faster, and for more unstructured patterns the gain will be larger.
Sparse matrix give complexity reduction.
But they are not very good for parallel implementation.
And they do not give maximal efficiency due to random data access.
Typically, peak efficiency of 10-15% is considered good.
We can measure peak efficiency of an ordinary dot product:
In [45]:
import numpy as np
import time
n = 1000
a = np.random.randn(n, n)
v = np.random.randn(n)
t = time.time()
np.dot(a, v)
t = time.time() - t
print 'Efficiency:', ((2 * n ** 2)/t) / 10 ** 9, 'Gflops'
And for the sparse one:
In [46]:
n = 1000
ex = np.ones(n);
a = sp.sparse.spdiags(np.vstack((ex, -2*ex, ex)), [-1, 0, 1], n, n, 'csr');
rhs = np.random.randn(n)
t = time.time()
a.dot(rhs)
t = time.time() - t
print 'Efficiency:', (3 * n) / t / 10 ** 9, 'Gflops'
How to solve linear systems with sparse matrices?
The direct methods use sparse Gaussian elimination, i.e.
they eliminate variables while trying to keep
the matrix as sparse as possible. And often, the inverse of a sparse matrix is not sparse
(but $L$ and $U$ factors can be).
In [55]:
n = 5
ex = np.ones(n);
a = sp.sparse.spdiags(np.vstack((ex, -2*ex, ex)), [-1, 0, 1], n, n, 'csr');
a = a.todense()
b = np.array(np.linalg.inv(a))
#print b
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
t = np.linspace(0, 1, n)
x, y = np.meshgrid(t, t)
ax.plot_wireframe(x, y, -b)
Out[55]:
The L and U factors are typically better
In [56]:
p, l, u = scipy.linalg.lu(a)
print l
However, for a 2D case everything is much worser:
In [58]:
n = 20
ex = np.ones(n);
lp1 = sp.sparse.spdiags(np.vstack((ex, -2*ex, ex)), [-1, 0, 1], n, n, 'csr');
e = sp.sparse.eye(n)
A = sp.sparse.kron(lp1, e) + sp.sparse.kron(e, lp1)
A = csc_matrix(A)
T = scipy.sparse.linalg.splu(A)
In [60]:
plt.spy(T.U, marker='.', markersize=0.4)
Out[60]:
For two-dimensional case the number of nonzeros in the L factor grows as $\mathcal{O}(N^{3/2})$.
In [61]:
import networkx as nx
n = 10
ex = np.ones(n);
lp1 = sp.sparse.spdiags(np.vstack((ex, -2*ex, ex)), [-1, 0, 1], n, n, 'csr');
e = sp.sparse.eye(n)
A = sp.sparse.kron(lp1, e) + sp.sparse.kron(e, lp1)
A = csc_matrix(A)
G = nx.Graph(A)
nx.draw(G, pos=nx.spring_layout(G), node_size=10)
The reordering that minimizes the fill-in is important, so we can you graph theory to find one.
In [65]:
import networkx as nx
from networkx.utils import reverse_cuthill_mckee_ordering, cuthill_mckee_ordering
n = 10
ex = np.ones(n);
lp1 = sp.sparse.spdiags(np.vstack((ex, -2*ex, ex)), [-1, 0, 1], n, n, 'csr');
e = sp.sparse.eye(n)
A = sp.sparse.kron(lp1, e) + sp.sparse.kron(e, lp1)
A = csc_matrix(A)
G = nx.Graph(A)
#rcm = list(reverse_cuthill_mckee_ordering(G))
rcm = list(reverse_cuthill_mckee_ordering(G))
A1 = A[rcm, :][:, rcm]
plt.spy(A1, marker='.', markersize=3)
#p, L, U = scipy.linalg.lu(A1.todense())
#plt.spy(L, marker='.', markersize=0.8)
#nx.draw(G, pos=nx.spring_layout(G), node_size=10)
Out[65]:
The most efficient one is nested dissection algorithm, which finds the ordering recursively by computing the separator. No good implementation was found by me at the moment.
Florida sparse matrix collection which contains all sorts of matrices for different applications. It also allows for finding test matrices as well! We can have a look.
In [66]:
from IPython.display import HTML
HTML('<iframe src=http://yifanhu.net/GALLERY/GRAPHS/search.html width=700 height=450></iframe>')
Out[66]:
In [67]:
fname = 'crystm02.mat'
!wget http://www.cise.ufl.edu/research/sparse/mat/Boeing/$fname
In [68]:
from scipy.io import loadmat
import scipy.sparse
q = loadmat(fname)
print q
mat = q['Problem']['A'][0, 0]
T = scipy.sparse.linalg.splu(mat)
In [69]:
#Compute its LU
%matplotlib inline
import matplotlib.pyplot as plt
plt.spy(T.L, markersize=0.1)
Out[69]:
In [134]:
from IPython.core.display import HTML
def css_styling():
styles = open("./styles/custom.css", "r").read()
return HTML(styles)
css_styling()
Out[134]: