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 }