gonum.org/v1/gonum@v0.14.0/mat/eigen.go (about)

     1  // Copyright ©2013 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 mat
     6  
     7  import (
     8  	"gonum.org/v1/gonum/lapack"
     9  	"gonum.org/v1/gonum/lapack/lapack64"
    10  )
    11  
    12  const (
    13  	badFact   = "mat: use without successful factorization"
    14  	noVectors = "mat: eigenvectors not computed"
    15  )
    16  
    17  // EigenSym is a type for creating and manipulating the Eigen decomposition of
    18  // symmetric matrices.
    19  type EigenSym struct {
    20  	vectorsComputed bool
    21  
    22  	values  []float64
    23  	vectors *Dense
    24  }
    25  
    26  // Factorize computes the eigenvalue decomposition of the symmetric matrix a.
    27  // The Eigen decomposition is defined as
    28  //
    29  //	A = P * D * P^-1
    30  //
    31  // where D is a diagonal matrix containing the eigenvalues of the matrix, and
    32  // P is a matrix of the eigenvectors of A. Factorize computes the eigenvalues
    33  // in ascending order. If the vectors input argument is false, the eigenvectors
    34  // are not computed.
    35  //
    36  // Factorize returns whether the decomposition succeeded. If the decomposition
    37  // failed, methods that require a successful factorization will panic.
    38  func (e *EigenSym) Factorize(a Symmetric, vectors bool) (ok bool) {
    39  	// kill previous decomposition
    40  	e.vectorsComputed = false
    41  	e.values = e.values[:]
    42  
    43  	n := a.SymmetricDim()
    44  	sd := NewSymDense(n, nil)
    45  	sd.CopySym(a)
    46  
    47  	jobz := lapack.EVNone
    48  	if vectors {
    49  		jobz = lapack.EVCompute
    50  	}
    51  	w := make([]float64, n)
    52  	work := []float64{0}
    53  	lapack64.Syev(jobz, sd.mat, w, work, -1)
    54  
    55  	work = getFloat64s(int(work[0]), false)
    56  	ok = lapack64.Syev(jobz, sd.mat, w, work, len(work))
    57  	putFloat64s(work)
    58  	if !ok {
    59  		e.vectorsComputed = false
    60  		e.values = nil
    61  		e.vectors = nil
    62  		return false
    63  	}
    64  	e.vectorsComputed = vectors
    65  	e.values = w
    66  	e.vectors = NewDense(n, n, sd.mat.Data)
    67  	return true
    68  }
    69  
    70  // succFact returns whether the receiver contains a successful factorization.
    71  func (e *EigenSym) succFact() bool {
    72  	return len(e.values) != 0
    73  }
    74  
    75  // Values extracts the eigenvalues of the factorized matrix in ascending order.
    76  // If dst is non-nil, the values are stored in-place into dst. In this case dst
    77  // must have length n, otherwise Values will panic. If dst is nil, then a new
    78  // slice will be allocated of the proper length and filled with the eigenvalues.
    79  //
    80  // Values panics if the Eigen decomposition was not successful.
    81  func (e *EigenSym) Values(dst []float64) []float64 {
    82  	if !e.succFact() {
    83  		panic(badFact)
    84  	}
    85  	if dst == nil {
    86  		dst = make([]float64, len(e.values))
    87  	}
    88  	if len(dst) != len(e.values) {
    89  		panic(ErrSliceLengthMismatch)
    90  	}
    91  	copy(dst, e.values)
    92  	return dst
    93  }
    94  
    95  // VectorsTo stores the eigenvectors of the decomposition into the columns of
    96  // dst.
    97  //
    98  // If dst is empty, VectorsTo will resize dst to be n×n. When dst is
    99  // non-empty, VectorsTo will panic if dst is not n×n. VectorsTo will also
   100  // panic if the eigenvectors were not computed during the factorization,
   101  // or if the receiver does not contain a successful factorization.
   102  func (e *EigenSym) VectorsTo(dst *Dense) {
   103  	if !e.succFact() {
   104  		panic(badFact)
   105  	}
   106  	if !e.vectorsComputed {
   107  		panic(noVectors)
   108  	}
   109  	r, c := e.vectors.Dims()
   110  	if dst.IsEmpty() {
   111  		dst.ReuseAs(r, c)
   112  	} else {
   113  		r2, c2 := dst.Dims()
   114  		if r != r2 || c != c2 {
   115  			panic(ErrShape)
   116  		}
   117  	}
   118  	dst.Copy(e.vectors)
   119  }
   120  
   121  // EigenKind specifies the computation of eigenvectors during factorization.
   122  type EigenKind int
   123  
   124  const (
   125  	// EigenNone specifies to not compute any eigenvectors.
   126  	EigenNone EigenKind = 0
   127  	// EigenLeft specifies to compute the left eigenvectors.
   128  	EigenLeft EigenKind = 1 << iota
   129  	// EigenRight specifies to compute the right eigenvectors.
   130  	EigenRight
   131  	// EigenBoth is a convenience value for computing both eigenvectors.
   132  	EigenBoth EigenKind = EigenLeft | EigenRight
   133  )
   134  
   135  // Eigen is a type for creating and using the eigenvalue decomposition of a dense matrix.
   136  type Eigen struct {
   137  	n int // The size of the factorized matrix.
   138  
   139  	kind EigenKind
   140  
   141  	values   []complex128
   142  	rVectors *CDense
   143  	lVectors *CDense
   144  }
   145  
   146  // succFact returns whether the receiver contains a successful factorization.
   147  func (e *Eigen) succFact() bool {
   148  	return e.n != 0
   149  }
   150  
   151  // Factorize computes the eigenvalues of the square matrix a, and optionally
   152  // the eigenvectors.
   153  //
   154  // A right eigenvalue/eigenvector combination is defined by
   155  //
   156  //	A * x_r = λ * x_r
   157  //
   158  // where x_r is the column vector called an eigenvector, and λ is the corresponding
   159  // eigenvalue.
   160  //
   161  // Similarly, a left eigenvalue/eigenvector combination is defined by
   162  //
   163  //	x_l * A = λ * x_l
   164  //
   165  // The eigenvalues, but not the eigenvectors, are the same for both decompositions.
   166  //
   167  // Typically eigenvectors refer to right eigenvectors.
   168  //
   169  // In all cases, Factorize computes the eigenvalues of the matrix. kind
   170  // specifies which of the eigenvectors, if any, to compute. See the EigenKind
   171  // documentation for more information.
   172  // Eigen panics if the input matrix is not square.
   173  //
   174  // Factorize returns whether the decomposition succeeded. If the decomposition
   175  // failed, methods that require a successful factorization will panic.
   176  func (e *Eigen) Factorize(a Matrix, kind EigenKind) (ok bool) {
   177  	// kill previous factorization.
   178  	e.n = 0
   179  	e.kind = 0
   180  	// Copy a because it is modified during the Lapack call.
   181  	r, c := a.Dims()
   182  	if r != c {
   183  		panic(ErrShape)
   184  	}
   185  	var sd Dense
   186  	sd.CloneFrom(a)
   187  
   188  	left := kind&EigenLeft != 0
   189  	right := kind&EigenRight != 0
   190  
   191  	var vl, vr Dense
   192  	jobvl := lapack.LeftEVNone
   193  	jobvr := lapack.RightEVNone
   194  	if left {
   195  		vl = *NewDense(r, r, nil)
   196  		jobvl = lapack.LeftEVCompute
   197  	}
   198  	if right {
   199  		vr = *NewDense(c, c, nil)
   200  		jobvr = lapack.RightEVCompute
   201  	}
   202  
   203  	wr := getFloat64s(c, false)
   204  	defer putFloat64s(wr)
   205  	wi := getFloat64s(c, false)
   206  	defer putFloat64s(wi)
   207  
   208  	work := []float64{0}
   209  	lapack64.Geev(jobvl, jobvr, sd.mat, wr, wi, vl.mat, vr.mat, work, -1)
   210  	work = getFloat64s(int(work[0]), false)
   211  	first := lapack64.Geev(jobvl, jobvr, sd.mat, wr, wi, vl.mat, vr.mat, work, len(work))
   212  	putFloat64s(work)
   213  
   214  	if first != 0 {
   215  		e.values = nil
   216  		return false
   217  	}
   218  	e.n = r
   219  	e.kind = kind
   220  
   221  	// Construct complex eigenvalues from float64 data.
   222  	values := make([]complex128, r)
   223  	for i, v := range wr {
   224  		values[i] = complex(v, wi[i])
   225  	}
   226  	e.values = values
   227  
   228  	// Construct complex eigenvectors from float64 data.
   229  	var cvl, cvr CDense
   230  	if left {
   231  		cvl = *NewCDense(r, r, nil)
   232  		e.complexEigenTo(&cvl, &vl)
   233  		e.lVectors = &cvl
   234  	} else {
   235  		e.lVectors = nil
   236  	}
   237  	if right {
   238  		cvr = *NewCDense(c, c, nil)
   239  		e.complexEigenTo(&cvr, &vr)
   240  		e.rVectors = &cvr
   241  	} else {
   242  		e.rVectors = nil
   243  	}
   244  	return true
   245  }
   246  
   247  // Kind returns the EigenKind of the decomposition. If no decomposition has been
   248  // computed, Kind returns -1.
   249  func (e *Eigen) Kind() EigenKind {
   250  	if !e.succFact() {
   251  		return -1
   252  	}
   253  	return e.kind
   254  }
   255  
   256  // Values extracts the eigenvalues of the factorized matrix. If dst is
   257  // non-nil, the values are stored in-place into dst. In this case
   258  // dst must have length n, otherwise Values will panic. If dst is
   259  // nil, then a new slice will be allocated of the proper length and
   260  // filed with the eigenvalues.
   261  //
   262  // Values panics if the Eigen decomposition was not successful.
   263  func (e *Eigen) Values(dst []complex128) []complex128 {
   264  	if !e.succFact() {
   265  		panic(badFact)
   266  	}
   267  	if dst == nil {
   268  		dst = make([]complex128, e.n)
   269  	}
   270  	if len(dst) != e.n {
   271  		panic(ErrSliceLengthMismatch)
   272  	}
   273  	copy(dst, e.values)
   274  	return dst
   275  }
   276  
   277  // complexEigenTo extracts the complex eigenvectors from the real matrix d
   278  // and stores them into the complex matrix dst.
   279  //
   280  // The columns of the returned n×n dense matrix contain the eigenvectors of the
   281  // decomposition in the same order as the eigenvalues.
   282  // If the j-th eigenvalue is real, then
   283  //
   284  //	dst[:,j] = d[:,j],
   285  //
   286  // and if it is not real, then the elements of the j-th and (j+1)-th columns of d
   287  // form complex conjugate pairs and the eigenvectors are recovered as
   288  //
   289  //	dst[:,j]   = d[:,j] + i*d[:,j+1],
   290  //	dst[:,j+1] = d[:,j] - i*d[:,j+1],
   291  //
   292  // where i is the imaginary unit.
   293  func (e *Eigen) complexEigenTo(dst *CDense, d *Dense) {
   294  	r, c := d.Dims()
   295  	cr, cc := dst.Dims()
   296  	if r != cr {
   297  		panic("size mismatch")
   298  	}
   299  	if c != cc {
   300  		panic("size mismatch")
   301  	}
   302  	for j := 0; j < c; j++ {
   303  		if imag(e.values[j]) == 0 {
   304  			for i := 0; i < r; i++ {
   305  				dst.set(i, j, complex(d.at(i, j), 0))
   306  			}
   307  			continue
   308  		}
   309  		for i := 0; i < r; i++ {
   310  			real := d.at(i, j)
   311  			imag := d.at(i, j+1)
   312  			dst.set(i, j, complex(real, imag))
   313  			dst.set(i, j+1, complex(real, -imag))
   314  		}
   315  		j++
   316  	}
   317  }
   318  
   319  // VectorsTo stores the right eigenvectors of the decomposition into the columns
   320  // of dst. The computed eigenvectors are normalized to have Euclidean norm equal
   321  // to 1 and largest component real.
   322  //
   323  // If dst is empty, VectorsTo will resize dst to be n×n. When dst is
   324  // non-empty, VectorsTo will panic if dst is not n×n. VectorsTo will also
   325  // panic if the eigenvectors were not computed during the factorization,
   326  // or if the receiver does not contain a successful factorization.
   327  func (e *Eigen) VectorsTo(dst *CDense) {
   328  	if !e.succFact() {
   329  		panic(badFact)
   330  	}
   331  	if e.kind&EigenRight == 0 {
   332  		panic(noVectors)
   333  	}
   334  	if dst.IsEmpty() {
   335  		dst.ReuseAs(e.n, e.n)
   336  	} else {
   337  		r, c := dst.Dims()
   338  		if r != e.n || c != e.n {
   339  			panic(ErrShape)
   340  		}
   341  	}
   342  	dst.Copy(e.rVectors)
   343  }
   344  
   345  // LeftVectorsTo stores the left eigenvectors of the decomposition into the
   346  // columns of dst. The computed eigenvectors are normalized to have Euclidean
   347  // norm equal to 1 and largest component real.
   348  //
   349  // If dst is empty, LeftVectorsTo will resize dst to be n×n. When dst is
   350  // non-empty, LeftVectorsTo will panic if dst is not n×n. LeftVectorsTo will also
   351  // panic if the left eigenvectors were not computed during the factorization,
   352  // or if the receiver does not contain a successful factorization
   353  func (e *Eigen) LeftVectorsTo(dst *CDense) {
   354  	if !e.succFact() {
   355  		panic(badFact)
   356  	}
   357  	if e.kind&EigenLeft == 0 {
   358  		panic(noVectors)
   359  	}
   360  	if dst.IsEmpty() {
   361  		dst.ReuseAs(e.n, e.n)
   362  	} else {
   363  		r, c := dst.Dims()
   364  		if r != e.n || c != e.n {
   365  			panic(ErrShape)
   366  		}
   367  	}
   368  	dst.Copy(e.lVectors)
   369  }