Reduction to Hessenberg form#
The upper-triangular Hessenberg form has zeros below the first sub-diagonal
\[\begin{split}
\newcommand{\star}{\color{red}*}
\begin{bmatrix}
\star & * & * & * & * & * \\
* & \star & * & * & * & * \\
0 & * & \star & * & * & * \\
0 & 0 & * & \star & * & * \\
0 & 0 & 0 & * & \star & * \\
0 & 0 & 0 & 0 & * & \star \\
\end{bmatrix}
\end{split}\]
import numpy as np
# We need sign function with sign(0) = 1
def sign(x):
if x >= 0.0:
return 1.0
else:
return -1.0
# Algorithm 26.1
def hessenberg(A):
m = A.shape[0]
for k in range(m-2):
x = A[k+1:m, k]
e1 = np.zeros_like(x)
e1[0] = 1.0
v = sign(x[0]) * np.linalg.norm(x) * e1 + x
v = v / np.linalg.norm(v)
A[k+1:m,k:m] = A[k+1:m,k:m] - 2.0 * np.outer(v, np.dot(v, A[k+1:m,k:m]))
A[0:m,k+1:m] = A[0:m,k+1:m] - 2.0 * np.outer(A[0:m,k+1:m] @ v, v)
return A
Test on a random matrix.
m = 7
A = np.random.rand(m,m)
H = hessenberg(A)
print(np.array_str(H, precision=2))
[[ 3.60e-01 -7.11e-01 7.19e-01 5.49e-01 -5.56e-01 6.03e-02 -5.06e-01]
[-1.01e+00 2.13e+00 -1.20e+00 -5.06e-02 1.88e-01 1.89e-01 1.72e-01]
[ 1.11e-16 -9.62e-01 5.47e-01 -3.44e-01 -1.53e-01 1.09e-01 -6.03e-01]
[ 0.00e+00 0.00e+00 -8.04e-01 -2.18e-01 -4.06e-01 -1.63e-01 1.46e-01]
[ 0.00e+00 0.00e+00 -6.94e-18 5.17e-01 -1.39e-01 1.75e-01 2.09e-01]
[ 2.78e-17 0.00e+00 -1.11e-16 0.00e+00 -5.89e-01 1.98e-01 -5.40e-01]
[ 5.55e-17 0.00e+00 -2.78e-17 0.00e+00 -1.11e-16 -8.54e-02 2.20e-01]]
Extract the lower triangular part which is supposed to be zero.
L = np.tril(H,k=-2)
print(np.array_str(L, precision=2))
[[ 0.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00]
[ 0.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00]
[ 1.11e-16 0.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00]
[ 0.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00 0.00e+00]
[ 0.00e+00 0.00e+00 -6.94e-18 0.00e+00 0.00e+00 0.00e+00 0.00e+00]
[ 2.78e-17 0.00e+00 -1.11e-16 0.00e+00 0.00e+00 0.00e+00 0.00e+00]
[ 5.55e-17 0.00e+00 -2.78e-17 0.00e+00 -1.11e-16 0.00e+00 0.00e+00]]
Compute the maximum over the zero elements.
print(np.abs(L).max())
1.1102230246251565e-16
Test on a larger matrix without printing.
m = 100
A = np.random.rand(m, m)
H = hessenberg(A)
L = np.tril(H,k=-2)
print(np.abs(L).max())
8.881784197001252e-16
Hermitian case#
In this case, the Hessenberg reduction should give a tridiagonal matrix.
m = 7
B = np.random.rand(m,m)
A = 0.5 * (B + B.T)
H = hessenberg(A)
print(np.array_str(H, precision=2, suppress_small=True))
[[ 0.37 -1.3 0. 0. 0. 0. 0. ]
[-1.3 2.21 0.83 0. 0. -0. -0. ]
[ 0. 0.83 -0.23 -0.55 0. 0. -0. ]
[ 0. 0. -0.55 0.38 0.2 0. -0. ]
[ 0. 0. 0. 0.2 0.5 0.1 0. ]
[ 0. -0. 0. -0. 0.1 0.26 -0.29]
[ 0. -0. 0. -0. 0. -0.29 -0.03]]
Remark#
The above function may not work correctly if the input matrix is complex. Write a complex version and test it.