|
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 |
}
|