1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
|
// 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 := getDenseWorkspace(c, c, true)
defer putDenseWorkspace(s)
sij := getDenseWorkspace(c, c, false)
defer putDenseWorkspace(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 := getDenseWorkspace(c, r, false)
defer putDenseWorkspace(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
// 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)
}
|