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