Skip to content

Commit

Permalink
Merge 9b8c04a into d0fc9ef
Browse files Browse the repository at this point in the history
  • Loading branch information
kortschak committed May 26, 2017
2 parents d0fc9ef + 9b8c04a commit d7bcc63
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 100 deletions.
40 changes: 27 additions & 13 deletions matrix/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 matrix/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 matrix/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 matrix/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 matrix/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 matrix/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 matrix/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 matrix/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)
}
Loading

0 comments on commit d7bcc63

Please sign in to comment.