gonum.org/v1/gonum@v0.14.0/lapack/gonum/dsteqr.go (about) 1 // Copyright ©2016 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 ( 8 "math" 9 10 "gonum.org/v1/gonum/blas" 11 "gonum.org/v1/gonum/blas/blas64" 12 "gonum.org/v1/gonum/lapack" 13 ) 14 15 // Dsteqr computes the eigenvalues and optionally the eigenvectors of a symmetric 16 // tridiagonal matrix using the implicit QL or QR method. The eigenvectors of a 17 // full or band symmetric matrix can also be found if Dsytrd, Dsptrd, or Dsbtrd 18 // have been used to reduce this matrix to tridiagonal form. 19 // 20 // d, on entry, contains the diagonal elements of the tridiagonal matrix. On exit, 21 // d contains the eigenvalues in ascending order. d must have length n and 22 // Dsteqr will panic otherwise. 23 // 24 // e, on entry, contains the off-diagonal elements of the tridiagonal matrix on 25 // entry, and is overwritten during the call to Dsteqr. e must have length n-1 and 26 // Dsteqr will panic otherwise. 27 // 28 // z, on entry, contains the n×n orthogonal matrix used in the reduction to 29 // tridiagonal form if compz == lapack.EVOrig. On exit, if 30 // compz == lapack.EVOrig, z contains the orthonormal eigenvectors of the 31 // original symmetric matrix, and if compz == lapack.EVTridiag, z contains the 32 // orthonormal eigenvectors of the symmetric tridiagonal matrix. z is not used 33 // if compz == lapack.EVCompNone. 34 // 35 // work must have length at least max(1, 2*n-2) if the eigenvectors are computed, 36 // and Dsteqr will panic otherwise. 37 // 38 // Dsteqr is an internal routine. It is exported for testing purposes. 39 func (impl Implementation) Dsteqr(compz lapack.EVComp, n int, d, e, z []float64, ldz int, work []float64) (ok bool) { 40 switch { 41 case compz != lapack.EVCompNone && compz != lapack.EVTridiag && compz != lapack.EVOrig: 42 panic(badEVComp) 43 case n < 0: 44 panic(nLT0) 45 case ldz < 1, compz != lapack.EVCompNone && ldz < n: 46 panic(badLdZ) 47 } 48 49 // Quick return if possible. 50 if n == 0 { 51 return true 52 } 53 54 switch { 55 case len(d) < n: 56 panic(shortD) 57 case len(e) < n-1: 58 panic(shortE) 59 case compz != lapack.EVCompNone && len(z) < (n-1)*ldz+n: 60 panic(shortZ) 61 case compz != lapack.EVCompNone && len(work) < max(1, 2*n-2): 62 panic(shortWork) 63 } 64 65 var icompz int 66 if compz == lapack.EVOrig { 67 icompz = 1 68 } else if compz == lapack.EVTridiag { 69 icompz = 2 70 } 71 72 if n == 1 { 73 if icompz == 2 { 74 z[0] = 1 75 } 76 return true 77 } 78 79 bi := blas64.Implementation() 80 81 eps := dlamchE 82 eps2 := eps * eps 83 safmin := dlamchS 84 safmax := 1 / safmin 85 ssfmax := math.Sqrt(safmax) / 3 86 ssfmin := math.Sqrt(safmin) / eps2 87 88 // Compute the eigenvalues and eigenvectors of the tridiagonal matrix. 89 if icompz == 2 { 90 impl.Dlaset(blas.All, n, n, 0, 1, z, ldz) 91 } 92 const maxit = 30 93 nmaxit := n * maxit 94 95 jtot := 0 96 97 // Determine where the matrix splits and choose QL or QR iteration for each 98 // block, according to whether top or bottom diagonal element is smaller. 99 l1 := 0 100 nm1 := n - 1 101 102 type scaletype int 103 const ( 104 down scaletype = iota + 1 105 up 106 ) 107 var iscale scaletype 108 109 for { 110 if l1 > n-1 { 111 // Order eigenvalues and eigenvectors. 112 if icompz == 0 { 113 impl.Dlasrt(lapack.SortIncreasing, n, d) 114 } else { 115 // TODO(btracey): Consider replacing this sort with a call to sort.Sort. 116 for ii := 1; ii < n; ii++ { 117 i := ii - 1 118 k := i 119 p := d[i] 120 for j := ii; j < n; j++ { 121 if d[j] < p { 122 k = j 123 p = d[j] 124 } 125 } 126 if k != i { 127 d[k] = d[i] 128 d[i] = p 129 bi.Dswap(n, z[i:], ldz, z[k:], ldz) 130 } 131 } 132 } 133 return true 134 } 135 if l1 > 0 { 136 e[l1-1] = 0 137 } 138 var m int 139 if l1 <= nm1 { 140 for m = l1; m < nm1; m++ { 141 test := math.Abs(e[m]) 142 if test == 0 { 143 break 144 } 145 if test <= (math.Sqrt(math.Abs(d[m]))*math.Sqrt(math.Abs(d[m+1])))*eps { 146 e[m] = 0 147 break 148 } 149 } 150 } 151 l := l1 152 lsv := l 153 lend := m 154 lendsv := lend 155 l1 = m + 1 156 if lend == l { 157 continue 158 } 159 160 // Scale submatrix in rows and columns L to Lend 161 anorm := impl.Dlanst(lapack.MaxAbs, lend-l+1, d[l:], e[l:]) 162 switch { 163 case anorm == 0: 164 continue 165 case anorm > ssfmax: 166 iscale = down 167 // Pretend that d and e are matrices with 1 column. 168 impl.Dlascl(lapack.General, 0, 0, anorm, ssfmax, lend-l+1, 1, d[l:], 1) 169 impl.Dlascl(lapack.General, 0, 0, anorm, ssfmax, lend-l, 1, e[l:], 1) 170 case anorm < ssfmin: 171 iscale = up 172 impl.Dlascl(lapack.General, 0, 0, anorm, ssfmin, lend-l+1, 1, d[l:], 1) 173 impl.Dlascl(lapack.General, 0, 0, anorm, ssfmin, lend-l, 1, e[l:], 1) 174 } 175 176 // Choose between QL and QR. 177 if math.Abs(d[lend]) < math.Abs(d[l]) { 178 lend = lsv 179 l = lendsv 180 } 181 if lend > l { 182 // QL Iteration. Look for small subdiagonal element. 183 for { 184 if l != lend { 185 for m = l; m < lend; m++ { 186 v := math.Abs(e[m]) 187 if v*v <= (eps2*math.Abs(d[m]))*math.Abs(d[m+1])+safmin { 188 break 189 } 190 } 191 } else { 192 m = lend 193 } 194 if m < lend { 195 e[m] = 0 196 } 197 p := d[l] 198 if m == l { 199 // Eigenvalue found. 200 l++ 201 if l > lend { 202 break 203 } 204 continue 205 } 206 207 // If remaining matrix is 2×2, use Dlae2 to compute its eigensystem. 208 if m == l+1 { 209 if icompz > 0 { 210 d[l], d[l+1], work[l], work[n-1+l] = impl.Dlaev2(d[l], e[l], d[l+1]) 211 impl.Dlasr(blas.Right, lapack.Variable, lapack.Backward, 212 n, 2, work[l:], work[n-1+l:], z[l:], ldz) 213 } else { 214 d[l], d[l+1] = impl.Dlae2(d[l], e[l], d[l+1]) 215 } 216 e[l] = 0 217 l += 2 218 if l > lend { 219 break 220 } 221 continue 222 } 223 224 if jtot == nmaxit { 225 break 226 } 227 jtot++ 228 229 // Form shift 230 g := (d[l+1] - p) / (2 * e[l]) 231 r := impl.Dlapy2(g, 1) 232 g = d[m] - p + e[l]/(g+math.Copysign(r, g)) 233 s := 1.0 234 c := 1.0 235 p = 0.0 236 237 // Inner loop 238 for i := m - 1; i >= l; i-- { 239 f := s * e[i] 240 b := c * e[i] 241 c, s, r = impl.Dlartg(g, f) 242 if i != m-1 { 243 e[i+1] = r 244 } 245 g = d[i+1] - p 246 r = (d[i]-g)*s + 2*c*b 247 p = s * r 248 d[i+1] = g + p 249 g = c*r - b 250 251 // If eigenvectors are desired, then save rotations. 252 if icompz > 0 { 253 work[i] = c 254 work[n-1+i] = -s 255 } 256 } 257 // If eigenvectors are desired, then apply saved rotations. 258 if icompz > 0 { 259 mm := m - l + 1 260 impl.Dlasr(blas.Right, lapack.Variable, lapack.Backward, 261 n, mm, work[l:], work[n-1+l:], z[l:], ldz) 262 } 263 d[l] -= p 264 e[l] = g 265 } 266 } else { 267 // QR Iteration. 268 // Look for small superdiagonal element. 269 for { 270 if l != lend { 271 for m = l; m > lend; m-- { 272 v := math.Abs(e[m-1]) 273 if v*v <= (eps2*math.Abs(d[m])*math.Abs(d[m-1]) + safmin) { 274 break 275 } 276 } 277 } else { 278 m = lend 279 } 280 if m > lend { 281 e[m-1] = 0 282 } 283 p := d[l] 284 if m == l { 285 // Eigenvalue found 286 l-- 287 if l < lend { 288 break 289 } 290 continue 291 } 292 293 // If remaining matrix is 2×2, use Dlae2 to compute its eigenvalues. 294 if m == l-1 { 295 if icompz > 0 { 296 d[l-1], d[l], work[m], work[n-1+m] = impl.Dlaev2(d[l-1], e[l-1], d[l]) 297 impl.Dlasr(blas.Right, lapack.Variable, lapack.Forward, 298 n, 2, work[m:], work[n-1+m:], z[l-1:], ldz) 299 } else { 300 d[l-1], d[l] = impl.Dlae2(d[l-1], e[l-1], d[l]) 301 } 302 e[l-1] = 0 303 l -= 2 304 if l < lend { 305 break 306 } 307 continue 308 } 309 if jtot == nmaxit { 310 break 311 } 312 jtot++ 313 314 // Form shift. 315 g := (d[l-1] - p) / (2 * e[l-1]) 316 r := impl.Dlapy2(g, 1) 317 g = d[m] - p + (e[l-1])/(g+math.Copysign(r, g)) 318 s := 1.0 319 c := 1.0 320 p = 0.0 321 322 // Inner loop. 323 for i := m; i < l; i++ { 324 f := s * e[i] 325 b := c * e[i] 326 c, s, r = impl.Dlartg(g, f) 327 if i != m { 328 e[i-1] = r 329 } 330 g = d[i] - p 331 r = (d[i+1]-g)*s + 2*c*b 332 p = s * r 333 d[i] = g + p 334 g = c*r - b 335 336 // If eigenvectors are desired, then save rotations. 337 if icompz > 0 { 338 work[i] = c 339 work[n-1+i] = s 340 } 341 } 342 343 // If eigenvectors are desired, then apply saved rotations. 344 if icompz > 0 { 345 mm := l - m + 1 346 impl.Dlasr(blas.Right, lapack.Variable, lapack.Forward, 347 n, mm, work[m:], work[n-1+m:], z[m:], ldz) 348 } 349 d[l] -= p 350 e[l-1] = g 351 } 352 } 353 354 // Undo scaling if necessary. 355 switch iscale { 356 case down: 357 // Pretend that d and e are matrices with 1 column. 358 impl.Dlascl(lapack.General, 0, 0, ssfmax, anorm, lendsv-lsv+1, 1, d[lsv:], 1) 359 impl.Dlascl(lapack.General, 0, 0, ssfmax, anorm, lendsv-lsv, 1, e[lsv:], 1) 360 case up: 361 impl.Dlascl(lapack.General, 0, 0, ssfmin, anorm, lendsv-lsv+1, 1, d[lsv:], 1) 362 impl.Dlascl(lapack.General, 0, 0, ssfmin, anorm, lendsv-lsv, 1, e[lsv:], 1) 363 } 364 365 // Check for no convergence to an eigenvalue after a total of n*maxit iterations. 366 if jtot >= nmaxit { 367 break 368 } 369 } 370 for i := 0; i < n-1; i++ { 371 if e[i] != 0 { 372 return false 373 } 374 } 375 return true 376 }