blob: 6afcb97b0aba584da5291a3ebc70accece765ab7 [file] [log] [blame]
// Copyright ©2018 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 (
"math"
"gonum.org/v1/gonum/blas"
"gonum.org/v1/gonum/blas/blas64"
"gonum.org/v1/gonum/lapack/lapack64"
)
var (
triBand TriBanded
_ Banded = triBand
_ Triangular = triBand
triBandDense *TriBandDense
_ Matrix = triBandDense
_ allMatrix = triBandDense
_ denseMatrix = triBandDense
_ Triangular = triBandDense
_ Banded = triBandDense
_ TriBanded = triBandDense
_ RawTriBander = triBandDense
_ MutableTriBanded = triBandDense
)
// TriBanded is a triangular band matrix interface type.
type TriBanded interface {
Banded
// Triangle returns the number of rows/columns in the matrix and its
// orientation.
Triangle() (n int, kind TriKind)
// TTri is the equivalent of the T() method in the Matrix interface but
// guarantees the transpose is of triangular type.
TTri() Triangular
// TriBand returns the number of rows/columns in the matrix, the
// size of the bandwidth, and the orientation.
TriBand() (n, k int, kind TriKind)
// TTriBand is the equivalent of the T() method in the Matrix interface but
// guarantees the transpose is of banded triangular type.
TTriBand() TriBanded
}
// A RawTriBander can return a blas64.TriangularBand representation of the receiver.
// Changes to the blas64.TriangularBand.Data slice will be reflected in the original
// matrix, changes to the N, K, Stride, Uplo and Diag fields will not.
type RawTriBander interface {
RawTriBand() blas64.TriangularBand
}
// MutableTriBanded is a triangular band matrix interface type that allows
// elements to be altered.
type MutableTriBanded interface {
TriBanded
SetTriBand(i, j int, v float64)
}
var (
tTriBand TransposeTriBand
_ Matrix = tTriBand
_ TriBanded = tTriBand
_ Untransposer = tTriBand
_ UntransposeTrier = tTriBand
_ UntransposeBander = tTriBand
_ UntransposeTriBander = tTriBand
)
// TransposeTriBand is a type for performing an implicit transpose of a TriBanded
// matrix. It implements the TriBanded interface, returning values from the
// transpose of the matrix within.
type TransposeTriBand struct {
TriBanded TriBanded
}
// At returns the value of the element at row i and column j of the transposed
// matrix, that is, row j and column i of the TriBanded field.
func (t TransposeTriBand) At(i, j int) float64 {
return t.TriBanded.At(j, i)
}
// Dims returns the dimensions of the transposed matrix. TriBanded matrices are
// square and thus this is the same size as the original TriBanded.
func (t TransposeTriBand) Dims() (r, c int) {
c, r = t.TriBanded.Dims()
return r, c
}
// T performs an implicit transpose by returning the TriBand field.
func (t TransposeTriBand) T() Matrix {
return t.TriBanded
}
// Triangle returns the number of rows/columns in the matrix and its orientation.
func (t TransposeTriBand) Triangle() (int, TriKind) {
n, upper := t.TriBanded.Triangle()
return n, !upper
}
// TTri performs an implicit transpose by returning the TriBand field.
func (t TransposeTriBand) TTri() Triangular {
return t.TriBanded
}
// Bandwidth returns the upper and lower bandwidths of the matrix.
func (t TransposeTriBand) Bandwidth() (kl, ku int) {
kl, ku = t.TriBanded.Bandwidth()
return ku, kl
}
// TBand performs an implicit transpose by returning the TriBand field.
func (t TransposeTriBand) TBand() Banded {
return t.TriBanded
}
// TriBand returns the number of rows/columns in the matrix, the
// size of the bandwidth, and the orientation.
func (t TransposeTriBand) TriBand() (n, k int, kind TriKind) {
n, k, kind = t.TriBanded.TriBand()
return n, k, !kind
}
// TTriBand performs an implicit transpose by returning the TriBand field.
func (t TransposeTriBand) TTriBand() TriBanded {
return t.TriBanded
}
// Untranspose returns the Triangular field.
func (t TransposeTriBand) Untranspose() Matrix {
return t.TriBanded
}
// UntransposeTri returns the underlying Triangular matrix.
func (t TransposeTriBand) UntransposeTri() Triangular {
return t.TriBanded
}
// UntransposeBand returns the underlying Banded matrix.
func (t TransposeTriBand) UntransposeBand() Banded {
return t.TriBanded
}
// UntransposeTriBand returns the underlying TriBanded matrix.
func (t TransposeTriBand) UntransposeTriBand() TriBanded {
return t.TriBanded
}
// TriBandDense represents a triangular band matrix in dense storage format.
type TriBandDense struct {
mat blas64.TriangularBand
}
// NewTriBandDense creates a new triangular banded matrix with n rows and columns,
// k bands in the direction of the specified kind. If data == nil,
// a new slice is allocated for the backing slice. If len(data) == n*(k+1),
// data is used as the backing slice, and changes to the elements of the returned
// TriBandDense will be reflected in data. If neither of these is true, NewTriBandDense
// will panic. k must be at least zero and less than n, otherwise NewTriBandDense will panic.
//
// The data must be arranged in row-major order constructed by removing the zeros
// from the rows outside the band and aligning the diagonals. For example, if
// the upper-triangular banded matrix
// 1 2 3 0 0 0
// 0 4 5 6 0 0
// 0 0 7 8 9 0
// 0 0 0 10 11 12
// 0 0 0 0 13 14
// 0 0 0 0 0 15
// becomes (* entries are never accessed)
// 1 2 3
// 4 5 6
// 7 8 9
// 10 11 12
// 13 14 *
// 15 * *
// which is passed to NewTriBandDense as []float64{1, 2, ..., 15, *, *, *}
// with k=2 and kind = mat.Upper.
// The lower triangular banded matrix
// 1 0 0 0 0 0
// 2 3 0 0 0 0
// 4 5 6 0 0 0
// 0 7 8 9 0 0
// 0 0 10 11 12 0
// 0 0 0 13 14 15
// becomes (* entries are never accessed)
// * * 1
// * 2 3
// 4 5 6
// 7 8 9
// 10 11 12
// 13 14 15
// which is passed to NewTriBandDense as []float64{*, *, *, 1, 2, ..., 15}
// with k=2 and kind = mat.Lower.
// Only the values in the band portion of the matrix are used.
func NewTriBandDense(n, k int, kind TriKind, data []float64) *TriBandDense {
if n <= 0 || k < 0 {
if n == 0 {
panic(ErrZeroLength)
}
panic(ErrNegativeDimension)
}
if k+1 > n {
panic(ErrBandwidth)
}
bc := k + 1
if data != nil && len(data) != n*bc {
panic(ErrShape)
}
if data == nil {
data = make([]float64, n*bc)
}
uplo := blas.Lower
if kind {
uplo = blas.Upper
}
return &TriBandDense{
mat: blas64.TriangularBand{
Uplo: uplo,
Diag: blas.NonUnit,
N: n,
K: k,
Data: data,
Stride: bc,
},
}
}
// Dims returns the number of rows and columns in the matrix.
func (t *TriBandDense) Dims() (r, c int) {
return t.mat.N, t.mat.N
}
// T performs an implicit transpose by returning the receiver inside a Transpose.
func (t *TriBandDense) T() Matrix {
return Transpose{t}
}
// IsEmpty returns whether the receiver is empty. Empty matrices can be the
// receiver for size-restricted operations. The receiver can be emptied using
// Reset.
func (t *TriBandDense) IsEmpty() bool {
// It must be the case that t.Dims() returns
// zeros in this case. See comment in Reset().
return t.mat.Stride == 0
}
// Reset empties the matrix so that it can be reused as the
// receiver of a dimensionally restricted operation.
//
// Reset should not be used when the matrix shares backing data.
// See the Reseter interface for more information.
func (t *TriBandDense) Reset() {
t.mat.N = 0
t.mat.Stride = 0
t.mat.K = 0
t.mat.Data = t.mat.Data[:0]
}
// ReuseAsTriBand changes the receiver to be of size n×n, bandwidth k+1 and of
// the given kind, re-using the backing data slice if it has sufficient capacity
// and allocating a new slice otherwise. The backing data is zero on return.
//
// The receiver must be empty, n must be positive and k must be non-negative and
// less than n, otherwise ReuseAsTriBand will panic. To empty the receiver for
// re-use, Reset should be used.
func (t *TriBandDense) ReuseAsTriBand(n, k int, kind TriKind) {
if n <= 0 || k < 0 {
if n == 0 {
panic(ErrZeroLength)
}
panic(ErrNegativeDimension)
}
if k+1 > n {
panic(ErrBandwidth)
}
if !t.IsEmpty() {
panic(ErrReuseNonEmpty)
}
t.reuseAsZeroed(n, k, kind)
}
// reuseAsZeroed resizes an empty receiver to an n×n triangular band matrix with
// the given bandwidth and orientation. If the receiver is not empty,
// reuseAsZeroed checks that the receiver has the correct size, bandwidth and
// orientation. It then zeros out the matrix data.
func (t *TriBandDense) reuseAsZeroed(n, k int, kind TriKind) {
// reuseAsZeroed must be kept in sync with reuseAsNonZeroed.
if n == 0 {
panic(ErrZeroLength)
}
ul := blas.Lower
if kind == Upper {
ul = blas.Upper
}
if t.IsEmpty() {
t.mat = blas64.TriangularBand{
Uplo: ul,
Diag: blas.NonUnit,
N: n,
K: k,
Data: useZeroed(t.mat.Data, n*(k+1)),
Stride: k + 1,
}
return
}
if t.mat.N != n || t.mat.K != k {
panic(ErrShape)
}
if t.mat.Uplo != ul {
panic(ErrTriangle)
}
t.Zero()
}
// reuseAsNonZeroed resizes an empty receiver to an n×n triangular band matrix
// with the given bandwidth and orientation. If the receiver is not empty,
// reuseAsZeroed checks that the receiver has the correct size, bandwidth and
// orientation.
func (t *TriBandDense) reuseAsNonZeroed(n, k int, kind TriKind) {
// reuseAsNonZeroed must be kept in sync with reuseAsZeroed.
if n == 0 {
panic(ErrZeroLength)
}
ul := blas.Lower
if kind == Upper {
ul = blas.Upper
}
if t.IsEmpty() {
t.mat = blas64.TriangularBand{
Uplo: ul,
Diag: blas.NonUnit,
N: n,
K: k,
Data: use(t.mat.Data, n*(k+1)),
Stride: k + 1,
}
return
}
if t.mat.N != n || t.mat.K != k {
panic(ErrShape)
}
if t.mat.Uplo != ul {
panic(ErrTriangle)
}
}
// Zero sets all of the matrix elements to zero.
func (t *TriBandDense) Zero() {
if t.isUpper() {
for i := 0; i < t.mat.N; i++ {
u := min(1+t.mat.K, t.mat.N-i)
zero(t.mat.Data[i*t.mat.Stride : i*t.mat.Stride+u])
}
return
}
for i := 0; i < t.mat.N; i++ {
l := max(0, t.mat.K-i)
zero(t.mat.Data[i*t.mat.Stride+l : i*t.mat.Stride+t.mat.K+1])
}
}
func (t *TriBandDense) isUpper() bool {
return isUpperUplo(t.mat.Uplo)
}
func (t *TriBandDense) triKind() TriKind {
return TriKind(isUpperUplo(t.mat.Uplo))
}
// Triangle returns the dimension of t and its orientation. The returned
// orientation is only valid when n is not zero.
func (t *TriBandDense) Triangle() (n int, kind TriKind) {
return t.mat.N, t.triKind()
}
// TTri performs an implicit transpose by returning the receiver inside a TransposeTri.
func (t *TriBandDense) TTri() Triangular {
return TransposeTri{t}
}
// Bandwidth returns the upper and lower bandwidths of the matrix.
func (t *TriBandDense) Bandwidth() (kl, ku int) {
if t.isUpper() {
return 0, t.mat.K
}
return t.mat.K, 0
}
// TBand performs an implicit transpose by returning the receiver inside a TransposeBand.
func (t *TriBandDense) TBand() Banded {
return TransposeBand{t}
}
// TriBand returns the number of rows/columns in the matrix, the
// size of the bandwidth, and the orientation.
func (t *TriBandDense) TriBand() (n, k int, kind TriKind) {
return t.mat.N, t.mat.K, TriKind(!t.IsEmpty()) && t.triKind()
}
// TTriBand performs an implicit transpose by returning the receiver inside a TransposeTriBand.
func (t *TriBandDense) TTriBand() TriBanded {
return TransposeTriBand{t}
}
// RawTriBand returns the underlying blas64.TriangularBand used by the receiver.
// Changes to the blas64.TriangularBand.Data slice will be reflected in the original
// matrix, changes to the N, K, Stride, Uplo and Diag fields will not.
func (t *TriBandDense) RawTriBand() blas64.TriangularBand {
return t.mat
}
// SetRawTriBand sets the underlying blas64.TriangularBand used by the receiver.
// Changes to elements in the receiver following the call will be reflected
// in the input.
//
// The supplied TriangularBand must not use blas.Unit storage format.
func (t *TriBandDense) SetRawTriBand(mat blas64.TriangularBand) {
if mat.Diag == blas.Unit {
panic("mat: cannot set TriBand with Unit storage")
}
t.mat = mat
}
// DiagView returns the diagonal as a matrix backed by the original data.
func (t *TriBandDense) DiagView() Diagonal {
if t.mat.Diag == blas.Unit {
panic("mat: cannot take view of Unit diagonal")
}
n := t.mat.N
data := t.mat.Data
if !t.isUpper() {
data = data[t.mat.K:]
}
return &DiagDense{
mat: blas64.Vector{
N: n,
Inc: t.mat.Stride,
Data: data[:(n-1)*t.mat.Stride+1],
},
}
}
// Trace returns the trace.
func (t *TriBandDense) Trace() float64 {
rb := t.RawTriBand()
var tr float64
var offsetIndex int
if rb.Uplo == blas.Lower {
offsetIndex = rb.K
}
for i := 0; i < rb.N; i++ {
tr += rb.Data[offsetIndex+i*rb.Stride]
}
return tr
}
// SolveTo solves a triangular system T * X = B or Tᵀ * X = B where T is an
// n×n triangular band matrix represented by the receiver and B is a given
// n×nrhs matrix. If T is non-singular, the result will be stored into dst and
// nil will be returned. If T is singular, the contents of dst will be undefined
// and a Condition error will be returned.
func (t *TriBandDense) SolveTo(dst *Dense, trans bool, b Matrix) error {
n, nrhs := b.Dims()
if n != t.mat.N {
panic(ErrShape)
}
if b, ok := b.(RawMatrixer); ok && dst != b {
dst.checkOverlap(b.RawMatrix())
}
dst.reuseAsNonZeroed(n, nrhs)
if dst != b {
dst.Copy(b)
}
var ok bool
if trans {
ok = lapack64.Tbtrs(blas.Trans, t.mat, dst.mat)
} else {
ok = lapack64.Tbtrs(blas.NoTrans, t.mat, dst.mat)
}
if !ok {
return Condition(math.Inf(1))
}
return nil
}
// SolveVecTo solves a triangular system T * x = b or Tᵀ * x = b where T is an
// n×n triangular band matrix represented by the receiver and b is a given
// n-vector. If T is non-singular, the result will be stored into dst and nil
// will be returned. If T is singular, the contents of dst will be undefined and
// a Condition error will be returned.
func (t *TriBandDense) SolveVecTo(dst *VecDense, trans bool, b Vector) error {
n, nrhs := b.Dims()
if n != t.mat.N || nrhs != 1 {
panic(ErrShape)
}
if b, ok := b.(RawVectorer); ok && dst != b {
dst.checkOverlap(b.RawVector())
}
dst.reuseAsNonZeroed(n)
if dst != b {
dst.CopyVec(b)
}
var ok bool
if trans {
ok = lapack64.Tbtrs(blas.Trans, t.mat, dst.asGeneral())
} else {
ok = lapack64.Tbtrs(blas.NoTrans, t.mat, dst.asGeneral())
}
if !ok {
return Condition(math.Inf(1))
}
return nil
}
func copySymBandIntoTriBand(dst *TriBandDense, s SymBanded) {
n, k, upper := dst.TriBand()
ns, ks := s.SymBand()
if n != ns {
panic("mat: triangle size mismatch")
}
if k != ks {
panic("mat: triangle bandwidth mismatch")
}
// TODO(vladimir-ch): implement the missing cases below as needed.
t := dst.mat
sU, _ := untransposeExtract(s)
if sbd, ok := sU.(*SymBandDense); ok {
s := sbd.RawSymBand()
if upper {
if s.Uplo == blas.Upper {
// dst is upper triangular, s is stored in upper triangle.
for i := 0; i < n; i++ {
ilen := min(k+1, n-i)
copy(t.Data[i*t.Stride:i*t.Stride+ilen], s.Data[i*s.Stride:i*s.Stride+ilen])
}
} else {
// dst is upper triangular, s is stored in lower triangle.
//
// The following is a possible implementation for this case but
// is commented out due to lack of test coverage.
// for i := 0; i < n; i++ {
// ilen := min(k+1, n-i)
// for j := 0; j < ilen; j++ {
// t.Data[i*t.Stride+j] = s.Data[(i+j)*s.Stride+k-j]
// }
// }
panic("not implemented")
}
} else {
if s.Uplo == blas.Upper {
// dst is lower triangular, s is stored in upper triangle.
panic("not implemented")
} else {
// dst is lower triangular, s is stored in lower triangle.
panic("not implemented")
}
}
return
}
if upper {
for i := 0; i < n; i++ {
ilen := min(k+1, n-i)
for j := 0; j < ilen; j++ {
t.Data[i*t.Stride+j] = s.At(i, i+j)
}
}
} else {
panic("not implemented")
}
}