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 }