function [P, B] = tridiagonalize(A)
% Tridiagonalize Hermitian matrix A, such that P * A * P' = B and P' * B * P = A.
% B is real, P is unitary, and B has the same eigenvalues as A.

% Copyright © 2005 Stephen J. Sangwine and Nicolas Le Bihan.
% See the file : Copyright.m for further details.

error(nargchk(1, 1, nargin)), error(nargoutchk(2, 2, nargout)) 

% NB: This is a reference implementation. It uses explicit Householder
% matrices. Golub and van Loan, 'Matrix Computations', 2e, 1989, section
% 5.1.4 discusses efficient calculation of Householder transformations.
% This has been tried for bidiagonalization (see bidiagonalize2.m), but
% found to be slower than the use of explicit matrices as used here. This
% requires further study.

[r, c] = size(A);

if r ~= c
    error('Cannot tridiagonalize a non-square matrix');
end

if r < 2
    error('Cannot tridiagonalize a matrix smaller than 2 by 2.');    
end

if ~ishermitian(A)
    error('Matrix to be tridiagonalized is not (accurately) Hermitian');
end

[P, B] = internal_tridiagonalizer(A);

B = check(B); % Verify the result and convert to exactly tridiagonal real form.

% -----------------------------------------------------------------------------

function [P, B] = internal_tridiagonalizer(A)

[r, c] = size(A);

if r ~= c
    error('Internal tridiagonalize error - non-square matrix');
end

% Compute and apply a Householder transformation to the first row and column of A (these are
% conjugates of each other because A is Hermitian).   We omit the first element of the first
% column in computing the Householder matrix, because it is already real (A is Hermitian).
% We apply P on both sides of A so that the first row and column are nulled out (apart from
% the first two elements in each case). Note that if B is 2 by 2, this is all we have to do.
    
P = quaternion(eye(r));
P(2 : end, 2 : end) = householder_matrix(A(2 : end, 1), eye(r - 1, 1));
B = P * A * P';

% Apply the algorithm recursively to sub-matrices of B, except when the sub-matrix is 2 by 2.

if r > 2
    Q = quaternion(eye(r));
    [Q(2 : end, 2 : end), B(2 : end, 2 : end)] = internal_tridiagonalizer(B(2 : end, 2 : end));
    P = Q * P;
end

% ---------------------------------------------------------------------------------------------

function R = check(B);

% Verify results, and convert the result to exactly tridiagonal form with no vector part.

r = length(B); % We know B is square, so we don't need to check this again.

D = [diag(B); diag(B, +1); diag(B, -1)]; % Extract the three diagonals.
O = tril(B, -2) + triu(B, +2);           % Extract the off-diagonal part.

% Find the largest on and off tridiagonal elements. We use abs twice to
% allow for the case where B is complex (this occurs when A is a complexified
% quaternion matrix).

T1 = max(max(abs(abs(O)))); % Find the largest off tridiagonal element.
T2 =     max(abs(abs(D)));  % Find the largest     tridiagonal element.

% NB T2 and/or T1 could be exactly zero (example, if A was zero, or an identity matrix).
% Therefore we do not divide one by the other, but instead multiply by a tolerance.

tolerance = eps .* 1.0e3; % This is empirically determined.

if T1 > T2 .* tolerance
    warning('Result of tridiagonalization was not accurately tridiagonal.');
    disp('Information: largest on- and off-tridiagonal moduli were:');
    disp(T2);
    disp(T1);
end

% Verify that the diagonal elements have neglible vector parts.

T1 = max(abs(abs(s(D)))); % The largest scalar modulus of the tridiagonal result
T2 = max(abs(abs(v(D)))); % The largest vector modulus of the tridiagonal result.

if T2 > T1 .* tolerance
    warning('Result of tridiagonalization was not accurately scalar.')
    disp('Information: largest on-tridiagonal vector and scalar moduli were:');
    disp(T2);
    disp(T1);
end

R = s(B - O); % Subtract the off-tridiagonal part and take the scalar part of the result.
