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