Skip to content

Saloon: symmetric eigendecomposition (eig) via cyclic Jacobi#47

Merged
sigilante merged 2 commits into
mainfrom
sigilante/saloon-eig
Jun 8, 2026
Merged

Saloon: symmetric eigendecomposition (eig) via cyclic Jacobi#47
sigilante merged 2 commits into
mainfrom
sigilante/saloon-eig

Conversation

@sigilante
Copy link
Copy Markdown
Collaborator

Summary

Implements Phase A1 of the Saloon eigendecomposition design (saloon/EIG-DESIGN.md): real symmetric eigenvalues and eigenvectors on Lagoon %i754 arrays (f32/f64) via the cyclic Jacobi algorithm. This is the marquee numerical-linear-algebra capability Saloon was built toward, and the motivating consumer of the %cplx work.

New arms in a +| %linalg section of the sa core:

arm result
+eig [vals=ray vecs=ray] — eigenvalues (1-D), eigenvectors (columns)
+eigvals ray — eigenvalues only
+eigvecs ray — eigenvectors only

plus building blocks: bloq-dispatched scalar float helpers, exact-symmetry check, off-diagonal / Frobenius norms, diag, Givens row/column rotations, and a single cyclic sweep.

Design choices

  • Asserts squareness + exact symmetry (crashes otherwise), per the design — a nonsymmetric matrix can't silently get the wrong algorithm (it belongs to the future general Phase B).
  • Cyclic Jacobi needs only sqrt, converges quadratically. 60-sweep cap with a warning rather than a silent non-converged result.
  • Scalar component arithmetic uses the rs/rd math doors at the array's bloq; per-entry get-item/set-item rotations (O(n) per pair) instead of full mmul.

Latent fix

Building Saloon against the current Lagoon surfaced a pre-existing bug: trans-scalar/fun-scalar used exhaustive ?- kind, but Lagoon's kind union has since grown (%unum, %cplx). Both switched to ?+ kind !! so unsupported kinds crash gracefully instead of failing to compile.

Tests

tests/lib/saloon-eig is generated by tools/eig_check.py against numpy.linalg.eigh, covering the four standard sources of symmetric test matrices:

  • closed-form: 1-D Laplacian (analytic spectrum)
  • torture: Rosser 8×8 (double + near-zero + clustered eigenvalues), Wilkinson W7⁺, Pascal, Hilbert (ill-conditioned)
  • real-world SPD: cycle-graph Laplacian, Gram MᵀM
  • random SPD: seeded

Per matrix it checks the order/sign-free invariant A·V = V·diag(vals) and sort(computed) = sort(eigh). All 18 pass in ~20s on a live ship.

Follow-ons (not in this PR)

  • A2: Hermitian (%cplx) Jacobi.
  • B: general real → complex (Hessenberg + double-shift QR).

🤖 Generated with Claude Code

sigilante and others added 2 commits June 6, 2026 14:09
Phased plan for eigenvalues/eigenvectors on Lagoon arrays, the marquee consumer
of %cplx.  Phase A: symmetric/Hermitian via the Jacobi algorithm (real
eigenvalues, orthonormal eigenvectors, pure %i754, only needs sqrt).  Phase B:
general real -> complex via Hessenberg + Francis double-shift QR (%cplx).
Decision: non-symmetric input crashes (assert symmetry) rather than silently
symmetrizing.  Tests check invariants (A*v=lambda*v, V'V=I, reconstruction)
vs NumPy eigh, not raw eigenvectors (sign/order/degeneracy).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase A1 of the eig design: real symmetric eigenvalues/eigenvectors on Lagoon
%i754 arrays (f32/f64), the marquee Saloon capability.  Adds a %linalg section
to the sa core:

  +eig      -> [vals=ray vecs=ray]   (eigenvalues 1-D, eigenvectors as columns)
  +eigvals  -> ray                   (eigenvalues only)
  +eigvecs  -> ray                   (eigenvectors only)

plus building blocks (bloq-dispatched scalar float helpers, symmetric check,
off-diagonal/Frobenius norms, diag, Givens row/column rotations, sweep-once).
Asserts squareness and exact symmetry (crashes otherwise, per design) so a
nonsymmetric matrix can't silently get the wrong algorithm.  Cyclic Jacobi
needs only sqrt, converges quadratically; 60-sweep cap with a warning rather
than a silent non-converged result.

Also fixes a latent bug surfaced by building Saloon against the current Lagoon:
trans-scalar/fun-scalar used exhaustive `?-  kind`, but Lagoon's kind union has
grown (%unum, %cplx).  Switched both to `?+  kind  !!` so unsupported kinds
crash gracefully instead of failing to compile.

Tests (tests/lib/saloon-eig, generated by tools/eig_check.py against
numpy.linalg.eigh) cover the four standard sources: closed-form (1-D
Laplacian), torture matrices (Rosser 8x8, Wilkinson W7+, Pascal, Hilbert),
real-world SPD (cycle Laplacian, Gram), and seeded random SPD.  Per matrix:
A*V = V*diag(vals) (order/sign-free) and sort(computed) = sort(eigh).  All 18
pass in ~20s.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sigilante sigilante merged commit 129695d into main Jun 8, 2026
@sigilante sigilante deleted the sigilante/saloon-eig branch June 8, 2026 01:00
sigilante added a commit that referenced this pull request Jun 8, 2026
Extends +eig to complex Hermitian matrices via complex cyclic Jacobi.  +eig now
dispatches on kind: %cplx -> Hermitian path, else the symmetric path.  The
unitary rotation J that zeros a_pq has real diagonal c and complex off-diagonal
b = s*(a_pq/|a_pq|) with J[q,p] = -conj(b); updates are A <- J^H*A*J, V <- V*J.
Eigenvalues are real (%i754), eigenvectors unitary (%cplx) -- matching NumPy
eigh.  Works on @cs (bloq 6) and @cd (bloq 7); asserts exact Hermitian.

Adds complex scalar helpers (cadd/csub/cmul/cdiv/cconj/cre/cpak/cabs-re over
/lib/complex) and the Hermitian arms (hermitian, off-norm-h, frob-h, diag-real,
rot-cols-h, rot-rows-h, sweep-herm, eig-herm).

Durability: replaced the unbounded /lib/math Newton sqrt with an
iteration-capped fsqt (50-step backstop, width-fixed feps).  A sub-ULP rtol
otherwise makes Newton oscillate between two adjacent floats forever, which
OOM-crashed the ship while developing this.  This also hardens the A1 path
(regression-checked: all 18 symmetric tests still green).

Tests (tests/lib/saloon-eigh, generated by tools/eigh_check.py vs
numpy.linalg.eigvalsh) cover @cs 2x2/3x3/4x4 and @cd 3x3/4x4 Hermitian
matrices; sort(computed eigenvalues) = sort(eigh).  All 5 pass.

Caveats documented in EIG-DESIGN.md: rtol width must match the component width
(@rs for @cs, @rd for @cd); the exact Hermitian assert rejects +-0.0 mismatches
in conjugate pairs.

Note: this branch stacks on %cplx (PR #46) and eig A1 (PR #47); rebase once
those land.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant