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 }