| // Copyright ©2016 The Gonum Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package gonum |
| |
| import ( |
| "math" |
| |
| "gonum.org/v1/gonum/blas" |
| "gonum.org/v1/gonum/blas/blas64" |
| "gonum.org/v1/gonum/lapack" |
| ) |
| |
| // Dsteqr computes the eigenvalues and optionally the eigenvectors of a symmetric |
| // tridiagonal matrix using the implicit QL or QR method. The eigenvectors of a |
| // full or band symmetric matrix can also be found if Dsytrd, Dsptrd, or Dsbtrd |
| // have been used to reduce this matrix to tridiagonal form. |
| // |
| // d, on entry, contains the diagonal elements of the tridiagonal matrix. On exit, |
| // d contains the eigenvalues in ascending order. d must have length n and |
| // Dsteqr will panic otherwise. |
| // |
| // e, on entry, contains the off-diagonal elements of the tridiagonal matrix on |
| // entry, and is overwritten during the call to Dsteqr. e must have length n-1 and |
| // Dsteqr will panic otherwise. |
| // |
| // z, on entry, contains the n×n orthogonal matrix used in the reduction to |
| // tridiagonal form if compz == lapack.EVOrig. On exit, if |
| // compz == lapack.EVOrig, z contains the orthonormal eigenvectors of the |
| // original symmetric matrix, and if compz == lapack.EVTridiag, z contains the |
| // orthonormal eigenvectors of the symmetric tridiagonal matrix. z is not used |
| // if compz == lapack.EVCompNone. |
| // |
| // work must have length at least max(1, 2*n-2) if the eigenvectors are computed, |
| // and Dsteqr will panic otherwise. |
| // |
| // Dsteqr is an internal routine. It is exported for testing purposes. |
| func (impl Implementation) Dsteqr(compz lapack.EVComp, n int, d, e, z []float64, ldz int, work []float64) (ok bool) { |
| switch { |
| case compz != lapack.EVCompNone && compz != lapack.EVTridiag && compz != lapack.EVOrig: |
| panic(badEVComp) |
| case n < 0: |
| panic(nLT0) |
| case ldz < 1, compz != lapack.EVCompNone && ldz < n: |
| panic(badLdZ) |
| } |
| |
| // Quick return if possible. |
| if n == 0 { |
| return true |
| } |
| |
| switch { |
| case len(d) < n: |
| panic(shortD) |
| case len(e) < n-1: |
| panic(shortE) |
| case compz != lapack.EVCompNone && len(z) < (n-1)*ldz+n: |
| panic(shortZ) |
| case compz != lapack.EVCompNone && len(work) < max(1, 2*n-2): |
| panic(shortWork) |
| } |
| |
| var icompz int |
| if compz == lapack.EVOrig { |
| icompz = 1 |
| } else if compz == lapack.EVTridiag { |
| icompz = 2 |
| } |
| |
| if n == 1 { |
| if icompz == 2 { |
| z[0] = 1 |
| } |
| return true |
| } |
| |
| bi := blas64.Implementation() |
| |
| eps := dlamchE |
| eps2 := eps * eps |
| safmin := dlamchS |
| safmax := 1 / safmin |
| ssfmax := math.Sqrt(safmax) / 3 |
| ssfmin := math.Sqrt(safmin) / eps2 |
| |
| // Compute the eigenvalues and eigenvectors of the tridiagonal matrix. |
| if icompz == 2 { |
| impl.Dlaset(blas.All, n, n, 0, 1, z, ldz) |
| } |
| const maxit = 30 |
| nmaxit := n * maxit |
| |
| jtot := 0 |
| |
| // Determine where the matrix splits and choose QL or QR iteration for each |
| // block, according to whether top or bottom diagonal element is smaller. |
| l1 := 0 |
| nm1 := n - 1 |
| |
| type scaletype int |
| const ( |
| down scaletype = iota + 1 |
| up |
| ) |
| var iscale scaletype |
| |
| for { |
| if l1 > n-1 { |
| // Order eigenvalues and eigenvectors. |
| if icompz == 0 { |
| impl.Dlasrt(lapack.SortIncreasing, n, d) |
| } else { |
| // TODO(btracey): Consider replacing this sort with a call to sort.Sort. |
| for ii := 1; ii < n; ii++ { |
| i := ii - 1 |
| k := i |
| p := d[i] |
| for j := ii; j < n; j++ { |
| if d[j] < p { |
| k = j |
| p = d[j] |
| } |
| } |
| if k != i { |
| d[k] = d[i] |
| d[i] = p |
| bi.Dswap(n, z[i:], ldz, z[k:], ldz) |
| } |
| } |
| } |
| return true |
| } |
| if l1 > 0 { |
| e[l1-1] = 0 |
| } |
| var m int |
| if l1 <= nm1 { |
| for m = l1; m < nm1; m++ { |
| test := math.Abs(e[m]) |
| if test == 0 { |
| break |
| } |
| if test <= (math.Sqrt(math.Abs(d[m]))*math.Sqrt(math.Abs(d[m+1])))*eps { |
| e[m] = 0 |
| break |
| } |
| } |
| } |
| l := l1 |
| lsv := l |
| lend := m |
| lendsv := lend |
| l1 = m + 1 |
| if lend == l { |
| continue |
| } |
| |
| // Scale submatrix in rows and columns L to Lend |
| anorm := impl.Dlanst(lapack.MaxAbs, lend-l+1, d[l:], e[l:]) |
| switch { |
| case anorm == 0: |
| continue |
| case anorm > ssfmax: |
| iscale = down |
| // Pretend that d and e are matrices with 1 column. |
| impl.Dlascl(lapack.General, 0, 0, anorm, ssfmax, lend-l+1, 1, d[l:], 1) |
| impl.Dlascl(lapack.General, 0, 0, anorm, ssfmax, lend-l, 1, e[l:], 1) |
| case anorm < ssfmin: |
| iscale = up |
| impl.Dlascl(lapack.General, 0, 0, anorm, ssfmin, lend-l+1, 1, d[l:], 1) |
| impl.Dlascl(lapack.General, 0, 0, anorm, ssfmin, lend-l, 1, e[l:], 1) |
| } |
| |
| // Choose between QL and QR. |
| if math.Abs(d[lend]) < math.Abs(d[l]) { |
| lend = lsv |
| l = lendsv |
| } |
| if lend > l { |
| // QL Iteration. Look for small subdiagonal element. |
| for { |
| if l != lend { |
| for m = l; m < lend; m++ { |
| v := math.Abs(e[m]) |
| if v*v <= (eps2*math.Abs(d[m]))*math.Abs(d[m+1])+safmin { |
| break |
| } |
| } |
| } else { |
| m = lend |
| } |
| if m < lend { |
| e[m] = 0 |
| } |
| p := d[l] |
| if m == l { |
| // Eigenvalue found. |
| l++ |
| if l > lend { |
| break |
| } |
| continue |
| } |
| |
| // If remaining matrix is 2×2, use Dlae2 to compute its eigensystem. |
| if m == l+1 { |
| if icompz > 0 { |
| d[l], d[l+1], work[l], work[n-1+l] = impl.Dlaev2(d[l], e[l], d[l+1]) |
| impl.Dlasr(blas.Right, lapack.Variable, lapack.Backward, |
| n, 2, work[l:], work[n-1+l:], z[l:], ldz) |
| } else { |
| d[l], d[l+1] = impl.Dlae2(d[l], e[l], d[l+1]) |
| } |
| e[l] = 0 |
| l += 2 |
| if l > lend { |
| break |
| } |
| continue |
| } |
| |
| if jtot == nmaxit { |
| break |
| } |
| jtot++ |
| |
| // Form shift |
| g := (d[l+1] - p) / (2 * e[l]) |
| r := impl.Dlapy2(g, 1) |
| g = d[m] - p + e[l]/(g+math.Copysign(r, g)) |
| s := 1.0 |
| c := 1.0 |
| p = 0.0 |
| |
| // Inner loop |
| for i := m - 1; i >= l; i-- { |
| f := s * e[i] |
| b := c * e[i] |
| c, s, r = impl.Dlartg(g, f) |
| if i != m-1 { |
| e[i+1] = r |
| } |
| g = d[i+1] - p |
| r = (d[i]-g)*s + 2*c*b |
| p = s * r |
| d[i+1] = g + p |
| g = c*r - b |
| |
| // If eigenvectors are desired, then save rotations. |
| if icompz > 0 { |
| work[i] = c |
| work[n-1+i] = -s |
| } |
| } |
| // If eigenvectors are desired, then apply saved rotations. |
| if icompz > 0 { |
| mm := m - l + 1 |
| impl.Dlasr(blas.Right, lapack.Variable, lapack.Backward, |
| n, mm, work[l:], work[n-1+l:], z[l:], ldz) |
| } |
| d[l] -= p |
| e[l] = g |
| } |
| } else { |
| // QR Iteration. |
| // Look for small superdiagonal element. |
| for { |
| if l != lend { |
| for m = l; m > lend; m-- { |
| v := math.Abs(e[m-1]) |
| if v*v <= (eps2*math.Abs(d[m])*math.Abs(d[m-1]) + safmin) { |
| break |
| } |
| } |
| } else { |
| m = lend |
| } |
| if m > lend { |
| e[m-1] = 0 |
| } |
| p := d[l] |
| if m == l { |
| // Eigenvalue found |
| l-- |
| if l < lend { |
| break |
| } |
| continue |
| } |
| |
| // If remaining matrix is 2×2, use Dlae2 to compute its eigenvalues. |
| if m == l-1 { |
| if icompz > 0 { |
| d[l-1], d[l], work[m], work[n-1+m] = impl.Dlaev2(d[l-1], e[l-1], d[l]) |
| impl.Dlasr(blas.Right, lapack.Variable, lapack.Forward, |
| n, 2, work[m:], work[n-1+m:], z[l-1:], ldz) |
| } else { |
| d[l-1], d[l] = impl.Dlae2(d[l-1], e[l-1], d[l]) |
| } |
| e[l-1] = 0 |
| l -= 2 |
| if l < lend { |
| break |
| } |
| continue |
| } |
| if jtot == nmaxit { |
| break |
| } |
| jtot++ |
| |
| // Form shift. |
| g := (d[l-1] - p) / (2 * e[l-1]) |
| r := impl.Dlapy2(g, 1) |
| g = d[m] - p + (e[l-1])/(g+math.Copysign(r, g)) |
| s := 1.0 |
| c := 1.0 |
| p = 0.0 |
| |
| // Inner loop. |
| for i := m; i < l; i++ { |
| f := s * e[i] |
| b := c * e[i] |
| c, s, r = impl.Dlartg(g, f) |
| if i != m { |
| e[i-1] = r |
| } |
| g = d[i] - p |
| r = (d[i+1]-g)*s + 2*c*b |
| p = s * r |
| d[i] = g + p |
| g = c*r - b |
| |
| // If eigenvectors are desired, then save rotations. |
| if icompz > 0 { |
| work[i] = c |
| work[n-1+i] = s |
| } |
| } |
| |
| // If eigenvectors are desired, then apply saved rotations. |
| if icompz > 0 { |
| mm := l - m + 1 |
| impl.Dlasr(blas.Right, lapack.Variable, lapack.Forward, |
| n, mm, work[m:], work[n-1+m:], z[m:], ldz) |
| } |
| d[l] -= p |
| e[l-1] = g |
| } |
| } |
| |
| // Undo scaling if necessary. |
| switch iscale { |
| case down: |
| // Pretend that d and e are matrices with 1 column. |
| impl.Dlascl(lapack.General, 0, 0, ssfmax, anorm, lendsv-lsv+1, 1, d[lsv:], 1) |
| impl.Dlascl(lapack.General, 0, 0, ssfmax, anorm, lendsv-lsv, 1, e[lsv:], 1) |
| case up: |
| impl.Dlascl(lapack.General, 0, 0, ssfmin, anorm, lendsv-lsv+1, 1, d[lsv:], 1) |
| impl.Dlascl(lapack.General, 0, 0, ssfmin, anorm, lendsv-lsv, 1, e[lsv:], 1) |
| } |
| |
| // Check for no convergence to an eigenvalue after a total of n*maxit iterations. |
| if jtot >= nmaxit { |
| break |
| } |
| } |
| for i := 0; i < n-1; i++ { |
| if e[i] != 0 { |
| return false |
| } |
| } |
| return true |
| } |