123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413 |
- // Copyright ©2013 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/lapack"
- "gonum.org/v1/gonum/lapack/lapack64"
- )
- const badRcond = "mat: invalid rcond value"
- // SVD is a type for creating and using the Singular Value Decomposition
- // of a matrix.
- type SVD struct {
- kind SVDKind
- s []float64
- u blas64.General
- vt blas64.General
- }
- // SVDKind specifies the treatment of singular vectors during an SVD
- // factorization.
- type SVDKind int
- const (
- // SVDNone specifies that no singular vectors should be computed during
- // the decomposition.
- SVDNone SVDKind = 0
- // SVDThinU specifies the thin decomposition for U should be computed.
- SVDThinU SVDKind = 1 << (iota - 1)
- // SVDFullU specifies the full decomposition for U should be computed.
- SVDFullU
- // SVDThinV specifies the thin decomposition for V should be computed.
- SVDThinV
- // SVDFullV specifies the full decomposition for V should be computed.
- SVDFullV
- // SVDThin is a convenience value for computing both thin vectors.
- SVDThin SVDKind = SVDThinU | SVDThinV
- // SVDFull is a convenience value for computing both full vectors.
- SVDFull SVDKind = SVDFullU | SVDFullV
- )
- // succFact returns whether the receiver contains a successful factorization.
- func (svd *SVD) succFact() bool {
- return len(svd.s) != 0
- }
- // Factorize computes the singular value decomposition (SVD) of the input matrix A.
- // The singular values of A are computed in all cases, while the singular
- // vectors are optionally computed depending on the input kind.
- //
- // The full singular value decomposition (kind == SVDFull) is a factorization
- // of an m×n matrix A of the form
- // A = U * Σ * Vᵀ
- // where Σ is an m×n diagonal matrix, U is an m×m orthogonal matrix, and V is an
- // n×n orthogonal matrix. The diagonal elements of Σ are the singular values of A.
- // The first min(m,n) columns of U and V are, respectively, the left and right
- // singular vectors of A.
- //
- // Significant storage space can be saved by using the thin representation of
- // the SVD (kind == SVDThin) instead of the full SVD, especially if
- // m >> n or m << n. The thin SVD finds
- // A = U~ * Σ * V~ᵀ
- // where U~ is of size m×min(m,n), Σ is a diagonal matrix of size min(m,n)×min(m,n)
- // and V~ is of size n×min(m,n).
- //
- // Factorize returns whether the decomposition succeeded. If the decomposition
- // failed, routines that require a successful factorization will panic.
- func (svd *SVD) Factorize(a Matrix, kind SVDKind) (ok bool) {
- // kill previous factorization
- svd.s = svd.s[:0]
- svd.kind = kind
- m, n := a.Dims()
- var jobU, jobVT lapack.SVDJob
- // TODO(btracey): This code should be modified to have the smaller
- // matrix written in-place into aCopy when the lapack/native/dgesvd
- // implementation is complete.
- switch {
- case kind&SVDFullU != 0:
- jobU = lapack.SVDAll
- svd.u = blas64.General{
- Rows: m,
- Cols: m,
- Stride: m,
- Data: use(svd.u.Data, m*m),
- }
- case kind&SVDThinU != 0:
- jobU = lapack.SVDStore
- svd.u = blas64.General{
- Rows: m,
- Cols: min(m, n),
- Stride: min(m, n),
- Data: use(svd.u.Data, m*min(m, n)),
- }
- default:
- jobU = lapack.SVDNone
- }
- switch {
- case kind&SVDFullV != 0:
- svd.vt = blas64.General{
- Rows: n,
- Cols: n,
- Stride: n,
- Data: use(svd.vt.Data, n*n),
- }
- jobVT = lapack.SVDAll
- case kind&SVDThinV != 0:
- svd.vt = blas64.General{
- Rows: min(m, n),
- Cols: n,
- Stride: n,
- Data: use(svd.vt.Data, min(m, n)*n),
- }
- jobVT = lapack.SVDStore
- default:
- jobVT = lapack.SVDNone
- }
- // A is destroyed on call, so copy the matrix.
- aCopy := DenseCopyOf(a)
- svd.kind = kind
- svd.s = use(svd.s, min(m, n))
- work := []float64{0}
- lapack64.Gesvd(jobU, jobVT, aCopy.mat, svd.u, svd.vt, svd.s, work, -1)
- work = getFloats(int(work[0]), false)
- ok = lapack64.Gesvd(jobU, jobVT, aCopy.mat, svd.u, svd.vt, svd.s, work, len(work))
- putFloats(work)
- if !ok {
- svd.kind = 0
- }
- return ok
- }
- // Kind returns the SVDKind of the decomposition. If no decomposition has been
- // computed, Kind returns -1.
- func (svd *SVD) Kind() SVDKind {
- if !svd.succFact() {
- return -1
- }
- return svd.kind
- }
- // Rank returns the rank of A based on the count of singular values greater than
- // rcond scaled by the largest singular value.
- // Rank will panic if the receiver does not contain a successful factorization or
- // rcond is negative.
- func (svd *SVD) Rank(rcond float64) int {
- if rcond < 0 {
- panic(badRcond)
- }
- if !svd.succFact() {
- panic(badFact)
- }
- s0 := svd.s[0]
- for i, v := range svd.s {
- if v <= rcond*s0 {
- return i
- }
- }
- return len(svd.s)
- }
- // Cond returns the 2-norm condition number for the factorized matrix. Cond will
- // panic if the receiver does not contain a successful factorization.
- func (svd *SVD) Cond() float64 {
- if !svd.succFact() {
- panic(badFact)
- }
- return svd.s[0] / svd.s[len(svd.s)-1]
- }
- // Values returns the singular values of the factorized matrix in descending order.
- //
- // 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(m,n), 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 (svd *SVD) Values(s []float64) []float64 {
- if !svd.succFact() {
- panic(badFact)
- }
- if s == nil {
- s = make([]float64, len(svd.s))
- }
- if len(s) != len(svd.s) {
- panic(ErrSliceLengthMismatch)
- }
- copy(s, svd.s)
- return s
- }
- // UTo extracts the matrix U from the singular value decomposition. The first
- // min(m,n) columns are the left singular vectors and correspond to the singular
- // values as returned from SVD.Values.
- //
- // If dst is empty, UTo will resize dst to be m×m if the full U was computed
- // and size m×min(m,n) if the thin U was computed. When dst is non-empty, then
- // UTo will panic if dst is not the appropriate size. UTo will also panic if
- // the receiver does not contain a successful factorization, or if U was
- // not computed during factorization.
- func (svd *SVD) UTo(dst *Dense) {
- if !svd.succFact() {
- panic(badFact)
- }
- kind := svd.kind
- if kind&SVDThinU == 0 && kind&SVDFullU == 0 {
- panic("svd: u not computed during factorization")
- }
- r := svd.u.Rows
- c := svd.u.Cols
- if dst.IsEmpty() {
- dst.ReuseAs(r, c)
- } else {
- r2, c2 := dst.Dims()
- if r != r2 || c != c2 {
- panic(ErrShape)
- }
- }
- tmp := &Dense{
- mat: svd.u,
- capRows: r,
- capCols: c,
- }
- dst.Copy(tmp)
- }
- // VTo extracts the matrix V from the singular value decomposition. The first
- // min(m,n) columns are the right singular vectors and correspond to the singular
- // values as returned from SVD.Values.
- //
- // If dst is empty, VTo will resize dst to be n×n if the full V was computed
- // and size n×min(m,n) if the thin V was computed. When dst is non-empty, then
- // VTo will panic if dst is not the appropriate size. VTo will also panic if
- // the receiver does not contain a successful factorization, or if V was
- // not computed during factorization.
- func (svd *SVD) VTo(dst *Dense) {
- if !svd.succFact() {
- panic(badFact)
- }
- kind := svd.kind
- if kind&SVDThinV == 0 && kind&SVDFullV == 0 {
- panic("svd: v not computed during factorization")
- }
- r := svd.vt.Rows
- c := svd.vt.Cols
- if dst.IsEmpty() {
- dst.ReuseAs(c, r)
- } else {
- r2, c2 := dst.Dims()
- if c != r2 || r != c2 {
- panic(ErrShape)
- }
- }
- tmp := &Dense{
- mat: svd.vt,
- capRows: r,
- capCols: c,
- }
- dst.Copy(tmp.T())
- }
- // SolveTo calculates the minimum-norm solution to a linear least squares problem
- // minimize over n-element vectors x: |b - A*x|_2 and |x|_2
- // where b is a given m-element vector, using the SVD of m×n matrix A stored in
- // the receiver. A may be rank-deficient, that is, the given effective rank can be
- // rank ≤ min(m,n)
- // The rank can be computed using SVD.Rank.
- //
- // Several right-hand side vectors b and solution vectors x can be handled in a
- // single call. Vectors b are stored in the columns of the m×k matrix B and the
- // resulting vectors x will be stored in the columns of dst. dst must be either
- // empty or have the size equal to n×k.
- //
- // The decomposition must have been factorized computing both the U and V
- // singular vectors.
- //
- // SolveTo returns the residuals calculated from the complete SVD. For this
- // value to be valid the factorization must have been performed with at least
- // SVDFullU.
- func (svd *SVD) SolveTo(dst *Dense, b Matrix, rank int) []float64 {
- if !svd.succFact() {
- panic(badFact)
- }
- if rank < 1 || len(svd.s) < rank {
- panic("svd: rank out of range")
- }
- kind := svd.kind
- if kind&SVDThinU == 0 && kind&SVDFullU == 0 {
- panic("svd: u not computed during factorization")
- }
- if kind&SVDThinV == 0 && kind&SVDFullV == 0 {
- panic("svd: v not computed during factorization")
- }
- u := Dense{
- mat: svd.u,
- capRows: svd.u.Rows,
- capCols: svd.u.Cols,
- }
- vt := Dense{
- mat: svd.vt,
- capRows: svd.vt.Rows,
- capCols: svd.vt.Cols,
- }
- s := svd.s[:rank]
- _, bc := b.Dims()
- c := getWorkspace(svd.u.Cols, bc, false)
- defer putWorkspace(c)
- c.Mul(u.T(), b)
- y := getWorkspace(rank, bc, false)
- defer putWorkspace(y)
- y.DivElem(c.slice(0, rank, 0, bc), repVector{vec: s, cols: bc})
- dst.Mul(vt.slice(0, rank, 0, svd.vt.Cols).T(), y)
- res := make([]float64, bc)
- if rank < svd.u.Cols {
- c = c.slice(len(s), svd.u.Cols, 0, bc)
- for j := range res {
- col := c.ColView(j)
- res[j] = Dot(col, col)
- }
- }
- return res
- }
- type repVector struct {
- vec []float64
- cols int
- }
- func (m repVector) Dims() (r, c int) { return len(m.vec), m.cols }
- func (m repVector) At(i, j int) float64 {
- if i < 0 || len(m.vec) <= i || j < 0 || m.cols <= j {
- panic(ErrIndexOutOfRange.string) // Panic with string to prevent mat.Error recovery.
- }
- return m.vec[i]
- }
- func (m repVector) T() Matrix { return Transpose{m} }
- // SolveVecTo calculates the minimum-norm solution to a linear least squares problem
- // minimize over n-element vectors x: |b - A*x|_2 and |x|_2
- // where b is a given m-element vector, using the SVD of m×n matrix A stored in
- // the receiver. A may be rank-deficient, that is, the given effective rank can be
- // rank ≤ min(m,n)
- // The rank can be computed using SVD.Rank.
- //
- // The resulting vector x will be stored in dst. dst must be either empty or
- // have length equal to n.
- //
- // The decomposition must have been factorized computing both the U and V
- // singular vectors.
- //
- // SolveVecTo returns the residuals calculated from the complete SVD. For this
- // value to be valid the factorization must have been performed with at least
- // SVDFullU.
- func (svd *SVD) SolveVecTo(dst *VecDense, b Vector, rank int) float64 {
- if !svd.succFact() {
- panic(badFact)
- }
- if rank < 1 || len(svd.s) < rank {
- panic("svd: rank out of range")
- }
- kind := svd.kind
- if kind&SVDThinU == 0 && kind&SVDFullU == 0 {
- panic("svd: u not computed during factorization")
- }
- if kind&SVDThinV == 0 && kind&SVDFullV == 0 {
- panic("svd: v not computed during factorization")
- }
- u := Dense{
- mat: svd.u,
- capRows: svd.u.Rows,
- capCols: svd.u.Cols,
- }
- vt := Dense{
- mat: svd.vt,
- capRows: svd.vt.Rows,
- capCols: svd.vt.Cols,
- }
- s := svd.s[:rank]
- c := getWorkspaceVec(svd.u.Cols, false)
- defer putWorkspaceVec(c)
- c.MulVec(u.T(), b)
- y := getWorkspaceVec(rank, false)
- defer putWorkspaceVec(y)
- y.DivElemVec(c.sliceVec(0, rank), NewVecDense(rank, s))
- dst.MulVec(vt.slice(0, rank, 0, svd.vt.Cols).T(), y)
- var res float64
- if rank < c.Len() {
- c = c.sliceVec(rank, c.Len())
- res = Dot(c, c)
- }
- return res
- }
|