437 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			437 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
| // 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 (
 | ||
| 	"gonum.org/v1/gonum/blas/blas64"
 | ||
| 	"gonum.org/v1/gonum/floats"
 | ||
| 	"gonum.org/v1/gonum/lapack"
 | ||
| 	"gonum.org/v1/gonum/lapack/lapack64"
 | ||
| )
 | ||
| 
 | ||
| // GSVDKind specifies the treatment of singular vectors during a GSVD
 | ||
| // factorization.
 | ||
| type GSVDKind int
 | ||
| 
 | ||
| const (
 | ||
| 	// GSVDNone specifies that no singular vectors should be computed during
 | ||
| 	// the decomposition.
 | ||
| 	GSVDNone GSVDKind = 0
 | ||
| 
 | ||
| 	// GSVDU specifies that the U singular vectors should be computed during
 | ||
| 	// the decomposition.
 | ||
| 	GSVDU GSVDKind = 1 << iota
 | ||
| 	// GSVDV specifies that the V singular vectors should be computed during
 | ||
| 	// the decomposition.
 | ||
| 	GSVDV
 | ||
| 	// GSVDQ specifies that the Q singular vectors should be computed during
 | ||
| 	// the decomposition.
 | ||
| 	GSVDQ
 | ||
| 
 | ||
| 	// GSVDAll is a convenience value for computing all of the singular vectors.
 | ||
| 	GSVDAll = GSVDU | GSVDV | GSVDQ
 | ||
| )
 | ||
| 
 | ||
| // GSVD is a type for creating and using the Generalized Singular Value Decomposition
 | ||
| // (GSVD) of a matrix.
 | ||
| //
 | ||
| // The factorization is a linear transformation of the data sets from the given
 | ||
| // variable×sample spaces to reduced and diagonalized "eigenvariable"×"eigensample"
 | ||
| // spaces.
 | ||
| type GSVD struct {
 | ||
| 	kind GSVDKind
 | ||
| 
 | ||
| 	r, p, c, k, l int
 | ||
| 	s1, s2        []float64
 | ||
| 	a, b, u, v, q blas64.General
 | ||
| 
 | ||
| 	work  []float64
 | ||
| 	iwork []int
 | ||
| }
 | ||
| 
 | ||
| // succFact returns whether the receiver contains a successful factorization.
 | ||
| func (gsvd *GSVD) succFact() bool {
 | ||
| 	return gsvd.r != 0
 | ||
| }
 | ||
| 
 | ||
| // Factorize computes the generalized singular value decomposition (GSVD) of the input
 | ||
| // the r×c matrix A and the p×c matrix B. The singular values of A and B are computed
 | ||
| // in all cases, while the singular vectors are optionally computed depending on the
 | ||
| // input kind.
 | ||
| //
 | ||
| // The full singular value decomposition (kind == GSVDAll) deconstructs A and B as
 | ||
| //
 | ||
| //	A = U * Σ₁ * [ 0 R ] * Qᵀ
 | ||
| //
 | ||
| //	B = V * Σ₂ * [ 0 R ] * Qᵀ
 | ||
| //
 | ||
| // where Σ₁ and Σ₂ are r×(k+l) and p×(k+l) diagonal matrices of singular values, and
 | ||
| // U, V and Q are r×r, p×p and c×c orthogonal matrices of singular vectors. k+l is the
 | ||
| // effective numerical rank of the matrix [ Aᵀ Bᵀ ]ᵀ.
 | ||
| //
 | ||
| // It is frequently not necessary to compute the full GSVD. Computation time and
 | ||
| // storage costs can be reduced using the appropriate kind. Either only the singular
 | ||
| // values can be computed (kind == SVDNone), or in conjunction with specific singular
 | ||
| // vectors (kind bit set according to GSVDU, GSVDV and GSVDQ).
 | ||
| //
 | ||
| // Factorize returns whether the decomposition succeeded. If the decomposition
 | ||
| // failed, routines that require a successful factorization will panic.
 | ||
| func (gsvd *GSVD) Factorize(a, b Matrix, kind GSVDKind) (ok bool) {
 | ||
| 	// kill the previous decomposition
 | ||
| 	gsvd.r = 0
 | ||
| 	gsvd.kind = 0
 | ||
| 
 | ||
| 	r, c := a.Dims()
 | ||
| 	gsvd.r, gsvd.c = r, c
 | ||
| 	p, c := b.Dims()
 | ||
| 	gsvd.p = p
 | ||
| 	if gsvd.c != c {
 | ||
| 		panic(ErrShape)
 | ||
| 	}
 | ||
| 	var jobU, jobV, jobQ lapack.GSVDJob
 | ||
| 	switch {
 | ||
| 	default:
 | ||
| 		panic("gsvd: bad input kind")
 | ||
| 	case kind == GSVDNone:
 | ||
| 		jobU = lapack.GSVDNone
 | ||
| 		jobV = lapack.GSVDNone
 | ||
| 		jobQ = lapack.GSVDNone
 | ||
| 	case GSVDAll&kind != 0:
 | ||
| 		if GSVDU&kind != 0 {
 | ||
| 			jobU = lapack.GSVDU
 | ||
| 			gsvd.u = blas64.General{
 | ||
| 				Rows:   r,
 | ||
| 				Cols:   r,
 | ||
| 				Stride: r,
 | ||
| 				Data:   use(gsvd.u.Data, r*r),
 | ||
| 			}
 | ||
| 		}
 | ||
| 		if GSVDV&kind != 0 {
 | ||
| 			jobV = lapack.GSVDV
 | ||
| 			gsvd.v = blas64.General{
 | ||
| 				Rows:   p,
 | ||
| 				Cols:   p,
 | ||
| 				Stride: p,
 | ||
| 				Data:   use(gsvd.v.Data, p*p),
 | ||
| 			}
 | ||
| 		}
 | ||
| 		if GSVDQ&kind != 0 {
 | ||
| 			jobQ = lapack.GSVDQ
 | ||
| 			gsvd.q = blas64.General{
 | ||
| 				Rows:   c,
 | ||
| 				Cols:   c,
 | ||
| 				Stride: c,
 | ||
| 				Data:   use(gsvd.q.Data, c*c),
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	// A and B are destroyed on call, so copy the matrices.
 | ||
| 	aCopy := DenseCopyOf(a)
 | ||
| 	bCopy := DenseCopyOf(b)
 | ||
| 
 | ||
| 	gsvd.s1 = use(gsvd.s1, c)
 | ||
| 	gsvd.s2 = use(gsvd.s2, c)
 | ||
| 
 | ||
| 	gsvd.iwork = useInt(gsvd.iwork, c)
 | ||
| 
 | ||
| 	gsvd.work = use(gsvd.work, 1)
 | ||
| 	lapack64.Ggsvd3(jobU, jobV, jobQ, aCopy.mat, bCopy.mat, gsvd.s1, gsvd.s2, gsvd.u, gsvd.v, gsvd.q, gsvd.work, -1, gsvd.iwork)
 | ||
| 	gsvd.work = use(gsvd.work, int(gsvd.work[0]))
 | ||
| 	gsvd.k, gsvd.l, ok = lapack64.Ggsvd3(jobU, jobV, jobQ, aCopy.mat, bCopy.mat, gsvd.s1, gsvd.s2, gsvd.u, gsvd.v, gsvd.q, gsvd.work, len(gsvd.work), gsvd.iwork)
 | ||
| 	if ok {
 | ||
| 		gsvd.a = aCopy.mat
 | ||
| 		gsvd.b = bCopy.mat
 | ||
| 		gsvd.kind = kind
 | ||
| 	}
 | ||
| 	return ok
 | ||
| }
 | ||
| 
 | ||
| // Kind returns the GSVDKind of the decomposition. If no decomposition has been
 | ||
| // computed, Kind returns -1.
 | ||
| func (gsvd *GSVD) Kind() GSVDKind {
 | ||
| 	if !gsvd.succFact() {
 | ||
| 		return -1
 | ||
| 	}
 | ||
| 	return gsvd.kind
 | ||
| }
 | ||
| 
 | ||
| // Rank returns the k and l terms of the rank of [ Aᵀ Bᵀ ]ᵀ.
 | ||
| func (gsvd *GSVD) Rank() (k, l int) {
 | ||
| 	return gsvd.k, gsvd.l
 | ||
| }
 | ||
| 
 | ||
| // GeneralizedValues returns the generalized singular values of the factorized matrices.
 | ||
| // 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 min(r,c)-k, and GeneralizedValues will
 | ||
| // panic with ErrSliceLengthMismatch otherwise. If the input slice is nil,
 | ||
| // a new slice of the appropriate length will be allocated and returned.
 | ||
| //
 | ||
| // GeneralizedValues will panic if the receiver does not contain a successful factorization.
 | ||
| func (gsvd *GSVD) GeneralizedValues(v []float64) []float64 {
 | ||
| 	if !gsvd.succFact() {
 | ||
| 		panic(badFact)
 | ||
| 	}
 | ||
| 	r := gsvd.r
 | ||
| 	c := gsvd.c
 | ||
| 	k := gsvd.k
 | ||
| 	d := min(r, c)
 | ||
| 	if v == nil {
 | ||
| 		v = make([]float64, d-k)
 | ||
| 	}
 | ||
| 	if len(v) != d-k {
 | ||
| 		panic(ErrSliceLengthMismatch)
 | ||
| 	}
 | ||
| 	floats.DivTo(v, gsvd.s1[k:d], gsvd.s2[k:d])
 | ||
| 	return v
 | ||
| }
 | ||
| 
 | ||
| // ValuesA returns the singular values of the factorized A matrix.
 | ||
| // 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 min(r,c)-k, and ValuesA will panic with
 | ||
| // ErrSliceLengthMismatch otherwise. If the input slice is nil,
 | ||
| // a new slice of the appropriate length will be allocated and returned.
 | ||
| //
 | ||
| // ValuesA will panic if the receiver does not contain a successful factorization.
 | ||
| func (gsvd *GSVD) ValuesA(s []float64) []float64 {
 | ||
| 	if !gsvd.succFact() {
 | ||
| 		panic(badFact)
 | ||
| 	}
 | ||
| 	r := gsvd.r
 | ||
| 	c := gsvd.c
 | ||
| 	k := gsvd.k
 | ||
| 	d := min(r, c)
 | ||
| 	if s == nil {
 | ||
| 		s = make([]float64, d-k)
 | ||
| 	}
 | ||
| 	if len(s) != d-k {
 | ||
| 		panic(ErrSliceLengthMismatch)
 | ||
| 	}
 | ||
| 	copy(s, gsvd.s1[k:min(r, c)])
 | ||
| 	return s
 | ||
| }
 | ||
| 
 | ||
| // ValuesB returns the singular values of the factorized B matrix.
 | ||
| // 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 min(r,c)-k, and ValuesB will panic with
 | ||
| // ErrSliceLengthMismatch otherwise. If the input slice is nil,
 | ||
| // a new slice of the appropriate length will be allocated and returned.
 | ||
| //
 | ||
| // ValuesB will panic if the receiver does not contain a successful factorization.
 | ||
| func (gsvd *GSVD) ValuesB(s []float64) []float64 {
 | ||
| 	if !gsvd.succFact() {
 | ||
| 		panic(badFact)
 | ||
| 	}
 | ||
| 	r := gsvd.r
 | ||
| 	c := gsvd.c
 | ||
| 	k := gsvd.k
 | ||
| 	d := min(r, c)
 | ||
| 	if s == nil {
 | ||
| 		s = make([]float64, d-k)
 | ||
| 	}
 | ||
| 	if len(s) != d-k {
 | ||
| 		panic(ErrSliceLengthMismatch)
 | ||
| 	}
 | ||
| 	copy(s, gsvd.s2[k:d])
 | ||
| 	return s
 | ||
| }
 | ||
| 
 | ||
| // ZeroRTo extracts the matrix [ 0 R ] from the singular value decomposition,
 | ||
| // storing the result into dst. [ 0 R ] is of size (k+l)×c.
 | ||
| //
 | ||
| // If dst is empty, ZeroRTo will resize dst to be (k+l)×c. When dst is
 | ||
| // non-empty, ZeroRTo will panic if dst is not (k+l)×c. ZeroRTo will also panic
 | ||
| // if the receiver does not contain a successful factorization.
 | ||
| func (gsvd *GSVD) ZeroRTo(dst *Dense) {
 | ||
| 	if !gsvd.succFact() {
 | ||
| 		panic(badFact)
 | ||
| 	}
 | ||
| 	r := gsvd.r
 | ||
| 	c := gsvd.c
 | ||
| 	k := gsvd.k
 | ||
| 	l := gsvd.l
 | ||
| 	h := min(k+l, r)
 | ||
| 	if dst.IsEmpty() {
 | ||
| 		dst.ReuseAs(k+l, c)
 | ||
| 	} else {
 | ||
| 		r2, c2 := dst.Dims()
 | ||
| 		if r2 != k+l || c != c2 {
 | ||
| 			panic(ErrShape)
 | ||
| 		}
 | ||
| 		dst.Zero()
 | ||
| 	}
 | ||
| 	a := Dense{
 | ||
| 		mat:     gsvd.a,
 | ||
| 		capRows: r,
 | ||
| 		capCols: c,
 | ||
| 	}
 | ||
| 	dst.slice(0, h, c-k-l, c).Copy(a.Slice(0, h, c-k-l, c))
 | ||
| 	if r < k+l {
 | ||
| 		b := Dense{
 | ||
| 			mat:     gsvd.b,
 | ||
| 			capRows: gsvd.p,
 | ||
| 			capCols: c,
 | ||
| 		}
 | ||
| 		dst.slice(r, k+l, c+r-k-l, c).Copy(b.Slice(r-k, l, c+r-k-l, c))
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // SigmaATo extracts the matrix Σ₁ from the singular value decomposition, storing
 | ||
| // the result into dst. Σ₁ is size r×(k+l).
 | ||
| //
 | ||
| // If dst is empty, SigmaATo will resize dst to be r×(k+l). When dst is
 | ||
| // non-empty, SigmATo will panic if dst is not r×(k+l). SigmaATo will also
 | ||
| // panic if the receiver does not contain a successful factorization.
 | ||
| func (gsvd *GSVD) SigmaATo(dst *Dense) {
 | ||
| 	if !gsvd.succFact() {
 | ||
| 		panic(badFact)
 | ||
| 	}
 | ||
| 	r := gsvd.r
 | ||
| 	k := gsvd.k
 | ||
| 	l := gsvd.l
 | ||
| 	if dst.IsEmpty() {
 | ||
| 		dst.ReuseAs(r, k+l)
 | ||
| 	} else {
 | ||
| 		r2, c := dst.Dims()
 | ||
| 		if r2 != r || c != k+l {
 | ||
| 			panic(ErrShape)
 | ||
| 		}
 | ||
| 		dst.Zero()
 | ||
| 	}
 | ||
| 	for i := 0; i < k; i++ {
 | ||
| 		dst.set(i, i, 1)
 | ||
| 	}
 | ||
| 	for i := k; i < min(r, k+l); i++ {
 | ||
| 		dst.set(i, i, gsvd.s1[i])
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // SigmaBTo extracts the matrix Σ₂ from the singular value decomposition, storing
 | ||
| // the result into dst. Σ₂ is size p×(k+l).
 | ||
| //
 | ||
| // If dst is empty, SigmaBTo will resize dst to be p×(k+l). When dst is
 | ||
| // non-empty, SigmBTo will panic if dst is not p×(k+l). SigmaBTo will also
 | ||
| // panic if the receiver does not contain a successful factorization.
 | ||
| func (gsvd *GSVD) SigmaBTo(dst *Dense) {
 | ||
| 	if !gsvd.succFact() {
 | ||
| 		panic(badFact)
 | ||
| 	}
 | ||
| 	r := gsvd.r
 | ||
| 	p := gsvd.p
 | ||
| 	k := gsvd.k
 | ||
| 	l := gsvd.l
 | ||
| 	if dst.IsEmpty() {
 | ||
| 		dst.ReuseAs(p, k+l)
 | ||
| 	} else {
 | ||
| 		r, c := dst.Dims()
 | ||
| 		if r != p || c != k+l {
 | ||
| 			panic(ErrShape)
 | ||
| 		}
 | ||
| 		dst.Zero()
 | ||
| 	}
 | ||
| 	for i := 0; i < min(l, r-k); i++ {
 | ||
| 		dst.set(i, i+k, gsvd.s2[k+i])
 | ||
| 	}
 | ||
| 	for i := r - k; i < l; i++ {
 | ||
| 		dst.set(i, i+k, 1)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // UTo extracts the matrix U from the singular value decomposition, storing
 | ||
| // the result into dst. U is size r×r.
 | ||
| //
 | ||
| // If dst is empty, UTo will resize dst to be r×r. When dst is
 | ||
| // non-empty, UTo will panic if dst is not r×r. UTo will also
 | ||
| // panic if the receiver does not contain a successful factorization.
 | ||
| func (gsvd *GSVD) UTo(dst *Dense) {
 | ||
| 	if !gsvd.succFact() {
 | ||
| 		panic(badFact)
 | ||
| 	}
 | ||
| 	if gsvd.kind&GSVDU == 0 {
 | ||
| 		panic("mat: improper GSVD kind")
 | ||
| 	}
 | ||
| 	r := gsvd.u.Rows
 | ||
| 	c := gsvd.u.Cols
 | ||
| 	if dst.IsEmpty() {
 | ||
| 		dst.ReuseAs(r, c)
 | ||
| 	} else {
 | ||
| 		r2, c2 := dst.Dims()
 | ||
| 		if r != r2 || c != c2 {
 | ||
| 			panic(ErrShape)
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	tmp := &Dense{
 | ||
| 		mat:     gsvd.u,
 | ||
| 		capRows: r,
 | ||
| 		capCols: c,
 | ||
| 	}
 | ||
| 	dst.Copy(tmp)
 | ||
| }
 | ||
| 
 | ||
| // VTo extracts the matrix V from the singular value decomposition, storing
 | ||
| // the result into dst. V is size p×p.
 | ||
| //
 | ||
| // If dst is empty, VTo will resize dst to be p×p. When dst is
 | ||
| // non-empty, VTo will panic if dst is not p×p. VTo will also
 | ||
| // panic if the receiver does not contain a successful factorization.
 | ||
| func (gsvd *GSVD) VTo(dst *Dense) {
 | ||
| 	if !gsvd.succFact() {
 | ||
| 		panic(badFact)
 | ||
| 	}
 | ||
| 	if gsvd.kind&GSVDV == 0 {
 | ||
| 		panic("mat: improper GSVD kind")
 | ||
| 	}
 | ||
| 	r := gsvd.v.Rows
 | ||
| 	c := gsvd.v.Cols
 | ||
| 	if dst.IsEmpty() {
 | ||
| 		dst.ReuseAs(r, c)
 | ||
| 	} else {
 | ||
| 		r2, c2 := dst.Dims()
 | ||
| 		if r != r2 || c != c2 {
 | ||
| 			panic(ErrShape)
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	tmp := &Dense{
 | ||
| 		mat:     gsvd.v,
 | ||
| 		capRows: r,
 | ||
| 		capCols: c,
 | ||
| 	}
 | ||
| 	dst.Copy(tmp)
 | ||
| }
 | ||
| 
 | ||
| // QTo extracts the matrix Q from the singular value decomposition, storing
 | ||
| // the result into dst. Q is size c×c.
 | ||
| //
 | ||
| // If dst is empty, QTo will resize dst to be c×c. When dst is
 | ||
| // non-empty, QTo will panic if dst is not c×c. QTo will also
 | ||
| // panic if the receiver does not contain a successful factorization.
 | ||
| func (gsvd *GSVD) QTo(dst *Dense) {
 | ||
| 	if !gsvd.succFact() {
 | ||
| 		panic(badFact)
 | ||
| 	}
 | ||
| 	if gsvd.kind&GSVDQ == 0 {
 | ||
| 		panic("mat: improper GSVD kind")
 | ||
| 	}
 | ||
| 	r := gsvd.q.Rows
 | ||
| 	c := gsvd.q.Cols
 | ||
| 	if dst.IsEmpty() {
 | ||
| 		dst.ReuseAs(r, c)
 | ||
| 	} else {
 | ||
| 		r2, c2 := dst.Dims()
 | ||
| 		if r != r2 || c != c2 {
 | ||
| 			panic(ErrShape)
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	tmp := &Dense{
 | ||
| 		mat:     gsvd.q,
 | ||
| 		capRows: r,
 | ||
| 		capCols: c,
 | ||
| 	}
 | ||
| 	dst.Copy(tmp)
 | ||
| }
 |