blob: bcd258916f556dc3407693e716a828d71e99cdaa [file] [log] [blame]
/* eigen/nonsymmv.c
*
* Copyright (C) 2006 Patrick Alken
*
* 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 <math.h>
#include <gsl/gsl_complex.h>
#include <gsl/gsl_complex_math.h>
#include <gsl/gsl_eigen.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_vector_complex.h>
#include <gsl/gsl_matrix.h>
/*
* This module computes the eigenvalues and eigenvectors of a real
* nonsymmetric matrix.
*
* This file contains routines based on original code from LAPACK
* which is distributed under the modified BSD license. The LAPACK
* routines used are DTREVC and DLALN2.
*/
#define GSL_NONSYMMV_SMLNUM (2.0 * GSL_DBL_MIN)
#define GSL_NONSYMMV_BIGNUM ((1.0 - GSL_DBL_EPSILON) / GSL_NONSYMMV_SMLNUM)
static void nonsymmv_get_right_eigenvectors(gsl_matrix *T, gsl_matrix *Z,
gsl_vector_complex *eval,
gsl_matrix_complex *evec,
gsl_eigen_nonsymmv_workspace *w);
static inline void nonsymmv_solve_equation(gsl_matrix *A, double z,
gsl_vector *b, gsl_vector *x,
double *s, double *xnorm,
double smin);
static inline void nonsymmv_solve_equation_z(gsl_matrix *A, gsl_complex *z,
gsl_vector_complex *b,
gsl_vector_complex *x,
double *s, double *xnorm,
double smin);
static void nonsymmv_normalize_eigenvectors(gsl_vector_complex *eval,
gsl_matrix_complex *evec);
/*
gsl_eigen_nonsymmv_alloc()
Allocate a workspace for solving the nonsymmetric eigenvalue problem.
The size of this workspace is O(5n).
Inputs: n - size of matrices
Return: pointer to workspace
*/
gsl_eigen_nonsymmv_workspace *
gsl_eigen_nonsymmv_alloc(const size_t n)
{
gsl_eigen_nonsymmv_workspace *w;
if (n == 0)
{
GSL_ERROR_NULL ("matrix dimension must be positive integer",
GSL_EINVAL);
}
w = (gsl_eigen_nonsymmv_workspace *)
malloc (sizeof (gsl_eigen_nonsymmv_workspace));
if (w == 0)
{
GSL_ERROR_NULL ("failed to allocate space for workspace", GSL_ENOMEM);
}
w->size = n;
w->Z = NULL;
w->nonsymm_workspace_p = gsl_eigen_nonsymm_alloc(n);
if (w->nonsymm_workspace_p == 0)
{
GSL_ERROR_NULL ("failed to allocate space for nonsymm workspace", GSL_ENOMEM);
}
/*
* set parameters to compute the full Schur form T and balance
* the matrices
*/
gsl_eigen_nonsymm_params(1, 1, w->nonsymm_workspace_p);
w->work = gsl_vector_alloc(n);
w->work2 = gsl_vector_alloc(n);
w->work3 = gsl_vector_alloc(n);
if (w->work == 0 || w->work2 == 0 || w->work3 == 0)
{
GSL_ERROR_NULL ("failed to allocate space for nonsymmv additional workspace", GSL_ENOMEM);
}
return (w);
} /* gsl_eigen_nonsymmv_alloc() */
/*
gsl_eigen_nonsymmv_free()
Free workspace w
*/
void
gsl_eigen_nonsymmv_free (gsl_eigen_nonsymmv_workspace * w)
{
gsl_eigen_nonsymm_free(w->nonsymm_workspace_p);
gsl_vector_free(w->work);
gsl_vector_free(w->work2);
gsl_vector_free(w->work3);
free(w);
} /* gsl_eigen_nonsymmv_free() */
/*
gsl_eigen_nonsymmv()
Solve the nonsymmetric eigensystem problem
A x = \lambda x
for the eigenvalues \lambda and right eigenvectors x
Inputs: A - general real matrix
eval - where to store eigenvalues
evec - where to store eigenvectors
w - workspace
Return: success or error
*/
int
gsl_eigen_nonsymmv (gsl_matrix * A, gsl_vector_complex * eval,
gsl_matrix_complex * evec,
gsl_eigen_nonsymmv_workspace * w)
{
const size_t N = A->size1;
/* check matrix and vector sizes */
if (N != A->size2)
{
GSL_ERROR ("matrix must be square to compute eigenvalues", GSL_ENOTSQR);
}
else if (eval->size != N)
{
GSL_ERROR ("eigenvalue vector must match matrix size", GSL_EBADLEN);
}
else if (evec->size1 != evec->size2)
{
GSL_ERROR ("eigenvector matrix must be square", GSL_ENOTSQR);
}
else if (evec->size1 != N)
{
GSL_ERROR ("eigenvector matrix has wrong size", GSL_EBADLEN);
}
else
{
int s;
gsl_matrix Z;
/*
* We need a place to store the Schur vectors, so we will
* treat evec as a real matrix and store them in the left
* half - the factor of 2 in the tda corresponds to the
* complex multiplicity
*/
Z.size1 = N;
Z.size2 = N;
Z.tda = 2 * N;
Z.data = evec->data;
Z.block = 0;
Z.owner = 0;
/* compute eigenvalues, Schur form, and Schur vectors */
s = gsl_eigen_nonsymm_Z(A, eval, &Z, w->nonsymm_workspace_p);
if (w->Z)
{
/*
* save the Schur vectors in user supplied matrix, since
* they will be destroyed when computing eigenvectors
*/
gsl_matrix_memcpy(w->Z, &Z);
}
/* only compute eigenvectors if we found all eigenvalues */
if (s == GSL_SUCCESS)
{
/* compute eigenvectors */
nonsymmv_get_right_eigenvectors(A, &Z, eval, evec, w);
/* normalize so that Euclidean norm is 1 */
nonsymmv_normalize_eigenvectors(eval, evec);
}
return s;
}
} /* gsl_eigen_nonsymmv() */
/*
gsl_eigen_nonsymmv_Z()
Compute eigenvalues and eigenvectors of a real nonsymmetric matrix
and also save the Schur vectors. See comments in gsl_eigen_nonsymm_Z
for more information.
Inputs: A - real nonsymmetric matrix
eval - where to store eigenvalues
evec - where to store eigenvectors
Z - where to store Schur vectors
w - nonsymmv workspace
Return: success or error
*/
int
gsl_eigen_nonsymmv_Z (gsl_matrix * A, gsl_vector_complex * eval,
gsl_matrix_complex * evec, gsl_matrix * Z,
gsl_eigen_nonsymmv_workspace * w)
{
/* check matrix and vector sizes */
if (A->size1 != A->size2)
{
GSL_ERROR ("matrix must be square to compute eigenvalues/eigenvectors", GSL_ENOTSQR);
}
else if (eval->size != A->size1)
{
GSL_ERROR ("eigenvalue vector must match matrix size", GSL_EBADLEN);
}
else if (evec->size1 != evec->size2)
{
GSL_ERROR ("eigenvector matrix must be square", GSL_ENOTSQR);
}
else if (evec->size1 != A->size1)
{
GSL_ERROR ("eigenvector matrix has wrong size", GSL_EBADLEN);
}
else if ((Z->size1 != Z->size2) || (Z->size1 != A->size1))
{
GSL_ERROR ("Z matrix has wrong dimensions", GSL_EBADLEN);
}
else
{
int s;
w->Z = Z;
s = gsl_eigen_nonsymmv(A, eval, evec, w);
w->Z = NULL;
return s;
}
} /* gsl_eigen_nonsymmv_Z() */
/********************************************
* INTERNAL ROUTINES *
********************************************/
/*
nonsymmv_get_right_eigenvectors()
Compute the right eigenvectors of the Schur form T and then
backtransform them using the Schur vectors to get right eigenvectors of
the original matrix.
Inputs: T - Schur form
Z - Schur vectors
eval - where to store eigenvalues (to ensure that the
correct eigenvalue is stored in the same position
as the eigenvectors)
evec - where to store eigenvectors
w - nonsymmv workspace
Return: none
Notes: 1) based on LAPACK routine DTREVC - the algorithm used is
backsubstitution on the upper quasi triangular system T
followed by backtransformation by Z to get vectors of the
original matrix.
2) The Schur vectors in Z are destroyed and replaced with
eigenvectors stored with the same storage scheme as DTREVC.
The eigenvectors are also stored in 'evec'
3) The matrix T is unchanged on output
4) Each eigenvector is normalized so that the element of
largest magnitude has magnitude 1; here the magnitude of
a complex number (x,y) is taken to be |x| + |y|
*/
static void
nonsymmv_get_right_eigenvectors(gsl_matrix *T, gsl_matrix *Z,
gsl_vector_complex *eval,
gsl_matrix_complex *evec,
gsl_eigen_nonsymmv_workspace *w)
{
const size_t N = T->size1;
const double smlnum = GSL_DBL_MIN * N / GSL_DBL_EPSILON;
const double bignum = (1.0 - GSL_DBL_EPSILON) / smlnum;
int i; /* looping */
size_t iu, /* looping */
ju,
ii;
gsl_complex lambda; /* current eigenvalue */
double lambda_re, /* Re(lambda) */
lambda_im; /* Im(lambda) */
gsl_matrix_view Tv, /* temporary views */
Zv;
gsl_vector_view y, /* temporary views */
y2,
ev,
ev2;
double dat[4], /* scratch arrays */
dat_X[4];
double scale; /* scale factor */
double xnorm; /* |X| */
gsl_vector_complex_view ecol, /* column of evec */
ecol2;
int complex_pair; /* complex eigenvalue pair? */
double smin;
/*
* Compute 1-norm of each column of upper triangular part of T
* to control overflow in triangular solver
*/
gsl_vector_set(w->work3, 0, 0.0);
for (ju = 1; ju < N; ++ju)
{
gsl_vector_set(w->work3, ju, 0.0);
for (iu = 0; iu < ju; ++iu)
{
gsl_vector_set(w->work3, ju,
gsl_vector_get(w->work3, ju) +
fabs(gsl_matrix_get(T, iu, ju)));
}
}
for (i = (int) N - 1; i >= 0; --i)
{
iu = (size_t) i;
/* get current eigenvalue and store it in lambda */
lambda_re = gsl_matrix_get(T, iu, iu);
if (iu != 0 && gsl_matrix_get(T, iu, iu - 1) != 0.0)
{
lambda_im = sqrt(fabs(gsl_matrix_get(T, iu, iu - 1))) *
sqrt(fabs(gsl_matrix_get(T, iu - 1, iu)));
}
else
{
lambda_im = 0.0;
}
GSL_SET_COMPLEX(&lambda, lambda_re, lambda_im);
smin = GSL_MAX(GSL_DBL_EPSILON * (fabs(lambda_re) + fabs(lambda_im)),
smlnum);
smin = GSL_MAX(smin, GSL_NONSYMMV_SMLNUM);
if (lambda_im == 0.0)
{
int k, l;
gsl_vector_view bv, xv;
/* real eigenvector */
/*
* The ordering of eigenvalues in 'eval' is arbitrary and
* does not necessarily follow the Schur form T, so store
* lambda in the right slot in eval to ensure it corresponds
* to the eigenvector we are about to compute
*/
gsl_vector_complex_set(eval, iu, lambda);
/*
* We need to solve the system:
*
* (T(1:iu-1, 1:iu-1) - lambda*I)*X = -T(1:iu-1,iu)
*/
/* construct right hand side */
for (k = 0; k < i; ++k)
{
gsl_vector_set(w->work,
(size_t) k,
-gsl_matrix_get(T, (size_t) k, iu));
}
gsl_vector_set(w->work, iu, 1.0);
for (l = i - 1; l >= 0; --l)
{
size_t lu = (size_t) l;
if (lu == 0)
complex_pair = 0;
else
complex_pair = gsl_matrix_get(T, lu, lu - 1) != 0.0;
if (!complex_pair)
{
double x;
/*
* 1-by-1 diagonal block - solve the system:
*
* (T_{ll} - lambda)*x = -T_{l(iu)}
*/
Tv = gsl_matrix_submatrix(T, lu, lu, 1, 1);
bv = gsl_vector_view_array(dat, 1);
gsl_vector_set(&bv.vector, 0,
gsl_vector_get(w->work, lu));
xv = gsl_vector_view_array(dat_X, 1);
nonsymmv_solve_equation(&Tv.matrix,
lambda_re,
&bv.vector,
&xv.vector,
&scale,
&xnorm,
smin);
/* scale x to avoid overflow */
x = gsl_vector_get(&xv.vector, 0);
if (xnorm > 1.0)
{
if (gsl_vector_get(w->work3, lu) > bignum / xnorm)
{
x /= xnorm;
scale /= xnorm;
}
}
if (scale != 1.0)
{
gsl_vector_view wv;
wv = gsl_vector_subvector(w->work, 0, iu + 1);
gsl_blas_dscal(scale, &wv.vector);
}
gsl_vector_set(w->work, lu, x);
if (lu > 0)
{
gsl_vector_view v1, v2;
/* update right hand side */
v1 = gsl_matrix_column(T, lu);
v1 = gsl_vector_subvector(&v1.vector, 0, lu);
v2 = gsl_vector_subvector(w->work, 0, lu);
gsl_blas_daxpy(-x, &v1.vector, &v2.vector);
} /* if (l > 0) */
} /* if (!complex_pair) */
else
{
double x11, x21;
/*
* 2-by-2 diagonal block
*/
Tv = gsl_matrix_submatrix(T, lu - 1, lu - 1, 2, 2);
bv = gsl_vector_view_array(dat, 2);
gsl_vector_set(&bv.vector, 0,
gsl_vector_get(w->work, lu - 1));
gsl_vector_set(&bv.vector, 1,
gsl_vector_get(w->work, lu));
xv = gsl_vector_view_array(dat_X, 2);
nonsymmv_solve_equation(&Tv.matrix,
lambda_re,
&bv.vector,
&xv.vector,
&scale,
&xnorm,
smin);
/* scale X(1,1) and X(2,1) to avoid overflow */
x11 = gsl_vector_get(&xv.vector, 0);
x21 = gsl_vector_get(&xv.vector, 1);
if (xnorm > 1.0)
{
double beta;
beta = GSL_MAX(gsl_vector_get(w->work3, lu - 1),
gsl_vector_get(w->work3, lu));
if (beta > bignum / xnorm)
{
x11 /= xnorm;
x21 /= xnorm;
scale /= xnorm;
}
}
/* scale if necessary */
if (scale != 1.0)
{
gsl_vector_view wv;
wv = gsl_vector_subvector(w->work, 0, iu + 1);
gsl_blas_dscal(scale, &wv.vector);
}
gsl_vector_set(w->work, lu - 1, x11);
gsl_vector_set(w->work, lu, x21);
/* update right hand side */
if (lu > 1)
{
gsl_vector_view v1, v2;
v1 = gsl_matrix_column(T, lu - 1);
v1 = gsl_vector_subvector(&v1.vector, 0, lu - 1);
v2 = gsl_vector_subvector(w->work, 0, lu - 1);
gsl_blas_daxpy(-x11, &v1.vector, &v2.vector);
v1 = gsl_matrix_column(T, lu);
v1 = gsl_vector_subvector(&v1.vector, 0, lu - 1);
gsl_blas_daxpy(-x21, &v1.vector, &v2.vector);
}
--l;
} /* if (complex_pair) */
} /* for (l = i - 1; l >= 0; --l) */
/*
* At this point, w->work is an eigenvector of the
* Schur form T. To get an eigenvector of the original
* matrix, we multiply on the left by Z, the matrix of
* Schur vectors
*/
ecol = gsl_matrix_complex_column(evec, iu);
y = gsl_matrix_column(Z, iu);
if (iu > 0)
{
gsl_vector_view x;
Zv = gsl_matrix_submatrix(Z, 0, 0, N, iu);
x = gsl_vector_subvector(w->work, 0, iu);
/* compute Z * w->work and store it in Z(:,iu) */
gsl_blas_dgemv(CblasNoTrans,
1.0,
&Zv.matrix,
&x.vector,
gsl_vector_get(w->work, iu),
&y.vector);
} /* if (iu > 0) */
/* store eigenvector into evec */
ev = gsl_vector_complex_real(&ecol.vector);
ev2 = gsl_vector_complex_imag(&ecol.vector);
scale = 0.0;
for (ii = 0; ii < N; ++ii)
{
double a = gsl_vector_get(&y.vector, ii);
/* store real part of eigenvector */
gsl_vector_set(&ev.vector, ii, a);
/* set imaginary part to 0 */
gsl_vector_set(&ev2.vector, ii, 0.0);
if (fabs(a) > scale)
scale = fabs(a);
}
if (scale != 0.0)
scale = 1.0 / scale;
/* scale by magnitude of largest element */
gsl_blas_dscal(scale, &ev.vector);
} /* if (GSL_IMAG(lambda) == 0.0) */
else
{
gsl_vector_complex_view bv, xv;
size_t k;
int l;
gsl_complex lambda2;
/* complex eigenvector */
/*
* Store the complex conjugate eigenvalues in the right
* slots in eval
*/
GSL_SET_REAL(&lambda2, GSL_REAL(lambda));
GSL_SET_IMAG(&lambda2, -GSL_IMAG(lambda));
gsl_vector_complex_set(eval, iu - 1, lambda);
gsl_vector_complex_set(eval, iu, lambda2);
/*
* First solve:
*
* [ T(i:i+1,i:i+1) - lambda*I ] * X = 0
*/
if (fabs(gsl_matrix_get(T, iu - 1, iu)) >=
fabs(gsl_matrix_get(T, iu, iu - 1)))
{
gsl_vector_set(w->work, iu - 1, 1.0);
gsl_vector_set(w->work2, iu,
lambda_im / gsl_matrix_get(T, iu - 1, iu));
}
else
{
gsl_vector_set(w->work, iu - 1,
-lambda_im / gsl_matrix_get(T, iu, iu - 1));
gsl_vector_set(w->work2, iu, 1.0);
}
gsl_vector_set(w->work, iu, 0.0);
gsl_vector_set(w->work2, iu - 1, 0.0);
/* construct right hand side */
for (k = 0; k < iu - 1; ++k)
{
gsl_vector_set(w->work, k,
-gsl_vector_get(w->work, iu - 1) *
gsl_matrix_get(T, k, iu - 1));
gsl_vector_set(w->work2, k,
-gsl_vector_get(w->work2, iu) *
gsl_matrix_get(T, k, iu));
}
/*
* We must solve the upper quasi-triangular system:
*
* [ T(1:i-2,1:i-2) - lambda*I ] * X = s*(work + i*work2)
*/
for (l = i - 2; l >= 0; --l)
{
size_t lu = (size_t) l;
if (lu == 0)
complex_pair = 0;
else
complex_pair = gsl_matrix_get(T, lu, lu - 1) != 0.0;
if (!complex_pair)
{
gsl_complex bval;
gsl_complex x;
/*
* 1-by-1 diagonal block - solve the system:
*
* (T_{ll} - lambda)*x = work + i*work2
*/
Tv = gsl_matrix_submatrix(T, lu, lu, 1, 1);
bv = gsl_vector_complex_view_array(dat, 1);
xv = gsl_vector_complex_view_array(dat_X, 1);
GSL_SET_COMPLEX(&bval,
gsl_vector_get(w->work, lu),
gsl_vector_get(w->work2, lu));
gsl_vector_complex_set(&bv.vector, 0, bval);
nonsymmv_solve_equation_z(&Tv.matrix,
&lambda,
&bv.vector,
&xv.vector,
&scale,
&xnorm,
smin);
if (xnorm > 1.0)
{
if (gsl_vector_get(w->work3, lu) > bignum / xnorm)
{
gsl_blas_zdscal(1.0/xnorm, &xv.vector);
scale /= xnorm;
}
}
/* scale if necessary */
if (scale != 1.0)
{
gsl_vector_view wv;
wv = gsl_vector_subvector(w->work, 0, iu + 1);
gsl_blas_dscal(scale, &wv.vector);
wv = gsl_vector_subvector(w->work2, 0, iu + 1);
gsl_blas_dscal(scale, &wv.vector);
}
x = gsl_vector_complex_get(&xv.vector, 0);
gsl_vector_set(w->work, lu, GSL_REAL(x));
gsl_vector_set(w->work2, lu, GSL_IMAG(x));
/* update the right hand side */
if (lu > 0)
{
gsl_vector_view v1, v2;
v1 = gsl_matrix_column(T, lu);
v1 = gsl_vector_subvector(&v1.vector, 0, lu);
v2 = gsl_vector_subvector(w->work, 0, lu);
gsl_blas_daxpy(-GSL_REAL(x), &v1.vector, &v2.vector);
v2 = gsl_vector_subvector(w->work2, 0, lu);
gsl_blas_daxpy(-GSL_IMAG(x), &v1.vector, &v2.vector);
} /* if (lu > 0) */
} /* if (!complex_pair) */
else
{
gsl_complex b1, b2, x1, x2;
/*
* 2-by-2 diagonal block - solve the system
*/
Tv = gsl_matrix_submatrix(T, lu - 1, lu - 1, 2, 2);
bv = gsl_vector_complex_view_array(dat, 2);
xv = gsl_vector_complex_view_array(dat_X, 2);
GSL_SET_COMPLEX(&b1,
gsl_vector_get(w->work, lu - 1),
gsl_vector_get(w->work2, lu - 1));
GSL_SET_COMPLEX(&b2,
gsl_vector_get(w->work, lu),
gsl_vector_get(w->work2, lu));
gsl_vector_complex_set(&bv.vector, 0, b1);
gsl_vector_complex_set(&bv.vector, 1, b2);
nonsymmv_solve_equation_z(&Tv.matrix,
&lambda,
&bv.vector,
&xv.vector,
&scale,
&xnorm,
smin);
x1 = gsl_vector_complex_get(&xv.vector, 0);
x2 = gsl_vector_complex_get(&xv.vector, 1);
if (xnorm > 1.0)
{
double beta;
beta = GSL_MAX(gsl_vector_get(w->work3, lu - 1),
gsl_vector_get(w->work3, lu));
if (beta > bignum / xnorm)
{
gsl_blas_zdscal(1.0/xnorm, &xv.vector);
scale /= xnorm;
}
}
/* scale if necessary */
if (scale != 1.0)
{
gsl_vector_view wv;
wv = gsl_vector_subvector(w->work, 0, iu + 1);
gsl_blas_dscal(scale, &wv.vector);
wv = gsl_vector_subvector(w->work2, 0, iu + 1);
gsl_blas_dscal(scale, &wv.vector);
}
gsl_vector_set(w->work, lu - 1, GSL_REAL(x1));
gsl_vector_set(w->work, lu, GSL_REAL(x2));
gsl_vector_set(w->work2, lu - 1, GSL_IMAG(x1));
gsl_vector_set(w->work2, lu, GSL_IMAG(x2));
/* update right hand side */
if (lu > 1)
{
gsl_vector_view v1, v2, v3, v4;
v1 = gsl_matrix_column(T, lu - 1);
v1 = gsl_vector_subvector(&v1.vector, 0, lu - 1);
v4 = gsl_matrix_column(T, lu);
v4 = gsl_vector_subvector(&v4.vector, 0, lu - 1);
v2 = gsl_vector_subvector(w->work, 0, lu - 1);
v3 = gsl_vector_subvector(w->work2, 0, lu - 1);
gsl_blas_daxpy(-GSL_REAL(x1), &v1.vector, &v2.vector);
gsl_blas_daxpy(-GSL_REAL(x2), &v4.vector, &v2.vector);
gsl_blas_daxpy(-GSL_IMAG(x1), &v1.vector, &v3.vector);
gsl_blas_daxpy(-GSL_IMAG(x2), &v4.vector, &v3.vector);
} /* if (lu > 1) */
--l;
} /* if (complex_pair) */
} /* for (l = i - 2; l >= 0; --l) */
/*
* At this point, work + i*work2 is an eigenvector
* of T - backtransform to get an eigenvector of the
* original matrix
*/
y = gsl_matrix_column(Z, iu - 1);
y2 = gsl_matrix_column(Z, iu);
if (iu > 1)
{
gsl_vector_view x;
/* compute real part of eigenvectors */
Zv = gsl_matrix_submatrix(Z, 0, 0, N, iu - 1);
x = gsl_vector_subvector(w->work, 0, iu - 1);
gsl_blas_dgemv(CblasNoTrans,
1.0,
&Zv.matrix,
&x.vector,
gsl_vector_get(w->work, iu - 1),
&y.vector);
/* now compute the imaginary part */
x = gsl_vector_subvector(w->work2, 0, iu - 1);
gsl_blas_dgemv(CblasNoTrans,
1.0,
&Zv.matrix,
&x.vector,
gsl_vector_get(w->work2, iu),
&y2.vector);
}
else
{
gsl_blas_dscal(gsl_vector_get(w->work, iu - 1), &y.vector);
gsl_blas_dscal(gsl_vector_get(w->work2, iu), &y2.vector);
}
/*
* Now store the eigenvectors into evec - the real parts
* are Z(:,iu - 1) and the imaginary parts are
* +/- Z(:,iu)
*/
/* get views of the two eigenvector slots */
ecol = gsl_matrix_complex_column(evec, iu - 1);
ecol2 = gsl_matrix_complex_column(evec, iu);
/*
* save imaginary part first as it may get overwritten
* when copying the real part due to our storage scheme
* in Z/evec
*/
ev = gsl_vector_complex_imag(&ecol.vector);
ev2 = gsl_vector_complex_imag(&ecol2.vector);
scale = 0.0;
for (ii = 0; ii < N; ++ii)
{
double a = gsl_vector_get(&y2.vector, ii);
scale = GSL_MAX(scale,
fabs(a) + fabs(gsl_vector_get(&y.vector, ii)));
gsl_vector_set(&ev.vector, ii, a);
gsl_vector_set(&ev2.vector, ii, -a);
}
/* now save the real part */
ev = gsl_vector_complex_real(&ecol.vector);
ev2 = gsl_vector_complex_real(&ecol2.vector);
for (ii = 0; ii < N; ++ii)
{
double a = gsl_vector_get(&y.vector, ii);
gsl_vector_set(&ev.vector, ii, a);
gsl_vector_set(&ev2.vector, ii, a);
}
if (scale != 0.0)
scale = 1.0 / scale;
/* scale by largest element magnitude */
gsl_blas_zdscal(scale, &ecol.vector);
gsl_blas_zdscal(scale, &ecol2.vector);
/*
* decrement i since we took care of two eigenvalues at
* the same time
*/
--i;
} /* if (GSL_IMAG(lambda) != 0.0) */
} /* for (i = (int) N - 1; i >= 0; --i) */
} /* nonsymmv_get_right_eigenvectors() */
/*
nonsymmv_solve_equation()
Solve the equation which comes up in the back substitution
when computing eigenvectors corresponding to real eigenvalues.
The equation that is solved is:
(A - z*I)*x = s*b
where
A is n-by-n with n = 1 or 2
b and x are n-by-1 real vectors
s is a scaling factor set by this function to prevent overflow in x
Inputs: A - square matrix (n-by-n)
z - real scalar (eigenvalue)
b - right hand side vector
x - (output) where to store solution
s - (output) scale factor
xnorm - (output) infinity norm of X
smin - lower bound on singular values of A - if A - z*I
is less than this value, we'll use smin*I instead.
This value should be a safe distance above underflow.
Notes: 1) A and b are not changed on output
2) Based on lapack routine DLALN2
*/
static inline void
nonsymmv_solve_equation(gsl_matrix *A, double z, gsl_vector *b,
gsl_vector *x, double *s, double *xnorm,
double smin)
{
size_t N = A->size1;
double bnorm;
double scale = 1.0;
if (N == 1)
{
double c, /* denominator */
cnorm; /* |c| */
/*
* we have a 1-by-1 (real) scalar system to solve:
*
* (a - z)*x = b
* with z real
*/
/* c = a - z */
c = gsl_matrix_get(A, 0, 0) - z;
cnorm = fabs(c);
if (cnorm < smin)
{
/* set c = smin*I */
c = smin;
cnorm = smin;
}
/* check scaling for x = b / c */
bnorm = fabs(gsl_vector_get(b, 0));
if (cnorm < 1.0 && bnorm > 1.0)
{
if (bnorm > GSL_NONSYMMV_BIGNUM*cnorm)
scale = 1.0 / bnorm;
}
/* compute x */
gsl_vector_set(x, 0, gsl_vector_get(b, 0) * scale / c);
*xnorm = fabs(gsl_vector_get(x, 0));
} /* if (N == 1) */
else
{
double cr[2][2];
double *crv;
double cmax;
size_t icmax, j;
double bval1, bval2;
double ur11, ur12, ur22, ur11r;
double cr21, cr22;
double lr21;
double b1, b2, bbnd;
double x1, x2;
double temp;
size_t ipivot[4][4] = { { 0, 1, 2, 3 },
{ 1, 0, 3, 2 },
{ 2, 3, 0, 1 },
{ 3, 2, 1, 0 } };
int rswap[4] = { 0, 1, 0, 1 };
int zswap[4] = { 0, 0, 1, 1 };
/*
* we have a 2-by-2 real system to solve:
*
* [ A11 - z A12 ] [ x1 ] = [ b1 ]
* [ A21 A22 - z ] [ x2 ] [ b2 ]
*
* (z real)
*/
crv = (double *) cr;
/*
* compute the real part of C = A - z*I - use column ordering
* here since porting from lapack
*/
cr[0][0] = gsl_matrix_get(A, 0, 0) - z;
cr[1][1] = gsl_matrix_get(A, 1, 1) - z;
cr[0][1] = gsl_matrix_get(A, 1, 0);
cr[1][0] = gsl_matrix_get(A, 0, 1);
/* find the largest element in C */
cmax = 0.0;
icmax = 0;
for (j = 0; j < 4; ++j)
{
if (fabs(crv[j]) > cmax)
{
cmax = fabs(crv[j]);
icmax = j;
}
}
bval1 = gsl_vector_get(b, 0);
bval2 = gsl_vector_get(b, 1);
/* if norm(C) < smin, use smin*I */
if (cmax < smin)
{
bnorm = GSL_MAX(fabs(bval1), fabs(bval2));
if (smin < 1.0 && bnorm > 1.0)
{
if (bnorm > GSL_NONSYMMV_BIGNUM*smin)
scale = 1.0 / bnorm;
}
temp = scale / smin;
gsl_vector_set(x, 0, temp * bval1);
gsl_vector_set(x, 1, temp * bval2);
*xnorm = temp * bnorm;
*s = scale;
return;
}
/* gaussian elimination with complete pivoting */
ur11 = crv[icmax];
cr21 = crv[ipivot[1][icmax]];
ur12 = crv[ipivot[2][icmax]];
cr22 = crv[ipivot[3][icmax]];
ur11r = 1.0 / ur11;
lr21 = ur11r * cr21;
ur22 = cr22 - ur12 * lr21;
/* if smaller pivot < smin, use smin */
if (fabs(ur22) < smin)
ur22 = smin;
if (rswap[icmax])
{
b1 = bval2;
b2 = bval1;
}
else
{
b1 = bval1;
b2 = bval2;
}
b2 -= lr21 * b1;
bbnd = GSL_MAX(fabs(b1 * (ur22 * ur11r)), fabs(b2));
if (bbnd > 1.0 && fabs(ur22) < 1.0)
{
if (bbnd >= GSL_NONSYMMV_BIGNUM * fabs(ur22))
scale = 1.0 / bbnd;
}
x2 = (b2 * scale) / ur22;
x1 = (scale * b1) * ur11r - x2 * (ur11r * ur12);
if (zswap[icmax])
{
gsl_vector_set(x, 0, x2);
gsl_vector_set(x, 1, x1);
}
else
{
gsl_vector_set(x, 0, x1);
gsl_vector_set(x, 1, x2);
}
*xnorm = GSL_MAX(fabs(x1), fabs(x2));
/* further scaling if norm(A) norm(X) > overflow */
if (*xnorm > 1.0 && cmax > 1.0)
{
if (*xnorm > GSL_NONSYMMV_BIGNUM / cmax)
{
temp = cmax / GSL_NONSYMMV_BIGNUM;
gsl_blas_dscal(temp, x);
*xnorm *= temp;
scale *= temp;
}
}
} /* if (N == 2) */
*s = scale;
} /* nonsymmv_solve_equation() */
/*
nonsymmv_solve_equation_z()
Solve the equation which comes up in the back substitution
when computing eigenvectors corresponding to complex eigenvalues.
The equation that is solved is:
(A - z*I)*x = s*b
where
A is n-by-n with n = 1 or 2
b and x are n-by-1 complex vectors
s is a scaling factor set by this function to prevent overflow in x
Inputs: A - square matrix (n-by-n)
z - complex scalar (eigenvalue)
b - right hand side vector
x - (output) where to store solution
s - (output) scale factor
xnorm - (output) infinity norm of X
smin - lower bound on singular values of A - if A - z*I
is less than this value, we'll use smin*I instead.
This value should be a safe distance above underflow.
Notes: 1) A and b are not changed on output
2) Based on lapack routine DLALN2
*/
static inline void
nonsymmv_solve_equation_z(gsl_matrix *A, gsl_complex *z,
gsl_vector_complex *b, gsl_vector_complex *x,
double *s, double *xnorm, double smin)
{
size_t N = A->size1;
double scale = 1.0;
double bnorm;
if (N == 1)
{
double cr, /* denominator */
ci,
cnorm; /* |c| */
gsl_complex bval, c, xval, tmp;
/*
* we have a 1-by-1 (complex) scalar system to solve:
*
* (a - z)*x = b
* (z is complex, a is real)
*/
/* c = a - z */
cr = gsl_matrix_get(A, 0, 0) - GSL_REAL(*z);
ci = -GSL_IMAG(*z);
cnorm = fabs(cr) + fabs(ci);
if (cnorm < smin)
{
/* set c = smin*I */
cr = smin;
ci = 0.0;
cnorm = smin;
}
/* check scaling for x = b / c */
bval = gsl_vector_complex_get(b, 0);
bnorm = fabs(GSL_REAL(bval)) + fabs(GSL_IMAG(bval));
if (cnorm < 1.0 && bnorm > 1.0)
{
if (bnorm > GSL_NONSYMMV_BIGNUM*cnorm)
scale = 1.0 / bnorm;
}
/* compute x */
GSL_SET_COMPLEX(&tmp, scale*GSL_REAL(bval), scale*GSL_IMAG(bval));
GSL_SET_COMPLEX(&c, cr, ci);
xval = gsl_complex_div(tmp, c);
gsl_vector_complex_set(x, 0, xval);
*xnorm = fabs(GSL_REAL(xval)) + fabs(GSL_IMAG(xval));
} /* if (N == 1) */
else
{
double cr[2][2], ci[2][2];
double *civ, *crv;
double cmax;
gsl_complex bval1, bval2;
gsl_complex xval1, xval2;
double xr1, xi1;
size_t icmax;
size_t j;
double temp;
double ur11, ur12, ur22, ui11, ui12, ui22, ur11r, ui11r;
double ur12s, ui12s;
double u22abs;
double lr21, li21;
double cr21, cr22, ci21, ci22;
double br1, bi1, br2, bi2, bbnd;
gsl_complex b1, b2;
size_t ipivot[4][4] = { { 0, 1, 2, 3 },
{ 1, 0, 3, 2 },
{ 2, 3, 0, 1 },
{ 3, 2, 1, 0 } };
int rswap[4] = { 0, 1, 0, 1 };
int zswap[4] = { 0, 0, 1, 1 };
/*
* complex 2-by-2 system:
*
* [ A11 - z A12 ] [ X1 ] = [ B1 ]
* [ A21 A22 - z ] [ X2 ] [ B2 ]
*
* (z complex)
*
* where the X and B values are complex.
*/
civ = (double *) ci;
crv = (double *) cr;
/*
* compute the real part of C = A - z*I - use column ordering
* here since porting from lapack
*/
cr[0][0] = gsl_matrix_get(A, 0, 0) - GSL_REAL(*z);
cr[1][1] = gsl_matrix_get(A, 1, 1) - GSL_REAL(*z);
cr[0][1] = gsl_matrix_get(A, 1, 0);
cr[1][0] = gsl_matrix_get(A, 0, 1);
/* compute the imaginary part */
ci[0][0] = -GSL_IMAG(*z);
ci[0][1] = 0.0;
ci[1][0] = 0.0;
ci[1][1] = -GSL_IMAG(*z);
cmax = 0.0;
icmax = 0;
for (j = 0; j < 4; ++j)
{
if (fabs(crv[j]) + fabs(civ[j]) > cmax)
{
cmax = fabs(crv[j]) + fabs(civ[j]);
icmax = j;
}
}
bval1 = gsl_vector_complex_get(b, 0);
bval2 = gsl_vector_complex_get(b, 1);
/* if norm(C) < smin, use smin*I */
if (cmax < smin)
{
bnorm = GSL_MAX(fabs(GSL_REAL(bval1)) + fabs(GSL_IMAG(bval1)),
fabs(GSL_REAL(bval2)) + fabs(GSL_IMAG(bval2)));
if (smin < 1.0 && bnorm > 1.0)
{
if (bnorm > GSL_NONSYMMV_BIGNUM*smin)
scale = 1.0 / bnorm;
}
temp = scale / smin;
xval1 = gsl_complex_mul_real(bval1, temp);
xval2 = gsl_complex_mul_real(bval2, temp);
gsl_vector_complex_set(x, 0, xval1);
gsl_vector_complex_set(x, 1, xval2);
*xnorm = temp * bnorm;
*s = scale;
return;
}
/* gaussian elimination with complete pivoting */
ur11 = crv[icmax];
ui11 = civ[icmax];
cr21 = crv[ipivot[1][icmax]];
ci21 = civ[ipivot[1][icmax]];
ur12 = crv[ipivot[2][icmax]];
ui12 = civ[ipivot[2][icmax]];
cr22 = crv[ipivot[3][icmax]];
ci22 = civ[ipivot[3][icmax]];
if (icmax == 0 || icmax == 3)
{
/* off diagonals of pivoted C are real */
if (fabs(ur11) > fabs(ui11))
{
temp = ui11 / ur11;
ur11r = 1.0 / (ur11 * (1.0 + temp*temp));
ui11r = -temp * ur11r;
}
else
{
temp = ur11 / ui11;
ui11r = -1.0 / (ui11 * (1.0 + temp*temp));
ur11r = -temp*ui11r;
}
lr21 = cr21 * ur11r;
li21 = cr21 * ui11r;
ur12s = ur12 * ur11r;
ui12s = ur12 * ui11r;
ur22 = cr22 - ur12 * lr21;
ui22 = ci22 - ur12 * li21;
}
else
{
/* diagonals of pivoted C are real */
ur11r = 1.0 / ur11;
ui11r = 0.0;
lr21 = cr21 * ur11r;
li21 = ci21 * ur11r;
ur12s = ur12 * ur11r;
ui12s = ui12 * ur11r;
ur22 = cr22 - ur12 * lr21 + ui12 * li21;
ui22 = -ur12 * li21 - ui12 * lr21;
}
u22abs = fabs(ur22) + fabs(ui22);
/* if smaller pivot < smin, use smin */
if (u22abs < smin)
{
ur22 = smin;
ui22 = 0.0;
}
if (rswap[icmax])
{
br2 = GSL_REAL(bval1);
bi2 = GSL_IMAG(bval1);
br1 = GSL_REAL(bval2);
bi1 = GSL_IMAG(bval2);
}
else
{
br1 = GSL_REAL(bval1);
bi1 = GSL_IMAG(bval1);
br2 = GSL_REAL(bval2);
bi2 = GSL_IMAG(bval2);
}
br2 += li21*bi1 - lr21*br1;
bi2 -= li21*br1 + lr21*bi1;
bbnd = GSL_MAX((fabs(br1) + fabs(bi1)) *
(u22abs * (fabs(ur11r) + fabs(ui11r))),
fabs(br2) + fabs(bi2));
if (bbnd > 1.0 && u22abs < 1.0)
{
if (bbnd >= GSL_NONSYMMV_BIGNUM*u22abs)
{
scale = 1.0 / bbnd;
br1 *= scale;
bi1 *= scale;
br2 *= scale;
bi2 *= scale;
}
}
GSL_SET_COMPLEX(&b1, br2, bi2);
GSL_SET_COMPLEX(&b2, ur22, ui22);
xval2 = gsl_complex_div(b1, b2);
xr1 = ur11r*br1 - ui11r*bi1 - ur12s*GSL_REAL(xval2) + ui12s*GSL_IMAG(xval2);
xi1 = ui11r*br1 + ur11r*bi1 - ui12s*GSL_REAL(xval2) - ur12s*GSL_IMAG(xval2);
GSL_SET_COMPLEX(&xval1, xr1, xi1);
if (zswap[icmax])
{
gsl_vector_complex_set(x, 0, xval2);
gsl_vector_complex_set(x, 1, xval1);
}
else
{
gsl_vector_complex_set(x, 0, xval1);
gsl_vector_complex_set(x, 1, xval2);
}
*xnorm = GSL_MAX(fabs(GSL_REAL(xval1)) + fabs(GSL_IMAG(xval1)),
fabs(GSL_REAL(xval2)) + fabs(GSL_IMAG(xval2)));
/* further scaling if norm(A) norm(X) > overflow */
if (*xnorm > 1.0 && cmax > 1.0)
{
if (*xnorm > GSL_NONSYMMV_BIGNUM / cmax)
{
temp = cmax / GSL_NONSYMMV_BIGNUM;
gsl_blas_zdscal(temp, x);
*xnorm *= temp;
scale *= temp;
}
}
} /* if (N == 2) */
*s = scale;
} /* nonsymmv_solve_equation_z() */
/*
nonsymmv_normalize_eigenvectors()
Normalize eigenvectors so that their Euclidean norm is 1
Inputs: eval - eigenvalues
evec - eigenvectors
*/
static void
nonsymmv_normalize_eigenvectors(gsl_vector_complex *eval,
gsl_matrix_complex *evec)
{
const size_t N = evec->size1;
size_t i; /* looping */
gsl_complex ei;
gsl_vector_complex_view vi;
gsl_vector_view re, im;
double scale; /* scaling factor */
for (i = 0; i < N; ++i)
{
ei = gsl_vector_complex_get(eval, i);
vi = gsl_matrix_complex_column(evec, i);
re = gsl_vector_complex_real(&vi.vector);
if (GSL_IMAG(ei) == 0.0)
{
scale = 1.0 / gsl_blas_dnrm2(&re.vector);
gsl_blas_dscal(scale, &re.vector);
}
else if (GSL_IMAG(ei) > 0.0)
{
im = gsl_vector_complex_imag(&vi.vector);
scale = 1.0 / gsl_hypot(gsl_blas_dnrm2(&re.vector),
gsl_blas_dnrm2(&im.vector));
gsl_blas_zdscal(scale, &vi.vector);
vi = gsl_matrix_complex_column(evec, i + 1);
gsl_blas_zdscal(scale, &vi.vector);
}
}
} /* nonsymmv_normalize_eigenvectors() */