forked from LaconicNetwork/kompose
194 lines
4.3 KiB
Go
194 lines
4.3 KiB
Go
// 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 mat64
|
|
|
|
import (
|
|
"math"
|
|
|
|
"github.com/gonum/blas"
|
|
"github.com/gonum/blas/blas64"
|
|
)
|
|
|
|
type LQFactor struct {
|
|
LQ *Dense
|
|
lDiag []float64
|
|
}
|
|
|
|
// LQ computes an LQ Decomposition for an m-by-n matrix a with m <= n by Householder
|
|
// reflections. The LQ decomposition is an m-by-n orthogonal matrix q and an m-by-m
|
|
// lower triangular matrix l so that a = l.q. LQ will panic with ErrShape if m > n.
|
|
//
|
|
// The LQ decomposition always exists, even if the matrix does not have full rank,
|
|
// so LQ will never fail unless m > n. The primary use of the LQ decomposition is
|
|
// in the least squares solution of non-square systems of simultaneous linear equations.
|
|
// This will fail if LQIsFullRank() returns false. The matrix a is overwritten by the
|
|
// decomposition.
|
|
func LQ(a *Dense) LQFactor {
|
|
// Initialize.
|
|
m, n := a.Dims()
|
|
if m > n {
|
|
panic(ErrShape)
|
|
}
|
|
|
|
lq := *a
|
|
|
|
lDiag := make([]float64, m)
|
|
projs := NewVector(m, nil)
|
|
|
|
// Main loop.
|
|
for k := 0; k < m; k++ {
|
|
hh := lq.RawRowView(k)[k:]
|
|
norm := blas64.Nrm2(len(hh), blas64.Vector{Inc: 1, Data: hh})
|
|
lDiag[k] = norm
|
|
|
|
if norm != 0 {
|
|
hhNorm := (norm * math.Sqrt(1-hh[0]/norm))
|
|
if hhNorm == 0 {
|
|
hh[0] = 0
|
|
} else {
|
|
// Form k-th Householder vector.
|
|
s := 1 / hhNorm
|
|
hh[0] -= norm
|
|
blas64.Scal(len(hh), s, blas64.Vector{Inc: 1, Data: hh})
|
|
|
|
// Apply transformation to remaining columns.
|
|
if k < m-1 {
|
|
a = lq.View(k+1, k, m-k-1, n-k).(*Dense)
|
|
projs = projs.ViewVec(0, m-k-1)
|
|
projs.MulVec(a, false, NewVector(len(hh), hh))
|
|
|
|
for j := 0; j < m-k-1; j++ {
|
|
dst := a.RawRowView(j)
|
|
blas64.Axpy(len(dst), -projs.at(j),
|
|
blas64.Vector{Inc: 1, Data: hh},
|
|
blas64.Vector{Inc: 1, Data: dst},
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*a = lq
|
|
|
|
return LQFactor{a, lDiag}
|
|
}
|
|
|
|
// IsFullRank returns whether the L matrix and hence a has full rank.
|
|
func (f LQFactor) IsFullRank() bool {
|
|
for _, v := range f.lDiag {
|
|
if v == 0 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// L returns the lower triangular factor for the LQ decomposition.
|
|
func (f LQFactor) L() *Dense {
|
|
lq, lDiag := f.LQ, f.lDiag
|
|
m, _ := lq.Dims()
|
|
l := NewDense(m, m, nil)
|
|
for i, v := range lDiag {
|
|
for j := 0; j < m; j++ {
|
|
if i < j {
|
|
l.set(j, i, lq.at(j, i))
|
|
} else if i == j {
|
|
l.set(j, i, v)
|
|
}
|
|
}
|
|
}
|
|
return l
|
|
}
|
|
|
|
// replaces x with Q.x
|
|
func (f LQFactor) applyQTo(x *Dense, trans bool) {
|
|
nh, nc := f.LQ.Dims()
|
|
m, n := x.Dims()
|
|
if m != nc {
|
|
panic(ErrShape)
|
|
}
|
|
proj := make([]float64, n)
|
|
|
|
if trans {
|
|
for k := nh - 1; k >= 0; k-- {
|
|
hh := f.LQ.RawRowView(k)[k:]
|
|
|
|
sub := x.View(k, 0, m-k, n).(*Dense)
|
|
|
|
blas64.Gemv(blas.Trans,
|
|
1, sub.mat, blas64.Vector{Inc: 1, Data: hh},
|
|
0, blas64.Vector{Inc: 1, Data: proj},
|
|
)
|
|
for i := k; i < m; i++ {
|
|
row := x.RawRowView(i)
|
|
blas64.Axpy(n, -hh[i-k],
|
|
blas64.Vector{Inc: 1, Data: proj},
|
|
blas64.Vector{Inc: 1, Data: row},
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
for k := 0; k < nh; k++ {
|
|
hh := f.LQ.RawRowView(k)[k:]
|
|
|
|
sub := x.View(k, 0, m-k, n).(*Dense)
|
|
|
|
blas64.Gemv(blas.Trans,
|
|
1, sub.mat, blas64.Vector{Inc: 1, Data: hh},
|
|
0, blas64.Vector{Inc: 1, Data: proj},
|
|
)
|
|
for i := k; i < m; i++ {
|
|
row := x.RawRowView(i)
|
|
blas64.Axpy(n, -hh[i-k],
|
|
blas64.Vector{Inc: 1, Data: proj},
|
|
blas64.Vector{Inc: 1, Data: row},
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Solve computes minimum norm least squares solution of a.x = b where b has as many rows as a.
|
|
// A matrix x is returned that minimizes the two norm of Q*R*X-B. Solve will panic
|
|
// if a is not full rank.
|
|
func (f LQFactor) Solve(b *Dense) (x *Dense) {
|
|
lq := f.LQ
|
|
lDiag := f.lDiag
|
|
m, n := lq.Dims()
|
|
bm, bn := b.Dims()
|
|
if bm != m {
|
|
panic(ErrShape)
|
|
}
|
|
if !f.IsFullRank() {
|
|
panic(ErrSingular)
|
|
}
|
|
|
|
x = NewDense(n, bn, nil)
|
|
x.Copy(b)
|
|
|
|
tau := make([]float64, m)
|
|
for i := range tau {
|
|
tau[i] = lq.at(i, i)
|
|
lq.set(i, i, lDiag[i])
|
|
}
|
|
lqT := blas64.Triangular{
|
|
// N omitted since it is not used by Trsm.
|
|
Stride: lq.mat.Stride,
|
|
Data: lq.mat.Data,
|
|
Uplo: blas.Lower,
|
|
Diag: blas.NonUnit,
|
|
}
|
|
x.mat.Rows = bm
|
|
blas64.Trsm(blas.Left, blas.NoTrans, 1, lqT, x.mat)
|
|
x.mat.Rows = n
|
|
for i := range tau {
|
|
lq.set(i, i, tau[i])
|
|
}
|
|
|
|
f.applyQTo(x, true)
|
|
|
|
return x
|
|
}
|