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