matrix/mat64: provide sugar for easy matrix extraction

This commit is contained in:
kortschak
2017-05-27 08:05:00 +09:30
committed by Dan Kortschak
parent 15641d34f4
commit 67bc20e87d
14 changed files with 166 additions and 80 deletions

View File

@@ -205,7 +205,8 @@ func (gsvd *GSVD) ValuesB(s []float64) []float64 {
// ZeroRTo extracts the matrix [ 0 R ] from the singular value decomposition, storing
// the result in-place into dst. [ 0 R ] is size (k+l)×c.
func (gsvd *GSVD) ZeroRTo(dst *Dense) {
// If dst is nil, a new matrix is allocated. The resulting ZeroR matrix is returned.
func (gsvd *GSVD) ZeroRTo(dst *Dense) *Dense {
if gsvd.kind == 0 {
panic("gsvd: no decomposition computed")
}
@@ -214,7 +215,11 @@ func (gsvd *GSVD) ZeroRTo(dst *Dense) {
k := gsvd.k
l := gsvd.l
h := min(k+l, r)
if dst == nil {
dst = NewDense(k+l, c, nil)
} else {
dst.reuseAsZeroed(k+l, c)
}
a := Dense{
mat: gsvd.a,
capRows: r,
@@ -231,29 +236,37 @@ func (gsvd *GSVD) ZeroRTo(dst *Dense) {
dst.Slice(r, k+l, c+r-k-l, c).(*Dense).
Copy(b.Slice(r-k, l, c+r-k-l, c))
}
return dst
}
// SigmaATo extracts the matrix Σ₁ from the singular value decomposition, storing
// the result in-place into dst. Σ₁ is size r×(k+l).
func (gsvd *GSVD) SigmaATo(dst *Dense) {
// If dst is nil, a new matrix is allocated. The resulting SigmaA matrix is returned.
func (gsvd *GSVD) SigmaATo(dst *Dense) *Dense {
if gsvd.kind == 0 {
panic("gsvd: no decomposition computed")
}
r := gsvd.r
k := gsvd.k
l := gsvd.l
if dst == nil {
dst = NewDense(r, k+l, nil)
} else {
dst.reuseAsZeroed(r, k+l)
}
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])
}
return dst
}
// SigmaBTo extracts the matrix Σ₂ from the singular value decomposition, storing
// the result in-place into dst. Σ₂ is size p×(k+l).
func (gsvd *GSVD) SigmaBTo(dst *Dense) {
// If dst is nil, a new matrix is allocated. The resulting SigmaB matrix is returned.
func (gsvd *GSVD) SigmaBTo(dst *Dense) *Dense {
if gsvd.kind == 0 {
panic("gsvd: no decomposition computed")
}
@@ -261,24 +274,34 @@ func (gsvd *GSVD) SigmaBTo(dst *Dense) {
p := gsvd.p
k := gsvd.k
l := gsvd.l
if dst == nil {
dst = NewDense(p, k+l, nil)
} else {
dst.reuseAsZeroed(p, k+l)
}
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)
}
return dst
}
// UTo extracts the matrix U from the singular value decomposition, storing
// the result in-place into dst. U is size r×r.
func (gsvd *GSVD) UTo(dst *Dense) {
// If dst is nil, a new matrix is allocated. The resulting U matrix is returned.
func (gsvd *GSVD) UTo(dst *Dense) *Dense {
if gsvd.kind&matrix.GSVDU == 0 {
panic("mat64: improper GSVD kind")
}
r := gsvd.u.Rows
c := gsvd.u.Cols
if dst == nil {
dst = NewDense(r, c, nil)
} else {
dst.reuseAs(r, c)
}
tmp := &Dense{
mat: gsvd.u,
@@ -286,17 +309,23 @@ func (gsvd *GSVD) UTo(dst *Dense) {
capCols: c,
}
dst.Copy(tmp)
return dst
}
// VTo extracts the matrix V from the singular value decomposition, storing
// the result in-place into dst. V is size p×p.
func (gsvd *GSVD) VTo(dst *Dense) {
// If dst is nil, a new matrix is allocated. The resulting V matrix is returned.
func (gsvd *GSVD) VTo(dst *Dense) *Dense {
if gsvd.kind&matrix.GSVDV == 0 {
panic("mat64: improper GSVD kind")
}
r := gsvd.v.Rows
c := gsvd.v.Cols
if dst == nil {
dst = NewDense(r, c, nil)
} else {
dst.reuseAs(r, c)
}
tmp := &Dense{
mat: gsvd.v,
@@ -304,17 +333,23 @@ func (gsvd *GSVD) VTo(dst *Dense) {
capCols: c,
}
dst.Copy(tmp)
return dst
}
// QTo extracts the matrix Q from the singular value decomposition, storing
// the result in-place into dst. Q is size c×c.
func (gsvd *GSVD) QTo(dst *Dense) {
// If dst is nil, a new matrix is allocated. The resulting Q matrix is returned.
func (gsvd *GSVD) QTo(dst *Dense) *Dense {
if gsvd.kind&matrix.GSVDQ == 0 {
panic("mat64: improper GSVD kind")
}
r := gsvd.q.Rows
c := gsvd.q.Cols
if dst == nil {
dst = NewDense(r, c, nil)
} else {
dst.reuseAs(r, c)
}
tmp := &Dense{
mat: gsvd.q,
@@ -322,4 +357,5 @@ func (gsvd *GSVD) QTo(dst *Dense) {
capCols: c,
}
dst.Copy(tmp)
return dst
}

View File

@@ -25,22 +25,19 @@ func ExampleGSVD() {
log.Fatal("GSVD factorization failed")
}
var u, v mat64.Dense
gsvd.UTo(&u)
gsvd.VTo(&v)
u := gsvd.UTo(nil)
v := gsvd.VTo(nil)
s1 := gsvd.ValuesA(nil)
s2 := gsvd.ValuesB(nil)
fmt.Printf("Africa\n\ts1 = %.4f\n\n\tU = %.4f\n\n",
s1, mat64.Formatted(&u, mat64.Prefix("\t "), mat64.Excerpt(2)))
s1, mat64.Formatted(u, mat64.Prefix("\t "), mat64.Excerpt(2)))
fmt.Printf("Latin America/Caribbean\n\ts2 = %.4f\n\n\tV = %.4f\n",
s2, mat64.Formatted(&v, mat64.Prefix("\t "), mat64.Excerpt(2)))
s2, mat64.Formatted(v, mat64.Prefix("\t "), mat64.Excerpt(2)))
var zeroR, q mat64.Dense
gsvd.ZeroRTo(&zeroR)
gsvd.QTo(&q)
q.Mul(&zeroR, &q)
var q mat64.Dense
q.Mul(gsvd.ZeroRTo(nil), gsvd.QTo(nil))
fmt.Printf("\nCommon basis vectors\n\n\tQ^T = %.4f\n",
mat64.Formatted(q.T(), mat64.Prefix("\t ")))

View File

@@ -106,14 +106,13 @@ func TestGSVD(t *testing.T) {
}
func extractGSVD(gsvd *GSVD) (c, s []float64, s1, s2, zR, u, v, q *Dense) {
var s1m, s2m, zeroR, um, vm, qm Dense
gsvd.SigmaATo(&s1m)
gsvd.SigmaBTo(&s2m)
gsvd.ZeroRTo(&zeroR)
gsvd.UTo(&um)
gsvd.VTo(&vm)
gsvd.QTo(&qm)
s1 = gsvd.SigmaATo(nil)
s2 = gsvd.SigmaBTo(nil)
zR = gsvd.ZeroRTo(nil)
u = gsvd.UTo(nil)
v = gsvd.VTo(nil)
q = gsvd.QTo(nil)
c = gsvd.ValuesA(nil)
s = gsvd.ValuesB(nil)
return c, s, &s1m, &s2m, &zeroR, &um, &vm, &qm
return c, s, s1, s2, zR, u, v, q
}

View File

@@ -143,9 +143,10 @@ func (gsvd *HOGSVD) Len() int {
// 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 nil, a new matrix is allocated. The resulting U matrix is returned.
//
// UTo will panic if the receiver does not contain a successful factorization.
func (gsvd *HOGSVD) UTo(dst *Dense, n int) {
func (gsvd *HOGSVD) UTo(dst *Dense, n int) *Dense {
if gsvd.n == 0 {
panic("hogsvd: unsuccessful factorization")
}
@@ -153,12 +154,18 @@ func (gsvd *HOGSVD) UTo(dst *Dense, n int) {
panic("hogsvd: invalid index")
}
if dst == nil {
r, c := gsvd.b[n].Dims()
dst = NewDense(r, c, nil)
} else {
dst.reuseAs(gsvd.b[n].Dims())
}
dst.Copy(&gsvd.b[n])
for j, f := range gsvd.Values(nil, n) {
v := dst.ColView(j)
v.ScaleVec(1/f, v)
}
return dst
}
// Values returns the nth set of singular values of the factorized system.
@@ -190,12 +197,19 @@ func (gsvd *HOGSVD) Values(s []float64, n int) []float64 {
// 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 nil, a new matrix is allocated. The resulting V matrix is returned.
//
// VTo will panic if the receiver does not contain a successful factorization.
func (gsvd *HOGSVD) VTo(dst *Dense) {
func (gsvd *HOGSVD) VTo(dst *Dense) *Dense {
if gsvd.n == 0 {
panic("hogsvd: unsuccessful factorization")
}
if dst == nil {
r, c := gsvd.v.Dims()
dst = NewDense(r, c, nil)
} else {
dst.reuseAs(gsvd.v.Dims())
}
dst.Copy(gsvd.v)
return dst
}

View File

@@ -24,15 +24,13 @@ func ExampleHOGSVD() {
}
for i, n := range []string{"Africa", "Asia", "Latin America/Caribbean", "Oceania"} {
var u mat64.Dense
gsvd.UTo(&u, i)
u := gsvd.UTo(nil, i)
s := gsvd.Values(nil, i)
fmt.Printf("%s\n\ts_%d = %.4f\n\n\tU_%[2]d = %.4[4]f\n",
n, i, s, mat64.Formatted(&u, mat64.Prefix("\t ")))
n, i, s, mat64.Formatted(u, mat64.Prefix("\t ")))
}
var v mat64.Dense
gsvd.VTo(&v)
v := gsvd.VTo(nil)
fmt.Printf("\nCommon basis vectors\n\n\tV^T = %.4f",
mat64.Formatted(v.T(), mat64.Prefix("\t ")))

View File

@@ -79,11 +79,9 @@ func extractHOGSVD(gsvd *HOGSVD) (u []*Dense, s [][]float64, v *Dense) {
u = make([]*Dense, gsvd.Len())
s = make([][]float64, gsvd.Len())
for i := 0; i < gsvd.Len(); i++ {
u[i] = &Dense{}
gsvd.UTo(u[i], i)
u[i] = gsvd.UTo(nil, i)
s[i] = gsvd.Values(nil, i)
}
v = &Dense{}
gsvd.VTo(v)
v = gsvd.VTo(nil)
return u, s, v
}

View File

@@ -59,11 +59,16 @@ func (lq *LQ) Factorize(a Matrix) {
// and upper triangular matrices.
// LTo extracts the m×n lower trapezoidal matrix from a LQ decomposition.
func (lq *LQ) LTo(dst *Dense) {
// If dst is nil, a new matrix is allocated. The resulting L matrix is returned.
func (lq *LQ) LTo(dst *Dense) *Dense {
r, c := lq.lq.Dims()
if dst == nil {
dst = NewDense(r, c, nil)
} else {
dst.reuseAs(r, c)
}
// Disguise the LQ as a lower triangular
// Disguise the LQ as a lower triangular.
t := &TriDense{
mat: blas64.Triangular{
N: r,
@@ -77,18 +82,25 @@ func (lq *LQ) LTo(dst *Dense) {
dst.Copy(t)
if r == c {
return
return dst
}
// Zero right of the triangular.
for i := 0; i < r; i++ {
zero(dst.mat.Data[i*dst.mat.Stride+r : i*dst.mat.Stride+c])
}
return dst
}
// QTo extracts the n×n orthonormal matrix Q from an LQ decomposition.
func (lq *LQ) QTo(dst *Dense) {
// If dst is nil, a new matrix is allocated. The resulting Q matrix is returned.
func (lq *LQ) QTo(dst *Dense) *Dense {
r, c := lq.lq.Dims()
if dst == nil {
dst = NewDense(c, c, nil)
} else {
dst.reuseAs(c, c)
}
// Set Q = I.
for i := 0; i < c; i++ {
@@ -132,6 +144,8 @@ func (lq *LQ) QTo(dst *Dense) {
1, h, qCopy.mat,
0, dst.mat)
}
return dst
}
// SolveLQ finds a minimum-norm solution to a system of linear equations defined

View File

@@ -29,17 +29,16 @@ func TestLQ(t *testing.T) {
var lq LQ
lq.Factorize(a)
var l, q Dense
lq.QTo(&q)
q := lq.QTo(nil)
if !isOrthonormal(&q, 1e-10) {
if !isOrthonormal(q, 1e-10) {
t.Errorf("Q is not orthonormal: m = %v, n = %v", m, n)
}
lq.LTo(&l)
l := lq.LTo(nil)
var got Dense
got.Mul(&l, &q)
got.Mul(l, q)
if !EqualApprox(&got, &want, 1e-12) {
t.Errorf("LQ does not equal original matrix. \nWant: %v\nGot: %v", want, got)
}

View File

@@ -205,9 +205,14 @@ func (lu *LU) RankOne(orig *LU, alpha float64, x, y *Vector) {
}
// LTo extracts the lower triangular matrix from an LU factorization.
func (lu *LU) LTo(dst *TriDense) {
// If dst is nil, a new matrix is allocated. The resulting L matrix is returned.
func (lu *LU) LTo(dst *TriDense) *TriDense {
_, n := lu.lu.Dims()
dst.reuseAs(n, false)
if dst == nil {
dst = NewTriDense(n, matrix.Lower, nil)
} else {
dst.reuseAs(n, matrix.Lower)
}
// Extract the lower triangular elements.
for i := 0; i < n; i++ {
for j := 0; j < i; j++ {
@@ -218,18 +223,25 @@ func (lu *LU) LTo(dst *TriDense) {
for i := 0; i < n; i++ {
dst.mat.Data[i*dst.mat.Stride+i] = 1
}
return dst
}
// UTo extracts the upper triangular matrix from an LU factorization.
func (lu *LU) UTo(dst *TriDense) {
// If dst is nil, a new matrix is allocated. The resulting U matrix is returned.
func (lu *LU) UTo(dst *TriDense) *TriDense {
_, n := lu.lu.Dims()
dst.reuseAs(n, true)
if dst == nil {
dst = NewTriDense(n, matrix.Upper, nil)
} else {
dst.reuseAs(n, matrix.Upper)
}
// Extract the upper triangular elements.
for i := 0; i < n; i++ {
for j := i; j < n; j++ {
dst.mat.Data[i*dst.mat.Stride+j] = lu.lu.mat.Data[i*lu.lu.mat.Stride+j]
}
}
return dst
}
// Permutation constructs an r×r permutation matrix with the given row swaps.

View File

@@ -23,15 +23,13 @@ func TestLUD(t *testing.T) {
var lu LU
lu.Factorize(a)
var l, u TriDense
lu.LTo(&l)
lu.UTo(&u)
l := lu.LTo(nil)
u := lu.UTo(nil)
var p Dense
pivot := lu.Pivot(nil)
p.Permutation(n, pivot)
var got Dense
got.Mul(&p, &l)
got.Mul(&got, &u)
got.Product(&p, l, u)
if !EqualApprox(&got, &want, 1e-12) {
t.Errorf("PLU does not equal original matrix.\nWant: %v\n Got: %v", want, got)
}

View File

@@ -61,9 +61,14 @@ func (qr *QR) Factorize(a Matrix) {
// and upper triangular matrices.
// RTo extracts the m×n upper trapezoidal matrix from a QR decomposition.
func (qr *QR) RTo(dst *Dense) {
// If dst is nil, a new matrix is allocated. The resulting dst matrix is returned.
func (qr *QR) RTo(dst *Dense) *Dense {
r, c := qr.qr.Dims()
if dst == nil {
dst = NewDense(r, c, nil)
} else {
dst.reuseAs(r, c)
}
// Disguise the QR as an upper triangular
t := &TriDense{
@@ -82,12 +87,19 @@ func (qr *QR) RTo(dst *Dense) {
for i := r; i < c; i++ {
zero(dst.mat.Data[i*dst.mat.Stride : i*dst.mat.Stride+c])
}
return dst
}
// QTo extracts the m×m orthonormal matrix Q from a QR decomposition.
func (qr *QR) QTo(dst *Dense) {
// If dst is nil, a new matrix is allocated. The resulting Q matrix is returned.
func (qr *QR) QTo(dst *Dense) *Dense {
r, _ := qr.qr.Dims()
if dst == nil {
dst = NewDense(r, r, nil)
} else {
dst.reuseAsZeroed(r, r)
}
// Set Q = I.
for i := 0; i < r*r; i += r + 1 {
@@ -99,6 +111,8 @@ func (qr *QR) QTo(dst *Dense) {
lapack64.Ormqr(blas.Left, blas.NoTrans, qr.qr.mat, qr.tau, dst.mat, work, -1)
work = make([]float64, int(work[0]))
lapack64.Ormqr(blas.Left, blas.NoTrans, qr.qr.mat, qr.tau, dst.mat, work, len(work))
return dst
}
// SolveQR finds a minimum-norm solution to a system of linear equations defined

View File

@@ -32,17 +32,16 @@ func TestQR(t *testing.T) {
var qr QR
qr.Factorize(a)
var q, r Dense
qr.QTo(&q)
q := qr.QTo(nil)
if !isOrthonormal(&q, 1e-10) {
if !isOrthonormal(q, 1e-10) {
t.Errorf("Q is not orthonormal: m = %v, n = %v", m, n)
}
qr.RTo(&r)
r := qr.RTo(nil)
var got Dense
got.Mul(&q, &r)
got.Mul(q, r)
if !EqualApprox(&got, &want, 1e-12) {
t.Errorf("QR does not equal original matrix. \nWant: %v\nGot: %v", want, got)
}

View File

@@ -141,14 +141,18 @@ func (svd *SVD) Values(s []float64) []float64 {
// UTo extracts the matrix U from the singular value decomposition, storing
// the result in-place into dst. U is size m×m if svd.Kind() == SVDFull,
// of size m×min(m,n) if svd.Kind() == SVDThin, and UTo panics otherwise.
func (svd *SVD) UTo(dst *Dense) {
func (svd *SVD) UTo(dst *Dense) *Dense {
kind := svd.kind
if kind != matrix.SVDFull && kind != matrix.SVDThin {
panic("mat64: improper SVD kind")
}
r := svd.u.Rows
c := svd.u.Cols
if dst == nil {
dst = NewDense(r, c, nil)
} else {
dst.reuseAs(r, c)
}
tmp := &Dense{
mat: svd.u,
@@ -156,19 +160,25 @@ func (svd *SVD) UTo(dst *Dense) {
capCols: c,
}
dst.Copy(tmp)
return dst
}
// VTo extracts the matrix V from the singular value decomposition, storing
// the result in-place into dst. V is size n×n if svd.Kind() == SVDFull,
// of size n×min(m,n) if svd.Kind() == SVDThin, and VTo panics otherwise.
func (svd *SVD) VTo(dst *Dense) {
func (svd *SVD) VTo(dst *Dense) *Dense {
kind := svd.kind
if kind != matrix.SVDFull && kind != matrix.SVDThin {
panic("mat64: improper SVD kind")
}
r := svd.vt.Rows
c := svd.vt.Cols
if dst == nil {
dst = NewDense(c, r, nil)
} else {
dst.reuseAs(c, r)
}
tmp := &Dense{
mat: svd.vt,
@@ -176,4 +186,6 @@ func (svd *SVD) VTo(dst *Dense) {
capCols: c,
}
dst.Copy(tmp.T())
return dst
}

View File

@@ -169,9 +169,5 @@ func TestSVD(t *testing.T) {
}
func extractSVD(svd *SVD) (s []float64, u, v *Dense) {
var um, vm Dense
svd.UTo(&um)
svd.VTo(&vm)
s = svd.Values(nil)
return s, &um, &vm
return svd.Values(nil), svd.UTo(nil), svd.VTo(nil)
}