gonum.org/v1/gonum@v0.14.0/mat/triangular_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 mat 6 7 import ( 8 "fmt" 9 "math" 10 "reflect" 11 "testing" 12 13 "golang.org/x/exp/rand" 14 15 "gonum.org/v1/gonum/blas" 16 "gonum.org/v1/gonum/blas/blas64" 17 ) 18 19 func TestNewTriangular(t *testing.T) { 20 t.Parallel() 21 for i, test := range []struct { 22 data []float64 23 n int 24 kind TriKind 25 mat *TriDense 26 }{ 27 { 28 data: []float64{ 29 1, 2, 3, 30 4, 5, 6, 31 7, 8, 9, 32 }, 33 n: 3, 34 kind: Upper, 35 mat: &TriDense{ 36 mat: blas64.Triangular{ 37 N: 3, 38 Stride: 3, 39 Uplo: blas.Upper, 40 Data: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9}, 41 Diag: blas.NonUnit, 42 }, 43 cap: 3, 44 }, 45 }, 46 } { 47 tri := NewTriDense(test.n, test.kind, test.data) 48 rows, cols := tri.Dims() 49 50 if rows != test.n { 51 t.Errorf("unexpected number of rows for test %d: got: %d want: %d", i, rows, test.n) 52 } 53 if cols != test.n { 54 t.Errorf("unexpected number of cols for test %d: got: %d want: %d", i, cols, test.n) 55 } 56 if !reflect.DeepEqual(tri, test.mat) { 57 t.Errorf("unexpected data slice for test %d: got: %v want: %v", i, tri, test.mat) 58 } 59 } 60 61 for _, kind := range []TriKind{Lower, Upper} { 62 panicked, message := panics(func() { NewTriDense(3, kind, []float64{1, 2}) }) 63 if !panicked || message != ErrShape.Error() { 64 t.Errorf("expected panic for invalid data slice length for upper=%t", kind) 65 } 66 } 67 } 68 69 func TestTriAtSet(t *testing.T) { 70 t.Parallel() 71 tri := &TriDense{ 72 mat: blas64.Triangular{ 73 N: 3, 74 Stride: 3, 75 Uplo: blas.Upper, 76 Data: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9}, 77 Diag: blas.NonUnit, 78 }, 79 cap: 3, 80 } 81 82 rows, cols := tri.Dims() 83 84 // Check At out of bounds 85 for _, row := range []int{-1, rows, rows + 1} { 86 panicked, message := panics(func() { tri.At(row, 0) }) 87 if !panicked || message != ErrRowAccess.Error() { 88 t.Errorf("expected panic for invalid row access N=%d r=%d", rows, row) 89 } 90 } 91 for _, col := range []int{-1, cols, cols + 1} { 92 panicked, message := panics(func() { tri.At(0, col) }) 93 if !panicked || message != ErrColAccess.Error() { 94 t.Errorf("expected panic for invalid column access N=%d c=%d", cols, col) 95 } 96 } 97 98 // Check Set out of bounds 99 for _, row := range []int{-1, rows, rows + 1} { 100 panicked, message := panics(func() { tri.SetTri(row, 0, 1.2) }) 101 if !panicked || message != ErrRowAccess.Error() { 102 t.Errorf("expected panic for invalid row access N=%d r=%d", rows, row) 103 } 104 } 105 for _, col := range []int{-1, cols, cols + 1} { 106 panicked, message := panics(func() { tri.SetTri(0, col, 1.2) }) 107 if !panicked || message != ErrColAccess.Error() { 108 t.Errorf("expected panic for invalid column access N=%d c=%d", cols, col) 109 } 110 } 111 112 for _, st := range []struct { 113 row, col int 114 uplo blas.Uplo 115 }{ 116 {row: 2, col: 1, uplo: blas.Upper}, 117 {row: 1, col: 2, uplo: blas.Lower}, 118 } { 119 tri.mat.Uplo = st.uplo 120 panicked, message := panics(func() { tri.SetTri(st.row, st.col, 1.2) }) 121 if !panicked || message != ErrTriangleSet.Error() { 122 t.Errorf("expected panic for %+v", st) 123 } 124 } 125 126 for _, st := range []struct { 127 row, col int 128 uplo blas.Uplo 129 orig, new float64 130 }{ 131 {row: 2, col: 1, uplo: blas.Lower, orig: 8, new: 15}, 132 {row: 1, col: 2, uplo: blas.Upper, orig: 6, new: 15}, 133 } { 134 tri.mat.Uplo = st.uplo 135 if e := tri.At(st.row, st.col); e != st.orig { 136 t.Errorf("unexpected value for At(%d, %d): got: %v want: %v", st.row, st.col, e, st.orig) 137 } 138 tri.SetTri(st.row, st.col, st.new) 139 if e := tri.At(st.row, st.col); e != st.new { 140 t.Errorf("unexpected value for At(%d, %d) after SetTri(%[1]d, %d, %v): got: %v want: %[3]v", st.row, st.col, st.new, e) 141 } 142 } 143 } 144 145 func TestTriDenseZero(t *testing.T) { 146 t.Parallel() 147 // Elements that equal 1 should be set to zero, elements that equal -1 148 // should remain unchanged. 149 for _, test := range []*TriDense{ 150 { 151 mat: blas64.Triangular{ 152 Uplo: blas.Upper, 153 N: 4, 154 Stride: 5, 155 Data: []float64{ 156 1, 1, 1, 1, -1, 157 -1, 1, 1, 1, -1, 158 -1, -1, 1, 1, -1, 159 -1, -1, -1, 1, -1, 160 }, 161 }, 162 }, 163 { 164 mat: blas64.Triangular{ 165 Uplo: blas.Lower, 166 N: 4, 167 Stride: 5, 168 Data: []float64{ 169 1, -1, -1, -1, -1, 170 1, 1, -1, -1, -1, 171 1, 1, 1, -1, -1, 172 1, 1, 1, 1, -1, 173 }, 174 }, 175 }, 176 } { 177 dataCopy := make([]float64, len(test.mat.Data)) 178 copy(dataCopy, test.mat.Data) 179 test.Zero() 180 for i, v := range test.mat.Data { 181 if dataCopy[i] != -1 && v != 0 { 182 t.Errorf("Matrix not zeroed in bounds") 183 } 184 if dataCopy[i] == -1 && v != -1 { 185 t.Errorf("Matrix zeroed out of bounds") 186 } 187 } 188 } 189 } 190 191 func TestTriDiagView(t *testing.T) { 192 t.Parallel() 193 for cas, test := range []*TriDense{ 194 NewTriDense(1, Upper, []float64{1}), 195 NewTriDense(2, Upper, []float64{1, 2, 0, 3}), 196 NewTriDense(3, Upper, []float64{1, 2, 3, 0, 4, 5, 0, 0, 6}), 197 NewTriDense(1, Lower, []float64{1}), 198 NewTriDense(2, Lower, []float64{1, 2, 2, 3}), 199 NewTriDense(3, Lower, []float64{1, 0, 0, 2, 3, 0, 4, 5, 6}), 200 } { 201 testDiagView(t, cas, test) 202 } 203 } 204 205 func TestTriDenseCopy(t *testing.T) { 206 t.Parallel() 207 src := rand.NewSource(1) 208 rnd := rand.New(src) 209 for i := 0; i < 100; i++ { 210 size := rnd.Intn(100) 211 r, err := randDense(size, 0.9, src) 212 if size == 0 { 213 if err != ErrZeroLength { 214 t.Fatalf("expected error %v: got: %v", ErrZeroLength, err) 215 } 216 continue 217 } 218 if err != nil { 219 t.Fatalf("unexpected error: %v", err) 220 } 221 222 u := NewTriDense(size, true, nil) 223 l := NewTriDense(size, false, nil) 224 225 for _, typ := range []Matrix{r, (*basicMatrix)(r)} { 226 for j := range u.mat.Data { 227 u.mat.Data[j] = math.NaN() 228 l.mat.Data[j] = math.NaN() 229 } 230 u.Copy(typ) 231 l.Copy(typ) 232 for m := 0; m < size; m++ { 233 for n := 0; n < size; n++ { 234 want := typ.At(m, n) 235 switch { 236 case m < n: // Upper triangular matrix. 237 if got := u.At(m, n); got != want { 238 t.Errorf("unexpected upper value for At(%d, %d) for test %d: got: %v want: %v", m, n, i, got, want) 239 } 240 case m == n: // Diagonal matrix. 241 if got := u.At(m, n); got != want { 242 t.Errorf("unexpected upper value for At(%d, %d) for test %d: got: %v want: %v", m, n, i, got, want) 243 } 244 if got := l.At(m, n); got != want { 245 t.Errorf("unexpected diagonal value for At(%d, %d) for test %d: got: %v want: %v", m, n, i, got, want) 246 } 247 case m > n: // Lower triangular matrix. 248 if got := l.At(m, n); got != want { 249 t.Errorf("unexpected lower value for At(%d, %d) for test %d: got: %v want: %v", m, n, i, got, want) 250 } 251 } 252 } 253 } 254 } 255 } 256 } 257 258 func TestTriTriDenseCopy(t *testing.T) { 259 t.Parallel() 260 src := rand.NewSource(1) 261 rnd := rand.New(src) 262 for i := 0; i < 100; i++ { 263 size := rnd.Intn(100) 264 r, err := randDense(size, 1, src) 265 if size == 0 { 266 if err != ErrZeroLength { 267 t.Fatalf("expected error %v: got: %v", ErrZeroLength, err) 268 } 269 continue 270 } 271 if err != nil { 272 t.Fatalf("unexpected error: %v", err) 273 } 274 275 ur := NewTriDense(size, true, nil) 276 lr := NewTriDense(size, false, nil) 277 278 ur.Copy(r) 279 lr.Copy(r) 280 281 u := NewTriDense(size, true, nil) 282 u.Copy(ur) 283 if !equal(u, ur) { 284 t.Fatal("unexpected result for U triangle copy of U triangle: not equal") 285 } 286 287 l := NewTriDense(size, false, nil) 288 l.Copy(lr) 289 if !equal(l, lr) { 290 t.Fatal("unexpected result for L triangle copy of L triangle: not equal") 291 } 292 293 zero(u.mat.Data) 294 u.Copy(lr) 295 if !isDiagonal(u) { 296 t.Fatal("unexpected result for U triangle copy of L triangle: off diagonal non-zero element") 297 } 298 if !equalDiagonal(u, lr) { 299 t.Fatal("unexpected result for U triangle copy of L triangle: diagonal not equal") 300 } 301 302 zero(l.mat.Data) 303 l.Copy(ur) 304 if !isDiagonal(l) { 305 t.Fatal("unexpected result for L triangle copy of U triangle: off diagonal non-zero element") 306 } 307 if !equalDiagonal(l, ur) { 308 t.Fatal("unexpected result for L triangle copy of U triangle: diagonal not equal") 309 } 310 } 311 } 312 313 func TestTriInverse(t *testing.T) { 314 t.Parallel() 315 rnd := rand.New(rand.NewSource(1)) 316 for _, kind := range []TriKind{Upper, Lower} { 317 for _, n := range []int{1, 3, 5, 9} { 318 data := make([]float64, n*n) 319 for i := range data { 320 data[i] = rnd.NormFloat64() 321 } 322 a := NewTriDense(n, kind, data) 323 var tr TriDense 324 err := tr.InverseTri(a) 325 if err != nil { 326 t.Errorf("Bad test: %s", err) 327 } 328 var d Dense 329 d.Mul(a, &tr) 330 if !equalApprox(eye(n), &d, 1e-8, false) { 331 var diff Dense 332 diff.Sub(eye(n), &d) 333 t.Errorf("Tri times inverse is not identity. Norm of difference: %v", Norm(&diff, 2)) 334 } 335 } 336 } 337 } 338 339 func TestTriMul(t *testing.T) { 340 t.Parallel() 341 method := func(receiver, a, b Matrix) { 342 type MulTrier interface { 343 MulTri(a, b Triangular) 344 } 345 receiver.(MulTrier).MulTri(a.(Triangular), b.(Triangular)) 346 } 347 denseComparison := func(receiver, a, b *Dense) { 348 receiver.Mul(a, b) 349 } 350 legalSizeTriMul := func(ar, ac, br, bc int) bool { 351 // Need both to be square and the sizes to be the same 352 return ar == ac && br == bc && ar == br 353 } 354 355 // The legal types are triangles with the same TriKind. 356 // legalTypesTri returns whether both input arguments are Triangular. 357 legalTypes := func(a, b Matrix) bool { 358 at, ok := a.(Triangular) 359 if !ok { 360 return false 361 } 362 bt, ok := b.(Triangular) 363 if !ok { 364 return false 365 } 366 _, ak := at.Triangle() 367 _, bk := bt.Triangle() 368 return ak == bk 369 } 370 legalTypesLower := func(a, b Matrix) bool { 371 legal := legalTypes(a, b) 372 if !legal { 373 return false 374 } 375 _, kind := a.(Triangular).Triangle() 376 r := kind == Lower 377 return r 378 } 379 receiver := NewTriDense(3, Lower, nil) 380 testTwoInput(t, "TriMul", receiver, method, denseComparison, legalTypesLower, legalSizeTriMul, 1e-14) 381 382 legalTypesUpper := func(a, b Matrix) bool { 383 legal := legalTypes(a, b) 384 if !legal { 385 return false 386 } 387 _, kind := a.(Triangular).Triangle() 388 r := kind == Upper 389 return r 390 } 391 receiver = NewTriDense(3, Upper, nil) 392 testTwoInput(t, "TriMul", receiver, method, denseComparison, legalTypesUpper, legalSizeTriMul, 1e-14) 393 } 394 395 func TestScaleTri(t *testing.T) { 396 t.Parallel() 397 for _, f := range []float64{0.5, 1, 3} { 398 method := func(receiver, a Matrix) { 399 type ScaleTrier interface { 400 ScaleTri(f float64, a Triangular) 401 } 402 rd := receiver.(ScaleTrier) 403 rd.ScaleTri(f, a.(Triangular)) 404 } 405 denseComparison := func(receiver, a *Dense) { 406 receiver.Scale(f, a) 407 } 408 testOneInput(t, "ScaleTriUpper", NewTriDense(3, Upper, nil), method, denseComparison, legalTypeTriUpper, isSquare, 1e-14) 409 testOneInput(t, "ScaleTriLower", NewTriDense(3, Lower, nil), method, denseComparison, legalTypeTriLower, isSquare, 1e-14) 410 } 411 } 412 413 func TestCopySymIntoTriangle(t *testing.T) { 414 t.Parallel() 415 nan := math.NaN() 416 for tc, test := range []struct { 417 n int 418 sUplo blas.Uplo 419 s []float64 420 421 tUplo TriKind 422 want []float64 423 }{ 424 { 425 n: 3, 426 sUplo: blas.Upper, 427 s: []float64{ 428 1, 2, 3, 429 nan, 4, 5, 430 nan, nan, 6, 431 }, 432 tUplo: Upper, 433 want: []float64{ 434 1, 2, 3, 435 0, 4, 5, 436 0, 0, 6, 437 }, 438 }, 439 { 440 n: 3, 441 sUplo: blas.Lower, 442 s: []float64{ 443 1, nan, nan, 444 2, 3, nan, 445 4, 5, 6, 446 }, 447 tUplo: Upper, 448 want: []float64{ 449 1, 2, 4, 450 0, 3, 5, 451 0, 0, 6, 452 }, 453 }, 454 { 455 n: 3, 456 sUplo: blas.Upper, 457 s: []float64{ 458 1, 2, 3, 459 nan, 4, 5, 460 nan, nan, 6, 461 }, 462 tUplo: Lower, 463 want: []float64{ 464 1, 0, 0, 465 2, 4, 0, 466 3, 5, 6, 467 }, 468 }, 469 { 470 n: 3, 471 sUplo: blas.Lower, 472 s: []float64{ 473 1, nan, nan, 474 2, 3, nan, 475 4, 5, 6, 476 }, 477 tUplo: Lower, 478 want: []float64{ 479 1, 0, 0, 480 2, 3, 0, 481 4, 5, 6, 482 }, 483 }, 484 } { 485 n := test.n 486 s := NewSymDense(n, test.s) 487 // For the purpose of the test, break the assumption that 488 // symmetric is stored in the upper triangle (only when S is 489 // RawSymmetricer). 490 s.mat.Uplo = test.sUplo 491 492 t1 := NewTriDense(n, test.tUplo, nil) 493 copySymIntoTriangle(t1, s) 494 495 equal := true 496 loop1: 497 for i := 0; i < n; i++ { 498 for j := 0; j < n; j++ { 499 if t1.At(i, j) != test.want[i*n+j] { 500 equal = false 501 break loop1 502 } 503 } 504 } 505 if !equal { 506 t.Errorf("Case %v: unexpected T when S is RawSymmetricer", tc) 507 } 508 509 if test.sUplo == blas.Lower { 510 continue 511 } 512 513 sb := (basicSymmetric)(*s) 514 t2 := NewTriDense(n, test.tUplo, nil) 515 copySymIntoTriangle(t2, &sb) 516 equal = true 517 loop2: 518 for i := 0; i < n; i++ { 519 for j := 0; j < n; j++ { 520 if t1.At(i, j) != test.want[i*n+j] { 521 equal = false 522 break loop2 523 } 524 } 525 } 526 if !equal { 527 t.Errorf("Case %v: unexpected T when S is not RawSymmetricer", tc) 528 } 529 } 530 } 531 532 func TestTriSliceTri(t *testing.T) { 533 t.Parallel() 534 rnd := rand.New(rand.NewSource(1)) 535 for cas, test := range []struct { 536 n, start1, span1, start2, span2 int 537 }{ 538 {10, 0, 10, 0, 10}, 539 {10, 0, 8, 0, 8}, 540 {10, 2, 8, 0, 6}, 541 {10, 2, 7, 4, 2}, 542 {10, 2, 6, 0, 5}, 543 {10, 2, 3, 1, 7}, 544 } { 545 n := test.n 546 for _, kind := range []TriKind{Upper, Lower} { 547 tri := NewTriDense(n, kind, nil) 548 if kind == Upper { 549 for i := 0; i < n; i++ { 550 for j := i; j < n; j++ { 551 tri.SetTri(i, j, rnd.Float64()) 552 } 553 } 554 } else { 555 for i := 0; i < n; i++ { 556 for j := 0; j <= i; j++ { 557 tri.SetTri(i, j, rnd.Float64()) 558 } 559 } 560 } 561 562 start1 := test.start1 563 span1 := test.span1 564 v1 := tri.SliceTri(start1, start1+span1).(*TriDense) 565 if kind == Upper { 566 for i := 0; i < span1; i++ { 567 for j := i; j < span1; j++ { 568 if v1.At(i, j) != tri.At(start1+i, start1+j) { 569 t.Errorf("Case %d,upper: view mismatch at %v,%v", cas, i, j) 570 } 571 } 572 } 573 } else { 574 for i := 0; i < span1; i++ { 575 for j := 0; j <= i; j++ { 576 if v1.At(i, j) != tri.At(start1+i, start1+j) { 577 t.Errorf("Case %d,lower: view mismatch at %v,%v", cas, i, j) 578 } 579 } 580 } 581 } 582 583 start2 := test.start2 584 span2 := test.span2 585 v2 := v1.SliceTri(start2, start2+span2).(*TriDense) 586 if kind == Upper { 587 for i := 0; i < span2; i++ { 588 for j := i; j < span2; j++ { 589 if v2.At(i, j) != tri.At(start1+start2+i, start1+start2+j) { 590 t.Errorf("Case %d,upper: second view mismatch at %v,%v", cas, i, j) 591 } 592 } 593 } 594 } else { 595 for i := 0; i < span1; i++ { 596 for j := 0; j <= i; j++ { 597 if v1.At(i, j) != tri.At(start1+i, start1+j) { 598 t.Errorf("Case %d,lower: second view mismatch at %v,%v", cas, i, j) 599 } 600 } 601 } 602 } 603 604 v2.SetTri(span2-1, span2-1, -123.45) 605 if tri.At(start1+start2+span2-1, start1+start2+span2-1) != -123.45 { 606 t.Errorf("Case %d: write to view not reflected in original", cas) 607 } 608 } 609 } 610 } 611 612 var triSumForBench float64 613 614 func BenchmarkTriSum(b *testing.B) { 615 rnd := rand.New(rand.NewSource(1)) 616 for n := 100; n <= 1600; n *= 2 { 617 a := randTriDense(n, rnd) 618 b.Run(fmt.Sprintf("BenchmarkTriSum%d", n), func(b *testing.B) { 619 for i := 0; i < b.N; i++ { 620 triSumForBench = Sum(a) 621 } 622 }) 623 } 624 } 625 626 var triProductForBench *TriDense 627 628 func BenchmarkTriMul(b *testing.B) { 629 rnd := rand.New(rand.NewSource(1)) 630 for n := 100; n <= 1600; n *= 2 { 631 triProductForBench = NewTriDense(n, Upper, nil) 632 a := randTriDense(n, rnd) 633 c := randTriDense(n, rnd) 634 b.Run(fmt.Sprintf("BenchmarkTriMul%d", n), func(b *testing.B) { 635 for i := 0; i < b.N; i++ { 636 triProductForBench.MulTri(a, c) 637 } 638 }) 639 } 640 } 641 642 func BenchmarkTriMulDiag(b *testing.B) { 643 rnd := rand.New(rand.NewSource(1)) 644 for n := 100; n <= 1600; n *= 2 { 645 triProductForBench = NewTriDense(n, Upper, nil) 646 a := randTriDense(n, rnd) 647 c := randDiagDense(n, rnd) 648 b.Run(fmt.Sprintf("BenchmarkTriMulDiag%d", n), func(b *testing.B) { 649 for i := 0; i < b.N; i++ { 650 triProductForBench.MulTri(a, c) 651 } 652 }) 653 } 654 } 655 656 func BenchmarkTriMul2Diag(b *testing.B) { 657 rnd := rand.New(rand.NewSource(1)) 658 for n := 100; n <= 1600; n *= 2 { 659 triProductForBench = NewTriDense(n, Upper, nil) 660 a := randDiagDense(n, rnd) 661 c := randDiagDense(n, rnd) 662 b.Run(fmt.Sprintf("BenchmarkTriMul2Diag%d", n), func(b *testing.B) { 663 for i := 0; i < b.N; i++ { 664 triProductForBench.MulTri(a, c) 665 } 666 }) 667 } 668 } 669 670 func randTriDense(size int, rnd *rand.Rand) *TriDense { 671 t := NewTriDense(size, Upper, nil) 672 for i := 0; i < size; i++ { 673 for j := i; j < size; j++ { 674 t.SetTri(i, j, rnd.Float64()) 675 } 676 } 677 return t 678 }