Blame multifit_nlinear/subspace2D.c

Packit 67cb25
/* multifit_nlinear/subspace2D.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
#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
#include <gsl/gsl_poly.h>
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
 * This module implements a 2D subspace trust region subproblem method,
Packit 67cb25
 * as outlined in
Packit 67cb25
 *
Packit 67cb25
 * [1] G. A. Shultz, R. B. Schnabel, and R. H. Byrd
Packit 67cb25
 *     A Family of Trust-Region-Based Algorithms for Unconstrained
Packit 67cb25
 *     Minimization with Strong Global Convergence Properties,
Packit 67cb25
 *     SIAM Journal on Numerical Analysis 1985 22:1, 47-67 
Packit 67cb25
 *
Packit 67cb25
 * [2] R. H. Byrd, R. B. Schnabel, G. A. Shultz,
Packit 67cb25
 *     Approximate solution of the trust region problem by
Packit 67cb25
 *     minimization over two-dimensional subspaces,
Packit 67cb25
 *     Mathematical Programming, January 1988, Volume 40,
Packit 67cb25
 *     Issue 1, pp 247-263
Packit 67cb25
 *
Packit 67cb25
 * The idea is to solve:
Packit 67cb25
 *
Packit 67cb25
 * min_{dx} g^T dx + 1/2 dx^T B dx
Packit 67cb25
 * 
Packit 67cb25
 * with constraints:
Packit 67cb25
 *
Packit 67cb25
 * ||D dx|| <= delta
Packit 67cb25
 * dx \in span{dx_sd, dx_gn}
Packit 67cb25
 *
Packit 67cb25
 * where B is the Hessian matrix, B = J^T J
Packit 67cb25
 *
Packit 67cb25
 * The steps are as follows:
Packit 67cb25
 *
Packit 67cb25
 * 1. preloop:
Packit 67cb25
 *    a. Compute Gauss-Newton and steepest descent vectors,
Packit 67cb25
 *       dx_gn, dx_sd
Packit 67cb25
 *    b. Compute an orthonormal basis for span(D dx_sd, D dx_gn) by
Packit 67cb25
 *       constructing W = [ D dx_sd, D dx_gn ] and performing a QR
Packit 67cb25
 *       decomposition of W. The 2 columns of the Q matrix
Packit 67cb25
 *       will then span the column space of W. W should have rank 2
Packit 67cb25
 *       unless D*dx_sd and D*dx_gn are parallel, in which case it will
Packit 67cb25
 *       have rank 1.
Packit 67cb25
 *    c. Precompute various quantities needed for the step calculation
Packit 67cb25
 *
Packit 67cb25
 * 2. step:
Packit 67cb25
 *    a. If the Gauss-Newton step is inside the trust region, use it
Packit 67cb25
 *    b. if W has rank 1, we cannot form a 2D subspace, so in this case
Packit 67cb25
 *       follow the steepest descent direction to the trust region boundary
Packit 67cb25
 *       and use that as the step.
Packit 67cb25
 *    c. In the full rank 2 case, if the GN point is outside the trust region,
Packit 67cb25
 *       then the minimizer of the objective function lies on the trust
Packit 67cb25
 *       region boundary. Therefore the minimization problem becomes:
Packit 67cb25
 *
Packit 67cb25
 *       min_{dx} g^T dx + 1/2 dx^T B dx, with ||dx|| = delta, dx = Q * x
Packit 67cb25
 *
Packit 67cb25
 *       where x is a 2-vector to be determined and the columns of Q are
Packit 67cb25
 *       the orthonormal basis vectors of the subspace. Note the equality
Packit 67cb25
 *       constraint now instead of <=. In terms of the new variable x,
Packit 67cb25
 *       the minimization problem becomes:
Packit 67cb25
 *
Packit 67cb25
 *       min_x subg^T x + 1/2 x^T subB x, with ||Q*x|| = ||x|| = delta
Packit 67cb25
 *
Packit 67cb25
 *       where:
Packit 67cb25
 *         subg = Q^T g   (2-by-1)
Packit 67cb25
 *         subB = Q^T B Q (2-by-2)
Packit 67cb25
 *
Packit 67cb25
 *       This equality constrained 2D minimization problem can be solved
Packit 67cb25
 *       with a Lagrangian multiplier, which results in a 4th degree polynomial
Packit 67cb25
 *       equation to be solved. The equation is:
Packit 67cb25
 *
Packit 67cb25
 *         lambda^4  1
Packit 67cb25
 *       + lambda^3  2 tr(B)
Packit 67cb25
 *       + lambda^2  (tr(B)^2 + 2 det(B) - g^T g / delta^2)
Packit 67cb25
 *       + lambda^1  (2 det(B) tr(B) - 2 g^T adj(B)^T g / delta^2)
Packit 67cb25
 *       + lambda^0  (det(B)^2 - g^T adj(B)^T adj(B) g / delta^2)
Packit 67cb25
 *
Packit 67cb25
 *       where adj(B) is the adjugate matrix of B.
Packit 67cb25
 *
Packit 67cb25
 *       We then check each of the 4 solutions for lambda to determine which
Packit 67cb25
 *       lambda results in the smallest objective function value. This x
Packit 67cb25
 *       is then used to construct the final step: dx = Q*x
Packit 67cb25
 */
Packit 67cb25
Packit 67cb25
typedef struct
Packit 67cb25
{
Packit 67cb25
  size_t n;                  /* number of observations */
Packit 67cb25
  size_t p;                  /* number of parameters */
Packit 67cb25
  gsl_vector *dx_gn;         /* Gauss-Newton step, size p */
Packit 67cb25
  gsl_vector *dx_sd;         /* steepest descent step, size p */
Packit 67cb25
  double norm_Dgn;           /* || D dx_gn || */
Packit 67cb25
  double norm_Dsd;           /* || D dx_sd || */
Packit 67cb25
  gsl_vector *workp;         /* workspace, length p */
Packit 67cb25
  gsl_vector *workn;         /* workspace, length n */
Packit 67cb25
  gsl_matrix *W;             /* orthonormal basis for 2D subspace, p-by-2 */
Packit 67cb25
  gsl_matrix *JQ;            /* J * Q, n-by-p */
Packit 67cb25
  gsl_vector *tau;           /* Householder scalars */
Packit 67cb25
  gsl_vector *subg;          /* subspace gradient = W^T g, 2-by-1 */
Packit 67cb25
  gsl_matrix *subB;          /* subspace Hessian = W^T B W, 2-by-2 */
Packit 67cb25
  gsl_permutation *perm;     /* permutation matrix */
Packit 67cb25
Packit 67cb25
  double trB;                /* Tr(subB) */
Packit 67cb25
  double detB;               /* det(subB) */
Packit 67cb25
  double normg;              /* || subg || */
Packit 67cb25
  double term0;              /* g^T adj(B)^T adj(B) g */
Packit 67cb25
  double term1;              /* g^T adj(B)^T g */
Packit 67cb25
Packit 67cb25
  size_t rank;               /* rank of [ dx_sd, dx_gn ] matrix */
Packit 67cb25
Packit 67cb25
  gsl_poly_complex_workspace *poly_p;
Packit 67cb25
Packit 67cb25
  /* tunable parameters */
Packit 67cb25
  gsl_multifit_nlinear_parameters params;
Packit 67cb25
} subspace2D_state_t;
Packit 67cb25
Packit 67cb25
#include "common.c"
Packit 67cb25
Packit 67cb25
static void * subspace2D_alloc (const void * params, const size_t n, const size_t p);
Packit 67cb25
static void subspace2D_free(void *vstate);
Packit 67cb25
static int subspace2D_init(const void *vtrust_state, void *vstate);
Packit 67cb25
static int subspace2D_preloop(const void * vtrust_state, void * vstate);
Packit 67cb25
static int subspace2D_step(const void * vtrust_state, const double delta,
Packit 67cb25
                           gsl_vector * dx, void * vstate);
Packit 67cb25
static int subspace2D_preduction(const void * vtrust_state, const gsl_vector * dx,
Packit 67cb25
                                 double * pred, void * vstate);
Packit 67cb25
static int subspace2D_solution(const double lambda, gsl_vector * x,
Packit 67cb25
                               subspace2D_state_t * state);
Packit 67cb25
static double subspace2D_objective(const gsl_vector * x, subspace2D_state_t * state);
Packit 67cb25
static int subspace2D_calc_gn(const gsl_multifit_nlinear_trust_state * trust_state, gsl_vector * dx);
Packit 67cb25
static int subspace2D_calc_sd(const gsl_multifit_nlinear_trust_state * trust_state, gsl_vector * dx,
Packit 67cb25
                              subspace2D_state_t * state);
Packit 67cb25
Packit 67cb25
static void *
Packit 67cb25
subspace2D_alloc (const void * params, const size_t n, const size_t p)
Packit 67cb25
{
Packit 67cb25
  const gsl_multifit_nlinear_parameters *par = (const gsl_multifit_nlinear_parameters *) params;
Packit 67cb25
  subspace2D_state_t *state;
Packit 67cb25
  
Packit 67cb25
  state = calloc(1, sizeof(subspace2D_state_t));
Packit 67cb25
  if (state == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate subspace2D state", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->dx_gn = gsl_vector_alloc(p);
Packit 67cb25
  if (state->dx_gn == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate space for dx_gn", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->dx_sd = gsl_vector_alloc(p);
Packit 67cb25
  if (state->dx_sd == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate space for dx_sd", 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", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->workn = gsl_vector_alloc(n);
Packit 67cb25
  if (state->workn == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate space for workn", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->W = gsl_matrix_alloc(p, 2);
Packit 67cb25
  if (state->W == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate space for W", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->JQ = gsl_matrix_alloc(n, p);
Packit 67cb25
  if (state->JQ == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate space for JQ", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->tau = gsl_vector_alloc(2);
Packit 67cb25
  if (state->tau == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate space for tau", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->subg = gsl_vector_alloc(2);
Packit 67cb25
  if (state->subg == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate space for subg", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->subB = gsl_matrix_alloc(2, 2);
Packit 67cb25
  if (state->subB == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate space for subB", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->perm = gsl_permutation_alloc(2);
Packit 67cb25
  if (state->perm == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate space for perm", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->poly_p = gsl_poly_complex_workspace_alloc(5);
Packit 67cb25
  if (state->poly_p == NULL)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_NULL ("failed to allocate space for poly workspace", GSL_ENOMEM);
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  state->n = n;
Packit 67cb25
  state->p = p;
Packit 67cb25
  state->rank = 0;
Packit 67cb25
  state->params = *par;
Packit 67cb25
Packit 67cb25
  return state;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
static void
Packit 67cb25
subspace2D_free(void *vstate)
Packit 67cb25
{
Packit 67cb25
  subspace2D_state_t *state = (subspace2D_state_t *) vstate;
Packit 67cb25
Packit 67cb25
  if (state->dx_gn)
Packit 67cb25
    gsl_vector_free(state->dx_gn);
Packit 67cb25
Packit 67cb25
  if (state->dx_sd)
Packit 67cb25
    gsl_vector_free(state->dx_sd);
Packit 67cb25
Packit 67cb25
  if (state->workp)
Packit 67cb25
    gsl_vector_free(state->workp);
Packit 67cb25
Packit 67cb25
  if (state->workn)
Packit 67cb25
    gsl_vector_free(state->workn);
Packit 67cb25
Packit 67cb25
  if (state->W)
Packit 67cb25
    gsl_matrix_free(state->W);
Packit 67cb25
Packit 67cb25
  if (state->JQ)
Packit 67cb25
    gsl_matrix_free(state->JQ);
Packit 67cb25
Packit 67cb25
  if (state->tau)
Packit 67cb25
    gsl_vector_free(state->tau);
Packit 67cb25
Packit 67cb25
  if (state->subg)
Packit 67cb25
    gsl_vector_free(state->subg);
Packit 67cb25
Packit 67cb25
  if (state->subB)
Packit 67cb25
    gsl_matrix_free(state->subB);
Packit 67cb25
Packit 67cb25
  if (state->perm)
Packit 67cb25
    gsl_permutation_free(state->perm);
Packit 67cb25
Packit 67cb25
  if (state->poly_p)
Packit 67cb25
    gsl_poly_complex_workspace_free(state->poly_p);
Packit 67cb25
Packit 67cb25
  free(state);
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
subspace2D_init()
Packit 67cb25
  Initialize subspace2D solver
Packit 67cb25
Packit 67cb25
Inputs: vtrust_state - trust state
Packit 67cb25
        vstate       - workspace
Packit 67cb25
Packit 67cb25
Return: success/error
Packit 67cb25
*/
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
subspace2D_init(const void *vtrust_state, void *vstate)
Packit 67cb25
{
Packit 67cb25
  (void)vtrust_state;
Packit 67cb25
  (void)vstate;
Packit 67cb25
Packit 67cb25
  return GSL_SUCCESS;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
subspace2D_preloop()
Packit 67cb25
  Initialize subspace2D method prior to iteration loop.
Packit 67cb25
This involves computing the Gauss-Newton step and
Packit 67cb25
steepest descent step
Packit 67cb25
Packit 67cb25
Notes: on output,
Packit 67cb25
1) state->dx_gn contains Gauss-Newton step
Packit 67cb25
2) state->dx_sd contains steepest descent step
Packit 67cb25
3) state->rank contains the rank([dx_sd, dx_gn])
Packit 67cb25
4) if full rank subspace (rank = 2), then:
Packit 67cb25
   state->trB = Tr(subB)
Packit 67cb25
   state->detB = det(subB)
Packit 67cb25
   state->normg = || subg ||
Packit 67cb25
*/
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
subspace2D_preloop(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
  subspace2D_state_t *state = (subspace2D_state_t *) vstate;
Packit 67cb25
  gsl_vector_view v;
Packit 67cb25
  double work_data[2];
Packit 67cb25
  gsl_vector_view work = gsl_vector_view_array(work_data, 2);
Packit 67cb25
  int signum;
Packit 67cb25
Packit 67cb25
  /* calculate Gauss-Newton step */
Packit 67cb25
  status = subspace2D_calc_gn(trust_state, state->dx_gn);
Packit 67cb25
  if (status)
Packit 67cb25
    return status;
Packit 67cb25
Packit 67cb25
  /* now calculate the steepest descent step */
Packit 67cb25
  status = subspace2D_calc_sd(trust_state, state->dx_sd, state);
Packit 67cb25
  if (status)
Packit 67cb25
    return status;
Packit 67cb25
Packit 67cb25
  /* store norms */
Packit 67cb25
  state->norm_Dgn = scaled_enorm(trust_state->diag, state->dx_gn);
Packit 67cb25
  state->norm_Dsd = scaled_enorm(trust_state->diag, state->dx_sd);
Packit 67cb25
Packit 67cb25
  /*
Packit 67cb25
   * now compute orthonormal basis for span(D dx_sd, D dx_gn) using
Packit 67cb25
   * QR decomposition; set W = [ D dx_sd, D dx_gn ] and normalize each
Packit 67cb25
   * column to unit magnitude. Then the Q matrix will form a basis for Col(W)
Packit 67cb25
   */
Packit 67cb25
Packit 67cb25
  v = gsl_matrix_column(state->W, 0);
Packit 67cb25
  gsl_vector_memcpy(&v.vector, state->dx_sd);
Packit 67cb25
  gsl_vector_mul(&v.vector, trust_state->diag);
Packit 67cb25
  if (state->norm_Dsd != 0)
Packit 67cb25
    gsl_vector_scale(&v.vector, 1.0 / state->norm_Dsd);
Packit 67cb25
Packit 67cb25
  v = gsl_matrix_column(state->W, 1);
Packit 67cb25
  gsl_vector_memcpy(&v.vector, state->dx_gn);
Packit 67cb25
  gsl_vector_mul(&v.vector, trust_state->diag);
Packit 67cb25
  if (state->norm_Dgn != 0)
Packit 67cb25
    gsl_vector_scale(&v.vector, 1.0 / state->norm_Dgn);
Packit 67cb25
Packit 67cb25
  /* use a rank revealing QR decomposition in case dx_sd and dx_gn
Packit 67cb25
   * are parallel */
Packit 67cb25
  gsl_linalg_QRPT_decomp(state->W, state->tau, state->perm, &signum, &work.vector);
Packit 67cb25
Packit 67cb25
  /* check for parallel dx_sd, dx_gn, in which case rank will be 1 */
Packit 67cb25
  state->rank = gsl_linalg_QRPT_rank(state->W, -1.0);
Packit 67cb25
Packit 67cb25
  if (state->rank == 2)
Packit 67cb25
    {
Packit 67cb25
      /*
Packit 67cb25
       * full rank subspace, compute:
Packit 67cb25
       * subg = Q^T D^{-1} g
Packit 67cb25
       * subB = Q^T D^{-1} B D^{-1} Q where B = J^T J
Packit 67cb25
       */
Packit 67cb25
      const size_t p = state->p;
Packit 67cb25
      size_t i;
Packit 67cb25
      gsl_matrix_view JQ = gsl_matrix_submatrix(state->JQ, 0, 0, state->n, GSL_MIN(2, p));
Packit 67cb25
      double B00, B10, B11, g0, g1;
Packit 67cb25
Packit 67cb25
      /* compute subg */
Packit 67cb25
      gsl_vector_memcpy(state->workp, trust_state->g);
Packit 67cb25
      gsl_vector_div(state->workp, trust_state->diag);
Packit 67cb25
      gsl_linalg_QR_QTvec(state->W, state->tau, state->workp);
Packit 67cb25
Packit 67cb25
      g0 = gsl_vector_get(state->workp, 0);
Packit 67cb25
      g1 = gsl_vector_get(state->workp, 1);
Packit 67cb25
Packit 67cb25
      gsl_vector_set(state->subg, 0, g0);
Packit 67cb25
      gsl_vector_set(state->subg, 1, g1);
Packit 67cb25
Packit 67cb25
      /* compute subB */
Packit 67cb25
Packit 67cb25
      /* compute J D^{-1} */
Packit 67cb25
      gsl_matrix_memcpy(state->JQ, trust_state->J);
Packit 67cb25
Packit 67cb25
      for (i = 0; i < p; ++i)
Packit 67cb25
        {
Packit 67cb25
          gsl_vector_view c = gsl_matrix_column(state->JQ, i);
Packit 67cb25
          double di = gsl_vector_get(trust_state->diag, i);
Packit 67cb25
          gsl_vector_scale(&c.vector, 1.0 / di);
Packit 67cb25
        }
Packit 67cb25
Packit 67cb25
      /* compute J D^{-1} Q */
Packit 67cb25
      gsl_linalg_QR_matQ(state->W, state->tau, state->JQ);
Packit 67cb25
Packit 67cb25
      /* compute subB = Q^T D^{-1} J^T J D^{-1} Q */
Packit 67cb25
      gsl_blas_dsyrk(CblasLower, CblasTrans, 1.0, &JQ.matrix, 0.0, state->subB);
Packit 67cb25
Packit 67cb25
      B00 = gsl_matrix_get(state->subB, 0, 0);
Packit 67cb25
      B10 = gsl_matrix_get(state->subB, 1, 0);
Packit 67cb25
      B11 = gsl_matrix_get(state->subB, 1, 1);
Packit 67cb25
Packit 67cb25
      state->trB = B00 + B11;
Packit 67cb25
      state->detB = B00*B11 - B10*B10;
Packit 67cb25
      state->normg = gsl_blas_dnrm2(state->subg);
Packit 67cb25
Packit 67cb25
      /* g^T adj(B)^T adj(B) g */
Packit 67cb25
      state->term0 = (B10*B10 + B11*B11)*g0*g0 -
Packit 67cb25
                     2*B10*(B00 + B11)*g0*g1 +
Packit 67cb25
                     (B00*B00 + B10*B10)*g1*g1;
Packit 67cb25
Packit 67cb25
      /* g^T adj(B)^T g */
Packit 67cb25
      state->term1 = B11 * g0 * g0 + g1 * (B00*g1 - 2*B10*g0);
Packit 67cb25
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  return GSL_SUCCESS;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
subspace2D_step()
Packit 67cb25
  Calculate a new step with 2D subspace method. Based on [1]. We
Packit 67cb25
seek a vector dx in span{dx_gn, dx_sd} which minimizes the model
Packit 67cb25
function subject to ||dx|| <= delta
Packit 67cb25
*/
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
subspace2D_step(const void * vtrust_state, const double delta,
Packit 67cb25
                gsl_vector * dx, void * vstate)
Packit 67cb25
{
Packit 67cb25
  const gsl_multifit_nlinear_trust_state *trust_state =
Packit 67cb25
    (const gsl_multifit_nlinear_trust_state *) vtrust_state;
Packit 67cb25
  subspace2D_state_t *state = (subspace2D_state_t *) vstate;
Packit 67cb25
Packit 67cb25
  if (state->norm_Dgn <= delta)
Packit 67cb25
    {
Packit 67cb25
      /* Gauss-Newton step is inside trust region, use it as final step
Packit 67cb25
       * since it is the global minimizer of the quadratic model function */
Packit 67cb25
      gsl_vector_memcpy(dx, state->dx_gn);
Packit 67cb25
    }
Packit 67cb25
  else if (state->rank < 2)
Packit 67cb25
    {
Packit 67cb25
      /* rank of [dx_sd, dx_gn] is 1, meaning dx_sd and dx_gn
Packit 67cb25
       * are parallel so we can't form a 2D subspace. Follow the steepest
Packit 67cb25
       * descent direction to the trust region boundary as our step */
Packit 67cb25
      gsl_vector_memcpy(dx, state->dx_sd);
Packit 67cb25
      gsl_vector_scale(dx, delta / state->norm_Dsd);
Packit 67cb25
    }
Packit 67cb25
  else
Packit 67cb25
    {
Packit 67cb25
      int status;
Packit 67cb25
      const double delta_sq = delta * delta;
Packit 67cb25
      double u = state->normg / delta;
Packit 67cb25
      double a[5];
Packit 67cb25
      double z[8];
Packit 67cb25
Packit 67cb25
#if 1
Packit 67cb25
      a[0] = state->detB * state->detB - state->term0 / delta_sq;
Packit 67cb25
      a[1] = 2 * state->detB * state->trB - 2 * state->term1 / delta_sq;
Packit 67cb25
      a[2] = state->trB * state->trB + 2 * state->detB - u * u;
Packit 67cb25
      a[3] = 2 * state->trB;
Packit 67cb25
      a[4] = 1.0;
Packit 67cb25
#else
Packit 67cb25
      double TrB_D = state->trB * delta;
Packit 67cb25
      double detB_D = state->detB * delta;
Packit 67cb25
      double normg_sq = state->normg * state->normg;
Packit 67cb25
Packit 67cb25
      a[0] = detB_D * detB_D - state->term0;
Packit 67cb25
      a[1] = 2 * state->detB * state->trB * delta_sq - 2 * state->term1;
Packit 67cb25
      a[2] = TrB_D * TrB_D + 2 * state->detB * delta_sq - normg_sq;
Packit 67cb25
      a[3] = 2 * state->trB * delta_sq;
Packit 67cb25
      a[4] = delta_sq;
Packit 67cb25
#endif
Packit 67cb25
Packit 67cb25
      status = gsl_poly_complex_solve(a, 5, state->poly_p, z);
Packit 67cb25
      if (status == GSL_SUCCESS)
Packit 67cb25
        {
Packit 67cb25
          size_t i;
Packit 67cb25
          double min = 0.0;
Packit 67cb25
          int mini = -1;
Packit 67cb25
          double x_data[2];
Packit 67cb25
          gsl_vector_view x = gsl_vector_view_array(x_data, 2);
Packit 67cb25
Packit 67cb25
          /*
Packit 67cb25
           * loop through all four values of the Lagrange multiplier
Packit 67cb25
           * lambda. For each lambda, evaluate the objective function
Packit 67cb25
           * with Re(lambda) to determine which lambda minimizes the
Packit 67cb25
           * function
Packit 67cb25
           */
Packit 67cb25
          for (i = 0; i < 4; ++i)
Packit 67cb25
            {
Packit 67cb25
              double cost, normx;
Packit 67cb25
Packit 67cb25
              /*fprintf(stderr, "root: %.12e + %.12e i\n",
Packit 67cb25
                      z[2*i], z[2*i+1]);*/
Packit 67cb25
Packit 67cb25
              status = subspace2D_solution(z[2*i], &x.vector, state);
Packit 67cb25
              if (status != GSL_SUCCESS)
Packit 67cb25
                continue; /* singular matrix system */
Packit 67cb25
Packit 67cb25
              /* ensure ||x|| = delta */
Packit 67cb25
Packit 67cb25
              normx = gsl_blas_dnrm2(&x.vector);
Packit 67cb25
              if (normx == 0.0)
Packit 67cb25
                continue;
Packit 67cb25
Packit 67cb25
              gsl_vector_scale(&x.vector, delta / normx);
Packit 67cb25
Packit 67cb25
              /* evaluate objective function to determine minimizer */
Packit 67cb25
              cost = subspace2D_objective(&x.vector, state);
Packit 67cb25
              if (mini < 0 || cost < min)
Packit 67cb25
                {
Packit 67cb25
                  mini = (int) i;
Packit 67cb25
                  min = cost;
Packit 67cb25
                }
Packit 67cb25
            }
Packit 67cb25
Packit 67cb25
          if (mini < 0)
Packit 67cb25
            {
Packit 67cb25
              /* did not find minimizer - should not get here */
Packit 67cb25
              return GSL_FAILURE;
Packit 67cb25
            }
Packit 67cb25
          else
Packit 67cb25
            {
Packit 67cb25
              /* compute x which minimizes objective function */
Packit 67cb25
              subspace2D_solution(z[2*mini], &x.vector, state);
Packit 67cb25
Packit 67cb25
              /* dx = Q * x */
Packit 67cb25
              gsl_vector_set_zero(dx);
Packit 67cb25
              gsl_vector_set(dx, 0, gsl_vector_get(&x.vector, 0));
Packit 67cb25
              gsl_vector_set(dx, 1, gsl_vector_get(&x.vector, 1));
Packit 67cb25
              gsl_linalg_QR_Qvec(state->W, state->tau, dx);
Packit 67cb25
Packit 67cb25
              /* compute final dx by multiplying by D^{-1} */
Packit 67cb25
              gsl_vector_div(dx, trust_state->diag);
Packit 67cb25
            }
Packit 67cb25
        }
Packit 67cb25
      else
Packit 67cb25
        {
Packit 67cb25
          GSL_ERROR ("gsl_poly_complex_solve failed", status);
Packit 67cb25
        }
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  return GSL_SUCCESS;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
subspace2D_preduction(const void * vtrust_state, const gsl_vector * dx,
Packit 67cb25
                  double * pred, void * vstate)
Packit 67cb25
{
Packit 67cb25
  const gsl_multifit_nlinear_trust_state *trust_state =
Packit 67cb25
    (const gsl_multifit_nlinear_trust_state *) vtrust_state;
Packit 67cb25
  subspace2D_state_t *state = (subspace2D_state_t *) vstate;
Packit 67cb25
Packit 67cb25
  *pred = quadratic_preduction(trust_state->f, trust_state->J, dx, state->workn);
Packit 67cb25
Packit 67cb25
  return GSL_SUCCESS;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
/* solve 2D subspace problem: (B + lambda*I) x = -g */
Packit 67cb25
static int
Packit 67cb25
subspace2D_solution(const double lambda, gsl_vector * x,
Packit 67cb25
                    subspace2D_state_t * state)
Packit 67cb25
{
Packit 67cb25
  int status = GSL_SUCCESS;
Packit 67cb25
  double C_data[4];
Packit 67cb25
  gsl_matrix_view C = gsl_matrix_view_array(C_data, 2, 2);
Packit 67cb25
  double B00 = gsl_matrix_get(state->subB, 0, 0);
Packit 67cb25
  double B10 = gsl_matrix_get(state->subB, 1, 0);
Packit 67cb25
  double B11 = gsl_matrix_get(state->subB, 1, 1);
Packit 67cb25
Packit 67cb25
  /* construct C = B + lambda*I */
Packit 67cb25
  gsl_matrix_set(&C.matrix, 0, 0, B00 + lambda);
Packit 67cb25
  gsl_matrix_set(&C.matrix, 1, 0, B10);
Packit 67cb25
  gsl_matrix_set(&C.matrix, 0, 1, B10);
Packit 67cb25
  gsl_matrix_set(&C.matrix, 1, 1, B11 + lambda);
Packit 67cb25
Packit 67cb25
  /* use modified Cholesky in case C is not positive definite */
Packit 67cb25
  gsl_linalg_mcholesky_decomp(&C.matrix, state->perm, NULL);
Packit 67cb25
  gsl_linalg_mcholesky_solve(&C.matrix, state->perm, state->subg, x);
Packit 67cb25
Packit 67cb25
  gsl_vector_scale(x, -1.0);
Packit 67cb25
Packit 67cb25
  return status;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
/* evaluate 2D objective function: f(x) = g^T x + 1/2 x^T B x */
Packit 67cb25
static double
Packit 67cb25
subspace2D_objective(const gsl_vector * x, subspace2D_state_t * state)
Packit 67cb25
{
Packit 67cb25
  double f;
Packit 67cb25
  double y_data[2];
Packit 67cb25
  gsl_vector_view y = gsl_vector_view_array(y_data, 2);
Packit 67cb25
Packit 67cb25
  /* compute: y = g + 1/2 B x */
Packit 67cb25
  gsl_vector_memcpy(&y.vector, state->subg);
Packit 67cb25
  gsl_blas_dsymv(CblasLower, 0.5, state->subB, x, 1.0, &y.vector);
Packit 67cb25
Packit 67cb25
  /* compute: f = x^T ( g + 1/2 B x ) */
Packit 67cb25
  gsl_blas_ddot(x, &y.vector, &f);
Packit 67cb25
Packit 67cb25
  return f;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
subspace2D_calc_gn()
Packit 67cb25
  Calculate Gauss-Newton step which satisfies:
Packit 67cb25
Packit 67cb25
J dx_gn = -f
Packit 67cb25
Packit 67cb25
Inputs: trust_state - trust state variables
Packit 67cb25
        dx          - (output) Gauss-Newton step
Packit 67cb25
Packit 67cb25
Return: success/error
Packit 67cb25
*/
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
subspace2D_calc_gn(const gsl_multifit_nlinear_trust_state * trust_state, gsl_vector * dx)
Packit 67cb25
{
Packit 67cb25
  int status;
Packit 67cb25
  const gsl_multifit_nlinear_parameters *params = trust_state->params;
Packit 67cb25
Packit 67cb25
  /* initialize linear least squares solver */
Packit 67cb25
  status = (params->solver->init)(trust_state, trust_state->solver_state);
Packit 67cb25
  if (status)
Packit 67cb25
    return status;
Packit 67cb25
Packit 67cb25
  /* prepare the linear solver to compute Gauss-Newton step */
Packit 67cb25
  status = (params->solver->presolve)(0.0, trust_state, trust_state->solver_state);
Packit 67cb25
  if (status)
Packit 67cb25
    return status;
Packit 67cb25
Packit 67cb25
  /* solve: J dx_gn = -f for Gauss-Newton step */
Packit 67cb25
  status = (params->solver->solve)(trust_state->f,
Packit 67cb25
                                   dx,
Packit 67cb25
                                   trust_state,
Packit 67cb25
                                   trust_state->solver_state);
Packit 67cb25
  if (status)
Packit 67cb25
    return status;
Packit 67cb25
Packit 67cb25
  return GSL_SUCCESS;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
subspace2D_calc_sd()
Packit 67cb25
  Calculate steepest descent step,
Packit 67cb25
Packit 67cb25
dx_sd = - || D^{-1} g ||^2 / || J D^{-2} g ||^2 D^{-2} g
Packit 67cb25
Packit 67cb25
Inputs: trust_state - trust state variables
Packit 67cb25
        dx          - (output) steepest descent vector
Packit 67cb25
        state       - workspace
Packit 67cb25
Packit 67cb25
Return: success/error
Packit 67cb25
*/
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
subspace2D_calc_sd(const gsl_multifit_nlinear_trust_state * trust_state, gsl_vector * dx,
Packit 67cb25
                   subspace2D_state_t * state)
Packit 67cb25
{
Packit 67cb25
  double norm_Dinvg;   /* || D^{-1} g || */
Packit 67cb25
  double norm_JDinv2g; /* || J D^{-2} g || */
Packit 67cb25
  double alpha;        /* || D^{-1} g ||^2 / || J D^{-2} g ||^2 */
Packit 67cb25
  double u;
Packit 67cb25
Packit 67cb25
  /* compute workp = D^{-1} g and its norm */
Packit 67cb25
  gsl_vector_memcpy(state->workp, trust_state->g);
Packit 67cb25
  gsl_vector_div(state->workp, trust_state->diag);
Packit 67cb25
  norm_Dinvg = gsl_blas_dnrm2(state->workp);
Packit 67cb25
Packit 67cb25
  /* compute workp = D^{-2} g */
Packit 67cb25
  gsl_vector_div(state->workp, trust_state->diag);
Packit 67cb25
Packit 67cb25
  /* compute: workn = J D^{-2} g */
Packit 67cb25
  gsl_blas_dgemv(CblasNoTrans, 1.0, trust_state->J, state->workp, 0.0, state->workn);
Packit 67cb25
  norm_JDinv2g = gsl_blas_dnrm2(state->workn);
Packit 67cb25
Packit 67cb25
  u = norm_Dinvg / norm_JDinv2g;
Packit 67cb25
  alpha = u * u;
Packit 67cb25
Packit 67cb25
  /* dx_sd = -alpha D^{-2} g */
Packit 67cb25
  gsl_vector_memcpy(dx, state->workp);
Packit 67cb25
  gsl_vector_scale(dx, -alpha);
Packit 67cb25
Packit 67cb25
  return GSL_SUCCESS;
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
static const gsl_multifit_nlinear_trs subspace2D_type =
Packit 67cb25
{
Packit 67cb25
  "2D-subspace",
Packit 67cb25
  subspace2D_alloc,
Packit 67cb25
  subspace2D_init,
Packit 67cb25
  subspace2D_preloop,
Packit 67cb25
  subspace2D_step,
Packit 67cb25
  subspace2D_preduction,
Packit 67cb25
  subspace2D_free
Packit 67cb25
};
Packit 67cb25
Packit 67cb25
const gsl_multifit_nlinear_trs *gsl_multifit_nlinear_trs_subspace2D = &subspace2D_type;