Arnoldi Iteration
QR Factorization
def gram_schmidt_unstable(A: np.ndarray):
"""
Algorithm 7.1 in Book Trefethen&Bau Numerical Linear Algebra.
A: m*n matrix, m >= n, very high matrix, full column rank.
test:
A = np.random.rand(5, 3) + 1j*np.random.rand(5, 3)
Q, R = gram_schmidt_unstable(A)
Q1, R1 = np.linalg.qr(A)
print(Q)
print(Q1)
print(R)
print(R1)
"""
_, n = A.shape
dtype = np.result_type(A, np.float64)
R = np.zeros((n, n), dtype=dtype)
Q = np.zeros(A.shape, dtype=dtype)
for i in range(n):
Q[:, i] = A[:, i]
for j in range(i):
R[j, i] = Q[:, j].conj()@A[:, i]
Q[:, i] = Q[:, i] - R[j, i]*Q[:, j]
R[i, i] = np.linalg.norm(Q[:, i])
Q[:, i] = Q[:, i] / R[i, i]
return Q, R
def gram_schmidt_modified(A: np.ndarray):
"""
Algorithm 8.1 in Book Trefethen&Bau Numerical Linear Algebra.
A: m*n matrix, m >= n, very high matrix, full column rank.
A = np.random.rand(3, 3) + 1j*np.random.rand(3, 3)
Q, R = gram_schmidt_modified(A)
Q1, R1 = np.linalg.qr(A)
print(Q)
print(Q1)
print(R)
print(R1)
"""
_, n = A.shape
dtype = np.result_type(A, np.float64)
R = np.zeros((n, n), dtype=dtype)
Q = A.astype(dtype, copy=True)
for i in range(n):
R[i, i] = np.linalg.norm(Q[:, i])
Q[:, i] = Q[:, i] / R[i, i]
for j in range(i+1, n):
R[i, j] = Q[:, i].conj()@Q[:, j]
Q[:, j] = Q[:, j] - R[i, j]*Q[:, i]
return Q, R
Power Iteration
def power_iteration(A: np.ndarray, n: int):
"""
test:
dim = 50
# A = np.random.rand(dim, dim)
A = np.random.uniform(-np.sqrt(3/dim), np.sqrt(3/dim), size=(dim, dim))
A = A + A.conj().T
w, v = np.linalg.eig(A)
n = 150
iter_num = list(range(1, n+1))
eigmax = [res[0] for res in power_iteration(A, n)]
fig = px.line(x=iter_num, y=eigmax)
for wi in w:
fig.add_scatter(x=iter_num, y=[np.real(wi)]*n,
mode='lines', line=dict(color='grey'))
fig.show()
"""
x = np.random.rand(A.shape[0])
ev = x / np.linalg.norm(x)
for _ in range(n):
x = ev
ev = A@x
lam = x.conj()@ev
ev = ev / np.linalg.norm(ev)
yield lam, ev
Arnoldi Iteration
Definition
这里采用线性代数中常用的记号,
与 power iteration 不同的是,Arnoldi Iteration 每一步都做正交化,类似于 power iteration 与 QR 分解的结合,
$$ \begin{align} A = Q R = Q H Q^*, \quad AQ = Q H. \end{align} $$递推公式
$$ \begin{align} A Q_n = Q_{n + 1} \tilde{H}_n, \end{align} $$即新一步的会新生一个新的向量,它是
也就是
$$ \begin{align} A w_{n-1} = w_0 H_{0, n-1} + w_1 H_{1, n-1} + \cdots + w_{n}H_{n, n-1}. \end{align} $$手算示例
$$ \begin{align} A = \begin{pmatrix} 1 & 3 & 0 \\ 4 & 3 & 7 \\ 7 & 4 & 1 \end{pmatrix} \end{align} $$$$ \begin{align} b = \begin{pmatrix} 0 \\ 2 \\ 0 \end{pmatrix} \end{align} $$Initialize
$$ \begin{align} w_0 = \frac{b}{||b||} = \begin{pmatrix} 0 \\ 1 \\ 0 \end{pmatrix} \end{align} $$$$ \begin{align} Q = [w_0] \end{align} $$Step 1
用
根据定义计算 $H_{00}$ ,也就是
同时它也是我们下一步需要的。减去在
计算模长,然后归一化,
$$ \begin{align} ||w_1^{0}|| = 5, \end{align} $$$$ \begin{align} w_1 = \frac{w_1^{(0)}}{||w_1^{(0)}||} = \begin{pmatrix} \frac{3}{5} \\ 0 \\ \frac{4}{5} \end{pmatrix}. \end{align} $$同时根据 $H_{10}$ 的定义,发现它就是模长,
$$ \begin{align} H_{10} = w_1^* A w_0 = \frac{1}{||w_1^{(0)}||} w_1^{(0)*} A w_0 = ||w_1^{(0)}|| = 5, \end{align} $$其中投影 $w_1^{(0)*} A w_0$ 的物理意义也很明确,
$$ \begin{align} w_1^{(0)*} A w_0 = w_1^{(0)*} w_1^{(1)} = w_1^{(0)*}\left[ w_1^{(0)} + w^*_0 w_1^{(0)}\cdot w_0 \right], \end{align} $$第一项是在 $w_1^{(0)}$ 方向上的分量,即为模长 $||w_1^{(0)}||^2$ ,
第二项是在与之垂直的方向上的分量,为
由此就得了本步的结果:
$$ \begin{align} Q = [w_0, w_1] = \begin{pmatrix} 0 & \frac{3}{5} \\ 1 & 0 \\ 0 & \frac{4}{5} \end{pmatrix}, \end{align} $$$$ \begin{align} H = \begin{pmatrix} 3 \\ 5 \end{pmatrix}, \end{align} $$验证,
$$ \begin{align} Q[0]^* A Q[0] = 3 = H_{00}. \end{align} $$Step 2
用
计算并减掉投影,
$$ \begin{align} H_{01} = w_0^* A w_1 = w^*_0 w_2^{(2)} = 8. \end{align} $$$$ \begin{align} w_2^{(1)} = \begin{pmatrix} \frac{3}{5} \\ 0 \\ 5 \end{pmatrix}. \end{align} $$计算另一个方向上的投影并减掉,
$$ \begin{align} H_{11} = w_1^* A w_1 = w^*_1 w_2^{(1)} = \frac{109}{25}, \end{align} $$$$ \begin{align} w_2^{(0)} = w_2^{(1)} - w_1^* w_2^{(1)} w_1 = \frac{63}{125}\begin{pmatrix} 4 \\ 0 \\ 3 \end{pmatrix}. \end{align} $$计算模长,并归一化,
$$ \begin{align} H_{21} = || w_2^{(0)} || = \frac{63}{25}. \end{align} $$得到本步的结果,
$$ \begin{align} w_2 = \frac{1}{5}\begin{pmatrix} -4 \\ 0 \\3 \end{pmatrix}. \end{align} $$$$ \begin{align} Q = [w_0, w_1, w_2] \end{align} $$$$ \begin{align} H = \begin{pmatrix} 3 & 0 \\ 5 & 8 \\ 0 & \frac{63}{25} \end{pmatrix} \end{align} $$code
def arnoldi_iteration(A: np.ndarray, n: int, b=None, output_all=False):
"""
Algorithm 33.1 in Book Trefethen&Bau Numerical Linear Algebra.
A: square matrix, full rank (no happy breakdown).
Good example:
A = np.array([[1, 3, 0],
[4, 3, 7],
[7, 4, 1]])
b = np.array([0, 2, 0])
arnoldi_iteration(A=A, b=b, n=2, output_all=True)
"""
if b is None:
b = np.random.rand(A.shape[0])
dtype = np.result_type(A, b, np.float64)
Q = [b/np.linalg.norm(b)]
H = np.zeros((n+1, n), dtype=dtype)
for i in range(n):
v = A@Q[-1]
for j in range(i+1):
H[j, i] = Q[j].conj()@v
v = v - H[j, i]*Q[j]
print('===', v)
H[i+1, i] = np.linalg.norm(v)
Q.append(v/H[i+1, i])
if output_all:
return np.column_stack(Q), H
else:
return np.column_stack(Q)[:, :-1], H[:-1, :]
Arnoldi and Polynomial Approximation
由递推式能看出 $w_n$ 是关于
的 Residual 。
Roots of $p^n(z)$
$w_n = p^n(A)b \bot \mathcal{K}_{n - 1}$ 可以写成,
$$ \begin{align} Q_n^* \cdot p^n(A) b = 0 \end{align} $$变换到
其中 $Q^* b = (||b||, 0, 0, \cdots)^T$ , 所以 $p^n(H) \cdot Q^* b = ||b|| \cdot p^n(H)[0]$ ,是 $p^n(H)$ 第一列。 由正交性可得 $Q_n^* Q = (I_n, \mathbf{0})$ 。最终
$$ \begin{align} Q_n^* \cdot p^n(A) b = ||b||\cdot p^n(H)[:n, 0] = 0 \end{align} $$由于 $p^n(H)$ 左下角为
即是我们要的 $p^n(z)$ 。
Arnoldi Lemniscates
dim = 100
A = a = np.random.normal(loc=0, scale=1/np.sqrt(dim), size=(dim, dim))
A[0, 0] = 1.5
b = np.random.rand(A.shape[0])
w, v = np.linalg.eig(A)
n = 8
Qn, Hn = arnoldi_iteration(A=A, n=n, b=b)
thetas, _ = np.linalg.eig(Hn)
pz = np.prod(1-thetas)
n_data = 500
re = np.linspace(-2, 2, n_data)
im = np.linspace(-2, 2, n_data)
RE, IM = np.meshgrid(re, im)
Z = RE + 1j*IM
abs_p = np.prod([np.abs(Z-theta) for theta in thetas], axis=0)
abs_p = np.log(abs_p)
coeff = np.poly(thetas)
C = coeff[0] * b
for c in coeff[1:]:
C = A@C + c*b
C = np.linalg.norm(C) / np.linalg.norm(b)
C = np.log(C)
# print(C)
fig = px.scatter(x=w.real, y=w.imag)
# fig.add_trace(go.Contour(x=re, y=im, z=abs_p))
fig.add_trace(go.Contour(x=re, y=im, z=abs_p,
# contours=dict(start=C, end=C,
# size=1, coloring='lines'),
# line=dict(color='black', width=1),
# colorscale=[[0, "black"], [1, "black"]]
))
# fig.update_yaxes(scaleanchor='x', scaleratio=1)
# fig.update_layout(width=700)
# print(Hn)
fig.show()
Reference
- 1997 - Lloyd N. Trefethen, David Bau - Numerical Linear Algebra