https://github.com/ma-tech/Woolz
Raw File
Tip revision: 5ab012fff0fb50186d6ea8508f0d8b3063c45dc3 authored by Bill Hill on 15 August 2022, 13:30:41 UTC
README now Readme.md.
Tip revision: 5ab012f
AlgMatrixLSQR.c
#if defined(__GNUC__)
#ident "University of Edinburgh $Id$"
#else
static char _AlgMatrixLSQR_c[] = "University of Edinburgh $Id$";
#endif
/*!
* \file         libAlg/AlgMatrixLSQR.c
* \author       Bill Hill
* \date         May 2010
* \version      $Id$
* \par
* Address:
*               MRC Human Genetics Unit,
*               MRC Institute of Genetics and Molecular Medicine,
*               University of Edinburgh,
*               Western General Hospital,
*               Edinburgh, EH4 2XU, UK.
* \par
* Copyright (C), [2012],
* The University Court of the University of Edinburgh,
* Old College, Edinburgh, UK.
* 
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be
* useful but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE.  See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA  02110-1301, USA.
* \brief	Provides functions for solving matrix equations using LSQR.
* 		This software is based on lsqr.c, a C version of LSQR derived
* 		by James W. Howse <jhowse@lanl.gov> from the Fortran 77
* 		implementation of C. C. Paige and M. A. Saunders.
*		In most cases the extensive comments from Howse's lsqr.c
*		have been preserved with little change.
* \ingroup	AlgMatrix
*/

#include <Alg.h>
#include <float.h>


static double 			AlgMatrixLSQRNorm2(
				  double a,
				  double b);
static double 			AlgMatrixLSQRNorm4(
				  double a,
				  double b,
				  double c,
				  double d);
/*!
* \return
* \ingroup	AlgMatrix
* \brief	This function finds a solution x to the following problems:
*		\verbatim
      1. Unsymmetric equations --    solve  A*x = b
 
      2. Linear least squares  --    solve  A*x = b
                                     in the least-squares sense
 
      3. Damped least squares  --    solve  (   A    )*x = ( b )
                                            ( damp*I )     ( 0 )
                                     in the least-squares sense
 
      where 'A' is a matrix with 'm' rows and 'n' columns, 'b' is an
      'm'-vector, and 'damp' is a scalar.  (All quantities are real.)
      The matrix 'A' is intended to be large and sparse.  
 
      Notation
      --------
      The following quantities are used in discussing the subroutine
      parameters:
 
      'Abar'   =  (   A    ),          'bbar'  =  ( b )
                  ( damp*I )                      ( 0 )
 
      'r'      =  b  -  A*x,           'rbar'  =  bbar  -  Abar*x
 
      'rnorm'  =  sqrt( norm(r)**2  +  damp**2 * norm(x)**2 )
               =  norm( rbar )
 
      'rel_prec'  =  the relative precision of floating-point arithmetic
                     on the machine being used.  Typically 2.22e-16
                     with 64-bit arithmetic.
 
      LSQR  minimizes the function 'rnorm' with respect to 'x'.
 
      References
      ----------
      C.C. Paige and M.A. Saunders,  LSQR: An algorithm for sparse
           linear equations and sparse least squares,
           ACM Transactions on Mathematical Software 8, 1 (March 1982),
           pp. 43-71.
      C.C. Paige and M.A. Saunders,  Algorithm 583, LSQR: Sparse
           linear equations and least-squares problems,
           ACM Transactions on Mathematical Software 8, 2 (June 1982),
           pp. 195-209.
      C.L. Lawson, R.J. Hanson, D.R. Kincaid and F.T. Krogh,
           Basic linear algebra subprograms for Fortran usage,
           ACM Transactions on Mathematical Software 5, 3 (Sept 1979),
           pp. 308-323 and 324-325.
           	\endverbatim
*
* \param	aM			Matrix A with nR rows and nC columns.
* 					The values of A are not modified by
* 					this function.
* \param	bV			Vector b with nR entries which are
* 					modified by this function.
* \param	xV			Vector x for with initial guess at
* 					solution and for the return of the
* 					solution with nC entries.
* \param	damping			Damping parameter, set to 0.0 for no
* 					damping.
* \param	relErrA			An estimate of the relative error in
* 					matrix A. If A is accurate to about
* 					6 digits, set to 1.0e-06.
* \param	relErrB			An estimate of the relative error in
* 					vector b. Set as for relErrA.
* \param	maxItr			An upper limit on the number of
* 					iterations. If zero set to nC.
* \param	condLim			Upper limit on the condition number
* 					of matrix 'Abar'. Iterations will be
* 					terminated if a computed estimate of
* 					exceeds this condition number.
* \param	dstTerm			Destination pointer for termination
* 					code, which may be NULL. The values
* 					used are:
*   - 0       x = x0  is the exact solution. No iterations were performed.
*   - 1      The equations A*x = b are probably compatible. Norm(A*x - b)
*             is sufficiently small, given the values of relErrA and relErrB.
*   - 2       The system A*x = b is probably not compatible.  A least-squares
*             solution has been obtained that is sufficiently accurate, given
*             the value of relErrA.
*   - 3       An estimate of cond('Abar') has exceeded condLim.  The system
*             A*x = b appears to be ill-conditioned.
*   - 4       The equations A*x = b are probably compatible.  Norm(A*x - b)
*             is as small as seems reasonable on this machine.
*   - 5       The system A*x = b is probably not compatible.  A least-squares
*   	      solution has been obtained that is as accurate as seems
*   	      reasonable on this machine.
*   - 6       Cond('Abar') seems to be so large that there is no point in
*   	      doing further iterations, given the precision of this machine.
*   - 7       The iteration limit maxItr was reached.
* \param	dstItr			Destination pointer for the number of
* 				 	iterations performed, which may be
* 				 	NULL.
*
* \param	dstFNorm		Destination pointer for an estimate of
* 					the Frobenius norm of 'Abar', may be
* 					NULL.
* 					This is the square-root of the sum of
* 					squares of the elements of 'Abar'. If
* 					'damping' is small and if the columns
* 					of 'A' have all been scaled to have
* 					length 1.0, then the Frobenius norm
* 					should increase to roughly sqrt('nC').
* \param	dstCondN		Destination pointer for an estimate of
* 					the condition number of 'Abar', may be
* 					NULL.
* \param	dstResNorm		Destination pointer for an estimate of
* 					the final value of norm('rbar'), the
* 					function being minimized. This will be
* 					small if A*x = b has a solution. May be
* 					NULL.
* \param	dstResNormA		Destination pointer for an estimate of
* 					the final value of the residual for the
* 					usual normal equations. This should be
* 					small in all cases. May be NULL.
* \param	dstNormX		Destination pointer for an estimate of
* 					the final solution vector 'x'.
*/
AlgError 	AlgMatrixSolveLSQR(AlgMatrix aM,
				double *bV, double *xV,
				double damping, double relErrA, double relErrB,
				long maxItr, long condLim,
				int *dstTerm, long *dstItr, double *dstFNorm,
				double *dstCondN, double *dstResNorm,
				double *dstResNormA, double *dstNormX)
{
  long    	idx,
          	termItrMax,
  	  	itr = 0,
		term = 0,
          	termItr = 0;
  size_t	nR,
  		nC;
  double  	bnorm,
		condNTol,
  		cs,
		cs1,
		delta,
		gamma,
		gammabar,
		phi,
		psi,
		resNorm,
		resNormA,
		resTol,
		resTolMach,
		rho,
		rhobar1,
		sn,
		sn1,
		t0,
		t1,
		t2,
		t3,
		tau,
		temp,
		theta,
		zetabar,
  		alpha = 0.0, 
          	bbnorm = 0.0,
		beta = 0.0,
		condN = 0.0,
		cs2 = -1.0,
		ddnorm = 0.0,
		fnorm = 0.0,
		normX = 0.0,
		phibar = 0.0,
		res = 0.0,
		rhobar = 0.0,
		sn2 = 0.0,
		stopCrit1,
		stopCrit2,
		stopCrit3,
		xxnorm = 0.0,
		zeta = 0.0;
  double	*vV = NULL,
  		*wV = NULL;
  AlgError	errNum = ALG_ERR_NONE;

  nR = aM.core->nR;
  nC = aM.core->nC;
  if(maxItr <= 0)
  {
    maxItr = nC;
  }
  if(condLim > 0.0)
  {
    condNTol = 1.0 / condLim;
  }
  else
  {
    condNTol = DBL_EPSILON;
  }
#ifdef ALG_MATRIXLSQR_DEBUG
  (void )fprintf(stderr,
		 "AlgMatrixSolveLSQR()\n"
                 "  Least Squares Solution of A*x = b\n"
		 "  Matrix A is %7li rows x %7li columns\n"
		 "  Damping parameter = %10.2e\n"
		 "  ATOL = %10.2e\t\tCONDLIM = %10.2e\n"
		 "  BTOL = %10.2e\t\tITERLIM = %10li\n\n",
        	 nR, nC, damping, relErrA,
        	 condLim, relErrB, maxItr);
#endif
  /* Allocate workspace vectors. */
  if(((vV = (double *)AlcCalloc(nC, sizeof(double))) == NULL) ||
     ((wV = (double *)AlcCalloc(nC, sizeof(double))) == NULL))
  {
    errNum = ALG_ERR_MALLOC;
  }
  if(errNum == ALG_ERR_NONE)
  {
    /* Set up the initial vectors u and v for bidiagonalization.  These
     * satisfy  the relations
     * beta*u = b - A*x0 
     * alpha*v = A^T*u
     */
    /* Compute Euclidean length of u and store as beta */
    beta = AlgVectorNorm(bV, nR);
    if(beta > 0.0)
    {
      /* Scale vector u by the inverse of beta */
      AlgVectorScale(bV, bV, 1.0 / beta, nR);
      /* Compute matrix-vector product A^T*u and store it in vector v */
      AlgMatrixTVectorMulAdd(vV, aM, bV, vV);
      /* Compute Euclidean length of v and store as alpha */
      alpha = AlgVectorNorm(vV, nC);
    }
    if(alpha > 0.0)
    {
      /* Scale vector v by the inverse of alpha */
      AlgVectorScale(vV, vV, 1.0 / alpha, nC);
      /* Copy vector v to vector w */
      AlgVectorCopy(wV, vV, nC);
    }    
    resNormA = alpha * beta;
    resNorm = beta;
    bnorm = beta;
    /*
     *  If the norm || A^T r || is zero, then the initial guess is the exact
     *  solution.  Exit and report this.
     */
    if(resNormA == 0.0)
    {
      term = 0;
#ifdef ALG_MATRIXLSQR_DEBUG
      (void )fprintf(stderr,
		     "AlgMatrixSolveLSQR()\n"
		     "  ISTOP = %3li\t\t\tITER = %9li\n"
		     "  || A ||_F = %13.5e\tcond(A) = %13.5e\n"
		     "  || r ||_2 = %13.5e\t|| A^T r ||_2 = %13.5e\n"
		     "  || b ||_2 = %13.5e\t|| x - x0 ||_2 = %13.5e\n\n", 
		     term, itr, fnorm, 
		     condN, resNorm, resNormA,
		     bnorm, normX);
#endif
    }
    else
    {
      rhobar = alpha;
      phibar = beta;
#ifdef ALG_MATRIXLSQR_DEBUG
      stopCrit1 = 1.0;
      stopCrit2 = alpha / beta;
      (void )fprintf(stderr,
		     "AlgMatrixSolveLSQR()\n"
		     " Er ||  ITER     || r ||    Compatible  "
		     "||A^T r|| / ||A|| ||r||  || A ||    cond(A)\n"
		     "%6li %13.5e %10.2e \t%10.2e \t%10.2e  %10.2e\n",
		     itr, resNorm, stopCrit1, stopCrit2,
		     fnorm, condN);
#endif
    }
    /*
     *  The main iteration loop is continued as long as no stopping criteria
     *  are satisfied and the number of total iterations is less than some upper
     *  bound.
     */
    while(term == 0)
    {
      ++itr;
      /*      
       *     Perform the next step of the bidiagonalization to obtain
       *     the next vectors u and v, and the scalars alpha and beta.
       *     These satisfy the relations
       *                beta*u  =  A*v  -  alpha*u,
       *                alpha*v =  A^T*u  -  beta*v.
       */      
      /* Scale vector u by -alpha */
      AlgVectorScale(bV, bV, -alpha, nR);
      /* Compute A*v - alpha*u and store in vector u */
      AlgMatrixVectorMulAdd(bV, aM, vV, bV);
      /* Compute Euclidean length of u and store as beta */
      beta = AlgVectorNorm(bV, nR);
      /* Accumulate this quantity to estimate Frobenius norm of matrix A */
      bbnorm = AlgMatrixLSQRNorm4(alpha, beta, damping, bbnorm);
      if(beta > 0.0)
      {
	/* Scale vector u by 1.0 / beta */
	AlgVectorScale(bV, bV, 1.0 / beta, nR);
	/* Scale vector v by -beta */
	AlgVectorScale(vV, vV, -beta, nC);
	/* Compute A^T*u - beta*v and store in vector v */
	AlgMatrixTVectorMulAdd(vV, aM, bV, vV);
	/* Compute Euclidean length of v and store as alpha */
	alpha = AlgVectorNorm(vV, nC);
	if(alpha > 0.0)
	{
	  /* Scale vector v by the inverse of alpha */
	  AlgVectorScale(vV, vV, 1.0 / alpha, nC); 
	}
      }
      /* Use a plane rotation to eliminate the damping parameter.
       * This alters the diagonal (RHOBAR) of the lower-bidiagonal matrix.
       */
      rhobar1 = AlgMatrixLSQRNorm2(rhobar, damping);
      cs1     = rhobar / rhobar1;
      sn1     = damping / rhobar1;
      psi     = sn1 * phibar;
      phibar  = cs1 * phibar;
      /* Use a plane rotation to eliminate the subdiagonal element (beta)
       * of the lower-bidiagonal matrix, giving an upper-bidiagonal matrix.
       */
      rho    =  AlgMatrixLSQRNorm2(rhobar1, beta);
      cs     =  rhobar1 / rho;
      sn     =  beta / rho;
      theta  =  sn * alpha;
      rhobar = -cs * alpha;
      phi    =  cs * phibar;
      phibar =  sn * phibar;
      tau    =  sn * phi;
      /* Update the solution vector x, the search direction vector w.
       */     
      t3 = 1.0 / rho;
      t1 = phi * t3;
      t2 = -theta * t3;
      for(idx = 0; idx < nC; idx++)
      {
	t0 = wV[idx];
	/* Update the solution vector x */
	xV[idx] = t0 * t1 + xV[idx];
	/* Update the search direction vector w */
	wV[idx] = t0 * t2 + vV[idx];
	/* Accumulate this quantity to estimate condition number of A */
	ddnorm += ALG_SQR(t0 * t3);
      }
      ddnorm = sqrt(ddnorm);
      /* Use a plane rotation on the right to eliminate the super-diagonal
       * element (THETA) of the upper-bidiagonal matrix.  Then use the result
       * to estimate the solution norm || x ||.
       */
      delta    = sn2 * rho;
      gammabar = -cs2 * rho;
      zetabar = (phi - delta * zeta) / gammabar;
      /* Compute an estimate of the solution norm || x || */
      normX = sqrt(xxnorm + ALG_SQR(zetabar));
      gamma = AlgMatrixLSQRNorm2(gammabar, theta);
      cs2 = gammabar / gamma;
      sn2 = theta / gamma;
      zeta = (phi - delta * zeta) / gamma;
      /* Accumulate this quantity to estimate solution norm || x || */
      xxnorm = AlgMatrixLSQRNorm2(xxnorm, zeta);
      /* Estimate the norm and condition of the matrix A, and the norms of
       * the vectors r and A^T*r.
       */
      condN = fnorm * sqrt(ddnorm);
      res = AlgMatrixLSQRNorm2(res, psi);
      resNorm = AlgMatrixLSQRNorm2(phibar, res) + 1e-30;     /* avoid == 0.0 */
      resNormA = alpha * fabs(tau);

      /* Use these norms to estimate certain quantities, some of which will be
       * small near a solution.
       */
      stopCrit1 = resNorm / bnorm;
      stopCrit2 = 0.0;
      if(resNorm > 0.0)
      {
	stopCrit2 = resNormA / (bbnorm * resNorm);
      }
      stopCrit3 = 1.0 / condN;
      resTol = relErrB + relErrA * fnorm * normX / bnorm;
      resTolMach = DBL_EPSILON + DBL_EPSILON * fnorm * normX / bnorm;
      /* Check to see if any of the stopping criteria are satisfied.
       * First compare the computed criteria to the machine precision.
       * Second compare the computed criteria to the the user specified
       * precision.
       */
      if(itr >= maxItr)
      {
        /* Iteration limit reached */
	term = 7;
	errNum = ALG_ERR_CONVERGENCE;
      }
      else if(stopCrit3 <= DBL_EPSILON)
      {
        /* Condition number greater than machine precision */
	term = 6;
	errNum = ALG_ERR_MATRIX_CONDITION;
      }
      else if(stopCrit2 <= DBL_EPSILON)
      {
        /* Least squares error less than machine precision */
	term = 5;
      }
      else if(stopCrit1 <= resTolMach)
      {
        /* Residual less than a function of machine precision */
	term = 4;
      }
      else if(stopCrit3 <= condNTol)
      {
        /* Condition number greater than CONLIM */
	term = 3;
	errNum = ALG_ERR_MATRIX_CONDITION;
      }
      else if(stopCrit2 <= relErrA)
      {
        /* Least squares error less than ATOL */
	term = 2;
      }
      else if(stopCrit1 <= resTol)
      {
        /* Residual less than a function of ATOL and BTOL */
	term = 1;
      }
#ifdef ALG_MATRIXLSQR_DEBUG
      (void )fprintf(stderr,
		     "AlgMatrixSolveLSQR()\n"
		     "%2d %6li %10.5e %10.2e \t%10.2e \t%10.2e %10.2e\n",
		     errNum,
		     itr, resNorm, stopCrit1, 
		     stopCrit2,
		     fnorm, condN);
#endif
      /* The convergence criteria are required to be met on NCONV consecutive 
       * iterations, where NCONV is set below.  Suggested values are 1, 2, or
       * 3.
       */
      if(term == 0)
      {
	termItr = -1;
      }
      termItrMax = 1;
      ++termItr;
      if((termItr < termItrMax) && (itr < maxItr))
      {
	term = 0;
      }
    }
    /* Finish computing the standard error estimates vector se.
     */
    temp = 1.0;
    if(nR > nC)
    {
      temp = (double )(nR - nC);
    }
    if(ALG_SQR(damping) > 0.0)
    {
      temp = (double )nR;
    }
    temp = resNorm / sqrt(temp);
    if(dstTerm)
    {
      *dstTerm = term;
    }
    if(dstItr)
    {
      *dstItr = itr;
    }
    if(dstFNorm)
    {
      *dstFNorm = fnorm;
    }
    if(dstCondN)
    {
      *dstCondN = condN;
    }
    if(dstResNorm)
    {
      *dstResNorm = resNorm;
    }
    if(dstResNormA)
    {
      *dstResNormA = resNormA;
    }
    if(dstNormX)
    {
      *dstNormX = normX;
    }
  }
  AlcFree(vV);
  AlcFree(wV);
#ifdef ALG_MATRIXLSQR_DEBUG
  (void )fprintf(stderr,
		 "AlgMatrixSolveLSQR()\n"
		 "errcode = %3d\n"
		 "\tISTOP = %3li\t\t\tITER = %9li\n"
		 "  || A ||_F = %13.5e\tcond( A ) = %13.5e\n"
		 "  || r ||_2 = %13.5e\t|| A^T r ||_2 = %13.5e\n"
		 "  || b ||_2 = %13.5e\t|| x - x0 ||_2 = %13.5e\n\n", 
		 errNum,
		 term, itr, fnorm, 
		 condN, resNorm, resNormA,
		 bnorm, normX );
#endif
  return(errNum);
}

/*!
* \return	Norm of the given values.
* \ingroup	AlgMatrix
* \brief	Computes the norm of the given values, i.e.
*               \f$\sqrt{a^2 + b^2}\f$ but with precautions to avoid overflow.
* \param	a			First value.
* \param	b			Second value.
*/
static double 	AlgMatrixLSQRNorm2(double a, double b)
{
  double	scale,
  		norm = 0.0;

  scale = fabs(a) + fabs(b);
  if(scale > DBL_EPSILON)
  {
    a /= scale;
    b /= scale;
    norm = scale * sqrt(ALG_SQR(a) + ALG_SQR(b));
  }
  return(norm);
}

/*!
* \return	Norm of the given values.
* \ingroup	AlgMatrix
* \brief	Computes the norm of the given values, i.e.
*               \f$\sqrt{a^2 + b^2 + c^2 + d^2}\f$ but with precautions to avoid
*               overflow.
* \param	a			First value.
* \param	b			Second value.
* \param	c			Third value.
*/
static double 	AlgMatrixLSQRNorm4(double a, double b, double c, double d)
{
  double	scale,
  		norm = 0.0;

  scale = fabs(a) + fabs(b) + fabs(c) + fabs(d);
  if(scale > DBL_EPSILON)
  {
    a /= scale;
    b /= scale;
    c /= scale;
    d /= scale;
    norm = scale * sqrt(ALG_SQR(a) + ALG_SQR(b) + ALG_SQR(c) + ALG_SQR(d));
  }
  return(norm);
}
back to top