function [result, t] = linfactor (arg1, arg2)
%LINFACTOR factorize a matrix, or use the factors to solve Ax=b.
% Uses LU or CHOL to factorize A, or uses a previously computed factorization to
% solve a linear system.  This function automatically selects an LU or Cholesky
% factorization, depending on the matrix.  A better method would be for you to
% select it yourself.  Note that mldivide uses a faster method for detecting
% whether or not A is a candidate for sparse Cholesky factorization (see spsym
% in the CHOLMOD package, for example).
%
% Example:
%   F = linfactor (A) ;     % factorizes A into the object F
%   x = linfactor (F,b) ;   % uses F to solve Ax=b
%   norm (A*x-b)
%
% A second output is the time taken by the method, ignoring the overhead of
% determining which method to use.  This makes for a fairer comparison between
% methods, since normally the user will know if the matrix is supposed to be
% symmetric positive definite or not, and whether or not the matrix is sparse.
% Also, the overhead here is much higher than mldivide or spsym.
%
% This function has its limitations:
%
% (1) determining whether or not the matrix is symmetric via nnz(A-A') is slow.
%     mldivide (and spsym in CHOLMOD) do it much faster.
%
% (2) MATLAB really needs a sparse linsolve.  See cs_lsolve, cs_ltsolve, and
%     cs_usolve in CSparse, for example.
%
% (3) this function really needs to be written as a mexFunction.
%
% (4) the full power of mldivide is not brought to bear.  For example, UMFPACK
%     is not very fast for sparse tridiagonal matrices.  It's about a factor of
%     four slower than a specialized tridiagonal solver as used in mldivide.
%
% (5) permuting a sparse vector or matrix is slower in MATLAB than it should be;
%     a built-in linfactor would reduce this overhead.
%
% (6) mldivide when using UMFPACK uses relaxed partial pivoting and then
%     iterative refinement.  This leads to sparser LU factors, and typically
%     accurate results.  linfactor uses sparse LU without iterative refinement.
%
% The primary purpose of this function is to answer The Perennially Asked
% Question (or The PAQ for short (*)):  "Why not use x=inv(A)*b to solve Ax=b?
% How do I use LU or CHOL to solve Ax=b?"  The full answer is below.  The short
% answer to The PAQ (*) is "PAQ=LU ... ;-) ... never EVER use inv(A) to solve
% Ax=b."
%
% The secondary purpose of this function is to provide a prototype for some of
% the functionality of a true MATLAB built-in linfactor function.
% 
% Finally, the third purpose of this function is that you might find it actually
% useful for production use, since its syntax is simpler than factorizing the
% matrix yourself and then using the factors to solve the system.
%
% See also lu, chol, mldivide, linsolve, umfpack, cholmod.
%
% Oh, did I tell you never to use inv(A) to solve Ax=b?
%
% Requires MATLAB 7.3 (R2006b) or later.

% Copyright 2007, Timothy A. Davis, University of Florida
% VERSION 1.1.0, Nov 1, 2007

if (nargin < 1 | nargin > 2 | nargout > 2)          %#ok
    error ('Usage: F=linfactor(A) or x=linfactor(F,b)') ;
end

if (nargin == 1)

    %---------------------------------------------------------------------------
    % F = linfactor (A) ;
    %---------------------------------------------------------------------------

    A = arg1 ;
    [m n] = size (A) ;
    if (m ~= n)
        error ('linfactor: A must be square') ;
    end

    if (issparse (A))

        % try sparse Cholesky (CHOLMOD): L*L' = P*A*P'
        if (nnz (A-A') == 0 & all (diag (A) > 0))   %#ok
            try
                tic ;
                [L, g, PT] = chol (A, 'lower') ;
                t = toc ;
                if (g == 0)
                    result.L = L ;
                    result.LT = L' ;    % takes more memory, but solve is faster
                    result.P = PT' ;    % ditto.  Need a sparse linsolve here...
                    result.PT = PT ;
                    result.kind = 'sparse Cholesky: L*L'' = P*A*P''' ;
                    result.code = 0 ;
                    return
                end
            catch
		% matrix is symmetric, but not positive definite
		% (or we ran out of memory)
            end
        end

        % try sparse LU (UMFPACK, with row scaling): L*U = P*(R\A)*Q
        tic ;
        [L, U, P, Q, R] = lu (A) ;
        t = toc ;
        result.L = L ;
        result.U = U ;
        result.P = P ;
        result.Q = Q ;
        result.R = R ;
        result.kind = 'sparse LU: L*U = P*(R\A)*Q where R is diagonal' ;
        result.code = 1 ;

    else

        % try dense Cholesky (LAPACK): L*L' = A
        if (nnz (A-A') == 0 & all (diag (A) > 0))                           %#ok
            try
                tic ;
                L = chol (A, 'lower') ;
                t = toc ;
                result.L = L ;
                result.kind = 'dense Cholesky: L*L'' = A' ;
                result.code = 2 ;
                return
            catch
		% matrix is symmetric, but not positive definite
		% (or we ran out of memory)
            end
        end

        % try dense LU (LAPACK): L*U = A(p,:)
        tic ;
        [L, U, p] = lu (A, 'vector') ;
        t = toc ;
        result.L = L ;
        result.U = U ;
        result.p = p ;
        result.kind = 'dense LU: L*U = A(p,:)' ;
        result.code = 3 ;

    end

else

    %---------------------------------------------------------------------------
    % x = linfactor (F,b)
    %---------------------------------------------------------------------------

    F = arg1 ;
    b = arg2 ;

    if (F.code == 0)

        % sparse Cholesky: MATLAB could use a sparse linsolve here ...
        tic ;
        result = F.PT * (F.LT \ (F.L \ (F.P * b))) ;
        t = toc ;

    elseif (F.code == 1)

        % sparse LU: MATLAB could use a sparse linsolve here too ...
        tic ;
        result = F.Q * (F.U \ (F.L \ (F.P * (F.R \ b)))) ;
        t = toc ;

    elseif (F.code == 2)

        % dense Cholesky: result = F.L' \ (F.L \ b) ;
        lower.LT = true ;
        upper.LT = true ;
        upper.TRANSA = true ;
        tic ;
        result = linsolve (F.L, linsolve (F.L, b, lower), upper) ;
        t = toc ;

    elseif (F.code == 3)

        % dense LU: result = F.U \ (F.L \ b (F.p,:)) ;
        lower.LT = true ;
        upper.UT = true ;
        tic ;
        result = linsolve (F.U, linsolve (F.L, b (F.p,:), lower), upper) ;
        t = toc ;
    end
end
