gonum.org/v1/gonum@v0.14.0/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 "gonum.org/v1/gonum/blas" 11 "gonum.org/v1/gonum/blas/blas64" 12 "gonum.org/v1/gonum/lapack" 13 "gonum.org/v1/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 // 326 // s = a + alpha * x * xᵀ 327 func (s *SymDense) SymRankOne(a Symmetric, alpha float64, x Vector) { 328 n := x.Len() 329 if a.SymmetricDim() != n { 330 panic(ErrShape) 331 } 332 s.reuseAsNonZeroed(n) 333 334 if s != a { 335 if rs, ok := a.(RawSymmetricer); ok { 336 s.checkOverlap(generalFromSymmetric(rs.RawSymmetric())) 337 } 338 s.CopySym(a) 339 } 340 341 xU, _ := untransposeExtract(x) 342 if rv, ok := xU.(*VecDense); ok { 343 r, c := xU.Dims() 344 xmat := rv.mat 345 s.checkOverlap(generalFromVector(xmat, r, c)) 346 blas64.Syr(alpha, xmat, s.mat) 347 return 348 } 349 350 for i := 0; i < n; i++ { 351 for j := i; j < n; j++ { 352 s.set(i, j, s.at(i, j)+alpha*x.AtVec(i)*x.AtVec(j)) 353 } 354 } 355 } 356 357 // SymRankK performs a symmetric rank-k update to the matrix a and stores the 358 // result into the receiver. If a is zero, see SymOuterK. 359 // 360 // s = a + alpha * x * x' 361 func (s *SymDense) SymRankK(a Symmetric, alpha float64, x Matrix) { 362 n := a.SymmetricDim() 363 r, _ := x.Dims() 364 if r != n { 365 panic(ErrShape) 366 } 367 xMat, aTrans := untransposeExtract(x) 368 var g blas64.General 369 if rm, ok := xMat.(*Dense); ok { 370 g = rm.mat 371 } else { 372 g = DenseCopyOf(x).mat 373 aTrans = false 374 } 375 if a != s { 376 if rs, ok := a.(RawSymmetricer); ok { 377 s.checkOverlap(generalFromSymmetric(rs.RawSymmetric())) 378 } 379 s.reuseAsNonZeroed(n) 380 s.CopySym(a) 381 } 382 t := blas.NoTrans 383 if aTrans { 384 t = blas.Trans 385 } 386 blas64.Syrk(t, alpha, g, 1, s.mat) 387 } 388 389 // SymOuterK calculates the outer product of x with itself and stores 390 // the result into the receiver. It is equivalent to the matrix 391 // multiplication 392 // 393 // s = alpha * x * x'. 394 // 395 // In order to update an existing matrix, see SymRankOne. 396 func (s *SymDense) SymOuterK(alpha float64, x Matrix) { 397 n, _ := x.Dims() 398 switch { 399 case s.IsEmpty(): 400 s.mat = blas64.Symmetric{ 401 N: n, 402 Stride: n, 403 Data: useZeroed(s.mat.Data, n*n), 404 Uplo: blas.Upper, 405 } 406 s.cap = n 407 s.SymRankK(s, alpha, x) 408 case s.mat.Uplo != blas.Upper: 409 panic(badSymTriangle) 410 case s.mat.N == n: 411 if s == x { 412 w := getSymDenseWorkspace(n, true) 413 w.SymRankK(w, alpha, x) 414 s.CopySym(w) 415 putSymDenseWorkspace(w) 416 } else { 417 switch r := x.(type) { 418 case RawMatrixer: 419 s.checkOverlap(r.RawMatrix()) 420 case RawSymmetricer: 421 s.checkOverlap(generalFromSymmetric(r.RawSymmetric())) 422 case RawTriangular: 423 s.checkOverlap(generalFromTriangular(r.RawTriangular())) 424 } 425 // Only zero the upper triangle. 426 for i := 0; i < n; i++ { 427 ri := i * s.mat.Stride 428 zero(s.mat.Data[ri+i : ri+n]) 429 } 430 s.SymRankK(s, alpha, x) 431 } 432 default: 433 panic(ErrShape) 434 } 435 } 436 437 // RankTwo performs a symmetric rank-two update to the matrix a with the 438 // vectors x and y, which are treated as column vectors, and stores the 439 // result in the receiver 440 // 441 // m = a + alpha * (x * yᵀ + y * xᵀ) 442 func (s *SymDense) RankTwo(a Symmetric, alpha float64, x, y Vector) { 443 n := s.mat.N 444 if x.Len() != n { 445 panic(ErrShape) 446 } 447 if y.Len() != n { 448 panic(ErrShape) 449 } 450 451 if s != a { 452 if rs, ok := a.(RawSymmetricer); ok { 453 s.checkOverlap(generalFromSymmetric(rs.RawSymmetric())) 454 } 455 } 456 457 var xmat, ymat blas64.Vector 458 fast := true 459 xU, _ := untransposeExtract(x) 460 if rv, ok := xU.(*VecDense); ok { 461 r, c := xU.Dims() 462 xmat = rv.mat 463 s.checkOverlap(generalFromVector(xmat, r, c)) 464 } else { 465 fast = false 466 } 467 yU, _ := untransposeExtract(y) 468 if rv, ok := yU.(*VecDense); ok { 469 r, c := yU.Dims() 470 ymat = rv.mat 471 s.checkOverlap(generalFromVector(ymat, r, c)) 472 } else { 473 fast = false 474 } 475 476 if s != a { 477 if rs, ok := a.(RawSymmetricer); ok { 478 s.checkOverlap(generalFromSymmetric(rs.RawSymmetric())) 479 } 480 s.reuseAsNonZeroed(n) 481 s.CopySym(a) 482 } 483 484 if fast { 485 if s != a { 486 s.reuseAsNonZeroed(n) 487 s.CopySym(a) 488 } 489 blas64.Syr2(alpha, xmat, ymat, s.mat) 490 return 491 } 492 493 for i := 0; i < n; i++ { 494 s.reuseAsNonZeroed(n) 495 for j := i; j < n; j++ { 496 s.set(i, j, a.At(i, j)+alpha*(x.AtVec(i)*y.AtVec(j)+y.AtVec(i)*x.AtVec(j))) 497 } 498 } 499 } 500 501 // ScaleSym multiplies the elements of a by f, placing the result in the receiver. 502 func (s *SymDense) ScaleSym(f float64, a Symmetric) { 503 n := a.SymmetricDim() 504 s.reuseAsNonZeroed(n) 505 if a, ok := a.(RawSymmetricer); ok { 506 amat := a.RawSymmetric() 507 if s != a { 508 s.checkOverlap(generalFromSymmetric(amat)) 509 } 510 for i := 0; i < n; i++ { 511 for j := i; j < n; j++ { 512 s.mat.Data[i*s.mat.Stride+j] = f * amat.Data[i*amat.Stride+j] 513 } 514 } 515 return 516 } 517 for i := 0; i < n; i++ { 518 for j := i; j < n; j++ { 519 s.mat.Data[i*s.mat.Stride+j] = f * a.At(i, j) 520 } 521 } 522 } 523 524 // SubsetSym extracts a subset of the rows and columns of the matrix a and stores 525 // the result in-place into the receiver. The resulting matrix size is 526 // len(set)×len(set). Specifically, at the conclusion of SubsetSym, 527 // s.At(i, j) equals a.At(set[i], set[j]). Note that the supplied set does not 528 // have to be a strict subset, dimension repeats are allowed. 529 func (s *SymDense) SubsetSym(a Symmetric, set []int) { 530 n := len(set) 531 na := a.SymmetricDim() 532 s.reuseAsNonZeroed(n) 533 var restore func() 534 if a == s { 535 s, restore = s.isolatedWorkspace(a) 536 defer restore() 537 } 538 539 if a, ok := a.(RawSymmetricer); ok { 540 raw := a.RawSymmetric() 541 if s != a { 542 s.checkOverlap(generalFromSymmetric(raw)) 543 } 544 for i := 0; i < n; i++ { 545 ssub := s.mat.Data[i*s.mat.Stride : i*s.mat.Stride+n] 546 r := set[i] 547 rsub := raw.Data[r*raw.Stride : r*raw.Stride+na] 548 for j := i; j < n; j++ { 549 c := set[j] 550 if r <= c { 551 ssub[j] = rsub[c] 552 } else { 553 ssub[j] = raw.Data[c*raw.Stride+r] 554 } 555 } 556 } 557 return 558 } 559 for i := 0; i < n; i++ { 560 for j := i; j < n; j++ { 561 s.mat.Data[i*s.mat.Stride+j] = a.At(set[i], set[j]) 562 } 563 } 564 } 565 566 // SliceSym returns a new Matrix that shares backing data with the receiver. 567 // The returned matrix starts at {i,i} of the receiver and extends k-i rows 568 // and columns. The final row and column in the resulting matrix is k-1. 569 // SliceSym panics with ErrIndexOutOfRange if the slice is outside the 570 // capacity of the receiver. 571 func (s *SymDense) SliceSym(i, k int) Symmetric { 572 return s.sliceSym(i, k) 573 } 574 575 func (s *SymDense) sliceSym(i, k int) *SymDense { 576 sz := s.cap 577 if i < 0 || sz < i || k < i || sz < k { 578 panic(ErrIndexOutOfRange) 579 } 580 v := *s 581 v.mat.Data = s.mat.Data[i*s.mat.Stride+i : (k-1)*s.mat.Stride+k] 582 v.mat.N = k - i 583 v.cap = s.cap - i 584 return &v 585 } 586 587 // Norm returns the specified norm of the receiver. Valid norms are: 588 // 589 // 1 - The maximum absolute column sum 590 // 2 - The Frobenius norm, the square root of the sum of the squares of the elements 591 // Inf - The maximum absolute row sum 592 // 593 // Norm will panic with ErrNormOrder if an illegal norm is specified and with 594 // ErrZeroLength if the matrix has zero size. 595 func (s *SymDense) Norm(norm float64) float64 { 596 if s.IsEmpty() { 597 panic(ErrZeroLength) 598 } 599 lnorm := normLapack(norm, false) 600 if lnorm == lapack.MaxColumnSum || lnorm == lapack.MaxRowSum { 601 work := getFloat64s(s.mat.N, false) 602 defer putFloat64s(work) 603 return lapack64.Lansy(lnorm, s.mat, work) 604 } 605 return lapack64.Lansy(lnorm, s.mat, nil) 606 } 607 608 // Trace returns the trace of the matrix. 609 // 610 // Trace will panic with ErrZeroLength if the matrix has zero size. 611 func (s *SymDense) Trace() float64 { 612 if s.IsEmpty() { 613 panic(ErrZeroLength) 614 } 615 // TODO(btracey): could use internal asm sum routine. 616 var v float64 617 for i := 0; i < s.mat.N; i++ { 618 v += s.mat.Data[i*s.mat.Stride+i] 619 } 620 return v 621 } 622 623 // GrowSym returns the receiver expanded by n rows and n columns. If the 624 // dimensions of the expanded matrix are outside the capacity of the receiver 625 // a new allocation is made, otherwise not. Note that the receiver itself is 626 // not modified during the call to GrowSquare. 627 func (s *SymDense) GrowSym(n int) Symmetric { 628 if n < 0 { 629 panic(ErrIndexOutOfRange) 630 } 631 if n == 0 { 632 return s 633 } 634 var v SymDense 635 n += s.mat.N 636 if s.IsEmpty() || n > s.cap { 637 v.mat = blas64.Symmetric{ 638 N: n, 639 Stride: n, 640 Uplo: blas.Upper, 641 Data: make([]float64, n*n), 642 } 643 v.cap = n 644 // Copy elements, including those not currently visible. Use a temporary 645 // structure to avoid modifying the receiver. 646 var tmp SymDense 647 tmp.mat = blas64.Symmetric{ 648 N: s.cap, 649 Stride: s.mat.Stride, 650 Data: s.mat.Data, 651 Uplo: s.mat.Uplo, 652 } 653 tmp.cap = s.cap 654 v.CopySym(&tmp) 655 return &v 656 } 657 v.mat = blas64.Symmetric{ 658 N: n, 659 Stride: s.mat.Stride, 660 Uplo: blas.Upper, 661 Data: s.mat.Data[:(n-1)*s.mat.Stride+n], 662 } 663 v.cap = s.cap 664 return &v 665 } 666 667 // PowPSD computes a^pow where a is a positive symmetric definite matrix. 668 // 669 // PowPSD returns an error if the matrix is not positive symmetric definite 670 // or the Eigen decomposition is not successful. 671 func (s *SymDense) PowPSD(a Symmetric, pow float64) error { 672 dim := a.SymmetricDim() 673 s.reuseAsNonZeroed(dim) 674 675 var eigen EigenSym 676 ok := eigen.Factorize(a, true) 677 if !ok { 678 return ErrFailedEigen 679 } 680 values := eigen.Values(nil) 681 for i, v := range values { 682 if v <= 0 { 683 return ErrNotPSD 684 } 685 values[i] = math.Pow(v, pow) 686 } 687 var u Dense 688 eigen.VectorsTo(&u) 689 690 s.SymOuterK(values[0], u.ColView(0)) 691 692 var v VecDense 693 for i := 1; i < dim; i++ { 694 v.ColViewOf(&u, i) 695 s.SymRankOne(s, values[i], &v) 696 } 697 return nil 698 }