github.com/gonum/matrix@v0.0.0-20181209220409-c518dec07be9/mat64/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 mat64 6 7 import ( 8 "math" 9 10 "github.com/gonum/blas" 11 "github.com/gonum/blas/blas64" 12 "github.com/gonum/matrix" 13 ) 14 15 var ( 16 symDense *SymDense 17 18 _ Matrix = symDense 19 _ Symmetric = symDense 20 _ RawSymmetricer = symDense 21 _ MutableSymmetric = symDense 22 ) 23 24 const ( 25 badSymTriangle = "mat64: blas64.Symmetric not upper" 26 badSymCap = "mat64: bad capacity for SymDense" 27 ) 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 // Symmetric returns the number of rows/columns in the matrix. 41 Symmetric() 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 type MutableSymmetric interface { 50 Symmetric 51 SetSym(i, j int, v float64) 52 } 53 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. 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 panic("mat64: negative dimension") 65 } 66 if data != nil && n*n != len(data) { 67 panic(matrix.ErrShape) 68 } 69 if data == nil { 70 data = make([]float64, n*n) 71 } 72 return &SymDense{ 73 mat: blas64.Symmetric{ 74 N: n, 75 Stride: n, 76 Data: data, 77 Uplo: blas.Upper, 78 }, 79 cap: n, 80 } 81 } 82 83 func (s *SymDense) Dims() (r, c int) { 84 return s.mat.N, s.mat.N 85 } 86 87 // T implements the Matrix interface. Symmetric matrices, by definition, are 88 // equal to their transpose, and this is a no-op. 89 func (s *SymDense) T() Matrix { 90 return s 91 } 92 93 func (s *SymDense) Symmetric() int { 94 return s.mat.N 95 } 96 97 // RawSymmetric returns the matrix as a blas64.Symmetric. The returned 98 // value must be stored in upper triangular format. 99 func (s *SymDense) RawSymmetric() blas64.Symmetric { 100 return s.mat 101 } 102 103 // SetRawSymmetric sets the underlying blas64.Symmetric used by the receiver. 104 // Changes to elements in the receiver following the call will be reflected 105 // in b. SetRawSymmetric will panic if b is not an upper-encoded symmetric 106 // matrix. 107 func (s *SymDense) SetRawSymmetric(b blas64.Symmetric) { 108 if b.Uplo != blas.Upper { 109 panic(badSymTriangle) 110 } 111 s.mat = b 112 } 113 114 // Reset zeros the dimensions of the matrix so that it can be reused as the 115 // receiver of a dimensionally restricted operation. 116 // 117 // See the Reseter interface for more information. 118 func (s *SymDense) Reset() { 119 // N and Stride must be zeroed in unison. 120 s.mat.N, s.mat.Stride = 0, 0 121 s.mat.Data = s.mat.Data[:0] 122 } 123 124 func (s *SymDense) isZero() bool { 125 // It must be the case that m.Dims() returns 126 // zeros in this case. See comment in Reset(). 127 return s.mat.N == 0 128 } 129 130 // reuseAs resizes an empty matrix to a n×n matrix, 131 // or checks that a non-empty matrix is n×n. 132 func (s *SymDense) reuseAs(n int) { 133 if s.mat.N > s.cap { 134 panic(badSymCap) 135 } 136 if s.isZero() { 137 s.mat = blas64.Symmetric{ 138 N: n, 139 Stride: n, 140 Data: use(s.mat.Data, n*n), 141 Uplo: blas.Upper, 142 } 143 s.cap = n 144 return 145 } 146 if s.mat.Uplo != blas.Upper { 147 panic(badSymTriangle) 148 } 149 if s.mat.N != n { 150 panic(matrix.ErrShape) 151 } 152 } 153 154 func (s *SymDense) isolatedWorkspace(a Symmetric) (w *SymDense, restore func()) { 155 n := a.Symmetric() 156 w = getWorkspaceSym(n, false) 157 return w, func() { 158 s.CopySym(w) 159 putWorkspaceSym(w) 160 } 161 } 162 163 func (s *SymDense) AddSym(a, b Symmetric) { 164 n := a.Symmetric() 165 if n != b.Symmetric() { 166 panic(matrix.ErrShape) 167 } 168 s.reuseAs(n) 169 170 if a, ok := a.(RawSymmetricer); ok { 171 if b, ok := b.(RawSymmetricer); ok { 172 amat, bmat := a.RawSymmetric(), b.RawSymmetric() 173 if s != a { 174 s.checkOverlap(amat) 175 } 176 if s != b { 177 s.checkOverlap(bmat) 178 } 179 for i := 0; i < n; i++ { 180 btmp := bmat.Data[i*bmat.Stride+i : i*bmat.Stride+n] 181 stmp := s.mat.Data[i*s.mat.Stride+i : i*s.mat.Stride+n] 182 for j, v := range amat.Data[i*amat.Stride+i : i*amat.Stride+n] { 183 stmp[j] = v + btmp[j] 184 } 185 } 186 return 187 } 188 } 189 190 for i := 0; i < n; i++ { 191 stmp := s.mat.Data[i*s.mat.Stride : i*s.mat.Stride+n] 192 for j := i; j < n; j++ { 193 stmp[j] = a.At(i, j) + b.At(i, j) 194 } 195 } 196 } 197 198 func (s *SymDense) CopySym(a Symmetric) int { 199 n := a.Symmetric() 200 n = min(n, s.mat.N) 201 if n == 0 { 202 return 0 203 } 204 switch a := a.(type) { 205 case RawSymmetricer: 206 amat := a.RawSymmetric() 207 if amat.Uplo != blas.Upper { 208 panic(badSymTriangle) 209 } 210 for i := 0; i < n; i++ { 211 copy(s.mat.Data[i*s.mat.Stride+i:i*s.mat.Stride+n], amat.Data[i*amat.Stride+i:i*amat.Stride+n]) 212 } 213 default: 214 for i := 0; i < n; i++ { 215 stmp := s.mat.Data[i*s.mat.Stride : i*s.mat.Stride+n] 216 for j := i; j < n; j++ { 217 stmp[j] = a.At(i, j) 218 } 219 } 220 } 221 return n 222 } 223 224 // SymRankOne performs a symetric rank-one update to the matrix a and stores 225 // the result in the receiver 226 // s = a + alpha * x * x' 227 func (s *SymDense) SymRankOne(a Symmetric, alpha float64, x *Vector) { 228 n := x.Len() 229 if a.Symmetric() != n { 230 panic(matrix.ErrShape) 231 } 232 s.reuseAs(n) 233 if s != a { 234 if rs, ok := a.(RawSymmetricer); ok { 235 s.checkOverlap(rs.RawSymmetric()) 236 } 237 s.CopySym(a) 238 } 239 blas64.Syr(alpha, x.mat, s.mat) 240 } 241 242 // SymRankK performs a symmetric rank-k update to the matrix a and stores the 243 // result into the receiver. If a is zero, see SymOuterK. 244 // s = a + alpha * x * x' 245 func (s *SymDense) SymRankK(a Symmetric, alpha float64, x Matrix) { 246 n := a.Symmetric() 247 r, _ := x.Dims() 248 if r != n { 249 panic(matrix.ErrShape) 250 } 251 xMat, aTrans := untranspose(x) 252 var g blas64.General 253 if rm, ok := xMat.(RawMatrixer); ok { 254 g = rm.RawMatrix() 255 } else { 256 g = DenseCopyOf(x).mat 257 aTrans = false 258 } 259 if a != s { 260 if rs, ok := a.(RawSymmetricer); ok { 261 s.checkOverlap(rs.RawSymmetric()) 262 } 263 s.reuseAs(n) 264 s.CopySym(a) 265 } 266 t := blas.NoTrans 267 if aTrans { 268 t = blas.Trans 269 } 270 blas64.Syrk(t, alpha, g, 1, s.mat) 271 } 272 273 // SymOuterK calculates the outer product of x with itself and stores 274 // the result into the receiver. It is equivalent to the matrix 275 // multiplication 276 // s = alpha * x * x'. 277 // In order to update an existing matrix, see SymRankOne. 278 func (s *SymDense) SymOuterK(alpha float64, x Matrix) { 279 n, _ := x.Dims() 280 switch { 281 case s.isZero(): 282 s.mat = blas64.Symmetric{ 283 N: n, 284 Stride: n, 285 Data: useZeroed(s.mat.Data, n*n), 286 Uplo: blas.Upper, 287 } 288 s.cap = n 289 s.SymRankK(s, alpha, x) 290 case s.mat.Uplo != blas.Upper: 291 panic(badSymTriangle) 292 case s.mat.N == n: 293 if s == x { 294 w := getWorkspaceSym(n, true) 295 w.SymRankK(w, alpha, x) 296 s.CopySym(w) 297 putWorkspaceSym(w) 298 } else { 299 if rs, ok := x.(RawSymmetricer); ok { 300 s.checkOverlap(rs.RawSymmetric()) 301 } 302 // Only zero the upper triangle. 303 for i := 0; i < n; i++ { 304 ri := i * s.mat.Stride 305 zero(s.mat.Data[ri+i : ri+n]) 306 } 307 s.SymRankK(s, alpha, x) 308 } 309 default: 310 panic(matrix.ErrShape) 311 } 312 } 313 314 // RankTwo performs a symmmetric rank-two update to the matrix a and stores 315 // the result in the receiver 316 // m = a + alpha * (x * y' + y * x') 317 func (s *SymDense) RankTwo(a Symmetric, alpha float64, x, y *Vector) { 318 n := s.mat.N 319 if x.Len() != n { 320 panic(matrix.ErrShape) 321 } 322 if y.Len() != n { 323 panic(matrix.ErrShape) 324 } 325 var w SymDense 326 if s == a { 327 w = *s 328 } 329 w.reuseAs(n) 330 if s != a { 331 if rs, ok := a.(RawSymmetricer); ok { 332 s.checkOverlap(rs.RawSymmetric()) 333 } 334 w.CopySym(a) 335 } 336 blas64.Syr2(alpha, x.mat, y.mat, w.mat) 337 *s = w 338 return 339 } 340 341 // ScaleSym multiplies the elements of a by f, placing the result in the receiver. 342 func (s *SymDense) ScaleSym(f float64, a Symmetric) { 343 n := a.Symmetric() 344 s.reuseAs(n) 345 if a, ok := a.(RawSymmetricer); ok { 346 amat := a.RawSymmetric() 347 if s != a { 348 s.checkOverlap(amat) 349 } 350 for i := 0; i < n; i++ { 351 for j := i; j < n; j++ { 352 s.mat.Data[i*s.mat.Stride+j] = f * amat.Data[i*amat.Stride+j] 353 } 354 } 355 return 356 } 357 for i := 0; i < n; i++ { 358 for j := i; j < n; j++ { 359 s.mat.Data[i*s.mat.Stride+j] = f * a.At(i, j) 360 } 361 } 362 } 363 364 // SubsetSym extracts a subset of the rows and columns of the matrix a and stores 365 // the result in-place into the receiver. The resulting matrix size is 366 // len(set)×len(set). Specifically, at the conclusion of SubsetSym, 367 // s.At(i, j) equals a.At(set[i], set[j]). Note that the supplied set does not 368 // have to be a strict subset, dimension repeats are allowed. 369 func (s *SymDense) SubsetSym(a Symmetric, set []int) { 370 n := len(set) 371 na := a.Symmetric() 372 s.reuseAs(n) 373 var restore func() 374 if a == s { 375 s, restore = s.isolatedWorkspace(a) 376 defer restore() 377 } 378 379 if a, ok := a.(RawSymmetricer); ok { 380 raw := a.RawSymmetric() 381 if s != a { 382 s.checkOverlap(raw) 383 } 384 for i := 0; i < n; i++ { 385 ssub := s.mat.Data[i*s.mat.Stride : i*s.mat.Stride+n] 386 r := set[i] 387 rsub := raw.Data[r*raw.Stride : r*raw.Stride+na] 388 for j := i; j < n; j++ { 389 c := set[j] 390 if r <= c { 391 ssub[j] = rsub[c] 392 } else { 393 ssub[j] = raw.Data[c*raw.Stride+r] 394 } 395 } 396 } 397 return 398 } 399 for i := 0; i < n; i++ { 400 for j := i; j < n; j++ { 401 s.mat.Data[i*s.mat.Stride+j] = a.At(set[i], set[j]) 402 } 403 } 404 } 405 406 // ViewSquare returns a view of the submatrix starting at {i, i} and extending 407 // for n rows and columns. ViewSquare panics if the view is outside the bounds 408 // of the receiver. 409 // 410 // ViewSquare is deprecated and should not be used. It will be removed at a later date. 411 func (s *SymDense) ViewSquare(i, n int) Matrix { 412 return s.SliceSquare(i, i+n) 413 } 414 415 // SliceSquare returns a new Matrix that shares backing data with the receiver. 416 // The returned matrix starts at {i,i} of the recevier and extends k-i rows 417 // and columns. The final row and column in the resulting matrix is k-1. 418 // SliceSquare panics with ErrIndexOutOfRange if the slice is outside the bounds 419 // of the receiver. 420 func (s *SymDense) SliceSquare(i, k int) Matrix { 421 sz := s.Symmetric() 422 if i < 0 || sz < i || k < i || sz < k { 423 panic(matrix.ErrIndexOutOfRange) 424 } 425 v := *s 426 v.mat.Data = s.mat.Data[i*s.mat.Stride+i : (k-1)*s.mat.Stride+k] 427 v.mat.N = k - i 428 v.cap = s.cap - i 429 return &v 430 } 431 432 // GrowSquare returns the receiver expanded by n rows and n columns. If the 433 // dimensions of the expanded matrix are outside the capacity of the receiver 434 // a new allocation is made, otherwise not. Note that the receiver itself is 435 // not modified during the call to GrowSquare. 436 func (s *SymDense) GrowSquare(n int) Matrix { 437 if n < 0 { 438 panic(matrix.ErrIndexOutOfRange) 439 } 440 if n == 0 { 441 return s 442 } 443 var v SymDense 444 n += s.mat.N 445 if n > s.cap { 446 v.mat = blas64.Symmetric{ 447 N: n, 448 Stride: n, 449 Uplo: blas.Upper, 450 Data: make([]float64, n*n), 451 } 452 v.cap = n 453 // Copy elements, including those not currently visible. Use a temporary 454 // structure to avoid modifying the receiver. 455 var tmp SymDense 456 tmp.mat = blas64.Symmetric{ 457 N: s.cap, 458 Stride: s.mat.Stride, 459 Data: s.mat.Data, 460 Uplo: s.mat.Uplo, 461 } 462 tmp.cap = s.cap 463 v.CopySym(&tmp) 464 return &v 465 } 466 v.mat = blas64.Symmetric{ 467 N: n, 468 Stride: s.mat.Stride, 469 Uplo: blas.Upper, 470 Data: s.mat.Data[:(n-1)*s.mat.Stride+n], 471 } 472 v.cap = s.cap 473 return &v 474 } 475 476 // PowPSD computes a^pow where a is a positive symmetric definite matrix. 477 // 478 // PowPSD returns an error if the matrix is not not positive symmetric definite 479 // or the Eigendecomposition is not successful. 480 func (s *SymDense) PowPSD(a Symmetric, pow float64) error { 481 dim := a.Symmetric() 482 s.reuseAs(dim) 483 484 var eigen EigenSym 485 ok := eigen.Factorize(a, true) 486 if !ok { 487 return matrix.ErrFailedEigen 488 } 489 values := eigen.Values(nil) 490 for i, v := range values { 491 if v <= 0 { 492 return matrix.ErrNotPSD 493 } 494 values[i] = math.Pow(v, pow) 495 } 496 var u Dense 497 u.EigenvectorsSym(&eigen) 498 499 s.SymOuterK(values[0], u.ColView(0)) 500 for i := 1; i < dim; i++ { 501 s.SymRankOne(s, values[i], u.ColView(i)) 502 } 503 return nil 504 }