github.com/gonum/matrix@v0.0.0-20181209220409-c518dec07be9/mat64/symmetric_test.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 "fmt" 9 "math/rand" 10 "os" 11 "reflect" 12 "testing" 13 14 "github.com/gonum/blas" 15 "github.com/gonum/blas/blas64" 16 "github.com/gonum/floats" 17 "github.com/gonum/matrix" 18 ) 19 20 func TestNewSymmetric(t *testing.T) { 21 for i, test := range []struct { 22 data []float64 23 n int 24 mat *SymDense 25 }{ 26 { 27 data: []float64{ 28 1, 2, 3, 29 4, 5, 6, 30 7, 8, 9, 31 }, 32 n: 3, 33 mat: &SymDense{ 34 mat: blas64.Symmetric{ 35 N: 3, 36 Stride: 3, 37 Uplo: blas.Upper, 38 Data: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9}, 39 }, 40 cap: 3, 41 }, 42 }, 43 } { 44 sym := NewSymDense(test.n, test.data) 45 rows, cols := sym.Dims() 46 47 if rows != test.n { 48 t.Errorf("unexpected number of rows for test %d: got: %d want: %d", i, rows, test.n) 49 } 50 if cols != test.n { 51 t.Errorf("unexpected number of cols for test %d: got: %d want: %d", i, cols, test.n) 52 } 53 if !reflect.DeepEqual(sym, test.mat) { 54 t.Errorf("unexpected data slice for test %d: got: %v want: %v", i, sym, test.mat) 55 } 56 57 m := NewDense(test.n, test.n, test.data) 58 if !reflect.DeepEqual(sym.mat.Data, m.mat.Data) { 59 t.Errorf("unexpected data slice mismatch for test %d: got: %v want: %v", i, sym.mat.Data, m.mat.Data) 60 } 61 } 62 63 panicked, message := panics(func() { NewSymDense(3, []float64{1, 2}) }) 64 if !panicked || message != matrix.ErrShape.Error() { 65 t.Error("expected panic for invalid data slice length") 66 } 67 } 68 69 func TestSymAtSet(t *testing.T) { 70 sym := &SymDense{ 71 mat: blas64.Symmetric{ 72 N: 3, 73 Stride: 3, 74 Uplo: blas.Upper, 75 Data: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9}, 76 }, 77 cap: 3, 78 } 79 rows, cols := sym.Dims() 80 81 // Check At out of bounds 82 for _, row := range []int{-1, rows, rows + 1} { 83 panicked, message := panics(func() { sym.At(row, 0) }) 84 if !panicked || message != matrix.ErrRowAccess.Error() { 85 t.Errorf("expected panic for invalid row access N=%d r=%d", rows, row) 86 } 87 } 88 for _, col := range []int{-1, cols, cols + 1} { 89 panicked, message := panics(func() { sym.At(0, col) }) 90 if !panicked || message != matrix.ErrColAccess.Error() { 91 t.Errorf("expected panic for invalid column access N=%d c=%d", cols, col) 92 } 93 } 94 95 // Check Set out of bounds 96 for _, row := range []int{-1, rows, rows + 1} { 97 panicked, message := panics(func() { sym.SetSym(row, 0, 1.2) }) 98 if !panicked || message != matrix.ErrRowAccess.Error() { 99 t.Errorf("expected panic for invalid row access N=%d r=%d", rows, row) 100 } 101 } 102 for _, col := range []int{-1, cols, cols + 1} { 103 panicked, message := panics(func() { sym.SetSym(0, col, 1.2) }) 104 if !panicked || message != matrix.ErrColAccess.Error() { 105 t.Errorf("expected panic for invalid column access N=%d c=%d", cols, col) 106 } 107 } 108 109 for _, st := range []struct { 110 row, col int 111 orig, new float64 112 }{ 113 {row: 1, col: 2, orig: 6, new: 15}, 114 {row: 2, col: 1, orig: 15, new: 12}, 115 } { 116 if e := sym.At(st.row, st.col); e != st.orig { 117 t.Errorf("unexpected value for At(%d, %d): got: %v want: %v", st.row, st.col, e, st.orig) 118 } 119 if e := sym.At(st.col, st.row); e != st.orig { 120 t.Errorf("unexpected value for At(%d, %d): got: %v want: %v", st.col, st.row, e, st.orig) 121 } 122 sym.SetSym(st.row, st.col, st.new) 123 if e := sym.At(st.row, st.col); e != st.new { 124 t.Errorf("unexpected value for At(%d, %d) after SetSym(%[1]d, %[2]d, %[4]v): got: %[3]v want: %v", st.row, st.col, e, st.new) 125 } 126 if e := sym.At(st.col, st.row); e != st.new { 127 t.Errorf("unexpected value for At(%d, %d) after SetSym(%[2]d, %[1]d, %[4]v): got: %[3]v want: %v", st.col, st.row, e, st.new) 128 } 129 } 130 } 131 132 func TestSymAdd(t *testing.T) { 133 for _, test := range []struct { 134 n int 135 }{ 136 {n: 1}, 137 {n: 2}, 138 {n: 3}, 139 {n: 4}, 140 {n: 5}, 141 {n: 10}, 142 } { 143 n := test.n 144 a := NewSymDense(n, nil) 145 for i := range a.mat.Data { 146 a.mat.Data[i] = rand.Float64() 147 } 148 b := NewSymDense(n, nil) 149 for i := range a.mat.Data { 150 b.mat.Data[i] = rand.Float64() 151 } 152 var m Dense 153 m.Add(a, b) 154 155 // Check with new receiver 156 var s SymDense 157 s.AddSym(a, b) 158 for i := 0; i < n; i++ { 159 for j := i; j < n; j++ { 160 want := m.At(i, j) 161 if got := s.At(i, j); got != want { 162 t.Errorf("unexpected value for At(%d, %d): got: %v want: %v", i, j, got, want) 163 } 164 } 165 } 166 167 // Check with equal receiver 168 s.CopySym(a) 169 s.AddSym(&s, b) 170 for i := 0; i < n; i++ { 171 for j := i; j < n; j++ { 172 want := m.At(i, j) 173 if got := s.At(i, j); got != want { 174 t.Errorf("unexpected value for At(%d, %d): got: %v want: %v", i, j, got, want) 175 } 176 } 177 } 178 } 179 180 method := func(receiver, a, b Matrix) { 181 type addSymer interface { 182 AddSym(a, b Symmetric) 183 } 184 rd := receiver.(addSymer) 185 rd.AddSym(a.(Symmetric), b.(Symmetric)) 186 } 187 denseComparison := func(receiver, a, b *Dense) { 188 receiver.Add(a, b) 189 } 190 testTwoInput(t, "AddSym", &SymDense{}, method, denseComparison, legalTypesSym, legalSizeSameSquare, 1e-14) 191 } 192 193 func TestCopy(t *testing.T) { 194 for _, test := range []struct { 195 n int 196 }{ 197 {n: 1}, 198 {n: 2}, 199 {n: 3}, 200 {n: 4}, 201 {n: 5}, 202 {n: 10}, 203 } { 204 n := test.n 205 a := NewSymDense(n, nil) 206 for i := range a.mat.Data { 207 a.mat.Data[i] = rand.Float64() 208 } 209 s := NewSymDense(n, nil) 210 s.CopySym(a) 211 for i := 0; i < n; i++ { 212 for j := i; j < n; j++ { 213 want := a.At(i, j) 214 if got := s.At(i, j); got != want { 215 t.Errorf("unexpected value for At(%d, %d): got: %v want: %v", i, j, got, want) 216 } 217 } 218 } 219 } 220 } 221 222 // TODO(kortschak) Roll this into testOneInput when it exists. 223 // https://github.com/gonum/matrix/issues/171 224 func TestSymCopyPanic(t *testing.T) { 225 var ( 226 a SymDense 227 n int 228 ) 229 m := NewSymDense(1, nil) 230 panicked, message := panics(func() { n = m.CopySym(&a) }) 231 if panicked { 232 t.Errorf("unexpected panic: %v", message) 233 } 234 if n != 0 { 235 t.Errorf("unexpected n: got: %d want: 0", n) 236 } 237 } 238 239 func TestSymRankOne(t *testing.T) { 240 for _, test := range []struct { 241 n int 242 }{ 243 {n: 1}, 244 {n: 2}, 245 {n: 3}, 246 {n: 4}, 247 {n: 5}, 248 {n: 10}, 249 } { 250 n := test.n 251 alpha := 2.0 252 a := NewSymDense(n, nil) 253 for i := range a.mat.Data { 254 a.mat.Data[i] = rand.Float64() 255 } 256 x := make([]float64, n) 257 for i := range x { 258 x[i] = rand.Float64() 259 } 260 261 xMat := NewDense(n, 1, x) 262 var m Dense 263 m.Mul(xMat, xMat.T()) 264 m.Scale(alpha, &m) 265 m.Add(&m, a) 266 267 // Check with new receiver 268 s := NewSymDense(n, nil) 269 s.SymRankOne(a, alpha, NewVector(len(x), x)) 270 for i := 0; i < n; i++ { 271 for j := i; j < n; j++ { 272 want := m.At(i, j) 273 if got := s.At(i, j); got != want { 274 t.Errorf("unexpected value for At(%d, %d): got: %v want: %v", i, j, got, want) 275 } 276 } 277 } 278 279 // Check with reused receiver 280 copy(s.mat.Data, a.mat.Data) 281 s.SymRankOne(s, alpha, NewVector(len(x), x)) 282 for i := 0; i < n; i++ { 283 for j := i; j < n; j++ { 284 want := m.At(i, j) 285 if got := s.At(i, j); got != want { 286 t.Errorf("unexpected value for At(%d, %d): got: %v want: %v", i, j, got, want) 287 } 288 } 289 } 290 } 291 292 alpha := 3.0 293 method := func(receiver, a, b Matrix) { 294 type SymRankOner interface { 295 SymRankOne(a Symmetric, alpha float64, x *Vector) 296 } 297 rd := receiver.(SymRankOner) 298 rd.SymRankOne(a.(Symmetric), alpha, b.(*Vector)) 299 } 300 denseComparison := func(receiver, a, b *Dense) { 301 var tmp Dense 302 tmp.Mul(b, b.T()) 303 tmp.Scale(alpha, &tmp) 304 receiver.Add(a, &tmp) 305 } 306 legalTypes := func(a, b Matrix) bool { 307 _, ok := a.(Symmetric) 308 if !ok { 309 return false 310 } 311 _, ok = b.(*Vector) 312 return ok 313 } 314 legalSize := func(ar, ac, br, bc int) bool { 315 if ar != ac { 316 return false 317 } 318 return br == ar 319 } 320 testTwoInput(t, "SymRankOne", &SymDense{}, method, denseComparison, legalTypes, legalSize, 1e-14) 321 } 322 323 func TestIssue250SymRankOne(t *testing.T) { 324 x := NewVector(5, []float64{1, 2, 3, 4, 5}) 325 var s1, s2 SymDense 326 s1.SymRankOne(NewSymDense(5, nil), 1, x) 327 s2.SymRankOne(NewSymDense(5, nil), 1, x) 328 s2.SymRankOne(NewSymDense(5, nil), 1, x) 329 if !Equal(&s1, &s2) { 330 t.Error("unexpected result from repeat") 331 } 332 } 333 334 func TestRankTwo(t *testing.T) { 335 for _, test := range []struct { 336 n int 337 }{ 338 {n: 1}, 339 {n: 2}, 340 {n: 3}, 341 {n: 4}, 342 {n: 5}, 343 {n: 10}, 344 } { 345 n := test.n 346 alpha := 2.0 347 a := NewSymDense(n, nil) 348 for i := range a.mat.Data { 349 a.mat.Data[i] = rand.Float64() 350 } 351 x := make([]float64, n) 352 y := make([]float64, n) 353 for i := range x { 354 x[i] = rand.Float64() 355 y[i] = rand.Float64() 356 } 357 358 xMat := NewDense(n, 1, x) 359 yMat := NewDense(n, 1, y) 360 var m Dense 361 m.Mul(xMat, yMat.T()) 362 var tmp Dense 363 tmp.Mul(yMat, xMat.T()) 364 m.Add(&m, &tmp) 365 m.Scale(alpha, &m) 366 m.Add(&m, a) 367 368 // Check with new receiver 369 s := NewSymDense(n, nil) 370 s.RankTwo(a, alpha, NewVector(len(x), x), NewVector(len(y), y)) 371 for i := 0; i < n; i++ { 372 for j := i; j < n; j++ { 373 if !floats.EqualWithinAbsOrRel(s.At(i, j), m.At(i, j), 1e-14, 1e-14) { 374 t.Errorf("unexpected element value at (%d,%d): got: %f want: %f", i, j, m.At(i, j), s.At(i, j)) 375 } 376 } 377 } 378 379 // Check with reused receiver 380 copy(s.mat.Data, a.mat.Data) 381 s.RankTwo(s, alpha, NewVector(len(x), x), NewVector(len(y), y)) 382 for i := 0; i < n; i++ { 383 for j := i; j < n; j++ { 384 if !floats.EqualWithinAbsOrRel(s.At(i, j), m.At(i, j), 1e-14, 1e-14) { 385 t.Errorf("unexpected element value at (%d,%d): got: %f want: %f", i, j, m.At(i, j), s.At(i, j)) 386 } 387 } 388 } 389 } 390 } 391 392 func TestSymRankK(t *testing.T) { 393 alpha := 3.0 394 method := func(receiver, a, b Matrix) { 395 type SymRankKer interface { 396 SymRankK(a Symmetric, alpha float64, x Matrix) 397 } 398 rd := receiver.(SymRankKer) 399 rd.SymRankK(a.(Symmetric), alpha, b) 400 } 401 denseComparison := func(receiver, a, b *Dense) { 402 var tmp Dense 403 tmp.Mul(b, b.T()) 404 tmp.Scale(alpha, &tmp) 405 receiver.Add(a, &tmp) 406 } 407 legalTypes := func(a, b Matrix) bool { 408 _, ok := a.(Symmetric) 409 return ok 410 } 411 legalSize := func(ar, ac, br, bc int) bool { 412 if ar != ac { 413 return false 414 } 415 return br == ar 416 } 417 testTwoInput(t, "SymRankK", &SymDense{}, method, denseComparison, legalTypes, legalSize, 1e-14) 418 } 419 420 func TestSymOuterK(t *testing.T) { 421 for _, f := range []float64{0.5, 1, 3} { 422 method := func(receiver, x Matrix) { 423 type SymOuterKer interface { 424 SymOuterK(alpha float64, x Matrix) 425 } 426 rd := receiver.(SymOuterKer) 427 rd.SymOuterK(f, x) 428 } 429 denseComparison := func(receiver, x *Dense) { 430 receiver.Mul(x, x.T()) 431 receiver.Scale(f, receiver) 432 } 433 testOneInput(t, "SymOuterK", &SymDense{}, method, denseComparison, isAnyType, isAnySize, 1e-14) 434 } 435 } 436 437 func TestIssue250SymOuterK(t *testing.T) { 438 x := NewVector(5, []float64{1, 2, 3, 4, 5}) 439 var s1, s2 SymDense 440 s1.SymOuterK(1, x) 441 s2.SymOuterK(1, x) 442 s2.SymOuterK(1, x) 443 if !Equal(&s1, &s2) { 444 t.Error("unexpected result from repeat") 445 } 446 } 447 448 func TestScaleSym(t *testing.T) { 449 for _, f := range []float64{0.5, 1, 3} { 450 method := func(receiver, a Matrix) { 451 type ScaleSymer interface { 452 ScaleSym(f float64, a Symmetric) 453 } 454 rd := receiver.(ScaleSymer) 455 rd.ScaleSym(f, a.(Symmetric)) 456 } 457 denseComparison := func(receiver, a *Dense) { 458 receiver.Scale(f, a) 459 } 460 testOneInput(t, "ScaleSym", &SymDense{}, method, denseComparison, legalTypeSym, isSquare, 1e-14) 461 } 462 } 463 464 func TestSubsetSym(t *testing.T) { 465 for _, test := range []struct { 466 a *SymDense 467 dims []int 468 ans *SymDense 469 }{ 470 { 471 a: NewSymDense(3, []float64{ 472 1, 2, 3, 473 0, 4, 5, 474 0, 0, 6, 475 }), 476 dims: []int{0, 2}, 477 ans: NewSymDense(2, []float64{ 478 1, 3, 479 0, 6, 480 }), 481 }, 482 { 483 a: NewSymDense(3, []float64{ 484 1, 2, 3, 485 0, 4, 5, 486 0, 0, 6, 487 }), 488 dims: []int{2, 0}, 489 ans: NewSymDense(2, []float64{ 490 6, 3, 491 0, 1, 492 }), 493 }, 494 { 495 a: NewSymDense(3, []float64{ 496 1, 2, 3, 497 0, 4, 5, 498 0, 0, 6, 499 }), 500 dims: []int{1, 1, 1}, 501 ans: NewSymDense(3, []float64{ 502 4, 4, 4, 503 0, 4, 4, 504 0, 0, 4, 505 }), 506 }, 507 } { 508 var s SymDense 509 s.SubsetSym(test.a, test.dims) 510 if !Equal(&s, test.ans) { 511 t.Errorf("SubsetSym mismatch dims %v\nGot:\n% v\nWant:\n% v\n", test.dims, s, test.ans) 512 } 513 } 514 515 dims := []int{0, 2} 516 maxDim := dims[0] 517 for _, v := range dims { 518 if maxDim < v { 519 maxDim = v 520 } 521 } 522 method := func(receiver, a Matrix) { 523 type SubsetSymer interface { 524 SubsetSym(a Symmetric, set []int) 525 } 526 rd := receiver.(SubsetSymer) 527 rd.SubsetSym(a.(Symmetric), dims) 528 } 529 denseComparison := func(receiver, a *Dense) { 530 *receiver = *NewDense(len(dims), len(dims), nil) 531 sz := len(dims) 532 for i := 0; i < sz; i++ { 533 for j := 0; j < sz; j++ { 534 receiver.Set(i, j, a.At(dims[i], dims[j])) 535 } 536 } 537 } 538 legalSize := func(ar, ac int) bool { 539 return ar == ac && ar > maxDim 540 } 541 542 testOneInput(t, "SubsetSym", &SymDense{}, method, denseComparison, legalTypeSym, legalSize, 0) 543 } 544 545 func TestViewGrowSquare(t *testing.T) { 546 // n is the size of the original SymDense. 547 // The first view uses start1, span1. The second view uses start2, span2 on 548 // the first view. 549 for _, test := range []struct { 550 n, start1, span1, start2, span2 int 551 }{ 552 {10, 0, 10, 0, 10}, 553 {10, 0, 8, 0, 8}, 554 {10, 2, 8, 0, 6}, 555 {10, 2, 7, 4, 2}, 556 {10, 2, 6, 0, 5}, 557 } { 558 n := test.n 559 s := NewSymDense(n, nil) 560 for i := 0; i < n; i++ { 561 for j := i; j < n; j++ { 562 s.SetSym(i, j, float64((i+1)*n+j+1)) 563 } 564 } 565 566 // Take a subset and check the view matches. 567 start1 := test.start1 568 span1 := test.span1 569 v := s.SliceSquare(start1, start1+span1).(*SymDense) 570 for i := 0; i < span1; i++ { 571 for j := i; j < span1; j++ { 572 if v.At(i, j) != s.At(start1+i, start1+j) { 573 t.Errorf("View mismatch") 574 } 575 } 576 } 577 578 start2 := test.start2 579 span2 := test.span2 580 v2 := v.SliceSquare(start2, start2+span2).(*SymDense) 581 582 for i := 0; i < span2; i++ { 583 for j := i; j < span2; j++ { 584 if v2.At(i, j) != s.At(start1+start2+i, start1+start2+j) { 585 t.Errorf("Second view mismatch") 586 } 587 } 588 } 589 590 // Check that a write to the view is reflected in the original. 591 v2.SetSym(0, 0, 1.2) 592 if s.At(start1+start2, start1+start2) != 1.2 { 593 t.Errorf("Write to view not reflected in original") 594 } 595 596 // Grow the matrix back to the original view 597 gn := n - start1 - start2 598 g := v2.GrowSquare(gn - v2.Symmetric()).(*SymDense) 599 g.SetSym(1, 1, 2.2) 600 601 for i := 0; i < gn; i++ { 602 for j := 0; j < gn; j++ { 603 if g.At(i, j) != s.At(start1+start2+i, start1+start2+j) { 604 t.Errorf("Grow mismatch") 605 606 fmt.Printf("g=\n% v\n", Formatted(g)) 607 fmt.Printf("s=\n% v\n", Formatted(s)) 608 os.Exit(1) 609 } 610 } 611 } 612 613 // View g, then grow it and make sure all the elements were copied. 614 gv := g.SliceSquare(0, gn-1).(*SymDense) 615 616 gg := gv.GrowSquare(2) 617 for i := 0; i < gn; i++ { 618 for j := 0; j < gn; j++ { 619 if g.At(i, j) != gg.At(i, j) { 620 t.Errorf("Expand mismatch") 621 } 622 } 623 } 624 } 625 } 626 627 func TestPowPSD(t *testing.T) { 628 for cas, test := range []struct { 629 a *SymDense 630 pow float64 631 ans *SymDense 632 }{ 633 // Comparison with Matlab. 634 { 635 a: NewSymDense(2, []float64{10, 5, 5, 12}), 636 pow: 0.5, 637 ans: NewSymDense(2, []float64{3.065533767740645, 0.776210486171016, 0.776210486171016, 3.376017962209052}), 638 }, 639 { 640 a: NewSymDense(2, []float64{11, -1, -1, 8}), 641 pow: 0.5, 642 ans: NewSymDense(2, []float64{3.312618742210524, -0.162963396980939, -0.162963396980939, 2.823728551267709}), 643 }, 644 { 645 a: NewSymDense(2, []float64{10, 5, 5, 12}), 646 pow: -0.5, 647 ans: NewSymDense(2, []float64{0.346372134547712, -0.079637515547296, -0.079637515547296, 0.314517128328794}), 648 }, 649 { 650 a: NewSymDense(3, []float64{15, -1, -3, -1, 8, 6, -3, 6, 14}), 651 pow: 0.6, 652 ans: NewSymDense(3, []float64{ 653 5.051214323034288, -0.163162161893975, -0.612153996497505, 654 -0.163162161893976, 3.283474884617009, 1.432842761381493, 655 -0.612153996497505, 1.432842761381494, 4.695873060862573, 656 }), 657 }, 658 } { 659 var s SymDense 660 err := s.PowPSD(test.a, test.pow) 661 if err != nil { 662 panic("bad test") 663 } 664 if !EqualApprox(&s, test.ans, 1e-10) { 665 t.Errorf("Case %d, pow mismatch", cas) 666 fmt.Println(Formatted(&s)) 667 fmt.Println(Formatted(test.ans)) 668 } 669 } 670 671 // Compare with Dense.Pow 672 rnd := rand.New(rand.NewSource(1)) 673 for dim := 2; dim < 10; dim++ { 674 for pow := 2; pow < 6; pow++ { 675 a := NewDense(dim, dim, nil) 676 for i := 0; i < dim; i++ { 677 for j := 0; j < dim; j++ { 678 a.Set(i, j, rnd.Float64()) 679 } 680 } 681 var mat SymDense 682 mat.SymOuterK(1, a) 683 684 var sym SymDense 685 sym.PowPSD(&mat, float64(pow)) 686 687 var dense Dense 688 dense.Pow(&mat, pow) 689 690 if !EqualApprox(&sym, &dense, 1e-10) { 691 t.Errorf("Dim %d: pow mismatch") 692 } 693 } 694 } 695 }