Skip to content
The Lumenary

Math laboratory

Eigenvalues of a compact operator, numerically

Discretises the Green's operator of -u'' on (0, 1) by an N x N symmetric matrix and compares its eigenvalues with the exact 1/(k pi)^2: they agree to about 2e-6, the eigenvectors are orthonormal, and the eigenvalues accumulate only at 0. Exact computations show that differentiation has no bound and that sup and L^1 norms are inequivalent. Numerical evidence for one operator; the spectral theorem is proved in the lesson.

Back to the labNumerical approximationExact computation
Eigenvalues of a compact operator, numericallyPythonNumerical approximationExact computationComputationally tested
"""The spectrum of a compact self-adjoint operator, seen through a matrix.

The operator (Gf)(x) = integral of G(x, y) f(y) dy on L^2(0, 1), with kernel
G(x, y) = min(x, y) (1 - max(x, y)), inverts -u'' = f, u(0) = u(1) = 0. It is
compact and self-adjoint, with eigenvalues 1/(k pi)^2 and eigenfunctions
sqrt(2) sin(k pi x). We discretise it by the midpoint rule (an N x N
symmetric matrix), compare its eigenvalues with the exact ones, and count how
they pile up at 0. Then two infinite-dimensional warnings: differentiation is
unbounded, and the sup and L^1 norms are not equivalent.
"""

import math
from fractions import Fraction

import numpy as np

def green_matrix(n):
    """Midpoint-rule matrix (1/n) G(x_i, x_j) with x_i = (i + 1/2)/n."""
    if n < 1:
        raise ValueError("n must be positive")
    x = (np.arange(n) + 0.5) / n
    big_x, big_y = np.meshgrid(x, x, indexing="ij")
    kernel = np.minimum(big_x, big_y) * (1 - np.maximum(big_x, big_y))
    return kernel / n

def eigen_decreasing(matrix):
    """Eigenvalues (decreasing) and orthonormal eigenvectors of a symmetric
    matrix."""
    values, vectors = np.linalg.eigh(matrix)
    return values[::-1], vectors[:, ::-1]

def exact_eigenvalue(k):
    """The k-th eigenvalue 1/(k pi)^2 of the Green's operator."""
    return 1 / (k * math.pi) ** 2

def count_above(values, threshold):
    """How many eigenvalues exceed the threshold."""
    return int(np.sum(np.asarray(values) > threshold))

def exact_count_above(threshold):
    """Number of k >= 1 with 1/(k pi)^2 > threshold."""
    k = 0
    while exact_eigenvalue(k + 1) > threshold:
        k += 1
    return k

def derivative_ratio(n, samples=4000):
    """max |f'| / max |f| for f(x) = sin(n x) on [0, 2 pi], sampled."""
    xs = np.linspace(0, 2 * math.pi, samples)
    return float(np.max(np.abs(n * np.cos(n * xs))) /
                 np.max(np.abs(np.sin(n * xs))))

def power_norms(n):
    """Exact (sup norm, L^1 norm) of x^n on [0, 1]."""
    return Fraction(1), Fraction(1, n + 1)

def main():
    n = 200
    values, vectors = eigen_decreasing(green_matrix(n))
    print(f"Green's operator of -u'' on (0, 1), midpoint matrix with N = {n}")
    print(f"{'k':>3} {'matrix':>12} {'exact 1/(k pi)^2':>18} {'rel. error':>11}")
    for k in range(1, 7):
        exact = exact_eigenvalue(k)
        err = abs(values[k - 1] - exact) / exact
        print(f"{k:>3} {values[k - 1]:>12.8f} {exact:>18.8f} {err:>11.1e}")
    orth = np.max(np.abs(vectors.T @ vectors - np.eye(n)))
    print(f"max |V^T V - I| = {orth:.1e} (orthonormal eigenvectors)")
    first = vectors[:, 0] * math.sqrt(n)
    sine = math.sqrt(2) * np.sin(math.pi * (np.arange(n) + 0.5) / n)
    first = first if first @ sine > 0 else -first
    print(f"first eigenvector vs sqrt(2) sin(pi x): max gap "
          f"{np.max(np.abs(first - sine)):.1e}")
    for t in [1e-1, 1e-2, 1e-3]:
        print(f"eigenvalues > {t:g}: matrix {count_above(values, t)}, "
              f"exact {exact_count_above(t)}")
    print(f"smallest matrix eigenvalue: {values[-1]:.2e} (they pile up at 0)")
    print()
    print("Differentiation is unbounded: f = sin(n x), ratio max|f'| / max|f|")
    print("  ", [round(derivative_ratio(m), 3) for m in [1, 10, 100]])
    print("Norms are not equivalent: x^n on [0, 1], (sup norm, L^1 norm)")
    print("  ", [tuple(str(v) for v in power_norms(m)) for m in [1, 10, 100]])
    print()
    print("Epistemic status: the eigenvalue table is a numerical approximation")
    print("of one operator (midpoint rule, floating point), within about 2e-6")
    print("of the exact eigenvalues; it proves nothing in general. The derivative")
    print("ratios are sampled, the norms of x^n exact. That no constant bounds")
    print("either ratio follows because n and n + 1 are unbounded.")

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

Used in