github.com/gonum/matrix@v0.0.0-20181209220409-c518dec07be9/mat64/hogsvd.go (about) 1 // Copyright ©2017 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 mat64 6 7 import ( 8 "errors" 9 10 "github.com/gonum/blas/blas64" 11 "github.com/gonum/matrix" 12 ) 13 14 // HOGSVD is a type for creating and using the Higher Order Generalized Singular Value 15 // Decomposition (HOGSVD) of a set of matrices. 16 // 17 // The factorization is a linear transformation of the data sets from the given 18 // variable×sample spaces to reduced and diagonalized "eigenvariable"×"eigensample" 19 // spaces. 20 type HOGSVD struct { 21 n int 22 v *Dense 23 b []Dense 24 25 err error 26 } 27 28 // Factorize computes the higher order generalized singular value decomposition (HOGSVD) 29 // of the n input r_i×c column tall matrices in m. HOGSV extends the GSVD case from 2 to n 30 // input matrices. 31 // 32 // M_0 = U_0 * Σ_0 * V^T 33 // M_1 = U_1 * Σ_1 * V^T 34 // . 35 // . 36 // . 37 // M_{n-1} = U_{n-1} * Σ_{n-1} * V^T 38 // 39 // where U_i are r_i×c matrices of singular vectors, Σ are c×c matrices singular values, and V 40 // is a c×c matrix of singular vectors. 41 // 42 // Factorize returns whether the decomposition succeeded. If the decomposition 43 // failed, routines that require a successful factorization will panic. 44 func (gsvd *HOGSVD) Factorize(m ...Matrix) (ok bool) { 45 // Factorize performs the HOGSVD factorisation 46 // essentially as described by Ponnapalli et al. 47 // https://doi.org/10.1371/journal.pone.0028072 48 49 if len(m) < 2 { 50 panic("hogsvd: too few matrices") 51 } 52 gsvd.n = 0 53 54 r, c := m[0].Dims() 55 a := make([]Cholesky, len(m)) 56 var ts SymDense 57 for i, d := range m { 58 rd, cd := d.Dims() 59 if rd < cd { 60 gsvd.err = matrix.ErrShape 61 return false 62 } 63 if rd > r { 64 r = rd 65 } 66 if cd != c { 67 panic(matrix.ErrShape) 68 } 69 ts.Reset() 70 ts.SymOuterK(1, d.T()) 71 ok = a[i].Factorize(&ts) 72 if !ok { 73 gsvd.err = errors.New("hogsvd: cholesky decomposition failed") 74 return false 75 } 76 } 77 78 s := getWorkspace(c, c, true) 79 defer putWorkspace(s) 80 sij := getWorkspace(c, c, false) 81 defer putWorkspace(sij) 82 for i, ai := range a { 83 for _, aj := range a[i+1:] { 84 gsvd.err = sij.solveTwoChol(&ai, &aj) 85 if gsvd.err != nil { 86 return false 87 } 88 s.Add(s, sij) 89 90 gsvd.err = sij.solveTwoChol(&aj, &ai) 91 if gsvd.err != nil { 92 return false 93 } 94 s.Add(s, sij) 95 } 96 } 97 s.Scale(1/float64(len(m)*(len(m)-1)), s) 98 99 var eig Eigen 100 ok = eig.Factorize(s.T(), false, true) 101 if !ok { 102 gsvd.err = errors.New("hogsvd: eigen decomposition failed") 103 return false 104 } 105 v := eig.Vectors() 106 for j := 0; j < c; j++ { 107 cv := v.ColView(j) 108 cv.ScaleVec(1/blas64.Nrm2(c, cv.mat), cv) 109 } 110 111 b := make([]Dense, len(m)) 112 biT := getWorkspace(c, r, false) 113 defer putWorkspace(biT) 114 for i, d := range m { 115 // All calls to reset will leave a zeroed 116 // matrix with capacity to store the result 117 // without additional allocation. 118 biT.Reset() 119 gsvd.err = biT.Solve(v, d.T()) 120 if gsvd.err != nil { 121 return false 122 } 123 b[i].Clone(biT.T()) 124 } 125 126 gsvd.n = len(m) 127 gsvd.v = v 128 gsvd.b = b 129 return true 130 } 131 132 // Err returns the reason for a factorization failure. 133 func (gsvd *HOGSVD) Err() error { 134 return gsvd.err 135 } 136 137 // Len returns the number of matrices that have been factorized. If Len returns 138 // zero, the factorization was not successful. 139 func (gsvd *HOGSVD) Len() int { 140 return gsvd.n 141 } 142 143 // UFromHOGSVD extracts the matrix U_n from the singular value decomposition, storing 144 // the result in-place into the receiver. U_n is size r×c. 145 // 146 // UFromHOGSVD will panic if the receiver does not contain a successful factorization. 147 func (m *Dense) UFromHOGSVD(gsvd *HOGSVD, n int) { 148 if gsvd.n == 0 { 149 panic("hogsvd: unsuccessful factorization") 150 } 151 if n < 0 || gsvd.n <= n { 152 panic("hogsvd: invalid index") 153 } 154 155 m.reuseAs(gsvd.b[n].Dims()) 156 m.Copy(&gsvd.b[n]) 157 for j, f := range gsvd.Values(nil, n) { 158 v := m.ColView(j) 159 v.ScaleVec(1/f, v) 160 } 161 } 162 163 // Values returns the nth set of singular values of the factorized system. 164 // If the input slice is non-nil, the values will be stored in-place into the slice. 165 // In this case, the slice must have length c, and Values will panic with 166 // matrix.ErrSliceLengthMismatch otherwise. If the input slice is nil, 167 // a new slice of the appropriate length will be allocated and returned. 168 // 169 // Values will panic if the receiver does not contain a successful factorization. 170 func (gsvd *HOGSVD) Values(s []float64, n int) []float64 { 171 if gsvd.n == 0 { 172 panic("hogsvd: unsuccessful factorization") 173 } 174 if n < 0 || gsvd.n <= n { 175 panic("hogsvd: invalid index") 176 } 177 178 r, c := gsvd.b[n].Dims() 179 if s == nil { 180 s = make([]float64, c) 181 } else if len(s) != c { 182 panic(matrix.ErrSliceLengthMismatch) 183 } 184 for j := 0; j < c; j++ { 185 s[j] = blas64.Nrm2(r, gsvd.b[n].ColView(j).mat) 186 } 187 return s 188 } 189 190 // VFromHOGSVD extracts the matrix V from the singular value decomposition, storing 191 // the result in-place into the receiver. V is size c×c. 192 // 193 // VFromHOGSVD will panic if the receiver does not contain a successful factorization. 194 func (m *Dense) VFromHOGSVD(gsvd *HOGSVD) { 195 if gsvd.n == 0 { 196 panic("hogsvd: unsuccessful factorization") 197 } 198 *m = *DenseCopyOf(gsvd.v) 199 }