blob: 6a5c853dcf2bf36cbece4d3f13decb93fb4f2d4e [file] [log] [blame]
// Copyright ©2013 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mat
import (
"math"
"math/rand"
"testing"
"gonum.org/v1/gonum/blas/testblas"
)
func TestCholesky(t *testing.T) {
for _, test := range []struct {
a *SymDense
cond float64
want *TriDense
posdef bool
}{
{
a: NewSymDense(3, []float64{
4, 1, 1,
0, 2, 3,
0, 0, 6,
}),
cond: 37,
want: NewTriDense(3, true, []float64{
2, 0.5, 0.5,
0, 1.3228756555322954, 2.0788046015507495,
0, 0, 1.195228609334394,
}),
posdef: true,
},
} {
_, n := test.a.Dims()
for _, chol := range []*Cholesky{
{},
{chol: NewTriDense(n-1, true, nil)},
{chol: NewTriDense(n, true, nil)},
{chol: NewTriDense(n+1, true, nil)},
} {
ok := chol.Factorize(test.a)
if ok != test.posdef {
t.Errorf("unexpected return from Cholesky factorization: got: ok=%t want: ok=%t", ok, test.posdef)
}
fc := DenseCopyOf(chol.chol)
if !Equal(fc, test.want) {
t.Error("incorrect Cholesky factorization")
}
if math.Abs(test.cond-chol.cond) > 1e-13 {
t.Errorf("Condition number mismatch: Want %v, got %v", test.cond, chol.cond)
}
U := chol.UTo(nil)
aCopy := DenseCopyOf(test.a)
var a Dense
a.Mul(U.TTri(), U)
if !EqualApprox(&a, aCopy, 1e-14) {
t.Error("unexpected Cholesky factor product")
}
L := chol.LTo(nil)
a.Mul(L, L.TTri())
if !EqualApprox(&a, aCopy, 1e-14) {
t.Error("unexpected Cholesky factor product")
}
}
}
}
func TestCholeskySolve(t *testing.T) {
for _, test := range []struct {
a *SymDense
b *Dense
ans *Dense
}{
{
a: NewSymDense(2, []float64{
1, 0,
0, 1,
}),
b: NewDense(2, 1, []float64{5, 6}),
ans: NewDense(2, 1, []float64{5, 6}),
},
{
a: NewSymDense(3, []float64{
53, 59, 37,
0, 83, 71,
37, 71, 101,
}),
b: NewDense(3, 1, []float64{5, 6, 7}),
ans: NewDense(3, 1, []float64{0.20745069393718094, -0.17421475529583694, 0.11577794010226464}),
},
} {
var chol Cholesky
ok := chol.Factorize(test.a)
if !ok {
t.Fatal("unexpected Cholesky factorization failure: not positive definite")
}
var x Dense
chol.Solve(&x, test.b)
if !EqualApprox(&x, test.ans, 1e-12) {
t.Error("incorrect Cholesky solve solution")
}
var ans Dense
ans.Mul(test.a, &x)
if !EqualApprox(&ans, test.b, 1e-12) {
t.Error("incorrect Cholesky solve solution product")
}
}
}
func TestCholeskySolveChol(t *testing.T) {
for _, test := range []struct {
a, b *SymDense
}{
{
a: NewSymDense(2, []float64{
1, 0,
0, 1,
}),
b: NewSymDense(2, []float64{
1, 0,
0, 1,
}),
},
{
a: NewSymDense(2, []float64{
1, 0,
0, 1,
}),
b: NewSymDense(2, []float64{
2, 0,
0, 2,
}),
},
{
a: NewSymDense(3, []float64{
53, 59, 37,
59, 83, 71,
37, 71, 101,
}),
b: NewSymDense(3, []float64{
2, -1, 0,
-1, 2, -1,
0, -1, 2,
}),
},
} {
var chola, cholb Cholesky
ok := chola.Factorize(test.a)
if !ok {
t.Fatal("unexpected Cholesky factorization failure for a: not positive definite")
}
ok = cholb.Factorize(test.b)
if !ok {
t.Fatal("unexpected Cholesky factorization failure for b: not positive definite")
}
var x Dense
chola.SolveChol(&x, &cholb)
var ans Dense
ans.Mul(test.a, &x)
if !EqualApprox(&ans, test.b, 1e-12) {
var y Dense
y.Solve(test.a, test.b)
t.Errorf("incorrect Cholesky solve solution product\ngot solution:\n%.4v\nwant solution\n%.4v",
Formatted(&x), Formatted(&y))
}
}
}
func TestCholeskySolveVec(t *testing.T) {
for _, test := range []struct {
a *SymDense
b *VecDense
ans *VecDense
}{
{
a: NewSymDense(2, []float64{
1, 0,
0, 1,
}),
b: NewVecDense(2, []float64{5, 6}),
ans: NewVecDense(2, []float64{5, 6}),
},
{
a: NewSymDense(3, []float64{
53, 59, 37,
0, 83, 71,
0, 0, 101,
}),
b: NewVecDense(3, []float64{5, 6, 7}),
ans: NewVecDense(3, []float64{0.20745069393718094, -0.17421475529583694, 0.11577794010226464}),
},
} {
var chol Cholesky
ok := chol.Factorize(test.a)
if !ok {
t.Fatal("unexpected Cholesky factorization failure: not positive definite")
}
var x VecDense
chol.SolveVec(&x, test.b)
if !EqualApprox(&x, test.ans, 1e-12) {
t.Error("incorrect Cholesky solve solution")
}
var ans VecDense
ans.MulVec(test.a, &x)
if !EqualApprox(&ans, test.b, 1e-12) {
t.Error("incorrect Cholesky solve solution product")
}
}
}
func TestCholeskyTo(t *testing.T) {
for _, test := range []*SymDense{
NewSymDense(3, []float64{
53, 59, 37,
0, 83, 71,
0, 0, 101,
}),
} {
var chol Cholesky
ok := chol.Factorize(test)
if !ok {
t.Fatal("unexpected Cholesky factorization failure: not positive definite")
}
s := chol.To(nil)
if !EqualApprox(s, test, 1e-12) {
t.Errorf("Cholesky reconstruction not equal to original matrix.\nWant:\n% v\nGot:\n% v\n", Formatted(test), Formatted(s))
}
}
}
func TestCloneCholesky(t *testing.T) {
for _, test := range []*SymDense{
NewSymDense(3, []float64{
53, 59, 37,
0, 83, 71,
0, 0, 101,
}),
} {
var chol Cholesky
ok := chol.Factorize(test)
if !ok {
panic("bad test")
}
var chol2 Cholesky
chol2.Clone(&chol)
if chol.cond != chol2.cond {
t.Errorf("condition number mismatch from zero")
}
if !Equal(chol.chol, chol2.chol) {
t.Errorf("chol mismatch from zero")
}
// Corrupt chol2 and try again
chol2.cond = math.NaN()
chol2.chol = NewTriDense(2, Upper, nil)
chol2.Clone(&chol)
if chol.cond != chol2.cond {
t.Errorf("condition number mismatch from non-zero")
}
if !Equal(chol.chol, chol2.chol) {
t.Errorf("chol mismatch from non-zero")
}
}
}
func TestCholeskyInverseTo(t *testing.T) {
for _, n := range []int{1, 3, 5, 9} {
data := make([]float64, n*n)
for i := range data {
data[i] = rand.NormFloat64()
}
var s SymDense
s.SymOuterK(1, NewDense(n, n, data))
var chol Cholesky
ok := chol.Factorize(&s)
if !ok {
t.Errorf("Bad test, cholesky decomposition failed")
}
var sInv SymDense
chol.InverseTo(&sInv)
var ans Dense
ans.Mul(&sInv, &s)
if !equalApprox(eye(n), &ans, 1e-8, false) {
var diff Dense
diff.Sub(eye(n), &ans)
t.Errorf("SymDense times Cholesky inverse not identity. Norm diff = %v", Norm(&diff, 2))
}
}
}
func TestCholeskySymRankOne(t *testing.T) {
rand.Seed(1)
for _, n := range []int{1, 2, 3, 4, 5, 7, 10, 20, 50, 100} {
for k := 0; k < 10; k++ {
data := make([]float64, n*n)
for i := range data {
data[i] = rand.NormFloat64()
}
var a SymDense
a.SymOuterK(1, NewDense(n, n, data))
xdata := make([]float64, n)
for i := range xdata {
xdata[i] = rand.NormFloat64()
}
x := NewVecDense(n, xdata)
var chol Cholesky
ok := chol.Factorize(&a)
if !ok {
t.Errorf("Bad random test, Cholesky factorization failed")
continue
}
alpha := rand.Float64()
ok = chol.SymRankOne(&chol, alpha, x)
if !ok {
t.Errorf("n=%v, alpha=%v: unexpected failure", n, alpha)
continue
}
a.SymRankOne(&a, alpha, x)
var achol SymDense
chol.To(&achol)
if !EqualApprox(&achol, &a, 1e-13) {
t.Errorf("n=%v, alpha=%v: mismatch between updated matrix and from Cholesky:\nupdated:\n%v\nfrom Cholesky:\n%v",
n, alpha, Formatted(&a), Formatted(&achol))
}
}
}
for i, test := range []struct {
a *SymDense
alpha float64
x []float64
wantOk bool
}{
{
// Update (to positive definite matrix).
a: NewSymDense(4, []float64{
1, 1, 1, 1,
0, 2, 3, 4,
0, 0, 6, 10,
0, 0, 0, 20,
}),
alpha: 1,
x: []float64{0, 0, 0, 1},
wantOk: true,
},
{
// Downdate to singular matrix.
a: NewSymDense(4, []float64{
1, 1, 1, 1,
0, 2, 3, 4,
0, 0, 6, 10,
0, 0, 0, 20,
}),
alpha: -1,
x: []float64{0, 0, 0, 1},
wantOk: false,
},
{
// Downdate to positive definite matrix.
a: NewSymDense(4, []float64{
1, 1, 1, 1,
0, 2, 3, 4,
0, 0, 6, 10,
0, 0, 0, 20,
}),
alpha: -1 / 2,
x: []float64{0, 0, 0, 1},
wantOk: true,
},
} {
var chol Cholesky
ok := chol.Factorize(test.a)
if !ok {
t.Errorf("Case %v: bad test, Cholesky factorization failed", i)
continue
}
x := NewVecDense(len(test.x), test.x)
ok = chol.SymRankOne(&chol, test.alpha, x)
if !ok {
if test.wantOk {
t.Errorf("Case %v: unexpected failure from SymRankOne", i)
}
continue
}
if ok && !test.wantOk {
t.Errorf("Case %v: expected a failure from SymRankOne", i)
}
a := test.a
a.SymRankOne(a, test.alpha, x)
var achol SymDense
chol.To(&achol)
if !EqualApprox(&achol, a, 1e-13) {
t.Errorf("Case %v: mismatch between updated matrix and from Cholesky:\nupdated:\n%v\nfrom Cholesky:\n%v",
i, Formatted(a), Formatted(&achol))
}
}
}
func BenchmarkCholeskySmall(b *testing.B) {
benchmarkCholesky(b, 2)
}
func BenchmarkCholeskyMedium(b *testing.B) {
benchmarkCholesky(b, testblas.MediumMat)
}
func BenchmarkCholeskyLarge(b *testing.B) {
benchmarkCholesky(b, testblas.LargeMat)
}
func benchmarkCholesky(b *testing.B, n int) {
base := make([]float64, n*n)
for i := range base {
base[i] = rand.Float64()
}
bm := NewDense(n, n, base)
bm.Mul(bm.T(), bm)
am := NewSymDense(n, bm.mat.Data)
var chol Cholesky
b.ResetTimer()
for i := 0; i < b.N; i++ {
ok := chol.Factorize(am)
if !ok {
panic("not pos def")
}
}
}