Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xSTEMR: are zero eigenvalues sufficiently accurate? #999

Open
christoph-conrads opened this issue Mar 23, 2024 · 0 comments
Open

xSTEMR: are zero eigenvalues sufficiently accurate? #999

christoph-conrads opened this issue Mar 23, 2024 · 0 comments

Comments

@christoph-conrads
Copy link
Contributor

christoph-conrads commented Mar 23, 2024

Continuing from #997, the SciPy maintainers noted that xSTEMR computes zero eigenvalues less accurately than expected for a full real symmetric 3x3 matrix with eigenvalues 3, 1, and 0. I thought that this is just a side effect of the small size but testing with random matrices shows that xSTEMR can compute an approximation as large as 20 ε max(|λ|) to the smallest eigenvalue for a 6x6 matrix with eigenvalues 3, 3, 3, 3, 1, 0 (code below). Personally, I am not sure I would consider such an eigenvalue zero anymore because it is larger than ε n max(|λ|), where n is the dimension of the matrix.

Is this approximation of a zero eigenvalue considered a bug?

Democode (requires Python3, NumPy, and Python):

#!/usr/bin/python3

# The code below computes with various methods ("drivers" in SciPy lingo) the
# eigenvalues of a random matrix with fixed eigenvalues and computes the
# accuracy of one of the eigenvalues. A five-number summary is shown for each
# method.

import sys

import numpy as np
import numpy.linalg as la
import scipy
from scipy.linalg import pinvh, pinv


def sample_smallest_ev(rng, num_samples: int, driver: str, dtype=np.float64):
    assert num_samples > 5

    eigs = np.array([3, 3, 3, 3, 1, 0], dtype=dtype)
    errors = []

    for i in range(num_samples):
        n = len(eigs)
        a = rng.uniform(-1, 1, size=(n, n)).astype(dtype)
        q, r = np.linalg.qr(a)
        d = np.diag(r)
        q = q @ np.diag(d / abs(d))

        eps = np.finfo(dtype).eps
        assert la.norm(q.T @ q - np.eye(n)) <= 4 * n * eps * la.norm(q)

        x = q @ np.diag(eigs) @ q.T
        x = np.triu(x) + np.triu(x, 1).T

        assert np.all(x == x.T)

        if driver == "srf":
            es = scipy.linalg.eigvalsh(x)
        else:
            es = scipy.linalg.eigh(x, driver=driver)[0]

        f = min
        errors.append(f(es) - f(eigs))

        if driver == "srf":
            continue

        y1 = pinvh(x)
        y2 = pinv(x)

        np.allclose(np.matmul(np.matmul(x, y1), x), x)  # true
        np.allclose(np.matmul(np.matmul(y1, x), y1), y1)  # false
        np.allclose(np.conjugate(np.matmul(x, y1)), np.matmul(x, y1))  # true
        np.allclose(np.conjugate(np.matmul(y1, x)), np.matmul(y1, x))  #true

        np.allclose(np.matmul(np.matmul(x, y2), x), x)  # true
        np.allclose(np.matmul(np.matmul(y2, x), y2), y2)  # true
        np.allclose(np.conjugate(np.matmul(x, y2)), np.matmul(x, y2))  # true
        np.allclose(np.conjugate(np.matmul(y2, x)), np.matmul(y2, x))  #true

    errors = sorted(map(lambda x: abs(x) / (max(eigs) * eps), errors))
    percentiles = range(0, 101, 25)
    five_number_summary = list(map(lambda p: np.percentile(errors, p),
                                   percentiles))

    return five_number_summary


def main():
    num_samples = 1000
    # srf = square-root free / Pal-Walker-Kahan QR algorithm (eigenvalues only)
    for driver in ["evd", "evr", "srf"]:
        rng = np.random.RandomState(seed=1)
        summary = sample_smallest_ev(rng, num_samples, driver=driver)

        print("n=%d driver=%-3s %+8.2e %+8.2e +%8.2e %+8.2e %+8.2e" %
              (num_samples, driver, *summary))


if __name__ == "__main__":
    sys.exit(main())

Output on my x86-64 machine (SciPy 1.10.1, OpenBLAS 0.3.21):

n=1000 driver=evd +0.00e+00 +3.12e-01 +6.46e-01 +1.15e+00 +4.12e+00
n=1000 driver=evr +0.00e+00 +3.33e+00 +7.33e+00 +1.07e+01 +2.00e+01
n=1000 driver=srf +0.00e+00 +2.29e-01 +4.79e-01 +8.55e-01 +2.69e+00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant