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  }