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