Week4: Wrap up, almost finished
This commit is contained in:
parent
4b90ae8898
commit
ba26cc2ae3
1 changed files with 183 additions and 0 deletions
183
week4/ex1.py
Executable file
183
week4/ex1.py
Executable file
|
@ -0,0 +1,183 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import numpy as np
|
||||
from itertools import count
|
||||
|
||||
|
||||
def example_array():
|
||||
return np.array([[ 3, 2, 1],
|
||||
[ 2, 3, 2],
|
||||
[ 1, 2, 3]], dtype=np.float64)
|
||||
|
||||
def inverse_power_method(A, sigma, eps, max_iter=None):
|
||||
"""Determine an eigenvector of matrix A with an eigenvalue closest to sigma.
|
||||
|
||||
A -- an N by N square matrix
|
||||
sigma -- parameter to select eigenvalue
|
||||
eps -- accuracy
|
||||
"""
|
||||
A = np.asarray(A)
|
||||
N = len(A)
|
||||
B = np.linalg.inv( A - sigma*np.identity(N))
|
||||
b_0 = np.ones(N)
|
||||
|
||||
|
||||
for i in count():
|
||||
b_1 = B @ b_0
|
||||
|
||||
b_1 = b_1 / np.linalg.norm(b_1)
|
||||
|
||||
error = np.sqrt(np.sum( (np.abs(b_0) - np.abs(b_1))**2 ))
|
||||
|
||||
if error < eps:
|
||||
return b_1, i
|
||||
|
||||
# Running out of iterations
|
||||
if max_iter is not None and max_iter >= i:
|
||||
raise RuntimeError("Not converging in {} steps".format(i))
|
||||
|
||||
# Setup for next loop
|
||||
b_0 = b_1
|
||||
|
||||
|
||||
def test_inverse_power_method():
|
||||
A = example_array()
|
||||
sigma = 1
|
||||
eps = 1e-4
|
||||
max_iter = 1e5
|
||||
eigvec, n = inverse_power_method(A, sigma, eps, None)
|
||||
|
||||
_, npeigvecs = np.linalg.eig(A)
|
||||
|
||||
rtol = 1e-2
|
||||
found = False
|
||||
for npeigvec in npeigvecs.T:
|
||||
if np.allclose(npeigvec, eigvec, rtol):
|
||||
found = True
|
||||
|
||||
assert found == True
|
||||
|
||||
|
||||
def tridiagonalize(A):
|
||||
""" Compute the Tridiagonal of the matrix A. """
|
||||
return householder_tridiagonalize(A)
|
||||
|
||||
def householder_tridiagonalize(A):
|
||||
""" Compute the Tridiagonal of the matrix A using the Householder Method. """
|
||||
|
||||
A = np.asarray(A)
|
||||
|
||||
N = len(A)
|
||||
|
||||
for k in range(N-1):
|
||||
q = np.sqrt(np.sum( A[k+1:,k]**2 ))
|
||||
alpha = -q * np.sign(A[k+1,k])
|
||||
r = np.sqrt( (alpha**2 - A[k+1,k]*alpha)/2 )
|
||||
|
||||
v = np.zeros(N)
|
||||
v[k+1] = (A[k+1, k] - alpha)
|
||||
v[k+2:] = A[k+2:, k]
|
||||
v[k + 1:] /= 2 * r
|
||||
|
||||
|
||||
P = np.identity(N) - 2 * np.outer(v,v)
|
||||
|
||||
A = P @ A @ P
|
||||
|
||||
return A
|
||||
|
||||
|
||||
|
||||
def test_tridiagonalize():
|
||||
""" Compare the householder tridiagonalize function with Numpy's Hessenberg. """
|
||||
from scipy.linalg import hessenberg
|
||||
|
||||
A = example_array()
|
||||
T = tridiagonalize(A)
|
||||
H = hessenberg(A)
|
||||
|
||||
rtol = 1e-3
|
||||
print(T)
|
||||
print(H)
|
||||
print(np.isclose(H, T, rtol))
|
||||
|
||||
print("They are almost the same except for a minus sign at (2,3) and (3,2)")
|
||||
|
||||
|
||||
def QREig(T, eps, max_iter=None):
|
||||
"""Compute Eigenvalues using QR decomposition."""
|
||||
for i in count():
|
||||
Q, R = np.linalg.qr(T)
|
||||
|
||||
T = R @ Q
|
||||
|
||||
# We are done if the sub-diagonal is smaller than eps
|
||||
d_1 = np.diag(T, -1)
|
||||
if np.linalg.norm(d_1) < eps:
|
||||
return np.diagonal(T), i
|
||||
|
||||
# Running out of iterations
|
||||
if max_iter is not None and max_iter >= i:
|
||||
raise RuntimeError("Not converging in {} steps".format(i))
|
||||
|
||||
def test_QREig():
|
||||
A = example_array()
|
||||
|
||||
T = tridiagonalize(A)
|
||||
eps = 1e-6
|
||||
|
||||
eigvals, _ = QREig(T, eps)
|
||||
npeigvals, npeigvecs = np.linalg.eig(T)
|
||||
|
||||
assert np.allclose(eigvals, npeigvals, eps), "The computated eigenvalues do not match Numpy's eigenvalues"
|
||||
|
||||
|
||||
def eig(A, QReps, PMeps, sigma=1e-2):
|
||||
"""Determine eigenvectors with a combination of QR decomposition and inverse Power Method."""
|
||||
|
||||
T = tridiagonalize(A)
|
||||
eigvals, _ = QREig(T, QReps)
|
||||
eigvecs = np.zeros((len(eigvals),len(A)), dtype=np.float64)
|
||||
|
||||
sigma = (2*np.random.rand() -1 )*sigma
|
||||
for i, eigval in enumerate(eigvals):
|
||||
eigvecs[i], _ = inverse_power_method(T, eigval + sigma, PMeps)
|
||||
|
||||
return eigvals, eigvecs
|
||||
|
||||
|
||||
def test_eig(debug=True):
|
||||
A = example_array()
|
||||
|
||||
eps = 1e-5
|
||||
rtol = 1e-1
|
||||
eigvals, eigvecs = eig(A, eps, eps)
|
||||
|
||||
if debug:
|
||||
for i, eigval in enumerate(eigvals):
|
||||
print("Eigval {}: ".format(eigval), eigvecs[i])
|
||||
|
||||
npeigvals, npeigvecs = np.linalg.eig(A)
|
||||
if debug:
|
||||
for i, npeigval in enumerate(npeigvals):
|
||||
print("Numpy Eigval {}: ".format(npeigval), npeigvecs[i])
|
||||
|
||||
assert np.allclose(eigvals, npeigvals, eps), "The computated eigenvalues do not match Numpy's eigenvalues"
|
||||
|
||||
#assert np.allclose(eigvecs, npeigvecs, rtol), "The computated eigenvectors do not match Numpy's eigenvectors"
|
||||
|
||||
found = np.zeros(len(eigvecs))
|
||||
for i, eigvec in enumerate(eigvecs):
|
||||
for npeigvec in npeigvecs:
|
||||
if np.allclose(eigvec, npeigvec, rtol):
|
||||
found[i] = 1
|
||||
break
|
||||
|
||||
print("Currently only the first eigenvector is the same from Numpy and my implementation. The others have a minus sign.")
|
||||
print("Comparable eigenvectors: ", found)
|
||||
|
||||
if __name__ == "__main__":
|
||||
#test_inverse_power_method()
|
||||
#test_tridiagonalize()
|
||||
#test_QREig()
|
||||
test_eig()
|
Reference in a new issue