blob: 883322d7aa488a424328ccfcd2e826af1bf4e5eb [file] [log] [blame]
// Copyright ©2015 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"
"reflect"
"testing"
"gonum.org/v1/gonum/blas"
"gonum.org/v1/gonum/blas/blas64"
)
func TestNewTriangular(t *testing.T) {
for i, test := range []struct {
data []float64
n int
kind TriKind
mat *TriDense
}{
{
data: []float64{
1, 2, 3,
4, 5, 6,
7, 8, 9,
},
n: 3,
kind: Upper,
mat: &TriDense{
mat: blas64.Triangular{
N: 3,
Stride: 3,
Uplo: blas.Upper,
Data: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9},
Diag: blas.NonUnit,
},
cap: 3,
},
},
} {
tri := NewTriDense(test.n, test.kind, test.data)
rows, cols := tri.Dims()
if rows != test.n {
t.Errorf("unexpected number of rows for test %d: got: %d want: %d", i, rows, test.n)
}
if cols != test.n {
t.Errorf("unexpected number of cols for test %d: got: %d want: %d", i, cols, test.n)
}
if !reflect.DeepEqual(tri, test.mat) {
t.Errorf("unexpected data slice for test %d: got: %v want: %v", i, tri, test.mat)
}
}
for _, kind := range []TriKind{Lower, Upper} {
panicked, message := panics(func() { NewTriDense(3, kind, []float64{1, 2}) })
if !panicked || message != ErrShape.Error() {
t.Errorf("expected panic for invalid data slice length for upper=%t", kind)
}
}
}
func TestTriAtSet(t *testing.T) {
tri := &TriDense{
mat: blas64.Triangular{
N: 3,
Stride: 3,
Uplo: blas.Upper,
Data: []float64{1, 2, 3, 4, 5, 6, 7, 8, 9},
Diag: blas.NonUnit,
},
cap: 3,
}
rows, cols := tri.Dims()
// Check At out of bounds
for _, row := range []int{-1, rows, rows + 1} {
panicked, message := panics(func() { tri.At(row, 0) })
if !panicked || message != ErrRowAccess.Error() {
t.Errorf("expected panic for invalid row access N=%d r=%d", rows, row)
}
}
for _, col := range []int{-1, cols, cols + 1} {
panicked, message := panics(func() { tri.At(0, col) })
if !panicked || message != ErrColAccess.Error() {
t.Errorf("expected panic for invalid column access N=%d c=%d", cols, col)
}
}
// Check Set out of bounds
for _, row := range []int{-1, rows, rows + 1} {
panicked, message := panics(func() { tri.SetTri(row, 0, 1.2) })
if !panicked || message != ErrRowAccess.Error() {
t.Errorf("expected panic for invalid row access N=%d r=%d", rows, row)
}
}
for _, col := range []int{-1, cols, cols + 1} {
panicked, message := panics(func() { tri.SetTri(0, col, 1.2) })
if !panicked || message != ErrColAccess.Error() {
t.Errorf("expected panic for invalid column access N=%d c=%d", cols, col)
}
}
for _, st := range []struct {
row, col int
uplo blas.Uplo
}{
{row: 2, col: 1, uplo: blas.Upper},
{row: 1, col: 2, uplo: blas.Lower},
} {
tri.mat.Uplo = st.uplo
panicked, message := panics(func() { tri.SetTri(st.row, st.col, 1.2) })
if !panicked || message != ErrTriangleSet.Error() {
t.Errorf("expected panic for %+v", st)
}
}
for _, st := range []struct {
row, col int
uplo blas.Uplo
orig, new float64
}{
{row: 2, col: 1, uplo: blas.Lower, orig: 8, new: 15},
{row: 1, col: 2, uplo: blas.Upper, orig: 6, new: 15},
} {
tri.mat.Uplo = st.uplo
if e := tri.At(st.row, st.col); e != st.orig {
t.Errorf("unexpected value for At(%d, %d): got: %v want: %v", st.row, st.col, e, st.orig)
}
tri.SetTri(st.row, st.col, st.new)
if e := tri.At(st.row, st.col); e != st.new {
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)
}
}
}
func TestTriDenseCopy(t *testing.T) {
for i := 0; i < 100; i++ {
size := rand.Intn(100)
r, err := randDense(size, 0.9, rand.NormFloat64)
if size == 0 {
if err != ErrZeroLength {
t.Fatalf("expected error %v: got: %v", ErrZeroLength, err)
}
continue
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
u := NewTriDense(size, true, nil)
l := NewTriDense(size, false, nil)
for _, typ := range []Matrix{r, (*basicMatrix)(r)} {
for j := range u.mat.Data {
u.mat.Data[j] = math.NaN()
l.mat.Data[j] = math.NaN()
}
u.Copy(typ)
l.Copy(typ)
for m := 0; m < size; m++ {
for n := 0; n < size; n++ {
want := typ.At(m, n)
switch {
case m < n: // Upper triangular matrix.
if got := u.At(m, n); got != want {
t.Errorf("unexpected upper value for At(%d, %d) for test %d: got: %v want: %v", m, n, i, got, want)
}
case m == n: // Diagonal matrix.
if got := u.At(m, n); got != want {
t.Errorf("unexpected upper value for At(%d, %d) for test %d: got: %v want: %v", m, n, i, got, want)
}
if got := l.At(m, n); got != want {
t.Errorf("unexpected diagonal value for At(%d, %d) for test %d: got: %v want: %v", m, n, i, got, want)
}
case m < n: // Lower triangular matrix.
if got := l.At(m, n); got != want {
t.Errorf("unexpected lower value for At(%d, %d) for test %d: got: %v want: %v", m, n, i, got, want)
}
}
}
}
}
}
}
func TestTriTriDenseCopy(t *testing.T) {
for i := 0; i < 100; i++ {
size := rand.Intn(100)
r, err := randDense(size, 1, rand.NormFloat64)
if size == 0 {
if err != ErrZeroLength {
t.Fatalf("expected error %v: got: %v", ErrZeroLength, err)
}
continue
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
ur := NewTriDense(size, true, nil)
lr := NewTriDense(size, false, nil)
ur.Copy(r)
lr.Copy(r)
u := NewTriDense(size, true, nil)
u.Copy(ur)
if !equal(u, ur) {
t.Fatal("unexpected result for U triangle copy of U triangle: not equal")
}
l := NewTriDense(size, false, nil)
l.Copy(lr)
if !equal(l, lr) {
t.Fatal("unexpected result for L triangle copy of L triangle: not equal")
}
zero(u.mat.Data)
u.Copy(lr)
if !isDiagonal(u) {
t.Fatal("unexpected result for U triangle copy of L triangle: off diagonal non-zero element")
}
if !equalDiagonal(u, lr) {
t.Fatal("unexpected result for U triangle copy of L triangle: diagonal not equal")
}
zero(l.mat.Data)
l.Copy(ur)
if !isDiagonal(l) {
t.Fatal("unexpected result for L triangle copy of U triangle: off diagonal non-zero element")
}
if !equalDiagonal(l, ur) {
t.Fatal("unexpected result for L triangle copy of U triangle: diagonal not equal")
}
}
}
func TestTriInverse(t *testing.T) {
for _, kind := range []TriKind{Upper, Lower} {
for _, n := range []int{1, 3, 5, 9} {
data := make([]float64, n*n)
for i := range data {
data[i] = rand.NormFloat64()
}
a := NewTriDense(n, kind, data)
var tr TriDense
err := tr.InverseTri(a)
if err != nil {
t.Errorf("Bad test: %s", err)
}
var d Dense
d.Mul(a, &tr)
if !equalApprox(eye(n), &d, 1e-8, false) {
var diff Dense
diff.Sub(eye(n), &d)
t.Errorf("Tri times inverse is not identity. Norm of difference: %v", Norm(&diff, 2))
}
}
}
}
func TestTriMul(t *testing.T) {
method := func(receiver, a, b Matrix) {
type MulTrier interface {
MulTri(a, b Triangular)
}
receiver.(MulTrier).MulTri(a.(Triangular), b.(Triangular))
}
denseComparison := func(receiver, a, b *Dense) {
receiver.Mul(a, b)
}
legalSizeTriMul := func(ar, ac, br, bc int) bool {
// Need both to be square and the sizes to be the same
return ar == ac && br == bc && ar == br
}
// The legal types are triangles with the same TriKind.
// legalTypesTri returns whether both input arguments are Triangular.
legalTypes := func(a, b Matrix) bool {
at, ok := a.(Triangular)
if !ok {
return false
}
bt, ok := b.(Triangular)
if !ok {
return false
}
_, ak := at.Triangle()
_, bk := bt.Triangle()
return ak == bk
}
legalTypesLower := func(a, b Matrix) bool {
legal := legalTypes(a, b)
if !legal {
return false
}
_, kind := a.(Triangular).Triangle()
r := kind == Lower
return r
}
receiver := NewTriDense(3, Lower, nil)
testTwoInput(t, "TriMul", receiver, method, denseComparison, legalTypesLower, legalSizeTriMul, 1e-14)
legalTypesUpper := func(a, b Matrix) bool {
legal := legalTypes(a, b)
if !legal {
return false
}
_, kind := a.(Triangular).Triangle()
r := kind == Upper
return r
}
receiver = NewTriDense(3, Upper, nil)
testTwoInput(t, "TriMul", receiver, method, denseComparison, legalTypesUpper, legalSizeTriMul, 1e-14)
}
func TestCopySymIntoTriangle(t *testing.T) {
nan := math.NaN()
for tc, test := range []struct {
n int
sUplo blas.Uplo
s []float64
tUplo TriKind
want []float64
}{
{
n: 3,
sUplo: blas.Upper,
s: []float64{
1, 2, 3,
nan, 4, 5,
nan, nan, 6,
},
tUplo: Upper,
want: []float64{
1, 2, 3,
0, 4, 5,
0, 0, 6,
},
},
{
n: 3,
sUplo: blas.Lower,
s: []float64{
1, nan, nan,
2, 3, nan,
4, 5, 6,
},
tUplo: Upper,
want: []float64{
1, 2, 4,
0, 3, 5,
0, 0, 6,
},
},
{
n: 3,
sUplo: blas.Upper,
s: []float64{
1, 2, 3,
nan, 4, 5,
nan, nan, 6,
},
tUplo: Lower,
want: []float64{
1, 0, 0,
2, 4, 0,
3, 5, 6,
},
},
{
n: 3,
sUplo: blas.Lower,
s: []float64{
1, nan, nan,
2, 3, nan,
4, 5, 6,
},
tUplo: Lower,
want: []float64{
1, 0, 0,
2, 3, 0,
4, 5, 6,
},
},
} {
n := test.n
s := NewSymDense(n, test.s)
// For the purpose of the test, break the assumption that
// symmetric is stored in the upper triangle (only when S is
// RawSymmetricer).
s.mat.Uplo = test.sUplo
t1 := NewTriDense(n, test.tUplo, nil)
copySymIntoTriangle(t1, s)
equal := true
loop1:
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if t1.At(i, j) != test.want[i*n+j] {
equal = false
break loop1
}
}
}
if !equal {
t.Errorf("Case %v: unexpected T when S is RawSymmetricer", tc)
}
if test.sUplo == blas.Lower {
continue
}
sb := (basicSymmetric)(*s)
t2 := NewTriDense(n, test.tUplo, nil)
copySymIntoTriangle(t2, &sb)
equal = true
loop2:
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if t1.At(i, j) != test.want[i*n+j] {
equal = false
break loop2
}
}
}
if !equal {
t.Errorf("Case %v: unexpected T when S is not RawSymmetricer", tc)
}
}
}