|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
""" |
|
The symmetric eigenvalue problem. |
|
--------------------------------- |
|
|
|
This file contains routines for the symmetric eigenvalue problem. |
|
|
|
high level routines: |
|
|
|
eigsy : real symmetric (ordinary) eigenvalue problem |
|
eighe : complex hermitian (ordinary) eigenvalue problem |
|
eigh : unified interface for eigsy and eighe |
|
svd_r : singular value decomposition for real matrices |
|
svd_c : singular value decomposition for complex matrices |
|
svd : unified interface for svd_r and svd_c |
|
|
|
|
|
low level routines: |
|
|
|
r_sy_tridiag : reduction of real symmetric matrix to real symmetric tridiagonal matrix |
|
c_he_tridiag_0 : reduction of complex hermitian matrix to real symmetric tridiagonal matrix |
|
c_he_tridiag_1 : auxiliary routine to c_he_tridiag_0 |
|
c_he_tridiag_2 : auxiliary routine to c_he_tridiag_0 |
|
tridiag_eigen : solves the real symmetric tridiagonal matrix eigenvalue problem |
|
svd_r_raw : raw singular value decomposition for real matrices |
|
svd_c_raw : raw singular value decomposition for complex matrices |
|
""" |
|
|
|
from ..libmp.backend import xrange |
|
from .eigen import defun |
|
|
|
|
|
def r_sy_tridiag(ctx, A, D, E, calc_ev = True): |
|
""" |
|
This routine transforms a real symmetric matrix A to a real symmetric |
|
tridiagonal matrix T using an orthogonal similarity transformation: |
|
Q' * A * Q = T (here ' denotes the matrix transpose). |
|
The orthogonal matrix Q is build up from Householder reflectors. |
|
|
|
parameters: |
|
A (input/output) On input, A contains the real symmetric matrix of |
|
dimension (n,n). On output, if calc_ev is true, A contains the |
|
orthogonal matrix Q, otherwise A is destroyed. |
|
|
|
D (output) real array of length n, contains the diagonal elements |
|
of the tridiagonal matrix |
|
|
|
E (output) real array of length n, contains the offdiagonal elements |
|
of the tridiagonal matrix in E[0:(n-1)] where is the dimension of |
|
the matrix A. E[n-1] is undefined. |
|
|
|
calc_ev (input) If calc_ev is true, this routine explicitly calculates the |
|
orthogonal matrix Q which is then returned in A. If calc_ev is |
|
false, Q is not explicitly calculated resulting in a shorter run time. |
|
|
|
This routine is a python translation of the fortran routine tred2.f in the |
|
software library EISPACK (see netlib.org) which itself is based on the algol |
|
procedure tred2 described in: |
|
- Num. Math. 11, p.181-195 (1968) by Martin, Reinsch and Wilkonson |
|
- Handbook for auto. comp., Vol II, Linear Algebra, p.212-226 (1971) |
|
|
|
For a good introduction to Householder reflections, see also |
|
Stoer, Bulirsch - Introduction to Numerical Analysis. |
|
""" |
|
|
|
|
|
|
|
|
|
n = A.rows |
|
for i in xrange(n - 1, 0, -1): |
|
|
|
|
|
scale = 0 |
|
for k in xrange(0, i): |
|
scale += abs(A[k,i]) |
|
|
|
scale_inv = 0 |
|
if scale != 0: |
|
scale_inv = 1/scale |
|
|
|
|
|
|
|
if i == 1 or scale == 0 or ctx.isinf(scale_inv): |
|
E[i] = A[i-1,i] |
|
D[i] = 0 |
|
continue |
|
|
|
|
|
|
|
H = 0 |
|
for k in xrange(0, i): |
|
A[k,i] *= scale_inv |
|
H += A[k,i] * A[k,i] |
|
|
|
F = A[i-1,i] |
|
G = ctx.sqrt(H) |
|
if F > 0: |
|
G = -G |
|
E[i] = scale * G |
|
H -= F * G |
|
A[i-1,i] = F - G |
|
F = 0 |
|
|
|
|
|
|
|
for j in xrange(0, i): |
|
if calc_ev: |
|
A[i,j] = A[j,i] / H |
|
|
|
G = 0 |
|
for k in xrange(0, j + 1): |
|
G += A[k,j] * A[k,i] |
|
for k in xrange(j + 1, i): |
|
G += A[j,k] * A[k,i] |
|
|
|
E[j] = G / H |
|
F += E[j] * A[j,i] |
|
|
|
HH = F / (2 * H) |
|
|
|
for j in xrange(0, i): |
|
F = A[j,i] |
|
G = E[j] - HH * F |
|
E[j] = G |
|
|
|
for k in xrange(0, j + 1): |
|
A[k,j] -= F * E[k] + G * A[k,i] |
|
|
|
D[i] = H |
|
|
|
for i in xrange(1, n): |
|
E[i-1] = E[i] |
|
E[n-1] = 0 |
|
|
|
if calc_ev: |
|
D[0] = 0 |
|
for i in xrange(0, n): |
|
if D[i] != 0: |
|
for j in xrange(0, i): |
|
G = 0 |
|
for k in xrange(0, i): |
|
G += A[i,k] * A[k,j] |
|
for k in xrange(0, i): |
|
A[k,j] -= G * A[k,i] |
|
|
|
D[i] = A[i,i] |
|
A[i,i] = 1 |
|
|
|
for j in xrange(0, i): |
|
A[j,i] = A[i,j] = 0 |
|
else: |
|
for i in xrange(0, n): |
|
D[i] = A[i,i] |
|
|
|
|
|
|
|
|
|
|
|
def c_he_tridiag_0(ctx, A, D, E, T): |
|
""" |
|
This routine transforms a complex hermitian matrix A to a real symmetric |
|
tridiagonal matrix T using an unitary similarity transformation: |
|
Q' * A * Q = T (here ' denotes the hermitian matrix transpose, |
|
i.e. transposition und conjugation). |
|
The unitary matrix Q is build up from Householder reflectors and |
|
an unitary diagonal matrix. |
|
|
|
parameters: |
|
A (input/output) On input, A contains the complex hermitian matrix |
|
of dimension (n,n). On output, A contains the unitary matrix Q |
|
in compressed form. |
|
|
|
D (output) real array of length n, contains the diagonal elements |
|
of the tridiagonal matrix. |
|
|
|
E (output) real array of length n, contains the offdiagonal elements |
|
of the tridiagonal matrix in E[0:(n-1)] where is the dimension of |
|
the matrix A. E[n-1] is undefined. |
|
|
|
T (output) complex array of length n, contains a unitary diagonal |
|
matrix. |
|
|
|
This routine is a python translation (in slightly modified form) of the fortran |
|
routine htridi.f in the software library EISPACK (see netlib.org) which itself |
|
is a complex version of the algol procedure tred1 described in: |
|
- Num. Math. 11, p.181-195 (1968) by Martin, Reinsch and Wilkonson |
|
- Handbook for auto. comp., Vol II, Linear Algebra, p.212-226 (1971) |
|
|
|
For a good introduction to Householder reflections, see also |
|
Stoer, Bulirsch - Introduction to Numerical Analysis. |
|
""" |
|
|
|
n = A.rows |
|
T[n-1] = 1 |
|
for i in xrange(n - 1, 0, -1): |
|
|
|
|
|
|
|
scale = 0 |
|
for k in xrange(0, i): |
|
scale += abs(ctx.re(A[k,i])) + abs(ctx.im(A[k,i])) |
|
|
|
scale_inv = 0 |
|
if scale != 0: |
|
scale_inv = 1 / scale |
|
|
|
|
|
|
|
if scale == 0 or ctx.isinf(scale_inv): |
|
E[i] = 0 |
|
D[i] = 0 |
|
T[i-1] = 1 |
|
continue |
|
|
|
if i == 1: |
|
F = A[i-1,i] |
|
f = abs(F) |
|
E[i] = f |
|
D[i] = 0 |
|
if f != 0: |
|
T[i-1] = T[i] * F / f |
|
else: |
|
T[i-1] = T[i] |
|
continue |
|
|
|
|
|
|
|
H = 0 |
|
for k in xrange(0, i): |
|
A[k,i] *= scale_inv |
|
rr = ctx.re(A[k,i]) |
|
ii = ctx.im(A[k,i]) |
|
H += rr * rr + ii * ii |
|
|
|
F = A[i-1,i] |
|
f = abs(F) |
|
G = ctx.sqrt(H) |
|
H += G * f |
|
E[i] = scale * G |
|
if f != 0: |
|
F = F / f |
|
TZ = - T[i] * F |
|
G *= F |
|
else: |
|
TZ = -T[i] |
|
A[i-1,i] += G |
|
F = 0 |
|
|
|
|
|
|
|
for j in xrange(0, i): |
|
A[i,j] = A[j,i] / H |
|
|
|
G = 0 |
|
for k in xrange(0, j + 1): |
|
G += ctx.conj(A[k,j]) * A[k,i] |
|
for k in xrange(j + 1, i): |
|
G += A[j,k] * A[k,i] |
|
|
|
T[j] = G / H |
|
F += ctx.conj(T[j]) * A[j,i] |
|
|
|
HH = F / (2 * H) |
|
|
|
for j in xrange(0, i): |
|
F = A[j,i] |
|
G = T[j] - HH * F |
|
T[j] = G |
|
|
|
for k in xrange(0, j + 1): |
|
A[k,j] -= ctx.conj(F) * T[k] + ctx.conj(G) * A[k,i] |
|
|
|
|
|
|
|
T[i-1] = TZ |
|
D[i] = H |
|
|
|
for i in xrange(1, n): |
|
E[i-1] = E[i] |
|
E[n-1] = 0 |
|
|
|
D[0] = 0 |
|
for i in xrange(0, n): |
|
zw = D[i] |
|
D[i] = ctx.re(A[i,i]) |
|
A[i,i] = zw |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def c_he_tridiag_1(ctx, A, T): |
|
""" |
|
This routine forms the unitary matrix Q described in c_he_tridiag_0. |
|
|
|
parameters: |
|
A (input/output) On input, A is the same matrix as delivered by |
|
c_he_tridiag_0. On output, A is set to Q. |
|
|
|
T (input) On input, T is the same array as delivered by c_he_tridiag_0. |
|
|
|
""" |
|
|
|
n = A.rows |
|
|
|
for i in xrange(0, n): |
|
if A[i,i] != 0: |
|
for j in xrange(0, i): |
|
G = 0 |
|
for k in xrange(0, i): |
|
G += ctx.conj(A[i,k]) * A[k,j] |
|
for k in xrange(0, i): |
|
A[k,j] -= G * A[k,i] |
|
|
|
A[i,i] = 1 |
|
|
|
for j in xrange(0, i): |
|
A[j,i] = A[i,j] = 0 |
|
|
|
for i in xrange(0, n): |
|
for k in xrange(0, n): |
|
A[i,k] *= T[k] |
|
|
|
|
|
|
|
|
|
def c_he_tridiag_2(ctx, A, T, B): |
|
""" |
|
This routine applied the unitary matrix Q described in c_he_tridiag_0 |
|
onto the the matrix B, i.e. it forms Q*B. |
|
|
|
parameters: |
|
A (input) On input, A is the same matrix as delivered by c_he_tridiag_0. |
|
|
|
T (input) On input, T is the same array as delivered by c_he_tridiag_0. |
|
|
|
B (input/output) On input, B is a complex matrix. On output B is replaced |
|
by Q*B. |
|
|
|
This routine is a python translation of the fortran routine htribk.f in the |
|
software library EISPACK (see netlib.org). See c_he_tridiag_0 for more |
|
references. |
|
""" |
|
|
|
n = A.rows |
|
|
|
for i in xrange(0, n): |
|
for k in xrange(0, n): |
|
B[k,i] *= T[k] |
|
|
|
for i in xrange(0, n): |
|
if A[i,i] != 0: |
|
for j in xrange(0, n): |
|
G = 0 |
|
for k in xrange(0, i): |
|
G += ctx.conj(A[i,k]) * B[k,j] |
|
for k in xrange(0, i): |
|
B[k,j] -= G * A[k,i] |
|
|
|
|
|
|
|
|
|
|
|
def tridiag_eigen(ctx, d, e, z = False): |
|
""" |
|
This subroutine find the eigenvalues and the first components of the |
|
eigenvectors of a real symmetric tridiagonal matrix using the implicit |
|
QL method. |
|
|
|
parameters: |
|
|
|
d (input/output) real array of length n. on input, d contains the diagonal |
|
elements of the input matrix. on output, d contains the eigenvalues in |
|
ascending order. |
|
|
|
e (input) real array of length n. on input, e contains the offdiagonal |
|
elements of the input matrix in e[0:(n-1)]. On output, e has been |
|
destroyed. |
|
|
|
z (input/output) If z is equal to False, no eigenvectors will be computed. |
|
Otherwise on input z should have the format z[0:m,0:n] (i.e. a real or |
|
complex matrix of dimension (m,n) ). On output this matrix will be |
|
multiplied by the matrix of the eigenvectors (i.e. the columns of this |
|
matrix are the eigenvectors): z --> z*EV |
|
That means if z[i,j]={1 if j==j; 0 otherwise} on input, then on output |
|
z will contain the first m components of the eigenvectors. That means |
|
if m is equal to n, the i-th eigenvector will be z[:,i]. |
|
|
|
This routine is a python translation (in slightly modified form) of the |
|
fortran routine imtql2.f in the software library EISPACK (see netlib.org) |
|
which itself is based on the algol procudure imtql2 desribed in: |
|
- num. math. 12, p. 377-383(1968) by matrin and wilkinson |
|
- modified in num. math. 15, p. 450(1970) by dubrulle |
|
- handbook for auto. comp., vol. II-linear algebra, p. 241-248 (1971) |
|
See also the routine gaussq.f in netlog.org or acm algorithm 726. |
|
""" |
|
|
|
n = len(d) |
|
e[n-1] = 0 |
|
iterlim = 2 * ctx.dps |
|
|
|
for l in xrange(n): |
|
j = 0 |
|
while 1: |
|
m = l |
|
while 1: |
|
|
|
if m + 1 == n: |
|
break |
|
if abs(e[m]) <= ctx.eps * (abs(d[m]) + abs(d[m + 1])): |
|
break |
|
m = m + 1 |
|
if m == l: |
|
break |
|
|
|
if j >= iterlim: |
|
raise RuntimeError("tridiag_eigen: no convergence to an eigenvalue after %d iterations" % iterlim) |
|
|
|
j += 1 |
|
|
|
|
|
|
|
p = d[l] |
|
g = (d[l + 1] - p) / (2 * e[l]) |
|
r = ctx.hypot(g, 1) |
|
|
|
if g < 0: |
|
s = g - r |
|
else: |
|
s = g + r |
|
|
|
g = d[m] - p + e[l] / s |
|
|
|
s, c, p = 1, 1, 0 |
|
|
|
for i in xrange(m - 1, l - 1, -1): |
|
f = s * e[i] |
|
b = c * e[i] |
|
if abs(f) > abs(g): |
|
c = g / f |
|
r = ctx.hypot(c, 1) |
|
e[i + 1] = f * r |
|
s = 1 / r |
|
c = c * s |
|
else: |
|
s = f / g |
|
r = ctx.hypot(s, 1) |
|
e[i + 1] = g * r |
|
c = 1 / r |
|
s = s * c |
|
g = d[i + 1] - p |
|
r = (d[i] - g) * s + 2 * c * b |
|
p = s * r |
|
d[i + 1] = g + p |
|
g = c * r - b |
|
|
|
if not isinstance(z, bool): |
|
|
|
for w in xrange(z.rows): |
|
f = z[w,i+1] |
|
z[w,i+1] = s * z[w,i] + c * f |
|
z[w,i ] = c * z[w,i] - s * f |
|
|
|
d[l] = d[l] - p |
|
e[l] = g |
|
e[m] = 0 |
|
|
|
for ii in xrange(1, n): |
|
|
|
i = ii - 1 |
|
k = i |
|
p = d[i] |
|
for j in xrange(ii, n): |
|
if d[j] >= p: |
|
continue |
|
k = j |
|
p = d[k] |
|
if k == i: |
|
continue |
|
d[k] = d[i] |
|
d[i] = p |
|
|
|
if not isinstance(z, bool): |
|
for w in xrange(z.rows): |
|
p = z[w,i] |
|
z[w,i] = z[w,k] |
|
z[w,k] = p |
|
|
|
|
|
|
|
@defun |
|
def eigsy(ctx, A, eigvals_only = False, overwrite_a = False): |
|
""" |
|
This routine solves the (ordinary) eigenvalue problem for a real symmetric |
|
square matrix A. Given A, an orthogonal matrix Q is calculated which |
|
diagonalizes A: |
|
|
|
Q' A Q = diag(E) and Q Q' = Q' Q = 1 |
|
|
|
Here diag(E) is a diagonal matrix whose diagonal is E. |
|
' denotes the transpose. |
|
|
|
The columns of Q are the eigenvectors of A and E contains the eigenvalues: |
|
|
|
A Q[:,i] = E[i] Q[:,i] |
|
|
|
|
|
input: |
|
|
|
A: real matrix of format (n,n) which is symmetric |
|
(i.e. A=A' or A[i,j]=A[j,i]) |
|
|
|
eigvals_only: if true, calculates only the eigenvalues E. |
|
if false, calculates both eigenvectors and eigenvalues. |
|
|
|
overwrite_a: if true, allows modification of A which may improve |
|
performance. if false, A is not modified. |
|
|
|
output: |
|
|
|
E: vector of format (n). contains the eigenvalues of A in ascending order. |
|
|
|
Q: orthogonal matrix of format (n,n). contains the eigenvectors |
|
of A as columns. |
|
|
|
return value: |
|
|
|
E if eigvals_only is true |
|
(E, Q) if eigvals_only is false |
|
|
|
example: |
|
>>> from mpmath import mp |
|
>>> A = mp.matrix([[3, 2], [2, 0]]) |
|
>>> E = mp.eigsy(A, eigvals_only = True) |
|
>>> print(E) |
|
[-1.0] |
|
[ 4.0] |
|
|
|
>>> A = mp.matrix([[1, 2], [2, 3]]) |
|
>>> E, Q = mp.eigsy(A) |
|
>>> print(mp.chop(A * Q[:,0] - E[0] * Q[:,0])) |
|
[0.0] |
|
[0.0] |
|
|
|
see also: eighe, eigh, eig |
|
""" |
|
|
|
if not overwrite_a: |
|
A = A.copy() |
|
|
|
d = ctx.zeros(A.rows, 1) |
|
e = ctx.zeros(A.rows, 1) |
|
|
|
if eigvals_only: |
|
r_sy_tridiag(ctx, A, d, e, calc_ev = False) |
|
tridiag_eigen(ctx, d, e, False) |
|
return d |
|
else: |
|
r_sy_tridiag(ctx, A, d, e, calc_ev = True) |
|
tridiag_eigen(ctx, d, e, A) |
|
return (d, A) |
|
|
|
|
|
@defun |
|
def eighe(ctx, A, eigvals_only = False, overwrite_a = False): |
|
""" |
|
This routine solves the (ordinary) eigenvalue problem for a complex |
|
hermitian square matrix A. Given A, an unitary matrix Q is calculated which |
|
diagonalizes A: |
|
|
|
Q' A Q = diag(E) and Q Q' = Q' Q = 1 |
|
|
|
Here diag(E) a is diagonal matrix whose diagonal is E. |
|
' denotes the hermitian transpose (i.e. ordinary transposition and |
|
complex conjugation). |
|
|
|
The columns of Q are the eigenvectors of A and E contains the eigenvalues: |
|
|
|
A Q[:,i] = E[i] Q[:,i] |
|
|
|
|
|
input: |
|
|
|
A: complex matrix of format (n,n) which is hermitian |
|
(i.e. A=A' or A[i,j]=conj(A[j,i])) |
|
|
|
eigvals_only: if true, calculates only the eigenvalues E. |
|
if false, calculates both eigenvectors and eigenvalues. |
|
|
|
overwrite_a: if true, allows modification of A which may improve |
|
performance. if false, A is not modified. |
|
|
|
output: |
|
|
|
E: vector of format (n). contains the eigenvalues of A in ascending order. |
|
|
|
Q: unitary matrix of format (n,n). contains the eigenvectors |
|
of A as columns. |
|
|
|
return value: |
|
|
|
E if eigvals_only is true |
|
(E, Q) if eigvals_only is false |
|
|
|
example: |
|
>>> from mpmath import mp |
|
>>> A = mp.matrix([[1, -3 - 1j], [-3 + 1j, -2]]) |
|
>>> E = mp.eighe(A, eigvals_only = True) |
|
>>> print(E) |
|
[-4.0] |
|
[ 3.0] |
|
|
|
>>> A = mp.matrix([[1, 2 + 5j], [2 - 5j, 3]]) |
|
>>> E, Q = mp.eighe(A) |
|
>>> print(mp.chop(A * Q[:,0] - E[0] * Q[:,0])) |
|
[0.0] |
|
[0.0] |
|
|
|
see also: eigsy, eigh, eig |
|
""" |
|
|
|
if not overwrite_a: |
|
A = A.copy() |
|
|
|
d = ctx.zeros(A.rows, 1) |
|
e = ctx.zeros(A.rows, 1) |
|
t = ctx.zeros(A.rows, 1) |
|
|
|
if eigvals_only: |
|
c_he_tridiag_0(ctx, A, d, e, t) |
|
tridiag_eigen(ctx, d, e, False) |
|
return d |
|
else: |
|
c_he_tridiag_0(ctx, A, d, e, t) |
|
B = ctx.eye(A.rows) |
|
tridiag_eigen(ctx, d, e, B) |
|
c_he_tridiag_2(ctx, A, t, B) |
|
return (d, B) |
|
|
|
@defun |
|
def eigh(ctx, A, eigvals_only = False, overwrite_a = False): |
|
""" |
|
"eigh" is a unified interface for "eigsy" and "eighe". Depending on |
|
whether A is real or complex the appropriate function is called. |
|
|
|
This routine solves the (ordinary) eigenvalue problem for a real symmetric |
|
or complex hermitian square matrix A. Given A, an orthogonal (A real) or |
|
unitary (A complex) matrix Q is calculated which diagonalizes A: |
|
|
|
Q' A Q = diag(E) and Q Q' = Q' Q = 1 |
|
|
|
Here diag(E) a is diagonal matrix whose diagonal is E. |
|
' denotes the hermitian transpose (i.e. ordinary transposition and |
|
complex conjugation). |
|
|
|
The columns of Q are the eigenvectors of A and E contains the eigenvalues: |
|
|
|
A Q[:,i] = E[i] Q[:,i] |
|
|
|
input: |
|
|
|
A: a real or complex square matrix of format (n,n) which is symmetric |
|
(i.e. A[i,j]=A[j,i]) or hermitian (i.e. A[i,j]=conj(A[j,i])). |
|
|
|
eigvals_only: if true, calculates only the eigenvalues E. |
|
if false, calculates both eigenvectors and eigenvalues. |
|
|
|
overwrite_a: if true, allows modification of A which may improve |
|
performance. if false, A is not modified. |
|
|
|
output: |
|
|
|
E: vector of format (n). contains the eigenvalues of A in ascending order. |
|
|
|
Q: an orthogonal or unitary matrix of format (n,n). contains the |
|
eigenvectors of A as columns. |
|
|
|
return value: |
|
|
|
E if eigvals_only is true |
|
(E, Q) if eigvals_only is false |
|
|
|
example: |
|
>>> from mpmath import mp |
|
>>> A = mp.matrix([[3, 2], [2, 0]]) |
|
>>> E = mp.eigh(A, eigvals_only = True) |
|
>>> print(E) |
|
[-1.0] |
|
[ 4.0] |
|
|
|
>>> A = mp.matrix([[1, 2], [2, 3]]) |
|
>>> E, Q = mp.eigh(A) |
|
>>> print(mp.chop(A * Q[:,0] - E[0] * Q[:,0])) |
|
[0.0] |
|
[0.0] |
|
|
|
>>> A = mp.matrix([[1, 2 + 5j], [2 - 5j, 3]]) |
|
>>> E, Q = mp.eigh(A) |
|
>>> print(mp.chop(A * Q[:,0] - E[0] * Q[:,0])) |
|
[0.0] |
|
[0.0] |
|
|
|
see also: eigsy, eighe, eig |
|
""" |
|
|
|
iscomplex = any(type(x) is ctx.mpc for x in A) |
|
|
|
if iscomplex: |
|
return ctx.eighe(A, eigvals_only = eigvals_only, overwrite_a = overwrite_a) |
|
else: |
|
return ctx.eigsy(A, eigvals_only = eigvals_only, overwrite_a = overwrite_a) |
|
|
|
|
|
@defun |
|
def gauss_quadrature(ctx, n, qtype = "legendre", alpha = 0, beta = 0): |
|
""" |
|
This routine calulates gaussian quadrature rules for different |
|
families of orthogonal polynomials. Let (a, b) be an interval, |
|
W(x) a positive weight function and n a positive integer. |
|
Then the purpose of this routine is to calculate pairs (x_k, w_k) |
|
for k=0, 1, 2, ... (n-1) which give |
|
|
|
int(W(x) * F(x), x = a..b) = sum(w_k * F(x_k),k = 0..(n-1)) |
|
|
|
exact for all polynomials F(x) of degree (strictly) less than 2*n. For all |
|
integrable functions F(x) the sum is a (more or less) good approximation to |
|
the integral. The x_k are called nodes (which are the zeros of the |
|
related orthogonal polynomials) and the w_k are called the weights. |
|
|
|
parameters |
|
n (input) The degree of the quadrature rule, i.e. its number of |
|
nodes. |
|
|
|
qtype (input) The family of orthogonal polynmomials for which to |
|
compute the quadrature rule. See the list below. |
|
|
|
alpha (input) real number, used as parameter for some orthogonal |
|
polynomials |
|
|
|
beta (input) real number, used as parameter for some orthogonal |
|
polynomials. |
|
|
|
return value |
|
|
|
(X, W) a pair of two real arrays where x_k = X[k] and w_k = W[k]. |
|
|
|
|
|
orthogonal polynomials: |
|
|
|
qtype polynomial |
|
----- ---------- |
|
|
|
"legendre" Legendre polynomials, W(x)=1 on the interval (-1, +1) |
|
"legendre01" shifted Legendre polynomials, W(x)=1 on the interval (0, +1) |
|
"hermite" Hermite polynomials, W(x)=exp(-x*x) on (-infinity,+infinity) |
|
"laguerre" Laguerre polynomials, W(x)=exp(-x) on (0,+infinity) |
|
"glaguerre" generalized Laguerre polynomials, W(x)=exp(-x)*x**alpha |
|
on (0, +infinity) |
|
"chebyshev1" Chebyshev polynomials of the first kind, W(x)=1/sqrt(1-x*x) |
|
on (-1, +1) |
|
"chebyshev2" Chebyshev polynomials of the second kind, W(x)=sqrt(1-x*x) |
|
on (-1, +1) |
|
"jacobi" Jacobi polynomials, W(x)=(1-x)**alpha * (1+x)**beta on (-1, +1) |
|
with alpha>-1 and beta>-1 |
|
|
|
examples: |
|
>>> from mpmath import mp |
|
>>> f = lambda x: x**8 + 2 * x**6 - 3 * x**4 + 5 * x**2 - 7 |
|
>>> X, W = mp.gauss_quadrature(5, "hermite") |
|
>>> A = mp.fdot([(f(x), w) for x, w in zip(X, W)]) |
|
>>> B = mp.sqrt(mp.pi) * 57 / 16 |
|
>>> C = mp.quad(lambda x: mp.exp(- x * x) * f(x), [-mp.inf, +mp.inf]) |
|
>>> mp.nprint((mp.chop(A-B, tol = 1e-10), mp.chop(A-C, tol = 1e-10))) |
|
(0.0, 0.0) |
|
|
|
>>> f = lambda x: x**5 - 2 * x**4 + 3 * x**3 - 5 * x**2 + 7 * x - 11 |
|
>>> X, W = mp.gauss_quadrature(3, "laguerre") |
|
>>> A = mp.fdot([(f(x), w) for x, w in zip(X, W)]) |
|
>>> B = 76 |
|
>>> C = mp.quad(lambda x: mp.exp(-x) * f(x), [0, +mp.inf]) |
|
>>> mp.nprint(mp.chop(A-B, tol = 1e-10), mp.chop(A-C, tol = 1e-10)) |
|
.0 |
|
|
|
# orthogonality of the chebyshev polynomials: |
|
>>> f = lambda x: mp.chebyt(3, x) * mp.chebyt(2, x) |
|
>>> X, W = mp.gauss_quadrature(3, "chebyshev1") |
|
>>> A = mp.fdot([(f(x), w) for x, w in zip(X, W)]) |
|
>>> print(mp.chop(A, tol = 1e-10)) |
|
0.0 |
|
|
|
references: |
|
- golub and welsch, "calculations of gaussian quadrature rules", mathematics of |
|
computation 23, p. 221-230 (1969) |
|
- golub, "some modified matrix eigenvalue problems", siam review 15, p. 318-334 (1973) |
|
- stroud and secrest, "gaussian quadrature formulas", prentice-hall (1966) |
|
|
|
See also the routine gaussq.f in netlog.org or ACM Transactions on |
|
Mathematical Software algorithm 726. |
|
""" |
|
|
|
d = ctx.zeros(n, 1) |
|
e = ctx.zeros(n, 1) |
|
z = ctx.zeros(1, n) |
|
|
|
z[0,0] = 1 |
|
|
|
if qtype == "legendre": |
|
|
|
w = 2 |
|
for i in xrange(n): |
|
j = i + 1 |
|
e[i] = ctx.sqrt(j * j / (4 * j * j - ctx.mpf(1))) |
|
elif qtype == "legendre01": |
|
|
|
w = 1 |
|
for i in xrange(n): |
|
d[i] = 1 / ctx.mpf(2) |
|
j = i + 1 |
|
e[i] = ctx.sqrt(j * j / (16 * j * j - ctx.mpf(4))) |
|
elif qtype == "hermite": |
|
|
|
w = ctx.sqrt(ctx.pi) |
|
for i in xrange(n): |
|
j = i + 1 |
|
e[i] = ctx.sqrt(j / ctx.mpf(2)) |
|
elif qtype == "laguerre": |
|
|
|
w = 1 |
|
for i in xrange(n): |
|
j = i + 1 |
|
d[i] = 2 * j - 1 |
|
e[i] = j |
|
elif qtype=="chebyshev1": |
|
|
|
w = ctx.pi |
|
for i in xrange(n): |
|
e[i] = 1 / ctx.mpf(2) |
|
e[0] = ctx.sqrt(1 / ctx.mpf(2)) |
|
elif qtype == "chebyshev2": |
|
|
|
w = ctx.pi / 2 |
|
for i in xrange(n): |
|
e[i] = 1 / ctx.mpf(2) |
|
elif qtype == "glaguerre": |
|
|
|
w = ctx.gamma(1 + alpha) |
|
for i in xrange(n): |
|
j = i + 1 |
|
d[i] = 2 * j - 1 + alpha |
|
e[i] = ctx.sqrt(j * (j + alpha)) |
|
elif qtype == "jacobi": |
|
|
|
alpha = ctx.mpf(alpha) |
|
beta = ctx.mpf(beta) |
|
ab = alpha + beta |
|
abi = ab + 2 |
|
w = (2**(ab+1)) * ctx.gamma(alpha + 1) * ctx.gamma(beta + 1) / ctx.gamma(abi) |
|
d[0] = (beta - alpha) / abi |
|
e[0] = ctx.sqrt(4 * (1 + alpha) * (1 + beta) / ((abi + 1) * (abi * abi))) |
|
a2b2 = beta * beta - alpha * alpha |
|
for i in xrange(1, n): |
|
j = i + 1 |
|
abi = 2 * j + ab |
|
d[i] = a2b2 / ((abi - 2) * abi) |
|
e[i] = ctx.sqrt(4 * j * (j + alpha) * (j + beta) * (j + ab) / ((abi * abi - 1) * abi * abi)) |
|
elif isinstance(qtype, str): |
|
raise ValueError("unknown quadrature rule \"%s\"" % qtype) |
|
elif not isinstance(qtype, str): |
|
w = qtype(d, e) |
|
else: |
|
assert 0 |
|
|
|
tridiag_eigen(ctx, d, e, z) |
|
|
|
for i in xrange(len(z)): |
|
z[i] *= z[i] |
|
|
|
z = z.transpose() |
|
return (d, w * z) |
|
|
|
|
|
|
|
|
|
|
|
def svd_r_raw(ctx, A, V = False, calc_u = False): |
|
""" |
|
This routine computes the singular value decomposition of a matrix A. |
|
Given A, two orthogonal matrices U and V are calculated such that |
|
|
|
A = U S V |
|
|
|
where S is a suitable shaped matrix whose off-diagonal elements are zero. |
|
The diagonal elements of S are the singular values of A, i.e. the |
|
squareroots of the eigenvalues of A' A or A A'. Here ' denotes the transpose. |
|
Householder bidiagonalization and a variant of the QR algorithm is used. |
|
|
|
overview of the matrices : |
|
|
|
A : m*n A gets replaced by U |
|
U : m*n U replaces A. If n>m then only the first m*m block of U is |
|
non-zero. column-orthogonal: U' U = B |
|
here B is a n*n matrix whose first min(m,n) diagonal |
|
elements are 1 and all other elements are zero. |
|
S : n*n diagonal matrix, only the diagonal elements are stored in |
|
the array S. only the first min(m,n) diagonal elements are non-zero. |
|
V : n*n orthogonal: V V' = V' V = 1 |
|
|
|
parameters: |
|
A (input/output) On input, A contains a real matrix of shape m*n. |
|
On output, if calc_u is true A contains the column-orthogonal |
|
matrix U; otherwise A is simply used as workspace and thus destroyed. |
|
|
|
V (input/output) if false, the matrix V is not calculated. otherwise |
|
V must be a matrix of shape n*n. |
|
|
|
calc_u (input) If true, the matrix U is calculated and replaces A. |
|
if false, U is not calculated and A is simply destroyed |
|
|
|
return value: |
|
S an array of length n containing the singular values of A sorted by |
|
decreasing magnitude. only the first min(m,n) elements are non-zero. |
|
|
|
This routine is a python translation of the fortran routine svd.f in the |
|
software library EISPACK (see netlib.org) which itself is based on the |
|
algol procedure svd described in: |
|
- num. math. 14, 403-420(1970) by golub and reinsch. |
|
- wilkinson/reinsch: handbook for auto. comp., vol ii-linear algebra, 134-151(1971). |
|
|
|
""" |
|
|
|
m, n = A.rows, A.cols |
|
|
|
S = ctx.zeros(n, 1) |
|
|
|
|
|
work = ctx.zeros(n, 1) |
|
|
|
g = scale = anorm = 0 |
|
maxits = 3 * ctx.dps |
|
|
|
for i in xrange(n): |
|
work[i] = scale*g |
|
g = s = scale = 0 |
|
if i < m: |
|
for k in xrange(i, m): |
|
scale += ctx.fabs(A[k,i]) |
|
if scale != 0: |
|
for k in xrange(i, m): |
|
A[k,i] /= scale |
|
s += A[k,i] * A[k,i] |
|
f = A[i,i] |
|
g = -ctx.sqrt(s) |
|
if f < 0: |
|
g = -g |
|
h = f * g - s |
|
A[i,i] = f - g |
|
for j in xrange(i+1, n): |
|
s = 0 |
|
for k in xrange(i, m): |
|
s += A[k,i] * A[k,j] |
|
f = s / h |
|
for k in xrange(i, m): |
|
A[k,j] += f * A[k,i] |
|
for k in xrange(i,m): |
|
A[k,i] *= scale |
|
|
|
S[i] = scale * g |
|
g = s = scale = 0 |
|
|
|
if i < m and i != n - 1: |
|
for k in xrange(i+1, n): |
|
scale += ctx.fabs(A[i,k]) |
|
if scale: |
|
for k in xrange(i+1, n): |
|
A[i,k] /= scale |
|
s += A[i,k] * A[i,k] |
|
f = A[i,i+1] |
|
g = -ctx.sqrt(s) |
|
if f < 0: |
|
g = -g |
|
h = f * g - s |
|
A[i,i+1] = f - g |
|
|
|
for k in xrange(i+1, n): |
|
work[k] = A[i,k] / h |
|
|
|
for j in xrange(i+1, m): |
|
s = 0 |
|
for k in xrange(i+1, n): |
|
s += A[j,k] * A[i,k] |
|
for k in xrange(i+1, n): |
|
A[j,k] += s * work[k] |
|
|
|
for k in xrange(i+1, n): |
|
A[i,k] *= scale |
|
|
|
anorm = max(anorm, ctx.fabs(S[i]) + ctx.fabs(work[i])) |
|
|
|
if not isinstance(V, bool): |
|
for i in xrange(n-2, -1, -1): |
|
V[i+1,i+1] = 1 |
|
|
|
if work[i+1] != 0: |
|
for j in xrange(i+1, n): |
|
V[i,j] = (A[i,j] / A[i,i+1]) / work[i+1] |
|
for j in xrange(i+1, n): |
|
s = 0 |
|
for k in xrange(i+1, n): |
|
s += A[i,k] * V[j,k] |
|
for k in xrange(i+1, n): |
|
V[j,k] += s * V[i,k] |
|
|
|
for j in xrange(i+1, n): |
|
V[j,i] = V[i,j] = 0 |
|
|
|
V[0,0] = 1 |
|
|
|
if m<n : minnm = m |
|
else : minnm = n |
|
|
|
if calc_u: |
|
for i in xrange(minnm-1, -1, -1): |
|
g = S[i] |
|
for j in xrange(i+1, n): |
|
A[i,j] = 0 |
|
if g != 0: |
|
g = 1 / g |
|
for j in xrange(i+1, n): |
|
s = 0 |
|
for k in xrange(i+1, m): |
|
s += A[k,i] * A[k,j] |
|
f = (s / A[i,i]) * g |
|
for k in xrange(i, m): |
|
A[k,j] += f * A[k,i] |
|
for j in xrange(i, m): |
|
A[j,i] *= g |
|
else: |
|
for j in xrange(i, m): |
|
A[j,i] = 0 |
|
A[i,i] += 1 |
|
|
|
for k in xrange(n - 1, -1, -1): |
|
|
|
|
|
|
|
its = 0 |
|
while 1: |
|
its += 1 |
|
flag = True |
|
|
|
for l in xrange(k, -1, -1): |
|
nm = l-1 |
|
|
|
if ctx.fabs(work[l]) + anorm == anorm: |
|
flag = False |
|
break |
|
|
|
if ctx.fabs(S[nm]) + anorm == anorm: |
|
break |
|
|
|
if flag: |
|
c = 0 |
|
s = 1 |
|
for i in xrange(l, k + 1): |
|
f = s * work[i] |
|
work[i] *= c |
|
if ctx.fabs(f) + anorm == anorm: |
|
break |
|
g = S[i] |
|
h = ctx.hypot(f, g) |
|
S[i] = h |
|
h = 1 / h |
|
c = g * h |
|
s = - f * h |
|
|
|
if calc_u: |
|
for j in xrange(m): |
|
y = A[j,nm] |
|
z = A[j,i] |
|
A[j,nm] = y * c + z * s |
|
A[j,i] = z * c - y * s |
|
|
|
z = S[k] |
|
|
|
if l == k: |
|
if z < 0: |
|
S[k] = -z |
|
if not isinstance(V, bool): |
|
for j in xrange(n): |
|
V[k,j] = -V[k,j] |
|
break |
|
|
|
if its >= maxits: |
|
raise RuntimeError("svd: no convergence to an eigenvalue after %d iterations" % its) |
|
|
|
x = S[l] |
|
nm = k-1 |
|
y = S[nm] |
|
g = work[nm] |
|
h = work[k] |
|
f = ((y - z) * (y + z) + (g - h) * (g + h))/(2 * h * y) |
|
g = ctx.hypot(f, 1) |
|
if f >= 0: f = ((x - z) * (x + z) + h * ((y / (f + g)) - h)) / x |
|
else: f = ((x - z) * (x + z) + h * ((y / (f - g)) - h)) / x |
|
|
|
c = s = 1 |
|
|
|
for j in xrange(l, nm + 1): |
|
g = work[j+1] |
|
y = S[j+1] |
|
h = s * g |
|
g = c * g |
|
z = ctx.hypot(f, h) |
|
work[j] = z |
|
c = f / z |
|
s = h / z |
|
f = x * c + g * s |
|
g = g * c - x * s |
|
h = y * s |
|
y *= c |
|
if not isinstance(V, bool): |
|
for jj in xrange(n): |
|
x = V[j ,jj] |
|
z = V[j+1,jj] |
|
V[j ,jj]= x * c + z * s |
|
V[j+1 ,jj]= z * c - x * s |
|
z = ctx.hypot(f, h) |
|
S[j] = z |
|
if z != 0: |
|
z = 1 / z |
|
c = f * z |
|
s = h * z |
|
f = c * g + s * y |
|
x = c * y - s * g |
|
|
|
if calc_u: |
|
for jj in xrange(m): |
|
y = A[jj,j ] |
|
z = A[jj,j+1] |
|
A[jj,j ] = y * c + z * s |
|
A[jj,j+1 ] = z * c - y * s |
|
|
|
work[l] = 0 |
|
work[k] = f |
|
S[k] = x |
|
|
|
|
|
|
|
|
|
|
|
for i in xrange(n): |
|
imax = i |
|
s = ctx.fabs(S[i]) |
|
|
|
for j in xrange(i + 1, n): |
|
c = ctx.fabs(S[j]) |
|
if c > s: |
|
s = c |
|
imax = j |
|
|
|
if imax != i: |
|
|
|
|
|
z = S[i] |
|
S[i] = S[imax] |
|
S[imax] = z |
|
|
|
if calc_u: |
|
for j in xrange(m): |
|
z = A[j,i] |
|
A[j,i] = A[j,imax] |
|
A[j,imax] = z |
|
|
|
if not isinstance(V, bool): |
|
for j in xrange(n): |
|
z = V[i,j] |
|
V[i,j] = V[imax,j] |
|
V[imax,j] = z |
|
|
|
return S |
|
|
|
|
|
|
|
def svd_c_raw(ctx, A, V = False, calc_u = False): |
|
""" |
|
This routine computes the singular value decomposition of a matrix A. |
|
Given A, two unitary matrices U and V are calculated such that |
|
|
|
A = U S V |
|
|
|
where S is a suitable shaped matrix whose off-diagonal elements are zero. |
|
The diagonal elements of S are the singular values of A, i.e. the |
|
squareroots of the eigenvalues of A' A or A A'. Here ' denotes the hermitian |
|
transpose (i.e. transposition and conjugation). Householder bidiagonalization |
|
and a variant of the QR algorithm is used. |
|
|
|
overview of the matrices : |
|
|
|
A : m*n A gets replaced by U |
|
U : m*n U replaces A. If n>m then only the first m*m block of U is |
|
non-zero. column-unitary: U' U = B |
|
here B is a n*n matrix whose first min(m,n) diagonal |
|
elements are 1 and all other elements are zero. |
|
S : n*n diagonal matrix, only the diagonal elements are stored in |
|
the array S. only the first min(m,n) diagonal elements are non-zero. |
|
V : n*n unitary: V V' = V' V = 1 |
|
|
|
parameters: |
|
A (input/output) On input, A contains a complex matrix of shape m*n. |
|
On output, if calc_u is true A contains the column-unitary |
|
matrix U; otherwise A is simply used as workspace and thus destroyed. |
|
|
|
V (input/output) if false, the matrix V is not calculated. otherwise |
|
V must be a matrix of shape n*n. |
|
|
|
calc_u (input) If true, the matrix U is calculated and replaces A. |
|
if false, U is not calculated and A is simply destroyed |
|
|
|
return value: |
|
S an array of length n containing the singular values of A sorted by |
|
decreasing magnitude. only the first min(m,n) elements are non-zero. |
|
|
|
This routine is a python translation of the fortran routine svd.f in the |
|
software library EISPACK (see netlib.org) which itself is based on the |
|
algol procedure svd described in: |
|
- num. math. 14, 403-420(1970) by golub and reinsch. |
|
- wilkinson/reinsch: handbook for auto. comp., vol ii-linear algebra, 134-151(1971). |
|
|
|
""" |
|
|
|
m, n = A.rows, A.cols |
|
|
|
S = ctx.zeros(n, 1) |
|
|
|
|
|
work = ctx.zeros(n, 1) |
|
lbeta = ctx.zeros(n, 1) |
|
rbeta = ctx.zeros(n, 1) |
|
dwork = ctx.zeros(n, 1) |
|
|
|
g = scale = anorm = 0 |
|
maxits = 3 * ctx.dps |
|
|
|
for i in xrange(n): |
|
dwork[i] = scale * g |
|
g = s = scale = 0 |
|
if i < m: |
|
for k in xrange(i, m): |
|
scale += ctx.fabs(ctx.re(A[k,i])) + ctx.fabs(ctx.im(A[k,i])) |
|
if scale != 0: |
|
for k in xrange(i, m): |
|
A[k,i] /= scale |
|
ar = ctx.re(A[k,i]) |
|
ai = ctx.im(A[k,i]) |
|
s += ar * ar + ai * ai |
|
f = A[i,i] |
|
g = -ctx.sqrt(s) |
|
if ctx.re(f) < 0: |
|
beta = -g - ctx.conj(f) |
|
g = -g |
|
else: |
|
beta = -g + ctx.conj(f) |
|
beta /= ctx.conj(beta) |
|
beta += 1 |
|
h = 2 * (ctx.re(f) * g - s) |
|
A[i,i] = f - g |
|
beta /= h |
|
lbeta[i] = (beta / scale) / scale |
|
for j in xrange(i+1, n): |
|
s = 0 |
|
for k in xrange(i, m): |
|
s += ctx.conj(A[k,i]) * A[k,j] |
|
f = beta * s |
|
for k in xrange(i, m): |
|
A[k,j] += f * A[k,i] |
|
for k in xrange(i, m): |
|
A[k,i] *= scale |
|
|
|
S[i] = scale * g |
|
g = s = scale = 0 |
|
|
|
if i < m and i != n - 1: |
|
for k in xrange(i+1, n): |
|
scale += ctx.fabs(ctx.re(A[i,k])) + ctx.fabs(ctx.im(A[i,k])) |
|
if scale: |
|
for k in xrange(i+1, n): |
|
A[i,k] /= scale |
|
ar = ctx.re(A[i,k]) |
|
ai = ctx.im(A[i,k]) |
|
s += ar * ar + ai * ai |
|
f = A[i,i+1] |
|
g = -ctx.sqrt(s) |
|
if ctx.re(f) < 0: |
|
beta = -g - ctx.conj(f) |
|
g = -g |
|
else: |
|
beta = -g + ctx.conj(f) |
|
|
|
beta /= ctx.conj(beta) |
|
beta += 1 |
|
|
|
h = 2 * (ctx.re(f) * g - s) |
|
A[i,i+1] = f - g |
|
|
|
beta /= h |
|
rbeta[i] = (beta / scale) / scale |
|
|
|
for k in xrange(i+1, n): |
|
work[k] = A[i, k] |
|
|
|
for j in xrange(i+1, m): |
|
s = 0 |
|
for k in xrange(i+1, n): |
|
s += ctx.conj(A[i,k]) * A[j,k] |
|
f = s * beta |
|
for k in xrange(i+1,n): |
|
A[j,k] += f * work[k] |
|
|
|
for k in xrange(i+1, n): |
|
A[i,k] *= scale |
|
|
|
anorm = max(anorm,ctx.fabs(S[i]) + ctx.fabs(dwork[i])) |
|
|
|
if not isinstance(V, bool): |
|
for i in xrange(n-2, -1, -1): |
|
V[i+1,i+1] = 1 |
|
|
|
if dwork[i+1] != 0: |
|
f = ctx.conj(rbeta[i]) |
|
for j in xrange(i+1, n): |
|
V[i,j] = A[i,j] * f |
|
for j in xrange(i+1, n): |
|
s = 0 |
|
for k in xrange(i+1, n): |
|
s += ctx.conj(A[i,k]) * V[j,k] |
|
for k in xrange(i+1, n): |
|
V[j,k] += s * V[i,k] |
|
|
|
for j in xrange(i+1,n): |
|
V[j,i] = V[i,j] = 0 |
|
|
|
V[0,0] = 1 |
|
|
|
if m < n : minnm = m |
|
else : minnm = n |
|
|
|
if calc_u: |
|
for i in xrange(minnm-1, -1, -1): |
|
g = S[i] |
|
for j in xrange(i+1, n): |
|
A[i,j] = 0 |
|
if g != 0: |
|
g = 1 / g |
|
for j in xrange(i+1, n): |
|
s = 0 |
|
for k in xrange(i+1, m): |
|
s += ctx.conj(A[k,i]) * A[k,j] |
|
f = s * ctx.conj(lbeta[i]) |
|
for k in xrange(i, m): |
|
A[k,j] += f * A[k,i] |
|
for j in xrange(i, m): |
|
A[j,i] *= g |
|
else: |
|
for j in xrange(i, m): |
|
A[j,i] = 0 |
|
A[i,i] += 1 |
|
|
|
for k in xrange(n-1, -1, -1): |
|
|
|
|
|
|
|
its = 0 |
|
while 1: |
|
its += 1 |
|
flag = True |
|
|
|
for l in xrange(k, -1, -1): |
|
nm = l - 1 |
|
|
|
if ctx.fabs(dwork[l]) + anorm == anorm: |
|
flag = False |
|
break |
|
|
|
if ctx.fabs(S[nm]) + anorm == anorm: |
|
break |
|
|
|
if flag: |
|
c = 0 |
|
s = 1 |
|
for i in xrange(l, k+1): |
|
f = s * dwork[i] |
|
dwork[i] *= c |
|
if ctx.fabs(f) + anorm == anorm: |
|
break |
|
g = S[i] |
|
h = ctx.hypot(f, g) |
|
S[i] = h |
|
h = 1 / h |
|
c = g * h |
|
s = -f * h |
|
|
|
if calc_u: |
|
for j in xrange(m): |
|
y = A[j,nm] |
|
z = A[j,i] |
|
A[j,nm]= y * c + z * s |
|
A[j,i] = z * c - y * s |
|
|
|
z = S[k] |
|
|
|
if l == k: |
|
if z < 0: |
|
S[k] = -z |
|
if not isinstance(V, bool): |
|
for j in xrange(n): |
|
V[k,j] = -V[k,j] |
|
break |
|
|
|
if its >= maxits: |
|
raise RuntimeError("svd: no convergence to an eigenvalue after %d iterations" % its) |
|
|
|
x = S[l] |
|
nm = k-1 |
|
y = S[nm] |
|
g = dwork[nm] |
|
h = dwork[k] |
|
f = ((y - z) * (y + z) + (g - h) * (g + h)) / (2 * h * y) |
|
g = ctx.hypot(f, 1) |
|
if f >=0: f = (( x - z) *( x + z) + h *((y / (f + g)) - h)) / x |
|
else: f = (( x - z) *( x + z) + h *((y / (f - g)) - h)) / x |
|
|
|
c = s = 1 |
|
|
|
for j in xrange(l, nm + 1): |
|
g = dwork[j+1] |
|
y = S[j+1] |
|
h = s * g |
|
g = c * g |
|
z = ctx.hypot(f, h) |
|
dwork[j] = z |
|
c = f / z |
|
s = h / z |
|
f = x * c + g * s |
|
g = g * c - x * s |
|
h = y * s |
|
y *= c |
|
if not isinstance(V, bool): |
|
for jj in xrange(n): |
|
x = V[j ,jj] |
|
z = V[j+1,jj] |
|
V[j ,jj]= x * c + z * s |
|
V[j+1,jj ]= z * c - x * s |
|
z = ctx.hypot(f, h) |
|
S[j] = z |
|
if z != 0: |
|
z = 1 / z |
|
c = f * z |
|
s = h * z |
|
f = c * g + s * y |
|
x = c * y - s * g |
|
if calc_u: |
|
for jj in xrange(m): |
|
y = A[jj,j ] |
|
z = A[jj,j+1] |
|
A[jj,j ]= y * c + z * s |
|
A[jj,j+1 ]= z * c - y * s |
|
|
|
dwork[l] = 0 |
|
dwork[k] = f |
|
S[k] = x |
|
|
|
|
|
|
|
|
|
|
|
for i in xrange(n): |
|
imax = i |
|
s = ctx.fabs(S[i]) |
|
|
|
for j in xrange(i + 1, n): |
|
c = ctx.fabs(S[j]) |
|
if c > s: |
|
s = c |
|
imax = j |
|
|
|
if imax != i: |
|
|
|
|
|
z = S[i] |
|
S[i] = S[imax] |
|
S[imax] = z |
|
|
|
if calc_u: |
|
for j in xrange(m): |
|
z = A[j,i] |
|
A[j,i] = A[j,imax] |
|
A[j,imax] = z |
|
|
|
if not isinstance(V, bool): |
|
for j in xrange(n): |
|
z = V[i,j] |
|
V[i,j] = V[imax,j] |
|
V[imax,j] = z |
|
|
|
return S |
|
|
|
|
|
|
|
@defun |
|
def svd_r(ctx, A, full_matrices = False, compute_uv = True, overwrite_a = False): |
|
""" |
|
This routine computes the singular value decomposition of a matrix A. |
|
Given A, two orthogonal matrices U and V are calculated such that |
|
|
|
A = U S V and U' U = 1 and V V' = 1 |
|
|
|
where S is a suitable shaped matrix whose off-diagonal elements are zero. |
|
Here ' denotes the transpose. The diagonal elements of S are the singular |
|
values of A, i.e. the squareroots of the eigenvalues of A' A or A A'. |
|
|
|
input: |
|
A : a real matrix of shape (m, n) |
|
full_matrices : if true, U and V are of shape (m, m) and (n, n). |
|
if false, U and V are of shape (m, min(m, n)) and (min(m, n), n). |
|
compute_uv : if true, U and V are calculated. if false, only S is calculated. |
|
overwrite_a : if true, allows modification of A which may improve |
|
performance. if false, A is not modified. |
|
|
|
output: |
|
U : an orthogonal matrix: U' U = 1. if full_matrices is true, U is of |
|
shape (m, m). ortherwise it is of shape (m, min(m, n)). |
|
|
|
S : an array of length min(m, n) containing the singular values of A sorted by |
|
decreasing magnitude. |
|
|
|
V : an orthogonal matrix: V V' = 1. if full_matrices is true, V is of |
|
shape (n, n). ortherwise it is of shape (min(m, n), n). |
|
|
|
return value: |
|
|
|
S if compute_uv is false |
|
(U, S, V) if compute_uv is true |
|
|
|
overview of the matrices: |
|
|
|
full_matrices true: |
|
A : m*n |
|
U : m*m U' U = 1 |
|
S as matrix : m*n |
|
V : n*n V V' = 1 |
|
|
|
full_matrices false: |
|
A : m*n |
|
U : m*min(n,m) U' U = 1 |
|
S as matrix : min(m,n)*min(m,n) |
|
V : min(m,n)*n V V' = 1 |
|
|
|
examples: |
|
|
|
>>> from mpmath import mp |
|
>>> A = mp.matrix([[2, -2, -1], [3, 4, -2], [-2, -2, 0]]) |
|
>>> S = mp.svd_r(A, compute_uv = False) |
|
>>> print(S) |
|
[6.0] |
|
[3.0] |
|
[1.0] |
|
|
|
>>> U, S, V = mp.svd_r(A) |
|
>>> print(mp.chop(A - U * mp.diag(S) * V)) |
|
[0.0 0.0 0.0] |
|
[0.0 0.0 0.0] |
|
[0.0 0.0 0.0] |
|
|
|
|
|
see also: svd, svd_c |
|
""" |
|
|
|
m, n = A.rows, A.cols |
|
|
|
if not compute_uv: |
|
if not overwrite_a: |
|
A = A.copy() |
|
S = svd_r_raw(ctx, A, V = False, calc_u = False) |
|
S = S[:min(m,n)] |
|
return S |
|
|
|
if full_matrices and n < m: |
|
V = ctx.zeros(m, m) |
|
A0 = ctx.zeros(m, m) |
|
A0[:,:n] = A |
|
S = svd_r_raw(ctx, A0, V, calc_u = True) |
|
|
|
S = S[:n] |
|
V = V[:n,:n] |
|
|
|
return (A0, S, V) |
|
else: |
|
if not overwrite_a: |
|
A = A.copy() |
|
V = ctx.zeros(n, n) |
|
S = svd_r_raw(ctx, A, V, calc_u = True) |
|
|
|
if n > m: |
|
if full_matrices == False: |
|
V = V[:m,:] |
|
|
|
S = S[:m] |
|
A = A[:,:m] |
|
|
|
return (A, S, V) |
|
|
|
|
|
|
|
@defun |
|
def svd_c(ctx, A, full_matrices = False, compute_uv = True, overwrite_a = False): |
|
""" |
|
This routine computes the singular value decomposition of a matrix A. |
|
Given A, two unitary matrices U and V are calculated such that |
|
|
|
A = U S V and U' U = 1 and V V' = 1 |
|
|
|
where S is a suitable shaped matrix whose off-diagonal elements are zero. |
|
Here ' denotes the hermitian transpose (i.e. transposition and complex |
|
conjugation). The diagonal elements of S are the singular values of A, |
|
i.e. the squareroots of the eigenvalues of A' A or A A'. |
|
|
|
input: |
|
A : a complex matrix of shape (m, n) |
|
full_matrices : if true, U and V are of shape (m, m) and (n, n). |
|
if false, U and V are of shape (m, min(m, n)) and (min(m, n), n). |
|
compute_uv : if true, U and V are calculated. if false, only S is calculated. |
|
overwrite_a : if true, allows modification of A which may improve |
|
performance. if false, A is not modified. |
|
|
|
output: |
|
U : an unitary matrix: U' U = 1. if full_matrices is true, U is of |
|
shape (m, m). ortherwise it is of shape (m, min(m, n)). |
|
|
|
S : an array of length min(m, n) containing the singular values of A sorted by |
|
decreasing magnitude. |
|
|
|
V : an unitary matrix: V V' = 1. if full_matrices is true, V is of |
|
shape (n, n). ortherwise it is of shape (min(m, n), n). |
|
|
|
return value: |
|
|
|
S if compute_uv is false |
|
(U, S, V) if compute_uv is true |
|
|
|
overview of the matrices: |
|
|
|
full_matrices true: |
|
A : m*n |
|
U : m*m U' U = 1 |
|
S as matrix : m*n |
|
V : n*n V V' = 1 |
|
|
|
full_matrices false: |
|
A : m*n |
|
U : m*min(n,m) U' U = 1 |
|
S as matrix : min(m,n)*min(m,n) |
|
V : min(m,n)*n V V' = 1 |
|
|
|
example: |
|
>>> from mpmath import mp |
|
>>> A = mp.matrix([[-2j, -1-3j, -2+2j], [2-2j, -1-3j, 1], [-3+1j,-2j,0]]) |
|
>>> S = mp.svd_c(A, compute_uv = False) |
|
>>> print(mp.chop(S - mp.matrix([mp.sqrt(34), mp.sqrt(15), mp.sqrt(6)]))) |
|
[0.0] |
|
[0.0] |
|
[0.0] |
|
|
|
>>> U, S, V = mp.svd_c(A) |
|
>>> print(mp.chop(A - U * mp.diag(S) * V)) |
|
[0.0 0.0 0.0] |
|
[0.0 0.0 0.0] |
|
[0.0 0.0 0.0] |
|
|
|
see also: svd, svd_r |
|
""" |
|
|
|
m, n = A.rows, A.cols |
|
|
|
if not compute_uv: |
|
if not overwrite_a: |
|
A = A.copy() |
|
S = svd_c_raw(ctx, A, V = False, calc_u = False) |
|
S = S[:min(m,n)] |
|
return S |
|
|
|
if full_matrices and n < m: |
|
V = ctx.zeros(m, m) |
|
A0 = ctx.zeros(m, m) |
|
A0[:,:n] = A |
|
S = svd_c_raw(ctx, A0, V, calc_u = True) |
|
|
|
S = S[:n] |
|
V = V[:n,:n] |
|
|
|
return (A0, S, V) |
|
else: |
|
if not overwrite_a: |
|
A = A.copy() |
|
V = ctx.zeros(n, n) |
|
S = svd_c_raw(ctx, A, V, calc_u = True) |
|
|
|
if n > m: |
|
if full_matrices == False: |
|
V = V[:m,:] |
|
|
|
S = S[:m] |
|
A = A[:,:m] |
|
|
|
return (A, S, V) |
|
|
|
@defun |
|
def svd(ctx, A, full_matrices = False, compute_uv = True, overwrite_a = False): |
|
""" |
|
"svd" is a unified interface for "svd_r" and "svd_c". Depending on |
|
whether A is real or complex the appropriate function is called. |
|
|
|
This routine computes the singular value decomposition of a matrix A. |
|
Given A, two orthogonal (A real) or unitary (A complex) matrices U and V |
|
are calculated such that |
|
|
|
A = U S V and U' U = 1 and V V' = 1 |
|
|
|
where S is a suitable shaped matrix whose off-diagonal elements are zero. |
|
Here ' denotes the hermitian transpose (i.e. transposition and complex |
|
conjugation). The diagonal elements of S are the singular values of A, |
|
i.e. the squareroots of the eigenvalues of A' A or A A'. |
|
|
|
input: |
|
A : a real or complex matrix of shape (m, n) |
|
full_matrices : if true, U and V are of shape (m, m) and (n, n). |
|
if false, U and V are of shape (m, min(m, n)) and (min(m, n), n). |
|
compute_uv : if true, U and V are calculated. if false, only S is calculated. |
|
overwrite_a : if true, allows modification of A which may improve |
|
performance. if false, A is not modified. |
|
|
|
output: |
|
U : an orthogonal or unitary matrix: U' U = 1. if full_matrices is true, U is of |
|
shape (m, m). ortherwise it is of shape (m, min(m, n)). |
|
|
|
S : an array of length min(m, n) containing the singular values of A sorted by |
|
decreasing magnitude. |
|
|
|
V : an orthogonal or unitary matrix: V V' = 1. if full_matrices is true, V is of |
|
shape (n, n). ortherwise it is of shape (min(m, n), n). |
|
|
|
return value: |
|
|
|
S if compute_uv is false |
|
(U, S, V) if compute_uv is true |
|
|
|
overview of the matrices: |
|
|
|
full_matrices true: |
|
A : m*n |
|
U : m*m U' U = 1 |
|
S as matrix : m*n |
|
V : n*n V V' = 1 |
|
|
|
full_matrices false: |
|
A : m*n |
|
U : m*min(n,m) U' U = 1 |
|
S as matrix : min(m,n)*min(m,n) |
|
V : min(m,n)*n V V' = 1 |
|
|
|
examples: |
|
|
|
>>> from mpmath import mp |
|
>>> A = mp.matrix([[2, -2, -1], [3, 4, -2], [-2, -2, 0]]) |
|
>>> S = mp.svd(A, compute_uv = False) |
|
>>> print(S) |
|
[6.0] |
|
[3.0] |
|
[1.0] |
|
|
|
>>> U, S, V = mp.svd(A) |
|
>>> print(mp.chop(A - U * mp.diag(S) * V)) |
|
[0.0 0.0 0.0] |
|
[0.0 0.0 0.0] |
|
[0.0 0.0 0.0] |
|
|
|
see also: svd_r, svd_c |
|
""" |
|
|
|
iscomplex = any(type(x) is ctx.mpc for x in A) |
|
|
|
if iscomplex: |
|
return ctx.svd_c(A, full_matrices = full_matrices, compute_uv = compute_uv, overwrite_a = overwrite_a) |
|
else: |
|
return ctx.svd_r(A, full_matrices = full_matrices, compute_uv = compute_uv, overwrite_a = overwrite_a) |
|
|