Blame multifit_nlinear/svd.c

Packit 67cb25
/* multifit_nlinear/svd.c
Packit 67cb25
 * 
Packit 67cb25
 * Copyright (C) 2016 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
/*
Packit 67cb25
 * This module handles the solution of the linear least squares
Packit 67cb25
 * system:
Packit 67cb25
 *
Packit 67cb25
 * [     J      ] dx = - [ f ]
Packit 67cb25
 * [ sqrt(mu)*D ]        [ 0 ]
Packit 67cb25
 *
Packit 67cb25
 * using an SVD approach. The system above is transformed to "standard form"
Packit 67cb25
 * via:
Packit 67cb25
 *
Packit 67cb25
 * J~ = J D^{-1}
Packit 67cb25
 * dx~ = D dx
Packit 67cb25
 *
Packit 67cb25
 * so that
Packit 67cb25
 *
Packit 67cb25
 * [     J~     ] dx~ = - [ f ]
Packit 67cb25
 * [ sqrt(mu)*I ]         [ 0 ]
Packit 67cb25
 *
Packit 67cb25
 * can be solved with a standard SVD method, and then dx is recovered
Packit 67cb25
 * from dx~ via: dx = D^{-1} dx~
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_multifit_nlinear.h>
Packit 67cb25
#include <gsl/gsl_errno.h>
Packit 67cb25
#include <gsl/gsl_blas.h>
Packit 67cb25
Packit 67cb25
typedef struct
Packit 67cb25
{
Packit 67cb25
  size_t n;                  /* number of residuals */
Packit 67cb25
  size_t p;                  /* number of parameters */
Packit 67cb25
  gsl_matrix *U;             /* U factor of J, n-by-p */
Packit 67cb25
  gsl_matrix *V;             /* V factor of J, p-by-p */
Packit 67cb25
  gsl_vector *S;             /* singular values, size p */
Packit 67cb25
  gsl_vector *workp;         /* workspace, length p */
Packit 67cb25
  double mu;                 /* LM parameter */
Packit 67cb25
} svd_state_t;
Packit 67cb25
Packit 67cb25
static int svd_init(const void * vtrust_state, void * vstate);
Packit 67cb25
static int svd_presolve(const double mu, const void * vtrust_state, void * vstate);
Packit 67cb25
static int svd_solve(const gsl_vector * f, gsl_vector *x,
Packit 67cb25
                     const void * vtrust_state, void *vstate);
Packit 67cb25
static int svd_rcond(double * rcond, void * vstate);
Packit 67cb25
Packit 67cb25
static void *
Packit 67cb25
svd_alloc (const size_t n, const size_t p)
Packit 67cb25
{
Packit 67cb25
  svd_state_t *state;
Packit 67cb25
Packit 67cb25
  (void)n;
Packit 67cb25
  
Packit 67cb25
  state = calloc(1, sizeof(svd_state_t));
Packit 67cb25
  if (state == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate svd state", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->U = gsl_matrix_alloc(n, p);
Packit 67cb25
  if (state->U == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate space for U", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->V = gsl_matrix_alloc(p, p);
Packit 67cb25
  if (state->V == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate space for V", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->S = gsl_vector_alloc(p);
Packit 67cb25
  if (state->S == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate space for S",
Packit 67cb25
                      GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->workp = gsl_vector_alloc(p);
Packit 67cb25
  if (state->workp == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate space for workp",
Packit 67cb25
                      GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->mu = 0.0;
Packit 67cb25
  state->n = n;
Packit 67cb25
  state->p = p;
Packit 67cb25
Packit 67cb25
  return state;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
static void
Packit 67cb25
svd_free(void *vstate)
Packit 67cb25
{
Packit 67cb25
  svd_state_t *state = (svd_state_t *) vstate;
Packit 67cb25
Packit 67cb25
  if (state->U)
Packit 67cb25
    gsl_matrix_free(state->U);
Packit 67cb25
Packit 67cb25
  if (state->V)
Packit 67cb25
    gsl_matrix_free(state->V);
Packit 67cb25
Packit 67cb25
  if (state->S)
Packit 67cb25
    gsl_vector_free(state->S);
Packit 67cb25
Packit 67cb25
  if (state->workp)
Packit 67cb25
    gsl_vector_free(state->workp);
Packit 67cb25
Packit 67cb25
  free(state);
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
/* compute svd of J */
Packit 67cb25
static int
Packit 67cb25
svd_init(const void * vtrust_state, void * vstate)
Packit 67cb25
{
Packit 67cb25
  int status;
Packit 67cb25
  const gsl_multifit_nlinear_trust_state *trust_state =
Packit 67cb25
    (const gsl_multifit_nlinear_trust_state *) vtrust_state;
Packit 67cb25
  svd_state_t *state = (svd_state_t *) vstate;
Packit 67cb25
  size_t i;
Packit 67cb25
Packit 67cb25
  gsl_matrix_set_zero(state->U);
Packit 67cb25
Packit 67cb25
  /* compute U = J D^{-1} */
Packit 67cb25
  for (i = 0; i < state->p; ++i)
Packit 67cb25
    {
Packit 67cb25
      gsl_vector_const_view Ji = gsl_matrix_const_column(trust_state->J, i);
Packit 67cb25
      gsl_vector_view ui = gsl_matrix_column(state->U, i);
Packit 67cb25
      double di = gsl_vector_get(trust_state->diag, i);
Packit 67cb25
Packit 67cb25
      gsl_blas_daxpy(1.0 / di, &Ji.vector, &ui.vector);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  status = gsl_linalg_SV_decomp(state->U, state->V, state->S, state->workp);
Packit 67cb25
Packit 67cb25
  return status;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
svd_presolve(const double mu, const void * vtrust_state, void * vstate)
Packit 67cb25
{
Packit 67cb25
  svd_state_t *state = (svd_state_t *) vstate;
Packit 67cb25
Packit 67cb25
  state->mu = mu;
Packit 67cb25
Packit 67cb25
  (void)vtrust_state;
Packit 67cb25
Packit 67cb25
  return GSL_SUCCESS;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
svd_solve(const gsl_vector * f, gsl_vector *x,
Packit 67cb25
          const void * vtrust_state, void *vstate)
Packit 67cb25
{
Packit 67cb25
  int status = GSL_SUCCESS;
Packit 67cb25
  const gsl_multifit_nlinear_trust_state *trust_state =
Packit 67cb25
    (const gsl_multifit_nlinear_trust_state *) vtrust_state;
Packit 67cb25
  svd_state_t *state = (svd_state_t *) vstate;
Packit 67cb25
  const size_t p = state->p;
Packit 67cb25
  const double tol = GSL_DBL_EPSILON;
Packit 67cb25
  const double s0 = gsl_vector_get(state->S, 0);
Packit 67cb25
  size_t j;
Packit 67cb25
Packit 67cb25
  /* compute workp = - U^T f */
Packit 67cb25
  gsl_blas_dgemv(CblasTrans, -1.0, state->U, f, 0.0, state->workp);
Packit 67cb25
Packit 67cb25
  /*
Packit 67cb25
   * compute:
Packit 67cb25
   *
Packit 67cb25
   * workp = sum_i s_i / (s_i^2 + mu) (-u_i^T f)
Packit 67cb25
   */
Packit 67cb25
Packit 67cb25
  if (state->mu == 0.0)
Packit 67cb25
    {
Packit 67cb25
      /*
Packit 67cb25
       * compute Gauss-Newton direction by solving
Packit 67cb25
       * J x = -f
Packit 67cb25
       */
Packit 67cb25
Packit 67cb25
      for (j = 0; j < p; ++j)
Packit 67cb25
        {
Packit 67cb25
          double sj = gsl_vector_get(state->S, j);
Packit 67cb25
          double *ptr = gsl_vector_ptr(state->workp, j);
Packit 67cb25
          double alpha;
Packit 67cb25
Packit 67cb25
          if (sj <= tol * s0)
Packit 67cb25
            alpha = 0.0;
Packit 67cb25
          else
Packit 67cb25
            alpha = 1.0 / sj;
Packit 67cb25
Packit 67cb25
          *ptr *= alpha;
Packit 67cb25
        }
Packit 67cb25
    }
Packit 67cb25
  else
Packit 67cb25
    {
Packit 67cb25
      /*
Packit 67cb25
       * solve:
Packit 67cb25
       *
Packit 67cb25
       * [  J D^{-1}  ] (D x) = -[ f ]
Packit 67cb25
       * [ sqrt(mu) I ]          [ 0 ]
Packit 67cb25
       *
Packit 67cb25
       * using SVD factorization of J D^{-1}
Packit 67cb25
       */
Packit 67cb25
Packit 67cb25
      for (j = 0; j < p; ++j)
Packit 67cb25
        {
Packit 67cb25
          double sj = gsl_vector_get(state->S, j);
Packit 67cb25
          double *ptr = gsl_vector_ptr(state->workp, j);
Packit 67cb25
Packit 67cb25
          *ptr *= sj / (sj*sj + state->mu);
Packit 67cb25
        }
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  /* compute: x = V * workp */
Packit 67cb25
  gsl_blas_dgemv(CblasNoTrans, 1.0, state->V, state->workp, 0.0, x);
Packit 67cb25
Packit 67cb25
  /* compute D^{-1} x */
Packit 67cb25
  gsl_vector_div(x, trust_state->diag);
Packit 67cb25
Packit 67cb25
  return status;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
svd_rcond(double * rcond, void * vstate)
Packit 67cb25
{
Packit 67cb25
  int status = GSL_SUCCESS;
Packit 67cb25
  svd_state_t *state = (svd_state_t *) vstate;
Packit 67cb25
  double smax = gsl_vector_get(state->S, 0);
Packit 67cb25
  double smin = gsl_vector_get(state->S, state->p - 1);
Packit 67cb25
Packit 67cb25
  *rcond = smin / smax;
Packit 67cb25
Packit 67cb25
  return status;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
static const gsl_multifit_nlinear_solver svd_type =
Packit 67cb25
{
Packit 67cb25
  "svd",
Packit 67cb25
  svd_alloc,
Packit 67cb25
  svd_init,
Packit 67cb25
  svd_presolve,
Packit 67cb25
  svd_solve,
Packit 67cb25
  svd_rcond,
Packit 67cb25
  svd_free
Packit 67cb25
};
Packit 67cb25
Packit 67cb25
const gsl_multifit_nlinear_solver *gsl_multifit_nlinear_solver_svd = &svd_type;