blob: cd5588ac3e1904ce2c8dd939a61793f6840ccc15 [file] [log] [blame]
/* linalg/svd.c
*
* Copyright (C) 1996, 1997, 1998, 1999, 2000, 2004 Gerard Jungman, Brian Gough
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <config.h>
#include <stdlib.h>
#include <string.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_linalg.h>
#include "givens.c"
#include "svdstep.c"
/* Factorise a general M x N matrix A into,
*
* A = U D V^T
*
* where U is a column-orthogonal M x N matrix (U^T U = I),
* D is a diagonal N x N matrix,
* and V is an N x N orthogonal matrix (V^T V = V V^T = I)
*
* U is stored in the original matrix A, which has the same size
*
* V is stored as a separate matrix (not V^T). You must take the
* transpose to form the product above.
*
* The diagonal matrix D is stored in the vector S, D_ii = S_i
*/
int
gsl_linalg_SV_decomp (gsl_matrix * A, gsl_matrix * V, gsl_vector * S,
gsl_vector * work)
{
size_t a, b, i, j;
const size_t M = A->size1;
const size_t N = A->size2;
const size_t K = GSL_MIN (M, N);
if (M < N)
{
GSL_ERROR ("svd of MxN matrix, M<N, is not implemented", GSL_EUNIMPL);
}
else if (V->size1 != N)
{
GSL_ERROR ("square matrix V must match second dimension of matrix A",
GSL_EBADLEN);
}
else if (V->size1 != V->size2)
{
GSL_ERROR ("matrix V must be square", GSL_ENOTSQR);
}
else if (S->size != N)
{
GSL_ERROR ("length of vector S must match second dimension of matrix A",
GSL_EBADLEN);
}
else if (work->size != N)
{
GSL_ERROR ("length of workspace must match second dimension of matrix A",
GSL_EBADLEN);
}
/* Handle the case of N = 1 (SVD of a column vector) */
if (N == 1)
{
gsl_vector_view column = gsl_matrix_column (A, 0);
double norm = gsl_blas_dnrm2 (&column.vector);
gsl_vector_set (S, 0, norm);
gsl_matrix_set (V, 0, 0, 1.0);
if (norm != 0.0)
{
gsl_blas_dscal (1.0/norm, &column.vector);
}
return GSL_SUCCESS;
}
{
gsl_vector_view f = gsl_vector_subvector (work, 0, K - 1);
/* bidiagonalize matrix A, unpack A into U S V */
gsl_linalg_bidiag_decomp (A, S, &f.vector);
gsl_linalg_bidiag_unpack2 (A, S, &f.vector, V);
/* apply reduction steps to B=(S,Sd) */
chop_small_elements (S, &f.vector);
/* Progressively reduce the matrix until it is diagonal */
b = N - 1;
while (b > 0)
{
double fbm1 = gsl_vector_get (&f.vector, b - 1);
if (fbm1 == 0.0 || gsl_isnan (fbm1))
{
b--;
continue;
}
/* Find the largest unreduced block (a,b) starting from b
and working backwards */
a = b - 1;
while (a > 0)
{
double fam1 = gsl_vector_get (&f.vector, a - 1);
if (fam1 == 0.0 || gsl_isnan (fam1))
{
break;
}
a--;
}
{
const size_t n_block = b - a + 1;
gsl_vector_view S_block = gsl_vector_subvector (S, a, n_block);
gsl_vector_view f_block = gsl_vector_subvector (&f.vector, a, n_block - 1);
gsl_matrix_view U_block =
gsl_matrix_submatrix (A, 0, a, A->size1, n_block);
gsl_matrix_view V_block =
gsl_matrix_submatrix (V, 0, a, V->size1, n_block);
qrstep (&S_block.vector, &f_block.vector, &U_block.matrix, &V_block.matrix);
/* remove any small off-diagonal elements */
chop_small_elements (&S_block.vector, &f_block.vector);
}
}
}
/* Make singular values positive by reflections if necessary */
for (j = 0; j < K; j++)
{
double Sj = gsl_vector_get (S, j);
if (Sj < 0.0)
{
for (i = 0; i < N; i++)
{
double Vij = gsl_matrix_get (V, i, j);
gsl_matrix_set (V, i, j, -Vij);
}
gsl_vector_set (S, j, -Sj);
}
}
/* Sort singular values into decreasing order */
for (i = 0; i < K; i++)
{
double S_max = gsl_vector_get (S, i);
size_t i_max = i;
for (j = i + 1; j < K; j++)
{
double Sj = gsl_vector_get (S, j);
if (Sj > S_max)
{
S_max = Sj;
i_max = j;
}
}
if (i_max != i)
{
/* swap eigenvalues */
gsl_vector_swap_elements (S, i, i_max);
/* swap eigenvectors */
gsl_matrix_swap_columns (A, i, i_max);
gsl_matrix_swap_columns (V, i, i_max);
}
}
return GSL_SUCCESS;
}
/* Modified algorithm which is better for M>>N */
int
gsl_linalg_SV_decomp_mod (gsl_matrix * A,
gsl_matrix * X,
gsl_matrix * V, gsl_vector * S, gsl_vector * work)
{
size_t i, j;
const size_t M = A->size1;
const size_t N = A->size2;
if (M < N)
{
GSL_ERROR ("svd of MxN matrix, M<N, is not implemented", GSL_EUNIMPL);
}
else if (V->size1 != N)
{
GSL_ERROR ("square matrix V must match second dimension of matrix A",
GSL_EBADLEN);
}
else if (V->size1 != V->size2)
{
GSL_ERROR ("matrix V must be square", GSL_ENOTSQR);
}
else if (X->size1 != N)
{
GSL_ERROR ("square matrix X must match second dimension of matrix A",
GSL_EBADLEN);
}
else if (X->size1 != X->size2)
{
GSL_ERROR ("matrix X must be square", GSL_ENOTSQR);
}
else if (S->size != N)
{
GSL_ERROR ("length of vector S must match second dimension of matrix A",
GSL_EBADLEN);
}
else if (work->size != N)
{
GSL_ERROR ("length of workspace must match second dimension of matrix A",
GSL_EBADLEN);
}
if (N == 1)
{
gsl_vector_view column = gsl_matrix_column (A, 0);
double norm = gsl_blas_dnrm2 (&column.vector);
gsl_vector_set (S, 0, norm);
gsl_matrix_set (V, 0, 0, 1.0);
if (norm != 0.0)
{
gsl_blas_dscal (1.0/norm, &column.vector);
}
return GSL_SUCCESS;
}
/* Convert A into an upper triangular matrix R */
for (i = 0; i < N; i++)
{
gsl_vector_view c = gsl_matrix_column (A, i);
gsl_vector_view v = gsl_vector_subvector (&c.vector, i, M - i);
double tau_i = gsl_linalg_householder_transform (&v.vector);
/* Apply the transformation to the remaining columns */
if (i + 1 < N)
{
gsl_matrix_view m =
gsl_matrix_submatrix (A, i, i + 1, M - i, N - (i + 1));
gsl_linalg_householder_hm (tau_i, &v.vector, &m.matrix);
}
gsl_vector_set (S, i, tau_i);
}
/* Copy the upper triangular part of A into X */
for (i = 0; i < N; i++)
{
for (j = 0; j < i; j++)
{
gsl_matrix_set (X, i, j, 0.0);
}
{
double Aii = gsl_matrix_get (A, i, i);
gsl_matrix_set (X, i, i, Aii);
}
for (j = i + 1; j < N; j++)
{
double Aij = gsl_matrix_get (A, i, j);
gsl_matrix_set (X, i, j, Aij);
}
}
/* Convert A into an orthogonal matrix L */
for (j = N; j > 0 && j--;)
{
/* Householder column transformation to accumulate L */
double tj = gsl_vector_get (S, j);
gsl_matrix_view m = gsl_matrix_submatrix (A, j, j, M - j, N - j);
gsl_linalg_householder_hm1 (tj, &m.matrix);
}
/* unpack R into X V S */
gsl_linalg_SV_decomp (X, V, S, work);
/* Multiply L by X, to obtain U = L X, stored in U */
{
gsl_vector_view sum = gsl_vector_subvector (work, 0, N);
for (i = 0; i < M; i++)
{
gsl_vector_view L_i = gsl_matrix_row (A, i);
gsl_vector_set_zero (&sum.vector);
for (j = 0; j < N; j++)
{
double Lij = gsl_vector_get (&L_i.vector, j);
gsl_vector_view X_j = gsl_matrix_row (X, j);
gsl_blas_daxpy (Lij, &X_j.vector, &sum.vector);
}
gsl_vector_memcpy (&L_i.vector, &sum.vector);
}
}
return GSL_SUCCESS;
}
/* Solves the system A x = b using the SVD factorization
*
* A = U S V^T
*
* to obtain x. For M x N systems it finds the solution in the least
* squares sense.
*/
int
gsl_linalg_SV_solve (const gsl_matrix * U,
const gsl_matrix * V,
const gsl_vector * S,
const gsl_vector * b, gsl_vector * x)
{
if (U->size1 != b->size)
{
GSL_ERROR ("first dimension of matrix U must size of vector b",
GSL_EBADLEN);
}
else if (U->size2 != S->size)
{
GSL_ERROR ("length of vector S must match second dimension of matrix U",
GSL_EBADLEN);
}
else if (V->size1 != V->size2)
{
GSL_ERROR ("matrix V must be square", GSL_ENOTSQR);
}
else if (S->size != V->size1)
{
GSL_ERROR ("length of vector S must match size of matrix V",
GSL_EBADLEN);
}
else if (V->size2 != x->size)
{
GSL_ERROR ("size of matrix V must match size of vector x", GSL_EBADLEN);
}
else
{
const size_t N = U->size2;
size_t i;
gsl_vector *w = gsl_vector_calloc (N);
gsl_blas_dgemv (CblasTrans, 1.0, U, b, 0.0, w);
for (i = 0; i < N; i++)
{
double wi = gsl_vector_get (w, i);
double alpha = gsl_vector_get (S, i);
if (alpha != 0)
alpha = 1.0 / alpha;
gsl_vector_set (w, i, alpha * wi);
}
gsl_blas_dgemv (CblasNoTrans, 1.0, V, w, 0.0, x);
gsl_vector_free (w);
return GSL_SUCCESS;
}
}
/* This is a the jacobi version */
/* Author: G. Jungman */
/*
* Algorithm due to J.C. Nash, Compact Numerical Methods for
* Computers (New York: Wiley and Sons, 1979), chapter 3.
* See also Algorithm 4.1 in
* James Demmel, Kresimir Veselic, "Jacobi's Method is more
* accurate than QR", Lapack Working Note 15 (LAWN15), October 1989.
* Available from netlib.
*
* Based on code by Arthur Kosowsky, Rutgers University
* kosowsky@physics.rutgers.edu
*
* Another relevant paper is, P.P.M. De Rijk, "A One-Sided Jacobi
* Algorithm for computing the singular value decomposition on a
* vector computer", SIAM Journal of Scientific and Statistical
* Computing, Vol 10, No 2, pp 359-371, March 1989.
*
*/
int
gsl_linalg_SV_decomp_jacobi (gsl_matrix * A, gsl_matrix * Q, gsl_vector * S)
{
if (A->size1 < A->size2)
{
/* FIXME: only implemented M>=N case so far */
GSL_ERROR ("svd of MxN matrix, M<N, is not implemented", GSL_EUNIMPL);
}
else if (Q->size1 != A->size2)
{
GSL_ERROR ("square matrix Q must match second dimension of matrix A",
GSL_EBADLEN);
}
else if (Q->size1 != Q->size2)
{
GSL_ERROR ("matrix Q must be square", GSL_ENOTSQR);
}
else if (S->size != A->size2)
{
GSL_ERROR ("length of vector S must match second dimension of matrix A",
GSL_EBADLEN);
}
else
{
const size_t M = A->size1;
const size_t N = A->size2;
size_t i, j, k;
/* Initialize the rotation counter and the sweep counter. */
int count = 1;
int sweep = 0;
int sweepmax = 5*N;
double tolerance = 10 * M * GSL_DBL_EPSILON;
/* Always do at least 12 sweeps. */
sweepmax = GSL_MAX (sweepmax, 12);
/* Set Q to the identity matrix. */
gsl_matrix_set_identity (Q);
/* Store the column error estimates in S, for use during the
orthogonalization */
for (j = 0; j < N; j++)
{
gsl_vector_view cj = gsl_matrix_column (A, j);
double sj = gsl_blas_dnrm2 (&cj.vector);
gsl_vector_set(S, j, GSL_DBL_EPSILON * sj);
}
/* Orthogonalize A by plane rotations. */
while (count > 0 && sweep <= sweepmax)
{
/* Initialize rotation counter. */
count = N * (N - 1) / 2;
for (j = 0; j < N - 1; j++)
{
for (k = j + 1; k < N; k++)
{
double a = 0.0;
double b = 0.0;
double p = 0.0;
double q = 0.0;
double cosine, sine;
double v;
double abserr_a, abserr_b;
int sorted, orthog, noisya, noisyb;
gsl_vector_view cj = gsl_matrix_column (A, j);
gsl_vector_view ck = gsl_matrix_column (A, k);
gsl_blas_ddot (&cj.vector, &ck.vector, &p);
p *= 2.0 ; /* equation 9a: p = 2 x.y */
a = gsl_blas_dnrm2 (&cj.vector);
b = gsl_blas_dnrm2 (&ck.vector);
q = a * a - b * b;
v = hypot(p, q);
/* test for columns j,k orthogonal, or dominant errors */
abserr_a = gsl_vector_get(S,j);
abserr_b = gsl_vector_get(S,k);
sorted = (gsl_coerce_double(a) >= gsl_coerce_double(b));
orthog = (fabs (p) <= tolerance * gsl_coerce_double(a * b));
noisya = (a < abserr_a);
noisyb = (b < abserr_b);
if (sorted && (orthog || noisya || noisyb))
{
count--;
continue;
}
/* calculate rotation angles */
if (v == 0 || !sorted)
{
cosine = 0.0;
sine = 1.0;
}
else
{
cosine = sqrt((v + q) / (2.0 * v));
sine = p / (2.0 * v * cosine);
}
/* apply rotation to A */
for (i = 0; i < M; i++)
{
const double Aik = gsl_matrix_get (A, i, k);
const double Aij = gsl_matrix_get (A, i, j);
gsl_matrix_set (A, i, j, Aij * cosine + Aik * sine);
gsl_matrix_set (A, i, k, -Aij * sine + Aik * cosine);
}
gsl_vector_set(S, j, fabs(cosine) * abserr_a + fabs(sine) * abserr_b);
gsl_vector_set(S, k, fabs(sine) * abserr_a + fabs(cosine) * abserr_b);
/* apply rotation to Q */
for (i = 0; i < N; i++)
{
const double Qij = gsl_matrix_get (Q, i, j);
const double Qik = gsl_matrix_get (Q, i, k);
gsl_matrix_set (Q, i, j, Qij * cosine + Qik * sine);
gsl_matrix_set (Q, i, k, -Qij * sine + Qik * cosine);
}
}
}
/* Sweep completed. */
sweep++;
}
/*
* Orthogonalization complete. Compute singular values.
*/
{
double prev_norm = -1.0;
for (j = 0; j < N; j++)
{
gsl_vector_view column = gsl_matrix_column (A, j);
double norm = gsl_blas_dnrm2 (&column.vector);
/* Determine if singular value is zero, according to the
criteria used in the main loop above (i.e. comparison
with norm of previous column). */
if (norm == 0.0 || prev_norm == 0.0
|| (j > 0 && norm <= tolerance * prev_norm))
{
gsl_vector_set (S, j, 0.0); /* singular */
gsl_vector_set_zero (&column.vector); /* annihilate column */
prev_norm = 0.0;
}
else
{
gsl_vector_set (S, j, norm); /* non-singular */
gsl_vector_scale (&column.vector, 1.0 / norm); /* normalize column */
prev_norm = norm;
}
}
}
if (count > 0)
{
/* reached sweep limit */
GSL_ERROR ("Jacobi iterations did not reach desired tolerance",
GSL_ETOL);
}
return GSL_SUCCESS;
}
}