Blame multifit/lmniel.c

Packit 67cb25
/* multifit/lmniel.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_multifit_nlin.h>
Packit 67cb25
#include <gsl/gsl_errno.h>
Packit 67cb25
#include <gsl/gsl_blas.h>
Packit 67cb25
Packit 67cb25
#define SCALE 0
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
 * This module contains an implementation of the Levenberg-Marquardt
Packit 67cb25
 * algorithm for nonlinear optimization problems. This implementation
Packit 67cb25
 * closely follows the following works:
Packit 67cb25
 *
Packit 67cb25
 * [1] H. B. Nielsen, K. Madsen, Introduction to Optimization and
Packit 67cb25
 *     Data Fitting, Informatics and Mathematical Modeling,
Packit 67cb25
 *     Technical University of Denmark (DTU), 2010.
Packit 67cb25
 */
Packit 67cb25
Packit 67cb25
typedef struct
Packit 67cb25
{
Packit 67cb25
  gsl_matrix *A;             /* J^T J */
Packit 67cb25
  gsl_matrix *A_copy;        /* copy of J^T J */
Packit 67cb25
  gsl_matrix *J;             /* Jacobian J(x) */
Packit 67cb25
  gsl_vector *diag;          /* D = diag(J^T J) */
Packit 67cb25
  gsl_vector *rhs;           /* rhs vector = -g = -J^T f */
Packit 67cb25
  gsl_vector *x_trial;       /* trial parameter vector */
Packit 67cb25
  gsl_vector *f_trial;       /* trial function vector */
Packit 67cb25
  gsl_vector *work;          /* workspace length p */
Packit 67cb25
  long nu;                   /* nu */
Packit 67cb25
  double mu;                 /* LM damping parameter mu */
Packit 67cb25
  double tau;                /* initial scale factor for mu */
Packit 67cb25
} lmniel_state_t;
Packit 67cb25
Packit 67cb25
#include "lmmisc.c"
Packit 67cb25
Packit 67cb25
#define LM_ONE_THIRD         (0.333333333333333)
Packit 67cb25
Packit 67cb25
static int lmniel_alloc (void *vstate, const size_t n, const size_t p);
Packit 67cb25
static void lmniel_free(void *vstate);
Packit 67cb25
static int lmniel_set(void *vstate, const gsl_vector * swts,
Packit 67cb25
                      gsl_multifit_function_fdf *fdf,
Packit 67cb25
                      gsl_vector *x, gsl_vector *f, gsl_vector *dx);
Packit 67cb25
static int lmniel_iterate(void *vstate, const gsl_vector *swts,
Packit 67cb25
                          gsl_multifit_function_fdf *fdf,
Packit 67cb25
                          gsl_vector *x, gsl_vector *f, gsl_vector *dx);
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
lmniel_alloc (void *vstate, const size_t n, const size_t p)
Packit 67cb25
{
Packit 67cb25
  lmniel_state_t *state = (lmniel_state_t *) vstate;
Packit 67cb25
Packit 67cb25
  state->A = gsl_matrix_alloc(p, p);
Packit 67cb25
  if (state->A == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR ("failed to allocate space for A", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->J = gsl_matrix_alloc(n, p);
Packit 67cb25
  if (state->J == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR ("failed to allocate space for J", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->diag = gsl_vector_alloc(p);
Packit 67cb25
  if (state->diag == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR ("failed to allocate space for diag", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->rhs = gsl_vector_alloc(p);
Packit 67cb25
  if (state->rhs == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR ("failed to allocate space for rhs", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->work = gsl_vector_alloc(p);
Packit 67cb25
  if (state->work == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR ("failed to allocate space for work", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->A_copy = gsl_matrix_alloc(p, p);
Packit 67cb25
  if (state->A_copy == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR ("failed to allocate space for A_copy", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->x_trial = gsl_vector_alloc(p);
Packit 67cb25
  if (state->x_trial == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR ("failed to allocate space for x_trial", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->f_trial = gsl_vector_alloc(n);
Packit 67cb25
  if (state->f_trial == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR ("failed to allocate space for f_trial", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->tau = 1.0e-3;
Packit 67cb25
Packit 67cb25
  return GSL_SUCCESS;
Packit 67cb25
} /* lmniel_alloc() */
Packit 67cb25
Packit 67cb25
static void
Packit 67cb25
lmniel_free(void *vstate)
Packit 67cb25
{
Packit 67cb25
  lmniel_state_t *state = (lmniel_state_t *) vstate;
Packit 67cb25
Packit 67cb25
  if (state->A)
Packit 67cb25
    gsl_matrix_free(state->A);
Packit 67cb25
Packit 67cb25
  if (state->J)
Packit 67cb25
    gsl_matrix_free(state->J);
Packit 67cb25
Packit 67cb25
  if (state->diag)
Packit 67cb25
    gsl_vector_free(state->diag);
Packit 67cb25
Packit 67cb25
  if (state->rhs)
Packit 67cb25
    gsl_vector_free(state->rhs);
Packit 67cb25
Packit 67cb25
  if (state->work)
Packit 67cb25
    gsl_vector_free(state->work);
Packit 67cb25
Packit 67cb25
  if (state->A_copy)
Packit 67cb25
    gsl_matrix_free(state->A_copy);
Packit 67cb25
Packit 67cb25
  if (state->x_trial)
Packit 67cb25
    gsl_vector_free(state->x_trial);
Packit 67cb25
Packit 67cb25
  if (state->f_trial)
Packit 67cb25
    gsl_vector_free(state->f_trial);
Packit 67cb25
} /* lmniel_free() */
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
lmniel_set(void *vstate, const gsl_vector *swts,
Packit 67cb25
           gsl_multifit_function_fdf *fdf, gsl_vector *x,
Packit 67cb25
           gsl_vector *f, gsl_vector *dx)
Packit 67cb25
{
Packit 67cb25
  int status;
Packit 67cb25
  lmniel_state_t *state = (lmniel_state_t *) vstate;
Packit 67cb25
  const size_t p = x->size;
Packit 67cb25
  size_t i;
Packit 67cb25
Packit 67cb25
  /* initialize counters for function and Jacobian evaluations */
Packit 67cb25
  fdf->nevalf = 0;
Packit 67cb25
  fdf->nevaldf = 0;
Packit 67cb25
Packit 67cb25
  /* evaluate function and Jacobian at x and apply weight transform */
Packit 67cb25
  status = gsl_multifit_eval_wf(fdf, x, swts, f);
Packit 67cb25
  if (status)
Packit 67cb25
   return status;
Packit 67cb25
Packit 67cb25
  if (fdf->df)
Packit 67cb25
    status = gsl_multifit_eval_wdf(fdf, x, swts, state->J);
Packit 67cb25
  else
Packit 67cb25
    status = gsl_multifit_fdfsolver_dif_df(x, swts, fdf, f, state->J);
Packit 67cb25
  if (status)
Packit 67cb25
    return status;
Packit 67cb25
Packit 67cb25
  /* compute rhs = -J^T f */
Packit 67cb25
  gsl_blas_dgemv(CblasTrans, -1.0, state->J, f, 0.0, state->rhs);
Packit 67cb25
Packit 67cb25
#if SCALE
Packit 67cb25
  gsl_vector_set_zero(state->diag);
Packit 67cb25
#else
Packit 67cb25
  gsl_vector_set_all(state->diag, 1.0);
Packit 67cb25
#endif
Packit 67cb25
Packit 67cb25
  /* set default parameters */
Packit 67cb25
  state->nu = 2;
Packit 67cb25
Packit 67cb25
#if SCALE
Packit 67cb25
  state->mu = state->tau;
Packit 67cb25
#else
Packit 67cb25
  /* compute mu_0 = tau * max(diag(J^T J)) */
Packit 67cb25
  state->mu = -1.0;
Packit 67cb25
  for (i = 0; i < p; ++i)
Packit 67cb25
    {
Packit 67cb25
      gsl_vector_view c = gsl_matrix_column(state->J, i);
Packit 67cb25
      double result; /* (J^T J)_{ii} */
Packit 67cb25
Packit 67cb25
      gsl_blas_ddot(&c.vector, &c.vector, &result);
Packit 67cb25
      state->mu = GSL_MAX(state->mu, result);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->mu *= state->tau;
Packit 67cb25
#endif
Packit 67cb25
Packit 67cb25
  return GSL_SUCCESS;
Packit 67cb25
} /* lmniel_set() */
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
lmniel_iterate()
Packit 67cb25
  This function performs 1 iteration of the LM algorithm 6.18
Packit 67cb25
from [1]. The algorithm is slightly modified to loop until we
Packit 67cb25
find an acceptable step dx, in order to guarantee that each
Packit 67cb25
function call contains a new input vector x.
Packit 67cb25
Packit 67cb25
Args: vstate - lm workspace
Packit 67cb25
      swts   - data weights (NULL if unweighted)
Packit 67cb25
      fdf    - function and Jacobian pointers
Packit 67cb25
      x      - on input, current parameter vector
Packit 67cb25
               on output, new parameter vector x + dx
Packit 67cb25
      f      - on input, f(x)
Packit 67cb25
               on output, f(x + dx)
Packit 67cb25
      dx     - (output only) parameter step vector
Packit 67cb25
Packit 67cb25
Notes:
Packit 67cb25
1) On input, the following must be initialized in state:
Packit 67cb25
nu, mu, rhs, J
Packit 67cb25
Packit 67cb25
2) On output, the following are updated with the current iterates:
Packit 67cb25
nu, mu, rhs, J
Packit 67cb25
Packit 67cb25
rhs needs to be set on each output, so that lmniel_gradient supplies
Packit 67cb25
the correct g = J^T f
Packit 67cb25
*/
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
lmniel_iterate(void *vstate, const gsl_vector *swts,
Packit 67cb25
               gsl_multifit_function_fdf *fdf, gsl_vector *x,
Packit 67cb25
               gsl_vector *f, gsl_vector *dx)
Packit 67cb25
{
Packit 67cb25
  int status;
Packit 67cb25
  lmniel_state_t *state = (lmniel_state_t *) vstate;
Packit 67cb25
  gsl_matrix *J = state->J;                   /* Jacobian J(x) */
Packit 67cb25
  gsl_matrix *A = state->A;                   /* J^T J */
Packit 67cb25
  gsl_vector *rhs = state->rhs;               /* -g = -J^T f */
Packit 67cb25
  gsl_vector *x_trial = state->x_trial;       /* trial x + dx */
Packit 67cb25
  gsl_vector *f_trial = state->f_trial;       /* trial f(x + dx) */
Packit 67cb25
  gsl_vector *diag = state->diag;             /* diag(D) */
Packit 67cb25
  double dF;                                  /* F(x) - F(x + dx) */
Packit 67cb25
  double dL;                                  /* L(0) - L(dx) */
Packit 67cb25
  int foundstep = 0;                          /* found step dx */
Packit 67cb25
Packit 67cb25
  /* compute A = J^T J */
Packit 67cb25
  status = gsl_blas_dsyrk(CblasLower, CblasTrans, 1.0, J, 0.0, A);
Packit 67cb25
  if (status)
Packit 67cb25
    return status;
Packit 67cb25
Packit 67cb25
  /* copy lower triangle to upper */
Packit 67cb25
  gsl_matrix_transpose_tricpy('L', 0, A, A);
Packit 67cb25
Packit 67cb25
#if SCALE
Packit 67cb25
  lmniel_update_diag(J, diag);
Packit 67cb25
#endif
Packit 67cb25
Packit 67cb25
  /* loop until we find an acceptable step dx */
Packit 67cb25
  while (!foundstep)
Packit 67cb25
    {
Packit 67cb25
      /* solve (A + mu*I) dx = g */
Packit 67cb25
      status = lmniel_calc_dx(state->mu, A, rhs, dx, state);
Packit 67cb25
      if (status)
Packit 67cb25
        return status;
Packit 67cb25
Packit 67cb25
      /* compute x_trial = x + dx */
Packit 67cb25
      lmniel_trial_step(x, dx, x_trial);
Packit 67cb25
Packit 67cb25
      /* compute f(x + dx) */
Packit 67cb25
      status = gsl_multifit_eval_wf(fdf, x_trial, swts, f_trial);
Packit 67cb25
      if (status)
Packit 67cb25
       return status;
Packit 67cb25
Packit 67cb25
      /* compute dF = F(x) - F(x + dx) */
Packit 67cb25
      dF = lmniel_calc_dF(f, f_trial);
Packit 67cb25
Packit 67cb25
      /* compute dL = L(0) - L(dx) = dx^T (mu*dx - g) */
Packit 67cb25
      dL = lmniel_calc_dL(state->mu, diag, dx, rhs);
Packit 67cb25
Packit 67cb25
      /* check that rho = dF/dL > 0 */
Packit 67cb25
      if ((dL > 0.0) && (dF >= 0.0))
Packit 67cb25
        {
Packit 67cb25
          /* reduction in error, step acceptable */
Packit 67cb25
Packit 67cb25
          double tmp;
Packit 67cb25
Packit 67cb25
          /* update LM parameter mu */
Packit 67cb25
          tmp = 2.0 * (dF / dL) - 1.0;
Packit 67cb25
          tmp = 1.0 - tmp*tmp*tmp;
Packit 67cb25
          state->mu *= GSL_MAX(LM_ONE_THIRD, tmp);
Packit 67cb25
          state->nu = 2;
Packit 67cb25
Packit 67cb25
          /* compute J <- J(x + dx) */
Packit 67cb25
          if (fdf->df)
Packit 67cb25
            status = gsl_multifit_eval_wdf(fdf, x_trial, swts, J);
Packit 67cb25
          else
Packit 67cb25
            status = gsl_multifit_fdfsolver_dif_df(x_trial, swts, fdf, f_trial, J);
Packit 67cb25
          if (status)
Packit 67cb25
            return status;
Packit 67cb25
Packit 67cb25
          /* update x <- x + dx */
Packit 67cb25
          gsl_vector_memcpy(x, x_trial);
Packit 67cb25
Packit 67cb25
          /* update f <- f(x + dx) */
Packit 67cb25
          gsl_vector_memcpy(f, f_trial);
Packit 67cb25
Packit 67cb25
          /* compute new rhs = -J^T f */
Packit 67cb25
          gsl_blas_dgemv(CblasTrans, -1.0, J, f, 0.0, rhs);
Packit 67cb25
Packit 67cb25
          foundstep = 1;
Packit 67cb25
        }
Packit 67cb25
      else
Packit 67cb25
        {
Packit 67cb25
          long nu2;
Packit 67cb25
Packit 67cb25
          /* step did not reduce error, reject step */
Packit 67cb25
          state->mu *= (double) state->nu;
Packit 67cb25
          nu2 = state->nu << 1; /* 2*nu */
Packit 67cb25
          if (nu2 <= state->nu)
Packit 67cb25
            {
Packit 67cb25
              gsl_vector_view d = gsl_matrix_diagonal(A);
Packit 67cb25
Packit 67cb25
              /*
Packit 67cb25
               * nu has wrapped around / overflown, reset mu and nu
Packit 67cb25
               * to original values and break to force another iteration
Packit 67cb25
               */
Packit 67cb25
              /*GSL_ERROR("nu parameter has overflown", GSL_EOVRFLW);*/
Packit 67cb25
              state->nu = 2;
Packit 67cb25
              state->mu = state->tau * gsl_vector_max(&d.vector);
Packit 67cb25
              break;
Packit 67cb25
            }
Packit 67cb25
          state->nu = nu2;
Packit 67cb25
        }
Packit 67cb25
    } /* while (!foundstep) */
Packit 67cb25
Packit 67cb25
  return GSL_SUCCESS;
Packit 67cb25
} /* lmniel_iterate() */
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
lmniel_gradient(void *vstate, gsl_vector * g)
Packit 67cb25
{
Packit 67cb25
  lmniel_state_t *state = (lmniel_state_t *) vstate;
Packit 67cb25
  gsl_vector_memcpy(g, state->rhs);
Packit 67cb25
  gsl_vector_scale(g, -1.0);
Packit 67cb25
  return GSL_SUCCESS;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
lmniel_jac(void *vstate, gsl_matrix * J)
Packit 67cb25
{
Packit 67cb25
  lmniel_state_t *state = (lmniel_state_t *) vstate;
Packit 67cb25
  int s = gsl_matrix_memcpy(J, state->J);
Packit 67cb25
Packit 67cb25
  return s;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
static const gsl_multifit_fdfsolver_type lmniel_type =
Packit 67cb25
{
Packit 67cb25
  "lmniel",
Packit 67cb25
  sizeof(lmniel_state_t),
Packit 67cb25
  &lmniel_alloc,
Packit 67cb25
  &lmniel_set,
Packit 67cb25
  &lmniel_iterate,
Packit 67cb25
  &lmniel_gradient,
Packit 67cb25
  &lmniel_jac,
Packit 67cb25
  &lmniel_free
Packit 67cb25
};
Packit 67cb25
Packit 67cb25
const gsl_multifit_fdfsolver_type *gsl_multifit_fdfsolver_lmniel = &lmniel_type;