A4 Solutions
P1
function m = Sturm(a,b,lambda)
% a is a column n-vector
% b is a column (n-1)-vector with non-zero entries
% lambda is a scalar
% m is the number of eigenvalues of T that are less than or equal to lambda where
% T is the symmetric tridiagonal matrix with T(i,i) = a(i) for i=1:n and
% T(i,i+1) = b(i) for i=1:n-1
pkm2 = 1;
pkm1 = a(1) - lambda;
if pkm2*pkm1<=0
m = 1;
else
m = 0;
end
n = length(a);
for k=2:n
pk = (a(k) - lambda)*pkm1 - b(k-1)^2*pkm2;
% If pk and pkm1 have opposite sign, increment m...
% Method 1
% if pk*pkm1 <=0 , m = m + 1; end
% Method 2
% if sign(pk) ~= sign(pkm1) , m = m + 1; end
% Method 3
if sign(pk)*sign(pkm1) <=0 , m = m + 1; end
pkm2 = pkm1;
pkm1 = pk;
end
% For even modest problems, the pk can get huge. For example, an
% 2m-by-2m matrix with eigenvalues -m, -m+1 , ..., -1 , 1 2 ,..., m
% has the property that p_n(0) = (m!)^2. The floating point work
% cannot represent such quantities. Thus, if pk = 1000! (for example)
% pk is assigned the value of inf and this can complicate the sign checking.
% one point for t he explanation
% You should also note that if pk is supposed to be exactly 0 that will
% not generally happen. Altho it is interesting to explore the imapact
% of more liberal definitions of zero, i.e., |pk| <= EPS *norm(a) there is
% marginal value in oncorporating such a standard. "Off by 1" in the value
% returned by sturm when mu is an "exact" eigenvalue is perfectly OK given
% fuzzy data and inexact arithetic
P2
function [mu,SturmCalls] = Locate(a,b,L,R,tol,m)
% a is a column n-vector
% b is a column (n-1)-vector with non-zero entries.
% Let T be the symmetric tridiagonal matrix
% with T(i,i) = a(i) for i=1:n and T(i,i+1) = b(i) for i=1:n-1.
% tol is a positive scalar.
% mu is an approximation to the mth smallest eigenvalue of T
% SturmCalls is the number of calls to Sturm that are required.
% Assume that the m-th smallest eigenvalue of T is in [L,R]
LeftVal = Sturm(a,b,L); % Should be <= m-1
RightVal = Sturm(a,b,R); % Should be >= m
SturmCalls = 2;
% Invariant: LeftVal <=m-1 RightVal > m-1
while (R-L > tol)
mid = L + (R-L)/2;
midVal = Sturm(a,b,mid);
SturmCalls = SturmCalls+1;
if midVal <=m-1
L = mid;
LeftVal = midVal;
else
R = mid;
RightVal = midVal;
end
end
mu = (L+R)/2;
function [mu,SturmCalls] = MinEig(a,b,tol)
% a is a column n-vector
% b is a column (n-1)-vector with non-zero entries.
% tol is a positive scalar.
% mu is an approximation to lambda_min, the eigenvalue of T that has
% minimum absolute value where T is the symmetric tridiagonal matrix
% with T(i,i) = a(i) for i=1:n and T(i,i+1) = b(i) for i=1:n-1.
% SturmCalls is the number of calls to Sturm that are required.
% It should be the case that |mu - lambda_min| <= tol.
n = length(a);
GershRadii = [0 ; abs(b)] + [abs(b); 0];
L = min(a-GershRadii);
R = max(a+GershRadii);
% All eigenvalues in union of intervals [a(k)-GershRadii(k),a(k)+GershRadii(k)],
m = Sturm(a,b,0); % The number of eigenvalues <= 0
if m==0
[mu,sCallsL] = Locate(a,b,0,R,tol,n);
SturmCalls = sCallsL + 1;
elseif m==n
[mu,sCallsR] = Locate(a,b,L,0,tol,1);
SturmCalls = sCallsR + 1;
else
% Look for eigenvalue m and m+1, which straddle the origin
% Let's find the smallest negative eigenvalue..
[muL,sCallsL] = Locate(a,b,L,0,tol,m);
% >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
% We could just do the same thing to the right of the origin...
% [muR,sCallsR] = Locate(a,b,0,R,tol,m+1);
% if abs(muL)<= abs(muR)
% mu = muL;
% else
% mu = muR;
% end
% SturmCalls = 1 + sCallsL + sCallsR;
% >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
% But why not check to see how many eigenvalues are to the left of -muL?
mplus = Sturm(a,b,-muL);
if mplus == m
mu = muL;
SturmCalls = 2 + sCallsL;
else
% We know that the smallest positive eigenvalue is between 0 and -muL
[muR,sCallsR] = Locate(a,b,0,-muL,tol,m+1);
if abs(muL)<= abs(muR)
mu = muL;
else
mu = muR;
end
SturmCalls = 2 + sCallsL + sCallsR;
end
end
% - 3 points for not exploiting the fact that once you know muL, you can cut
% some corners getting muL. On random examples, half the time |muL| < |muR| and
% you only need one call to Locate Sturm to discover that. And when that is not the
% case, you go after muR with a smaller initial interval, i.e., [0,-muL] instead of
% [0,R]
P3
function [Q,D,mu,x] = SchurPlus(A,v)
% A is an n-by-n symmetric positive definite matrix.
% v is a column n-vector
% Q'*A*Q = D is the Schur decomposition of A.
% mu is the largest eigenvalue of A + v*v' and x is an associated eigenvector,
% normalized so norm(x,2) = 1. Assume that the largest eigenvalue of A and
% A + v*v' are unique
n = length(v);
[Q,D] = schur(A);
% (A + v*v')x = mu* x transforms to (D + w*w')y = mu*y where
% D = Q'*A*Q, w = Q'*v, and y = Q'*x
% So go after the largest eigenvalue/eigenvector of D + w*w' using power
% method.
w = Q'*v;
d = diag(D);
% Apply power method to get mu and x.
%Initial guess (1 point for something better than a random guess)
[mu,j] = max(d+w.*w); % mu = largest diagonal value of D + w*w'
y = zeros(n,1); y(j) = 1;
tol = 10^(-14)*norm(A + v*v','inf');
z = d.*y + (w'*y)*w; % z = (D + w*w')*(current y)
r = z - mu*y; % r = z - (current mu)*(current y)
while norm(r) > tol
y = z/norm(z); % New y is the normalized old z
z = d.*y + (w'*y)*w; % z = (D + w*w')*(current y)
mu = y'*z;
r = z - mu*y; % r = z - (current mu)*(current y)
end
% About 11n flops per iteration
% -2 if O(n^2) per iteration
x = Q*y;