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  }