demoAINumerics
Jacobi's eigenvalue method
Jacobi's method diagonalizes a symmetric matrix by plane rotations, each of which zeroes one off-diagonal pair. On an 8 by 8 matrix the off-diagonal norm goes 1.33, 0.453, 0.0299, , over four sweeps, and after the fifth it is under , rounding error. The diagonal is then the spectrum, and it matches numpy.linalg.eigh to .
Open in the workbenchDownload .vivOutputs below are from the site build’s run; Run all runs it again in your browser.
Look inside this program
- Atlas
The program’s map: every function and which calls which.
There’s no recorded run of this program yet.
A symmetric matrix has an orthogonal matrix of eigenvectors with diagonal. Jacobi's method (1846) builds out of plane rotations. A rotation in the plane, through an angle set by , and (derived under "One rotation"), makes the entry zero; doing that for every pair once is a sweep. A later rotation can make an earlier zero nonzero again, so one sweep does not finish the job, but the size of the off-diagonal part, , falls with every rotation.
The cells below run it on a fixed 8 by 8 matrix and measure
after each sweep. The reference is numpy: the same sweeps in
Python and numpy.linalg.eigh on the same matrix
(posts/refs/jacobi-eigenvalue-sweeps.py, output beside it).
Printed
One rotation
Rotating the and rows and columns through changes only those two rows and columns. The new entry is
which is zero when . atan2
picks the angle with when , the smaller
rotation, which disturbs the rest of the matrix least.
A rotation leaves the sum of squares of all entries unchanged, and it moves exactly of it from off the diagonal onto it. That is why can only fall. The cell checks both facts on the first rotation, .
Printed
Tests 2 of 2 pass
- one rotation zeroes its entry, to rounding (passes)
- and moves exactly 2 a_pq^2 of the sum of squares onto the diagonal (passes)
Sweeps
A sweep rotates every pair in order, : 28 rotations for . The cell runs six sweeps and records after each.
While the off-diagonal part is large, a sweep removes a fixed fraction of it: the first two take it from 1.33 to 0.453 to 0.0299. Once it is small next to the gaps between the eigenvalues (here the smallest gap is 0.44), each sweep roughly squares it: and the next value is ; and the next is . The number of correct digits doubles each sweep, as it does for Newton's method, and the fifth sweep hits rounding: under , about times the size of the matrix itself. Through the fourth sweep every value agrees with the numpy twin, which does the same rotations in the same order, to ; after that both are rounding noise, here and in numpy.
Printed
Tests 6 of 6 pass
- the start and the first four sweeps give the numpy twin's off-norms (passes)
- a sweep of the 8 by 8 matrix is 28 rotations, N(N - 1)/2 (passes)
- after the fifth sweep off(A) is 4.5e-17 times the norm of A (passes)
- after the fifth sweep the off-diagonal norm is rounding, under 1e-15 (passes)
- sweeps 2 to 4 each leave less than the square of what they found (passes)
- the smallest gap between eigenvalues is 0.44, as numpy finds (passes)
The answer
After the sweeps the diagonal of holds the eigenvalues and the columns of
the eigenvectors: is diagonal. The eigenvalues are checked
against numpy.linalg.eigh, which uses a different algorithm (LAPACK's
tridiagonal reduction), and the product is
checked against the matrix we started from.
Printed
Tests 2 of 2 pass
- the eigenvalues match numpy.linalg.eigh to 1e-14 (passes)
- V diag(lambda) V^T rebuilds the matrix to 1e-14, and V is orthogonal to 1e-14 (passes)