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 }