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