shifting does not improve
Lanczos
/Arnoldi
Concept
Lanczos iteration for symmetric
import numpy as np
def lanczos_iter(A, tol=1e-8):
n = A.shape[0]
# q0 = np.zeros(n) # q0 = 0
# b0 = 0 # b0 = 0
x0 = np.random.rand(n)
q1 = x0 / np.linalg.norm(x0)
Q = [q1] # start with k = 1
alphas = []
betas = [b0]
qk = q1
qk_prev = q0
bk_prev = b0
for k in range(1, n+1):
# u_k = A*q_k
uk = A @ qk
# .T same as .H cause it's real
# a_k = q_k^T * u_k
ak = qk.T @ uk
alphas.append(ak)
# u_k = u_k - b_{k-1}*q_{k-1} - a_k*q_k
uk = uk - bk_prev * qk_prev - ak * qk
# b_k = ||u_k||
bk = np.linalg.norm(uk)
if abs(bk) < tol:
break
betas.append(bk)
# q_{k+1} = u_k/b_k
qk_next = uk / bk
Q.append(qk_next)
qk_prev = qk
qk = qk_next
bk_prev = bk
m = len(alphas)
T = np.zeros((m, m))
np.fill_diagonal(T, alphas)
for i in range(m-1):
T[i, i+1] = betas[i+1]
T[i+1, i] = betas[i+1]
return np.column_stack(Q), T
Optimization
turn to Symmetric positive definite matrix.
Arnoldi iteration for nonsymmetric
import numpy as np
def arnoldi_iter(A, tol=1e-8):
n = A.shape[0]
x0 = np.random.rand(n)
q1 = x0 / np.linalg.norm(x0)
# Initialize properly
Q = np.zeros((n, n+1))
Q[:, 0] = q1
H = np.zeros((n+1, n))
for k in range(n):
v = A @ Q[:, k]
# Orthogonalization
for j in range(k+1):
H[j, k] = Q[:, j].T @ v
v = v - H[j, k] * Q[:, j]
H[k+1, k] = np.linalg.norm(v)
if H[k+1, k] < tol:
break
Q[:, k+1] = v / H[k+1, k]
return Q[:, :k+1], H[:k+2, :k+1]