gonum.org/v1/gonum@v0.14.0/spatial/r3/mat_test.go (about)

     1  // Copyright ©2021 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 r3
     6  
     7  import (
     8  	"math"
     9  	"testing"
    10  
    11  	"golang.org/x/exp/rand"
    12  
    13  	"gonum.org/v1/gonum/mat"
    14  	"gonum.org/v1/gonum/num/quat"
    15  )
    16  
    17  func TestMatAdd(t *testing.T) {
    18  	const tol = 1e-16
    19  	rnd := rand.New(rand.NewSource(1))
    20  	for tc := 0; tc < 20; tc++ {
    21  		a := randomMat(rnd)
    22  		b := randomMat(rnd)
    23  		var (
    24  			want mat.Dense
    25  			got  Mat
    26  		)
    27  		want.Add(a, b)
    28  		got.Add(a, b)
    29  		if !mat.EqualApprox(&got, &want, tol) {
    30  			t.Errorf("unexpected result for matrix add:\ngot:\n%v\nwant:\n%v", mat.Formatted(&got), mat.Formatted(&want))
    31  		}
    32  	}
    33  }
    34  
    35  func TestMatSub(t *testing.T) {
    36  	const tol = 1e-16
    37  	rnd := rand.New(rand.NewSource(1))
    38  	for tc := 0; tc < 20; tc++ {
    39  		a := randomMat(rnd)
    40  		b := randomMat(rnd)
    41  		var (
    42  			want mat.Dense
    43  			got  Mat
    44  		)
    45  		want.Sub(a, b)
    46  		got.Sub(a, b)
    47  		if !mat.EqualApprox(&got, &want, tol) {
    48  			t.Errorf("unexpected result for matrix subtract:\ngot:\n%v\nwant:\n%v", mat.Formatted(&got), mat.Formatted(&want))
    49  		}
    50  	}
    51  }
    52  
    53  func TestMatMul(t *testing.T) {
    54  	const tol = 1e-14
    55  	rnd := rand.New(rand.NewSource(1))
    56  	for tc := 0; tc < 20; tc++ {
    57  		a := randomMat(rnd)
    58  		b := randomMat(rnd)
    59  		var (
    60  			want mat.Dense
    61  			got  Mat
    62  		)
    63  		want.Mul(a, b)
    64  		got.Mul(a, b)
    65  		if !mat.EqualApprox(&got, &want, tol) {
    66  			t.Errorf("unexpected result for matrix multiply:\ngot:\n%v\nwant:\n%v", mat.Formatted(&got), mat.Formatted(&want))
    67  		}
    68  	}
    69  }
    70  
    71  func TestMatScale(t *testing.T) {
    72  	const tol = 1e-16
    73  	rnd := rand.New(rand.NewSource(1))
    74  	for tc := 0; tc < 20; tc++ {
    75  		v := rnd.Float64()
    76  		a := randomMat(rnd)
    77  		var (
    78  			want mat.Dense
    79  			got  Mat
    80  		)
    81  		want.Scale(v, a)
    82  		got.Scale(v, a)
    83  		if !mat.EqualApprox(&got, &want, tol) {
    84  			t.Errorf("unexpected result for matrix scale:\ngot:\n%v\nwant:\n%v", mat.Formatted(&got), mat.Formatted(&want))
    85  		}
    86  	}
    87  }
    88  
    89  func TestMatCloneFrom(t *testing.T) {
    90  	const tol = 1e-16
    91  	rnd := rand.New(rand.NewSource(1))
    92  	for tc := 0; tc < 20; tc++ {
    93  		want := randomMat(rnd)
    94  		got := NewMat(nil)
    95  		got.CloneFrom(want)
    96  		if !mat.EqualApprox(got, want, tol) {
    97  			t.Errorf("unexpected result from CloneFrom:\ngot:\n%v\nwant:\n%v", mat.Formatted(got), mat.Formatted(want))
    98  		}
    99  	}
   100  }
   101  
   102  func TestSkew(t *testing.T) {
   103  	const tol = 1e-16
   104  	rnd := rand.New(rand.NewSource(1))
   105  	for tc := 0; tc < 20; tc++ {
   106  		sk := NewMat(nil)
   107  		v1 := randomVec(rnd)
   108  		v2 := randomVec(rnd)
   109  		sk.Skew(v1)
   110  		want := Cross(v1, v2)
   111  		got := sk.MulVec(v2)
   112  		if d := Sub(want, got); Dot(d, d) > tol {
   113  			t.Errorf("r3.Cross(v1,v2) does not agree with r3.Skew(v1)*v2: got:%v want:%v", got, want)
   114  		}
   115  	}
   116  }
   117  
   118  func TestTranspose(t *testing.T) {
   119  	const tol = 1e-16
   120  	rnd := rand.New(rand.NewSource(1))
   121  	for tc := 0; tc < 20; tc++ {
   122  		d := mat.NewDense(3, 3, nil)
   123  		m := randomMat(rnd)
   124  		d.CloneFrom(m)
   125  		mt := m.T()
   126  		dt := d.T()
   127  		if !mat.Equal(mt, dt) {
   128  			t.Errorf("Dense.T() not equal to r3.Mat.T():\ngot:\n%v\nwant:\n%v", mat.Formatted(mt), mat.Formatted(dt))
   129  		}
   130  		vd := mat.NewVecDense(3, nil)
   131  		v := randomVec(rnd)
   132  		vd.SetVec(0, v.X)
   133  		vd.SetVec(1, v.Y)
   134  		vd.SetVec(2, v.Z)
   135  		vd.MulVec(dt, vd)
   136  		want := Vec{X: vd.AtVec(0), Y: vd.AtVec(1), Z: vd.AtVec(2)}
   137  		got := m.MulVecTrans(v)
   138  		if d := Sub(want, got); Dot(d, d) > tol {
   139  			t.Errorf("VecDense.MulVec(dense.T()) not agree with r3.Mat.MulVec(r3.Vec): got:%v want:%v", got, want)
   140  		}
   141  	}
   142  }
   143  
   144  func randomMat(rnd *rand.Rand) *Mat {
   145  	m := Mat{new(array)}
   146  	for iv := 0; iv < 9; iv++ {
   147  		i := iv / 3
   148  		j := iv % 3
   149  		m.Set(i, j, (rnd.Float64()-0.5)*20)
   150  	}
   151  	return &m
   152  }
   153  
   154  func randomVec(rnd *rand.Rand) (v Vec) {
   155  	v.X = (rnd.Float64() - 0.5) * 20
   156  	v.Y = (rnd.Float64() - 0.5) * 20
   157  	v.Z = (rnd.Float64() - 0.5) * 20
   158  	return v
   159  }
   160  
   161  func TestDet(t *testing.T) {
   162  	const tol = 1e-11
   163  	rnd := rand.New(rand.NewSource(1))
   164  	for tc := 0; tc < 20; tc++ {
   165  		m := randomMat(rnd)
   166  		got := m.Det()
   167  		want := mat.Det(m)
   168  		if math.Abs(got-want) > tol {
   169  			t.Errorf("r3.Mat.Det() not equal to mat.Det(). got %f, want %f", got, want)
   170  		}
   171  	}
   172  }
   173  
   174  func TestOuter(t *testing.T) {
   175  	rnd := rand.New(rand.NewSource(1))
   176  	for tc := 0; tc < 20; tc++ {
   177  		alpha := rnd.Float64()
   178  		d := mat.NewDense(3, 3, nil)
   179  		n := NewMat(nil)
   180  		v1 := randomVec(rnd)
   181  		v2 := randomVec(rnd)
   182  		d1 := mat.NewVecDense(3, []float64{v1.X, v1.Y, v1.Z})
   183  		d2 := mat.NewVecDense(3, []float64{v2.X, v2.Y, v2.Z})
   184  		d.Outer(alpha, d1, d2)
   185  		n.Outer(alpha, v1, v2)
   186  		if !mat.Equal(d, n) {
   187  			t.Error("matrices not equal")
   188  		}
   189  	}
   190  }
   191  
   192  func TestRotationMat(t *testing.T) {
   193  	const tol = 1e-14
   194  	rnd := rand.New(rand.NewSource(1))
   195  
   196  	for tc := 0; tc < 20; tc++ {
   197  		// Generate a random unit quaternion.
   198  		q := quat.Number{Real: rnd.NormFloat64(), Imag: rnd.NormFloat64(), Jmag: rnd.NormFloat64(), Kmag: rnd.NormFloat64()}
   199  		q = quat.Scale(1/quat.Abs(q), q)
   200  
   201  		// Convert it to a rotation matrix R.
   202  		r := Rotation(q).Mat()
   203  
   204  		// Check that the matrix has the determinant approximately equal to 1.
   205  		diff := math.Abs(r.Det() - 1)
   206  		if diff > tol {
   207  			t.Errorf("case %d: unexpected determinant of R; got=%f, want=1 (diff=%v)", tc, r.Det(), diff)
   208  			continue
   209  		}
   210  
   211  		// Generate a random point.
   212  		v := Vec{X: rnd.NormFloat64(), Y: rnd.NormFloat64(), Z: rnd.NormFloat64()}
   213  		// Rotate it using the formula q*v*conj(q).
   214  		want := Rotation(q).Rotate(v)
   215  		// Rotate it using the rotation matrix R.
   216  		got := r.MulVec(v)
   217  		// Check that |got-want| is small.
   218  		diff = Norm(Sub(got, want))
   219  		if diff > tol {
   220  			t.Errorf("case %d: unexpected result; got=%f, want=%f, (diff=%v)", tc, got, want, diff)
   221  		}
   222  	}
   223  }
   224  
   225  func BenchmarkQuat(b *testing.B) {
   226  	rnd := rand.New(rand.NewSource(1))
   227  	for i := 0; i < b.N; i++ {
   228  		q := quat.Number{Real: rnd.Float64(), Imag: rnd.Float64(), Jmag: rnd.Float64(), Kmag: rnd.Float64()}
   229  		if Rotation(q).Mat() == nil {
   230  			b.Fatal("nil return")
   231  		}
   232  	}
   233  }
   234  
   235  var scalarFields = []struct {
   236  	// This is the scalar field function.
   237  	field    func(p Vec) float64
   238  	gradient func(p Vec) Vec
   239  	hessian  func(p Vec) *Mat
   240  }{
   241  	{
   242  		field: func(p Vec) float64 {
   243  			// 4*x^3 + 5*y^2 + 3*z^4
   244  			z2 := p.Z * p.Z
   245  			return 4*p.X*p.X*p.X + 5*p.Y*p.Y + 3*z2*z2
   246  		},
   247  		gradient: func(p Vec) Vec {
   248  			return Vec{X: 12 * p.X * p.X, Y: 10 * p.Y, Z: 12 * p.Z * p.Z * p.Z}
   249  		},
   250  		hessian: func(p Vec) *Mat {
   251  			return NewMat([]float64{
   252  				24 * p.X, 0, 0,
   253  				0, 10, 0,
   254  				0, 0, 36 * p.Z * p.Z,
   255  			})
   256  		},
   257  	},
   258  	{
   259  		field: func(p Vec) float64 {
   260  			// cos(x) * sin(z) * y^4
   261  			y2 := p.Y * p.Y
   262  			return math.Cos(p.X) * math.Sin(p.Z) * y2 * y2
   263  		},
   264  		gradient: func(p Vec) Vec {
   265  			y3 := p.Y * p.Y * p.Y
   266  			y4 := p.Y * y3
   267  			sx, cx := math.Sincos(p.X)
   268  			sz, cz := math.Sincos(p.Z)
   269  			return Vec{X: -y4 * sx * sz, Y: 4 * y3 * cx * sz, Z: y4 * cx * cz}
   270  		},
   271  		hessian: func(p Vec) *Mat {
   272  			y3 := p.Y * p.Y * p.Y
   273  			y4 := y3 * p.Y
   274  			sx, cx := math.Sincos(p.X)
   275  			sz, cz := math.Sincos(p.Z)
   276  			return NewMat([]float64{
   277  				-y4 * cx * sz, -4 * y3 * sx * sz, -y4 * sx * cz,
   278  				-4 * y3 * sx * sz, 12 * p.Y * p.Y * cx * sz, 4 * y3 * cx * cz,
   279  				-y4 * sx * cz, 4 * y3 * cx * cz, -y4 * cx * sz,
   280  			})
   281  		},
   282  	},
   283  }
   284  
   285  func TestMatHessian(t *testing.T) {
   286  	const (
   287  		tol = 1e-5
   288  		h   = 8e-4
   289  	)
   290  	step := Vec{X: h, Y: h, Z: h}
   291  	rnd := rand.New(rand.NewSource(1))
   292  	for _, test := range scalarFields {
   293  		for i := 0; i < 30; i++ {
   294  			p := randomVec(rnd)
   295  			got := NewMat(nil)
   296  			got.Hessian(p, step, test.field)
   297  			want := test.hessian(p)
   298  			if !mat.EqualApprox(got, want, tol) {
   299  				t.Errorf("matrices not equal within tol\ngot:  %v\nwant:  %v",
   300  					mat.Formatted(got), mat.Formatted(want))
   301  			}
   302  		}
   303  	}
   304  }
   305  
   306  func TestMatJacobian(t *testing.T) {
   307  	const (
   308  		tol = 1e-5
   309  		h   = 8e-4
   310  	)
   311  	step := Vec{X: h, Y: h, Z: h}
   312  	rnd := rand.New(rand.NewSource(1))
   313  	for _, test := range vectorFields {
   314  		for i := 0; i < 1; i++ {
   315  			p := randomVec(rnd)
   316  			got := NewMat(nil)
   317  			got.Jacobian(p, step, test.field)
   318  			want := test.jacobian(p)
   319  			if !mat.EqualApprox(got, want, tol) {
   320  				t.Errorf("matrices not equal within tol\ngot:  %v\nwant:  %v",
   321  					mat.Formatted(got), mat.Formatted(want))
   322  			}
   323  		}
   324  	}
   325  }