gonum.org/v1/gonum@v0.14.0/lapack/gonum/dlag2.go (about)

     1  // Copyright ©2021 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package gonum
     6  
     7  import "math"
     8  
     9  // Dlag2 computes the eigenvalues of a 2×2 generalized eigenvalue problem
    10  //
    11  //	A - w*B
    12  //
    13  // where B is an upper triangular matrix.
    14  //
    15  // Dlag2 uses scaling as necessary to avoid over-/underflow. Scaling results in
    16  // a modified eigenvalue problem
    17  //
    18  //	s*A - w*B
    19  //
    20  // where s is a non-negative scaling factor chosen so that w, w*B, and s*A do
    21  // not overflow and, if possible, do not underflow, either.
    22  //
    23  // scale1 and scale2 are used to avoid over-/underflow in the eigenvalue
    24  // equation which defines the first and second eigenvalue respectively. Note
    25  // that scale1 and scale2 may be zero or less than the underflow threshold if
    26  // the corresponding exact eigenvalue is sufficiently large.
    27  //
    28  // If the eigenvalues are real, then:
    29  //   - wi is zero,
    30  //   - the eigenvalues are wr1/scale1 and wr2/scale2.
    31  //
    32  // If the eigenvalues are complex, then:
    33  //   - wi is non-negative,
    34  //   - the eigenvalues are (wr1 ± wi*i)/scale1,
    35  //   - wr1 = wr2,
    36  //   - scale1 = scale2.
    37  //
    38  // Dlag2 assumes that the one-norm of A and B is less than 1/dlamchS. Entries of
    39  // A less than sqrt(dlamchS)*norm(A) are subject to being treated as zero. The
    40  // diagonals of B should be at least sqrt(dlamchS) times the largest element of
    41  // B (in absolute value); if a diagonal is smaller than that, then
    42  // ±sqrt(dlamchS) will be used instead of that diagonal.
    43  //
    44  // Dlag2 is an internal routine. It is exported for testing purposes.
    45  func (Implementation) Dlag2(a []float64, lda int, b []float64, ldb int) (scale1, scale2, wr1, wr2, wi float64) {
    46  	switch {
    47  	case lda < 2:
    48  		panic(badLdA)
    49  	case ldb < 2:
    50  		panic(badLdB)
    51  	case len(a) < lda+2:
    52  		panic(shortA)
    53  	case len(b) < ldb+2:
    54  		panic(shortB)
    55  	}
    56  
    57  	const (
    58  		safmin = dlamchS
    59  		safmax = 1 / safmin
    60  		fuzzy1 = 1 + 1e-5
    61  	)
    62  	rtmin := math.Sqrt(safmin)
    63  	rtmax := 1 / rtmin
    64  
    65  	// Scale A.
    66  	anorm := math.Max(math.Abs(a[0])+math.Abs(a[lda]),
    67  		math.Abs(a[1])+math.Abs(a[lda+1]))
    68  	anorm = math.Max(anorm, safmin)
    69  	ascale := 1 / anorm
    70  	a11 := ascale * a[0]
    71  	a21 := ascale * a[lda]
    72  	a12 := ascale * a[1]
    73  	a22 := ascale * a[lda+1]
    74  
    75  	// Perturb B if necessary to insure non-singularity.
    76  	b11 := b[0]
    77  	b12 := b[1]
    78  	b22 := b[ldb+1]
    79  	bmin := rtmin * math.Max(math.Max(math.Abs(b11), math.Abs(b12)),
    80  		math.Max(math.Abs(b22), rtmin))
    81  	if math.Abs(b11) < bmin {
    82  		b11 = math.Copysign(bmin, b11)
    83  	}
    84  	if math.Abs(b22) < bmin {
    85  		b22 = math.Copysign(bmin, b22)
    86  	}
    87  
    88  	// Scale B.
    89  	bnorm := math.Max(math.Max(math.Abs(b11), math.Abs(b12)+math.Abs(b22)), safmin)
    90  	bsize := math.Max(math.Abs(b11), math.Abs(b22))
    91  	bscale := 1 / bsize
    92  	b11 *= bscale
    93  	b12 *= bscale
    94  	b22 *= bscale
    95  
    96  	// Compute larger eigenvalue by method described by C. van Loan.
    97  	var (
    98  		as12, abi22   float64
    99  		pp, qq, shift float64
   100  	)
   101  	binv11 := 1 / b11
   102  	binv22 := 1 / b22
   103  	s1 := a11 * binv11
   104  	s2 := a22 * binv22
   105  	// AS is A shifted by -shift*B.
   106  	if math.Abs(s1) <= math.Abs(s2) {
   107  		shift = s1
   108  		as12 = a12 - shift*b12
   109  		as22 := a22 - shift*b22
   110  		ss := a21 * (binv11 * binv22)
   111  		abi22 = as22*binv22 - ss*b12
   112  		pp = 0.5 * abi22
   113  		qq = ss * as12
   114  	} else {
   115  		shift = s2
   116  		as12 = a12 - shift*b12
   117  		as11 := a11 - shift*b11
   118  		ss := a21 * (binv11 * binv22)
   119  		abi22 = -ss * b12
   120  		pp = 0.5 * (as11*binv11 + abi22)
   121  		qq = ss * as12
   122  	}
   123  	var discr, r float64
   124  	if math.Abs(pp*rtmin) >= 1 {
   125  		tmp := rtmin * pp
   126  		discr = tmp*tmp + qq*safmin
   127  		r = math.Sqrt(math.Abs(discr)) * rtmax
   128  	} else {
   129  		pp2 := pp * pp
   130  		if pp2+math.Abs(qq) <= safmin {
   131  			tmp := rtmax * pp
   132  			discr = tmp*tmp + qq*safmax
   133  			r = math.Sqrt(math.Abs(discr)) * rtmin
   134  		} else {
   135  			discr = pp2 + qq
   136  			r = math.Sqrt(math.Abs(discr))
   137  		}
   138  	}
   139  
   140  	// TODO(vladimir-ch): Is the following comment from the reference needed in
   141  	// a Go implementation?
   142  	//
   143  	// Note: the test of r in the following `if` is to cover the case when discr
   144  	// is small and negative and is flushed to zero during the calculation of r.
   145  	// On machines which have a consistent flush-to-zero threshold and handle
   146  	// numbers above that threshold correctly, it would not be necessary.
   147  	if discr >= 0 || r == 0 {
   148  		sum := pp + math.Copysign(r, pp)
   149  		diff := pp - math.Copysign(r, pp)
   150  		wbig := shift + sum
   151  
   152  		// Compute smaller eigenvalue.
   153  		wsmall := shift + diff
   154  		if 0.5*math.Abs(wbig) > math.Max(math.Abs(wsmall), safmin) {
   155  			wdet := (a11*a22 - a12*a21) * (binv11 * binv22)
   156  			wsmall = wdet / wbig
   157  		}
   158  		// Choose (real) eigenvalue closest to 2,2 element of A*B^{-1} for wr1.
   159  		if pp > abi22 {
   160  			wr1 = math.Min(wbig, wsmall)
   161  			wr2 = math.Max(wbig, wsmall)
   162  		} else {
   163  			wr1 = math.Max(wbig, wsmall)
   164  			wr2 = math.Min(wbig, wsmall)
   165  		}
   166  	} else {
   167  		// Complex eigenvalues.
   168  		wr1 = shift + pp
   169  		wr2 = wr1
   170  		wi = r
   171  	}
   172  
   173  	// Further scaling to avoid underflow and overflow in computing
   174  	// scale1 and overflow in computing w*B.
   175  	//
   176  	// This scale factor (wscale) is bounded from above using c1 and c2,
   177  	// and from below using c3 and c4:
   178  	//  - c1 implements the condition s*A must never overflow.
   179  	//  - c2 implements the condition w*B must never overflow.
   180  	//  - c3, with c2, implement the condition that s*A - w*B must never overflow.
   181  	//  - c4 implements the condition s should not underflow.
   182  	//  - c5 implements the condition max(s,|w|) should be at least 2.
   183  	c1 := bsize * (safmin * math.Max(1, ascale))
   184  	c2 := safmin * math.Max(1, bnorm)
   185  	c3 := bsize * safmin
   186  	c4 := 1.0
   187  	c5 := 1.0
   188  	if ascale <= 1 || bsize <= 1 {
   189  		c5 = math.Min(1, ascale*bsize)
   190  		if ascale <= 1 && bsize <= 1 {
   191  			c4 = math.Min(1, (ascale/safmin)*bsize)
   192  		}
   193  	}
   194  
   195  	// Scale first eigenvalue.
   196  	wabs := math.Abs(wr1) + math.Abs(wi)
   197  	wsize := math.Max(math.Max(safmin, c1), math.Max(fuzzy1*(wabs*c2+c3),
   198  		math.Min(c4, 0.5*math.Max(wabs, c5))))
   199  	maxABsize := math.Max(ascale, bsize)
   200  	minABsize := math.Min(ascale, bsize)
   201  	if wsize != 1 {
   202  		wscale := 1 / wsize
   203  		if wsize > 1 {
   204  			scale1 = (maxABsize * wscale) * minABsize
   205  		} else {
   206  			scale1 = (minABsize * wscale) * maxABsize
   207  		}
   208  		wr1 *= wscale
   209  		if wi != 0 {
   210  			wi *= wscale
   211  			wr2 = wr1
   212  			scale2 = scale1
   213  		}
   214  	} else {
   215  		scale1 = ascale * bsize
   216  		scale2 = scale1
   217  	}
   218  
   219  	// Scale second eigenvalue if real.
   220  	if wi == 0 {
   221  		wsize = math.Max(math.Max(safmin, c1), math.Max(fuzzy1*(math.Abs(wr2)*c2+c3),
   222  			math.Min(c4, 0.5*math.Max(math.Abs(wr2), c5))))
   223  		if wsize != 1 {
   224  			wscale := 1 / wsize
   225  			if wsize > 1 {
   226  				scale2 = (maxABsize * wscale) * minABsize
   227  			} else {
   228  				scale2 = (minABsize * wscale) * maxABsize
   229  			}
   230  			wr2 *= wscale
   231  		} else {
   232  			scale2 = ascale * bsize
   233  		}
   234  	}
   235  
   236  	return scale1, scale2, wr1, wr2, wi
   237  }