** Calling lapack directly from scipy
:PROPERTIES:
:date: 2013/05/21 11:28:27
:updated: 2013/06/26 18:55:43
:categories: linear algebra
:END:
If the built in linear algebra functions in numpy and scipy do not meet your needs, it is often possible to directly call lapack functions. Here we call a function to solve a set of complex linear equations. The lapack function for this is ZGBSV. The description of this function (http://linux.die.net/man/l/zgbsv) is:
ZGBSV computes the solution to a complex system of linear equations A * X = B, where A is a band matrix of order N with KL subdiagonals and KU superdiagonals, and X and B are N-by-NRHS matrices. The LU decomposition with partial pivoting and row interchanges is used to factor A as A = L * U, where L is a product of permutation and unit lower triangular matrices with KL subdiagonals, and U is upper triangular with KL+KU superdiagonals. The factored form of A is then used to solve the system of equations A * X = B.
The python signature is (http://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.lapack.zgbsv.html#scipy.linalg.lapack.zgbsv):
lub,piv,x,info = zgbsv(kl,ku,ab,b,[overwrite_ab,overwrite_b])
We will look at an example from http://www.nag.com/lapack-ex/node22.html.
We solve $A x = b$ with
\(
A = \left(
\begin{array}{cccc}
-1.65 + 2.26 i & -2.05 - 0.85 i & 0.97 - 2.84 i & 0 \\
6.30 i & -1.48 - 1.75 i & -3.99 + 4.01 i & 0.59 - 0.48 i \\
0 & -0.77 + 2.83 i & -1.06 + 1.94 i & 3.33 - 1.04 i \\
0 & 0 & 4.48 - 1.09 i & -0.46 - 1.72 i
\end{array}
\right)
\)
\(
b = \left(
\begin{array}{cc}
-1.06 + 21.50 i \\
-22.72 - 53.90 i \\
28.24 - 38.60 i \\
-34.56 + 16.73 i
\end{array}
\right).
\)
The $A$ matrix has one lower diagonal (kl = 1) and two upper diagonals (ku = 2), four equations (n = 4) and one right-hand side.
#+BEGIN_SRC python
import scipy.linalg.lapack as la
# http://www.nag.com/lapack-ex/node22.html
import numpy as np
A = np.array([[-1.65 + 2.26j, -2.05 - 0.85j, 0.97 - 2.84j, 0.0 ],
[6.30j, -1.48 - 1.75j, -3.99 + 4.01j, 0.59 - 0.48j],
[0.0, -0.77 + 2.83j, -1.06 + 1.94j, 3.33 - 1.04j],
[0.0, 0.0, 4.48 - 1.09j, -0.46 - 1.72j]])
# construction of Ab is tricky. Fortran indexing starts at 1, not
# 0. This code is based on the definition of Ab at
# http://linux.die.net/man/l/zgbsv. First, we create the Fortran
# indices based on the loops, and then subtract one from them to index
# the numpy arrays.
Ab = np.zeros((5,4),dtype=np.complex)
n, kl, ku = 4, 1, 2
for j in range(1, n + 1):
for i in range(max(1, j - ku), min(n, j + kl) + 1):
Ab[kl + ku + 1 + i - j - 1, j - 1] = A[i-1, j-1]
b = np.array([[-1.06 + 21.50j],
[-22.72 - 53.90j],
[28.24 - 38.60j],
[-34.56 + 16.73j]])
lub, piv, x, info = la.flapack.zgbsv(kl, ku, Ab, b)
# compare to results at http://www.nag.com/lapack-ex/examples/results/zgbsv-ex.r
print 'x = ',x
print 'info = ',info
# check solution
print 'solved: ',np.all(np.dot(A,x) - b < 1e-12)
# here is the easy way!!!
print '\n\nbuilt-in solver'
print np.linalg.solve(A,b)
#+END_SRC
#+RESULTS:
#+begin_example
x = [[-3.+2.j]
[ 1.-7.j]
[-5.+4.j]
[ 6.-8.j]]
info = 0
solved: True
built-in solver
[[-3.+2.j]
[ 1.-7.j]
[-5.+4.j]
[ 6.-8.j]]
#+end_example
Some points of discussion.
1. Kind of painful! but, nevertheless, possible. You have to do a lot more work figuring out the dimensions of the problem, how to setup the problem, keeping track of indices, etc...
But, one day it might be helpful to know this can be done, e.g. to debug an installation, to validate an approach against known results, etc...