function [ x, istop, itn, anorm, acond, rnorm, xnorm ] = ...
           symmlq( n, b, aprodname, msolvename, iw, rw,  ...
                   precon, shift, show, check, itnlim, rtol )
%        [ x, istop, itn, anorm, acond, rnorm, xnorm ] = ...
%          symmlq( n, b, aprodname, msolvename, iw, rw,  ...
%                  precon, shift, show, check, itnlim, rtol )
%
%  SYMMLQ is designed to solve the system of linear equations Ax = b
%  where A is an n by n symmetric matrix and b is a given vector.
%  A is accessed by means of a function call of the form
%               y = aprod ( n, x, iw, rw )
%  which must return the product y = Ax for any given vector x.
%  A positive-definite preconditioner M = C C' may optionally
%  be specified.  If precon is true, a function call of the form
%               y = msolve( n, x, iw, rw )
%  must solve the system My = x.
%  WARNING:   The files containing the functions 'aprod' and 'msolve'
%             must not be called aprodname.m or msolvename.m !!!!
%
%  For further information, type    help symdoc.

%  07 Jun 1989: Date of Fortran 77 version written by
%               Michael Saunders, Stanford University.
%  15 May 1990: MATLAB m-file symmlq.m derived from Fortran version
%               by Gerald N. Miranda Jr, UCSD.
%  02 Oct 1997: Move to CG point only if it is better than LQ point.
%               For interest, print qrnorm (= rnorm for minres).
%               Note that cgnorm is always one step ahead of qrnorm.
%  20 Oct 1999: Bug.  alfa1 = 0 caused Anorm = 0, divide by zero.
%               Need to estimate Anorm from column of Tk.
%  ------------------------------------------------------------------
%
%  Initialize                               
first = 'Enter SYMMLQ.   ';
last  = 'Exit  SYMMLQ.   ';
space = ' ';
msg   =[' beta2 = 0.  If M = I, b and x are eigenvectors    '
        ' beta1 = 0.  The exact solution is  x = 0          '
        ' Requested accuracy achieved, as determined by rtol'
        ' Reasonable accuracy achieved, given eps           '
        ' x has converged to an eigenvector                 '
        ' acond has exceeded 0.1/eps                        '
        ' The iteration limit was reached                   '
        ' aprod  does not define a symmetric matrix         '
        ' msolve does not define a symmetric matrix         '
        ' msolve does not define a pos-def preconditioner   '];

true   = 1;   false  = 0;
 
if show
   disp( space );
   disp( [ first 'Solution of symmetric Ax = b' ] );
   disp( sprintf( 'n      =  %3g     precon =  %4g           shift  =  %23.14e',...
                   n,                precon,                 shift ) );
   disp( sprintf( 'itnlim =  %3g     eps    =  %11.2e    rtol   =  %11.2e\n' ,...
                   itnlim,           eps,                rtol ) );
end

istop  = 0;   ynorm  = 0;    w = zeros(n,1);  acond = 0;
itn    = 0;   xnorm  = 0;    x = zeros(n,1);  done  = false;
anorm  = 0;   rnorm  = 0;    v = zeros(n,1);

%  Set up y for the first Lanczos vector v1.
%  y is really beta1 * P * v1  where  P = C^(-1).
%  y and beta1 will be zero if b = 0.

y      = b;     r1     = b;
if precon,      y      = feval( msolvename, n, r1, iw, rw );  end
b1     = y(1);  beta1  = r1' * y;

%  See if msolve is symmetric.

if check  &  precon
   r2     = feval( msolvename, n, y, iw, rw );
   s      = y' * y;
   t      = r1' * r2;
   z      = abs( s - t );
   epsa   = (s + eps) * eps^(1/3);
   if z > epsa, istop = 7;  show = true;  done = true; end
end
                                                
%  Test for an indefinite preconditioner.
%  If b = 0 exactly, stop with x = 0.

if beta1 <  0, istop = 8;  show = true;  done = true;  end
if beta1 == 0,             show = true;  done = true;  end

%  Here and later, v is really P * (the Lanczos v).

if beta1 > 0
  beta1  = sqrt( beta1 );
  s      = 1 / beta1;
  v      = s * y;

  %  See if aprod is symmetric.

  y = feval( aprodname, n, v, iw, rw );
  if check
     r2   = feval( aprodname, n, y, iw, rw );
     s    = y' * y;
     t    = v' * r2;
     z    = abs( s - t );
     epsa = (s + eps) * eps^(1/3);
     if z > epsa, istop = 6;  done  = true;  show = true;  end
  end

  %  Set up y for the second Lanczos vector.
  %  Again, y is beta * P * v2  where  P = C^(-1).
  %  y and beta will be zero or very small if Abar = I or constant * I.

  y    = (- shift) * v + y;
  alfa = v' * y;
  y    = (- alfa / beta1) * r1 + y;

  %  Make sure  r2  will be orthogonal to the first  v.

  z  = v' * y;
  s  = v' * v;
  y  = (- z / s) * v + y;
  r2 = y;

  if precon,  y = feval( msolvename, n, r2, iw, rw );  end
  oldb   = beta1;
  beta   = r2' * y;
  if beta < 0, istop = 8; show = true;  done = true; end

  %  Cause termination (later) if beta is essentially zero.

  beta  = sqrt( beta );
  if beta <= eps, istop = -1; end

  %  See if the local reorthogonalization achieved anything.

  denom = sqrt( s ) * norm( r2 )  +  eps;
  s     = z / denom;
  t     = v' * r2;
  t     = t / denom;

  if show
     disp( space );
     disp( sprintf( 'beta1 =  %10.2e   alpha1 =  %9.2e', beta1, alfa ) );
     disp( sprintf( '(v1, v2) before and after  %14.2e', s ) );
     disp( sprintf( 'local reorthogonalization  %14.2e\n', t ) );
  end

  %  Initialize other quantities.

  cgnorm = beta1;     rhs2   = 0;       tnorm  = alfa^2 + beta^2;
  gbar   = alfa;      bstep  = 0;       ynorm2 = 0;
  dbar   = beta;      snprod = 1;       gmax   = abs( alfa ) + eps;
  rhs1   = beta1;     x1cg   = 0;       gmin   = gmax;
  qrnorm = beta1;

end  % of beta1 > 0

if show
  head1 = '   Itn     x(1)(cg)  normr(cg)  r(minres)';
  head2 = '    bstep    anorm    acond';
  disp( [head1 head2] )

  str1 = sprintf ( '%6g %12.5e %10.3e', itn, x1cg, cgnorm );
  str2 = sprintf ( ' %10.3e  %8.1e',    qrnorm, bstep/beta1 );
  disp( [str1 str2] )
end

%  ------------------------------------------------------------------
%  Main iteration loop.
%  ------------------------------------------------------------------
%  Estimate various norms and test for convergence.

if ~done
   while itn < itnlim
      itn    = itn  +  1;
      anorm  = sqrt( tnorm  );
      ynorm  = sqrt( ynorm2 );
      epsa   = anorm * eps;
      epsx   = anorm * ynorm * eps;
      epsr   = anorm * ynorm * rtol;
      diag   = gbar;

      if diag == 0, diag = epsa; end

      lqnorm = norm( [rhs1; rhs2] );
      qrnorm = snprod * beta1;
      cgnorm = qrnorm * beta / abs( diag );

%     Estimate  Cond(A).
%     In this version we look at the diagonals of  L  in the
%     factorization of the tridiagonal matrix,  T = L*Q.
%     Sometimes, T(k) can be misleadingly ill-conditioned when
%     T(k+1) is not, so we must be careful not to overestimate acond.

      if lqnorm < cgnorm
         acond  = gmax / gmin;
      else
         denom  = min( gmin, abs( diag ) );
         acond  = gmax / denom;
      end

      zbar   = rhs1 / diag;
      z      = (snprod * zbar + bstep) / beta1;
      x1lq   = x(1) + b1 * bstep / beta1;
      x1cg   = x(1) + w(1) * zbar  +  b1 * z;

%     See if any of the stopping criteria are satisfied.
%     In rare cases, istop is already -1 from above (Abar = const * I).

      if istop == 0
         if itn    >= itnlim , istop = 5; end
         if acond  >= 0.1/eps, istop = 4; end
         if epsx   >= beta1  , istop = 3; end
         if cgnorm <= epsx   , istop = 2; end
         if cgnorm <= epsr   , istop = 1; end
      end
%     ==================================================================
%     See if it is time to print something.

      prnt = 0;
      if n      <= 40         ,   prnt = 1; end
      if itn    <= 10         ,   prnt = 1; end
      if itn    >= itnlim - 10,   prnt = 1; end
      if rem(itn,10) == 0     ,   prnt = 1; end
      if cgnorm <= 10.0*epsx  ,   prnt = 1; end
      if cgnorm <= 10.0*epsr  ,   prnt = 1; end
      if acond  >= 0.01/eps   ,   prnt = 1; end
      if istop  ~= 0          ,   prnt = 1; end
%     if ~show                ,   prnt = 0; end

      if show & prnt == 1
         str1 = sprintf ( '%6g %12.5e %10.3e', itn, x1cg, cgnorm );
         str2 = sprintf ( ' %10.3e  %8.1e',    qrnorm, bstep/beta1 );
         str3 = sprintf ( ' %8.1e %8.1e',      anorm, acond );
         disp( [str1 str2 str3] )
      end
      if istop > 0, break; end

%     Obtain the current Lanczos vector  v = (1 / beta)*y
%     and set up  y  for the next iteration.

      if istop ~= 0, break; end

      s      = 1/beta;
      v      = s * y;
      y      = feval( aprodname, n, v, iw, rw );
      y      = (- shift) * v + y;
      y      = (- beta / oldb) * r1 + y;
      alfa   = v' * y;
      y      = (- alfa / beta) * r2 + y;
      r1     = r2;
      r2     = y;
      if precon,  y = feval( msolvename, n, r2, iw, rw );  end
      oldb   = beta;
      beta   = r2' * y;

      if beta < 0, istop = 6;  break;  end
      beta   = sqrt( beta );
      tnorm  = tnorm  +  alfa^2  +  oldb^2  +  beta^2;

%     Compute the next plane rotation for Q.

      gamma  = sqrt( gbar^2 + oldb^2 );
      cs     = gbar / gamma;
      sn     = oldb / gamma;
      delta  = cs * dbar  +  sn * alfa;
      gbar   = sn * dbar  -  cs * alfa;
      epsln  = sn * beta;
      dbar   =            -  cs * beta;

%     Update  X.

      z      = rhs1 / gamma;
      s      = z*cs;
      t      = z*sn;
      x      = s*w  +  t*v  +  x;
      w      = sn*w - cs*v;

%     Accumulate the step along the direction  b, and go round again.

      bstep  = snprod * cs * z  +  bstep;
      snprod = snprod * sn;
      gmax   = max( gmax, gamma );
      gmin   = min( gmin, gamma );
      ynorm2 = z^2  +  ynorm2;
      rhs1   = rhs2  -  delta * z;
      rhs2   =       -  epsln * z;
   end % while
%  ------------------------------------------------------------------
%  End of main iteration loop.
%  ------------------------------------------------------------------

%  Move to the CG point if it seems better.
%  In this version of SYMMLQ, the convergence tests involve
%  only cgnorm, so we're unlikely to stop at an LQ point,
%  EXCEPT if the iteration limit interferes.

   if cgnorm < lqnorm
      zbar   = rhs1 / diag;
      bstep  = snprod * zbar  +  bstep;
      ynorm  = sqrt( ynorm2  +  zbar^2 );
      x      = zbar * w + x;
   end

%  Add the step along  b.

   bstep  = bstep / beta1;
   y      = b;
   if precon,  y =  feval( msolvename, n, b, iw, rw );  end
   x      = bstep * y + x;

%  Compute the final residual,  r1 = b - (A - shift*I)*x.
            
   y      = feval( aprodname, n, x, iw, rw );
   y      = (- shift) * x + y;
   r1     = b - y;
   rnorm  = norm ( r1 );
   xnorm  = norm (  x );
end % if

%  ==================================================================
%  Display final status.
%  ==================================================================
if show
   disp( space )
   disp( [ last sprintf( ' istop   =  %3g               itn   =  %5g',...
                           istop,                      itn  ) ] )
   disp( [ last sprintf( ' anorm   =  %12.4e      acond =  %12.4e' ,...
                           anorm,                 acond  ) ] )
   disp( [ last sprintf( ' rnorm   =  %12.4e      xnorm =  %12.4e' ,...
                           rnorm,                 xnorm  ) ] )
   disp( [ last msg(istop+2,:) ])
end
% end SYMMLQ
