// Copyright ©2015 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 ( triDense *TriDense _ Matrix = triDense _ Triangular = triDense _ RawTriangular = triDense _ MutableTriangular = triDense _ NonZeroDoer = triDense _ RowNonZeroDoer = triDense _ ColNonZeroDoer = triDense ) const badTriCap = "mat: bad capacity for TriDense" // TriDense represents an upper or lower triangular matrix in dense storage // format. type TriDense struct { mat blas64.Triangular cap int } // Triangular represents a triangular matrix. Triangular matrices are always square. type Triangular interface { Matrix // 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 } // A RawTriangular can return a blas64.Triangular representation of the receiver. // Changes to the blas64.Triangular.Data slice will be reflected in the original // matrix, changes to the N, Stride, Uplo and Diag fields will not. type RawTriangular interface { RawTriangular() blas64.Triangular } // A MutableTriangular can set elements of a triangular matrix. type MutableTriangular interface { Triangular SetTri(i, j int, v float64) } var ( _ Matrix = TransposeTri{} _ Triangular = TransposeTri{} _ UntransposeTrier = TransposeTri{} ) // TransposeTri is a type for performing an implicit transpose of a Triangular // matrix. It implements the Triangular interface, returning values from the // transpose of the matrix within. type TransposeTri struct { Triangular Triangular } // 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 Triangular field. func (t TransposeTri) At(i, j int) float64 { return t.Triangular.At(j, i) } // Dims returns the dimensions of the transposed matrix. Triangular matrices are // square and thus this is the same size as the original Triangular. func (t TransposeTri) Dims() (r, c int) { c, r = t.Triangular.Dims() return r, c } // T performs an implicit transpose by returning the Triangular field. func (t TransposeTri) T() Matrix { return t.Triangular } // Triangle returns the number of rows/columns in the matrix and its orientation. func (t TransposeTri) Triangle() (int, TriKind) { n, upper := t.Triangular.Triangle() return n, !upper } // TTri performs an implicit transpose by returning the Triangular field. func (t TransposeTri) TTri() Triangular { return t.Triangular } // Untranspose returns the Triangular field. func (t TransposeTri) Untranspose() Matrix { return t.Triangular } func (t TransposeTri) UntransposeTri() Triangular { return t.Triangular } // NewTriDense creates a new Triangular matrix with n rows and columns. If data == nil, // a new slice is allocated for the backing slice. If len(data) == n*n, data is // used as the backing slice, and changes to the elements of the returned TriDense // will be reflected in data. If neither of these is true, NewTriDense will panic. // NewTriDense will panic if n is zero. // // The data must be arranged in row-major order, i.e. the (i*c + j)-th // element in the data slice is the {i, j}-th element in the matrix. // Only the values in the triangular portion corresponding to kind are used. func NewTriDense(n int, kind TriKind, data []float64) *TriDense { if n <= 0 { if n == 0 { panic(ErrZeroLength) } panic("mat: negative dimension") } if data != nil && len(data) != n*n { panic(ErrShape) } if data == nil { data = make([]float64, n*n) } uplo := blas.Lower if kind == Upper { uplo = blas.Upper } return &TriDense{ mat: blas64.Triangular{ N: n, Stride: n, Data: data, Uplo: uplo, Diag: blas.NonUnit, }, cap: n, } } func (t *TriDense) Dims() (r, c int) { return t.mat.N, t.mat.N } // Triangle returns the dimension of t and its orientation. The returned // orientation is only valid when n is not zero. func (t *TriDense) Triangle() (n int, kind TriKind) { return t.mat.N, t.triKind() } func (t *TriDense) isUpper() bool { return isUpperUplo(t.mat.Uplo) } func (t *TriDense) triKind() TriKind { return TriKind(isUpperUplo(t.mat.Uplo)) } func isUpperUplo(u blas.Uplo) bool { switch u { case blas.Upper: return true case blas.Lower: return false default: panic(badTriangle) } } func uploToTriKind(u blas.Uplo) TriKind { switch u { case blas.Upper: return Upper case blas.Lower: return Lower default: panic(badTriangle) } } // asSymBlas returns the receiver restructured as a blas64.Symmetric with the // same backing memory. Panics if the receiver is unit. // This returns a blas64.Symmetric and not a *SymDense because SymDense can only // be upper triangular. func (t *TriDense) asSymBlas() blas64.Symmetric { if t.mat.Diag == blas.Unit { panic("mat: cannot convert unit TriDense into blas64.Symmetric") } return blas64.Symmetric{ N: t.mat.N, Stride: t.mat.Stride, Data: t.mat.Data, Uplo: t.mat.Uplo, } } // T performs an implicit transpose by returning the receiver inside a Transpose. func (t *TriDense) T() Matrix { return Transpose{t} } // TTri performs an implicit transpose by returning the receiver inside a TransposeTri. func (t *TriDense) TTri() Triangular { return TransposeTri{t} } func (t *TriDense) RawTriangular() blas64.Triangular { return t.mat } // SetRawTriangular sets the underlying blas64.Triangular used by the receiver. // Changes to elements in the receiver following the call will be reflected // in the input. // // The supplied Triangular must not use blas.Unit storage format. func (t *TriDense) SetRawTriangular(mat blas64.Triangular) { if mat.Diag == blas.Unit { panic("mat: cannot set TriDense with Unit storage format") } t.mat = mat } // Reset zeros the dimensions of the matrix so that it can be reused as the // receiver of a dimensionally restricted operation. // // See the Reseter interface for more information. func (t *TriDense) Reset() { // N and Stride must be zeroed in unison. t.mat.N, t.mat.Stride = 0, 0 // Defensively zero Uplo to ensure // it is set correctly later. t.mat.Uplo = 0 t.mat.Data = t.mat.Data[:0] } // Zero sets all of the matrix elements to zero. func (t *TriDense) Zero() { if t.isUpper() { for i := 0; i < t.mat.N; i++ { zero(t.mat.Data[i*t.mat.Stride+i : i*t.mat.Stride+t.mat.N]) } return } for i := 0; i < t.mat.N; i++ { zero(t.mat.Data[i*t.mat.Stride : i*t.mat.Stride+i+1]) } } // IsZero returns whether the receiver is zero-sized. Zero-sized matrices can be the // receiver for size-restricted operations. TriDense matrices can be zeroed using Reset. func (t *TriDense) IsZero() bool { // It must be the case that t.Dims() returns // zeros in this case. See comment in Reset(). return t.mat.Stride == 0 } // untranspose untransposes a matrix if applicable. If a is an Untransposer, then // untranspose returns the underlying matrix and true. If it is not, then it returns // the input matrix and false. func untransposeTri(a Triangular) (Triangular, bool) { if ut, ok := a.(UntransposeTrier); ok { return ut.UntransposeTri(), true } return a, false } // reuseAs resizes a zero receiver to an n×n triangular matrix with the given // orientation. If the receiver is non-zero, reuseAs checks that the receiver // is the correct size and orientation. func (t *TriDense) reuseAs(n int, kind TriKind) { if n == 0 { panic(ErrZeroLength) } ul := blas.Lower if kind == Upper { ul = blas.Upper } if t.mat.N > t.cap { panic(badTriCap) } if t.IsZero() { t.mat = blas64.Triangular{ N: n, Stride: n, Diag: blas.NonUnit, Data: use(t.mat.Data, n*n), Uplo: ul, } t.cap = n return } if t.mat.N != n { panic(ErrShape) } if t.mat.Uplo != ul { panic(ErrTriangle) } } // isolatedWorkspace returns a new TriDense matrix w with the size of a and // returns a callback to defer which performs cleanup at the return of the call. // This should be used when a method receiver is the same pointer as an input argument. func (t *TriDense) isolatedWorkspace(a Triangular) (w *TriDense, restore func()) { n, kind := a.Triangle() if n == 0 { panic(ErrZeroLength) } w = getWorkspaceTri(n, kind, false) return w, func() { t.Copy(w) putWorkspaceTri(w) } } // DiagView returns the diagonal as a matrix backed by the original data. func (t *TriDense) DiagView() Diagonal { if t.mat.Diag == blas.Unit { panic("mat: cannot take view of Unit diagonal") } n := t.mat.N return &DiagDense{ mat: blas64.Vector{ N: n, Inc: t.mat.Stride + 1, Data: t.mat.Data[:(n-1)*t.mat.Stride+n], }, } } // Copy makes a copy of elements of a into the receiver. It is similar to the // built-in copy; it copies as much as the overlap between the two matrices and // returns the number of rows and columns it copied. Only elements within the // receiver's non-zero triangle are set. // // See the Copier interface for more information. func (t *TriDense) Copy(a Matrix) (r, c int) { r, c = a.Dims() r = min(r, t.mat.N) c = min(c, t.mat.N) if r == 0 || c == 0 { return 0, 0 } switch a := a.(type) { case RawMatrixer: amat := a.RawMatrix() if t.isUpper() { for i := 0; i < r; i++ { copy(t.mat.Data[i*t.mat.Stride+i:i*t.mat.Stride+c], amat.Data[i*amat.Stride+i:i*amat.Stride+c]) } } else { for i := 0; i < r; i++ { copy(t.mat.Data[i*t.mat.Stride:i*t.mat.Stride+i+1], amat.Data[i*amat.Stride:i*amat.Stride+i+1]) } } case RawTriangular: amat := a.RawTriangular() aIsUpper := isUpperUplo(amat.Uplo) tIsUpper := t.isUpper() switch { case tIsUpper && aIsUpper: for i := 0; i < r; i++ { copy(t.mat.Data[i*t.mat.Stride+i:i*t.mat.Stride+c], amat.Data[i*amat.Stride+i:i*amat.Stride+c]) } case !tIsUpper && !aIsUpper: for i := 0; i < r; i++ { copy(t.mat.Data[i*t.mat.Stride:i*t.mat.Stride+i+1], amat.Data[i*amat.Stride:i*amat.Stride+i+1]) } default: for i := 0; i < r; i++ { t.set(i, i, amat.Data[i*amat.Stride+i]) } } default: isUpper := t.isUpper() for i := 0; i < r; i++ { if isUpper { for j := i; j < c; j++ { t.set(i, j, a.At(i, j)) } } else { for j := 0; j <= i; j++ { t.set(i, j, a.At(i, j)) } } } } return r, c } // InverseTri computes the inverse of the triangular matrix a, storing the result // into the receiver. If a is ill-conditioned, a Condition error will be returned. // Note that matrix inversion is numerically unstable, and should generally be // avoided where possible, for example by using the Solve routines. func (t *TriDense) InverseTri(a Triangular) error { t.checkOverlapMatrix(a) n, _ := a.Triangle() t.reuseAs(a.Triangle()) t.Copy(a) work := getFloats(3*n, false) iwork := getInts(n, false) cond := lapack64.Trcon(CondNorm, t.mat, work, iwork) putFloats(work) putInts(iwork) if math.IsInf(cond, 1) { return Condition(cond) } ok := lapack64.Trtri(t.mat) if !ok { return Condition(math.Inf(1)) } if cond > ConditionTolerance { return Condition(cond) } return nil } // MulTri takes the product of triangular matrices a and b and places the result // in the receiver. The size of a and b must match, and they both must have the // same TriKind, or Mul will panic. func (t *TriDense) MulTri(a, b Triangular) { n, kind := a.Triangle() nb, kindb := b.Triangle() if n != nb { panic(ErrShape) } if kind != kindb { panic(ErrTriangle) } aU, _ := untransposeTri(a) bU, _ := untransposeTri(b) t.checkOverlapMatrix(bU) t.checkOverlapMatrix(aU) t.reuseAs(n, kind) var restore func() if t == aU { t, restore = t.isolatedWorkspace(aU) defer restore() } else if t == bU { t, restore = t.isolatedWorkspace(bU) defer restore() } // TODO(btracey): Improve the set of fast-paths. if kind == Upper { for i := 0; i < n; i++ { for j := i; j < n; j++ { var v float64 for k := i; k <= j; k++ { v += a.At(i, k) * b.At(k, j) } t.SetTri(i, j, v) } } return } for i := 0; i < n; i++ { for j := 0; j <= i; j++ { var v float64 for k := j; k <= i; k++ { v += a.At(i, k) * b.At(k, j) } t.SetTri(i, j, v) } } } // ScaleTri multiplies the elements of a by f, placing the result in the receiver. // If the receiver is non-zero, the size and kind of the receiver must match // the input, or ScaleTri will panic. func (t *TriDense) ScaleTri(f float64, a Triangular) { n, kind := a.Triangle() t.reuseAs(n, kind) // TODO(btracey): Improve the set of fast-paths. switch a := a.(type) { case RawTriangular: amat := a.RawTriangular() if t != a { t.checkOverlap(generalFromTriangular(amat)) } if kind == Upper { for i := 0; i < n; i++ { ts := t.mat.Data[i*t.mat.Stride+i : i*t.mat.Stride+n] as := amat.Data[i*amat.Stride+i : i*amat.Stride+n] for i, v := range as { ts[i] = v * f } } return } for i := 0; i < n; i++ { ts := t.mat.Data[i*t.mat.Stride : i*t.mat.Stride+i+1] as := amat.Data[i*amat.Stride : i*amat.Stride+i+1] for i, v := range as { ts[i] = v * f } } return default: t.checkOverlapMatrix(a) isUpper := kind == Upper for i := 0; i < n; i++ { if isUpper { for j := i; j < n; j++ { t.set(i, j, f*a.At(i, j)) } } else { for j := 0; j <= i; j++ { t.set(i, j, f*a.At(i, j)) } } } } } // Trace returns the trace of the matrix. func (t *TriDense) Trace() float64 { // TODO(btracey): could use internal asm sum routine. var v float64 for i := 0; i < t.mat.N; i++ { v += t.mat.Data[i*t.mat.Stride+i] } return v } // copySymIntoTriangle copies a symmetric matrix into a TriDense func copySymIntoTriangle(t *TriDense, s Symmetric) { n, upper := t.Triangle() ns := s.Symmetric() if n != ns { panic("mat: triangle size mismatch") } ts := t.mat.Stride if rs, ok := s.(RawSymmetricer); ok { sd := rs.RawSymmetric() ss := sd.Stride if upper { if sd.Uplo == blas.Upper { for i := 0; i < n; i++ { copy(t.mat.Data[i*ts+i:i*ts+n], sd.Data[i*ss+i:i*ss+n]) } return } for i := 0; i < n; i++ { for j := i; j < n; j++ { t.mat.Data[i*ts+j] = sd.Data[j*ss+i] } } return } if sd.Uplo == blas.Upper { for i := 0; i < n; i++ { for j := 0; j <= i; j++ { t.mat.Data[i*ts+j] = sd.Data[j*ss+i] } } return } for i := 0; i < n; i++ { copy(t.mat.Data[i*ts:i*ts+i+1], sd.Data[i*ss:i*ss+i+1]) } return } if upper { for i := 0; i < n; i++ { for j := i; j < n; j++ { t.mat.Data[i*ts+j] = s.At(i, j) } } return } for i := 0; i < n; i++ { for j := 0; j <= i; j++ { t.mat.Data[i*ts+j] = s.At(i, j) } } } // DoNonZero calls the function fn for each of the non-zero elements of t. The function fn // takes a row/column index and the element value of t at (i, j). func (t *TriDense) DoNonZero(fn func(i, j int, v float64)) { if t.isUpper() { for i := 0; i < t.mat.N; i++ { for j := i; j < t.mat.N; j++ { v := t.at(i, j) if v != 0 { fn(i, j, v) } } } return } for i := 0; i < t.mat.N; i++ { for j := 0; j <= i; j++ { v := t.at(i, j) if v != 0 { fn(i, j, v) } } } } // DoRowNonZero calls the function fn for each of the non-zero elements of row i of t. The function fn // takes a row/column index and the element value of t at (i, j). func (t *TriDense) DoRowNonZero(i int, fn func(i, j int, v float64)) { if i < 0 || t.mat.N <= i { panic(ErrRowAccess) } if t.isUpper() { for j := i; j < t.mat.N; j++ { v := t.at(i, j) if v != 0 { fn(i, j, v) } } return } for j := 0; j <= i; j++ { v := t.at(i, j) if v != 0 { fn(i, j, v) } } } // DoColNonZero calls the function fn for each of the non-zero elements of column j of t. The function fn // takes a row/column index and the element value of t at (i, j). func (t *TriDense) DoColNonZero(j int, fn func(i, j int, v float64)) { if j < 0 || t.mat.N <= j { panic(ErrColAccess) } if t.isUpper() { for i := 0; i <= j; i++ { v := t.at(i, j) if v != 0 { fn(i, j, v) } } return } for i := j; i < t.mat.N; i++ { v := t.at(i, j) if v != 0 { fn(i, j, v) } } }