/*******************************************************************************
 *
 *  Solving a linear system with symmlq
 *  Classification: ???
 *  Matrix:         symmetric and positve-definite
 *  Workspace:      6*dim
 *
 *******************************************************************************
 *
 * Based on Fortran-Code: Michael A. Saunders
 *                         Department of Operations Research
 *                         Stanford University
 *                         15 Sept 1985.
 *
 * The long list of contributors and kind of a change log is attached
 * at the end. The initial C-port was done by using f2c.
 *
 *******************************************************************************
 *
 * The only new feature is that the applicastion provided start-vector
 * X is used to form the initial vector in the Lanczos sequence (if
 * suitable), and that X and the rhs B both may or may not incorporate
 * inhomogeneous Dirichlet boundary conditions.
 *
 *******************************************************************************
 *
 * Port to ALBERTA's OEM-solvers by
 *
 *  authors:   Notger Noll
 *             Abteilung fuer angewandte Mathematik
 *             Albert Ludwigs Universitaet Freiburg
 *             Herman Herder Str. 10
 *             D-79106 Freiburg, Germany
 *
 *             Claus-Justus Heine
 *             Abteilung fuer Angewandte Mathematik
 *             Albert-Ludwigs-Universitaet Freiburg
 *             Hermann-Herder-Str. 10
 *             79104 Freiburg
 *             Germany
 *             Claus.Heine@Mathematik.Uni-Freiburg.DE
 *
 *  www.mathematik.uni-freiburg.de/IAM/ALBERTA
 *
 *  (c) by N. Noll (2008),
 *         C.-J. Heine (2008).
 *
 ******************************************************************************/

/******************************************************************************
 * SYMMLQ is designed to solve the system of linear equations
 *
 * A*x = b
 *
 * where A is an n*n symmetric matrix and b is a given vector.
 * The matrix A is not required to be positive definite.
 * (If A is known to be definite, the method of conjugate gradients
 * may be used -- it will require about the same number of iterations
 * as SYMMLQ but slightly less work per iteration.)
 *
 *
 * The matrix A is intended to be large and sparse. It is accessed
 * by means of a subroutine call of the form
 *
 * mv( dim,x,y )
 *
 * which must return the product y = A*x for any given vector x.
 *
 *
 * More generally, SYMMLQ is designed to solve the system
 *
 * (A - shift*I)*x = b
 *
 * where shift is a specified scalar value. If shift and b
 * are suitably chosen, the computed vector x may approximate an
 * (unnormalized) eigenvector of A, as in the methods of
 * inverse iteration and/or Rayleigh-quotient iteration.
 * Again, the matrix (A - shift*I) need not be positive definite.
 * The work per iteration is very slightly less if shift = 0.
 *
 *
 * A further option is that of preconditioning, which may reduce
 * the number of iterations required. If M is a positive
 * definite matrix that is known to approximate (A - shift*I)
 * in some sense, and if systems of the form M*y = x can be
 * solved efficiently, the parameters PRECON and MSOLVE may be
 * used (see below). When PRECON = .true. , SYMMLQ will
 * implicitly solve the system of equations
 *
 * P*( A - shift*I )*P*y = P*b, where P = M**(-1/2),
 *
 * and return the solution x = P*y.
 *
 *
 * Parameters
 * ----------
 *
 * dim             input          The dimension of the matrix A.
 *
 * b(dim)          input          The rhs vector b.
 *
 * r1(dim)         output         Returns the final residual vector,
 *                                res = b - (A - shift*I)*x.
 *
 * r2(dim)         workspace
 * v(dim)          workspace
 * w(dim)          workspace
 *
 * x(dim)          output         Returns the computed solution x.
 *
 * y(dim)          workspace
 *
 * mv              external       A subroutine defining the matrix A.
 *                                For a given vector x, the statement
 *
 *                                mv(dim, x, y)
 *
 *                                must return the product y = A*x
 *                                without altering the vector x.
 *
 * precon          external       An optional subroutine defining a
 *                                preconditioning matrix M, which should
 *                                approximate (A - shift*I) in some sense ---
 *                                typically having many similar eigenvalues.
 *                                M must be positive definite.
 *                                For a given vector x, the statement
 *
 *                                precon(dim, x, y)
 *
 *                                must solve the linear system M*y = x
 *                                without altering the vector x.
 *
 * PRECON input If PRECON = .true. , preconditioning will
 * be invoked. Otherwise, subroutine precon
 * will not be referenced; in this case the
 * actual parameter corresponding to precon may
 * be the same as that corresponding to mv.
 *
 * SHIFT           input          Should be zero if the system A*x = b is to
 *                                be solved. Otherwise, it could be an
 *                                approximation to an eigenvalue of A, such as
 *                                the Rayleigh quotient b(t)*A*b/(b(t)*b)
 *                                corresponding to the vector b.
 *                                If b is sufficiently like an eigenvector
 *                                corresponding to an eigenvalue near SHIFT,
 *                                then the computed x may have very large
 *                                components. When normalized, x may be
 *                                closer to an eigenvector than b.
 *
 * NOUT            input          The output file number. If positive,
 *                                a summary will be printed on unit NOUT.
 *
 * MAX_ITER        input          An upper limit on the number of iterations.
 *
 * EPS             input          The relative machine precision.
 *                                REAL_EPSILON
 *
 * RTOL            input          A user-specified tolerance. SYMMLQ terminates
 *                                if it appears that norm(Rbar) is smaller than
 *                                RTOL * norm(Abar) * norm(y), where
 * Abar                           is the transformed matrix operator
 *                                Abar = P * (A - shift*I) * P
 * Rbar                           is the transformed residual vector
 *                                Rbar = P * ( b - (A - shift*I)*x ).
 *
 *                                If shift = 0 and PRECON = .false., SYMMLQ
 *                                terminates if norm(b - A*x) is smaller than
 *                                RTOL * norm(A) * norm(x).
 *
 * ISTOP           output         An int giving the reason for termination...
 *
 *                                1    b = 0, so the exact solution is x = 0.
 *                                     No iterations were performed.
 *
 *                                2    Norm(Rbar) appears to be less than
 *                                     the value RTOL * norm(Abar) * norm(y).
 *                                     The solution in X should be acceptable.
 *
 *                                3    Norm(Rbar) appears to be less than
 *                                     the value EPS * norm(Abar) * norm(y).
 *                                     This means that the residual is as small
 *                                     as seems reasonable on this machine.
 *
 *                                4    Norm(Abar)*norm(y) exceeds norm(b)/EPS,
 *                                     which should indicate that x has
 *                                     essentially converged to an eigenvector
 *                                     of A corresponding to the eigenvalue
 *                                     shift.
 *
 *                                5    ACOND (see below) has exceeded 0.1/EPS,
 *                                     so the matrix Abar must be very
 *                                     ill-conditioned.X may not contain an
 *                                     acceptable solution.
 *
 *                                6    The iteration limit was reached before
 *                                     any of the previous criteria were
 *                                     satisfied.
 *
 *                                7    An inner product of the form
 *                                     x(t)*M**(-1)*x was not positive,
 *                                     so the preconditioning matrix M does not
 *                                     appear to be positive definite. X will
 *                                     not contain an acceptable solution.
 *
 * ITER            output         The number of iterations performed.
 *
 * ANORM           output         An estimate of the norm of the matrix operator
 *                                Abar = P*(A - shift*I)*P, where P = M**(-1/2).
 *
 * ACOND           output         An estimate of the condition of Abar above.
 *                                This will usually be a substantial
 *                                under-estimate of the true condition.
 *
 * RNORM           output         The norm of the final residual vector,
 *                                b - (A - shift*I)*x.
 *
 * XNORM           output         The norm of the final solution vector x.
 *
 *
 *
 * Subroutines and functions
 *
 * USER       mv, precon
 * BLAS       daxpy, dcopy, dscal, ddot , dnrm2
 *
 * ------------------------------------------------------------------
 * This routine is an implementation of the algorithm described in
 * the following references:
 *
 * C.C. Paige and M.A. Saunders, Solution of Sparse Indefinite
 * Systems of Linear Equations,
 * SIAM J. Numer. Anal. 12, 4, September 1975, pp. 617-629.
 *
 * J.G. Lewis, Algorithms for Sparse Matrix Eigenvalue Problems,
 * Report STAN-CS-77-595, Computer Science Department,
 * Stanford University, Stanford, California, March 1977.
 *
 * Applications of SYMMLQ and the theory of preconditioning
 * are described in the following references:
 *
 * D.B. Szyld and O.B. Widlund, Applications of Conjugate Gradient
 * Type Methods to Eigenvalue Calculations,
 * in R. Vichnevetsky and R.S. Steplman (editors),
 * Advances in Computer Methods for Partial Differential
 * Equations -- III, IMACS, 1979, 167-173.
 *
 * D.B. Szyld, A Two-level Iterative Method for Large Sparse
 * Generalized Eigenvalue Calculations,
 * Ph. D. dissertation, Department of Mathematics,
 * New York University, New York, October 1983.
 *
 *****************************************************************************/

#include "alberta_util.h"
#include "alberta_util_intern.h"

#define WORK_MULTIPLE 6

int oem_symmlq(OEM_DATA *oem, int dim, const REAL *b, REAL *x)
{

  FUNCNAME("oem_symmlq");
  int iter = 0, istop = 0;
  REAL *r1, *r2, *v, *w, *y, *b0, old_res = -1.0;
  const REAL shift = 0.0;
  int  max_iter = oem->max_iter;
  int   (*mv)(void *, int, const REAL *, REAL *) = oem->mat_vec;
  void  *mvd = oem->mat_vec_data;
  void  (*precon)(void *, int, REAL *) = oem->left_precon;
  void  *pd = oem->left_precon_data;
  REAL  (*scp)(void *, int, const REAL *, const REAL *) =
    oem->scp ? oem->scp : std_scp;
  void  *sd = oem->scp_data;
  WORKSPACE  *ws = CHECK_WORKSPACE(WORK_MULTIPLE * dim, oem->ws);

  const REAL eps  = REAL_EPSILON;
  const REAL rtol = oem->tolerance;

  /* Local variables */
  REAL s, z, cs, sn, rhs1, rhs2, diag = 0.0, dbar, gbar, zbar, oldb;
  REAL gmin, gmax, epsr, epsx;
  REAL alpha, beta, beta1, gamma, delta, epsa, epsln;

  REAL denom, bstep, snprod;
  REAL anorm = 0.0, qrnorm, /*rnorm,*/ tnorm;
  REAL cgnorm;

  r1 = (REAL *)ws->work;
  r2 = r1 + dim;
  v  = r2 + dim;
  w  = v + dim;
  y  = w + dim;
  b0 = y + dim; /* Modified rhs. */

  oem->terminate_reason = 0;

  REAL bnorm = scp(sd, dim, b, b);
  REAL xnorm = scp(sd, dim, x, x);

  if (bnorm < SQR(eps) && xnorm < SQR(eps)) {
	oem->terminate_reason = 1;
    INFO(oem->info,2,
	 "b == x_0 == 0, x = 0 is the solution of the linear system\n");
    fflush(stdout);
    dset(dim, 0.0, x, 1);
    oem->initial_residual = oem->residual = 0.0;
    return 0;
  }

#if 0
  /* The code below completly discards any application provided
   * start-vector, which is not a good idea in the context of
   * time-dependent and/or adaptive methods. Instead, we check whether
   * X has a sufficiently large norm and use it as first Lanczos
   * vector. At the same time we take care of any Dirichlet boundary
   * conditions contained in X.
   */
  for (i = 0; i < dim; ++i) {
    x[i] = 0.0;
    w[i] = 0.0;
  }

  /* Set up V, the first vector in the Lanczos sequence. */

  dcopy(dim, b, 1, y, 1);
  dcopy(dim, b, 1, r1, 1);
  if (precon) {
    precon(pd, dim, y);
  }
  beta1 = ddot(dim, r1, 1, y, 1);

  b1 = y[0];
  if (beta1 <= 0.0) {
    istop = 7;
    BREAK_INFO(oem,"beta1 <= 0.0 : PRECON does not define a positive "
	       "definite preconditioner.", iter, beta1, &old_res, ws);
    return iter;
  }
  beta1 = sqrt(beta1);
  s = 1.0 / beta1;
  dcopy(dim, y, 1, v, 1);
  dscal(dim, s, v, 1);
#else
  /* First make sure to add any implicitly coded boundary valus to the RHS.
   * Afterwards we have two scenarios which will work:
   *
   * 1) mv() operates as identity on Dirichlet nodes _and_ _both_
   * vectors, x _and_ b, contain the interpolated boundary valus
   * (using this scheme is, in principle, a design bug).
   *
   * 2) mv() operates trivially on the Dirichlet nodes as it
   * should. Then x may contain Dirichlet boundary data but b _must_
   * _not_. This is the scheme which is closer to the mathematical
   * background and should be preferred.
   *
   * In both cases, the updates which are later added to X to finally
   * form the solution will not touch the Dirichlet-data stored in X,
   * and mv() will no longer "see" the Dirichlet data after this
   * initial setup and operate only on the non-Dirichlet DOFs, as it
   * should.
   */
  dset(dim, 0.0, w, 1);

  /* Compute a modified RHS. */
  mv(mvd, dim, x, b0);
  dxpay(dim, b, 1, -1.0, b0, 1);

  /* Then use this modfied RHS to solve the system. */
  dcopy(dim, b0, 1, y, 1);
  dcopy(dim, y, 1, r1, 1);
  if (precon) {
    precon(pd, dim, y);
  }
  beta1 = scp(sd, dim, r1, y);

  START_INFO(oem);
  /* Really use <, != 0.0, otherwise we kill our initial residual check. */
  if (beta1 < 0.0) {
	oem->terminate_reason = 7;
    istop = 7;
    BREAK_INFO(oem,"beta1 < 0.0 : PRECON does not define a positive "
	       "definite preconditioner.", iter, beta1, &old_res, ws);
    return iter;
  }

  /* This is the point where we have to check for the initial
   * residual. Afterwards we know that y can be used as first Lanczos
   * vector.
   */
  beta1 = sqrt(beta1);
  if (SOLVE_INFO(oem, 0, oem->initial_residual = beta1, &old_res, ws)) {
    return 0;
  }

  /* Scale y by 1/beta1 and use that as initial Lanczos vector. At
   * this point all Dirichlet DOFs in v are already 0.
   */
  s = 1.0 / beta1;
  dcopy(dim, y, 1, v, 1);
  dscal(dim, s, v, 1);
#endif

  /* Set up Y for the second Lanczos vector. */
  mv(mvd, dim, v, y);
  daxpy(dim, -shift, v, 1, y, 1);
  alpha = scp(sd, dim, v, y);
  daxpy(dim, -alpha / beta1, r1, 1, y, 1);

  /* Make sure R2 will be orthogonal to the first V. */
  z = scp(sd, dim, v, y);
  s = scp(sd, dim, v, v);
  daxpy(dim, -z / s, v, 1, y, 1);
  dcopy(dim, y, 1, r2, 1);
  if (precon) {
    precon(pd, dim, y);
  }
  beta = scp(sd, dim, r2, y);
  oldb = beta1;
  if (beta <= 0.0) {
	oem->terminate_reason = 7;
    istop = 7;
    BREAK_INFO(oem,"beta <= 0.0 : PRECON does not define a positive "
	       "definite preconditioner.", iter, beta, &old_res, ws);
    return iter;
  }
  beta = sqrt(beta);

  /* See if the local reorthogonalization achieved anything. */

  denom = sqrt(s) * dnrm2(dim, r2, 1);
  s = z / denom;

  /* Initialize other quantities. */
  cgnorm = beta1;
  gbar = alpha;
  dbar = beta;
  rhs1 = beta1;
  rhs2 = 0.0;
  bstep = 0.0;
  snprod = 1.0;
  tnorm = alpha * alpha;
  gmax = 0.0;
  gmin = 1.0 / eps;

/*****************************************************************************/
/*                           Main iteration loop.                            */
/*****************************************************************************/

  for (iter = 1; iter <= max_iter; iter++) {
    /* Estimate various norms and test for convergence. */

    /*anorm = sqrt(tnorm);
      ynorm = sqrt(ynorm2);*/

    epsa = anorm * eps;
    epsx = /*anorm * ynorm */ eps;
    epsr = /*anorm * ynorm */ rtol;
    diag = gbar;
    if (diag == 0.0) {
      diag = epsa;
    }

    /*elqnrm = sqrt(rhs1 * rhs1 + rhs2 * rhs2);*/
    qrnorm = snprod * beta1;
    cgnorm = qrnorm * beta / fabs(diag);

    /* Estimate COND(A). */
    /* In this version we look at the diagonals of L in the */
    /* factorization of the tridiagonal matrix, T = L*Q. */

    denom = MIN(gmin, fabs(diag));
    REAL acond = gmax / denom;


    /*--------------------------------------------------------------------*/
    /*         See if any of the stopping criteria are satisfied.         */
    /*--------------------------------------------------------------------*/
    if (iter == 0) {
      oem->initial_residual = cgnorm;
    }


    if (acond >= (REAL).1 / eps) {
      istop = 5;
      oem->terminate_reason = 5;
    }
    if (epsx >= beta1) {
      istop = 4;
      oem->terminate_reason = 4;
    }
    if (cgnorm <= epsx) {
      istop = 3;
      oem->terminate_reason = 3;
    }
    if (cgnorm <= epsr) {
      oem->terminate_reason = 2;
      istop = 2;
    }

    static int  step[11] = {0, 1000, 500, 200, 100, 50, 20, 10, 5, 2, 1};

    if ((oem->info && (iter%step[oem->info] == 0)) && istop == 0
	&& iter != max_iter) {
      if (old_res) {
	if (old_res > 0.0) {
	  REAL  red = cgnorm/(old_res);
	  INFO(oem->info,2,"%5d | %12.5le | %8.2le\n",
	       iter, cgnorm, red);
	} else {
	  INFO(oem->info,2,"%5d | %12.5le | --------\n",
	       iter, cgnorm);
	}
	old_res = cgnorm;

      } else {
	INFO(oem->info,2,"%5d | %12.5le |\n", iter, cgnorm);
      }
      fflush(stdout);
    }
    oem->residual = cgnorm;

    if (istop != 0) {
      break;
    }
    /*--------------------------------------------------------------------*/

    /* Obtain the current Lanczos vector V = (1/BETA)*Y */
    /* and set up Y for the next iteration. */
    s = 1.0 / beta;

    dcopy(dim, y, 1, v, 1);
    dscal(dim, s, v, 1);

    mv(mvd, dim, v, y);
    daxpy(dim, -shift, v, 1, y, 1);
    daxpy(dim, -beta / oldb, r1, 1, y, 1);
    alpha = scp(sd, dim, v, y);
    tnorm = tnorm + SQR(alpha) + SQR(beta) * 2.0;
    daxpy(dim, -alpha / beta, r2, 1, y, 1);
    dcopy(dim, r2, 1, r1, 1);
    dcopy(dim, y, 1, r2, 1);
    oldb = beta;
    if (precon) {
      precon(pd, dim, y);
    }
    beta = scp(sd, dim, r2, y);

    if (beta <= 0.0) {
      oem->terminate_reason = 7;
      istop = 7;
      break;
    }
    beta = sqrt(beta);

    /* Compute the next plane rotation for Q. */
    gamma = sqrt(SQR(gbar) + SQR(oldb));
    cs = gbar / gamma;
    sn = oldb / gamma;
    delta = cs * dbar + sn * alpha;
    gbar = sn * dbar - cs * alpha;
    epsln = sn * beta;
    dbar = -cs * beta;

    /* Update X. */

    z = rhs1 / gamma;
    s = z * cs;
    REAL t = z * sn;

    daxpy(dim, s, w, 1, x, 1);
    daxpy(dim, t, v, 1, x, 1);
    dscal(dim, sn, w, 1);
    daxpy(dim, -cs, v, 1, w, 1);

    /* Accumulate the step along the direction B, */
    /* and go round again. */

    bstep = snprod * cs * z + bstep;
    snprod *= sn;
    gmax = MAX(gmax,gamma);
    gmin = MIN(gmin,gamma);
    /*ynorm2 = SQR(z) + ynorm2;*/
    rhs1 = rhs2 - delta * z;
    rhs2 = -epsln * z;
  }

/*****************************************************************************/
/*                        End of main iteration loop.                        */
/*****************************************************************************/

  if (iter >= max_iter) {
	oem->terminate_reason = 6;
    iter = max_iter;
    istop = 6;
  }

  /* Move to the CG point. */
  /* (In this version of SYMMLQ, we never stop at an LQ point.) */
  zbar = rhs1 / diag;
  bstep = snprod * zbar + bstep;
  /*ynorm = sqrt(ynorm2 + SQR(zbar));*/
  daxpy(dim, zbar, w, 1, x, 1);

  /* Add the step along B. */
  bstep /= beta1;
  if (precon) {
    precon(pd, dim, b0);
  }
  daxpy(dim, bstep, b0, 1, x, 1);

  /* Compute the final residual, R1 = B - (A - SHIFT*I)*X. */
  mv(mvd, dim, x, y);
  daxpy(dim, -shift, x, 1, y, 1);

  dcopy(dim, b, 1, r1, 1);
  daxpy(dim, -1.0, y, 1, r1, 1);

  /*rnorm = dnrm2(dim, r1, 1);*/
  xnorm = dnrm2(dim, x, 1);

  /*break - infos*/
  if (istop == 7) {
    BREAK_INFO(oem, "beta <= 0.0.     PRECON does not define a positive "
	       "definite preconditioner.", iter, cgnorm, &old_res, ws);
    return iter;
  }
  if (istop == 6) {
    BREAK_INFO(oem, "The algorithm did not converge.",
	       iter, cgnorm, &old_res, ws);
    return iter;
  }
  if (istop == 5) {
    BREAK_INFO(oem, "acond >= 0.1 / eps.     ACOND has exceeded 0.1/EPS.",
	       iter, cgnorm, &old_res, ws);
    return iter;
  }
  if (istop == 4) {
    BREAK_INFO(oem, "epsx >= beta1.     X has converged to an eigenvector.",
	       iter, cgnorm, &old_res, ws);
    return iter;
  }
  if (istop == 3) {
    SOLVE_INFO(oem, iter, cgnorm, &old_res, ws);
    return iter;
  }
  if (istop == 2) {
    SOLVE_INFO(oem, iter, cgnorm, &old_res, ws);
    return iter;
  }

  return 0;    /*  statement never reached!!!                            */
} /* symmlq_ */


const char *symmlq_strerror(int reason)
{
  switch (reason) {
  case 1:
    return "b == x_0 == 0, x = 0 is the solution of the linear system";

  case 2:
    return "(h,r^*)_2 = 0";

  case 3:
    return "(Ad,d^*)_2 = 0";

  case 4:
    return "epsx >= beta1.     X has converged to an eigenvector.";

  case 5:
    return "acond >= 0.1 / eps.     ACOND has exceeded 0.1/EPS.";

  case 6:
    return "The algorithm did not converge.";

  case 7:
    return "beta <= 0.0.     PRECON does not define a positive definite preconditioner.";

  default:
    break;
  }
  return "Unknown error code.";
}


/******************************************************************************
 *
 *     SYMMLQ development:
 *            1972: First version.
 *            1975: John Lewis recommended modifications to help with
 *                  inverse iteration:
 *                  1. Reorthogonalize v1 and v2.
 *                  2. Regard the solution as x = x1  +  bstep * b,
 *                     with x1 and bstep accumulated separately
 *                     and bstep * b added at the end.
 *                     (In inverse iteration, b might be close to the
 *                     required x already, so x1 may be a lot smaller
 *                     than the multiple of b.)
 *            1978: Daniel Szyld and Olof Widlund implemented the first
 *
 *                  form of preconditioning.
 *                  This required both a solve and a multiply with M.
 *            1979: Implemented present method for preconditioning.
 *                  This requires only a solve with M.
 *            1984: Sven Hammarling noted corrections to tnorm.
 *
 *                  SYMMLQ added to NAG Fortran Library.
 *     15 Sep 1985: Final F66 version.  SYMMLQ sent to "misc" in netlib.
 *
 *     16 Feb 1989: First F77 version.
 *
 *     22 Feb 1989: Hans Mittelmann observed beta2 = 0 (hence failure)
 *                  if Abar = const*I.  istop = -1 added for this case.
 *
 *
 *     01 Mar 1989: Hans Mittelmann observed premature termination on
 *                  ( 1  1  1 )     (   )                   ( 1  1    )
 *
 *                  ( 1  1    ) x = ( 1 ),  for which  T3 = ( 1  1  1 ).
 *
 *                  ( 1     1 )     (   )                   (    1  1 )
 *
 *                  T2 is exactly singular, so estimating cond(A) from
 *                  the diagonals of Lbar is unsafe.  We now use
 *                  L       or  Lbar         depending on whether
 *                  lqnorm  or  cgnorm       is least.
 *
 *     03 Mar 1989: eps computed internally instead of coming in as a
 *                  parameter.
 *     07 Jun 1989: ncheck added as a parameter to say if A and M
 *                  should be checked for symmetry.
 *                  Later changed to checka (see below).
 *     20 Nov 1990: goodb added as a parameter to make Lewis's changes
 *                  an option.  Usually b is NOT much like x.  Setting
 *                  goodb = .false. saves a call to msolve at the end.
 *     20 Nov 1990: Residual not computed exactly at end, to save time
 *                  when only one or two iterations are required
 *                  (e.g. if the preconditioner is very good).
 *                  Beware, if precon is true, rnorm estimates the
 *                  residual of the preconditioned system, not Ax = b.
 *     04 Sep 1991: Parameter list changed and reordered.
 *                  integer ncheck is now llong intchecka.
 *     22 Jul 1992: Example from Lothar Reichel and Daniela Calvetti
 *                  showed that beta2 = 0 (istop = -1) means that
 *                  b is an eigenvector when M = I.
 *                  More complicated if there is a preconditioner;
 *                  not clear yet how to describe it.
 *     14 Dec 1992: Modified by Robert Leland, Sandia National Laboratories
 *                          to integrate with a C application code. The matrix
 *                  data is now passed by reference through symmlq to
 *                  mv and msolve. These are now just Fortran wrappers
 *
 *                  for C codes consistent with the matrix data passed
 *                  via the pointers "A", "vwsqrt", "work" and "orthlist"
 *
 *     10 Feb 1993: Modified by Robert Leland to return calculate machine
 *                  precision and terminate if the norm of the iterate gets
 *                  above the limit normxlim. Relevant for inverse iteration.
 *                  Also incorporated min_iter to enforce minimum number itns.
 *     17 Aug 1993: Observed that the Fortran i/o in this routine won't
 *                  work because there is no main fortran program to open
 *                  the standard i/o files. So for this (and other reasons)
 *                  converted the Fortran to C, necessitating inclusion of
 *                  the file f2c.h. To avoid a problem with maintaining
 *
 *****************************************************************************/
