![]() | Text provided under a Creative Commons Attribution license, CC-BY. All code is made available under the FSF-approved MIT license. (c) Kyle T. Mandli |
Note: This material largely follows the text “Numerical Linear Algebra” by Trefethen and Bau (SIAM, 1997) and is meant as a guide and supplement to the material presented there.
from __future__ import print_function
%matplotlib inline
import warnings
import matplotlib.pyplot as plt
import numpyEigenproblems¶
Overview¶
We will now consider eigenproblems of the form
where , and . The vector is known as the eigenvector and the eigenvalue. The set of all eigenvalues is called the spectrum of .
The basics¶
The eigenproblem
can be rewritten as
which implies that the eigenvectors are in the Null space of .
However for this matrix to have a non-trivial Null space, requires that is singular.
Characteristic Polynomial¶
If is singular, it follows that
where can be shown to be a th order polynomial in known as the characteristic polynomial of a matrix
We can then state the following theorem regarding the zeros of and the eigenvalues of :
Theorem: is an eigenvalue of if and only if .
i.e. the eigenvalues are the roots of , and therefore there are exactly eigenvalues.
Computing Eigenvalues¶
In basic linear algebra classes we usually find the eigenvalues by directly calculating the roots of which can work for low-degree polynomials. Unfortunately the following theorem (due to Galois) suggests this is not a good way to compute eigenvalues:
Theorem: For an there is a polynomial of degree with rational coefficients that has a real root with the property that cannot be written using any expression involving rational numbers, addition, subtraction, multiplication, division, and th roots.
I.e., there is no way to find the roots of a polynomial of degree in a deterministic, fixed number of steps.
Not all is lost however!¶
We just must use an iterative approach where we construct a sequence that converges to the eigenvalues.
Some Questions
How does this relate to how we found roots previously?
Why will it still be difficult to use our rootfinding routines to find Eigenvalues?
We will return to how we actually find Eigenvalues (and roots of polynomials) after a bit more review
Eigenvalue Factorization and Diagonalization¶
Given that there are exactly (possibly repeated) eigenvalues for a system, the eigenproblem is really more correctly written as
Or in Matrix form as
where is the matrix formed by the eigenvectors as its columns and is a diagonal matrix with the eigenvalues along its diagonal.
Expanded, looks like:
Here we note that the eigenpair are matched as the th column of and the th element of on the diagonal.
Diagonalization¶
Eigenproblems can always be written as
However, if there are a linearly independent set of eigenvectors then the matrix is invertible (why?). Under this condition we can transform into the diagonal matrix by
Factorization¶
or more usefully, we can rewrite as a product of more useful matrices
And use it in similar ways to how we use other factorization such as and
The Rules for diagonalizability¶
Unfortunately, not all matrices can be diagonalized (i.e. don’t have a full linearly independent set of eigenvectors that span or ). Here are the rules.
A matrix can be factored as if
all eigenvalues are distinct (never repeat). These are known as simple eigenvalues
Eigenvalues repeat, but for every repeated eigenvalue there can be found a linearly independent eigenvector
The matrix is Hermitian (, or if real, then symmetric ). In this case the eigenvalues are always real and the eigenvectors can always be chosen orthonormal. In this special case and
The issue of repeated eigenvalues can be made a bit more precise by defining
Algebraic multiplicity: the number of times an eigenvalue is repeated
Geometric multiplicity: the number of linearly independent eigenvectors corresponding to each eigenvalue.
If the algebraic multiplicity is equal to the geometric multiplicity for all then we can say that there is a full eigenspace and the matrix is diagonalizable.
Example: Computing Multiplicities¶
Compute the geometric and algebraic multiplicities for the following matrices. What is the relationship between the algebraic and geometric multiplicities?
The characteristic polynomial of is
so the eigenvalues are all so we know the algebraic multiplicity is 3 of this eigenvalue. The geometric multiplicity is determined by the number of linearly independent eigenvectors. For this matrix we have three eigenvectors that are all linearly independent which happen to be the unit vectors in each direction (check!). This means that the geometric multiplicity is also 3.
The characteristic polynomial of is the same as so again we know but now we need to be a bit careful about the eigenvectors. In this case the only eigenvector is a scalar multiple of so the geometric multiplicity is 1.
Interpretations of the Eigenspace¶
One way to interpret the eigenproblem is that of one that tries to find the subspaces of which act like scalar multiplication by . The eigenvectors associated with one eigenvalue then form a subspace of .
When an eigenvalue has algebraic multiplicity that equals its geometric then it is called non-defective and otherwise defective. This property is also inherited to the matrix so in the above example and are non-defective and defective matrices respectively.
Determinant and Trace¶
Two important properties of matrices have important relationships with their eigenvalues, namely the determinant and trace. The determinant we have seen, the trace is defined as the sum of the elements on the diagonal of a matrix, in other words
The relationship between the determinant and the eigenvalues is not difficult to guess due to the nature of the characteristic polynomial. The trace of a diagonal matrix is clear and provides another suggestion to the relationship.
Theorem: The determinant and trace are equal to the product and sum of the eigenvalues of respectively counting algebraic multiplicity.
Similarity Transformations¶
Generally, we say any two matrices and are similar if they can be related through an invertible matrix as
Theorem: If and are similar matrices, they have the same eigenvalues and their eigenvectors are related through an invertible matrix
Schur Factorization¶
A Schur factorization of a matrix is defined as
where is unitary and is upper-triangular. Because (for square unitary matrices). It follows directly that and are similar.
Good News! is upper triangular so its eigenvalues can just be read of the diagonal
Bad News! There is no deterministic way to calculate as that would violate Galois theory of polynomials
Theorem: Every matrix has a Schur factorization.
Note that the above results imply the following
An eigen-decomposition exists if and only if is non-defective (it has a complete set of eigenvectors)
A unitary transformation exists if and only if is normal ()
A Schur factorization always exists
Note that each of these lead to a means for isolating the eigenvalues of a matrix and will be useful when considering algorithms for finding them.
Condition Number of a Simple Eigenvalue¶
Before we discuss a number of approaches to computing eigenvalues it good to consider what the condition number of a given eigenproblem is.
Let
define the eigenvalue problem in question. Here we will introduce a related problem
where is the left eigenvector and from before is the right eigenvector. These vectors also can be shown to have the relationship for a simple eigenvalue.
Now consider the perturbed problem
Expanding this and throwing out quadratic terms and removing the eigenproblem we have
Multiple both sides of the above by the left eigenvector and use to find
where we again use the slightly different definition of the eigenproblem. We can then solve for to find
meaning that the ratio between the dot-product of the left and right eigenvectors and the conjugate dot-product of the matrix then form a form of bound on the expected error in the simple eigenvalue.
Computing Eigenvalues¶
Almost all useful approaches to computing eigenvalues do so through the computation of the Schur factorization. The Schur factorization, as we have seen, will preserve the eigenvalues. The steps to compute the Schur factorization are usually broken down into two steps
Directly transform into a Hessenberg matrix, a matrix that contains zeros below its first sub-diagonal, directly using Householder reflections. This is as close to triangular that you can get by direct similarity transformations.
Use an iterative method to change the sub-diagonal into all zeros
Hessenberg and Tridiagonal form¶
What we want to do is construct a sequence of similarity transformations matrices that turns into a Hessenberg matrix with the same eigenvalues as . We use Householder reflections to do this with the important distinction that we only want to remove zeros below the first sub-diagonal.
so we have the sequence which has the same eigenvalues as the original matrix .
Question? Why can’t we just use Householder to take like we did for the ?
One important special case of this sequence of transformations is that if the matrix is hermitian (the matrix is its own conjugate transpose, , or symmetric in the real case) then the Hessenberg matrix is tridiagonal.
We now will focus on how to formulate the iteration step of the eigenproblem. We will also restrict our attention to symmetric, real matrices. This implies that all eigenvalues will be real and have a complete set of orthogonal eigenvectors. Generalizations can be made of many of the following algorithms but is beyond the scope of this class.
Rayleigh Quotient and Inverse Iteration¶
There are a number of classical approaches to computing the iterative step above which we will review here. Inverse power iteration in particular is today still the dominant means of finding the eigenvectors once the eigenvalues are known.
Rayleigh Quotient¶
The Rayleigh quotient of a vector is the scalar
The importance of the Rayleigh quotient is made clear when we evaluate at an eigenvector. When this is the case the quotient evaluates to the corresponding eigenvalue.
The Rayleigh quotient can be motivated by asking the question, given an eigenvector , what value acts most like an eigenvalue in an sense:
This can be reformulated as a least-squares problem noting that is the “matrix”, is the unknown vector (scalar) and is the right-hand side so we have
which can be solved so that
Power Iteration¶
Power iteration is a straightforward approach to finding the eigenvector of the largest eigenvalue of . The basic idea is that the sequence
will converge (although very slowly) to the desired eigenvector.
We implement this method by initializing the algorithm with some vector with . We then apply the sequence of multiplications.
# Demo
# generate a random symmetric matrix
A = numpy.random.rand(3, 3)
A = 0.5 * (A + A.T)
print("A=\n{}".format(A))
lams = numpy.linalg.eigvals(A)
print("\nLambda = {}".format(lams))# the Rayleigh Quotient
def rayleighq(A, x):
return numpy.dot(x.T, A.dot(x)) / numpy.dot(x.T, x)
def power_iteration(A, tol=1.0e-6):
"""power_iteration to find the largest eigenvector and corresponding eigenvalue
parameters:
-----------
A: ndarray (square)
m x m matrix
tol: float
stopping criteria for iteration.
iteration will cease when ||x_{i+1} - x_{i}|| < tol or
MAX_ITS exceeded
returns:
--------
x: ndarray
array of iterates of the eigenvector
r: ndarray
"""
MAX_ITS = 100
m = A.shape[0]
x = numpy.empty((MAX_ITS, m))
lam = numpy.empty(MAX_ITS)
res = numpy.empty(MAX_ITS)
# generate a random unit vector
x0 = numpy.random.rand(A.shape[0])
x[0, :] = x0 / numpy.linalg.norm(x0, ord=2)
lam[0] = rayleighq(A, x0)
for i in range(1, MAX_ITS + 1):
xi = A.dot(x[i - 1, :])
x[i, :] = xi / numpy.linalg.norm(xi, ord=2)
lam[i] = rayleighq(A, x[i, :])
res[i - 1] = numpy.abs(lam[i] - lam[i - 1]) / numpy.abs(lam[i])
if res[i - 1] < tol:
break
if i == MAX_ITS:
warnings.warn("Maximum iterations exceeded")
x.resize(i + 1, m)
lam.resize(i + 1)
res.resize(i)
return x, lam, resx, r, res = power_iteration(A, tol=1.0e-8)
print("{} Iterations".format(len(r)))
print("x = {}".format(x[-1]))
print("Eigenvalue = {}".format(r[-1]))
print("eigs(A) = {}".format(numpy.linalg.eigvals(A)))fig = plt.figure(figsize=(16, 6))
axes = fig.add_subplot(1, 2, 1)
axes.plot(r, "o", markersize=8)
lam_max = numpy.max(numpy.linalg.eigvals(A))
axes.plot(lam_max * numpy.ones(r.shape), "k--")
axes.grid()
axes.set_xlabel("Iteration", fontsize=16)
axes.set_ylabel("Rayleigh Quotient", fontsize=16)
axes = fig.add_subplot(1, 2, 2)
axes.semilogy(res, "o-", markersize=8)
axes.grid()
axes.set_xlabel("Iteration", fontsize=16)
axes.set_ylabel("Residual", fontsize=16)
plt.show()The reason why this works can be seen by considering the initial vector as a linear combination of the orthonormal eigenvectors (which we have assumed exist) such that
Multiplying by then leads to
here is some constant due to the fact the eigenvectors are not uniquely specified.
wRepeating this times we have
Since for all then in the limit the terms will approach zero and on normalization .
Inverse Iteration with shifts¶
Inverse iteration with shifts uses a similar approach with the difference being that we can use it to find any of the eigenvectors for the matrix .
Some Preliminaries: inverse and shift rules of Eigenvalues
Show that if is an eigenvector of with eigenvalue , then
is an eigenvector of with eigenvalue
is an eigenvector of with eigenvalue
So...
If we want to find the smallest eigenvalue we can consider the power method on ,
But we really don’t want to find which is expensive, instead we can do the equivalent iteration
x[0] = x0
for i in range(MAX_ITS):
solve A w[i] = x[i]
x[i+1] = w[i]/norm(w[i]) as
and if we want to find the eigenvalue closest to some number we can apply the power method to
the eigenvectors of this matrix are
where are the eigenvalues of .
If is close to a particular , say , then
will be larger than any of the other . In this way we effectively have picked out the eigenvalue we want to consider in the power iteration!
Rayleigh Quotient Iteration¶
By themselves the above approaches are not particularly useful but combining them we can iterate back and forth to find the eigenvalue, eigenvector pair:
Compute the Rayleigh quotient and find an estimate for
Compute one step of inverse iteration to approximate
Repeat...
def rayleigh_quotient_iteration(A, tol=1.0e10):
"""rayleigh quotient iteration to find eigenvalues
parameters:
-----------
A: ndarray (square)
m x m matrix
tol: float
stopping criteria for iteration.
iteration will cease when ||x_{i+1} - x_{i}|| < tol or
MAX_ITS exceeded
returns:
--------
x: ndarray
array of iterates of the eigenvector
r: ndarray
"""
MAX_ITS = 100
m = A.shape[0]
x = numpy.empty((MAX_ITS, m))
lam = numpy.empty(MAX_ITS)
res = numpy.empty(MAX_ITS)
I = numpy.eye(m)
# generate a random unit vector
x0 = numpy.random.rand(A.shape[0])
x[0, :] = x0 / numpy.linalg.norm(x0, ord=2)
lam[0] = rayleighq(A, x0)
for i in range(1, MAX_ITS + 1):
# this is the only different line
w = numpy.linalg.solve(A - lam[i - 1] * I, x[i - 1, :])
x[i, :] = w / numpy.linalg.norm(w, ord=2)
lam[i] = rayleighq(A, x[i, :])
res[i - 1] = numpy.abs(lam[i] - lam[i - 1]) / numpy.abs(lam[i])
if res[i - 1] < tol:
break
if i == MAX_ITS:
warnings.warn("Maximum iterations exceeded")
x.resize(i + 1, m)
lam.resize(i + 1)
res.resize(i)
return x, lam, resx, r, res = rayleigh_quotient_iteration(A, tol=1.0e-10)
print("{} Iterations".format(len(r)))
print("x = {}".format(x[-1]))
print("Rayleighquotient = {}".format(r[-1]))
print("eigs(A) = {}".format(numpy.linalg.eigvals(A)))fig = plt.figure(figsize=(16, 6))
axes = fig.add_subplot(1, 2, 1)
axes.plot(r, "o", markersize=8)
lam_max = numpy.max(numpy.linalg.eigvals(A))
axes.plot(lam_max * numpy.ones(r.shape), "k--")
axes.grid()
axes.set_xlabel("Iteration", fontsize=16)
axes.set_ylabel("Rayleigh Quotient", fontsize=16)
axes = fig.add_subplot(1, 2, 2)
axes.semilogy(res, "o-", markersize=8)
axes.grid()
axes.set_xlabel("Iteration", fontsize=16)
axes.set_ylabel("Residual", fontsize=16)
plt.show()QR/RQ Algorithm¶
All of the above methods pick out at most a few eigenvalues at a time. However it turns out we can use the algorithm, to iterate towards the Schur factorization and find all the eigenvalues simultaneously.
The simplest algorithm just iterates
while not converged:
Q, R = numpy.linalg.qr(A)
A = R.dot(Q) calculating the factorization of , then forming a new , This sequence will eventually converge to the Schur decomposition of the matrix .
Code this up and see what happens.
%precision 6
m = 3
A = numpy.array([[2, 1, 1], [1, 3, 1], [1, 1, 4]])
print(numpy.linalg.eigvals(A))from scipy.linalg import hessenberg
A = hessenberg(A)
print(A)MAX_STEPS = 10
for i in range(MAX_STEPS):
Q, R = numpy.linalg.qr(A)
A = numpy.dot(R, Q)
print()
print("A(%s) =" % (i))
print(A)print()
print("True eigenvalues: ")
print(numpy.linalg.eigvals(A))
print()
print("Computed eigenvalues: ")
print(numpy.diag(A))So why does this work? The first step is to find the factorization of or
which is equivalent to finding
and multiplying on the right leads to
In this way we can see that this is a similarity transformation of the matrix since the is an orthogonal matrix (). This of course is not a great idea to do directly but works great in this case as we iterate to find the upper triangular matrix which is exactly where the eigenvalues appear.
In practice this basic algorithm is modified to include a few additions:
Before starting the iteration is reduced to Hessenberg form.
Motivated by the inverse power iteration we observed we instead consider a shifted matrix for factoring. The picked is related to the estimate given by the Rayleigh quotient. Here we have
Deflation is used to reduce the matrix into smaller matrices once (or when we are close to) finding an eigenvalue to simplify the problem.
This has been the standard approach until recently for finding eigenvalues of a matrix.
Application: Finding the roots of a polynomial¶
Numpy has a nice function called roots which returns the roots of a th degree polynomial
described by a vector of coefficients
c = numpy.array([-1, -1, 1])
r = numpy.roots(c)
print(r)c = numpy.random.rand(6)
r = numpy.roots(c)
print(r)This routine, does not try and actually find the roots of a high-order polynomial, instead it actually calculates the eigenvalues of a companion matrix whose characteristic polynomial is the monic polynomial .
It can be shown that this matrix can be constructed as (see e.g.)
def myroots(p, verbose=True):
"""Calculate the roots of a polynomial described by coefficient vector
p(x) = p_0 + p_1 x + p_2 x^2 + \ldots + p_n x^n
by finding the eigenvalues of the companion matrix
"""
# construct the companion matrix of the coefficient vector c
# make p monic and drop the last coefficient
c = p / p[-1]
if verbose:
print(c)
m = len(c) - 1
C = numpy.zeros((m, m))
C[:, -1] = -c[:-1]
C[1:, :-1] = numpy.eye(m - 1)
if verbose:
print("C = \n{}".format(C))
return numpy.linalg.eigvals(C)c = numpy.array([-1, -1, 1])
r = numpy.roots(c)
print(r)
mr = myroots(c)
print
print(mr)c = numpy.random.rand(5)
r = numpy.roots(c)
print(r)
mr = myroots(c)
print
print(mr)Alternatives¶
Jacobi¶
Jacobi iteration employs the idea that we know the eigenvalues of a matrix of size equal to or less than 4 (we know the roots of the characteristic polynomial directly). Jacobi iteration therefore attempts to break the matrix down into at most 4 by 4 matrices along the diagonal via a series of similarity transformations based on only diagonalizing sub-matrices 4 by 4 or smaller.
Bisection¶
It turns out if you do not want all of the eigenvalues of a matrix that using a bisection method to find some subset of the eigenvalues is often the most efficient way to get these. This avoids the pitfall of trying to find the eigenvalues via other root-finding approaches by only needing evaluations of the function and if a suitable initial guess is provided can find the eigenvalue quickly that is closest to the initial bracket provided.
Divide-and-conquer¶
This algorithm is actually the one used most often used if both eigenvalues and eigenvectors are needed and performs up to twice as fast as the approach. The basic idea is to split the matrix into two pieces at every iteration by introducing zeros on the appropriate off-diagonals which neatly divides the problem into two pieces.
Arnoldi and Lanczos Iteration¶
Krylov subspace methods (which we will unfortunately not cover) are another approach to finding eigenvalues of a matrix. These methods generally use some piece of the approach outlined above and are extremely effective at finding the “extreme” eigenvalues of the matrix.
