Skip to content
This repository has been archived by the owner on Dec 10, 2018. It is now read-only.

mat64: reverse signatures for decomposition matrix extractions #436

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 27 additions & 13 deletions mat64/lq.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (lq *LQ) updateCond() {
//
// The LQ decomposition is a factorization of the matrix A such that A = L * Q.
// The matrix Q is an orthonormal n×n matrix, and L is an m×n upper triangular matrix.
// L and Q can be extracted from the LFromLQ and QFromLQ methods on Dense.
// L and Q can be extracted from the LTo and QTo methods.
func (lq *LQ) Factorize(a Matrix) {
m, n := a.Dims()
if m > n {
Expand All @@ -58,10 +58,15 @@ func (lq *LQ) Factorize(a Matrix) {
// TODO(btracey): Add in the "Reduced" forms for extracting the m×m orthogonal
// and upper triangular matrices.

// LFromLQ extracts the m×n lower trapezoidal matrix from a LQ decomposition.
func (m *Dense) LFromLQ(lq *LQ) {
// LTo extracts the m×n lower trapezoidal matrix from a LQ decomposition.
// 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()
m.reuseAs(r, c)
if dst == nil {
dst = NewDense(r, c, nil)
} else {
dst.reuseAs(r, c)
}

// Disguise the LQ as a lower triangular
t := &TriDense{
Expand All @@ -74,25 +79,32 @@ func (m *Dense) LFromLQ(lq *LQ) {
},
cap: lq.lq.capCols,
}
m.Copy(t)
dst.Copy(t)

if r == c {
return
return dst
}
// Zero right of the triangular.
for i := 0; i < r; i++ {
zero(m.mat.Data[i*m.mat.Stride+r : i*m.mat.Stride+c])
zero(dst.mat.Data[i*dst.mat.Stride+r : i*dst.mat.Stride+c])
}

return dst
}

// QFromLQ extracts the n×n orthonormal matrix Q from an LQ decomposition.
func (m *Dense) QFromLQ(lq *LQ) {
// QTo extracts the n×n orthonormal matrix Q from an LQ decomposition.
// 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()
m.reuseAs(c, c)
if dst == nil {
dst = NewDense(c, c, nil)
} else {
dst.reuseAs(c, c)
}

// Set Q = I.
for i := 0; i < c; i++ {
v := m.mat.Data[i*m.mat.Stride : i*m.mat.Stride+c]
v := dst.mat.Data[i*dst.mat.Stride : i*dst.mat.Stride+c]
zero(v)
v[i] = 1
}
Expand Down Expand Up @@ -127,11 +139,13 @@ func (m *Dense) QFromLQ(lq *LQ) {

// Compute the multiplication matrix.
blas64.Ger(-lq.tau[i], v, v, h)
qCopy.Copy(m)
qCopy.Copy(dst)
blas64.Gemm(blas.NoTrans, blas.NoTrans,
1, h, qCopy.mat,
0, m.mat)
0, dst.mat)
}

return dst
}

// SolveLQ finds a minimum-norm solution to a system of linear equations defined
Expand Down
11 changes: 5 additions & 6 deletions mat64/lq_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,18 @@ func TestLQ(t *testing.T) {
var want Dense
want.Clone(a)

lq := &LQ{}
var lq LQ
lq.Factorize(a)
var l, q Dense
q.QFromLQ(lq)
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)
}

l.LFromLQ(lq)
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)
}
Expand Down
32 changes: 22 additions & 10 deletions mat64/lu.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (lu *LU) updateCond(norm float64) {
// The LU factorization is computed with pivoting, and so really the decomposition
// is a PLU decomposition where P is a permutation matrix. The individual matrix
// factors can be extracted from the factorization using the Permutation method
// on Dense, and the LFrom and UFrom methods on TriDense.
// on Dense, and the LU LTo and UTo methods.
func (lu *LU) Factorize(a Matrix) {
r, c := a.Dims()
if r != c {
Expand Down Expand Up @@ -204,32 +204,44 @@ func (lu *LU) RankOne(orig *LU, alpha float64, x, y *Vector) {
lu.updateCond(-1)
}

// LFromLU extracts the lower triangular matrix from an LU factorization.
func (t *TriDense) LFromLU(lu *LU) {
// LTo extracts the lower triangular matrix from an LU factorization.
// 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()
t.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++ {
t.mat.Data[i*t.mat.Stride+j] = lu.lu.mat.Data[i*lu.lu.mat.Stride+j]
dst.mat.Data[i*dst.mat.Stride+j] = lu.lu.mat.Data[i*lu.lu.mat.Stride+j]
}
}
// Set ones on the diagonal.
for i := 0; i < n; i++ {
t.mat.Data[i*t.mat.Stride+i] = 1
dst.mat.Data[i*dst.mat.Stride+i] = 1
}
return dst
}

// UFromLU extracts the upper triangular matrix from an LU factorization.
func (t *TriDense) UFromLU(lu *LU) {
// UTo extracts the upper triangular matrix from an LU factorization.
// 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()
t.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++ {
t.mat.Data[i*t.mat.Stride+j] = lu.lu.mat.Data[i*lu.lu.mat.Stride+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.
Expand Down
14 changes: 6 additions & 8 deletions mat64/lu_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,16 @@ func TestLUD(t *testing.T) {
var want Dense
want.Clone(a)

lu := &LU{}
var lu LU
lu.Factorize(a)

var l, u TriDense
l.LFromLU(lu)
u.UFromLU(lu)
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)
}
Expand Down Expand Up @@ -93,8 +91,8 @@ func TestLURankOne(t *testing.T) {
// luReconstruct reconstructs the original A matrix from an LU decomposition.
func luReconstruct(lu *LU) *Dense {
var L, U TriDense
L.LFromLU(lu)
U.UFromLU(lu)
lu.LTo(&L)
lu.UTo(&U)
var P Dense
pivot := lu.Pivot(nil)
P.Permutation(len(pivot), pivot)
Expand Down
38 changes: 26 additions & 12 deletions mat64/qr.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (qr *QR) updateCond() {
//
// The QR decomposition is a factorization of the matrix A such that A = Q * R.
// The matrix Q is an orthonormal m×m matrix, and R is an m×n upper triangular matrix.
// Q and R can be extracted from the QFromQR and RFromQR methods on Dense.
// Q and R can be extracted using the QTo and RTo methods.
func (qr *QR) Factorize(a Matrix) {
m, n := a.Dims()
if m < n {
Expand All @@ -60,10 +60,15 @@ func (qr *QR) Factorize(a Matrix) {
// TODO(btracey): Add in the "Reduced" forms for extracting the n×n orthogonal
// and upper triangular matrices.

// RFromQR extracts the m×n upper trapezoidal matrix from a QR decomposition.
func (m *Dense) RFromQR(qr *QR) {
// RTo extracts the m×n upper trapezoidal matrix from a QR decomposition.
// 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()
m.reuseAs(r, c)
if dst == nil {
dst = NewDense(r, c, nil)
} else {
dst.reuseAs(r, c)
}

// Disguise the QR as an upper triangular
t := &TriDense{
Expand All @@ -76,29 +81,38 @@ func (m *Dense) RFromQR(qr *QR) {
},
cap: qr.qr.capCols,
}
m.Copy(t)
dst.Copy(t)

// Zero below the triangular.
for i := r; i < c; i++ {
zero(m.mat.Data[i*m.mat.Stride : i*m.mat.Stride+c])
zero(dst.mat.Data[i*dst.mat.Stride : i*dst.mat.Stride+c])
}

return dst
}

// QFromQR extracts the m×m orthonormal matrix Q from a QR decomposition.
func (m *Dense) QFromQR(qr *QR) {
// QTo extracts the m×m orthonormal matrix Q from a QR decomposition.
// 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()
m.reuseAsZeroed(r, r)
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 {
m.mat.Data[i] = 1
dst.mat.Data[i] = 1
}

// Construct Q from the elementary reflectors.
work := make([]float64, 1)
lapack64.Ormqr(blas.Left, blas.NoTrans, qr.qr.mat, qr.tau, m.mat, work, -1)
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, m.mat, work, len(work))
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
Expand Down
11 changes: 5 additions & 6 deletions mat64/qr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,18 @@ func TestQR(t *testing.T) {
var want Dense
want.Clone(a)

qr := &QR{}
var qr QR
qr.Factorize(a)
var q, r Dense
q.QFromQR(qr)
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)
}

r.RFromQR(qr)
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)
}
Expand Down
32 changes: 22 additions & 10 deletions mat64/svd.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,42 +138,54 @@ func (svd *SVD) Values(s []float64) []float64 {
return s
}

// UFromSVD extracts the matrix U from the singular value decomposition, storing
// UTo extracts the matrix U from the singular value decomposition, storing
// the result in-place into the receiver. U is size m×m if svd.Kind() == SVDFull,
// of size m×min(m,n) if svd.Kind() == SVDThin, and UFromSVD panics otherwise.
func (m *Dense) UFromSVD(svd *SVD) {
// of size m×min(m,n) if svd.Kind() == SVDThin, and UTo panics otherwise.
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
m.reuseAs(r, c)
if dst == nil {
dst = NewDense(r, c, nil)
} else {
dst.reuseAs(r, c)
}

tmp := &Dense{
mat: svd.u,
capRows: r,
capCols: c,
}
m.Copy(tmp)
dst.Copy(tmp)

return dst
}

// VFromSVD extracts the matrix V from the singular value decomposition, storing
// VTo extracts the matrix V from the singular value decomposition, storing
// the result in-place into the receiver. V is size n×n if svd.Kind() == SVDFull,
// of size n×min(m,n) if svd.Kind() == SVDThin, and VFromSVD panics otherwise.
func (m *Dense) VFromSVD(svd *SVD) {
// of size n×min(m,n) if svd.Kind() == SVDThin, and VTo panics otherwise.
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
m.reuseAs(c, r)
if dst == nil {
dst = NewDense(c, r, nil)
} else {
dst.reuseAs(c, r)
}

tmp := &Dense{
mat: svd.vt,
capRows: r,
capCols: c,
}
m.Copy(tmp.T())
dst.Copy(tmp.T())

return dst
}
6 changes: 1 addition & 5 deletions mat64/svd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,5 @@ func TestSVD(t *testing.T) {
}

func extractSVD(svd *SVD) (s []float64, u, v *Dense) {
var um, vm Dense
um.UFromSVD(svd)
vm.VFromSVD(svd)
s = svd.Values(nil)
return s, &um, &vm
return svd.Values(nil), svd.UTo(nil), svd.VTo(nil)
}