k3s/vendor/gonum.org/v1/gonum/mat/symmetric.go

663 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 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"
)
var (
symDense *SymDense
_ Matrix = symDense
_ allMatrix = symDense
_ denseMatrix = symDense
_ Symmetric = symDense
_ RawSymmetricer = symDense
_ MutableSymmetric = symDense
)
const (
badSymTriangle = "mat: blas64.Symmetric not upper"
badSymCap = "mat: bad capacity for SymDense"
)
// SymDense is a symmetric matrix that uses dense storage. SymDense
// matrices are stored in the upper triangle.
type SymDense struct {
mat blas64.Symmetric
cap int
}
// Symmetric represents a symmetric matrix (where the element at {i, j} equals
// the element at {j, i}). Symmetric matrices are always square.
type Symmetric interface {
Matrix
// Symmetric returns the number of rows/columns in the matrix.
Symmetric() int
}
// A RawSymmetricer can return a view of itself as a BLAS Symmetric matrix.
type RawSymmetricer interface {
RawSymmetric() blas64.Symmetric
}
// A MutableSymmetric can set elements of a symmetric matrix.
type MutableSymmetric interface {
Symmetric
SetSym(i, j int, v float64)
}
// NewSymDense creates a new Symmetric 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 SymDense
// will be reflected in data. If neither of these is true, NewSymDense will panic.
// NewSymDense 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 upper triangular portion of the matrix are used.
func NewSymDense(n int, data []float64) *SymDense {
if n <= 0 {
if n == 0 {
panic(ErrZeroLength)
}
panic("mat: negative dimension")
}
if data != nil && n*n != len(data) {
panic(ErrShape)
}
if data == nil {
data = make([]float64, n*n)
}
return &SymDense{
mat: blas64.Symmetric{
N: n,
Stride: n,
Data: data,
Uplo: blas.Upper,
},
cap: n,
}
}
// Dims returns the number of rows and columns in the matrix.
func (s *SymDense) Dims() (r, c int) {
return s.mat.N, s.mat.N
}
// Caps returns the number of rows and columns in the backing matrix.
func (s *SymDense) Caps() (r, c int) {
return s.cap, s.cap
}
// T returns the receiver, the transpose of a symmetric matrix.
func (s *SymDense) T() Matrix {
return s
}
// Symmetric implements the Symmetric interface and returns the number of rows
// and columns in the matrix.
func (s *SymDense) Symmetric() int {
return s.mat.N
}
// RawSymmetric returns the matrix as a blas64.Symmetric. The returned
// value must be stored in upper triangular format.
func (s *SymDense) RawSymmetric() blas64.Symmetric {
return s.mat
}
// SetRawSymmetric sets the underlying blas64.Symmetric used by the receiver.
// Changes to elements in the receiver following the call will be reflected
// in the input.
//
// The supplied Symmetric must use blas.Upper storage format.
func (s *SymDense) SetRawSymmetric(mat blas64.Symmetric) {
if mat.Uplo != blas.Upper {
panic(badSymTriangle)
}
s.cap = mat.N
s.mat = mat
}
// 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 (s *SymDense) Reset() {
// N and Stride must be zeroed in unison.
s.mat.N, s.mat.Stride = 0, 0
s.mat.Data = s.mat.Data[:0]
}
// ReuseAsSym changes the receiver if it IsEmpty() to be of size n×n.
//
// ReuseAsSym re-uses the backing data slice if it has sufficient capacity,
// otherwise a new slice is allocated. The backing data is zero on return.
//
// ReuseAsSym panics if the receiver is not empty, and panics if
// the input size is less than one. To empty the receiver for re-use,
// Reset should be used.
func (s *SymDense) ReuseAsSym(n int) {
if n <= 0 {
if n == 0 {
panic(ErrZeroLength)
}
panic(ErrNegativeDimension)
}
if !s.IsEmpty() {
panic(ErrReuseNonEmpty)
}
s.reuseAsZeroed(n)
}
// Zero sets all of the matrix elements to zero.
func (s *SymDense) Zero() {
for i := 0; i < s.mat.N; i++ {
zero(s.mat.Data[i*s.mat.Stride+i : i*s.mat.Stride+s.mat.N])
}
}
// 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 (s *SymDense) IsEmpty() bool {
// It must be the case that m.Dims() returns
// zeros in this case. See comment in Reset().
return s.mat.N == 0
}
// reuseAsNonZeroed resizes an empty matrix to a n×n matrix,
// or checks that a non-empty matrix is n×n.
func (s *SymDense) reuseAsNonZeroed(n int) {
// reuseAsNonZeroed must be kept in sync with reuseAsZeroed.
if n == 0 {
panic(ErrZeroLength)
}
if s.mat.N > s.cap {
panic(badSymCap)
}
if s.IsEmpty() {
s.mat = blas64.Symmetric{
N: n,
Stride: n,
Data: use(s.mat.Data, n*n),
Uplo: blas.Upper,
}
s.cap = n
return
}
if s.mat.Uplo != blas.Upper {
panic(badSymTriangle)
}
if s.mat.N != n {
panic(ErrShape)
}
}
// reuseAsNonZeroed resizes an empty matrix to a n×n matrix,
// or checks that a non-empty matrix is n×n. It then zeros the
// elements of the matrix.
func (s *SymDense) reuseAsZeroed(n int) {
// reuseAsZeroed must be kept in sync with reuseAsNonZeroed.
if n == 0 {
panic(ErrZeroLength)
}
if s.mat.N > s.cap {
panic(badSymCap)
}
if s.IsEmpty() {
s.mat = blas64.Symmetric{
N: n,
Stride: n,
Data: useZeroed(s.mat.Data, n*n),
Uplo: blas.Upper,
}
s.cap = n
return
}
if s.mat.Uplo != blas.Upper {
panic(badSymTriangle)
}
if s.mat.N != n {
panic(ErrShape)
}
s.Zero()
}
func (s *SymDense) isolatedWorkspace(a Symmetric) (w *SymDense, restore func()) {
n := a.Symmetric()
if n == 0 {
panic(ErrZeroLength)
}
w = getWorkspaceSym(n, false)
return w, func() {
s.CopySym(w)
putWorkspaceSym(w)
}
}
// DiagView returns the diagonal as a matrix backed by the original data.
func (s *SymDense) DiagView() Diagonal {
n := s.mat.N
return &DiagDense{
mat: blas64.Vector{
N: n,
Inc: s.mat.Stride + 1,
Data: s.mat.Data[:(n-1)*s.mat.Stride+n],
},
}
}
func (s *SymDense) AddSym(a, b Symmetric) {
n := a.Symmetric()
if n != b.Symmetric() {
panic(ErrShape)
}
s.reuseAsNonZeroed(n)
if a, ok := a.(RawSymmetricer); ok {
if b, ok := b.(RawSymmetricer); ok {
amat, bmat := a.RawSymmetric(), b.RawSymmetric()
if s != a {
s.checkOverlap(generalFromSymmetric(amat))
}
if s != b {
s.checkOverlap(generalFromSymmetric(bmat))
}
for i := 0; i < n; i++ {
btmp := bmat.Data[i*bmat.Stride+i : i*bmat.Stride+n]
stmp := s.mat.Data[i*s.mat.Stride+i : i*s.mat.Stride+n]
for j, v := range amat.Data[i*amat.Stride+i : i*amat.Stride+n] {
stmp[j] = v + btmp[j]
}
}
return
}
}
s.checkOverlapMatrix(a)
s.checkOverlapMatrix(b)
for i := 0; i < n; i++ {
stmp := s.mat.Data[i*s.mat.Stride : i*s.mat.Stride+n]
for j := i; j < n; j++ {
stmp[j] = a.At(i, j) + b.At(i, j)
}
}
}
func (s *SymDense) CopySym(a Symmetric) int {
n := a.Symmetric()
n = min(n, s.mat.N)
if n == 0 {
return 0
}
switch a := a.(type) {
case RawSymmetricer:
amat := a.RawSymmetric()
if amat.Uplo != blas.Upper {
panic(badSymTriangle)
}
for i := 0; i < n; i++ {
copy(s.mat.Data[i*s.mat.Stride+i:i*s.mat.Stride+n], amat.Data[i*amat.Stride+i:i*amat.Stride+n])
}
default:
for i := 0; i < n; i++ {
stmp := s.mat.Data[i*s.mat.Stride : i*s.mat.Stride+n]
for j := i; j < n; j++ {
stmp[j] = a.At(i, j)
}
}
}
return n
}
// SymRankOne performs a symmetric rank-one update to the matrix a with x,
// which is treated as a column vector, and stores the result in the receiver
// s = a + alpha * x * xᵀ
func (s *SymDense) SymRankOne(a Symmetric, alpha float64, x Vector) {
n := x.Len()
if a.Symmetric() != n {
panic(ErrShape)
}
s.reuseAsNonZeroed(n)
if s != a {
if rs, ok := a.(RawSymmetricer); ok {
s.checkOverlap(generalFromSymmetric(rs.RawSymmetric()))
}
s.CopySym(a)
}
xU, _ := untransposeExtract(x)
if rv, ok := xU.(*VecDense); ok {
r, c := xU.Dims()
xmat := rv.mat
s.checkOverlap(generalFromVector(xmat, r, c))
blas64.Syr(alpha, xmat, s.mat)
return
}
for i := 0; i < n; i++ {
for j := i; j < n; j++ {
s.set(i, j, s.at(i, j)+alpha*x.AtVec(i)*x.AtVec(j))
}
}
}
// SymRankK performs a symmetric rank-k update to the matrix a and stores the
// result into the receiver. If a is zero, see SymOuterK.
// s = a + alpha * x * x'
func (s *SymDense) SymRankK(a Symmetric, alpha float64, x Matrix) {
n := a.Symmetric()
r, _ := x.Dims()
if r != n {
panic(ErrShape)
}
xMat, aTrans := untransposeExtract(x)
var g blas64.General
if rm, ok := xMat.(*Dense); ok {
g = rm.mat
} else {
g = DenseCopyOf(x).mat
aTrans = false
}
if a != s {
if rs, ok := a.(RawSymmetricer); ok {
s.checkOverlap(generalFromSymmetric(rs.RawSymmetric()))
}
s.reuseAsNonZeroed(n)
s.CopySym(a)
}
t := blas.NoTrans
if aTrans {
t = blas.Trans
}
blas64.Syrk(t, alpha, g, 1, s.mat)
}
// SymOuterK calculates the outer product of x with itself and stores
// the result into the receiver. It is equivalent to the matrix
// multiplication
// s = alpha * x * x'.
// In order to update an existing matrix, see SymRankOne.
func (s *SymDense) SymOuterK(alpha float64, x Matrix) {
n, _ := x.Dims()
switch {
case s.IsEmpty():
s.mat = blas64.Symmetric{
N: n,
Stride: n,
Data: useZeroed(s.mat.Data, n*n),
Uplo: blas.Upper,
}
s.cap = n
s.SymRankK(s, alpha, x)
case s.mat.Uplo != blas.Upper:
panic(badSymTriangle)
case s.mat.N == n:
if s == x {
w := getWorkspaceSym(n, true)
w.SymRankK(w, alpha, x)
s.CopySym(w)
putWorkspaceSym(w)
} else {
switch r := x.(type) {
case RawMatrixer:
s.checkOverlap(r.RawMatrix())
case RawSymmetricer:
s.checkOverlap(generalFromSymmetric(r.RawSymmetric()))
case RawTriangular:
s.checkOverlap(generalFromTriangular(r.RawTriangular()))
}
// Only zero the upper triangle.
for i := 0; i < n; i++ {
ri := i * s.mat.Stride
zero(s.mat.Data[ri+i : ri+n])
}
s.SymRankK(s, alpha, x)
}
default:
panic(ErrShape)
}
}
// RankTwo performs a symmetric rank-two update to the matrix a with the
// vectors x and y, which are treated as column vectors, and stores the
// result in the receiver
// m = a + alpha * (x * yᵀ + y * xᵀ)
func (s *SymDense) RankTwo(a Symmetric, alpha float64, x, y Vector) {
n := s.mat.N
if x.Len() != n {
panic(ErrShape)
}
if y.Len() != n {
panic(ErrShape)
}
if s != a {
if rs, ok := a.(RawSymmetricer); ok {
s.checkOverlap(generalFromSymmetric(rs.RawSymmetric()))
}
}
var xmat, ymat blas64.Vector
fast := true
xU, _ := untransposeExtract(x)
if rv, ok := xU.(*VecDense); ok {
r, c := xU.Dims()
xmat = rv.mat
s.checkOverlap(generalFromVector(xmat, r, c))
} else {
fast = false
}
yU, _ := untransposeExtract(y)
if rv, ok := yU.(*VecDense); ok {
r, c := yU.Dims()
ymat = rv.mat
s.checkOverlap(generalFromVector(ymat, r, c))
} else {
fast = false
}
if s != a {
if rs, ok := a.(RawSymmetricer); ok {
s.checkOverlap(generalFromSymmetric(rs.RawSymmetric()))
}
s.reuseAsNonZeroed(n)
s.CopySym(a)
}
if fast {
if s != a {
s.reuseAsNonZeroed(n)
s.CopySym(a)
}
blas64.Syr2(alpha, xmat, ymat, s.mat)
return
}
for i := 0; i < n; i++ {
s.reuseAsNonZeroed(n)
for j := i; j < n; j++ {
s.set(i, j, a.At(i, j)+alpha*(x.AtVec(i)*y.AtVec(j)+y.AtVec(i)*x.AtVec(j)))
}
}
}
// ScaleSym multiplies the elements of a by f, placing the result in the receiver.
func (s *SymDense) ScaleSym(f float64, a Symmetric) {
n := a.Symmetric()
s.reuseAsNonZeroed(n)
if a, ok := a.(RawSymmetricer); ok {
amat := a.RawSymmetric()
if s != a {
s.checkOverlap(generalFromSymmetric(amat))
}
for i := 0; i < n; i++ {
for j := i; j < n; j++ {
s.mat.Data[i*s.mat.Stride+j] = f * amat.Data[i*amat.Stride+j]
}
}
return
}
for i := 0; i < n; i++ {
for j := i; j < n; j++ {
s.mat.Data[i*s.mat.Stride+j] = f * a.At(i, j)
}
}
}
// SubsetSym extracts a subset of the rows and columns of the matrix a and stores
// the result in-place into the receiver. The resulting matrix size is
// len(set)×len(set). Specifically, at the conclusion of SubsetSym,
// s.At(i, j) equals a.At(set[i], set[j]). Note that the supplied set does not
// have to be a strict subset, dimension repeats are allowed.
func (s *SymDense) SubsetSym(a Symmetric, set []int) {
n := len(set)
na := a.Symmetric()
s.reuseAsNonZeroed(n)
var restore func()
if a == s {
s, restore = s.isolatedWorkspace(a)
defer restore()
}
if a, ok := a.(RawSymmetricer); ok {
raw := a.RawSymmetric()
if s != a {
s.checkOverlap(generalFromSymmetric(raw))
}
for i := 0; i < n; i++ {
ssub := s.mat.Data[i*s.mat.Stride : i*s.mat.Stride+n]
r := set[i]
rsub := raw.Data[r*raw.Stride : r*raw.Stride+na]
for j := i; j < n; j++ {
c := set[j]
if r <= c {
ssub[j] = rsub[c]
} else {
ssub[j] = raw.Data[c*raw.Stride+r]
}
}
}
return
}
for i := 0; i < n; i++ {
for j := i; j < n; j++ {
s.mat.Data[i*s.mat.Stride+j] = a.At(set[i], set[j])
}
}
}
// SliceSym returns a new Matrix that shares backing data with the receiver.
// The returned matrix starts at {i,i} of the receiver and extends k-i rows
// and columns. The final row and column in the resulting matrix is k-1.
// SliceSym panics with ErrIndexOutOfRange if the slice is outside the
// capacity of the receiver.
func (s *SymDense) SliceSym(i, k int) Symmetric {
sz := s.cap
if i < 0 || sz < i || k < i || sz < k {
panic(ErrIndexOutOfRange)
}
v := *s
v.mat.Data = s.mat.Data[i*s.mat.Stride+i : (k-1)*s.mat.Stride+k]
v.mat.N = k - i
v.cap = s.cap - i
return &v
}
// Trace returns the trace of the matrix.
func (s *SymDense) Trace() float64 {
// TODO(btracey): could use internal asm sum routine.
var v float64
for i := 0; i < s.mat.N; i++ {
v += s.mat.Data[i*s.mat.Stride+i]
}
return v
}
// GrowSym returns the receiver expanded by n rows and n columns. If the
// dimensions of the expanded matrix are outside the capacity of the receiver
// a new allocation is made, otherwise not. Note that the receiver itself is
// not modified during the call to GrowSquare.
func (s *SymDense) GrowSym(n int) Symmetric {
if n < 0 {
panic(ErrIndexOutOfRange)
}
if n == 0 {
return s
}
var v SymDense
n += s.mat.N
if n > s.cap {
v.mat = blas64.Symmetric{
N: n,
Stride: n,
Uplo: blas.Upper,
Data: make([]float64, n*n),
}
v.cap = n
// Copy elements, including those not currently visible. Use a temporary
// structure to avoid modifying the receiver.
var tmp SymDense
tmp.mat = blas64.Symmetric{
N: s.cap,
Stride: s.mat.Stride,
Data: s.mat.Data,
Uplo: s.mat.Uplo,
}
tmp.cap = s.cap
v.CopySym(&tmp)
return &v
}
v.mat = blas64.Symmetric{
N: n,
Stride: s.mat.Stride,
Uplo: blas.Upper,
Data: s.mat.Data[:(n-1)*s.mat.Stride+n],
}
v.cap = s.cap
return &v
}
// PowPSD computes a^pow where a is a positive symmetric definite matrix.
//
// PowPSD returns an error if the matrix is not not positive symmetric definite
// or the Eigendecomposition is not successful.
func (s *SymDense) PowPSD(a Symmetric, pow float64) error {
dim := a.Symmetric()
s.reuseAsNonZeroed(dim)
var eigen EigenSym
ok := eigen.Factorize(a, true)
if !ok {
return ErrFailedEigen
}
values := eigen.Values(nil)
for i, v := range values {
if v <= 0 {
return ErrNotPSD
}
values[i] = math.Pow(v, pow)
}
var u Dense
eigen.VectorsTo(&u)
s.SymOuterK(values[0], u.ColView(0))
var v VecDense
for i := 1; i < dim; i++ {
v.ColViewOf(&u, i)
s.SymRankOne(s, values[i], &v)
}
return nil
}