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