mirror of
https://github.com/gonum/gonum.git
synced 2025-10-16 04:00:48 +08:00

This merges the three packages, matrix, mat64, and cmat128. It then renames this big package to mat. It fixes the import statements and corresponding code
524 lines
11 KiB
Go
524 lines
11 KiB
Go
// 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 (
|
||
"fmt"
|
||
"math"
|
||
"reflect"
|
||
"testing"
|
||
|
||
"gonum.org/v1/gonum/blas"
|
||
"gonum.org/v1/gonum/blas/blas64"
|
||
"gonum.org/v1/gonum/floats"
|
||
)
|
||
|
||
func panics(fn func()) (panicked bool, message string) {
|
||
defer func() {
|
||
r := recover()
|
||
panicked = r != nil
|
||
message = fmt.Sprint(r)
|
||
}()
|
||
fn()
|
||
return
|
||
}
|
||
|
||
func flatten(f [][]float64) (r, c int, d []float64) {
|
||
r = len(f)
|
||
if r == 0 {
|
||
panic("bad test: no row")
|
||
}
|
||
c = len(f[0])
|
||
d = make([]float64, 0, r*c)
|
||
for _, row := range f {
|
||
if len(row) != c {
|
||
panic("bad test: ragged input")
|
||
}
|
||
d = append(d, row...)
|
||
}
|
||
return r, c, d
|
||
}
|
||
|
||
func unflatten(r, c int, d []float64) [][]float64 {
|
||
m := make([][]float64, r)
|
||
for i := 0; i < r; i++ {
|
||
m[i] = d[i*c : (i+1)*c]
|
||
}
|
||
return m
|
||
}
|
||
|
||
// eye returns a new identity matrix of size n×n.
|
||
func eye(n int) *Dense {
|
||
d := make([]float64, n*n)
|
||
for i := 0; i < n*n; i += n + 1 {
|
||
d[i] = 1
|
||
}
|
||
return NewDense(n, n, d)
|
||
}
|
||
|
||
func TestCol(t *testing.T) {
|
||
for id, af := range [][][]float64{
|
||
{
|
||
{1, 2, 3},
|
||
{4, 5, 6},
|
||
{7, 8, 9},
|
||
},
|
||
{
|
||
{1, 2, 3},
|
||
{4, 5, 6},
|
||
{7, 8, 9},
|
||
{10, 11, 12},
|
||
},
|
||
{
|
||
{1, 2, 3, 4},
|
||
{5, 6, 7, 8},
|
||
{9, 10, 11, 12},
|
||
},
|
||
} {
|
||
a := NewDense(flatten(af))
|
||
col := make([]float64, a.mat.Rows)
|
||
for j := range af[0] {
|
||
for i := range col {
|
||
col[i] = float64(i*a.mat.Cols + j + 1)
|
||
}
|
||
|
||
if got := Col(nil, j, a); !reflect.DeepEqual(got, col) {
|
||
t.Errorf("test %d: unexpected values returned for dense col %d: got: %v want: %v",
|
||
id, j, got, col)
|
||
}
|
||
|
||
got := make([]float64, a.mat.Rows)
|
||
if Col(got, j, a); !reflect.DeepEqual(got, col) {
|
||
t.Errorf("test %d: unexpected values filled for dense col %d: got: %v want: %v",
|
||
id, j, got, col)
|
||
}
|
||
}
|
||
}
|
||
|
||
denseComparison := func(a *Dense) interface{} {
|
||
r, c := a.Dims()
|
||
ans := make([][]float64, c)
|
||
for j := range ans {
|
||
ans[j] = make([]float64, r)
|
||
for i := range ans[j] {
|
||
ans[j][i] = a.At(i, j)
|
||
}
|
||
}
|
||
return ans
|
||
}
|
||
|
||
f := func(a Matrix) interface{} {
|
||
_, c := a.Dims()
|
||
ans := make([][]float64, c)
|
||
for j := range ans {
|
||
ans[j] = Col(nil, j, a)
|
||
}
|
||
return ans
|
||
}
|
||
testOneInputFunc(t, "Col", f, denseComparison, sameAnswerF64SliceOfSlice, isAnyType, isAnySize)
|
||
|
||
f = func(a Matrix) interface{} {
|
||
r, c := a.Dims()
|
||
ans := make([][]float64, c)
|
||
for j := range ans {
|
||
ans[j] = make([]float64, r)
|
||
Col(ans[j], j, a)
|
||
}
|
||
return ans
|
||
}
|
||
testOneInputFunc(t, "Col", f, denseComparison, sameAnswerF64SliceOfSlice, isAnyType, isAnySize)
|
||
}
|
||
|
||
func TestRow(t *testing.T) {
|
||
for id, af := range [][][]float64{
|
||
{
|
||
{1, 2, 3},
|
||
{4, 5, 6},
|
||
{7, 8, 9},
|
||
},
|
||
{
|
||
{1, 2, 3},
|
||
{4, 5, 6},
|
||
{7, 8, 9},
|
||
{10, 11, 12},
|
||
},
|
||
{
|
||
{1, 2, 3, 4},
|
||
{5, 6, 7, 8},
|
||
{9, 10, 11, 12},
|
||
},
|
||
} {
|
||
a := NewDense(flatten(af))
|
||
for i, row := range af {
|
||
if got := Row(nil, i, a); !reflect.DeepEqual(got, row) {
|
||
t.Errorf("test %d: unexpected values returned for dense row %d: got: %v want: %v",
|
||
id, i, got, row)
|
||
}
|
||
|
||
got := make([]float64, len(row))
|
||
if Row(got, i, a); !reflect.DeepEqual(got, row) {
|
||
t.Errorf("test %d: unexpected values filled for dense row %d: got: %v want: %v",
|
||
id, i, got, row)
|
||
}
|
||
}
|
||
}
|
||
|
||
denseComparison := func(a *Dense) interface{} {
|
||
r, c := a.Dims()
|
||
ans := make([][]float64, r)
|
||
for i := range ans {
|
||
ans[i] = make([]float64, c)
|
||
for j := range ans[i] {
|
||
ans[i][j] = a.At(i, j)
|
||
}
|
||
}
|
||
return ans
|
||
}
|
||
|
||
f := func(a Matrix) interface{} {
|
||
r, _ := a.Dims()
|
||
ans := make([][]float64, r)
|
||
for i := range ans {
|
||
ans[i] = Row(nil, i, a)
|
||
}
|
||
return ans
|
||
}
|
||
testOneInputFunc(t, "Row", f, denseComparison, sameAnswerF64SliceOfSlice, isAnyType, isAnySize)
|
||
|
||
f = func(a Matrix) interface{} {
|
||
r, c := a.Dims()
|
||
ans := make([][]float64, r)
|
||
for i := range ans {
|
||
ans[i] = make([]float64, c)
|
||
Row(ans[i], i, a)
|
||
}
|
||
return ans
|
||
}
|
||
testOneInputFunc(t, "Row", f, denseComparison, sameAnswerF64SliceOfSlice, isAnyType, isAnySize)
|
||
}
|
||
|
||
func TestCond(t *testing.T) {
|
||
for i, test := range []struct {
|
||
a *Dense
|
||
condOne float64
|
||
condTwo float64
|
||
condInf float64
|
||
}{
|
||
{
|
||
a: NewDense(3, 3, []float64{
|
||
8, 1, 6,
|
||
3, 5, 7,
|
||
4, 9, 2,
|
||
}),
|
||
condOne: 16.0 / 3.0,
|
||
condTwo: 4.330127018922192,
|
||
condInf: 16.0 / 3.0,
|
||
},
|
||
{
|
||
a: NewDense(4, 4, []float64{
|
||
2, 9, 3, 2,
|
||
10, 9, 9, 3,
|
||
1, 1, 5, 2,
|
||
8, 4, 10, 2,
|
||
}),
|
||
condOne: 1 / 0.024740155174938,
|
||
condTwo: 34.521576567075087,
|
||
condInf: 1 / 0.012034465570035,
|
||
},
|
||
{
|
||
a: NewDense(3, 3, []float64{
|
||
5, 6, 7,
|
||
8, -2, 1,
|
||
7, 7, 7}),
|
||
condOne: 30.769230769230749,
|
||
condTwo: 21.662689498448440,
|
||
condInf: 31.153846153846136,
|
||
},
|
||
} {
|
||
condOne := Cond(test.a, 1)
|
||
if !floats.EqualWithinAbsOrRel(test.condOne, condOne, 1e-13, 1e-13) {
|
||
t.Errorf("Case %d: inf norm mismatch. Want %v, got %v", i, test.condOne, condOne)
|
||
}
|
||
condTwo := Cond(test.a, 2)
|
||
if !floats.EqualWithinAbsOrRel(test.condTwo, condTwo, 1e-13, 1e-13) {
|
||
t.Errorf("Case %d: inf norm mismatch. Want %v, got %v", i, test.condTwo, condTwo)
|
||
}
|
||
condInf := Cond(test.a, math.Inf(1))
|
||
if !floats.EqualWithinAbsOrRel(test.condInf, condInf, 1e-13, 1e-13) {
|
||
t.Errorf("Case %d: inf norm mismatch. Want %v, got %v", i, test.condInf, condInf)
|
||
}
|
||
}
|
||
|
||
for _, test := range []struct {
|
||
name string
|
||
norm float64
|
||
}{
|
||
{
|
||
name: "CondOne",
|
||
norm: 1,
|
||
},
|
||
{
|
||
name: "CondTwo",
|
||
norm: 2,
|
||
},
|
||
{
|
||
name: "CondInf",
|
||
norm: math.Inf(1),
|
||
},
|
||
} {
|
||
f := func(a Matrix) interface{} {
|
||
return Cond(a, test.norm)
|
||
}
|
||
denseComparison := func(a *Dense) interface{} {
|
||
return Cond(a, test.norm)
|
||
}
|
||
testOneInputFunc(t, test.name, f, denseComparison, sameAnswerFloatApproxTol(1e-12), isAnyType, isAnySize)
|
||
}
|
||
}
|
||
|
||
func TestDet(t *testing.T) {
|
||
for c, test := range []struct {
|
||
a *Dense
|
||
ans float64
|
||
}{
|
||
{
|
||
a: NewDense(2, 2, []float64{1, 0, 0, 1}),
|
||
ans: 1,
|
||
},
|
||
{
|
||
a: NewDense(2, 2, []float64{1, 0, 0, -1}),
|
||
ans: -1,
|
||
},
|
||
{
|
||
a: NewDense(3, 3, []float64{
|
||
1, 2, 0,
|
||
0, 1, 2,
|
||
0, 2, 1,
|
||
}),
|
||
ans: -3,
|
||
},
|
||
{
|
||
a: NewDense(3, 3, []float64{
|
||
1, 2, 3,
|
||
5, 7, 9,
|
||
6, 9, 12,
|
||
}),
|
||
ans: 0,
|
||
},
|
||
} {
|
||
a := DenseCopyOf(test.a)
|
||
det := Det(a)
|
||
if !Equal(a, test.a) {
|
||
t.Errorf("Input matrix changed during Det. Case %d.", c)
|
||
}
|
||
if !floats.EqualWithinAbsOrRel(det, test.ans, 1e-14, 1e-14) {
|
||
t.Errorf("Det mismatch case %d. Got %v, want %v", c, det, test.ans)
|
||
}
|
||
}
|
||
// Perform the normal list test to ensure it works for all types.
|
||
f := func(a Matrix) interface{} {
|
||
return Det(a)
|
||
}
|
||
denseComparison := func(a *Dense) interface{} {
|
||
return Det(a)
|
||
}
|
||
testOneInputFunc(t, "Det", f, denseComparison, sameAnswerFloatApproxTol(1e-12), isAnyType, isSquare)
|
||
|
||
// Check that it gives approximately the same answer as Cholesky
|
||
// Ensure the input matrices are wider than tall so they are full rank
|
||
isWide := func(ar, ac int) bool {
|
||
return ar <= ac
|
||
}
|
||
f = func(a Matrix) interface{} {
|
||
ar, ac := a.Dims()
|
||
if !isWide(ar, ac) {
|
||
panic(ErrShape)
|
||
}
|
||
var tmp Dense
|
||
tmp.Mul(a, a.T())
|
||
return Det(&tmp)
|
||
}
|
||
denseComparison = func(a *Dense) interface{} {
|
||
ar, ac := a.Dims()
|
||
if !isWide(ar, ac) {
|
||
panic(ErrShape)
|
||
}
|
||
var tmp SymDense
|
||
tmp.SymOuterK(1, a)
|
||
var chol Cholesky
|
||
ok := chol.Factorize(&tmp)
|
||
if !ok {
|
||
panic("bad chol test")
|
||
}
|
||
return chol.Det()
|
||
}
|
||
testOneInputFunc(t, "DetVsChol", f, denseComparison, sameAnswerFloatApproxTol(1e-10), isAnyType, isWide)
|
||
}
|
||
|
||
func TestDot(t *testing.T) {
|
||
f := func(a, b Matrix) interface{} {
|
||
return Dot(a.(*Vector), b.(*Vector))
|
||
}
|
||
denseComparison := func(a, b *Dense) interface{} {
|
||
ra, ca := a.Dims()
|
||
rb, cb := b.Dims()
|
||
if ra != rb || ca != cb {
|
||
panic(ErrShape)
|
||
}
|
||
var sum float64
|
||
for i := 0; i < ra; i++ {
|
||
for j := 0; j < ca; j++ {
|
||
sum += a.At(i, j) * b.At(i, j)
|
||
}
|
||
}
|
||
return sum
|
||
}
|
||
testTwoInputFunc(t, "Dot", f, denseComparison, sameAnswerFloatApproxTol(1e-12), legalTypesVecVec, legalSizeSameVec)
|
||
}
|
||
|
||
func TestEqual(t *testing.T) {
|
||
f := func(a, b Matrix) interface{} {
|
||
return Equal(a, b)
|
||
}
|
||
denseComparison := func(a, b *Dense) interface{} {
|
||
return Equal(a, b)
|
||
}
|
||
testTwoInputFunc(t, "Equal", f, denseComparison, sameAnswerBool, legalTypesAll, isAnySize2)
|
||
}
|
||
|
||
func TestMax(t *testing.T) {
|
||
// A direct test of Max with *Dense arguments is in TestNewDense.
|
||
f := func(a Matrix) interface{} {
|
||
return Max(a)
|
||
}
|
||
denseComparison := func(a *Dense) interface{} {
|
||
return Max(a)
|
||
}
|
||
testOneInputFunc(t, "Max", f, denseComparison, sameAnswerFloat, isAnyType, isAnySize)
|
||
}
|
||
|
||
func TestMin(t *testing.T) {
|
||
// A direct test of Min with *Dense arguments is in TestNewDense.
|
||
f := func(a Matrix) interface{} {
|
||
return Min(a)
|
||
}
|
||
denseComparison := func(a *Dense) interface{} {
|
||
return Min(a)
|
||
}
|
||
testOneInputFunc(t, "Min", f, denseComparison, sameAnswerFloat, isAnyType, isAnySize)
|
||
}
|
||
|
||
func TestNorm(t *testing.T) {
|
||
for i, test := range []struct {
|
||
a [][]float64
|
||
ord float64
|
||
norm float64
|
||
}{
|
||
{
|
||
a: [][]float64{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}},
|
||
ord: 1,
|
||
norm: 30,
|
||
},
|
||
{
|
||
a: [][]float64{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}},
|
||
ord: 2,
|
||
norm: 25.495097567963924,
|
||
},
|
||
{
|
||
a: [][]float64{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}},
|
||
ord: math.Inf(1),
|
||
norm: 33,
|
||
},
|
||
{
|
||
a: [][]float64{{1, -2, -2}, {-4, 5, 6}},
|
||
ord: 1,
|
||
norm: 8,
|
||
},
|
||
{
|
||
a: [][]float64{{1, -2, -2}, {-4, 5, 6}},
|
||
ord: math.Inf(1),
|
||
norm: 15,
|
||
},
|
||
} {
|
||
a := NewDense(flatten(test.a))
|
||
if math.Abs(Norm(a, test.ord)-test.norm) > 1e-14 {
|
||
t.Errorf("Mismatch test %d: %v norm = %f", i, test.a, test.norm)
|
||
}
|
||
}
|
||
|
||
for _, test := range []struct {
|
||
name string
|
||
norm float64
|
||
}{
|
||
{"NormOne", 1},
|
||
{"NormTwo", 2},
|
||
{"NormInf", math.Inf(1)},
|
||
} {
|
||
f := func(a Matrix) interface{} {
|
||
return Norm(a, test.norm)
|
||
}
|
||
denseComparison := func(a *Dense) interface{} {
|
||
return Norm(a, test.norm)
|
||
}
|
||
testOneInputFunc(t, test.name, f, denseComparison, sameAnswerFloatApproxTol(1e-12), isAnyType, isAnySize)
|
||
}
|
||
}
|
||
|
||
func TestNormZero(t *testing.T) {
|
||
for _, a := range []Matrix{
|
||
&Dense{},
|
||
&SymDense{},
|
||
&SymDense{mat: blas64.Symmetric{Uplo: blas.Upper}},
|
||
&TriDense{},
|
||
&TriDense{mat: blas64.Triangular{Uplo: blas.Upper, Diag: blas.NonUnit}},
|
||
&Vector{},
|
||
} {
|
||
for _, norm := range []float64{1, 2, math.Inf(1)} {
|
||
panicked, message := panics(func() { Norm(a, norm) })
|
||
if !panicked {
|
||
t.Errorf("expected panic for Norm(&%T{}, %v)", a, norm)
|
||
}
|
||
if message != ErrShape.Error() {
|
||
t.Errorf("unexpected panic string for Norm(&%T{}, %v): got:%s want:%s",
|
||
a, norm, message, ErrShape.Error())
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestSum(t *testing.T) {
|
||
f := func(a Matrix) interface{} {
|
||
return Sum(a)
|
||
}
|
||
denseComparison := func(a *Dense) interface{} {
|
||
return Sum(a)
|
||
}
|
||
testOneInputFunc(t, "Sum", f, denseComparison, sameAnswerFloatApproxTol(1e-12), isAnyType, isAnySize)
|
||
}
|
||
|
||
func TestTrace(t *testing.T) {
|
||
for _, test := range []struct {
|
||
a *Dense
|
||
trace float64
|
||
}{
|
||
{
|
||
a: NewDense(3, 3, []float64{1, 2, 3, 4, 5, 6, 7, 8, 9}),
|
||
trace: 15,
|
||
},
|
||
} {
|
||
trace := Trace(test.a)
|
||
if trace != test.trace {
|
||
t.Errorf("Trace mismatch. Want %v, got %v", test.trace, trace)
|
||
}
|
||
}
|
||
f := func(a Matrix) interface{} {
|
||
return Trace(a)
|
||
}
|
||
denseComparison := func(a *Dense) interface{} {
|
||
return Trace(a)
|
||
}
|
||
testOneInputFunc(t, "Trace", f, denseComparison, sameAnswerFloat, isAnyType, isSquare)
|
||
}
|