function [U,S,V] = svd(X, econ)
% SVD    Singular value decomposition.
% (Quaternion overloading of standard Matlab function.)

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

% Reference:
%
% S. J. Sangwine and N. Le Bihan,
% Quaternion Singular Value Decomposition based on Bidiagonalization
% to a Real Matrix using Quaternion Householder Transformations,
% arXiv:math.NA/0603251, 10 March 2006. Available at http://www.arxiv.org/.
%
% Note that the title of the article above refers to a real bidiagonal
% matrix, but the algorithm is valid even when X is a complex quaternion
% matrix, in which case the bidiagonal matrix will be complex. The later
% paper below covers both the real and complex cases.
%
% S. J. Sangwine and N. Le Bihan,
% Quaternion Singular Value Decomposition based on Bidiagonalization
% to a Real or Complex Matrix using Quaternion Householder Transformations,
% Applied Mathematics and Computation, in press, 2006.
% DOI:10.1016/j.amc.2006.04.032. Available via http://www.doi.org/.

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

if nargin == 2
    if econ ~= 0 & econ ~= 'econ'
        error('Use svd(X,0) or svd(X,''econ'') for economy size decomposition.');
    end
end

if numel(X) == 1
    
    % The argument is a single quaternion. This case could be handled by
    % using the standard Matlab svd() function on the complex adjoint of
    % X, but there are problems if X is a complexified quaternion, since
    % we cannot make a complex value with complex parts.
    %
    % For this reason we output an error message and leave it to the user
    % to use the appropriate adjoint.
    
    X = inputname(1); if X == '' X = 'X'; end
    
    disp('The svd function is not implemented for single quaternions.');
    disp(sprintf('Try using svd(adjoint(%s, ''real'')) or', X));
    disp(sprintf('          svd(adjoint(%s, ''complex'')',  X));
    error('Implementation restriction - see advice above');
end

% X is not a single quaternion, so proceed ...

% The method used is to bidiagonalize X using Householder transformations to obtain
% H, B, and G where B is a real or complex bidiagonal matrix, and H and G are the
% products of the Householder matrices used to compute B. Then: H' * B * G' = X.

[H, B, G] = bidiagonalize(X);

% The second step is to compute the SVD of B using the standard Matlab routine,
% for a real or complex matrix (which happens to be bidiagonal).

if nargout == 0

    % The econ parameter is passed to the Matlab svd routine, even though
    % it appears to have no effect if there is no output parameter, since
    % only the singular values are output, and they are output as a column
    % vector.

    if nargin == 1
        svd(B)
    else
        svd(B, econ)
    end

elseif nargout == 1

    % The econ parameter is passed to the Matlab svd routine, even though
    % it appears to have no effect if there is only one output parameter,
    % because the output is a column vector of the singular values.

    if nargin == 1 U = svd(B); else U = svd(B, econ); end

else

    % In this case, the econ parameter has been given, but if we were to
    % pass it to the Matlab svd routine, the returned U and V matrices
    % would not be compatible with H and G. Therefore, we don't pass it,
    % but after multiplying the results by H and G, we truncate them to
    % give the economy mode result, so that the result is compatible with
    % that produced by the standard Matlab svd function.

    [US, S, VS] = svd(B); 
    
    U = H' * US;     % Multiply together the intermediate unitary
    V = (VS' * G')'; % matrices to construct U and V.
                     % Notice that US and VS may be complex and that we must
                     % use ' and not .' here. (H and G are quaternion-valued,
                     % and therefore H' means a quaternion transpose conjugate.)

    if nargin == 2

        % The economy size decomposition has been demanded, so we have
        % to truncate U or V and S accordingly. The description of how
        % the truncation is done can be found in the Matlab help page
        % for the standard svd function.

        [m, n] = size(X);

        % In what follows, we need to use subscripted references, but
        % these do not work inside a class method (which this is). A
        % brief mention of this can be found in the Matlab documentation
        % under Programming/Classes and Objects/Designing User Classes
        % under the title Object Indexing within Methods. The equally
        % obscure solution is to use the substruct function, as below.
        % For each statement where it is used, the comment gives the
        % normal Matlab notation (which won't work here). Since S is real,
        % the normal notation works.

        if econ == 0
            if m > n % If m <= n, there is nothing to do.
                U = subsref(U, substruct('()', {':', 1:n})); % U = U(:,1:n)
                S = S(1 : n, 1 : n);
            end
        elseif econ == 'econ'
            if m == n
                return
            elseif m > n % The Matlab documentation says >= here, but if m
                         % and n are equal all three matrices are square,
                         % so we don't need to do anything.
                U = subsref(U, substruct('()', {':', 1:n})); % U = U(:,1 n)
                S = S(1 : n, 1 : n);
            else % m < n, since we eliminated m == n above.
                S = S(1 : m, 1 : m);
                V = subsref(V, substruct('()', {1:n, 1:m})); % V = V(1:n,1:m)
            end
        else
            error('econ parameter has incorrect value');
        end
    end
end
