function [L U p q] = lucp(A,tol,pm_opt)
%LUCP     LU factorization with complete pivoting.
%
% To compute the LU factorization under default settings:
%
%  [L U p q] = lucp(A)
%
% This produces a factorization such that L*U = A(p,q).  Vectors p and q
% permute the rows and columns, respectively.
%
% The pivot tolerance can be controlled:
%
%  [L U p q] = lucp(A,tol)
%
% The algorithm will terminate if the absolute value of the pivot is less
% than tol.
%
% Permutation matrices can be generated:
%
%  [L U P Q] = lucp(A,tol,'matrix')
%  [L U P Q] = lucp(A,tol,'sparse')
%
% The first will generate full permutation matrices P and Q such that 
% L*U = P*A*Q.  The second generates sparse P and Q.
%
% If A is sparse, L and U will be sparse.
%
% This function works on non-square matrices.
%
% Input:
%  A = matrix
%  tol = pivot tolerance (defualt is 1e-10)
%  pm_opt = permutation output options
%         = 'vector' for permutation vectors, L*U = A(p,q), defualt
%         = 'matrix' for full permutation matrices, L*U = P*A*Q
%         = 'sparse' for sparse permutation matrices, L*U = P*A*Q
%
% Output:
%  L = lower triangular factor
%  U = upper triangular factor
%  p = row permutation
%  q = column permutation
%
% Reference:
%  Algorithm 3.4.2, Matrix Computations, Golub and Van Loan. (3rd ed)
%  
% Other Implementations:
%  Gaussian Elimination using Complete Pivoting.  Alvaro Moraes.
%  http://www.mathworks.com/matlabcentral/fileexchange/25758
%
%  Gauss elimination with complete pivoting.  Nickolas Cheilakos.
%  http://www.mathworks.com/matlabcentral/fileexchange/13451
%  (Does not work with rectangular A)
%
%  Rank Revealing Code.  Leslie Foster.
%  http://www.math.sjsu.edu/~foster/rankrevealingcode.html
%  (Uses mex libraries for computation)
%


%
% 2010-03-28 (nwh) first version.
% 2010-04-14 (nwh) added more references.
% 2010-04-24 (nwh) perform final column swap so U is well conditioned
%

%
% License: see license.txt.
%

% handle optional inputs
if nargin < 2 || isempty(tol)
  tol = 1e-10;
end

if nargin < 3 || isempty(pm_opt)
  pm_opt = 'vector';
end

if strcmp(pm_opt,'vector')
  pm_flag = false;
  sp_flag = false;
elseif strcmp(pm_opt,'matrix')
  pm_flag = true;
  sp_flag = false;
elseif strcmp(pm_opt,'sparse')
  pm_flag = true;
  sp_flag = true;
else
  error('lucp:invalidOption','''%s'' is an un recognized option.',pm_opt)
end

[n m] = size(A);

% pivot vectors
p = (1:n)';
q = (1:m)';

% temp storage
rt = zeros(m,1); % row temp
ct = zeros(n,1); % col temp
t = 0; % scalar temp

for k = 1:min(n-1,m)
  % determine pivot
  [cv ri] = max(abs(A(k:n,k:m)));
  [rv ci] = max(cv);
  rp = ri(ci)+k-1;
  cp = ci+k-1;
  
  % swap row
  t = p(k);
  p(k) = p(rp);
  p(rp) = t;
  rt = A(k,:);
  A(k,:) = A(rp,:);
  A(rp,:) = rt;
  
  % swap col
  t = q(k);
  q(k) = q(cp);
  q(cp) = t;
  ct = A(:,k);
  A(:,k) = A(:,cp);
  A(:,cp) = ct;
  
  if abs(A(k,k)) >= tol
    rows = (k+1):n;
    cols = (k+1):m;
    A(rows,k) = A(rows,k)/A(k,k);
    A(rows,cols) = A(rows,cols)-A(rows,k)*A(k,cols);
  else
    % stop factorization if pivot is too small
    break
  end
end

% final column swap if m > n
if m > n
  % determine col pivot
  [cv ci] = max(abs(A(n,n:m)));
  cp = ci+n-1;
  
  % swap col
  t = q(n);
  q(n) = q(cp);
  q(cp) = t;
  ct = A(:,n);
  A(:,n) = A(:,cp);
  A(:,cp) = ct;
end

% produce L and U matrices
% these are sparse if L and U are sparse
l = min(n,m);
L = tril(A(1:n,1:l),-1) + speye(n,l);
U = triu(A(1:l,1:m));

% produce sparse permutation matrices if desired
if pm_flag
  p = sparse(1:n,p,1);
  q = sparse(q,1:m,1);
end

% produce full permutation matrices if desired
if ~sp_flag
  p = full(p);
  q = full(q);
end
