Blame multifit/gcv.c

Packit 67cb25
/* multifit/gcv.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
 * References:
Packit 67cb25
 *
Packit 67cb25
 * [1] P. C. Hansen, "Discrete Inverse Problems: Insight and Algorithms,"
Packit 67cb25
 * SIAM Press, 2010.
Packit 67cb25
 */
Packit 67cb25
Packit 67cb25
#include <config.h>
Packit 67cb25
#include <gsl/gsl_errno.h>
Packit 67cb25
#include <gsl/gsl_multifit.h>
Packit 67cb25
#include <gsl/gsl_blas.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_min.h>
Packit 67cb25
Packit 67cb25
typedef struct
Packit 67cb25
{
Packit 67cb25
  const gsl_vector * S;
Packit 67cb25
  const gsl_vector * UTy;
Packit 67cb25
  double delta0;
Packit 67cb25
  size_t np;
Packit 67cb25
  gsl_vector * workp;
Packit 67cb25
} gcv_params;
Packit 67cb25
Packit 67cb25
static double gcv_func(double lambda, void * params);
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
gsl_multifit_linear_gcv_init()
Packit 67cb25
  Initialize Generalized Cross Validation parameters
Packit 67cb25
Packit 67cb25
Inputs: y         - right hand side vector
Packit 67cb25
        reg_param - (output) regularization parameters
Packit 67cb25
        UTy       - (output) U^T y
Packit 67cb25
        delta0    - (output) delta0
Packit 67cb25
        work      - workspace
Packit 67cb25
*/
Packit 67cb25
Packit 67cb25
int
Packit 67cb25
gsl_multifit_linear_gcv_init(const gsl_vector * y,
Packit 67cb25
                             gsl_vector * reg_param,
Packit 67cb25
                             gsl_vector * UTy,
Packit 67cb25
                             double * delta0,
Packit 67cb25
                             gsl_multifit_linear_workspace * work)
Packit 67cb25
{
Packit 67cb25
  const size_t n = y->size;
Packit 67cb25
Packit 67cb25
  if (n != work->n)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR("y vector does not match workspace", GSL_EBADLEN);
Packit 67cb25
    }
Packit 67cb25
  else if (UTy->size != work->p)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR ("UTy vector does not match workspace", GSL_EBADLEN);
Packit 67cb25
    }
Packit 67cb25
  else
Packit 67cb25
    {
Packit 67cb25
      const size_t p = work->p;
Packit 67cb25
Packit 67cb25
      gsl_matrix_view U = gsl_matrix_submatrix(work->A, 0, 0, n, p);
Packit 67cb25
      gsl_vector_view S = gsl_vector_subvector(work->S, 0, p);
Packit 67cb25
Packit 67cb25
      const double smax = gsl_vector_get(&S.vector, 0);
Packit 67cb25
      const double smin = gsl_vector_get(&S.vector, p - 1);
Packit 67cb25
Packit 67cb25
      double dr; /* residual error from projection */
Packit 67cb25
Packit 67cb25
      double normy = gsl_blas_dnrm2(y);
Packit 67cb25
      double normUTy;
Packit 67cb25
Packit 67cb25
      /* compute projection UTy = U^T y */
Packit 67cb25
      gsl_blas_dgemv (CblasTrans, 1.0, &U.matrix, y, 0.0, UTy);
Packit 67cb25
      normUTy = gsl_blas_dnrm2(UTy);
Packit 67cb25
Packit 67cb25
      /* dr = ||y||^2 - ||U^T y||^2 */
Packit 67cb25
      dr = (normy + normUTy) * (normy - normUTy);
Packit 67cb25
Packit 67cb25
      /* calculate regularization parameters */
Packit 67cb25
      gsl_multifit_linear_lreg(smin, smax, reg_param);
Packit 67cb25
Packit 67cb25
      if (n > p && dr > 0.0)
Packit 67cb25
        *delta0 = dr;
Packit 67cb25
      else
Packit 67cb25
        *delta0 = 0.0;
Packit 67cb25
Packit 67cb25
      return GSL_SUCCESS;
Packit 67cb25
    }
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
gsl_multifit_linear_gcv_curve()
Packit 67cb25
  Calculate Generalized Cross Validation curve for a set
Packit 67cb25
of regularization parameters
Packit 67cb25
Packit 67cb25
Inputs: reg_param - regularization parameters
Packit 67cb25
        UTy       - U^T y vector, size p
Packit 67cb25
        delta0    - delta0
Packit 67cb25
        G         - (output) GCV curve values
Packit 67cb25
        work      - workspace
Packit 67cb25
*/
Packit 67cb25
Packit 67cb25
int
Packit 67cb25
gsl_multifit_linear_gcv_curve(const gsl_vector * reg_param,
Packit 67cb25
                              const gsl_vector * UTy,
Packit 67cb25
                              const double delta0,
Packit 67cb25
                              gsl_vector * G,
Packit 67cb25
                              gsl_multifit_linear_workspace * work)
Packit 67cb25
{
Packit 67cb25
  const size_t n = work->n;
Packit 67cb25
  const size_t p = work->p;
Packit 67cb25
  const size_t N = reg_param->size; /* number of points on GCV curve */
Packit 67cb25
Packit 67cb25
  if (UTy->size != p)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR("UTy vector does not match workspace", GSL_EBADLEN);
Packit 67cb25
    }
Packit 67cb25
  else if (G->size != N)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR ("size of reg_param and G vectors do not match",
Packit 67cb25
                 GSL_EBADLEN);
Packit 67cb25
    }
Packit 67cb25
  else
Packit 67cb25
    {
Packit 67cb25
      size_t i;
Packit 67cb25
Packit 67cb25
      gsl_vector_view S = gsl_vector_subvector(work->S, 0, p);
Packit 67cb25
      gsl_vector_view workp = gsl_matrix_subcolumn(work->QSI, 0, 0, p);
Packit 67cb25
Packit 67cb25
      gcv_params params;
Packit 67cb25
Packit 67cb25
      params.S = &S.vector;
Packit 67cb25
      params.UTy = UTy;
Packit 67cb25
      params.delta0 = delta0;
Packit 67cb25
      params.np = n - p;
Packit 67cb25
      params.workp = &workp.vector;
Packit 67cb25
Packit 67cb25
      for (i = 0; i < N; ++i)
Packit 67cb25
        {
Packit 67cb25
          double lambdai = gsl_vector_get(reg_param, i);
Packit 67cb25
          double Gi = gcv_func(lambdai, &params);
Packit 67cb25
Packit 67cb25
          gsl_vector_set(G, i, Gi);
Packit 67cb25
        }
Packit 67cb25
Packit 67cb25
      return GSL_SUCCESS;
Packit 67cb25
    }
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
gsl_multifit_linear_gcv_min()
Packit 67cb25
  Find regularization parameter which minimizes GCV curve
Packit 67cb25
Packit 67cb25
Inputs: reg_param - regularization parameters
Packit 67cb25
        UTy       - U^T y vector, size p
Packit 67cb25
        G         - GCV curve values
Packit 67cb25
        delta0    - delta0
Packit 67cb25
        lambda    - (output) optimal regularization parameter
Packit 67cb25
        work      - workspace
Packit 67cb25
*/
Packit 67cb25
Packit 67cb25
int
Packit 67cb25
gsl_multifit_linear_gcv_min(const gsl_vector * reg_param,
Packit 67cb25
                            const gsl_vector * UTy,
Packit 67cb25
                            const gsl_vector * G,
Packit 67cb25
                            const double delta0,
Packit 67cb25
                            double * lambda,
Packit 67cb25
                            gsl_multifit_linear_workspace * work)
Packit 67cb25
{
Packit 67cb25
  const size_t n = work->n;
Packit 67cb25
  const size_t p = work->p;
Packit 67cb25
  const size_t npts = reg_param->size; /* number of points on GCV curve */
Packit 67cb25
Packit 67cb25
  if (UTy->size != p)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR("UTy vector does not match workspace", GSL_EBADLEN);
Packit 67cb25
    }
Packit 67cb25
  else if (G->size != npts)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR ("size of reg_param and G vectors do not match",
Packit 67cb25
                 GSL_EBADLEN);
Packit 67cb25
    }
Packit 67cb25
  else
Packit 67cb25
    {
Packit 67cb25
      int status;
Packit 67cb25
      const size_t max_iter = 500;
Packit 67cb25
      const double tol = 1.0e-4;
Packit 67cb25
      gsl_vector_view S = gsl_vector_subvector(work->S, 0, p);
Packit 67cb25
      gsl_vector_view workp = gsl_matrix_subcolumn(work->QSI, 0, 0, p);
Packit 67cb25
      gcv_params params;
Packit 67cb25
      int idxG = (int) gsl_vector_min_index(G);
Packit 67cb25
      double a = gsl_vector_get(reg_param, GSL_MIN(idxG + 1, (int) npts - 1));
Packit 67cb25
      double b = gsl_vector_get(reg_param, GSL_MAX(idxG - 1, 0));
Packit 67cb25
      double m = gsl_vector_get(reg_param, idxG);
Packit 67cb25
      size_t iter = 0;
Packit 67cb25
      gsl_function F;
Packit 67cb25
Packit 67cb25
      /* XXX FIXME */
Packit 67cb25
      gsl_min_fminimizer *min_workspace_p;
Packit 67cb25
Packit 67cb25
      if (idxG == 0 || idxG == ((int)npts - 1))
Packit 67cb25
        {
Packit 67cb25
          /* the minimum is an endpoint of the curve, no need to search */
Packit 67cb25
          *lambda = m;
Packit 67cb25
          return GSL_SUCCESS;
Packit 67cb25
        }
Packit 67cb25
Packit 67cb25
      /* XXX FIXME */
Packit 67cb25
      min_workspace_p = gsl_min_fminimizer_alloc(gsl_min_fminimizer_brent);
Packit 67cb25
Packit 67cb25
      params.S = &S.vector;
Packit 67cb25
      params.UTy = UTy;
Packit 67cb25
      params.delta0 = delta0;
Packit 67cb25
      params.np = n - p;
Packit 67cb25
      params.workp = &workp.vector;
Packit 67cb25
Packit 67cb25
      F.function = gcv_func;
Packit 67cb25
      F.params = ¶m;;
Packit 67cb25
Packit 67cb25
      gsl_min_fminimizer_set(min_workspace_p, &F, m, a, b);
Packit 67cb25
Packit 67cb25
      do
Packit 67cb25
        {
Packit 67cb25
          iter++;
Packit 67cb25
          status = gsl_min_fminimizer_iterate(min_workspace_p);
Packit 67cb25
Packit 67cb25
          a = gsl_min_fminimizer_x_lower(min_workspace_p);
Packit 67cb25
          b = gsl_min_fminimizer_x_upper(min_workspace_p);
Packit 67cb25
Packit 67cb25
          status = gsl_min_test_interval(a, b, 0.0, tol);
Packit 67cb25
        }
Packit 67cb25
      while (status == GSL_CONTINUE && iter < max_iter);
Packit 67cb25
Packit 67cb25
      if (status == GSL_SUCCESS)
Packit 67cb25
        *lambda = gsl_min_fminimizer_minimum(min_workspace_p);
Packit 67cb25
      else
Packit 67cb25
        status = GSL_EMAXITER;
Packit 67cb25
Packit 67cb25
      gsl_min_fminimizer_free(min_workspace_p);
Packit 67cb25
Packit 67cb25
      return status;
Packit 67cb25
    }
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
gsl_multifit_linear_gcv_calc()
Packit 67cb25
  Calculate GCV function G(lambda) for given lambda
Packit 67cb25
Packit 67cb25
Inputs: reg_param - regularization parameters
Packit 67cb25
        UTy       - U^T y vector, size p
Packit 67cb25
        delta0    - delta0
Packit 67cb25
        G         - (output) GCV curve values
Packit 67cb25
        work      - workspace
Packit 67cb25
*/
Packit 67cb25
Packit 67cb25
double
Packit 67cb25
gsl_multifit_linear_gcv_calc(const double lambda,
Packit 67cb25
                             const gsl_vector * UTy,
Packit 67cb25
                             const double delta0,
Packit 67cb25
                             gsl_multifit_linear_workspace * work)
Packit 67cb25
{
Packit 67cb25
  const size_t n = work->n;
Packit 67cb25
  const size_t p = work->p;
Packit 67cb25
Packit 67cb25
  if (UTy->size != p)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR_VAL("UTy vector does not match workspace", GSL_EBADLEN, 0.0);
Packit 67cb25
    }
Packit 67cb25
  else
Packit 67cb25
    {
Packit 67cb25
      gsl_vector_view S = gsl_vector_subvector(work->S, 0, p);
Packit 67cb25
      gsl_vector_view workp = gsl_matrix_subcolumn(work->QSI, 0, 0, p);
Packit 67cb25
      gcv_params params;
Packit 67cb25
      double G;
Packit 67cb25
Packit 67cb25
      params.S = &S.vector;
Packit 67cb25
      params.UTy = UTy;
Packit 67cb25
      params.delta0 = delta0;
Packit 67cb25
      params.np = n - p;
Packit 67cb25
      params.workp = &workp.vector;
Packit 67cb25
Packit 67cb25
      G = gcv_func(lambda, &params);
Packit 67cb25
Packit 67cb25
      return G;
Packit 67cb25
    }
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
/*
Packit 67cb25
gsl_multifit_linear_gcv()
Packit 67cb25
  Calculate Generalized Cross Validation curve for a set
Packit 67cb25
of regularization parameters
Packit 67cb25
Packit 67cb25
Inputs: y         - right hand side vector
Packit 67cb25
        reg_param - (output) regularization parameters
Packit 67cb25
        G         - (output) GCV curve values
Packit 67cb25
        lambda    - (output) optimal regularization parameter which
Packit 67cb25
                    minimizes GCV curve
Packit 67cb25
        G_lambda  - (output) G(lambda) value at optimal parameter
Packit 67cb25
        work      - workspace
Packit 67cb25
*/
Packit 67cb25
Packit 67cb25
int
Packit 67cb25
gsl_multifit_linear_gcv(const gsl_vector * y,
Packit 67cb25
                        gsl_vector * reg_param,
Packit 67cb25
                        gsl_vector * G,
Packit 67cb25
                        double * lambda,
Packit 67cb25
                        double * G_lambda,
Packit 67cb25
                        gsl_multifit_linear_workspace * work)
Packit 67cb25
{
Packit 67cb25
  const size_t n = y->size;
Packit 67cb25
  const size_t N = G->size; /* number of points on GCV curve */
Packit 67cb25
Packit 67cb25
  if (n != work->n)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR("y vector does not match workspace", GSL_EBADLEN);
Packit 67cb25
    }
Packit 67cb25
  else if (reg_param->size != N)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR ("size of reg_param and G vectors do not match",
Packit 67cb25
                 GSL_EBADLEN);
Packit 67cb25
    }
Packit 67cb25
  else
Packit 67cb25
    {
Packit 67cb25
      int status;
Packit 67cb25
      const size_t p = work->p;
Packit 67cb25
      gsl_vector_view UTy = gsl_vector_subvector(work->xt, 0, p);
Packit 67cb25
      double delta0;
Packit 67cb25
Packit 67cb25
      status = gsl_multifit_linear_gcv_init(y, reg_param, &UTy.vector, &delta0, work);
Packit 67cb25
      if (status)
Packit 67cb25
        return status;
Packit 67cb25
Packit 67cb25
      status = gsl_multifit_linear_gcv_curve(reg_param, &UTy.vector, delta0, G, work);
Packit 67cb25
      if (status)
Packit 67cb25
        return status;
Packit 67cb25
Packit 67cb25
      status = gsl_multifit_linear_gcv_min(reg_param, &UTy.vector, G, delta0, lambda, work);
Packit 67cb25
      if (status)
Packit 67cb25
        return status;
Packit 67cb25
Packit 67cb25
      *G_lambda = gsl_multifit_linear_gcv_calc(*lambda, &UTy.vector, delta0, work);
Packit 67cb25
Packit 67cb25
      return GSL_SUCCESS;
Packit 67cb25
    }
Packit 67cb25
}
Packit 67cb25
Packit 67cb25
static double
Packit 67cb25
gcv_func(double lambda, void * params)
Packit 67cb25
{
Packit 67cb25
  gcv_params * par = (gcv_params *) params;
Packit 67cb25
  const gsl_vector *S = par->S;
Packit 67cb25
  const gsl_vector *UTy = par->UTy;
Packit 67cb25
  double delta0 = par->delta0;
Packit 67cb25
  size_t np = par->np;
Packit 67cb25
  gsl_vector *workp = par->workp;
Packit 67cb25
  const size_t p = S->size;
Packit 67cb25
  size_t i;
Packit 67cb25
  double lambda_sq = lambda * lambda;
Packit 67cb25
  double G, d, norm;
Packit 67cb25
  double sumf = 0.0;
Packit 67cb25
Packit 67cb25
  /* compute workp = 1 - filter_factors */
Packit 67cb25
  for (i = 0; i < p; ++i)
Packit 67cb25
    {
Packit 67cb25
      double si = gsl_vector_get(S, i);
Packit 67cb25
      double fi = lambda_sq / (si * si + lambda_sq);
Packit 67cb25
      gsl_vector_set(workp, i, fi);
Packit 67cb25
      sumf += fi;
Packit 67cb25
    }
Packit 67cb25
Packit 67cb25
  d = (double)np + sumf;
Packit 67cb25
Packit 67cb25
  gsl_vector_mul(workp, UTy);
Packit 67cb25
  norm = gsl_blas_dnrm2(workp);
Packit 67cb25
Packit 67cb25
  G = (norm*norm + delta0) / (d * d);
Packit 67cb25
Packit 67cb25
  return G;
Packit 67cb25
}