123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- // Copyright ©2017 The Gonum Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package mat
- import (
- "errors"
- "gonum.org/v1/gonum/blas/blas64"
- )
- // HOGSVD is a type for creating and using the Higher Order Generalized Singular Value
- // Decomposition (HOGSVD) of a set of matrices.
- //
- // The factorization is a linear transformation of the data sets from the given
- // variable×sample spaces to reduced and diagonalized "eigenvariable"×"eigensample"
- // spaces.
- type HOGSVD struct {
- n int
- v *Dense
- b []Dense
- err error
- }
- // succFact returns whether the receiver contains a successful factorization.
- func (gsvd *HOGSVD) succFact() bool {
- return gsvd.n != 0
- }
- // Factorize computes the higher order generalized singular value decomposition (HOGSVD)
- // of the n input r_i×c column tall matrices in m. HOGSV extends the GSVD case from 2 to n
- // input matrices.
- //
- // M_0 = U_0 * Σ_0 * Vᵀ
- // M_1 = U_1 * Σ_1 * Vᵀ
- // .
- // .
- // .
- // M_{n-1} = U_{n-1} * Σ_{n-1} * Vᵀ
- //
- // where U_i are r_i×c matrices of singular vectors, Σ are c×c matrices singular values, and V
- // is a c×c matrix of singular vectors.
- //
- // Factorize returns whether the decomposition succeeded. If the decomposition
- // failed, routines that require a successful factorization will panic.
- func (gsvd *HOGSVD) Factorize(m ...Matrix) (ok bool) {
- // Factorize performs the HOGSVD factorisation
- // essentially as described by Ponnapalli et al.
- // https://doi.org/10.1371/journal.pone.0028072
- if len(m) < 2 {
- panic("hogsvd: too few matrices")
- }
- gsvd.n = 0
- r, c := m[0].Dims()
- a := make([]Cholesky, len(m))
- var ts SymDense
- for i, d := range m {
- rd, cd := d.Dims()
- if rd < cd {
- gsvd.err = ErrShape
- return false
- }
- if rd > r {
- r = rd
- }
- if cd != c {
- panic(ErrShape)
- }
- ts.Reset()
- ts.SymOuterK(1, d.T())
- ok = a[i].Factorize(&ts)
- if !ok {
- gsvd.err = errors.New("hogsvd: cholesky decomposition failed")
- return false
- }
- }
- s := getWorkspace(c, c, true)
- defer putWorkspace(s)
- sij := getWorkspace(c, c, false)
- defer putWorkspace(sij)
- for i, ai := range a {
- for _, aj := range a[i+1:] {
- gsvd.err = ai.SolveCholTo(sij, &aj)
- if gsvd.err != nil {
- return false
- }
- s.Add(s, sij)
- gsvd.err = aj.SolveCholTo(sij, &ai)
- if gsvd.err != nil {
- return false
- }
- s.Add(s, sij)
- }
- }
- s.Scale(1/float64(len(m)*(len(m)-1)), s)
- var eig Eigen
- ok = eig.Factorize(s.T(), EigenRight)
- if !ok {
- gsvd.err = errors.New("hogsvd: eigen decomposition failed")
- return false
- }
- var vc CDense
- eig.VectorsTo(&vc)
- // vc is guaranteed to have real eigenvalues.
- rc, cc := vc.Dims()
- v := NewDense(rc, cc, nil)
- for i := 0; i < rc; i++ {
- for j := 0; j < cc; j++ {
- a := vc.At(i, j)
- v.set(i, j, real(a))
- }
- }
- // Rescale the columns of v by their Frobenius norms.
- // Work done in cv is reflected in v.
- var cv VecDense
- for j := 0; j < c; j++ {
- cv.ColViewOf(v, j)
- cv.ScaleVec(1/blas64.Nrm2(cv.mat), &cv)
- }
- b := make([]Dense, len(m))
- biT := getWorkspace(c, r, false)
- defer putWorkspace(biT)
- for i, d := range m {
- // All calls to reset will leave an emptied
- // matrix with capacity to store the result
- // without additional allocation.
- biT.Reset()
- gsvd.err = biT.Solve(v, d.T())
- if gsvd.err != nil {
- return false
- }
- b[i].CloneFrom(biT.T())
- }
- gsvd.n = len(m)
- gsvd.v = v
- gsvd.b = b
- return true
- }
- // Err returns the reason for a factorization failure.
- func (gsvd *HOGSVD) Err() error {
- return gsvd.err
- }
- // Len returns the number of matrices that have been factorized. If Len returns
- // zero, the factorization was not successful.
- func (gsvd *HOGSVD) Len() int {
- return gsvd.n
- }
- // UTo extracts the matrix U_n from the singular value decomposition, storing
- // the result in-place into dst. U_n is size r×c.
- //
- // If dst is empty, UTo will resize dst to be r×c. When dst is
- // non-empty, UTo will panic if dst is not r×c. UTo will also
- // panic if the receiver does not contain a successful factorization.
- func (gsvd *HOGSVD) UTo(dst *Dense, n int) {
- if !gsvd.succFact() {
- panic(badFact)
- }
- if n < 0 || gsvd.n <= n {
- panic("hogsvd: invalid index")
- }
- r, c := gsvd.b[n].Dims()
- if dst.IsEmpty() {
- dst.ReuseAs(r, c)
- } else {
- r2, c2 := dst.Dims()
- if r != r2 || c != c2 {
- panic(ErrShape)
- }
- }
- dst.Copy(&gsvd.b[n])
- var v VecDense
- for j, f := range gsvd.Values(nil, n) {
- v.ColViewOf(dst, j)
- v.ScaleVec(1/f, &v)
- }
- }
- // Values returns the nth set of singular values of the factorized system.
- // If the input slice is non-nil, the values will be stored in-place into the slice.
- // In this case, the slice must have length c, and Values will panic with
- // matrix.ErrSliceLengthMismatch otherwise. If the input slice is nil,
- // a new slice of the appropriate length will be allocated and returned.
- //
- // Values will panic if the receiver does not contain a successful factorization.
- func (gsvd *HOGSVD) Values(s []float64, n int) []float64 {
- if !gsvd.succFact() {
- panic(badFact)
- }
- if n < 0 || gsvd.n <= n {
- panic("hogsvd: invalid index")
- }
- _, c := gsvd.b[n].Dims()
- if s == nil {
- s = make([]float64, c)
- } else if len(s) != c {
- panic(ErrSliceLengthMismatch)
- }
- var v VecDense
- for j := 0; j < c; j++ {
- v.ColViewOf(&gsvd.b[n], j)
- s[j] = blas64.Nrm2(v.mat)
- }
- return s
- }
- // VTo extracts the matrix V from the singular value decomposition, storing
- // the result in-place into dst. V is size c×c.
- //
- // If dst is empty, VTo will resize dst to be c×c. When dst is
- // non-empty, VTo will panic if dst is not c×c. VTo will also
- // panic if the receiver does not contain a successful factorization.
- func (gsvd *HOGSVD) VTo(dst *Dense) {
- if !gsvd.succFact() {
- panic(badFact)
- }
- r, c := gsvd.v.Dims()
- if dst.IsEmpty() {
- dst.ReuseAs(r, c)
- } else {
- r2, c2 := dst.Dims()
- if r != r2 || c != c2 {
- panic(ErrShape)
- }
- }
- dst.Copy(gsvd.v)
- }
|