blob: 5e18d9adb03211e27d49fee3f6f1776c076e99dc [file] [log] [blame]
// Copyright ©2021 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 r3
import (
"math"
"testing"
"golang.org/x/exp/rand"
"gonum.org/v1/gonum/mat"
"gonum.org/v1/gonum/num/quat"
)
func TestMatAdd(t *testing.T) {
const tol = 1e-16
rnd := rand.New(rand.NewSource(1))
for tc := 0; tc < 20; tc++ {
a := randomMat(rnd)
b := randomMat(rnd)
var (
want mat.Dense
got Mat
)
want.Add(a, b)
got.Add(a, b)
if !mat.EqualApprox(&got, &want, tol) {
t.Errorf("unexpected result for matrix add:\ngot:\n%v\nwant:\n%v", mat.Formatted(&got), mat.Formatted(&want))
}
}
}
func TestMatSub(t *testing.T) {
const tol = 1e-16
rnd := rand.New(rand.NewSource(1))
for tc := 0; tc < 20; tc++ {
a := randomMat(rnd)
b := randomMat(rnd)
var (
want mat.Dense
got Mat
)
want.Sub(a, b)
got.Sub(a, b)
if !mat.EqualApprox(&got, &want, tol) {
t.Errorf("unexpected result for matrix subtract:\ngot:\n%v\nwant:\n%v", mat.Formatted(&got), mat.Formatted(&want))
}
}
}
func TestMatMul(t *testing.T) {
const tol = 1e-14
rnd := rand.New(rand.NewSource(1))
for tc := 0; tc < 20; tc++ {
a := randomMat(rnd)
b := randomMat(rnd)
var (
want mat.Dense
got Mat
)
want.Mul(a, b)
got.Mul(a, b)
if !mat.EqualApprox(&got, &want, tol) {
t.Errorf("unexpected result for matrix multiply:\ngot:\n%v\nwant:\n%v", mat.Formatted(&got), mat.Formatted(&want))
}
}
}
func TestMatScale(t *testing.T) {
const tol = 1e-16
rnd := rand.New(rand.NewSource(1))
for tc := 0; tc < 20; tc++ {
v := rnd.Float64()
a := randomMat(rnd)
var (
want mat.Dense
got Mat
)
want.Scale(v, a)
got.Scale(v, a)
if !mat.EqualApprox(&got, &want, tol) {
t.Errorf("unexpected result for matrix scale:\ngot:\n%v\nwant:\n%v", mat.Formatted(&got), mat.Formatted(&want))
}
}
}
func TestMatCloneFrom(t *testing.T) {
const tol = 1e-16
rnd := rand.New(rand.NewSource(1))
for tc := 0; tc < 20; tc++ {
want := randomMat(rnd)
got := NewMat(nil)
got.CloneFrom(want)
if !mat.EqualApprox(got, want, tol) {
t.Errorf("unexpected result from CloneFrom:\ngot:\n%v\nwant:\n%v", mat.Formatted(got), mat.Formatted(want))
}
}
}
func TestSkew(t *testing.T) {
const tol = 1e-16
rnd := rand.New(rand.NewSource(1))
for tc := 0; tc < 20; tc++ {
sk := NewMat(nil)
v1 := randomVec(rnd)
v2 := randomVec(rnd)
sk.Skew(v1)
want := Cross(v1, v2)
got := sk.MulVec(v2)
if d := Sub(want, got); Dot(d, d) > tol {
t.Errorf("r3.Cross(v1,v2) does not agree with r3.Skew(v1)*v2: got:%v want:%v", got, want)
}
}
}
func TestTranspose(t *testing.T) {
const tol = 1e-16
rnd := rand.New(rand.NewSource(1))
for tc := 0; tc < 20; tc++ {
d := mat.NewDense(3, 3, nil)
m := randomMat(rnd)
d.CloneFrom(m)
mt := m.T()
dt := d.T()
if !mat.Equal(mt, dt) {
t.Errorf("Dense.T() not equal to r3.Mat.T():\ngot:\n%v\nwant:\n%v", mat.Formatted(mt), mat.Formatted(dt))
}
vd := mat.NewVecDense(3, nil)
v := randomVec(rnd)
vd.SetVec(0, v.X)
vd.SetVec(1, v.Y)
vd.SetVec(2, v.Z)
vd.MulVec(dt, vd)
want := Vec{X: vd.AtVec(0), Y: vd.AtVec(1), Z: vd.AtVec(2)}
got := m.MulVecTrans(v)
if d := Sub(want, got); Dot(d, d) > tol {
t.Errorf("VecDense.MulVec(dense.T()) not agree with r3.Mat.MulVec(r3.Vec): got:%v want:%v", got, want)
}
}
}
func randomMat(rnd *rand.Rand) *Mat {
m := Mat{new(array)}
for iv := 0; iv < 9; iv++ {
i := iv / 3
j := iv % 3
m.Set(i, j, (rnd.Float64()-0.5)*20)
}
return &m
}
func randomVec(rnd *rand.Rand) (v Vec) {
v.X = (rnd.Float64() - 0.5) * 20
v.Y = (rnd.Float64() - 0.5) * 20
v.Z = (rnd.Float64() - 0.5) * 20
return v
}
func TestDet(t *testing.T) {
const tol = 1e-11
rnd := rand.New(rand.NewSource(1))
for tc := 0; tc < 20; tc++ {
m := randomMat(rnd)
got := m.Det()
want := mat.Det(m)
if math.Abs(got-want) > tol {
t.Errorf("r3.Mat.Det() not equal to mat.Det(). got %f, want %f", got, want)
}
}
}
func TestOuter(t *testing.T) {
rnd := rand.New(rand.NewSource(1))
for tc := 0; tc < 20; tc++ {
alpha := rnd.Float64()
d := mat.NewDense(3, 3, nil)
n := NewMat(nil)
v1 := randomVec(rnd)
v2 := randomVec(rnd)
d1 := mat.NewVecDense(3, []float64{v1.X, v1.Y, v1.Z})
d2 := mat.NewVecDense(3, []float64{v2.X, v2.Y, v2.Z})
d.Outer(alpha, d1, d2)
n.Outer(alpha, v1, v2)
if !mat.Equal(d, n) {
t.Error("matrices not equal")
}
}
}
func TestRotationMat(t *testing.T) {
const tol = 1e-14
rnd := rand.New(rand.NewSource(1))
for tc := 0; tc < 20; tc++ {
// Generate a random unit quaternion.
q := quat.Number{Real: rnd.NormFloat64(), Imag: rnd.NormFloat64(), Jmag: rnd.NormFloat64(), Kmag: rnd.NormFloat64()}
q = quat.Scale(1/quat.Abs(q), q)
// Convert it to a rotation matrix R.
r := Rotation(q).Mat()
// Check that the matrix has the determinant approximately equal to 1.
diff := math.Abs(r.Det() - 1)
if diff > tol {
t.Errorf("case %d: unexpected determinant of R; got=%f, want=1 (diff=%v)", tc, r.Det(), diff)
continue
}
// Generate a random point.
v := Vec{X: rnd.NormFloat64(), Y: rnd.NormFloat64(), Z: rnd.NormFloat64()}
// Rotate it using the formula q*v*conj(q).
want := Rotation(q).Rotate(v)
// Rotate it using the rotation matrix R.
got := r.MulVec(v)
// Check that |got-want| is small.
diff = Norm(Sub(got, want))
if diff > tol {
t.Errorf("case %d: unexpected result; got=%f, want=%f, (diff=%v)", tc, got, want, diff)
}
}
}
func BenchmarkQuat(b *testing.B) {
rnd := rand.New(rand.NewSource(1))
for i := 0; i < b.N; i++ {
q := quat.Number{Real: rnd.Float64(), Imag: rnd.Float64(), Jmag: rnd.Float64(), Kmag: rnd.Float64()}
if Rotation(q).Mat() == nil {
b.Fatal("nil return")
}
}
}
var scalarFields = []struct {
// This is the scalar field function.
field func(p Vec) float64
gradient func(p Vec) Vec
hessian func(p Vec) *Mat
}{
{
field: func(p Vec) float64 {
// 4*x^3 + 5*y^2 + 3*z^4
z2 := p.Z * p.Z
return 4*p.X*p.X*p.X + 5*p.Y*p.Y + 3*z2*z2
},
gradient: func(p Vec) Vec {
return Vec{X: 12 * p.X * p.X, Y: 10 * p.Y, Z: 12 * p.Z * p.Z * p.Z}
},
hessian: func(p Vec) *Mat {
return NewMat([]float64{
24 * p.X, 0, 0,
0, 10, 0,
0, 0, 36 * p.Z * p.Z,
})
},
},
{
field: func(p Vec) float64 {
// cos(x) * sin(z) * y^4
y2 := p.Y * p.Y
return math.Cos(p.X) * math.Sin(p.Z) * y2 * y2
},
gradient: func(p Vec) Vec {
y3 := p.Y * p.Y * p.Y
y4 := p.Y * y3
sx, cx := math.Sincos(p.X)
sz, cz := math.Sincos(p.Z)
return Vec{X: -y4 * sx * sz, Y: 4 * y3 * cx * sz, Z: y4 * cx * cz}
},
hessian: func(p Vec) *Mat {
y3 := p.Y * p.Y * p.Y
y4 := y3 * p.Y
sx, cx := math.Sincos(p.X)
sz, cz := math.Sincos(p.Z)
return NewMat([]float64{
-y4 * cx * sz, -4 * y3 * sx * sz, -y4 * sx * cz,
-4 * y3 * sx * sz, 12 * p.Y * p.Y * cx * sz, 4 * y3 * cx * cz,
-y4 * sx * cz, 4 * y3 * cx * cz, -y4 * cx * sz,
})
},
},
}
func TestMatHessian(t *testing.T) {
const (
tol = 1e-5
h = 8e-4
)
step := Vec{X: h, Y: h, Z: h}
rnd := rand.New(rand.NewSource(1))
for _, test := range scalarFields {
for i := 0; i < 30; i++ {
p := randomVec(rnd)
got := NewMat(nil)
got.Hessian(p, step, test.field)
want := test.hessian(p)
if !mat.EqualApprox(got, want, tol) {
t.Errorf("matrices not equal within tol\ngot: %v\nwant: %v",
mat.Formatted(got), mat.Formatted(want))
}
}
}
}
func TestMatJacobian(t *testing.T) {
const (
tol = 1e-5
h = 8e-4
)
step := Vec{X: h, Y: h, Z: h}
rnd := rand.New(rand.NewSource(1))
for _, test := range vectorFields {
for i := 0; i < 1; i++ {
p := randomVec(rnd)
got := NewMat(nil)
got.Jacobian(p, step, test.field)
want := test.jacobian(p)
if !mat.EqualApprox(got, want, tol) {
t.Errorf("matrices not equal within tol\ngot: %v\nwant: %v",
mat.Formatted(got), mat.Formatted(want))
}
}
}
}