gonum.org/v1/gonum@v0.14.0/mat/cholesky_test.go (about) 1 // Copyright ©2013 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 "strconv" 11 "testing" 12 13 "golang.org/x/exp/rand" 14 15 "gonum.org/v1/gonum/floats/scalar" 16 ) 17 18 func TestCholesky(t *testing.T) { 19 t.Parallel() 20 for _, test := range []struct { 21 a *SymDense 22 23 cond float64 24 want *TriDense 25 posdef bool 26 }{ 27 { 28 a: NewSymDense(3, []float64{ 29 4, 1, 1, 30 0, 2, 3, 31 0, 0, 6, 32 }), 33 cond: 37, 34 want: NewTriDense(3, true, []float64{ 35 2, 0.5, 0.5, 36 0, 1.3228756555322954, 2.0788046015507495, 37 0, 0, 1.195228609334394, 38 }), 39 posdef: true, 40 }, 41 } { 42 _, n := test.a.Dims() 43 for _, chol := range []*Cholesky{ 44 {}, 45 {chol: NewTriDense(n-1, true, nil)}, 46 {chol: NewTriDense(n, true, nil)}, 47 {chol: NewTriDense(n+1, true, nil)}, 48 } { 49 ok := chol.Factorize(test.a) 50 if ok != test.posdef { 51 t.Errorf("unexpected return from Cholesky factorization: got: ok=%t want: ok=%t", ok, test.posdef) 52 } 53 fc := DenseCopyOf(chol.chol) 54 if !Equal(fc, test.want) { 55 t.Error("incorrect Cholesky factorization") 56 } 57 if math.Abs(test.cond-chol.cond) > 1e-13 { 58 t.Errorf("Condition number mismatch: Want %v, got %v", test.cond, chol.cond) 59 } 60 var U TriDense 61 chol.UTo(&U) 62 aCopy := DenseCopyOf(test.a) 63 var a Dense 64 a.Mul(U.TTri(), &U) 65 if !EqualApprox(&a, aCopy, 1e-14) { 66 t.Error("unexpected Cholesky factor product") 67 } 68 var L TriDense 69 chol.LTo(&L) 70 a.Mul(&L, L.TTri()) 71 if !EqualApprox(&a, aCopy, 1e-14) { 72 t.Error("unexpected Cholesky factor product") 73 } 74 } 75 } 76 } 77 78 func TestCholeskyAt(t *testing.T) { 79 t.Parallel() 80 for _, test := range []*SymDense{ 81 NewSymDense(3, []float64{ 82 53, 59, 37, 83 59, 83, 71, 84 37, 71, 101, 85 }), 86 } { 87 var chol Cholesky 88 ok := chol.Factorize(test) 89 if !ok { 90 t.Fatalf("Matrix not positive definite") 91 } 92 n := test.SymmetricDim() 93 cn := chol.SymmetricDim() 94 if cn != n { 95 t.Errorf("Cholesky size does not match. Got %d, want %d", cn, n) 96 } 97 for i := 0; i < n; i++ { 98 for j := 0; j < n; j++ { 99 got := chol.At(i, j) 100 want := test.At(i, j) 101 if math.Abs(got-want) > 1e-12 { 102 t.Errorf("Cholesky at does not match at %d, %d. Got %v, want %v", i, j, got, want) 103 } 104 } 105 } 106 } 107 } 108 109 func TestCholeskySolveTo(t *testing.T) { 110 t.Parallel() 111 for _, test := range []struct { 112 a *SymDense 113 b *Dense 114 ans *Dense 115 }{ 116 { 117 a: NewSymDense(2, []float64{ 118 1, 0, 119 0, 1, 120 }), 121 b: NewDense(2, 1, []float64{5, 6}), 122 ans: NewDense(2, 1, []float64{5, 6}), 123 }, 124 { 125 a: NewSymDense(3, []float64{ 126 53, 59, 37, 127 0, 83, 71, 128 37, 71, 101, 129 }), 130 b: NewDense(3, 1, []float64{5, 6, 7}), 131 ans: NewDense(3, 1, []float64{0.20745069393718094, -0.17421475529583694, 0.11577794010226464}), 132 }, 133 } { 134 var chol Cholesky 135 ok := chol.Factorize(test.a) 136 if !ok { 137 t.Fatal("unexpected Cholesky factorization failure: not positive definite") 138 } 139 140 var x Dense 141 err := chol.SolveTo(&x, test.b) 142 if err != nil { 143 t.Errorf("unexpected error from Cholesky solve: %v", err) 144 } 145 if !EqualApprox(&x, test.ans, 1e-12) { 146 t.Error("incorrect Cholesky solve solution") 147 } 148 149 var ans Dense 150 ans.Mul(test.a, &x) 151 if !EqualApprox(&ans, test.b, 1e-12) { 152 t.Error("incorrect Cholesky solve solution product") 153 } 154 } 155 } 156 157 func TestCholeskySolveCholTo(t *testing.T) { 158 t.Parallel() 159 for _, test := range []struct { 160 a, b *SymDense 161 }{ 162 { 163 a: NewSymDense(2, []float64{ 164 1, 0, 165 0, 1, 166 }), 167 b: NewSymDense(2, []float64{ 168 1, 0, 169 0, 1, 170 }), 171 }, 172 { 173 a: NewSymDense(2, []float64{ 174 1, 0, 175 0, 1, 176 }), 177 b: NewSymDense(2, []float64{ 178 2, 0, 179 0, 2, 180 }), 181 }, 182 { 183 a: NewSymDense(3, []float64{ 184 53, 59, 37, 185 59, 83, 71, 186 37, 71, 101, 187 }), 188 b: NewSymDense(3, []float64{ 189 2, -1, 0, 190 -1, 2, -1, 191 0, -1, 2, 192 }), 193 }, 194 } { 195 var chola, cholb Cholesky 196 ok := chola.Factorize(test.a) 197 if !ok { 198 t.Fatal("unexpected Cholesky factorization failure for a: not positive definite") 199 } 200 ok = cholb.Factorize(test.b) 201 if !ok { 202 t.Fatal("unexpected Cholesky factorization failure for b: not positive definite") 203 } 204 205 var x Dense 206 err := chola.SolveCholTo(&x, &cholb) 207 if err != nil { 208 t.Errorf("unexpected error from Cholesky solve: %v", err) 209 } 210 211 var ans Dense 212 ans.Mul(test.a, &x) 213 if !EqualApprox(&ans, test.b, 1e-12) { 214 var y Dense 215 err := y.Solve(test.a, test.b) 216 if err != nil { 217 t.Errorf("unexpected error from dense solve: %v", err) 218 } 219 t.Errorf("incorrect Cholesky solve solution product\ngot solution:\n%.4v\nwant solution\n%.4v", 220 Formatted(&x), Formatted(&y)) 221 } 222 } 223 } 224 225 func TestCholeskySolveVecTo(t *testing.T) { 226 t.Parallel() 227 for _, test := range []struct { 228 a *SymDense 229 b *VecDense 230 ans *VecDense 231 }{ 232 { 233 a: NewSymDense(2, []float64{ 234 1, 0, 235 0, 1, 236 }), 237 b: NewVecDense(2, []float64{5, 6}), 238 ans: NewVecDense(2, []float64{5, 6}), 239 }, 240 { 241 a: NewSymDense(3, []float64{ 242 53, 59, 37, 243 0, 83, 71, 244 0, 0, 101, 245 }), 246 b: NewVecDense(3, []float64{5, 6, 7}), 247 ans: NewVecDense(3, []float64{0.20745069393718094, -0.17421475529583694, 0.11577794010226464}), 248 }, 249 } { 250 var chol Cholesky 251 ok := chol.Factorize(test.a) 252 if !ok { 253 t.Fatal("unexpected Cholesky factorization failure: not positive definite") 254 } 255 256 var x VecDense 257 err := chol.SolveVecTo(&x, test.b) 258 if err != nil { 259 t.Errorf("unexpected error from Cholesky solve: %v", err) 260 } 261 if !EqualApprox(&x, test.ans, 1e-12) { 262 t.Error("incorrect Cholesky solve solution") 263 } 264 265 var ans VecDense 266 ans.MulVec(test.a, &x) 267 if !EqualApprox(&ans, test.b, 1e-12) { 268 t.Error("incorrect Cholesky solve solution product") 269 } 270 } 271 } 272 273 func TestCholeskyToSym(t *testing.T) { 274 t.Parallel() 275 for _, test := range []*SymDense{ 276 NewSymDense(3, []float64{ 277 53, 59, 37, 278 0, 83, 71, 279 0, 0, 101, 280 }), 281 } { 282 var chol Cholesky 283 ok := chol.Factorize(test) 284 if !ok { 285 t.Fatal("unexpected Cholesky factorization failure: not positive definite") 286 } 287 var s SymDense 288 chol.ToSym(&s) 289 290 if !EqualApprox(&s, test, 1e-12) { 291 t.Errorf("Cholesky reconstruction not equal to original matrix.\nWant:\n% v\nGot:\n% v\n", Formatted(test), Formatted(&s)) 292 } 293 } 294 } 295 296 func TestCloneCholesky(t *testing.T) { 297 t.Parallel() 298 for _, test := range []*SymDense{ 299 NewSymDense(3, []float64{ 300 53, 59, 37, 301 0, 83, 71, 302 0, 0, 101, 303 }), 304 } { 305 var chol Cholesky 306 ok := chol.Factorize(test) 307 if !ok { 308 panic("bad test") 309 } 310 var chol2 Cholesky 311 chol2.Clone(&chol) 312 313 if chol.cond != chol2.cond { 314 t.Errorf("condition number mismatch from empty") 315 } 316 if !Equal(chol.chol, chol2.chol) { 317 t.Errorf("chol mismatch from empty") 318 } 319 320 // Corrupt chol2 and try again 321 chol2.cond = math.NaN() 322 chol2.chol = NewTriDense(2, Upper, nil) 323 chol2.Clone(&chol) 324 if chol.cond != chol2.cond { 325 t.Errorf("condition number mismatch from non-empty") 326 } 327 if !Equal(chol.chol, chol2.chol) { 328 t.Errorf("chol mismatch from non-empty") 329 } 330 } 331 } 332 333 func TestCholeskyInverseTo(t *testing.T) { 334 t.Parallel() 335 rnd := rand.New(rand.NewSource(1)) 336 for _, n := range []int{1, 3, 5, 9} { 337 data := make([]float64, n*n) 338 for i := range data { 339 data[i] = rnd.NormFloat64() 340 } 341 var s SymDense 342 s.SymOuterK(1, NewDense(n, n, data)) 343 344 var chol Cholesky 345 ok := chol.Factorize(&s) 346 if !ok { 347 t.Errorf("Bad test, cholesky decomposition failed") 348 } 349 350 var sInv SymDense 351 err := chol.InverseTo(&sInv) 352 if err != nil { 353 t.Errorf("unexpected error from Cholesky inverse: %v", err) 354 } 355 356 var ans Dense 357 ans.Mul(&sInv, &s) 358 if !equalApprox(eye(n), &ans, 1e-8, false) { 359 var diff Dense 360 diff.Sub(eye(n), &ans) 361 t.Errorf("SymDense times Cholesky inverse not identity. Norm diff = %v", Norm(&diff, 2)) 362 } 363 } 364 } 365 366 func TestCholeskySymRankOne(t *testing.T) { 367 t.Parallel() 368 rnd := rand.New(rand.NewSource(1)) 369 for _, n := range []int{1, 2, 3, 4, 5, 7, 10, 20, 50, 100} { 370 for k := 0; k < 50; k++ { 371 // Construct a random positive definite matrix. 372 data := make([]float64, n*n) 373 for i := range data { 374 data[i] = rnd.NormFloat64() 375 } 376 var a SymDense 377 a.SymOuterK(1, NewDense(n, n, data)) 378 379 // Construct random data for updating. 380 xdata := make([]float64, n) 381 for i := range xdata { 382 xdata[i] = rnd.NormFloat64() 383 } 384 x := NewVecDense(n, xdata) 385 alpha := rnd.NormFloat64() 386 387 // Compute the updated matrix directly. If alpha > 0, there are no 388 // issues. If alpha < 0, it could be that the final matrix is not 389 // positive definite, so instead switch the two matrices. 390 aUpdate := NewSymDense(n, nil) 391 if alpha > 0 { 392 aUpdate.SymRankOne(&a, alpha, x) 393 } else { 394 aUpdate.CopySym(&a) 395 a.Reset() 396 a.SymRankOne(aUpdate, -alpha, x) 397 } 398 399 // Compare the Cholesky decomposition computed with Cholesky.SymRankOne 400 // with that computed from updating A directly. 401 var chol Cholesky 402 ok := chol.Factorize(&a) 403 if !ok { 404 t.Errorf("Bad random test, Cholesky factorization failed") 405 continue 406 } 407 408 var cholUpdate Cholesky 409 ok = cholUpdate.SymRankOne(&chol, alpha, x) 410 if !ok { 411 t.Errorf("n=%v, alpha=%v: unexpected failure", n, alpha) 412 continue 413 } 414 415 var aCompare SymDense 416 cholUpdate.ToSym(&aCompare) 417 if !EqualApprox(&aCompare, aUpdate, 1e-13) { 418 t.Errorf("n=%v, alpha=%v: mismatch between updated matrix and from Cholesky:\nupdated:\n%v\nfrom Cholesky:\n%v", 419 n, alpha, Formatted(aUpdate), Formatted(&aCompare)) 420 } 421 } 422 } 423 424 for i, test := range []struct { 425 a *SymDense 426 alpha float64 427 x []float64 428 429 wantOk bool 430 }{ 431 { 432 // Update (to positive definite matrix). 433 a: NewSymDense(4, []float64{ 434 1, 1, 1, 1, 435 0, 2, 3, 4, 436 0, 0, 6, 10, 437 0, 0, 0, 20, 438 }), 439 alpha: 1, 440 x: []float64{0, 0, 0, 1}, 441 wantOk: true, 442 }, 443 { 444 // Downdate to singular matrix. 445 a: NewSymDense(4, []float64{ 446 1, 1, 1, 1, 447 0, 2, 3, 4, 448 0, 0, 6, 10, 449 0, 0, 0, 20, 450 }), 451 alpha: -1, 452 x: []float64{0, 0, 0, 1}, 453 wantOk: false, 454 }, 455 { 456 // Downdate to positive definite matrix. 457 a: NewSymDense(4, []float64{ 458 1, 1, 1, 1, 459 0, 2, 3, 4, 460 0, 0, 6, 10, 461 0, 0, 0, 20, 462 }), 463 alpha: -0.5, 464 x: []float64{0, 0, 0, 1}, 465 wantOk: true, 466 }, 467 { 468 // Issue #453. 469 a: NewSymDense(1, []float64{1}), 470 alpha: -1, 471 x: []float64{0.25}, 472 wantOk: true, 473 }, 474 } { 475 var chol Cholesky 476 ok := chol.Factorize(test.a) 477 if !ok { 478 t.Errorf("Case %v: bad test, Cholesky factorization failed", i) 479 continue 480 } 481 482 x := NewVecDense(len(test.x), test.x) 483 ok = chol.SymRankOne(&chol, test.alpha, x) 484 if !ok { 485 if test.wantOk { 486 t.Errorf("Case %v: unexpected failure from SymRankOne", i) 487 } 488 continue 489 } 490 if ok && !test.wantOk { 491 t.Errorf("Case %v: expected a failure from SymRankOne", i) 492 } 493 494 a := test.a 495 a.SymRankOne(a, test.alpha, x) 496 497 var achol SymDense 498 chol.ToSym(&achol) 499 if !EqualApprox(&achol, a, 1e-13) { 500 t.Errorf("Case %v: mismatch between updated matrix and from Cholesky:\nupdated:\n%v\nfrom Cholesky:\n%v", 501 i, Formatted(a), Formatted(&achol)) 502 } 503 } 504 } 505 506 func TestCholeskyExtendVecSym(t *testing.T) { 507 t.Parallel() 508 for cas, test := range []struct { 509 a *SymDense 510 }{ 511 { 512 a: NewSymDense(3, []float64{ 513 4, 1, 1, 514 0, 2, 3, 515 0, 0, 6, 516 }), 517 }, 518 } { 519 n := test.a.SymmetricDim() 520 as := test.a.sliceSym(0, n-1) 521 522 // Compute the full factorization to use later (do the full factorization 523 // first to ensure the matrix is positive definite). 524 var cholFull Cholesky 525 ok := cholFull.Factorize(test.a) 526 if !ok { 527 panic("mat: bad test, matrix not positive definite") 528 } 529 530 var chol Cholesky 531 ok = chol.Factorize(as) 532 if !ok { 533 panic("mat: bad test, subset is not positive definite") 534 } 535 row := NewVecDense(n, nil) 536 for i := 0; i < n; i++ { 537 row.SetVec(i, test.a.At(n-1, i)) 538 } 539 540 var cholNew Cholesky 541 ok = cholNew.ExtendVecSym(&chol, row) 542 if !ok { 543 t.Errorf("cas %v: update not positive definite", cas) 544 } 545 var a SymDense 546 cholNew.ToSym(&a) 547 if !EqualApprox(&a, test.a, 1e-12) { 548 t.Errorf("cas %v: mismatch", cas) 549 } 550 551 // test in-place 552 ok = chol.ExtendVecSym(&chol, row) 553 if !ok { 554 t.Errorf("cas %v: in-place update not positive definite", cas) 555 } 556 if !equalChol(&chol, &cholNew) { 557 t.Errorf("cas %v: Cholesky different in-place vs. new", cas) 558 } 559 560 // Test that the factorization is about right compared with the direct 561 // full factorization. Use a high tolerance on the condition number 562 // since the condition number with the updated rule is approximate. 563 if !equalApproxChol(&chol, &cholFull, 1e-12, 0.3) { 564 t.Errorf("cas %v: updated Cholesky does not match full", cas) 565 } 566 } 567 } 568 569 func TestCholeskyScale(t *testing.T) { 570 t.Parallel() 571 for cas, test := range []struct { 572 a *SymDense 573 f float64 574 }{ 575 { 576 a: NewSymDense(3, []float64{ 577 4, 1, 1, 578 0, 2, 3, 579 0, 0, 6, 580 }), 581 f: 0.5, 582 }, 583 } { 584 var chol Cholesky 585 ok := chol.Factorize(test.a) 586 if !ok { 587 t.Errorf("Case %v: bad test, Cholesky factorization failed", cas) 588 continue 589 } 590 591 // Compare the update to a new Cholesky to an update in-place. 592 var cholUpdate Cholesky 593 cholUpdate.Scale(test.f, &chol) 594 chol.Scale(test.f, &chol) 595 if !equalChol(&chol, &cholUpdate) { 596 t.Errorf("Case %d: cholesky mismatch new receiver", cas) 597 } 598 var sym SymDense 599 chol.ToSym(&sym) 600 var comp SymDense 601 comp.ScaleSym(test.f, test.a) 602 if !EqualApprox(&comp, &sym, 1e-14) { 603 t.Errorf("Case %d: cholesky reconstruction doesn't match scaled matrix", cas) 604 } 605 606 var cholTest Cholesky 607 cholTest.Factorize(&comp) 608 if !equalApproxChol(&cholTest, &chol, 1e-12, 1e-12) { 609 t.Errorf("Case %d: cholesky mismatch with scaled matrix. %v, %v", cas, cholTest.cond, chol.cond) 610 } 611 } 612 } 613 614 // equalApproxChol checks that the two Cholesky decompositions are equal. 615 func equalChol(a, b *Cholesky) bool { 616 return Equal(a.chol, b.chol) && a.cond == b.cond 617 } 618 619 // equalApproxChol checks that the two Cholesky decompositions are approximately 620 // the same with the given tolerance on equality for the Triangular component and 621 // condition. 622 func equalApproxChol(a, b *Cholesky, matTol, condTol float64) bool { 623 if !EqualApprox(a.chol, b.chol, matTol) { 624 return false 625 } 626 return scalar.EqualWithinAbsOrRel(a.cond, b.cond, condTol, condTol) 627 } 628 629 func BenchmarkCholeskyFactorize(b *testing.B) { 630 for _, n := range []int{10, 100, 1000} { 631 b.Run("n="+strconv.Itoa(n), func(b *testing.B) { 632 rnd := rand.New(rand.NewSource(1)) 633 634 data := make([]float64, n*n) 635 for i := range data { 636 data[i] = rnd.NormFloat64() 637 } 638 var a SymDense 639 a.SymOuterK(1, NewDense(n, n, data)) 640 641 var chol Cholesky 642 b.ResetTimer() 643 for i := 0; i < b.N; i++ { 644 ok := chol.Factorize(&a) 645 if !ok { 646 panic("not positive definite") 647 } 648 } 649 }) 650 } 651 } 652 653 func BenchmarkCholeskyToSym(b *testing.B) { 654 for _, n := range []int{10, 100, 1000} { 655 b.Run("n="+strconv.Itoa(n), func(b *testing.B) { 656 rnd := rand.New(rand.NewSource(1)) 657 658 data := make([]float64, n*n) 659 for i := range data { 660 data[i] = rnd.NormFloat64() 661 } 662 var a SymDense 663 a.SymOuterK(1, NewDense(n, n, data)) 664 665 var chol Cholesky 666 ok := chol.Factorize(&a) 667 if !ok { 668 panic("not positive definite") 669 } 670 671 dst := NewSymDense(n, nil) 672 673 b.ResetTimer() 674 for i := 0; i < b.N; i++ { 675 chol.ToSym(dst) 676 } 677 }) 678 } 679 } 680 681 func BenchmarkCholeskyInverseTo(b *testing.B) { 682 for _, n := range []int{10, 100, 1000} { 683 b.Run("n="+strconv.Itoa(n), func(b *testing.B) { 684 rnd := rand.New(rand.NewSource(1)) 685 686 data := make([]float64, n*n) 687 for i := range data { 688 data[i] = rnd.NormFloat64() 689 } 690 var a SymDense 691 a.SymOuterK(1, NewDense(n, n, data)) 692 693 var chol Cholesky 694 ok := chol.Factorize(&a) 695 if !ok { 696 panic("not positive definite") 697 } 698 699 dst := NewSymDense(n, nil) 700 701 b.ResetTimer() 702 for i := 0; i < b.N; i++ { 703 err := chol.InverseTo(dst) 704 if err != nil { 705 b.Fatalf("unexpected error from Cholesky inverse: %v", err) 706 } 707 } 708 }) 709 } 710 } 711 712 func TestBandCholeskySolveTo(t *testing.T) { 713 t.Parallel() 714 715 const ( 716 nrhs = 4 717 tol = 1e-14 718 ) 719 rnd := rand.New(rand.NewSource(1)) 720 for _, n := range []int{1, 2, 3, 5, 10} { 721 for _, k := range []int{0, 1, n / 2, n - 1} { 722 k := min(k, n-1) 723 724 a := NewSymBandDense(n, k, nil) 725 for i := 0; i < n; i++ { 726 a.SetSymBand(i, i, rnd.Float64()+float64(n)) 727 for j := i + 1; j < min(i+k+1, n); j++ { 728 a.SetSymBand(i, j, rnd.Float64()) 729 } 730 } 731 732 want := NewDense(n, nrhs, nil) 733 for i := 0; i < n; i++ { 734 for j := 0; j < nrhs; j++ { 735 want.Set(i, j, rnd.NormFloat64()) 736 } 737 } 738 var b Dense 739 b.Mul(a, want) 740 741 for _, typ := range []SymBanded{a, (*basicSymBanded)(a)} { 742 name := fmt.Sprintf("Case n=%d,k=%d,type=%T,nrhs=%d", n, k, typ, nrhs) 743 744 var chol BandCholesky 745 ok := chol.Factorize(typ) 746 if !ok { 747 t.Fatalf("%v: Factorize failed", name) 748 } 749 750 var got Dense 751 err := chol.SolveTo(&got, &b) 752 if err != nil { 753 t.Errorf("%v: unexpected error from SolveTo: %v", name, err) 754 continue 755 } 756 757 var resid Dense 758 resid.Sub(want, &got) 759 diff := Norm(&resid, math.Inf(1)) 760 if diff > tol { 761 t.Errorf("%v: unexpected solution; diff=%v", name, diff) 762 } 763 764 got.Copy(&b) 765 err = chol.SolveTo(&got, &got) 766 if err != nil { 767 t.Errorf("%v: unexpected error from SolveTo when dst==b: %v", name, err) 768 continue 769 } 770 771 resid.Sub(want, &got) 772 diff = Norm(&resid, math.Inf(1)) 773 if diff > tol { 774 t.Errorf("%v: unexpected solution when dst==b; diff=%v", name, diff) 775 } 776 } 777 } 778 } 779 } 780 781 func TestBandCholeskySolveVecTo(t *testing.T) { 782 t.Parallel() 783 784 const tol = 1e-14 785 rnd := rand.New(rand.NewSource(1)) 786 for _, n := range []int{1, 2, 3, 5, 10} { 787 for _, k := range []int{0, 1, n / 2, n - 1} { 788 k := min(k, n-1) 789 790 a := NewSymBandDense(n, k, nil) 791 for i := 0; i < n; i++ { 792 a.SetSymBand(i, i, rnd.Float64()+float64(n)) 793 for j := i + 1; j < min(i+k+1, n); j++ { 794 a.SetSymBand(i, j, rnd.Float64()) 795 } 796 } 797 798 want := NewVecDense(n, nil) 799 for i := 0; i < n; i++ { 800 want.SetVec(i, rnd.NormFloat64()) 801 } 802 var b VecDense 803 b.MulVec(a, want) 804 805 for _, typ := range []SymBanded{a, (*basicSymBanded)(a)} { 806 name := fmt.Sprintf("Case n=%d,k=%d,type=%T", n, k, typ) 807 808 var chol BandCholesky 809 ok := chol.Factorize(typ) 810 if !ok { 811 t.Fatalf("%v: Factorize failed", name) 812 } 813 814 var got VecDense 815 err := chol.SolveVecTo(&got, &b) 816 if err != nil { 817 t.Errorf("%v: unexpected error from SolveVecTo: %v", name, err) 818 continue 819 } 820 821 var resid VecDense 822 resid.SubVec(want, &got) 823 diff := Norm(&resid, math.Inf(1)) 824 if diff > tol { 825 t.Errorf("%v: unexpected solution; diff=%v", name, diff) 826 } 827 828 got.CopyVec(&b) 829 err = chol.SolveVecTo(&got, &got) 830 if err != nil { 831 t.Errorf("%v: unexpected error from SolveVecTo when dst==b: %v", name, err) 832 continue 833 } 834 835 resid.SubVec(want, &got) 836 diff = Norm(&resid, math.Inf(1)) 837 if diff > tol { 838 t.Errorf("%v: unexpected solution when dst==b; diff=%v", name, diff) 839 } 840 } 841 } 842 } 843 } 844 845 func TestBandCholeskyAt(t *testing.T) { 846 t.Parallel() 847 848 const tol = 1e-14 849 rnd := rand.New(rand.NewSource(1)) 850 for _, n := range []int{1, 2, 3, 5, 10} { 851 for _, k := range []int{0, 1, n / 2, n - 1} { 852 k := min(k, n-1) 853 name := fmt.Sprintf("Case n=%d,k=%d", n, k) 854 855 a := NewSymBandDense(n, k, nil) 856 for i := 0; i < n; i++ { 857 a.SetSymBand(i, i, rnd.Float64()+float64(n)) 858 for j := i + 1; j < min(i+k+1, n); j++ { 859 a.SetSymBand(i, j, rnd.Float64()) 860 } 861 } 862 863 var chol BandCholesky 864 ok := chol.Factorize(a) 865 if !ok { 866 t.Fatalf("%v: Factorize failed", name) 867 } 868 869 resid := NewDense(n, n, nil) 870 for i := 0; i < n; i++ { 871 for j := 0; j < n; j++ { 872 resid.Set(i, j, math.Abs(a.At(i, j)-chol.At(i, j))) 873 } 874 } 875 diff := Norm(resid, math.Inf(1)) 876 if diff > tol { 877 t.Errorf("%v: unexpected result; diff=%v, want<=%v", name, diff, tol) 878 } 879 } 880 } 881 } 882 883 func TestBandCholeskyDet(t *testing.T) { 884 t.Parallel() 885 886 const tol = 1e-14 887 rnd := rand.New(rand.NewSource(1)) 888 for _, n := range []int{1, 2, 3, 5, 10} { 889 for _, k := range []int{0, 1, n / 2, n - 1} { 890 k := min(k, n-1) 891 name := fmt.Sprintf("Case n=%d,k=%d", n, k) 892 893 a := NewSymBandDense(n, k, nil) 894 aSym := NewSymDense(n, nil) 895 for i := 0; i < n; i++ { 896 aii := rnd.Float64() + float64(n) 897 a.SetSymBand(i, i, aii) 898 aSym.SetSym(i, i, aii) 899 for j := i + 1; j < min(i+k+1, n); j++ { 900 aij := rnd.Float64() 901 a.SetSymBand(i, j, aij) 902 aSym.SetSym(i, j, aij) 903 } 904 } 905 906 var chol BandCholesky 907 ok := chol.Factorize(a) 908 if !ok { 909 t.Fatalf("%v: Factorize failed", name) 910 } 911 912 var cholDense Cholesky 913 ok = cholDense.Factorize(aSym) 914 if !ok { 915 t.Fatalf("%v: dense Factorize failed", name) 916 } 917 918 want := cholDense.Det() 919 got := chol.Det() 920 diff := math.Abs(got - want) 921 if diff > tol { 922 t.Errorf("%v: unexpected result; got=%v, want=%v (diff=%v)", name, got, want, diff) 923 } 924 } 925 } 926 } 927 928 func TestPivotedCholesky(t *testing.T) { 929 t.Parallel() 930 931 const tol = 1e-14 932 src := rand.NewSource(1) 933 for _, n := range []int{1, 2, 3, 4, 5, 10} { 934 for _, rank := range []int{int(0.3 * float64(n)), int(0.7 * float64(n)), n} { 935 name := fmt.Sprintf("n=%d, rank=%d", n, rank) 936 937 // Generate a random symmetric semi-definite matrix A with the given rank. 938 a := NewSymDense(n, nil) 939 for i := 0; i < rank; i++ { 940 x := randVecDense(n, 1, 1, src) 941 a.SymRankOne(a, 1, x) 942 } 943 944 // Compute the pivoted Cholesky factorization of A. 945 var chol PivotedCholesky 946 ok := chol.Factorize(a, -1) 947 948 // Check that the ok return matches the rank of A. 949 if !ok && rank == n { 950 t.Errorf("%s: unexpected factorization failure with full rank", name) 951 } 952 if ok && rank != n { 953 t.Errorf("%s: unexpected factorization success with deficit rank", name) 954 } 955 956 // Check that the computed rank matches the rank of A. 957 if chol.Rank() != rank { 958 t.Errorf("%s: unexpected computed rank, got %d", name, chol.Rank()) 959 } 960 961 // Check the size. 962 r, c := chol.Dims() 963 if r != n || c != n { 964 t.Errorf("n=%d, rank=%d: unexpected dims: r=%d, c=%d", n, rank, r, c) 965 } 966 if chol.SymmetricDim() != n { 967 t.Errorf("n=%d, rank=%d: unexpected symmetric dim: dim=%d", n, rank, chol.SymmetricDim()) 968 } 969 970 // Compute the norm of the difference |P*Uᵀ*U*Pᵀ - A|. 971 diff := NewDense(n, n, nil) 972 for i := 0; i < n; i++ { 973 for j := 0; j < n; j++ { 974 diff.Set(i, j, chol.At(i, j)-a.At(i, j)) 975 } 976 } 977 res := Norm(diff, 1) 978 if res > tol { 979 t.Errorf("n=%d, rank=%d: unexpected result (|diff|=%v)\ndiff = %.4g", n, rank, res, Formatted(diff, Prefix(" "))) 980 } 981 } 982 } 983 } 984 985 func TestPivotedCholeskySolveTo(t *testing.T) { 986 t.Parallel() 987 988 const ( 989 nrhs = 4 990 tol = 1e-14 991 ) 992 rnd := rand.New(rand.NewSource(1)) 993 for _, n := range []int{1, 2, 3, 5, 10} { 994 a := NewSymDense(n, nil) 995 for i := 0; i < n; i++ { 996 a.SetSym(i, i, rnd.Float64()+float64(n)) 997 for j := i + 1; j < n; j++ { 998 a.SetSym(i, j, rnd.Float64()) 999 } 1000 } 1001 1002 want := NewDense(n, nrhs, nil) 1003 for i := 0; i < n; i++ { 1004 for j := 0; j < nrhs; j++ { 1005 want.Set(i, j, rnd.NormFloat64()) 1006 } 1007 } 1008 1009 var b Dense 1010 b.Mul(a, want) 1011 1012 for _, typ := range []Symmetric{a, asBasicSymmetric(a)} { 1013 name := fmt.Sprintf("Case n=%d,type=%T,nrhs=%d", n, typ, nrhs) 1014 1015 var chol PivotedCholesky 1016 ok := chol.Factorize(typ, -1) 1017 if !ok { 1018 t.Fatalf("%v: matrix not positive definite", name) 1019 } 1020 1021 var got Dense 1022 err := chol.SolveTo(&got, &b) 1023 if err != nil { 1024 t.Errorf("%v: unexpected error from SolveTo: %v", name, err) 1025 continue 1026 } 1027 1028 var resid Dense 1029 resid.Sub(want, &got) 1030 diff := Norm(&resid, math.Inf(1)) 1031 if diff > tol { 1032 t.Errorf("%v: unexpected solution; diff=%v", name, diff) 1033 } 1034 1035 got.Copy(&b) 1036 err = chol.SolveTo(&got, &got) 1037 if err != nil { 1038 t.Errorf("%v: unexpected error from SolveTo when dst==b: %v", name, err) 1039 continue 1040 } 1041 1042 resid.Sub(want, &got) 1043 diff = Norm(&resid, math.Inf(1)) 1044 if diff > tol { 1045 t.Errorf("%v: unexpected solution when dst==b; diff=%v", name, diff) 1046 } 1047 } 1048 } 1049 } 1050 1051 func TestPivotedCholeskySolveVecTo(t *testing.T) { 1052 t.Parallel() 1053 1054 const tol = 1e-14 1055 rnd := rand.New(rand.NewSource(1)) 1056 for _, n := range []int{1, 2, 3, 5, 10} { 1057 1058 a := NewSymDense(n, nil) 1059 for i := 0; i < n; i++ { 1060 a.SetSym(i, i, rnd.Float64()+float64(n)) 1061 for j := i + 1; j < n; j++ { 1062 a.SetSym(i, j, rnd.Float64()) 1063 } 1064 } 1065 1066 want := NewVecDense(n, nil) 1067 for i := 0; i < n; i++ { 1068 want.SetVec(i, rnd.NormFloat64()) 1069 } 1070 var b VecDense 1071 b.MulVec(a, want) 1072 1073 for _, typ := range []Symmetric{a, asBasicSymmetric(a)} { 1074 name := fmt.Sprintf("Case n=%d,type=%T", n, typ) 1075 1076 var chol PivotedCholesky 1077 ok := chol.Factorize(typ, -1) 1078 if !ok { 1079 t.Fatalf("%v: matrix not positive definite", name) 1080 } 1081 1082 var got VecDense 1083 err := chol.SolveVecTo(&got, &b) 1084 if err != nil { 1085 t.Errorf("%v: unexpected error from SolveVecTo: %v", name, err) 1086 continue 1087 } 1088 1089 var resid VecDense 1090 resid.SubVec(want, &got) 1091 diff := Norm(&resid, math.Inf(1)) 1092 if diff > tol { 1093 t.Errorf("%v: unexpected solution; diff=%v", name, diff) 1094 } 1095 1096 got.CopyVec(&b) 1097 err = chol.SolveVecTo(&got, &got) 1098 if err != nil { 1099 t.Errorf("%v: unexpected error from SolveVecTo when dst==b: %v", name, err) 1100 continue 1101 } 1102 1103 resid.SubVec(want, &got) 1104 diff = Norm(&resid, math.Inf(1)) 1105 if diff > tol { 1106 t.Errorf("%v: unexpected solution when dst==b; diff=%v", name, diff) 1107 } 1108 } 1109 } 1110 }