Skip to content

Hessenberg Form

Hessenberg form reduces a matrix to almost-triangular form.

Basic Decomposition

1. linalg.hessenberg

import numpy as np
from scipy import linalg

def main():
    A = np.array([[1, 2, 3, 4],
                  [5, 6, 7, 8],
                  [9, 10, 11, 12],
                  [13, 14, 15, 16]])

    H = linalg.hessenberg(A)

    print("A =")
    print(A)
    print()
    print("H (upper Hessenberg):")
    print(H.round(6))

if __name__ == "__main__":
    main()

2. Mathematical Form

\[A = QHQ^H\]

where: - \(Q\) is unitary - \(H\) is upper Hessenberg (zero below first subdiagonal)

3. Structure

Upper Hessenberg:
[* * * *]
[* * * *]
[0 * * *]
[0 0 * *]

With Transformation Matrix

1. Return Q

import numpy as np
from scipy import linalg

def main():
    A = np.array([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]])

    H, Q = linalg.hessenberg(A, calc_q=True)

    print("H (Hessenberg form):")
    print(H.round(6))
    print()
    print("Q (unitary):")
    print(Q.round(6))
    print()
    print("Verify Q @ H @ Q.T:")
    print((Q @ H @ Q.T).round(6))

if __name__ == "__main__":
    main()

2. Verify Orthogonality

import numpy as np
from scipy import linalg

def main():
    A = np.random.randn(4, 4)

    H, Q = linalg.hessenberg(A, calc_q=True)

    print("Q.T @ Q (should be identity):")
    print((Q.T @ Q).round(10))

if __name__ == "__main__":
    main()

Eigenvalue Computation

1. Why Hessenberg

import numpy as np
from scipy import linalg

def main():
    n = 5
    A = np.random.randn(n, n)

    # Eigenvalue algorithms work faster on Hessenberg form
    # QR iteration on H: O(n^2) per iteration vs O(n^3) on A

    H = linalg.hessenberg(A)

    # Eigenvalues are preserved
    eig_A = np.sort(np.linalg.eigvals(A))
    eig_H = np.sort(np.linalg.eigvals(H))

    print("Eigenvalues of A:")
    print(eig_A.round(6))
    print()
    print("Eigenvalues of H:")
    print(eig_H.round(6))

if __name__ == "__main__":
    main()

2. QR Iteration on Hessenberg

import numpy as np
from scipy import linalg

def main():
    A = np.array([[4, 1, 0],
                  [1, 3, 1],
                  [0, 1, 2]])

    # Reduce to Hessenberg first
    H = linalg.hessenberg(A)

    # QR iteration (faster on Hessenberg)
    Hk = H.copy()
    for _ in range(30):
        Q, R = np.linalg.qr(Hk)
        Hk = R @ Q

    print("Converged to quasi-triangular:")
    print(Hk.round(6))
    print()
    print("Eigenvalues (diagonal):")
    print(np.diag(Hk).round(6))

if __name__ == "__main__":
    main()

Symmetric Case

1. Tridiagonal Form

import numpy as np
from scipy import linalg

def main():
    # Symmetric matrix
    A = np.array([[4, 1, 0, 0],
                  [1, 4, 1, 0],
                  [0, 1, 4, 1],
                  [0, 0, 1, 4]])

    H = linalg.hessenberg(A)

    print("H for symmetric A (tridiagonal):")
    print(H.round(6))

if __name__ == "__main__":
    main()

2. Why Tridiagonal

For symmetric matrices, Hessenberg form becomes tridiagonal, enabling even faster algorithms.

Computational Cost

1. Complexity Analysis

import numpy as np
from scipy import linalg
import time

def main():
    sizes = [100, 200, 400, 800]

    print("Hessenberg reduction times:")
    print(f"{'n':>6} | {'Time (sec)':>12}")
    print("-" * 22)

    for n in sizes:
        A = np.random.randn(n, n)

        start = time.perf_counter()
        H = linalg.hessenberg(A)
        elapsed = time.perf_counter() - start

        print(f"{n:6d} | {elapsed:12.4f}")

if __name__ == "__main__":
    main()

2. Reduction is O(n³)

Initial reduction: \(O(n^3)\)

Each QR step on H: \(O(n^2)\) instead of \(O(n^3)\)

Applications

1. Polynomial Eigenvalues

import numpy as np
from scipy import linalg

def main():
    # Roots of p(x) = x^3 - 6x^2 + 11x - 6
    # Are eigenvalues of companion matrix

    coeffs = [1, -6, 11, -6]  # x^3 - 6x^2 + 11x - 6

    # Companion matrix is already Hessenberg
    C = np.array([[6, -11, 6],
                  [1, 0, 0],
                  [0, 1, 0]])

    roots = np.linalg.eigvals(C)

    print("Polynomial roots:")
    print(np.sort(roots.real).round(6))
    print()
    print("Verify: (x-1)(x-2)(x-3) = x^3 - 6x^2 + 11x - 6")

if __name__ == "__main__":
    main()

2. Transfer Function Analysis

import numpy as np
from scipy import linalg

def main():
    # State-space system: dx/dt = Ax
    A = np.array([[0, 1, 0],
                  [0, 0, 1],
                  [-6, -11, -6]])

    H, Q = linalg.hessenberg(A, calc_q=True)

    print("System in Hessenberg form:")
    print(H.round(6))
    print()

    # Eigenvalues determine stability
    eigs = np.linalg.eigvals(H)
    print("Poles (eigenvalues):")
    print(eigs.round(6))

if __name__ == "__main__":
    main()

3. Matrix Exponential Preprocessing

import numpy as np
from scipy import linalg

def main():
    A = np.array([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]])

    # Hessenberg reduction can accelerate exp(A) computation
    H, Q = linalg.hessenberg(A, calc_q=True)

    # exp(A) = Q @ exp(H) @ Q.T
    exp_H = linalg.expm(H)
    exp_A = Q @ exp_H @ Q.T

    print("exp(A) via Hessenberg:")
    print(exp_A.round(6))
    print()
    print("exp(A) direct:")
    print(linalg.expm(A).round(6))

if __name__ == "__main__":
    main()

Summary

1. Functions

Function Description
linalg.hessenberg(A) Hessenberg form
linalg.hessenberg(A, calc_q=True) With transformation Q

2. Key Properties

  • \(H\) is upper Hessenberg (zeros below subdiagonal)
  • Preserves eigenvalues
  • Symmetric A → Tridiagonal H
  • Speeds up QR iteration: \(O(n^2)\) vs \(O(n^3)\) per step