Blob Blame History Raw
/* eigen/schur.c
 * 
 * Copyright (C) 2006, 2007 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 3 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 <gsl/gsl_eigen.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_vector_complex.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_complex.h>
#include <gsl/gsl_complex_math.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.
 */

#define GSL_SCHUR_SMLNUM (2.0 * GSL_DBL_MIN)
#define GSL_SCHUR_BIGNUM ((1.0 - GSL_DBL_EPSILON) / GSL_SCHUR_SMLNUM)

/*
gsl_schur_gen_eigvals()
  Compute the eigenvalues of a 2-by-2 generalized block.

Inputs: A      - 2-by-2 matrix
        B      - 2-by-2 upper triangular matrix
        wr1    - (output) see notes
        wr2    - (output) see notes
        wi     - (output) see notes
        scale1 - (output) see notes
        scale2 - (output) see notes

Return: success

Notes:

1)

If the block contains real eigenvalues, then wi is set to 0,
and wr1, wr2, scale1, and scale2 are set such that:

eval1 = wr1 * scale1
eval2 = wr2 * scale2

If the block contains complex eigenvalues, then wr1, wr2, scale1,
scale2, and wi are set such that:

wr1 = wr2 = scale1 * Re(eval)
wi = scale1 * Im(eval)

wi is always non-negative

2) This routine is based on LAPACK DLAG2
*/

int
gsl_schur_gen_eigvals(const gsl_matrix *A, const gsl_matrix *B, double *wr1,
                      double *wr2, double *wi, double *scale1,
                      double *scale2)
{
  const double safemin = GSL_DBL_MIN * 1.0e2;
  const double safemax = 1.0 / safemin;
  const double rtmin = sqrt(safemin);
  const double rtmax = 1.0 / rtmin;
  double anorm, bnorm;
  double ascale, bscale, bsize;
  double s1, s2;
  double A11, A12, A21, A22;
  double B11, B12, B22;
  double binv11, binv22;
  double bmin;
  double as11, as12, as22, abi22;
  double pp, qq, shift, ss, discr, r;

  /* scale A */
  anorm = GSL_MAX(GSL_MAX(fabs(gsl_matrix_get(A, 0, 0)) +
                          fabs(gsl_matrix_get(A, 1, 0)),
                          fabs(gsl_matrix_get(A, 0, 1)) +
                          fabs(gsl_matrix_get(A, 1, 1))),
                  safemin);
  ascale = 1.0 / anorm;
  A11 = ascale * gsl_matrix_get(A, 0, 0);
  A12 = ascale * gsl_matrix_get(A, 0, 1);
  A21 = ascale * gsl_matrix_get(A, 1, 0);
  A22 = ascale * gsl_matrix_get(A, 1, 1);

  /* perturb B if necessary to ensure non-singularity */
  B11 = gsl_matrix_get(B, 0, 0);
  B12 = gsl_matrix_get(B, 0, 1);
  B22 = gsl_matrix_get(B, 1, 1);
  bmin = rtmin * GSL_MAX(fabs(B11),
                         GSL_MAX(fabs(B12), GSL_MAX(fabs(B22), rtmin)));
  if (fabs(B11) < bmin)
    B11 = GSL_SIGN(B11) * bmin;
  if (fabs(B22) < bmin)
    B22 = GSL_SIGN(B22) * bmin;

  /* scale B */
  bnorm = GSL_MAX(fabs(B11), GSL_MAX(fabs(B12) + fabs(B22), safemin));
  bsize = GSL_MAX(fabs(B11), fabs(B22));
  bscale = 1.0 / bsize;
  B11 *= bscale;
  B12 *= bscale;
  B22 *= bscale;

  /* compute larger eigenvalue */

  binv11 = 1.0 / B11;
  binv22 = 1.0 / B22;
  s1 = A11 * binv11;
  s2 = A22 * binv22;
  if (fabs(s1) <= fabs(s2))
    {
      as12 = A12 - s1 * B12;
      as22 = A22 - s1 * B22;
      ss = A21 * (binv11 * binv22);
      abi22 = as22 * binv22 - ss * B12;
      pp = 0.5 * abi22;
      shift = s1;
    }
  else
    {
      as12 = A12 - s2 * B12;
      as11 = A11 - s2 * B11;
      ss = A21 * (binv11 * binv22);
      abi22 = -ss * B12;
      pp = 0.5 * (as11 * binv11 + abi22);
      shift = s2;
    }

  qq = ss * as12;
  if (fabs(pp * rtmin) >= 1.0)
    {
      discr = (rtmin * pp) * (rtmin * pp) + qq * safemin;
      r = sqrt(fabs(discr)) * rtmax;
    }
  else if (pp * pp + fabs(qq) <= safemin)
    {
      discr = (rtmax * pp) * (rtmax * pp) + qq * safemax;
      r = sqrt(fabs(discr)) * rtmin;
    }
  else
    {
      discr = pp * pp + qq;
      r = sqrt(fabs(discr));
    }

  if (discr >= 0.0 || r == 0.0)
    {
      double sum = pp + GSL_SIGN(pp) * r;
      double diff = pp - GSL_SIGN(pp) * r;
      double wbig = shift + sum;
      double wsmall = shift + diff;

      /* compute smaller eigenvalue */

      if (0.5 * fabs(wbig) > GSL_MAX(fabs(wsmall), safemin))
        {
          double wdet = (A11*A22 - A12*A21) * (binv11 * binv22);
          wsmall = wdet / wbig;
        }

      /* choose (real) eigenvalue closest to 2,2 element of AB^{-1} for wr1 */
      if (pp > abi22)
        {
          *wr1 = GSL_MIN(wbig, wsmall);
          *wr2 = GSL_MAX(wbig, wsmall);
        }
      else
        {
          *wr1 = GSL_MAX(wbig, wsmall);
          *wr2 = GSL_MIN(wbig, wsmall);
        }
      *wi = 0.0;
    }
  else
    {
      /* complex eigenvalues */
      *wr1 = shift + pp;
      *wr2 = *wr1;
      *wi = r;
    }

  /* compute scaling */
  {
    const double fuzzy1 = 1.0 + 1.0e-5;
    double c1, c2, c3, c4, c5;
    double wabs, wsize, wscale;

    c1 = bsize * (safemin * GSL_MAX(1.0, ascale));
    c2 = safemin * GSL_MAX(1.0, bnorm);
    c3 = bsize * safemin;
    if (ascale <= 1.0 && bsize <= 1.0)
      c4 = GSL_MIN(1.0, (ascale / safemin) * bsize);
    else
      c4 = 1.0;

    if (ascale <= 1.0 || bsize <= 1.0)
      c5 = GSL_MIN(1.0, ascale * bsize);
    else
      c5 = 1.0;

    /* scale first eigenvalue */
    wabs = fabs(*wr1) + fabs(*wi);
    wsize = GSL_MAX(safemin,
              GSL_MAX(c1,
                GSL_MAX(fuzzy1 * (wabs*c2 + c3),
                  GSL_MIN(c4, 0.5 * GSL_MAX(wabs, c5)))));
    if (wsize != 1.0)
      {
        wscale = 1.0 / wsize;
        if (wsize > 1.0)
          {
            *scale1 = (GSL_MAX(ascale, bsize) * wscale) *
                      GSL_MIN(ascale, bsize);
          }
        else
          {
            *scale1 = (GSL_MIN(ascale, bsize) * wscale) *
                      GSL_MAX(ascale, bsize);
          }

        *wr1 *= wscale;
        if (*wi != 0.0)
          {
            *wi *= wscale;
            *wr2 = *wr1;
            *scale2 = *scale1;
          }
      }
    else
      {
        *scale1 = ascale * bsize;
        *scale2 = *scale1;
      }

    /* scale second eigenvalue if real */
    if (*wi == 0.0)
      {
        wsize = GSL_MAX(safemin,
                  GSL_MAX(c1,
                    GSL_MAX(fuzzy1 * (fabs(*wr2) * c2 + c3),
                      GSL_MIN(c4, 0.5 * GSL_MAX(fabs(*wr2), c5)))));
        if (wsize != 1.0)
          {
            wscale = 1.0 / wsize;
            if (wsize > 1.0)
              {
                *scale2 = (GSL_MAX(ascale, bsize) * wscale) *
                          GSL_MIN(ascale, bsize);
              }
            else
              {
                *scale2 = (GSL_MIN(ascale, bsize) * wscale) *
                          GSL_MAX(ascale, bsize);
              }

            *wr2 *= wscale;
          }
        else
          {
            *scale2 = ascale * bsize;
          }
      }
  }

  return GSL_SUCCESS;
} /* gsl_schur_gen_eigvals() */

/*
gsl_schur_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:

(ca*A - z*D)*x = s*b

where

A is n-by-n with n = 1 or 2
D is a n-by-n diagonal matrix
b and x are n-by-1 real vectors
s is a scaling factor set by this function to prevent overflow in x

Inputs: ca    - coefficient multiplying A
        A     - square matrix (n-by-n)
        z     - real scalar (eigenvalue)
        d1    - (1,1) element in diagonal matrix D
        d2    - (2,2) element in diagonal matrix D
        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 ca*A - z*D
                is less than this value, we'll use smin*I instead.
                This value should be a safe distance above underflow.

Return: success

Notes: 1) A and b are not changed on output
       2) Based on lapack routine DLALN2
*/

int
gsl_schur_solve_equation(double ca, const gsl_matrix *A, double z,
                         double d1, double d2, const 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 */

      c = ca * gsl_matrix_get(A, 0, 0) - z * d1;
      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_SCHUR_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:
       *
       * ( ca * [ A11  A12 ] - z * [ D1 0  ] ) [ x1 ] = [ b1 ]
       * (      [ A21  A22 ]       [ 0  D2 ] ) [ x2 ]   [ b2 ]
       *
       * (z real)
       */

      crv = (double *) cr;

      /*
       * compute the real part of C = ca*A - z*D - use column ordering
       * here since porting from lapack
       */
      cr[0][0] = ca * gsl_matrix_get(A, 0, 0) - z * d1;
      cr[1][1] = ca * gsl_matrix_get(A, 1, 1) - z * d2;
      cr[0][1] = ca * gsl_matrix_get(A, 1, 0);
      cr[1][0] = ca * 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_SCHUR_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 GSL_SUCCESS;
        }

      /* 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_SCHUR_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_SCHUR_BIGNUM / cmax)
            {
              temp = cmax / GSL_SCHUR_BIGNUM;
              gsl_blas_dscal(temp, x);
              *xnorm *= temp;
              scale *= temp;
            }
        }
    } /* if (N == 2) */

  *s = scale;
  return GSL_SUCCESS;
} /* gsl_schur_solve_equation() */

/*
gsl_schur_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:

(ca*A - z*D)*x = s*b

where

A is n-by-n with n = 1 or 2
D is a n-by-n diagonal matrix
b and x are n-by-1 complex vectors
s is a scaling factor set by this function to prevent overflow in x

Inputs: ca    - coefficient multiplying A
        A     - square matrix (n-by-n)
        z     - complex scalar (eigenvalue)
        d1    - (1,1) element in diagonal matrix D
        d2    - (2,2) element in diagonal matrix D
        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 ca*A - z*D
                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
*/

int
gsl_schur_solve_equation_z(double ca, const gsl_matrix *A, gsl_complex *z,
                           double d1, double d2,
                           const 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 */

      /* c = ca*a - z*d1 */
      cr = ca * gsl_matrix_get(A, 0, 0) - GSL_REAL(*z) * d1;
      ci = -GSL_IMAG(*z) * d1;
      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_SCHUR_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:
       *
       * ( ca * [ A11 A12 ] - z * [ D1 0 ] ) [ X1 ] = [ B1 ]
       * (      [ A21 A22 ]       [ 0  D2] ) [ X2 ]   [ B2 ]
       *
       * (z complex)
       *
       * where the X and B values are complex.
       */

      civ = (double *) ci;
      crv = (double *) cr;

      /*
       * compute the real part of C = ca*A - z*D - use column ordering
       * here since porting from lapack
       */
      cr[0][0] = ca*gsl_matrix_get(A, 0, 0) - GSL_REAL(*z)*d1;
      cr[1][1] = ca*gsl_matrix_get(A, 1, 1) - GSL_REAL(*z)*d2;
      cr[0][1] = ca*gsl_matrix_get(A, 1, 0);
      cr[1][0] = ca*gsl_matrix_get(A, 0, 1);

      /* compute the imaginary part */
      ci[0][0] = -GSL_IMAG(*z) * d1;
      ci[0][1] = 0.0;
      ci[1][0] = 0.0;
      ci[1][1] = -GSL_IMAG(*z) * d2;

      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_SCHUR_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 GSL_SUCCESS;
        }

      /* 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_SCHUR_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_SCHUR_BIGNUM / cmax)
            {
              temp = cmax / GSL_SCHUR_BIGNUM;
              gsl_blas_zdscal(temp, x);
              *xnorm *= temp;
              scale *= temp;
            }
        }
    } /* if (N == 2) */

  *s = scale;
  return GSL_SUCCESS;
} /* gsl_schur_solve_equation_z() */