Code covered by the BSD License  

Highlights from
pcg.m with 'null' and 'flex' options

image thumbnail
from pcg.m with 'null' and 'flex' options by Andrew Knyazev
Preconditioned Conjugate Gradients handles homogeneous equations and nonsymmetric preconditioning

pcg(A,b,tol,maxit,M1,M2,x0,opts1,opts2,varargin)
function [x,flag,relres,iter,resvec] = pcg(A,b,tol,maxit,M1,M2,x0,opts1,opts2,varargin)
%PCG   Preconditioned Conjugate Gradients Method.
%   X = PCG(A,B) attempts to solve the system of linear equations A*X=B for
%   X. The N-by-N coefficient matrix A must be symmetric and positive
%   definite and the right hand side column vector B must have length N.
%
%   X = PCG(AFUN,B) accepts a function handle AFUN instead of the matrix A.
%   AFUN(X) accepts a vector input X and returns the matrix-vector product
%   A*X. In all of the following syntaxes, you can replace A by AFUN.
%
%   X = PCG(A,B,TOL) specifies the tolerance of the method. If TOL is []
%   then PCG uses the default, 1e-6.
%
%   X = PCG(A,B,TOL,MAXIT) specifies the maximum number of iterations. If
%   MAXIT is [] then PCG uses the default, min(N,20).
%
%   X = PCG(A,B,TOL,MAXIT,M) and X = PCG(A,B,TOL,MAXIT,M1,M2) use symmetric
%   positive definite preconditioner M or M=M1*M2 and effectively solve the
%   system inv(M)*A*X = inv(M)*B for X. If M is [] then a preconditioner
%   is not applied. M may be a function handle MFUN returning M\X.
%
%   X = PCG(A,B,TOL,MAXIT,M1,M2,X0) specifies the initial guess. If X0 is
%   [] then PCG uses the default, an all zero vector (or a random vector if
%   B is empty).
%
%   X = PCG(A,B,TOL,MAXIT,M1,M2,X0,'flex') changes the stadard PCG into the
%   flexibble PCG. The latter is a bit more expensive, but it works in some
%   cases where the standard PCG fails, e.g., if M is not fixed symmetric
%   positive definite.
%
%   X = PCG(A,B,TOL,MAXIT,M1,M2,X0,'null') can be used, if B is a
%   zero vector, to force the PCG to attempt to calculate the nontrivial
%   solution of the homegeneous system of linear equations A*X=0, where A
%   must be symmetric and positive semi-definite. Without the 'null' option,
%   if B is zero, the code immideately returns the trivial solution X=0.
%   The 'flex' and 'null' options can be used together.
%
%   [X,FLAG] = PCG(A,B,...) also returns a convergence FLAG:
%    0 PCG converged to the desired tolerance TOL within MAXIT iterations
%    1 PCG iterated MAXIT times but did not converge.
%    2 preconditioner M was ill-conditioned.
%    3 PCG stagnated (two consecutive iterates were the same).
%    4 one of the scalar quantities calculated during PCG became too
%      small or too large to continue computing.
%
%   [X,FLAG,RELRES] = PCG(A,B,...) also returns the relative residual
%   NORM(B-A*X)/NORM(B). If B is zero, then RELRES is set to 0, unless
%   the 'null' option is set, where the relative residual is defined as
%   NORM(A*X)/NORM(X). If FLAG is 0, then RELRES <= TOL.
%
%   [X,FLAG,RELRES,ITER] = PCG(A,B,...) also returns the iteration number
%   at which X was computed: 0 <= ITER <= MAXIT.
%
%   [X,FLAG,RELRES,ITER,RESVEC] = PCG(A,B,...) also returns a vector of the
%   estimated residual norms at each iteration including NORM(B-A*X0) if
%   the 'null' option in absent. With the 'null' option, the residuals and
%   the relative residuals are defined to be the same.
%
% % Example:
% clear all; n = 100; A = spdiags([1:n]',0,n,n);
% tol = 1e-6;  maxit = 20;
% M = A;
% A=A+sprandsym(n,.1); b = A*ones(n,1);
% x = pcg(A,b,tol,maxit,M);
% % In the last line, one can use a matrix-vector product function as well:
% afun = @(x)A*x; x = pcg(@(x)afun(x),b,tol,maxit,M);
%
% %  Example of the 'flex' option is the same as above, except for M:
% clear all; n = 100; A = spdiags([1:n]',0,n,n);
% tol = 1e-6;  maxit = 20;
% M = A; M(1,2) = 2; % M is no longer symmetric 
% A=A+sprandsym(n,.1); b = A*ones(n,1);
% [x,~,~,~,rv] = pcg(A,b,tol,maxit,M);
% [xf,~,~,~,rvf] = pcg(A,b,tol,maxit,M,[],[],'flex');
% semilogy(rv); hold on; semilogy(rvf,'--rs');
% title('Standard vs. Flexible PCG. Nonsymmetric preconditioning.')
%
% %  Example of the 'null' option with and without the 'flex' option:
% clear all; close all; n = 100; b = zeros(n,1); 
% v = (0:n-1)'; A = spdiags([v 2*v v-1],-1:1,n,n); % symmetric semidefinite
% % A has a null-space spanned by the first coordinate vector 
% tol = 1e-6;  maxit = 50; v = (1:n)'; 
% M = spdiags([v 2*v v-1],-1:1,n,n); % M is SPD
% [xn,~,~,~,rvn] = pcg(A,b,tol,maxit,M,[],[],'null');
% [xnf,~,~,~,rvnf] = pcg(A,b,tol,maxit,M,[],[],'null','flex');
% figure(665); semilogy(rvn); hold on; semilogy(rvnf,'--rs'); 
% title('Standard vs. Flexible PCG null. SPD preconditioning.')
% M = spdiags([v 2*v v],-1:1,n,n);   % M is non-symmetric
% [xn,~,~,~,rvn] = pcg(A,b,tol,maxit,M,[],[],'null');
% [xnf,~,~,~,rvnf] = pcg(A,b,tol,maxit,M,[],[],'null','flex');
% figure(667); semilogy(rvn); hold on; semilogy(rvnf,'--rs');
% title('Standard vs. Flexible PCG null. Nonsymmetric preconditioning.')
%
%   Class support for inputs A,B,M1,M2,X0 and the output of AFUN:
%      float: double
%
%   See also BICG, BICGSTAB, BICGSTABL, CGS, GMRES, LSQR, MINRES, QMR,
%   SYMMLQ, TFQMR, CHOLINC, FUNCTION_HANDLE.

% This is an updated for MATLAB R2011a Revision 2.0 of the code
% http://www.mathworks.com/matlabcentral/fileexchange/50-pcgnull-m
% by Andrew Knyazev, andrew.knyazev@na-net.ornl.gov
% Updated by Andrew Knyazev to implement the new 'flex' option and to make 
% the code PCG compatible so that it can be used as a PCG replacement. 
% Also included new examples in the header.  
%
% This Revision 2.0 is subject to the conditiones of original Revision 1.0:
% "This is a modified version of PCG, Revision 1.6, 1996, by Penny Anderson,
% modified with the permission of The MathWorks, Inc., the copyright owner.
% This Revision 1.0 may not be used with any products other than products of
% The MathWorks, Inc., nor may it be used in or as part of another computer program.
% MATLAB is a registered trademark of The MathWorks, Inc."

if (nargin < 2)
    error('MATLAB:pcg:NotEnoughInputs', 'Not enough input arguments.');
end

% Determine whether A is a matrix or a function.
[atype,afun,afcnstr] = iterchk(A);
if strcmp(atype,'matrix')
    % Check matrix and right hand side vector inputs have appropriate sizes
    [m,n] = size(A);
    if (m ~= n)
        error('MATLAB:pcg:NonSquareMatrix', 'Matrix must be square.');
    end
    if ~isequal(size(b),[m,1])
        error('MATLAB:pcg:RSHsizeMatchCoeffMatrix', ...
            ['Right hand side must be a column vector of' ...
            ' length %d to match the coefficient matrix.'],m);
    end
else
    m = size(b,1);
    n = m;
    if ~iscolumn(b)
        error('MATLAB:pcg:RSHnotColumn',...
            'Right hand side must be a column vector.');
    end
end

% Assign default values to unspecified parameters
if (nargin < 3) || isempty(tol)
    tol = 1e-6;
end
warned = 0;
if tol <= eps
    warning('MATLAB:pcg:tooSmallTolerance', ...
        strcat('Input tol is smaller than eps and may not be achieved',...
        ' by PCG\n','         Try to use a bigger tolerance'));
    warned = 1;
    tol = eps;
elseif tol >= 1
    warning('MATLAB:pcg:tooBigTolerance', ...
        strcat('Input tol is bigger than 1 \n',...
        '         Try to use a smaller tolerance'));
    warned = 1;
    tol = 1-eps;
end
if (nargin < 4) || isempty(maxit)
    maxit = min(n,20);
end

if ((nargin >= 5) && ~isempty(M1))
    existM1 = 1;
    [m1type,m1fun,m1fcnstr] = iterchk(M1);
    if strcmp(m1type,'matrix')
        if ~isequal(size(M1),[m,m])
            error('MATLAB:pcg:WrongPrecondSize', ...
                ['Preconditioner must be a square matrix' ...
                ' of size %d to match the problem size.'],m);
        end
    end
else
    existM1 = 0;
    m1type = 'matrix';
end

if ((nargin >= 6) && ~isempty(M2))
    existM2 = 1;
    [m2type,m2fun,m2fcnstr] = iterchk(M2);
    if strcmp(m2type,'matrix')
        if ~isequal(size(M2),[m,m])
            error('MATLAB:pcg:WrongPrecondSize', ...
                ['Preconditioner must be a square matrix' ...
                ' of size %d to match the problem size.'],m);
        end
    end
else
    existM2 = 0;
    m2type = 'matrix';
end

flexible = 0; nullPCG=0; %the default
if ((nargin >= 8))
    if strcmp(opts1,'flex')
        flexible = 1; %Flexible PCG
    elseif strcmp(opts1,'null')
        nullPCG = 1; %nullPCG
    end
end
if ((nargin >= 9))
    if strcmp(opts2,'flex')
        flexible = 1; %Flexible PCG
    elseif strcmp(opts2,'null')
        nullPCG = 1; %nullPCG
    end
end

if ((nargin >= 7) && ~isempty(x0))
    if ~isequal(size(x0),[n,1])
        error('MATLAB:pcg:WrongInitGuessSize', ...
            ['Initial guess must be a column vector of' ...
            ' length %d to match the problem size.'],n);
    elseif norm(x0)==0 && pcgNull
        error('MATLAB:pcg:WrongInitGuessSizeNullPCG', ...
            ['Initial nullPCG guess must be a nonzero vector.'],n);
    else
        if ~nullPCG
            x = x0;
        else
            x = x0/norm(x0); %normalize nullPCG initial guess
        end
    end
else
    if ~nullPCG
        x = zeros(n,1);
    else
        x0 = randn(n,1); %nullPCG does not allow zero initial guess
        x = x0/norm(x0); %better make it normalized
    end
end

if ((nargin > 9) && strcmp(atype,'matrix') && ...
        strcmp(m1type,'matrix') && strcmp(m2type,'matrix'))
    error('MATLAB:pcg:TooManyInputs', 'Too many input arguments.');
end

% Check for all zero right hand side vector => all zero solution
n2b = norm(b);                     % Norm of rhs vector, b
if (n2b == 0) && ~nullPCG          % if    rhs vector is all zeros
    x = zeros(n,1);                % then  solution is all zeros
    flag = 0;                      % a valid solution has been obtained
    relres = 0;                    % the relative residual is actually 0/0
    iter = 0;                      % no iterations need be performed
    resvec = 0;                    % resvec(1) = norm(b-A*x) = norm(0)
    if (nargout < 2)
        itermsg('pcg',tol,maxit,0,flag,iter,NaN);
    end
    return
end

% Set up for the method
flag = 1;
xmin = x;                          % Iterate which has minimal residual so far
imin = 0;                          % Iteration at which xmin was computed
if ~nullPCG
    tolb = tol * n2b;                  % Relative tolerance
else
    tolb = tol;
end
r = b - iterapp('mtimes',afun,atype,afcnstr,x,varargin{:});
rp = zeros(size(r));
normr = norm(r); % Norm of residual
normr_act = normr;

if (normr <= tolb)                 % Initial guess is a good enough solution
    flag = 0;
    if ~nullPCG
        relres = normr / n2b;
    else
        relres = normr;
    end
    iter = 0;
    resvec = normr;
    if (nargout < 2)
        itermsg('pcg',tol,maxit,0,flag,iter,relres);
    end
    return
end

resvec = zeros(maxit+1,1);         % Preallocate vector for norm of residuals
resvec(1,:) = normr;               % resvec(1) = norm(b-A*x0)
normrmin = normr;                  % Norm of minimum residual
rho = 1;
stag = 0;                          % stagnation of the method
moresteps = 0;
maxmsteps = min([floor(n/50),5,n-maxit]);
maxstagsteps = 3;

% loop over maxit iterations (unless convergence or failure)

for ii = 1 : maxit
    if existM1
        y = iterapp('mldivide',m1fun,m1type,m1fcnstr,r,varargin{:});
        if ~all(isfinite(y))
            flag = 2;
            break
        end
    else % no preconditioner
        y = r;
    end
    
    if existM2
        z = iterapp('mldivide',m2fun,m2type,m2fcnstr,y,varargin{:});
        if ~all(isfinite(z))
            flag = 2;
            break
        end
    else % no preconditioner
        z = y;
    end
    
    rho1 = rho;
    rho = r' * z;
    if ((rho == 0) || isinf(rho))
        flag = 4;
        break
    end
    if (ii == 1)
        p = z;
    else
        if flexible
            beta = (r-rp)' * z / rho1;  %flexible
        else
            beta = rho / rho1; %standard
        end
        if ((beta == 0) || isinf(beta))
            flag = 4;
            break
        end
        p = z + beta * p;
    end
    q = iterapp('mtimes',afun,atype,afcnstr,p,varargin{:});
    pq = p' * q;
    if ((pq <= 0) || isinf(pq))
        flag = 4;
        break
    else
        alpha = rho / pq;
    end
    if isinf(alpha)
        flag = 4;
        break
    end
    
    % Check for stagnation of the method
    if (norm(p)*abs(alpha) < eps*norm(x))
        stag = stag + 1;
    else
        stag = 0;
    end
    
    x = x + alpha * p;             % form new iterate
    if flexible
        rp = r;
    end
    r = r - alpha * q;
    if ~nullPCG
        normr = norm(r);
    else
        normr = norm(r)/norm(x);
    end
    normr_act = normr;
    resvec(ii+1,1) = normr;
    
    % check for convergence
    if (normr <= tolb || stag >= maxstagsteps || moresteps)
        r = b - iterapp('mtimes',afun,atype,afcnstr,x,varargin{:});
        normr_act = norm(r);
        resvec(ii+1,1) = normr_act;
        if (normr_act <= tolb)
            flag = 0;
            iter = ii;
            break
        else
            if stag >= maxstagsteps && moresteps == 0
                stag = 0;
            end
            moresteps = moresteps + 1;
            if moresteps >= maxmsteps
                if ~warned
                    warning('MATLAB:pcg:tooSmallTolerance', ...
                        strcat('Input tol may be smaller than eps*cond(A)',...
                        ' and might not be achieved by PCG\n',...
                        '         Try to use a bigger tolerance'));
                end
                flag = 3;
                iter = ii;
                break;
            end
        end
    end
    if (normr_act < normrmin)      % update minimal norm quantities
        normrmin = normr_act;
        xmin = x;
        imin = ii;
    end
    if stag >= maxstagsteps
        flag = 3;
        break;
    end
end                                % for ii = 1 : maxit

% returned solution is first with minimal residual
if (flag == 0)
    if ~nullPCG
        relres = normr_act / n2b;
    else
        relres = normr_act;
    end
else
    r_comp = b - iterapp('mtimes',afun,atype,afcnstr,xmin,varargin{:});
    if norm(r_comp) <= normr_act
        x = xmin;
        iter = imin;
        if ~nullPCG
            relres = norm(r_comp) / n2b;
        else
            relres = norm(r_comp);
        end
    else
        iter = ii;
        if ~nullPCG
            relres = normr_act / n2b;
        else
            relres = normr_act;
        end
    end
end

% truncate the zeros from resvec
if ((flag <= 1) || (flag == 3))
    resvec = resvec(1:ii+1,:);
else
    resvec = resvec(1:ii,:);
end

% only display a message if the output flag is not used
if (nargout < 2)
    itermsg('pcg',tol,maxit,ii,flag,iter,relres);
end

Contact us