blob: b2b7adb3ab3824993cca8c6542cbbc8eda929a7b [file] [log] [blame]
/* eigen/schur.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_eigen.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_linalg.h>
#include <gsl/gsl_cblas.h>
#include "schur.h"
/*
* This module contains some routines related to manipulating the
* Schur form of a matrix which are needed by the eigenvalue solvers
*
* This file contains routines based on original code from LAPACK
* which is distributed under the modified BSD license. The LAPACK
* routine used is DLANV2.
*/
static inline void schur_standard_form(gsl_matrix *A, gsl_complex *eval1,
gsl_complex *eval2, double *cs,
double *sn);
/*
gsl_schur_standardize()
Wrapper function for schur_standard_form - convert a 2-by-2 eigenvalue
block to standard form and then update the Schur form and
Schur vectors.
Inputs: T - Schur form
row - row of T of 2-by-2 block to be updated
eval1 - where to store eigenvalue 1
eval2 - where to store eigenvalue 2
update_t - 1 = update the entire matrix T with the transformation
0 = do not update rest of T
Z - (optional) if non-null, accumulate transformation
*/
void
gsl_schur_standardize(gsl_matrix *T, size_t row, gsl_complex *eval1,
gsl_complex *eval2, int update_t, gsl_matrix *Z)
{
const size_t N = T->size1;
gsl_matrix_view m;
double cs, sn;
m = gsl_matrix_submatrix(T, row, row, 2, 2);
schur_standard_form(&m.matrix, eval1, eval2, &cs, &sn);
if (update_t)
{
gsl_vector_view xv, yv, v;
/*
* The above call to schur_standard_form transformed a 2-by-2 block
* of T into upper triangular form via the transformation
*
* U = [ CS -SN ]
* [ SN CS ]
*
* The original matrix T was
*
* T = [ T_{11} | T_{12} | T_{13} ]
* [ 0* | A | T_{23} ]
* [ 0 | 0* | T_{33} ]
*
* where 0* indicates all zeros except for possibly
* one subdiagonal element next to A.
*
* After schur_standard_form, T looks like this:
*
* T = [ T_{11} | T_{12} | T_{13} ]
* [ 0* | U^t A U | T_{23} ]
* [ 0 | 0* | T_{33} ]
*
* since only the 2-by-2 block of A was changed. However,
* in order to be able to back transform T at the end,
* we need to apply the U transformation to the rest
* of the matrix T since there is no way to apply a
* similarity transformation to T and change only the
* middle 2-by-2 block. In other words, let
*
* M = [ I 0 0 ]
* [ 0 U 0 ]
* [ 0 0 I ]
*
* and compute
*
* M^t T M = [ T_{11} | T_{12} U | T_{13} ]
* [ U^t 0* | U^t A U | U^t T_{23} ]
* [ 0 | 0* U | T_{33} ]
*
* So basically we need to apply the transformation U
* to the i x 2 matrix T_{12} and the 2 x (n - i + 2)
* matrix T_{23}, where i is the index of the top of A
* in T.
*
* The BLAS routine drot() is suited for this.
*/
if (row < (N - 2))
{
/* transform the 2 rows of T_{23} */
v = gsl_matrix_row(T, row);
xv = gsl_vector_subvector(&v.vector,
row + 2,
N - row - 2);
v = gsl_matrix_row(T, row + 1);
yv = gsl_vector_subvector(&v.vector,
row + 2,
N - row - 2);
gsl_blas_drot(&xv.vector, &yv.vector, cs, sn);
}
if (row > 0)
{
/* transform the 2 columns of T_{12} */
v = gsl_matrix_column(T, row);
xv = gsl_vector_subvector(&v.vector,
0,
row);
v = gsl_matrix_column(T, row + 1);
yv = gsl_vector_subvector(&v.vector,
0,
row);
gsl_blas_drot(&xv.vector, &yv.vector, cs, sn);
}
} /* if (update_t) */
if (Z)
{
gsl_vector_view xv, yv;
/*
* Accumulate the transformation in Z. Here, Z -> Z * M
*
* So:
*
* Z -> [ Z_{11} | Z_{12} U | Z_{13} ]
* [ Z_{21} | Z_{22} U | Z_{23} ]
* [ Z_{31} | Z_{32} U | Z_{33} ]
*
* So we just need to apply drot() to the 2 columns
* starting at index 'row'
*/
xv = gsl_matrix_column(Z, row);
yv = gsl_matrix_column(Z, row + 1);
gsl_blas_drot(&xv.vector, &yv.vector, cs, sn);
} /* if (Z) */
} /* gsl_schur_standardize() */
/*******************************************************
* INTERNAL ROUTINES *
*******************************************************/
/*
schur_standard_form()
Compute the Schur factorization of a real 2-by-2 matrix in
standard form:
[ A B ] = [ CS -SN ] [ T11 T12 ] [ CS SN ]
[ C D ] [ SN CS ] [ T21 T22 ] [-SN CS ]
where either:
1) T21 = 0 so that T11 and T22 are real eigenvalues of the matrix, or
2) T11 = T22 and T21*T12 < 0, so that T11 +/- sqrt(|T21*T12|) are
complex conjugate eigenvalues
Inputs: A - 2-by-2 matrix
eval1 - where to store eigenvalue 1
eval2 - where to store eigenvalue 2
cs - where to store cosine parameter of rotation matrix
sn - where to store sine parameter of rotation matrix
Notes: based on LAPACK routine DLANV2
*/
static inline void
schur_standard_form(gsl_matrix *A, gsl_complex *eval1, gsl_complex *eval2,
double *cs, double *sn)
{
double a, b, c, d; /* input matrix values */
double tmp;
double p, z;
double bcmax, bcmis, scale;
double tau, sigma;
double cs1, sn1;
double aa, bb, cc, dd;
double sab, sac;
a = gsl_matrix_get(A, 0, 0);
b = gsl_matrix_get(A, 0, 1);
c = gsl_matrix_get(A, 1, 0);
d = gsl_matrix_get(A, 1, 1);
if (c == 0.0)
{
/*
* matrix is already upper triangular - set rotation matrix
* to the identity
*/
*cs = 1.0;
*sn = 0.0;
}
else if (b == 0.0)
{
/* swap rows and columns to make it upper triangular */
*cs = 0.0;
*sn = 1.0;
tmp = d;
d = a;
a = tmp;
b = -c;
c = 0.0;
}
else if (((a - d) == 0.0) && (GSL_SIGN(b) != GSL_SIGN(c)))
{
/* the matrix has complex eigenvalues with a == d */
*cs = 1.0;
*sn = 0.0;
}
else
{
tmp = a - d;
p = 0.5 * tmp;
bcmax = GSL_MAX(fabs(b), fabs(c));
bcmis = GSL_MIN(fabs(b), fabs(c)) * GSL_SIGN(b) * GSL_SIGN(c);
scale = GSL_MAX(fabs(p), bcmax);
z = (p / scale) * p + (bcmax / scale) * bcmis;
if (z >= 4.0 * GSL_DBL_EPSILON)
{
/* real eigenvalues, compute a and d */
z = p + GSL_SIGN(p) * fabs(sqrt(scale) * sqrt(z));
a = d + z;
d -= (bcmax / z) * bcmis;
/* compute b and the rotation matrix */
tau = gsl_hypot(c, z);
*cs = z / tau;
*sn = c / tau;
b -= c;
c = 0.0;
}
else
{
/*
* complex eigenvalues, or real (almost) equal eigenvalues -
* make diagonal elements equal
*/
sigma = b + c;
tau = gsl_hypot(sigma, tmp);
*cs = sqrt(0.5 * (1.0 + fabs(sigma) / tau));
*sn = -(p / (tau * (*cs))) * GSL_SIGN(sigma);
/*
* Compute [ AA BB ] = [ A B ] [ CS -SN ]
* [ CC DD ] [ C D ] [ SN CS ]
*/
aa = a * (*cs) + b * (*sn);
bb = -a * (*sn) + b * (*cs);
cc = c * (*cs) + d * (*sn);
dd = -c * (*sn) + d * (*cs);
/*
* Compute [ A B ] = [ CS SN ] [ AA BB ]
* [ C D ] [-SN CS ] [ CC DD ]
*/
a = aa * (*cs) + cc * (*sn);
b = bb * (*cs) + dd * (*sn);
c = -aa * (*sn) + cc * (*cs);
d = -bb * (*sn) + dd * (*cs);
tmp = 0.5 * (a + d);
a = d = tmp;
if (c != 0.0)
{
if (b != 0.0)
{
if (GSL_SIGN(b) == GSL_SIGN(c))
{
/*
* real eigenvalues: reduce to upper triangular
* form
*/
sab = sqrt(fabs(b));
sac = sqrt(fabs(c));
p = GSL_SIGN(c) * fabs(sab * sac);
tau = 1.0 / sqrt(fabs(b + c));
a = tmp + p;
d = tmp - p;
b -= c;
c = 0.0;
cs1 = sab * tau;
sn1 = sac * tau;
tmp = (*cs) * cs1 - (*sn) * sn1;
*sn = (*cs) * sn1 + (*sn) * cs1;
*cs = tmp;
}
}
else
{
b = -c;
c = 0.0;
tmp = *cs;
*cs = -(*sn);
*sn = tmp;
}
}
}
}
/* set eigenvalues */
GSL_SET_REAL(eval1, a);
GSL_SET_REAL(eval2, d);
if (c == 0.0)
{
GSL_SET_IMAG(eval1, 0.0);
GSL_SET_IMAG(eval2, 0.0);
}
else
{
tmp = sqrt(fabs(b) * fabs(c));
GSL_SET_IMAG(eval1, tmp);
GSL_SET_IMAG(eval2, -tmp);
}
/* set new matrix elements */
gsl_matrix_set(A, 0, 0, a);
gsl_matrix_set(A, 0, 1, b);
gsl_matrix_set(A, 1, 0, c);
gsl_matrix_set(A, 1, 1, d);
} /* schur_standard_form() */