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