Code covered by the BSD License  

Highlights from
Multi-preconditioned GMRES

from Multi-preconditioned GMRES by Tyrone Rees
An extension of GMRES which allows the use of multiple preconditioners.

mpgmres(A,b,P,type_in,tol,maxits,x0,varargin)
function [x,relres,iter,resvec] = mpgmres(A,b,P,type_in,tol,maxits,x0,varargin)
%MPGMRES An extension of GMRES which accepts multiple preconditioners.
%
%   X = MPGMRES(A,B,P) solves the linear system A*X = B for X.  The matrix
%   A should be of dimension n by n, and generally large and sparse.  The
%   vector B must have length n.  P should be a cell array of
%   preconditioners, i.e. n by n matrices (or function handles which act on
%   n-vectors) which approximate 'A' in some sense.  If the supplied P is a
%   single matrix, then the method is equivalent to right-preconditioned
%   GMRES.  
%
%   X = MPGMRES(A,B,P,TYPE) specifies the type of mpgmres algorithm used.
%   TYPE can be a string, in which case the accepted values are:
%    - 'full',which runs full MPGMRES, or 
%    - 'trunc', which runs a truncated version.
%   TYPE can also be a structure, which gives more control over the type of
%   truncation.  For details, see <a href="matlab: help mpgmres_120112>extended_help">here</a>.  
%
%   X = MPGMRES(A,B,P,TYPE,TOL) specifies the tolerance of the method.  The
%   default value is 1e-6.
%
%   X = MPGMRES(A,B,P,TYPE,TOL,MAXITS) specifies the maxiumum number of
%   iterations the methods allows.  The default value is n.
%
%   X = MPGMRES(A,B,P,TYPE,TOL,MAXITS,X0) specifies the initial guess.  X0
%   must be a vector of dimension n.  The default value is the zero vector.
%
%   X = MPGMRES(A,B,P,TYPE,TOL,MAXITS,X0,STOREZ) specifies whether to store
%   or calculate the matrix of search directions.  STOREZ = 1 stores, 
%   STOREZ = 0 calculates.  The default is 0. 
%
%   X = MPGMRES(A,B,P,TYPE,TOL,MAXITS,X0,STOREZ,TESTORTH) specifies whether
%   or not to check for loss of orthogonality (should only be used for
%   diagonostic purposes, as this is a relatively expensive process).
%   TESTORTH = 1 runs the check, TESTORTH = 0 does not.  The default is 0.
%
%   X = MPGMRES(A,B,P,TYPE,TOL,MAXITS,X0,STOREZ,TESTORTH,SAVEMATS)
%   specifies whether or not to save the matrices generated in the Arnoldi process at each iteration.  This
%   should only be used for diagonostic purposes, as it is a relatively 
%   expensive process.  SAVEMATS = 0 doesn't save matrices, SAVEMATS = 1
%   does.  The default is 0.
%
%   [X,RELRES] = MPGMRES(A,B,...) also returns the relative residual.
%
%   [X,RELRES,ITER] = MPGMRES(A,B,...) also returns the number of
%   iterations needed for convergence.
% 
%   [X,RELRES,ITER,RESVEC] = MPGMRES(A,B,...) also returns a vector of the
%   residual norms at each iteration.
%
%   For more information, see:
%   Greif, C., Rees, T., Szyld, D. B.,
%   <a href="http://www.cs.ubc.ca/~tyronere/TR-2011-12.pdf">Multi-preconditioned GMRES</a>
%   Technical report: UBC CS TR-2011-12, or Temple Math. report 11-12-23
%   
%   12 Jan 2012: First release version of MPGMRES
%
%   Tyrone Rees                        tyronere@cs.ubc.ca
%   Department of Computer Science 
%   University of British Columbia


[lb,wb] = size(b);
if (lb==1)||(wb==1) % test if  b is a vector
    if (wb>=1)&&(lb==1) % make sure it's a column vector
        b = b'; lb = wb;
    end
else
    error('right hand side b must be a vector')
end

if isa(A,'float')
    [lA,wA] = size(A);
    if lA ~= wA % test if A is a square matrix
        error('The input matrix A must be square')
    end    
    if lb ~= lA % test if the size of the matrix and the size of b
        error('The right hand side vector must be of the same length as A')
    end
    Atype = 'matrix';
elseif isa(A,'function_handle')
    Atype = 'func';
else
    error('unsupported type of A supplied')
end

if nargin < 3
    error ('Not enough input parameters: ensure A, b and P are specified')
end

if nargin < 4
    type_in = 'trunc';
end

if nargin < 5
    tol = 1e-6;
else
    if isa(tol,'numeric') ~= 1
        error('The tolerance must be a numeric value')
    end
end
if nargin < 6
    maxits = lb;
else
    if isa(maxits,'numeric') ~= 1
        error('The max number of iterations must be an integer')
    end
end
if nargin < 7
    x0 = zeros(lb,1);
else
    [lx0,wx0] = size(x0);
    if (lx0 == 1)&&(wx0 == lb)
        x0 = x0'; lx0 = wx0;
    end
    if lx0 ~= lb
        error('The starting vector must be of the same length as b')
    end
end
numvarargs = length(varargin);
if numvarargs > 4
    error('too many inputs')
end
optargs = {0 0 0}; % optional values of 'storeZ','testorth', 'savemats'
optargs(1:numvarargs) = varargin;
[storeZ, testorth, savemats] = optargs{:};
% testorth -- tests orthogonality, see Paige slide 14: 
%     http://www.stanford.edu/dept/ICME/docs/seminars/Paige-2009-11-04.pdf


%% Get the type and number of preconditoiners
if isa(P,'cell')
    k=length(P);  % number of preconditioners
else % otherwise, just one preconditioner, applies standard right pre-GMRES
    k = 1;  P = {P};
end

% check that it's a valid type and calculate the number of 'inner' interations for the maxit supplied
if isa(type_in,'char') % if just a string, then just a vanilla full or trunc mpgmres
    switch type_in
        case 'trunc' 
            nmaxits = maxits*k;
            type.type = 'trunc'; % set defaults
            type.col = 0;
            type.method = 'sum';
        case 'full' % full algorithm
            if k == 1
                nmaxits = maxits;
            else
                nmaxits = (maxits^(k+1) - maxits)/(maxits - 1);
            end
            type = type_in;
        otherwise
            error('Invalid type parameter')
    end
elseif isa(type_in,'struct') % if a structure, then allow more control
    type = type_in;
    switch type.type
        case 'trunc'
            nmaxits = maxits*k+1;
            switch type.col
                case 1
                    valid = {'inorder','reverse','alternate','random'};
                    if max(strcmp(type.method,valid)) == 0
                            error('invalid type.method parameter')
                    end
                case 0
                    valid = {'sum','random'};
                    if max(strcmp(type.method,valid)) == 0
                            error('invalid type.method parameter')
                    end
                otherwise
                    error('invalid type.col value: must be 0 or 1')
            end
    end
end

%% pre-allocate variables

indvar = min(lb,nmaxits);
if storeZ
    Z = zeros(lb,indvar);
else
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %!! We don't need to store Z, as Z_k = [P_1^{-1} V_k ... P_t^{-1} V_k]%
    %!! and hence (Z^)y_k = P_1^{-1}(V^(:,Vindex_1))y_k(yindex_1) + ...   %
    %!!                     P_t^{-1}(V^(:,Vindex_t))y_k(yindex_t)         %
    %!! the values for the indices are stored in                          %
    %!! V_index and yk_index                                              %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % initialize arrays
    end_V = cell(k,1); end_V(:) = {0};      % stores the end of Vk_index
    % initalize the structure that holds this information:
    Zinfo = struct('yk_index',zeros(nmaxits,1),'V_index',zeros(nmaxits,1),'end_V',end_V);
    lastV = 0;
end
V = zeros(lb,indvar+1);
H = zeros(indvar+1,indvar);
resvec = zeros(indvar,1);
c = zeros(indvar,1);
s = zeros(indvar,1);
rhs = zeros(indvar,1);

%% Set some flags
lindep_flag = 0;        % set the linear dependence flag - set to 1 if H_(p+1,p) = 0


%% Initialization
if strcmp(Atype,'matrix')
    r = b - A*x0;           % initial residual
else
    r = b - A(x0);
end
nr = norm(r);           % norm of initial residual
rhs(1) = nr;            % initialize rhs
resvec(1) = 1;          % initialize the vector which stores the residuals
V(:,1) = r/nr;          % initialize the V matrix with normalized init. resid.
Z_temp = fullmultiprecondition(P,V(:,1));    % send to multiprecondition to get Z_temp
z_it = [1,0];           % [(outer) iteration, col of Z]
Zk = k;                 % a parameter which stores the size of the current Z_i
if ~storeZ
    VforZ = 1;
    ind_V = ones(k,1);
end
pp = 0;                 % initialize a concurrent index
nVk = 0;                % initialize the current size of Vk

%% Start the loop...
for p = 1:nmaxits                 % loop over the columns of Zk
    pp = pp+1;                    % update the 'real' index -- allows removal of rows
    if strcmp(Atype,'matrix')     % get vector which needs orthogonalizing
        w = A*Z_temp(:,z_it(2)+1);           % initial residual
    else
        w = A(Z_temp(:,z_it(2)+1));
    end        
    for i = 1:pp                  % loop over columns of V
        H(i,pp) = V(:,i)'*w;
        w = w - H(i,pp)*V(:,i);   % remove component of V_i from w
    end
    nw = norm(w);
    H(pp+1,pp) = nw;          % calculate norm of vector w
    
    % TEST if w is the zero vector, hence Z_k is linearly dependent
    if H(pp+1,pp)<sqrt(eps); lindep_flag = 1;  end
    for l = 1:pp-1  % update previous rots
        h1 = H(l,pp);
        h2 = H(l+1,pp);
        H(l,pp) = c(l)*h1 + s(l)*h2;
        H(l+1,pp) = -s(l)*h1 + c(l)*h2;
    end
    h1 = H(pp,pp);
    h2 = H(pp+1,pp);
    gam = sqrt(h1^2 + h2^2);
    c(pp) = h1/gam;
    s(pp) = h2/gam;
    H(pp,pp) = c(pp)*h1+s(pp)*h2;
    H(pp+1,pp) = 0;
    
    % update rhs
    rhs_old = rhs;              % save old rhs
    rhs(pp+1) = -s(pp)*rhs(pp);
    rhs(pp) = c(pp)*rhs(pp);
    
    %    test_lindep_block;
    if (lindep_flag==1)&&((abs(rhs(pp+1)) >= tol*nr)||(isnan(abs(rhs(pp+1))))); % the last column of Z is dependent on the others
        fprintf('Column of Z linearly dependent...removing \n')
        pp = pp-1;          % reduce index by 1
        lindep_flag = 0;    % reset linear dependence flag
        rhs = rhs_old;      % replace rhs
    else
        nVk = nVk + 1;
        if storeZ
            Z(:,pp) = Z_temp(:,z_it(2)+1);
        else
            ik = ceil(nVk/VforZ); % find which preconditioner
            Zinfo(ik).end_V = Zinfo(ik).end_V + 1;
            Zinfo(ik).yk_index(Zinfo(ik).end_V) = pp;
            if isa(Zinfo(ik).V_index,'cell') % then type.col == 1, and cells are involved
                Zinfo(ik).V_index{Zinfo(ik).end_V} = ind_V;
                Zinfo(ik).wt{Zinfo(ik).end_V} = wt;
                lastV = max(lastV,max(Zinfo(ik).V_index{Zinfo(ik).end_V}));
            else
                Zinfo(ik).V_index(Zinfo(ik).end_V) = ind_V(nVk);
                lastV = max(lastV,Zinfo(ik).V_index(Zinfo(ik).end_V));
            end
            
        end
        if lindep_flag==1  % we have a lucky breakdown
            z_it(1) = z_it(1)+1;
            resvec(z_it(1)) = abs(rhs(pp+1))/nr;    % save residual to resvec
            fprintf('MPGMRES converged -- lucky breakdown! \n')
            break
        end
        V(:,pp+1) = w/nw;       % set new basis vector V_{p+1}
        switch testorth
            case 1             % test if V's are still orthogonal (see note above)
                S = triu(V'*V,1); fprintf('Measure of loss of orthog. \n')
                norm((eye(size(S)) + S)\S)
        end
    end
    z_it(2) = z_it(2)+1;         % update the index column of Z we're working on
    
    if z_it(2) == Zk
        resvec(z_it(1)+1) = abs(rhs(pp+1))/nr;    % save residual to resvec
        %% test convergence
        if resvec(z_it(1)+1)<tol
            fprintf('MPGMRES converged! \n')
            z_it(1) = z_it(1) + 1;
            break
        end
        %% multiprecondition
        if nVk == 0
            error('All current search directions are linearly dependent on the previous ones.\n Re-reun with a different trucation rule or starting vector.')
        end
        if isa(type,'char') % if just a string, then just a vanilla full or trunc mpgmres
            switch type
                case 'full' % full algorithm
                    ind_V = repmat(pp+1-nVk+1:pp+1,1,k);
                    Z_temp = fullmultiprecondition(P,V(:,pp+1-nVk+1:pp+1));
                    Zk = k*nVk;
                    if ~storeZ
                        VforZ = nVk;
                    end
                    nVk = 0; % reset nVk
            end
        elseif isa(type,'struct') % if a structure, then allow more control
            if type.col == 1 % act on individual columns of V
                % the supported methods in this case are currently:
                % in order, reverse order, random order, alternate order
                switch type.method
                    case 'inorder' % P_i^{-1}Ve_i
                        ind_V = pp+1 + (1-nVk:0);
                        Z_temp = multipreconditionvec(P,V(:,ind_V));
                        if ~storeZ
                            VforZ = 1;
                            ind_V = increase_ind_V(ind_V,k);
                        end
                    case 'reverse' % P_i^{-1}Ve_j, j = n-i+1
                        ind_V = fliplr(pp+1 + (1-nVk:0));
                        Z_temp = multipreconditionvec(P,V(:,ind_V));
                        if ~storeZ
                            VforZ = 1;
                            ind_V = increase_ind_V(ind_V,k);
                        end
                    case 'alternate' % swaps between previous two cases
                        if rem(pp,2) == 0
                            ind_V = fliplr(pp+1-nVk+1:pp+1);
                            Z_temp = multipreconditionvec(P,V(:,ind_V));
                            if ~storeZ
                                ind_V = increase_ind_V(ind_V,k);
                            end
                        else
                            ind_V = pp+1-nVk+1:pp+1;
                            Z_temp = multipreconditionvec(P,V(:,ind_V));
                            if ~storeZ
                                VforZ = 1;
                                ind_V = increase_ind_V(ind_V,k);
                            end
                        end
                    case 'random' % P_i^{-1}Ve_j, j random
                        order = randperm(nVk);
                        ind_V = zeros(1,nVk);
                        for iv = 1:nVk
                            ind_V(iv) = pp+1-nVk+order(iv);
                        end
                        Z_temp = multipreconditionvec(P,V(:,ind_V));
                        if ~storeZ
                            VforZ = 1;
                            ind_V = increase_ind_V(ind_V,k);
                        end
                    otherwise
                        error('Unsupported value of type.method')
                end
                Zk = k; nVk = 0;      % update size of Z_k and current size of V_k
            elseif type.col == 0 % act on all the columns of V
                if (z_it(1) == 1)&&(~storeZ) % upadate V_index so it's a cell array, not a vector                    
                    for ii = 1:k
                        Zinfo(ii).V_index = cell(nmaxits,1);
                        Zinfo(ii).V_index(1) = {1};
                        Zinfo(ii).wt = cell(nmaxits,1);
                        Zinfo(ii).wt(1) = {1};
                    end
                end 
                switch type.method     
                    case 'sum' % sum the columns
                        VforZ = 1;
                        ind_V = pp+1-nVk+1:pp+1;
                        wt = ones(nVk,1);
                        v_temp = V(:,ind_V)*wt;
                        Z_temp = fullmultiprecondition(P,v_temp);
                    case 'random' % sum the columns with random weights
                        ind_V = pp+1-nVk+1:pp+1;
                        wt = rand(nVk,1);
                        v_temp = V(:,ind_V)*wt;
                        Z_temp = fullmultiprecondition(P,v_temp);
                    otherwise
                        error('Unsupported value of type.method')
                end
                Zk = k; nVk = 0;      % update size of Z_k and current size of V_k
            else
                error('Please set type.col to be 0 or 1')
            end
        else
            error('Invalid ''type'' option. \n')
        end
        z_it(1) = z_it(1)+1;
        z_it(2) = 0;
        
    end
    switch savemats
        case 1
            save('mpgmres_const.mat','V','H','Z');
    end
end
switch savemats
    case 1
        save('mpgmres_b_const.mat','V','H','Z');
end
yk = H(1:pp,1:pp)\rhs(1:pp);
if storeZ
    x = x0 + Z(:,1:pp)*yk;
else
    x = x0;
    if isa(Zinfo(1).V_index,'cell') % then type.col = 0
        for ii = 1:k
            Vyk_i = zeros(lb,1);
            for iv = 1:Zinfo(ii).end_V % get the summed matrix
                Vyk_i = Vyk_i + (V(:,Zinfo(ii).V_index{iv})*Zinfo(ii).wt{iv})*yk(Zinfo(ii).yk_index(iv));
            end
            if isa(P{ii},'function_handle')
                x = x + P{ii}(Vyk_i);
            else
                x = x + P{ii}\Vyk_i;
            end
            
        end
    else
        for ii = 1:k
            Vyk_i = V(:,Zinfo(ii).V_index(1:Zinfo(ii).end_V))*yk(Zinfo(ii).yk_index(1:Zinfo(ii).end_V));
            if isa(P{ii},'function_handle')
                x = x + P{ii}(Vyk_i);
            else
                x = x + P{ii}\Vyk_i;
            end
        end
    end
end



resvec = resvec(1:z_it(1));

iter = length(resvec) - 1;
relres = resvec(end)/norm(b);


%% apply multiple preconditioners to the residual r

function [z]=multipreconditionvec(pre,Q)

if isa(pre{1},'cell')
    [z]=multipreconditionvec(pre{1},Q);
else
    [n,m] = size(Q);
    k = length(pre);
    z=zeros(n,k);
    for i=1:m
        if isa(pre{i},'function_handle')
            z(:,i)=pre{i}(Q(:,i));
        else
            z(:,i)=pre{i}\Q(:,i);
        end
    end
    for i = m+1 : length(pre);
        if isa(pre{i},'function_handle')
            z(:,i)=pre{i}(Q(:,mod(i,m)+1));
        else
            z(:,i)=pre{i}\Q(:,mod(i,m)+1);
        end
    end
end

function [z]=fullmultiprecondition(pre,Q)

if isa(pre{1},'cell')
    [z]=fullmultiprecondition(pre{1},Q);
else
    lpre = length(pre);
    [lA,lQ] = size(Q);
    z=zeros(lA,lQ*lpre);
    ind = 1;
    ivec = 1:lQ*lpre;
    for i=1:lpre
        if isa(pre{i},'function_handle')
            for j = 1:lQ
                z(:,ivec(ind))=pre{i}(Q(:,j)); ind = ind+1;
            end
        else
            z(:,ivec(ind:ind+lQ-1))=pre{i}\Q; ind = ind + lQ;
        end
    end
end

function new_ind_V = increase_ind_V(ind_V,k)
sizeInd_V = length(ind_V);
if sizeInd_V < k
    new_ind_V = zeros(1,k);
    new_ind_V(1:sizeInd_V) = ind_V;
    for jv = sizeInd_V + 1:k
        new_ind_V(jv) = ind_V(mod(jv,sizeInd_V)+1);
    end
else
    new_ind_V = ind_V;
end


function extended_help
%EXTENDED_HELP some technical details about the 'type' option
%   As well as accepting the strings 'full' and 'trunc', MPGMRES also
%   accepts a structure input.  The structure should have the fields
%   TYPE.col and TYPE.method  Accepted values in this case are:
%    - TYPE.col = 1; these act on individual columns of the matrix of basis 
%      vectors, V_k.  Supported values of TYPE.method in this case are:
%       + TYPE.method = 'inorder'.  Applies P_i to the ith column of V_k +
%       TYPE.method = 'reverse'.  Applies P_i to the (t+1-i)th column of
%          V_k, where V_k has t columns
%       + TYPE.method = 'alternate'.  Alternates between the previous two
%          methods between iterations
%       + TYPE.method = 'random'.  Applies P_i to a random permutation of
%          V_k
%     - TYPE.col = 0; these act all of the columns of V_k.  Supported
%       values of TYPE.method in this case are:
%        + TYPE.method = 'sum'.  Applies P_i to the sum of the columns of
%           V_k
%        + TYPE.mehtod = 'random'. Applies P_i to a linear combination of
%           the columns of V_k, with weights uniformly distributed in the
%           range [0,1]
%   For more information, see the manuscript 
%   <a
%   href="http://www.cs.ubc.ca/~tyronere/TR-2011-12.pdf">Multi-preconditioned GMRES</a>.
error('This is a placeholder function just for helptext');

Contact us