QR algorithm for eigenvalues#
import numpy as np
from numpy.linalg import qr, eig
from scipy.linalg import hessenberg
Pure QR algorithm#
# Algorithm 28.1
def qr_pure(A, maxiter=1000, tol=1e-12):
for k in range(maxiter):
Q, R = qr(A)
A = R @ Q
sub_diag = np.diag(A,-1)
if np.abs(sub_diag).max() < tol:
print('Converged in iterations = ', k+1)
return A
print('Did not converge in maxiter =', maxiter)
return A
m = 5
B = np.random.random((m,m))
A = 0.5 * (B + B.T)
evals,evecs = eig(A)
print('Eigenvalues = ', evals)
Eigenvalues = [ 2.64291079 0.61003094 -0.55932349 -0.26836586 0.11833976]
D = qr_pure(A.copy())
print(np.array_str(D,suppress_small=True))
Converged in iterations = 317
[[ 2.64291079 -0. -0. 0. 0. ]
[ 0. 0.61003094 -0. -0. 0. ]
[-0. -0. -0.55932349 0. -0. ]
[-0. 0. 0. -0.26836586 -0. ]
[ 0. 0. -0. -0. 0.11833976]]
Eigenvalues appear on diagonal with decreasing magnitude.
Practical QR algorithm#
We perform an initial reduction to Hessenberg form and use shifted QR with Rayleigh-quotient shifts.
# Algorithm 28.2
def qr_with_shifts(A, maxiter=1000, tol=1e-12):
m = A.shape[0]
H = hessenberg(A)
for k in range(maxiter):
mu = H[-1,-1]
Q, R = qr(H - mu*np.eye(m))
H = R @ Q + mu * np.eye(m)
sub_diag = np.diag(H,-1)
if np.abs(sub_diag).max() < tol:
print('Converged in iterations = ', k+1)
return H
print('Did not converge in maxiter =', maxiter)
return H
TODO: Add deflation step.
D = qr_with_shifts(A.copy())
print(np.array_str(D,suppress_small=True))
Converged in iterations = 109
[[ 2.64291079 0. -0. 0. -0. ]
[ 0. -0.55932349 -0. -0. -0. ]
[ 0. -0. 0.61003094 0. 0. ]
[ 0. 0. 0. -0.26836586 -0. ]
[ 0. 0. 0. 0. 0.11833976]]
The convergence is usually faster than pure QR. The eigenvalues may not be ordered in any particular way on the diagonal.