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