1 // Copyright ©2015 The Gonum Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
10 "gonum.org/v1/gonum/blas"
11 "gonum.org/v1/gonum/blas/blas64"
18 _ Symmetric = symDense
19 _ RawSymmetricer = symDense
20 _ MutableSymmetric = symDense
24 badSymTriangle = "mat: blas64.Symmetric not upper"
25 badSymCap = "mat: bad capacity for SymDense"
28 // SymDense is a symmetric matrix that uses dense storage. SymDense
29 // matrices are stored in the upper triangle.
30 type SymDense struct {
35 // Symmetric represents a symmetric matrix (where the element at {i, j} equals
36 // the element at {j, i}). Symmetric matrices are always square.
37 type Symmetric interface {
39 // Symmetric returns the number of rows/columns in the matrix.
43 // A RawSymmetricer can return a view of itself as a BLAS Symmetric matrix.
44 type RawSymmetricer interface {
45 RawSymmetric() blas64.Symmetric
48 // A MutableSymmetric can set elements of a symmetric matrix.
49 type MutableSymmetric interface {
51 SetSym(i, j int, v float64)
54 // NewSymDense creates a new Symmetric matrix with n rows and columns. If data == nil,
55 // a new slice is allocated for the backing slice. If len(data) == n*n, data is
56 // used as the backing slice, and changes to the elements of the returned SymDense
57 // will be reflected in data. If neither of these is true, NewSymDense will panic.
59 // The data must be arranged in row-major order, i.e. the (i*c + j)-th
60 // element in the data slice is the {i, j}-th element in the matrix.
61 // Only the values in the upper triangular portion of the matrix are used.
62 func NewSymDense(n int, data []float64) *SymDense {
64 panic("mat: negative dimension")
66 if data != nil && n*n != len(data) {
70 data = make([]float64, n*n)
73 mat: blas64.Symmetric{
83 // Dims returns the number of rows and columns in the matrix.
84 func (s *SymDense) Dims() (r, c int) {
85 return s.mat.N, s.mat.N
88 // Caps returns the number of rows and columns in the backing matrix.
89 func (s *SymDense) Caps() (r, c int) {
93 // T implements the Matrix interface. Symmetric matrices, by definition, are
94 // equal to their transpose, and this is a no-op.
95 func (s *SymDense) T() Matrix {
99 func (s *SymDense) Symmetric() int {
103 // RawSymmetric returns the matrix as a blas64.Symmetric. The returned
104 // value must be stored in upper triangular format.
105 func (s *SymDense) RawSymmetric() blas64.Symmetric {
109 // SetRawSymmetric sets the underlying blas64.Symmetric used by the receiver.
110 // Changes to elements in the receiver following the call will be reflected
111 // in b. SetRawSymmetric will panic if b is not an upper-encoded symmetric
113 func (s *SymDense) SetRawSymmetric(b blas64.Symmetric) {
114 if b.Uplo != blas.Upper {
115 panic(badSymTriangle)
120 // Reset zeros the dimensions of the matrix so that it can be reused as the
121 // receiver of a dimensionally restricted operation.
123 // See the Reseter interface for more information.
124 func (s *SymDense) Reset() {
125 // N and Stride must be zeroed in unison.
126 s.mat.N, s.mat.Stride = 0, 0
127 s.mat.Data = s.mat.Data[:0]
130 // IsZero returns whether the receiver is zero-sized. Zero-sized matrices can be the
131 // receiver for size-restricted operations. SymDense matrices can be zeroed using Reset.
132 func (s *SymDense) IsZero() bool {
133 // It must be the case that m.Dims() returns
134 // zeros in this case. See comment in Reset().
138 // reuseAs resizes an empty matrix to a n×n matrix,
139 // or checks that a non-empty matrix is n×n.
140 func (s *SymDense) reuseAs(n int) {
145 s.mat = blas64.Symmetric{
148 Data: use(s.mat.Data, n*n),
154 if s.mat.Uplo != blas.Upper {
155 panic(badSymTriangle)
162 func (s *SymDense) isolatedWorkspace(a Symmetric) (w *SymDense, restore func()) {
164 w = getWorkspaceSym(n, false)
171 func (s *SymDense) AddSym(a, b Symmetric) {
173 if n != b.Symmetric() {
178 if a, ok := a.(RawSymmetricer); ok {
179 if b, ok := b.(RawSymmetricer); ok {
180 amat, bmat := a.RawSymmetric(), b.RawSymmetric()
182 s.checkOverlap(generalFromSymmetric(amat))
185 s.checkOverlap(generalFromSymmetric(bmat))
187 for i := 0; i < n; i++ {
188 btmp := bmat.Data[i*bmat.Stride+i : i*bmat.Stride+n]
189 stmp := s.mat.Data[i*s.mat.Stride+i : i*s.mat.Stride+n]
190 for j, v := range amat.Data[i*amat.Stride+i : i*amat.Stride+n] {
191 stmp[j] = v + btmp[j]
198 for i := 0; i < n; i++ {
199 stmp := s.mat.Data[i*s.mat.Stride : i*s.mat.Stride+n]
200 for j := i; j < n; j++ {
201 stmp[j] = a.At(i, j) + b.At(i, j)
206 func (s *SymDense) CopySym(a Symmetric) int {
212 switch a := a.(type) {
214 amat := a.RawSymmetric()
215 if amat.Uplo != blas.Upper {
216 panic(badSymTriangle)
218 for i := 0; i < n; i++ {
219 copy(s.mat.Data[i*s.mat.Stride+i:i*s.mat.Stride+n], amat.Data[i*amat.Stride+i:i*amat.Stride+n])
222 for i := 0; i < n; i++ {
223 stmp := s.mat.Data[i*s.mat.Stride : i*s.mat.Stride+n]
224 for j := i; j < n; j++ {
232 // SymRankOne performs a symetric rank-one update to the matrix a and stores
233 // the result in the receiver
234 // s = a + alpha * x * x'
235 func (s *SymDense) SymRankOne(a Symmetric, alpha float64, x Vector) {
237 if a.Symmetric() != n || c != 1 {
243 if rs, ok := a.(RawSymmetricer); ok {
244 s.checkOverlap(generalFromSymmetric(rs.RawSymmetric()))
249 xU, _ := untranspose(x)
250 if rv, ok := xU.(RawVectorer); ok {
251 xmat := rv.RawVector()
252 s.checkOverlap((&VecDense{mat: xmat, n: n}).asGeneral())
253 blas64.Syr(alpha, xmat, s.mat)
257 for i := 0; i < n; i++ {
258 for j := i; j < n; j++ {
259 s.set(i, j, s.at(i, j)+alpha*x.AtVec(i)*x.AtVec(j))
264 // SymRankK performs a symmetric rank-k update to the matrix a and stores the
265 // result into the receiver. If a is zero, see SymOuterK.
266 // s = a + alpha * x * x'
267 func (s *SymDense) SymRankK(a Symmetric, alpha float64, x Matrix) {
273 xMat, aTrans := untranspose(x)
275 if rm, ok := xMat.(RawMatrixer); ok {
278 g = DenseCopyOf(x).mat
282 if rs, ok := a.(RawSymmetricer); ok {
283 s.checkOverlap(generalFromSymmetric(rs.RawSymmetric()))
292 blas64.Syrk(t, alpha, g, 1, s.mat)
295 // SymOuterK calculates the outer product of x with itself and stores
296 // the result into the receiver. It is equivalent to the matrix
298 // s = alpha * x * x'.
299 // In order to update an existing matrix, see SymRankOne.
300 func (s *SymDense) SymOuterK(alpha float64, x Matrix) {
304 s.mat = blas64.Symmetric{
307 Data: useZeroed(s.mat.Data, n*n),
311 s.SymRankK(s, alpha, x)
312 case s.mat.Uplo != blas.Upper:
313 panic(badSymTriangle)
316 w := getWorkspaceSym(n, true)
317 w.SymRankK(w, alpha, x)
321 switch r := x.(type) {
323 s.checkOverlap(r.RawMatrix())
325 s.checkOverlap(generalFromSymmetric(r.RawSymmetric()))
327 s.checkOverlap(generalFromTriangular(r.RawTriangular()))
329 // Only zero the upper triangle.
330 for i := 0; i < n; i++ {
331 ri := i * s.mat.Stride
332 zero(s.mat.Data[ri+i : ri+n])
334 s.SymRankK(s, alpha, x)
341 // RankTwo performs a symmmetric rank-two update to the matrix a and stores
342 // the result in the receiver
343 // m = a + alpha * (x * y' + y * x')
344 func (s *SymDense) RankTwo(a Symmetric, alpha float64, x, y Vector) {
347 if xr != n || xc != 1 {
351 if yr != n || yc != 1 {
356 if rs, ok := a.(RawSymmetricer); ok {
357 s.checkOverlap(generalFromSymmetric(rs.RawSymmetric()))
361 var xmat, ymat blas64.Vector
363 xU, _ := untranspose(x)
364 if rv, ok := xU.(RawVectorer); ok {
365 xmat = rv.RawVector()
366 s.checkOverlap((&VecDense{mat: xmat, n: x.Len()}).asGeneral())
370 yU, _ := untranspose(y)
371 if rv, ok := yU.(RawVectorer); ok {
372 ymat = rv.RawVector()
373 s.checkOverlap((&VecDense{mat: ymat, n: y.Len()}).asGeneral())
379 if rs, ok := a.(RawSymmetricer); ok {
380 s.checkOverlap(generalFromSymmetric(rs.RawSymmetric()))
391 blas64.Syr2(alpha, xmat, ymat, s.mat)
395 for i := 0; i < n; i++ {
397 for j := i; j < n; j++ {
398 s.set(i, j, a.At(i, j)+alpha*(x.AtVec(i)*y.AtVec(j)+y.AtVec(i)*x.AtVec(j)))
403 // ScaleSym multiplies the elements of a by f, placing the result in the receiver.
404 func (s *SymDense) ScaleSym(f float64, a Symmetric) {
407 if a, ok := a.(RawSymmetricer); ok {
408 amat := a.RawSymmetric()
410 s.checkOverlap(generalFromSymmetric(amat))
412 for i := 0; i < n; i++ {
413 for j := i; j < n; j++ {
414 s.mat.Data[i*s.mat.Stride+j] = f * amat.Data[i*amat.Stride+j]
419 for i := 0; i < n; i++ {
420 for j := i; j < n; j++ {
421 s.mat.Data[i*s.mat.Stride+j] = f * a.At(i, j)
426 // SubsetSym extracts a subset of the rows and columns of the matrix a and stores
427 // the result in-place into the receiver. The resulting matrix size is
428 // len(set)×len(set). Specifically, at the conclusion of SubsetSym,
429 // s.At(i, j) equals a.At(set[i], set[j]). Note that the supplied set does not
430 // have to be a strict subset, dimension repeats are allowed.
431 func (s *SymDense) SubsetSym(a Symmetric, set []int) {
437 s, restore = s.isolatedWorkspace(a)
441 if a, ok := a.(RawSymmetricer); ok {
442 raw := a.RawSymmetric()
444 s.checkOverlap(generalFromSymmetric(raw))
446 for i := 0; i < n; i++ {
447 ssub := s.mat.Data[i*s.mat.Stride : i*s.mat.Stride+n]
449 rsub := raw.Data[r*raw.Stride : r*raw.Stride+na]
450 for j := i; j < n; j++ {
455 ssub[j] = raw.Data[c*raw.Stride+r]
461 for i := 0; i < n; i++ {
462 for j := i; j < n; j++ {
463 s.mat.Data[i*s.mat.Stride+j] = a.At(set[i], set[j])
468 // SliceSquare returns a new Matrix that shares backing data with the receiver.
469 // The returned matrix starts at {i,i} of the receiver and extends k-i rows
470 // and columns. The final row and column in the resulting matrix is k-1.
471 // SliceSquare panics with ErrIndexOutOfRange if the slice is outside the capacity
473 func (s *SymDense) SliceSquare(i, k int) Matrix {
475 if i < 0 || sz < i || k < i || sz < k {
476 panic(ErrIndexOutOfRange)
479 v.mat.Data = s.mat.Data[i*s.mat.Stride+i : (k-1)*s.mat.Stride+k]
485 // GrowSquare returns the receiver expanded by n rows and n columns. If the
486 // dimensions of the expanded matrix are outside the capacity of the receiver
487 // a new allocation is made, otherwise not. Note that the receiver itself is
488 // not modified during the call to GrowSquare.
489 func (s *SymDense) GrowSquare(n int) Matrix {
491 panic(ErrIndexOutOfRange)
499 v.mat = blas64.Symmetric{
503 Data: make([]float64, n*n),
506 // Copy elements, including those not currently visible. Use a temporary
507 // structure to avoid modifying the receiver.
509 tmp.mat = blas64.Symmetric{
511 Stride: s.mat.Stride,
519 v.mat = blas64.Symmetric{
521 Stride: s.mat.Stride,
523 Data: s.mat.Data[:(n-1)*s.mat.Stride+n],
529 // PowPSD computes a^pow where a is a positive symmetric definite matrix.
531 // PowPSD returns an error if the matrix is not not positive symmetric definite
532 // or the Eigendecomposition is not successful.
533 func (s *SymDense) PowPSD(a Symmetric, pow float64) error {
538 ok := eigen.Factorize(a, true)
540 return ErrFailedEigen
542 values := eigen.Values(nil)
543 for i, v := range values {
547 values[i] = math.Pow(v, pow)
550 u.EigenvectorsSym(&eigen)
552 s.SymOuterK(values[0], u.ColView(0))
555 for i := 1; i < dim; i++ {
557 s.SymRankOne(s, values[i], &v)