github.com/gopherd/gonum@v0.0.4/mat/symmetric.go (about) 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. 4 5 package mat 6 7 import ( 8 "math" 9 10 "github.com/gopherd/gonum/blas" 11 "github.com/gopherd/gonum/blas/blas64" 12 "github.com/gopherd/gonum/lapack" 13 "github.com/gopherd/gonum/lapack/lapack64" 14 ) 15 16 var ( 17 symDense *SymDense 18 19 _ Matrix = symDense 20 _ allMatrix = symDense 21 _ denseMatrix = symDense 22 _ Symmetric = symDense 23 _ RawSymmetricer = symDense 24 _ MutableSymmetric = symDense 25 ) 26 27 const badSymTriangle = "mat: blas64.Symmetric not upper" 28 29 // SymDense is a symmetric matrix that uses dense storage. SymDense 30 // matrices are stored in the upper triangle. 31 type SymDense struct { 32 mat blas64.Symmetric 33 cap int 34 } 35 36 // Symmetric represents a symmetric matrix (where the element at {i, j} equals 37 // the element at {j, i}). Symmetric matrices are always square. 38 type Symmetric interface { 39 Matrix 40 // SymmetricDim returns the number of rows/columns in the matrix. 41 SymmetricDim() int 42 } 43 44 // A RawSymmetricer can return a view of itself as a BLAS Symmetric matrix. 45 type RawSymmetricer interface { 46 RawSymmetric() blas64.Symmetric 47 } 48 49 // A MutableSymmetric can set elements of a symmetric matrix. 50 type MutableSymmetric interface { 51 Symmetric 52 SetSym(i, j int, v float64) 53 } 54 55 // NewSymDense creates a new Symmetric matrix with n rows and columns. If data == nil, 56 // a new slice is allocated for the backing slice. If len(data) == n*n, data is 57 // used as the backing slice, and changes to the elements of the returned SymDense 58 // will be reflected in data. If neither of these is true, NewSymDense will panic. 59 // NewSymDense will panic if n is zero. 60 // 61 // The data must be arranged in row-major order, i.e. the (i*c + j)-th 62 // element in the data slice is the {i, j}-th element in the matrix. 63 // Only the values in the upper triangular portion of the matrix are used. 64 func NewSymDense(n int, data []float64) *SymDense { 65 if n <= 0 { 66 if n == 0 { 67 panic(ErrZeroLength) 68 } 69 panic("mat: negative dimension") 70 } 71 if data != nil && n*n != len(data) { 72 panic(ErrShape) 73 } 74 if data == nil { 75 data = make([]float64, n*n) 76 } 77 return &SymDense{ 78 mat: blas64.Symmetric{ 79 N: n, 80 Stride: n, 81 Data: data, 82 Uplo: blas.Upper, 83 }, 84 cap: n, 85 } 86 } 87 88 // Dims returns the number of rows and columns in the matrix. 89 func (s *SymDense) Dims() (r, c int) { 90 return s.mat.N, s.mat.N 91 } 92 93 // Caps returns the number of rows and columns in the backing matrix. 94 func (s *SymDense) Caps() (r, c int) { 95 return s.cap, s.cap 96 } 97 98 // T returns the receiver, the transpose of a symmetric matrix. 99 func (s *SymDense) T() Matrix { 100 return s 101 } 102 103 // SymmetricDim implements the Symmetric interface and returns the number of rows 104 // and columns in the matrix. 105 func (s *SymDense) SymmetricDim() int { 106 return s.mat.N 107 } 108 109 // RawSymmetric returns the matrix as a blas64.Symmetric. The returned 110 // value must be stored in upper triangular format. 111 func (s *SymDense) RawSymmetric() blas64.Symmetric { 112 return s.mat 113 } 114 115 // SetRawSymmetric sets the underlying blas64.Symmetric used by the receiver. 116 // Changes to elements in the receiver following the call will be reflected 117 // in the input. 118 // 119 // The supplied Symmetric must use blas.Upper storage format. 120 func (s *SymDense) SetRawSymmetric(mat blas64.Symmetric) { 121 if mat.Uplo != blas.Upper { 122 panic(badSymTriangle) 123 } 124 s.cap = mat.N 125 s.mat = mat 126 } 127 128 // Reset empties the matrix so that it can be reused as the 129 // receiver of a dimensionally restricted operation. 130 // 131 // Reset should not be used when the matrix shares backing data. 132 // See the Reseter interface for more information. 133 func (s *SymDense) Reset() { 134 // N and Stride must be zeroed in unison. 135 s.mat.N, s.mat.Stride = 0, 0 136 s.mat.Data = s.mat.Data[:0] 137 } 138 139 // ReuseAsSym changes the receiver if it IsEmpty() to be of size n×n. 140 // 141 // ReuseAsSym re-uses the backing data slice if it has sufficient capacity, 142 // otherwise a new slice is allocated. The backing data is zero on return. 143 // 144 // ReuseAsSym panics if the receiver is not empty, and panics if 145 // the input size is less than one. To empty the receiver for re-use, 146 // Reset should be used. 147 func (s *SymDense) ReuseAsSym(n int) { 148 if n <= 0 { 149 if n == 0 { 150 panic(ErrZeroLength) 151 } 152 panic(ErrNegativeDimension) 153 } 154 if !s.IsEmpty() { 155 panic(ErrReuseNonEmpty) 156 } 157 s.reuseAsZeroed(n) 158 } 159 160 // Zero sets all of the matrix elements to zero. 161 func (s *SymDense) Zero() { 162 for i := 0; i < s.mat.N; i++ { 163 zero(s.mat.Data[i*s.mat.Stride+i : i*s.mat.Stride+s.mat.N]) 164 } 165 } 166 167 // IsEmpty returns whether the receiver is empty. Empty matrices can be the 168 // receiver for size-restricted operations. The receiver can be emptied using 169 // Reset. 170 func (s *SymDense) IsEmpty() bool { 171 // It must be the case that m.Dims() returns 172 // zeros in this case. See comment in Reset(). 173 return s.mat.N == 0 174 } 175 176 // reuseAsNonZeroed resizes an empty matrix to a n×n matrix, 177 // or checks that a non-empty matrix is n×n. 178 func (s *SymDense) reuseAsNonZeroed(n int) { 179 // reuseAsNonZeroed must be kept in sync with reuseAsZeroed. 180 if n == 0 { 181 panic(ErrZeroLength) 182 } 183 if s.mat.N > s.cap { 184 // Panic as a string, not a mat.Error. 185 panic(badCap) 186 } 187 if s.IsEmpty() { 188 s.mat = blas64.Symmetric{ 189 N: n, 190 Stride: n, 191 Data: use(s.mat.Data, n*n), 192 Uplo: blas.Upper, 193 } 194 s.cap = n 195 return 196 } 197 if s.mat.Uplo != blas.Upper { 198 panic(badSymTriangle) 199 } 200 if s.mat.N != n { 201 panic(ErrShape) 202 } 203 } 204 205 // reuseAsNonZeroed resizes an empty matrix to a n×n matrix, 206 // or checks that a non-empty matrix is n×n. It then zeros the 207 // elements of the matrix. 208 func (s *SymDense) reuseAsZeroed(n int) { 209 // reuseAsZeroed must be kept in sync with reuseAsNonZeroed. 210 if n == 0 { 211 panic(ErrZeroLength) 212 } 213 if s.mat.N > s.cap { 214 // Panic as a string, not a mat.Error. 215 panic(badCap) 216 } 217 if s.IsEmpty() { 218 s.mat = blas64.Symmetric{ 219 N: n, 220 Stride: n, 221 Data: useZeroed(s.mat.Data, n*n), 222 Uplo: blas.Upper, 223 } 224 s.cap = n 225 return 226 } 227 if s.mat.Uplo != blas.Upper { 228 panic(badSymTriangle) 229 } 230 if s.mat.N != n { 231 panic(ErrShape) 232 } 233 s.Zero() 234 } 235 236 func (s *SymDense) isolatedWorkspace(a Symmetric) (w *SymDense, restore func()) { 237 n := a.SymmetricDim() 238 if n == 0 { 239 panic(ErrZeroLength) 240 } 241 w = getSymDenseWorkspace(n, false) 242 return w, func() { 243 s.CopySym(w) 244 putSymDenseWorkspace(w) 245 } 246 } 247 248 // DiagView returns the diagonal as a matrix backed by the original data. 249 func (s *SymDense) DiagView() Diagonal { 250 n := s.mat.N 251 return &DiagDense{ 252 mat: blas64.Vector{ 253 N: n, 254 Inc: s.mat.Stride + 1, 255 Data: s.mat.Data[:(n-1)*s.mat.Stride+n], 256 }, 257 } 258 } 259 260 func (s *SymDense) AddSym(a, b Symmetric) { 261 n := a.SymmetricDim() 262 if n != b.SymmetricDim() { 263 panic(ErrShape) 264 } 265 s.reuseAsNonZeroed(n) 266 267 if a, ok := a.(RawSymmetricer); ok { 268 if b, ok := b.(RawSymmetricer); ok { 269 amat, bmat := a.RawSymmetric(), b.RawSymmetric() 270 if s != a { 271 s.checkOverlap(generalFromSymmetric(amat)) 272 } 273 if s != b { 274 s.checkOverlap(generalFromSymmetric(bmat)) 275 } 276 for i := 0; i < n; i++ { 277 btmp := bmat.Data[i*bmat.Stride+i : i*bmat.Stride+n] 278 stmp := s.mat.Data[i*s.mat.Stride+i : i*s.mat.Stride+n] 279 for j, v := range amat.Data[i*amat.Stride+i : i*amat.Stride+n] { 280 stmp[j] = v + btmp[j] 281 } 282 } 283 return 284 } 285 } 286 287 s.checkOverlapMatrix(a) 288 s.checkOverlapMatrix(b) 289 for i := 0; i < n; i++ { 290 stmp := s.mat.Data[i*s.mat.Stride : i*s.mat.Stride+n] 291 for j := i; j < n; j++ { 292 stmp[j] = a.At(i, j) + b.At(i, j) 293 } 294 } 295 } 296 297 func (s *SymDense) CopySym(a Symmetric) int { 298 n := a.SymmetricDim() 299 n = min(n, s.mat.N) 300 if n == 0 { 301 return 0 302 } 303 switch a := a.(type) { 304 case RawSymmetricer: 305 amat := a.RawSymmetric() 306 if amat.Uplo != blas.Upper { 307 panic(badSymTriangle) 308 } 309 for i := 0; i < n; i++ { 310 copy(s.mat.Data[i*s.mat.Stride+i:i*s.mat.Stride+n], amat.Data[i*amat.Stride+i:i*amat.Stride+n]) 311 } 312 default: 313 for i := 0; i < n; i++ { 314 stmp := s.mat.Data[i*s.mat.Stride : i*s.mat.Stride+n] 315 for j := i; j < n; j++ { 316 stmp[j] = a.At(i, j) 317 } 318 } 319 } 320 return n 321 } 322 323 // SymRankOne performs a symmetric rank-one update to the matrix a with x, 324 // which is treated as a column vector, and stores the result in the receiver 325 // s = a + alpha * x * xᵀ 326 func (s *SymDense) SymRankOne(a Symmetric, alpha float64, x Vector) { 327 n := x.Len() 328 if a.SymmetricDim() != n { 329 panic(ErrShape) 330 } 331 s.reuseAsNonZeroed(n) 332 333 if s != a { 334 if rs, ok := a.(RawSymmetricer); ok { 335 s.checkOverlap(generalFromSymmetric(rs.RawSymmetric())) 336 } 337 s.CopySym(a) 338 } 339 340 xU, _ := untransposeExtract(x) 341 if rv, ok := xU.(*VecDense); ok { 342 r, c := xU.Dims() 343 xmat := rv.mat 344 s.checkOverlap(generalFromVector(xmat, r, c)) 345 blas64.Syr(alpha, xmat, s.mat) 346 return 347 } 348 349 for i := 0; i < n; i++ { 350 for j := i; j < n; j++ { 351 s.set(i, j, s.at(i, j)+alpha*x.AtVec(i)*x.AtVec(j)) 352 } 353 } 354 } 355 356 // SymRankK performs a symmetric rank-k update to the matrix a and stores the 357 // result into the receiver. If a is zero, see SymOuterK. 358 // s = a + alpha * x * x' 359 func (s *SymDense) SymRankK(a Symmetric, alpha float64, x Matrix) { 360 n := a.SymmetricDim() 361 r, _ := x.Dims() 362 if r != n { 363 panic(ErrShape) 364 } 365 xMat, aTrans := untransposeExtract(x) 366 var g blas64.General 367 if rm, ok := xMat.(*Dense); ok { 368 g = rm.mat 369 } else { 370 g = DenseCopyOf(x).mat 371 aTrans = false 372 } 373 if a != s { 374 if rs, ok := a.(RawSymmetricer); ok { 375 s.checkOverlap(generalFromSymmetric(rs.RawSymmetric())) 376 } 377 s.reuseAsNonZeroed(n) 378 s.CopySym(a) 379 } 380 t := blas.NoTrans 381 if aTrans { 382 t = blas.Trans 383 } 384 blas64.Syrk(t, alpha, g, 1, s.mat) 385 } 386 387 // SymOuterK calculates the outer product of x with itself and stores 388 // the result into the receiver. It is equivalent to the matrix 389 // multiplication 390 // s = alpha * x * x'. 391 // In order to update an existing matrix, see SymRankOne. 392 func (s *SymDense) SymOuterK(alpha float64, x Matrix) { 393 n, _ := x.Dims() 394 switch { 395 case s.IsEmpty(): 396 s.mat = blas64.Symmetric{ 397 N: n, 398 Stride: n, 399 Data: useZeroed(s.mat.Data, n*n), 400 Uplo: blas.Upper, 401 } 402 s.cap = n 403 s.SymRankK(s, alpha, x) 404 case s.mat.Uplo != blas.Upper: 405 panic(badSymTriangle) 406 case s.mat.N == n: 407 if s == x { 408 w := getSymDenseWorkspace(n, true) 409 w.SymRankK(w, alpha, x) 410 s.CopySym(w) 411 putSymDenseWorkspace(w) 412 } else { 413 switch r := x.(type) { 414 case RawMatrixer: 415 s.checkOverlap(r.RawMatrix()) 416 case RawSymmetricer: 417 s.checkOverlap(generalFromSymmetric(r.RawSymmetric())) 418 case RawTriangular: 419 s.checkOverlap(generalFromTriangular(r.RawTriangular())) 420 } 421 // Only zero the upper triangle. 422 for i := 0; i < n; i++ { 423 ri := i * s.mat.Stride 424 zero(s.mat.Data[ri+i : ri+n]) 425 } 426 s.SymRankK(s, alpha, x) 427 } 428 default: 429 panic(ErrShape) 430 } 431 } 432 433 // RankTwo performs a symmetric rank-two update to the matrix a with the 434 // vectors x and y, which are treated as column vectors, and stores the 435 // result in the receiver 436 // m = a + alpha * (x * yᵀ + y * xᵀ) 437 func (s *SymDense) RankTwo(a Symmetric, alpha float64, x, y Vector) { 438 n := s.mat.N 439 if x.Len() != n { 440 panic(ErrShape) 441 } 442 if y.Len() != n { 443 panic(ErrShape) 444 } 445 446 if s != a { 447 if rs, ok := a.(RawSymmetricer); ok { 448 s.checkOverlap(generalFromSymmetric(rs.RawSymmetric())) 449 } 450 } 451 452 var xmat, ymat blas64.Vector 453 fast := true 454 xU, _ := untransposeExtract(x) 455 if rv, ok := xU.(*VecDense); ok { 456 r, c := xU.Dims() 457 xmat = rv.mat 458 s.checkOverlap(generalFromVector(xmat, r, c)) 459 } else { 460 fast = false 461 } 462 yU, _ := untransposeExtract(y) 463 if rv, ok := yU.(*VecDense); ok { 464 r, c := yU.Dims() 465 ymat = rv.mat 466 s.checkOverlap(generalFromVector(ymat, r, c)) 467 } else { 468 fast = false 469 } 470 471 if s != a { 472 if rs, ok := a.(RawSymmetricer); ok { 473 s.checkOverlap(generalFromSymmetric(rs.RawSymmetric())) 474 } 475 s.reuseAsNonZeroed(n) 476 s.CopySym(a) 477 } 478 479 if fast { 480 if s != a { 481 s.reuseAsNonZeroed(n) 482 s.CopySym(a) 483 } 484 blas64.Syr2(alpha, xmat, ymat, s.mat) 485 return 486 } 487 488 for i := 0; i < n; i++ { 489 s.reuseAsNonZeroed(n) 490 for j := i; j < n; j++ { 491 s.set(i, j, a.At(i, j)+alpha*(x.AtVec(i)*y.AtVec(j)+y.AtVec(i)*x.AtVec(j))) 492 } 493 } 494 } 495 496 // ScaleSym multiplies the elements of a by f, placing the result in the receiver. 497 func (s *SymDense) ScaleSym(f float64, a Symmetric) { 498 n := a.SymmetricDim() 499 s.reuseAsNonZeroed(n) 500 if a, ok := a.(RawSymmetricer); ok { 501 amat := a.RawSymmetric() 502 if s != a { 503 s.checkOverlap(generalFromSymmetric(amat)) 504 } 505 for i := 0; i < n; i++ { 506 for j := i; j < n; j++ { 507 s.mat.Data[i*s.mat.Stride+j] = f * amat.Data[i*amat.Stride+j] 508 } 509 } 510 return 511 } 512 for i := 0; i < n; i++ { 513 for j := i; j < n; j++ { 514 s.mat.Data[i*s.mat.Stride+j] = f * a.At(i, j) 515 } 516 } 517 } 518 519 // SubsetSym extracts a subset of the rows and columns of the matrix a and stores 520 // the result in-place into the receiver. The resulting matrix size is 521 // len(set)×len(set). Specifically, at the conclusion of SubsetSym, 522 // s.At(i, j) equals a.At(set[i], set[j]). Note that the supplied set does not 523 // have to be a strict subset, dimension repeats are allowed. 524 func (s *SymDense) SubsetSym(a Symmetric, set []int) { 525 n := len(set) 526 na := a.SymmetricDim() 527 s.reuseAsNonZeroed(n) 528 var restore func() 529 if a == s { 530 s, restore = s.isolatedWorkspace(a) 531 defer restore() 532 } 533 534 if a, ok := a.(RawSymmetricer); ok { 535 raw := a.RawSymmetric() 536 if s != a { 537 s.checkOverlap(generalFromSymmetric(raw)) 538 } 539 for i := 0; i < n; i++ { 540 ssub := s.mat.Data[i*s.mat.Stride : i*s.mat.Stride+n] 541 r := set[i] 542 rsub := raw.Data[r*raw.Stride : r*raw.Stride+na] 543 for j := i; j < n; j++ { 544 c := set[j] 545 if r <= c { 546 ssub[j] = rsub[c] 547 } else { 548 ssub[j] = raw.Data[c*raw.Stride+r] 549 } 550 } 551 } 552 return 553 } 554 for i := 0; i < n; i++ { 555 for j := i; j < n; j++ { 556 s.mat.Data[i*s.mat.Stride+j] = a.At(set[i], set[j]) 557 } 558 } 559 } 560 561 // SliceSym returns a new Matrix that shares backing data with the receiver. 562 // The returned matrix starts at {i,i} of the receiver and extends k-i rows 563 // and columns. The final row and column in the resulting matrix is k-1. 564 // SliceSym panics with ErrIndexOutOfRange if the slice is outside the 565 // capacity of the receiver. 566 func (s *SymDense) SliceSym(i, k int) Symmetric { 567 return s.sliceSym(i, k) 568 } 569 570 func (s *SymDense) sliceSym(i, k int) *SymDense { 571 sz := s.cap 572 if i < 0 || sz < i || k < i || sz < k { 573 panic(ErrIndexOutOfRange) 574 } 575 v := *s 576 v.mat.Data = s.mat.Data[i*s.mat.Stride+i : (k-1)*s.mat.Stride+k] 577 v.mat.N = k - i 578 v.cap = s.cap - i 579 return &v 580 } 581 582 // Norm returns the specified norm of the receiver. Valid norms are: 583 // 1 - The maximum absolute column sum 584 // 2 - The Frobenius norm, the square root of the sum of the squares of the elements 585 // Inf - The maximum absolute row sum 586 // 587 // Norm will panic with ErrNormOrder if an illegal norm is specified and with 588 // ErrZeroLength if the matrix has zero size. 589 func (s *SymDense) Norm(norm float64) float64 { 590 if s.IsEmpty() { 591 panic(ErrZeroLength) 592 } 593 lnorm := normLapack(norm, false) 594 if lnorm == lapack.MaxColumnSum || lnorm == lapack.MaxRowSum { 595 work := getFloat64s(s.mat.N, false) 596 defer putFloat64s(work) 597 return lapack64.Lansy(lnorm, s.mat, work) 598 } 599 return lapack64.Lansy(lnorm, s.mat, nil) 600 } 601 602 // Trace returns the trace of the matrix. 603 // 604 // Trace will panic with ErrZeroLength if the matrix has zero size. 605 func (s *SymDense) Trace() float64 { 606 if s.IsEmpty() { 607 panic(ErrZeroLength) 608 } 609 // TODO(btracey): could use internal asm sum routine. 610 var v float64 611 for i := 0; i < s.mat.N; i++ { 612 v += s.mat.Data[i*s.mat.Stride+i] 613 } 614 return v 615 } 616 617 // GrowSym returns the receiver expanded by n rows and n columns. If the 618 // dimensions of the expanded matrix are outside the capacity of the receiver 619 // a new allocation is made, otherwise not. Note that the receiver itself is 620 // not modified during the call to GrowSquare. 621 func (s *SymDense) GrowSym(n int) Symmetric { 622 if n < 0 { 623 panic(ErrIndexOutOfRange) 624 } 625 if n == 0 { 626 return s 627 } 628 var v SymDense 629 n += s.mat.N 630 if s.IsEmpty() || n > s.cap { 631 v.mat = blas64.Symmetric{ 632 N: n, 633 Stride: n, 634 Uplo: blas.Upper, 635 Data: make([]float64, n*n), 636 } 637 v.cap = n 638 // Copy elements, including those not currently visible. Use a temporary 639 // structure to avoid modifying the receiver. 640 var tmp SymDense 641 tmp.mat = blas64.Symmetric{ 642 N: s.cap, 643 Stride: s.mat.Stride, 644 Data: s.mat.Data, 645 Uplo: s.mat.Uplo, 646 } 647 tmp.cap = s.cap 648 v.CopySym(&tmp) 649 return &v 650 } 651 v.mat = blas64.Symmetric{ 652 N: n, 653 Stride: s.mat.Stride, 654 Uplo: blas.Upper, 655 Data: s.mat.Data[:(n-1)*s.mat.Stride+n], 656 } 657 v.cap = s.cap 658 return &v 659 } 660 661 // PowPSD computes a^pow where a is a positive symmetric definite matrix. 662 // 663 // PowPSD returns an error if the matrix is not positive symmetric definite 664 // or the Eigen decomposition is not successful. 665 func (s *SymDense) PowPSD(a Symmetric, pow float64) error { 666 dim := a.SymmetricDim() 667 s.reuseAsNonZeroed(dim) 668 669 var eigen EigenSym 670 ok := eigen.Factorize(a, true) 671 if !ok { 672 return ErrFailedEigen 673 } 674 values := eigen.Values(nil) 675 for i, v := range values { 676 if v <= 0 { 677 return ErrNotPSD 678 } 679 values[i] = math.Pow(v, pow) 680 } 681 var u Dense 682 eigen.VectorsTo(&u) 683 684 s.SymOuterK(values[0], u.ColView(0)) 685 686 var v VecDense 687 for i := 1; i < dim; i++ { 688 v.ColViewOf(&u, i) 689 s.SymRankOne(s, values[i], &v) 690 } 691 return nil 692 }