| 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 matimport (	"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)}
 |