Blame splinalg/gmres.c

Packit 67cb25
/* gmres.c
Packit 67cb25
 * 
Packit 67cb25
 * Copyright (C) 2014 Patrick Alken
Packit 67cb25
 * 
Packit 67cb25
 * This program is free software; you can redistribute it and/or modify
Packit 67cb25
 * it under the terms of the GNU General Public License as published by
Packit 67cb25
 * the Free Software Foundation; either version 3 of the License, or (at
Packit 67cb25
 * your option) any later version.
Packit 67cb25
 * 
Packit 67cb25
 * This program is distributed in the hope that it will be useful, but
Packit 67cb25
 * WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 67cb25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 67cb25
 * General Public License for more details.
Packit 67cb25
 * 
Packit 67cb25
 * You should have received a copy of the GNU General Public License
Packit 67cb25
 * along with this program; if not, write to the Free Software
Packit 67cb25
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Packit 67cb25
 */
Packit 67cb25
Packit 67cb25
#include <config.h>
Packit 67cb25
#include <gsl/gsl_math.h>
Packit 67cb25
#include <gsl/gsl_vector.h>
Packit 67cb25
#include <gsl/gsl_matrix.h>
Packit 67cb25
#include <gsl/gsl_linalg.h>
Packit 67cb25
#include <gsl/gsl_errno.h>
Packit 67cb25
#include <gsl/gsl_blas.h>
Packit 67cb25
#include <gsl/gsl_spmatrix.h>
Packit 67cb25
#include <gsl/gsl_spblas.h>
Packit 67cb25
#include <gsl/gsl_splinalg.h>
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
 * The code in this module is based on the Householder GMRES
Packit 67cb25
 * algorithm described in
Packit 67cb25
 *
Packit 67cb25
 * [1] H. F. Walker, Implementation of the GMRES method using
Packit 67cb25
 *     Householder transformations, SIAM J. Sci. Stat. Comput.
Packit 67cb25
 *     9(1), 1988.
Packit 67cb25
 *
Packit 67cb25
 * [2] Y. Saad, Iterative methods for sparse linear systems,
Packit 67cb25
 *     2nd edition, SIAM, 2003.
Packit 67cb25
 */
Packit 67cb25
Packit 67cb25
typedef struct
Packit 67cb25
{
Packit 67cb25
  size_t n;        /* size of linear system */
Packit 67cb25
  size_t m;        /* dimension of Krylov subspace K_m */
Packit 67cb25
  gsl_vector *r;   /* residual vector r = b - A*x */
Packit 67cb25
  gsl_matrix *H;   /* Hessenberg matrix n-by-(m+1) */
Packit 67cb25
  gsl_vector *tau; /* householder scalars */
Packit 67cb25
  gsl_vector *y;   /* least squares rhs and solution vector */
Packit 67cb25
Packit 67cb25
  double *c;       /* Givens rotations */
Packit 67cb25
  double *s;
Packit 67cb25
Packit 67cb25
  double normr;    /* residual norm ||r|| */
Packit 67cb25
} gmres_state_t;
Packit 67cb25
Packit 67cb25
static void gmres_free(void *vstate);
Packit 67cb25
static int gmres_iterate(const gsl_spmatrix *A, const gsl_vector *b,
Packit 67cb25
                         const double tol, gsl_vector *x, void *vstate);
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
gmres_alloc()
Packit 67cb25
  Allocate a GMRES workspace for solving an n-by-n system A x = b
Packit 67cb25
Packit 67cb25
Inputs: n        - size of system
Packit 67cb25
        krylov_m - size of Krylov subspace (ie: number of inner iterations)
Packit 67cb25
                   if this parameter is 0, the value GSL_MIN(n,10) is
Packit 67cb25
                   used
Packit 67cb25
Packit 67cb25
Return: pointer to workspace
Packit 67cb25
*/
Packit 67cb25
Packit 67cb25
static void *
Packit 67cb25
gmres_alloc(const size_t n, const size_t m)
Packit 67cb25
{
Packit 67cb25
  gmres_state_t *state;
Packit 67cb25
Packit 67cb25
  if (n == 0)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL("matrix dimension n must be a positive integer",
Packit 67cb25
                     GSL_EINVAL);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state = calloc(1, sizeof(gmres_state_t));
Packit 67cb25
  if (!state)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL("failed to allocate gmres state", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->n = n;
Packit 67cb25
Packit 67cb25
  /* compute size of Krylov subspace */
Packit 67cb25
  if (m == 0)
Packit 67cb25
    state->m = GSL_MIN(n, 10);
Packit 67cb25
  else
Packit 67cb25
    state->m = GSL_MIN(n, m);
Packit 67cb25
Packit 67cb25
  state->r = gsl_vector_alloc(n);
Packit 67cb25
  if (!state->r)
Packit 67cb25
    {
Packit 67cb25
      gmres_free(state);
Packit 67cb25
      GSL_ERROR_NULL("failed to allocate r vector", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->H = gsl_matrix_alloc(n, state->m + 1);
Packit 67cb25
  if (!state->H)
Packit 67cb25
    {
Packit 67cb25
      gmres_free(state);
Packit 67cb25
      GSL_ERROR_NULL("failed to allocate H matrix", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->tau = gsl_vector_alloc(state->m + 1);
Packit 67cb25
  if (!state->tau)
Packit 67cb25
    {
Packit 67cb25
      gmres_free(state);
Packit 67cb25
      GSL_ERROR_NULL("failed to allocate tau vector", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->y = gsl_vector_alloc(state->m + 1);
Packit 67cb25
  if (!state->y)
Packit 67cb25
    {
Packit 67cb25
      gmres_free(state);
Packit 67cb25
      GSL_ERROR_NULL("failed to allocate y vector", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->c = malloc(state->m * sizeof(double));
Packit 67cb25
  state->s = malloc(state->m * sizeof(double));
Packit 67cb25
  if (!state->c || !state->s)
Packit 67cb25
    {
Packit 67cb25
      gmres_free(state);
Packit 67cb25
      GSL_ERROR_NULL("failed to allocate Givens vectors", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->normr = 0.0;
Packit 67cb25
Packit 67cb25
  return state;
Packit 67cb25
} /* gmres_alloc() */
Packit 67cb25
Packit 67cb25
static void
Packit 67cb25
gmres_free(void *vstate)
Packit 67cb25
{
Packit 67cb25
  gmres_state_t *state = (gmres_state_t *) vstate;
Packit 67cb25
Packit 67cb25
  if (state->r)
Packit 67cb25
    gsl_vector_free(state->r);
Packit 67cb25
Packit 67cb25
  if (state->H)
Packit 67cb25
    gsl_matrix_free(state->H);
Packit 67cb25
Packit 67cb25
  if (state->tau)
Packit 67cb25
    gsl_vector_free(state->tau);
Packit 67cb25
Packit 67cb25
  if (state->y)
Packit 67cb25
    gsl_vector_free(state->y);
Packit 67cb25
Packit 67cb25
  if (state->c)
Packit 67cb25
    free(state->c);
Packit 67cb25
Packit 67cb25
  if (state->s)
Packit 67cb25
    free(state->s);
Packit 67cb25
Packit 67cb25
  free(state);
Packit 67cb25
} /* gmres_free() */
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
gmres_iterate()
Packit 67cb25
  Solve A*x = b using GMRES algorithm
Packit 67cb25
Packit 67cb25
Inputs: A    - sparse square matrix
Packit 67cb25
        b    - right hand side vector
Packit 67cb25
        tol  - stopping tolerance (see below)
Packit 67cb25
        x    - (input/output) on input, initial estimate x_0;
Packit 67cb25
               on output, solution vector
Packit 67cb25
        work - workspace
Packit 67cb25
Packit 67cb25
Return:
Packit 67cb25
GSL_SUCCESS if converged to solution (solution stored in x). In
Packit 67cb25
this case the following will be true:
Packit 67cb25
Packit 67cb25
||b - A*x|| <= tol * ||b||
Packit 67cb25
Packit 67cb25
GSL_CONTINUE if not yet converged; in this case x contains the
Packit 67cb25
most recent solution vector and calling this function more times
Packit 67cb25
with the input x could result in convergence (ie: restarted GMRES)
Packit 67cb25
Packit 67cb25
Notes:
Packit 67cb25
1) Based on algorithm 2.2 of (Walker, 1998 [1]) and algorithm 6.10 of
Packit 67cb25
(Saad, 2003 [2])
Packit 67cb25
Packit 67cb25
2) On output, work->normr contains ||b - A*x||
Packit 67cb25
*/
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
gmres_iterate(const gsl_spmatrix *A, const gsl_vector *b,
Packit 67cb25
              const double tol, gsl_vector *x,
Packit 67cb25
              void *vstate)
Packit 67cb25
{
Packit 67cb25
  const size_t N = A->size1;
Packit 67cb25
  gmres_state_t *state = (gmres_state_t *) vstate;
Packit 67cb25
Packit 67cb25
  if (N != A->size2)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR("matrix must be square", GSL_ENOTSQR);
Packit 67cb25
    }
Packit 67cb25
  else if (N != b->size)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR("matrix does not match right hand side", GSL_EBADLEN);
Packit 67cb25
    }
Packit 67cb25
  else if (N != x->size)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR("matrix does not match solution vector", GSL_EBADLEN);
Packit 67cb25
    }
Packit 67cb25
  else if (N != state->n)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR("matrix does not match workspace", GSL_EBADLEN);
Packit 67cb25
    }
Packit 67cb25
  else
Packit 67cb25
    {
Packit 67cb25
      int status = GSL_SUCCESS;
Packit 67cb25
      const size_t maxit = state->m;
Packit 67cb25
      const double normb = gsl_blas_dnrm2(b); /* ||b|| */
Packit 67cb25
      const double reltol = tol * normb;      /* tol*||b|| */
Packit 67cb25
      double normr;                           /* ||r|| */
Packit 67cb25
      size_t m, k;
Packit 67cb25
      double tau;                             /* householder scalar */
Packit 67cb25
      gsl_matrix *H = state->H;               /* Hessenberg matrix */
Packit 67cb25
      gsl_vector *r = state->r;               /* residual vector */
Packit 67cb25
      gsl_vector *w = state->y;               /* least squares RHS */
Packit 67cb25
      gsl_matrix_view Rm;                     /* R_m = H(1:m,2:m+1) */
Packit 67cb25
      gsl_vector_view ym;                     /* y(1:m) */
Packit 67cb25
      gsl_vector_view h0 = gsl_matrix_column(H, 0);
Packit 67cb25
Packit 67cb25
      /*
Packit 67cb25
       * The Hessenberg matrix will have the following structure:
Packit 67cb25
       *
Packit 67cb25
       * H = [ ||r_0|| | v_1 v_2 ... v_m     ]
Packit 67cb25
       *     [   u_1   | u_2 u_3 ... u_{m+1} ]
Packit 67cb25
       *
Packit 67cb25
       * where v_j are the orthonormal vectors spanning the Krylov
Packit 67cb25
       * subpsace of length j + 1 and u_{j+1} are the householder
Packit 67cb25
       * vectors of length n - j - 1.
Packit 67cb25
       * In fact, u_{j+1} has length n - j since u_{j+1}[0] = 1,
Packit 67cb25
       * but this 1 is not stored.
Packit 67cb25
       */
Packit 67cb25
      gsl_matrix_set_zero(H);
Packit 67cb25
Packit 67cb25
      /* Step 1a: compute r = b - A*x_0 */
Packit 67cb25
      gsl_vector_memcpy(r, b);
Packit 67cb25
      gsl_spblas_dgemv(CblasNoTrans, -1.0, A, x, 1.0, r);
Packit 67cb25
Packit 67cb25
      /* Step 1b */
Packit 67cb25
      gsl_vector_memcpy(&h0.vector, r);
Packit 67cb25
      tau = gsl_linalg_householder_transform(&h0.vector);
Packit 67cb25
Packit 67cb25
      /* store tau_1 */
Packit 67cb25
      gsl_vector_set(state->tau, 0, tau);
Packit 67cb25
Packit 67cb25
      /* initialize w (stored in state->y) */
Packit 67cb25
      gsl_vector_set_zero(w);
Packit 67cb25
      gsl_vector_set(w, 0, gsl_vector_get(&h0.vector, 0));
Packit 67cb25
Packit 67cb25
      for (m = 1; m <= maxit; ++m)
Packit 67cb25
        {
Packit 67cb25
          size_t j = m - 1; /* C indexing */
Packit 67cb25
          double c, s;      /* Givens rotation */
Packit 67cb25
Packit 67cb25
          /* v_m */
Packit 67cb25
          gsl_vector_view vm = gsl_matrix_column(H, m);
Packit 67cb25
Packit 67cb25
          /* v_m(m:end) */
Packit 67cb25
          gsl_vector_view vv = gsl_vector_subvector(&vm.vector, j, N - j);
Packit 67cb25
Packit 67cb25
          /* householder vector u_m for projection P_m */
Packit 67cb25
          gsl_vector_view um = gsl_matrix_subcolumn(H, j, j, N - j);
Packit 67cb25
Packit 67cb25
          /* Step 2a: form v_m = P_m e_m = e_m - tau_m w_m */
Packit 67cb25
          gsl_vector_set_zero(&vm.vector);
Packit 67cb25
          gsl_vector_memcpy(&vv.vector, &um.vector);
Packit 67cb25
          tau = gsl_vector_get(state->tau, j); /* tau_m */
Packit 67cb25
          gsl_vector_scale(&vv.vector, -tau);
Packit 67cb25
          gsl_vector_set(&vv.vector, 0, 1.0 - tau);
Packit 67cb25
Packit 67cb25
          /* Step 2a: v_m <- P_1 P_2 ... P_{m-1} v_m */
Packit 67cb25
          for (k = j; k > 0 && k--; )
Packit 67cb25
            {
Packit 67cb25
              gsl_vector_view uk =
Packit 67cb25
                gsl_matrix_subcolumn(H, k, k, N - k);
Packit 67cb25
              gsl_vector_view vk =
Packit 67cb25
                gsl_vector_subvector(&vm.vector, k, N - k);
Packit 67cb25
              tau = gsl_vector_get(state->tau, k);
Packit 67cb25
              gsl_linalg_householder_hv(tau, &uk.vector, &vk.vector);
Packit 67cb25
            }
Packit 67cb25
Packit 67cb25
          /* Step 2a: v_m <- A*v_m */
Packit 67cb25
          gsl_spblas_dgemv(CblasNoTrans, 1.0, A, &vm.vector, 0.0, r);
Packit 67cb25
          gsl_vector_memcpy(&vm.vector, r);
Packit 67cb25
Packit 67cb25
          /* Step 2a: v_m <- P_m ... P_1 v_m */
Packit 67cb25
          for (k = 0; k <= j; ++k)
Packit 67cb25
            {
Packit 67cb25
              gsl_vector_view uk = gsl_matrix_subcolumn(H, k, k, N - k);
Packit 67cb25
              gsl_vector_view vk = gsl_vector_subvector(&vm.vector, k, N - k);
Packit 67cb25
              tau = gsl_vector_get(state->tau, k);
Packit 67cb25
              gsl_linalg_householder_hv(tau, &uk.vector, &vk.vector);
Packit 67cb25
            }
Packit 67cb25
Packit 67cb25
          /* Steps 2c,2d: find P_{m+1} and set v_m <- P_{m+1} v_m */
Packit 67cb25
          if (m < N)
Packit 67cb25
            {
Packit 67cb25
              /* householder vector u_{m+1} for projection P_{m+1} */
Packit 67cb25
              gsl_vector_view ump1 = gsl_matrix_subcolumn(H, m, m, N - m);
Packit 67cb25
Packit 67cb25
              tau = gsl_linalg_householder_transform(&ump1.vector);
Packit 67cb25
              gsl_vector_set(state->tau, j + 1, tau);
Packit 67cb25
            }
Packit 67cb25
Packit 67cb25
          /* Step 2e: v_m <- J_{m-1} ... J_1 v_m */
Packit 67cb25
          for (k = 0; k < j; ++k)
Packit 67cb25
            {
Packit 67cb25
              gsl_linalg_givens_gv(&vm.vector, k, k + 1,
Packit 67cb25
                                   state->c[k], state->s[k]);
Packit 67cb25
            }
Packit 67cb25
Packit 67cb25
          if (m < N)
Packit 67cb25
            {
Packit 67cb25
              /* Step 2g: find givens rotation J_m for v_m(m:m+1) */
Packit 67cb25
              gsl_linalg_givens(gsl_vector_get(&vm.vector, j),
Packit 67cb25
                                gsl_vector_get(&vm.vector, j + 1),
Packit 67cb25
                                &c, &s);
Packit 67cb25
Packit 67cb25
              /* store givens rotation for later use */
Packit 67cb25
              state->c[j] = c;
Packit 67cb25
              state->s[j] = s;
Packit 67cb25
Packit 67cb25
              /* Step 2h: v_m <- J_m v_m */
Packit 67cb25
              gsl_linalg_givens_gv(&vm.vector, j, j + 1, c, s);
Packit 67cb25
Packit 67cb25
              /* Step 2h: w <- J_m w */
Packit 67cb25
              gsl_linalg_givens_gv(w, j, j + 1, c, s);
Packit 67cb25
            }
Packit 67cb25
Packit 67cb25
          /*
Packit 67cb25
           * Step 2i: R_m = [ R_{m-1}, v_m ] - already taken care
Packit 67cb25
           * of due to our memory storage scheme
Packit 67cb25
           */
Packit 67cb25
Packit 67cb25
          /* Step 2j: check residual w_{m+1} for convergence */
Packit 67cb25
          normr = fabs(gsl_vector_get(w, j + 1));
Packit 67cb25
          if (normr <= reltol)
Packit 67cb25
            {
Packit 67cb25
              /*
Packit 67cb25
               * method has converged, break out of loop to compute
Packit 67cb25
               * update to solution vector x
Packit 67cb25
               */
Packit 67cb25
              break;
Packit 67cb25
            }
Packit 67cb25
        }
Packit 67cb25
Packit 67cb25
      /*
Packit 67cb25
       * At this point, we have either converged to a solution or
Packit 67cb25
       * completed all maxit iterations. In either case, compute
Packit 67cb25
       * an update to the solution vector x and test again for
Packit 67cb25
       * convergence.
Packit 67cb25
       */
Packit 67cb25
Packit 67cb25
      /* rewind m if we exceeded maxit iterations */
Packit 67cb25
      if (m > maxit)
Packit 67cb25
        m--;
Packit 67cb25
Packit 67cb25
      /* Step 3a: solve triangular system R_m y_m = w, in place */
Packit 67cb25
      Rm = gsl_matrix_submatrix(H, 0, 1, m, m);
Packit 67cb25
      ym = gsl_vector_subvector(w, 0, m);
Packit 67cb25
      gsl_blas_dtrsv(CblasUpper, CblasNoTrans, CblasNonUnit,
Packit 67cb25
                     &Rm.matrix, &ym.vector);
Packit 67cb25
Packit 67cb25
      /*
Packit 67cb25
       * Step 3b: update solution vector x; the loop below
Packit 67cb25
       * uses a different but equivalent formulation from
Packit 67cb25
       * Saad, algorithm 6.10, step 14; store Krylov projection
Packit 67cb25
       * V_m y_m in 'r'
Packit 67cb25
       */
Packit 67cb25
      gsl_vector_set_zero(r);
Packit 67cb25
      for (k = m; k > 0 && k--; )
Packit 67cb25
        {
Packit 67cb25
          double ymk = gsl_vector_get(&ym.vector, k);
Packit 67cb25
          gsl_vector_view uk = gsl_matrix_subcolumn(H, k, k, N - k);
Packit 67cb25
          gsl_vector_view rk = gsl_vector_subvector(r, k, N - k);
Packit 67cb25
Packit 67cb25
          /* r <- n_k e_k + r */
Packit 67cb25
          gsl_vector_set(r, k, gsl_vector_get(r, k) + ymk);
Packit 67cb25
Packit 67cb25
          /* r <- P_k r */
Packit 67cb25
          tau = gsl_vector_get(state->tau, k);
Packit 67cb25
          gsl_linalg_householder_hv(tau, &uk.vector, &rk.vector);
Packit 67cb25
        }
Packit 67cb25
Packit 67cb25
      /* x <- x + V_m y_m */
Packit 67cb25
      gsl_vector_add(x, r);
Packit 67cb25
Packit 67cb25
      /* compute new residual r = b - A*x */
Packit 67cb25
      gsl_vector_memcpy(r, b);
Packit 67cb25
      gsl_spblas_dgemv(CblasNoTrans, -1.0, A, x, 1.0, r);
Packit 67cb25
      normr = gsl_blas_dnrm2(r);
Packit 67cb25
Packit 67cb25
      if (normr <= reltol)
Packit 67cb25
        status = GSL_SUCCESS;  /* converged */
Packit 67cb25
      else
Packit 67cb25
        status = GSL_CONTINUE; /* not yet converged */
Packit 67cb25
Packit 67cb25
      /* store residual norm */
Packit 67cb25
      state->normr = normr;
Packit 67cb25
Packit 67cb25
      return status;
Packit 67cb25
    }
Packit 67cb25
} /* gmres_iterate() */
Packit 67cb25
Packit 67cb25
static double
Packit 67cb25
gmres_normr(const void *vstate)
Packit 67cb25
{
Packit 67cb25
  const gmres_state_t *state = (const gmres_state_t *) vstate;
Packit 67cb25
  return state->normr;
Packit 67cb25
} /* gmres_normr() */
Packit 67cb25
Packit 67cb25
static const gsl_splinalg_itersolve_type gmres_type =
Packit 67cb25
{
Packit 67cb25
  "gmres",
Packit 67cb25
  &gmres_alloc,
Packit 67cb25
  &gmres_iterate,
Packit 67cb25
  &gmres_normr,
Packit 67cb25
  &gmres_free
Packit 67cb25
};
Packit 67cb25
Packit 67cb25
const gsl_splinalg_itersolve_type * gsl_splinalg_itersolve_gmres =
Packit 67cb25
  &gmres_type;