mirror of https://github.com/k3s-io/k3s
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 matrix.GSVDU, matrix.GSVDV and matrix.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 matrix.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
|
||
// matrix.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
|
||
// matrix.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).(*Dense).
|
||
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).(*Dense).
|
||
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)
|
||
}
|