Blame multifit/linear_common.c

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_errno.h>
Packit 67cb25
#include <gsl/gsl_linalg.h>
Packit 67cb25
#include <gsl/gsl_blas.h>
Packit 67cb25
#include <gsl/gsl_multifit.h>
Packit 67cb25
Packit 67cb25
/* Fit
Packit 67cb25
 *
Packit 67cb25
 * y = X c
Packit 67cb25
 *
Packit 67cb25
 * where X is an n x p matrix of n observations for p variables.
Packit 67cb25
 *
Packit 67cb25
 * The solution includes a possible standard form Tikhonov regularization:
Packit 67cb25
 *
Packit 67cb25
 * c = (X^T X + lambda^2 I)^{-1} X^T y
Packit 67cb25
 *
Packit 67cb25
 * where lambda^2 is the Tikhonov regularization parameter.
Packit 67cb25
 *
Packit 67cb25
 * The function multifit_linear_svd() must first be called to
Packit 67cb25
 * compute the SVD decomposition of X
Packit 67cb25
 *
Packit 67cb25
 * Inputs: X        - least squares matrix
Packit 67cb25
 *         y        - right hand side vector
Packit 67cb25
 *         tol      - singular value tolerance
Packit 67cb25
 *         lambda   - Tikhonov regularization parameter lambda;
Packit 67cb25
 *                    ignored if <= 0
Packit 67cb25
 *         rank     - (output) effective rank
Packit 67cb25
 *         c        - (output) model coefficient vector
Packit 67cb25
 *         rnorm    - (output) residual norm ||y - X c||
Packit 67cb25
 *         snorm    - (output) solution norm ||c||
Packit 67cb25
 *         work     - workspace
Packit 67cb25
 *
Packit 67cb25
 * Notes:
Packit 67cb25
 * 1) The dimensions of X must match work->n and work->p which are set
Packit 67cb25
 *    by multifit_linear_svd()
Packit 67cb25
 * 2) On input:
Packit 67cb25
 *    work->A contains U
Packit 67cb25
 *    work->Q contains Q
Packit 67cb25
 *    work->S contains singular values
Packit 67cb25
 * 3) If this function is called from gsl_multifit_wlinear(), then
Packit 67cb25
 *    the input y points to work->t, which contains sqrt(W) y. Since
Packit 67cb25
 *    work->t is also used as scratch workspace by this function, we
Packit 67cb25
 *    do the necessary computations with y first to avoid problems.
Packit 67cb25
 * 4) When lambda <= 0, singular values are truncated when:
Packit 67cb25
 *    s_j <= tol * s_0
Packit 67cb25
 */
Packit 67cb25
Packit 67cb25
static int
Packit 67cb25
multifit_linear_solve (const gsl_matrix * X,
Packit 67cb25
                       const gsl_vector * y,
Packit 67cb25
                       const double tol,
Packit 67cb25
                       const double lambda,
Packit 67cb25
                       size_t * rank,
Packit 67cb25
                       gsl_vector * c,
Packit 67cb25
                       double *rnorm,
Packit 67cb25
                       double *snorm,
Packit 67cb25
                       gsl_multifit_linear_workspace * work)
Packit 67cb25
{
Packit 67cb25
  const size_t n = X->size1;
Packit 67cb25
  const size_t p = X->size2;
Packit 67cb25
Packit 67cb25
  if (n != work->n || p != work->p)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR("observation matrix does not match workspace", GSL_EBADLEN);
Packit 67cb25
    }
Packit 67cb25
  else if (n != y->size)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR("number of observations in y does not match matrix",
Packit 67cb25
                GSL_EBADLEN);
Packit 67cb25
    }
Packit 67cb25
  else if (p != c->size)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR ("number of parameters c does not match matrix",
Packit 67cb25
                 GSL_EBADLEN);
Packit 67cb25
    }
Packit 67cb25
  else if (tol <= 0)
Packit 67cb25
    {
Packit 67cb25
      GSL_ERROR ("tolerance must be positive", GSL_EINVAL);
Packit 67cb25
    }
Packit 67cb25
  else
Packit 67cb25
    {
Packit 67cb25
      const double lambda_sq = lambda * lambda;
Packit 67cb25
Packit 67cb25
      double rho_ls = 0.0;     /* contribution to rnorm from OLS */
Packit 67cb25
Packit 67cb25
      size_t j, p_eff;
Packit 67cb25
Packit 67cb25
      /* these inputs are previously computed by multifit_linear_svd() */
Packit 67cb25
      gsl_matrix_view A = gsl_matrix_submatrix(work->A, 0, 0, n, p);
Packit 67cb25
      gsl_matrix_view Q = gsl_matrix_submatrix(work->Q, 0, 0, p, p);
Packit 67cb25
      gsl_vector_view S = gsl_vector_subvector(work->S, 0, p);
Packit 67cb25
Packit 67cb25
      /* workspace */
Packit 67cb25
      gsl_matrix_view QSI = gsl_matrix_submatrix(work->QSI, 0, 0, p, p);
Packit 67cb25
      gsl_vector_view xt = gsl_vector_subvector(work->xt, 0, p);
Packit 67cb25
      gsl_vector_view D = gsl_vector_subvector(work->D, 0, p);
Packit 67cb25
      gsl_vector_view t = gsl_vector_subvector(work->t, 0, n);
Packit 67cb25
Packit 67cb25
      /*
Packit 67cb25
       * Solve y = A c for c
Packit 67cb25
       * c = Q diag(s_i / (s_i^2 + lambda_i^2)) U^T y
Packit 67cb25
       */
Packit 67cb25
Packit 67cb25
      /* compute xt = U^T y */
Packit 67cb25
      gsl_blas_dgemv (CblasTrans, 1.0, &A.matrix, y, 0.0, &xt.vector);
Packit 67cb25
Packit 67cb25
      if (n > p)
Packit 67cb25
        {
Packit 67cb25
          /*
Packit 67cb25
           * compute OLS residual norm = || y - U U^T y ||;
Packit 67cb25
           * for n = p, U U^T = I, so no need to calculate norm
Packit 67cb25
           */
Packit 67cb25
          gsl_vector_memcpy(&t.vector, y);
Packit 67cb25
          gsl_blas_dgemv(CblasNoTrans, -1.0, &A.matrix, &xt.vector, 1.0, &t.vector);
Packit 67cb25
          rho_ls = gsl_blas_dnrm2(&t.vector);
Packit 67cb25
        }
Packit 67cb25
Packit 67cb25
      if (lambda > 0.0)
Packit 67cb25
        {
Packit 67cb25
          /* xt <-- [ s(i) / (s(i)^2 + lambda^2) ] .* U^T y */
Packit 67cb25
          for (j = 0; j < p; ++j)
Packit 67cb25
            {
Packit 67cb25
              double sj = gsl_vector_get(&S.vector, j);
Packit 67cb25
              double f = (sj * sj) / (sj * sj + lambda_sq);
Packit 67cb25
              double *ptr = gsl_vector_ptr(&xt.vector, j);
Packit 67cb25
Packit 67cb25
              /* use D as workspace for residual norm */
Packit 67cb25
              gsl_vector_set(&D.vector, j, (1.0 - f) * (*ptr));
Packit 67cb25
Packit 67cb25
              *ptr *= sj / (sj*sj + lambda_sq);
Packit 67cb25
            }
Packit 67cb25
Packit 67cb25
          /* compute regularized solution vector */
Packit 67cb25
          gsl_blas_dgemv (CblasNoTrans, 1.0, &Q.matrix, &xt.vector, 0.0, c);
Packit 67cb25
Packit 67cb25
          /* compute solution norm */
Packit 67cb25
          *snorm = gsl_blas_dnrm2(c);
Packit 67cb25
Packit 67cb25
          /* compute residual norm */
Packit 67cb25
          *rnorm = gsl_blas_dnrm2(&D.vector);
Packit 67cb25
Packit 67cb25
          if (n > p)
Packit 67cb25
            {
Packit 67cb25
              /* add correction to residual norm (see eqs 6-7 of [1]) */
Packit 67cb25
              *rnorm = sqrt((*rnorm) * (*rnorm) + rho_ls * rho_ls);
Packit 67cb25
            }
Packit 67cb25
Packit 67cb25
          /* reset D vector */
Packit 67cb25
          gsl_vector_set_all(&D.vector, 1.0);
Packit 67cb25
        }
Packit 67cb25
      else
Packit 67cb25
        {
Packit 67cb25
          /* Scale the matrix Q, QSI = Q S^{-1} */
Packit 67cb25
Packit 67cb25
          gsl_matrix_memcpy (&QSI.matrix, &Q.matrix);
Packit 67cb25
Packit 67cb25
          {
Packit 67cb25
            double s0 = gsl_vector_get (&S.vector, 0);
Packit 67cb25
            p_eff = 0;
Packit 67cb25
Packit 67cb25
            for (j = 0; j < p; j++)
Packit 67cb25
              {
Packit 67cb25
                gsl_vector_view column = gsl_matrix_column (&QSI.matrix, j);
Packit 67cb25
                double sj = gsl_vector_get (&S.vector, j);
Packit 67cb25
                double alpha;
Packit 67cb25
Packit 67cb25
                if (sj <= tol * s0)
Packit 67cb25
                  {
Packit 67cb25
                    alpha = 0.0;
Packit 67cb25
                  }
Packit 67cb25
                else
Packit 67cb25
                  {
Packit 67cb25
                    alpha = 1.0 / sj;
Packit 67cb25
                    p_eff++;
Packit 67cb25
                  }
Packit 67cb25
Packit 67cb25
                gsl_vector_scale (&column.vector, alpha);
Packit 67cb25
              }
Packit 67cb25
Packit 67cb25
            *rank = p_eff;
Packit 67cb25
          }
Packit 67cb25
Packit 67cb25
          gsl_blas_dgemv (CblasNoTrans, 1.0, &QSI.matrix, &xt.vector, 0.0, c);
Packit 67cb25
Packit 67cb25
          /* Unscale the balancing factors */
Packit 67cb25
          gsl_vector_div (c, &D.vector);
Packit 67cb25
Packit 67cb25
          *snorm = gsl_blas_dnrm2(c);
Packit 67cb25
          *rnorm = rho_ls;
Packit 67cb25
        }
Packit 67cb25
Packit 67cb25
      return GSL_SUCCESS;
Packit 67cb25
    }
Packit 67cb25
}