"""
eigenvalue.py — numerical companion to the glossary entry 'eigenvalue'.

One block per paragraph of the entry (marked [P...]): each block verifies
numerically what the corresponding statement asserts. Self-contained
(numpy/matplotlib only), fixed seed.

Blocks
------
[P-def] lambda is an eigenvalue of a square matrix A iff A x = lambda x
        for some nonzero vector x: every (lambda, x) pair returned by
        np.linalg.eig satisfies the defining equation, the eigenvectors
        are nonzero, and applying A to an eigenvector only rescales it
        (direction preserved — the content of the entry's figure).

Outputs
-------
eigenvalue.png : preview figure (checking only).

Data generated by pythondemos/eigenvalue.py.
"""

import numpy as np
import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt

rng = np.random.default_rng(42)
report = []


def check(name, ok):
    report.append((name, bool(ok)))
    print(f"  [{'ok' if ok else 'FAIL'}] {name}")


# ----------------------------------------------------------- [P-def]
print("[P-def] A x = lambda x for every eigenpair")
A = np.array([[2.0, 1.0], [1.0, 3.0]])         # symmetric -> real eigenvalues
lam, V = np.linalg.eig(A)
for i in range(2):
    x = V[:, i]
    check(f"pair {i}: ||A x - lambda x|| < 1e-12 "
          f"(lambda = {lam[i]:.4f})",
          np.linalg.norm(A @ x - lam[i] * x) < 1e-12)
    check(f"pair {i}: eigenvector is nonzero", np.linalg.norm(x) > 0)
    # direction preserved: A x is collinear with x
    cos = abs(x @ (A @ x)) / (np.linalg.norm(x) * np.linalg.norm(A @ x))
    check(f"pair {i}: A x collinear with x (|cos| = 1)",
          abs(cos - 1) < 1e-12)
# a non-eigenvector is NOT mapped to a multiple of itself
u = np.array([1.0, 0.0])
cos_u = abs(u @ (A @ u)) / (np.linalg.norm(u) * np.linalg.norm(A @ u))
check("generic vector changes direction under A (|cos| < 1)",
      cos_u < 1 - 1e-6)

# ------------------------------------------------------------ preview
fig, ax = plt.subplots(figsize=(4.2, 4.0))
for i, c in zip(range(2), ("C0", "C1")):
    x = V[:, i]
    ax.arrow(0, 0, *x, head_width=0.06, color=c, length_includes_head=True)
    ax.arrow(0, 0, *(A @ x), head_width=0.06, color=c, alpha=0.4,
             length_includes_head=True)
    ax.annotate(f"$\\lambda_{i+1}={lam[i]:.2f}$", xy=A @ x)
ax.arrow(0, 0, *u, head_width=0.06, color="k", length_includes_head=True)
ax.arrow(0, 0, *(A @ u), head_width=0.06, color="k", alpha=0.35,
         length_includes_head=True)
ax.set_aspect("equal"); ax.set_title("[P-def] eigenvectors keep direction")
fig.tight_layout()
fig.savefig("eigenvalue.png", dpi=110)
print(f"\n{sum(ok for _, ok in report)}/{len(report)} checks passed")
assert all(ok for _, ok in report)
