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

     1  // Copyright ©2020 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/floats/scalar"
    14  	"gonum.org/v1/gonum/mat"
    15  )
    16  
    17  func TestAdd(t *testing.T) {
    18  	for _, test := range []struct {
    19  		v1, v2 Vec
    20  		want   Vec
    21  	}{
    22  		{Vec{0, 0, 0}, Vec{0, 0, 0}, Vec{0, 0, 0}},
    23  		{Vec{1, 0, 0}, Vec{0, 0, 0}, Vec{1, 0, 0}},
    24  		{Vec{1, 2, 3}, Vec{4, 5, 7}, Vec{5, 7, 10}},
    25  		{Vec{1, -3, 5}, Vec{1, -6, -6}, Vec{2, -9, -1}},
    26  		{Vec{1, 2, 3}, Vec{-1, -2, -3}, Vec{}},
    27  	} {
    28  		got := Add(test.v1, test.v2)
    29  		if got != test.want {
    30  			t.Errorf(
    31  				"error: %v + %v: got=%v, want=%v",
    32  				test.v1, test.v2, got, test.want,
    33  			)
    34  		}
    35  	}
    36  }
    37  
    38  func TestSub(t *testing.T) {
    39  	for _, test := range []struct {
    40  		v1, v2 Vec
    41  		want   Vec
    42  	}{
    43  		{Vec{0, 0, 0}, Vec{0, 0, 0}, Vec{0, 0, 0}},
    44  		{Vec{1, 0, 0}, Vec{0, 0, 0}, Vec{1, 0, 0}},
    45  		{Vec{1, 2, 3}, Vec{4, 5, 7}, Vec{-3, -3, -4}},
    46  		{Vec{1, -3, 5}, Vec{1, -6, -6}, Vec{0, 3, 11}},
    47  		{Vec{1, 2, 3}, Vec{1, 2, 3}, Vec{}},
    48  	} {
    49  		got := Sub(test.v1, test.v2)
    50  		if got != test.want {
    51  			t.Errorf(
    52  				"error: %v - %v: got=%v, want=%v",
    53  				test.v1, test.v2, got, test.want,
    54  			)
    55  		}
    56  	}
    57  }
    58  
    59  func TestScale(t *testing.T) {
    60  	for _, test := range []struct {
    61  		a    float64
    62  		v    Vec
    63  		want Vec
    64  	}{
    65  		{3, Vec{0, 0, 0}, Vec{0, 0, 0}},
    66  		{1, Vec{1, 0, 0}, Vec{1, 0, 0}},
    67  		{0, Vec{1, 0, 0}, Vec{0, 0, 0}},
    68  		{3, Vec{1, 0, 0}, Vec{3, 0, 0}},
    69  		{-1, Vec{1, -3, 5}, Vec{-1, 3, -5}},
    70  		{2, Vec{1, -3, 5}, Vec{2, -6, 10}},
    71  		{10, Vec{1, 2, 3}, Vec{10, 20, 30}},
    72  	} {
    73  		got := Scale(test.a, test.v)
    74  		if got != test.want {
    75  			t.Errorf(
    76  				"error: %v * %v: got=%v, want=%v",
    77  				test.a, test.v, got, test.want)
    78  		}
    79  	}
    80  }
    81  
    82  func TestDot(t *testing.T) {
    83  	for _, test := range []struct {
    84  		u, v Vec
    85  		want float64
    86  	}{
    87  		{Vec{1, 2, 3}, Vec{1, 2, 3}, 14},
    88  		{Vec{1, 0, 0}, Vec{1, 0, 0}, 1},
    89  		{Vec{1, 0, 0}, Vec{0, 1, 0}, 0},
    90  		{Vec{1, 0, 0}, Vec{0, 1, 1}, 0},
    91  		{Vec{1, 1, 1}, Vec{-1, -1, -1}, -3},
    92  		{Vec{1, 2, 2}, Vec{-0.3, 0.4, -1.2}, -1.9},
    93  	} {
    94  		{
    95  			got := Dot(test.u, test.v)
    96  			if got != test.want {
    97  				t.Errorf(
    98  					"error: %v · %v: got=%v, want=%v",
    99  					test.u, test.v, got, test.want,
   100  				)
   101  			}
   102  		}
   103  		{
   104  			got := Dot(test.v, test.u)
   105  			if got != test.want {
   106  				t.Errorf(
   107  					"error: %v · %v: got=%v, want=%v",
   108  					test.v, test.u, got, test.want,
   109  				)
   110  			}
   111  		}
   112  	}
   113  }
   114  
   115  func TestCross(t *testing.T) {
   116  	for _, test := range []struct {
   117  		v1, v2, want Vec
   118  	}{
   119  		{Vec{1, 0, 0}, Vec{1, 0, 0}, Vec{0, 0, 0}},
   120  		{Vec{1, 0, 0}, Vec{0, 1, 0}, Vec{0, 0, 1}},
   121  		{Vec{0, 1, 0}, Vec{1, 0, 0}, Vec{0, 0, -1}},
   122  		{Vec{1, 2, 3}, Vec{-4, 5, -6}, Vec{-27, -6, 13}},
   123  		{Vec{1, 2, 3}, Vec{1, 2, 3}, Vec{}},
   124  		{Vec{1, 2, 3}, Vec{2, 3, 4}, Vec{-1, 2, -1}},
   125  	} {
   126  		got := Cross(test.v1, test.v2)
   127  		if got != test.want {
   128  			t.Errorf(
   129  				"error: %v × %v = %v, want %v",
   130  				test.v1, test.v2, got, test.want,
   131  			)
   132  		}
   133  	}
   134  }
   135  
   136  func TestNorm(t *testing.T) {
   137  	for _, test := range []struct {
   138  		v    Vec
   139  		want float64
   140  	}{
   141  		{Vec{0, 0, 0}, 0},
   142  		{Vec{0, 1, 0}, 1},
   143  		{Vec{3, -4, 12}, 13},
   144  		{Vec{1, 1e-16, 1e-32}, 1},
   145  		{Vec{-0, 4.3145006366056343748277397783556100978621924913975e-196, 4.3145006366056343748277397783556100978621924913975e-196}, 6.101625315155041e-196},
   146  	} {
   147  		if got, want := Norm(test.v), test.want; got != want {
   148  			t.Errorf("|%v| = %v, want %v", test.v, got, want)
   149  		}
   150  	}
   151  }
   152  
   153  func TestNorm2(t *testing.T) {
   154  	for _, test := range []struct {
   155  		v    Vec
   156  		want float64
   157  	}{
   158  		{Vec{0, 0, 0}, 0},
   159  		{Vec{0, 1, 0}, 1},
   160  		{Vec{1, 1, 1}, 3},
   161  		{Vec{1, 2, 3}, 14},
   162  		{Vec{3, -4, 12}, 169},
   163  		{Vec{1, 1e-16, 1e-32}, 1},
   164  		// This will underflow and return zero.
   165  		{Vec{-0, 4.3145006366056343748277397783556100978621924913975e-196, 4.3145006366056343748277397783556100978621924913975e-196}, 0},
   166  	} {
   167  		if got, want := Norm2(test.v), test.want; got != want {
   168  			t.Errorf("|%v|^2 = %v, want %v", test.v, got, want)
   169  		}
   170  	}
   171  }
   172  
   173  func TestUnit(t *testing.T) {
   174  	for _, test := range []struct {
   175  		v, want Vec
   176  	}{
   177  		{Vec{}, Vec{math.NaN(), math.NaN(), math.NaN()}},
   178  		{Vec{1, 0, 0}, Vec{1, 0, 0}},
   179  		{Vec{0, 1, 0}, Vec{0, 1, 0}},
   180  		{Vec{0, 0, 1}, Vec{0, 0, 1}},
   181  		{Vec{1, 1, 1}, Vec{1. / math.Sqrt(3), 1. / math.Sqrt(3), 1. / math.Sqrt(3)}},
   182  		{Vec{1, 1e-16, 1e-32}, Vec{1, 1e-16, 1e-32}},
   183  	} {
   184  		got := Unit(test.v)
   185  		if !vecEqual(got, test.want) {
   186  			t.Errorf(
   187  				"Normalize(%v) = %v, want %v",
   188  				test.v, got, test.want,
   189  			)
   190  		}
   191  		if test.v == (Vec{}) {
   192  			return
   193  		}
   194  		if n, want := Norm(got), 1.0; n != want {
   195  			t.Errorf("|%v| = %v, want 1", got, n)
   196  		}
   197  	}
   198  }
   199  
   200  func TestCos(t *testing.T) {
   201  	for _, test := range []struct {
   202  		v1, v2 Vec
   203  		want   float64
   204  	}{
   205  		{Vec{1, 1, 1}, Vec{1, 1, 1}, 1},
   206  		{Vec{1, 1, 1}, Vec{-1, -1, -1}, -1},
   207  		{Vec{1, 1, 1}, Vec{1, -1, 1}, 1.0 / 3},
   208  		{Vec{1, 0, 0}, Vec{1, 0, 0}, 1},
   209  		{Vec{1, 0, 0}, Vec{0, 1, 0}, 0},
   210  		{Vec{1, 0, 0}, Vec{0, 1, 1}, 0},
   211  		{Vec{1, 0, 0}, Vec{-1, 0, 0}, -1},
   212  	} {
   213  		tol := 1e-14
   214  		got := Cos(test.v1, test.v2)
   215  		if !scalar.EqualWithinAbs(got, test.want, tol) {
   216  			t.Errorf("cos(%v, %v)= %v, want %v",
   217  				test.v1, test.v2, got, test.want,
   218  			)
   219  		}
   220  	}
   221  }
   222  
   223  func TestRotate(t *testing.T) {
   224  	const tol = 1e-14
   225  	for _, test := range []struct {
   226  		v, axis Vec
   227  		alpha   float64
   228  		want    Vec
   229  	}{
   230  		{Vec{1, 0, 0}, Vec{1, 0, 0}, math.Pi / 2, Vec{1, 0, 0}},
   231  		{Vec{1, 0, 0}, Vec{1, 0, 0}, 0, Vec{1, 0, 0}},
   232  		{Vec{1, 0, 0}, Vec{1, 0, 0}, 2 * math.Pi, Vec{1, 0, 0}},
   233  		{Vec{1, 0, 0}, Vec{0, 0, 0}, math.Pi / 2, Vec{math.NaN(), math.NaN(), math.NaN()}},
   234  		{Vec{1, 0, 0}, Vec{0, 1, 0}, math.Pi / 2, Vec{0, 0, -1}},
   235  		{Vec{1, 0, 0}, Vec{0, 1, 0}, math.Pi, Vec{-1, 0, 0}},
   236  		{Vec{2, 0, 0}, Vec{0, 1, 0}, math.Pi, Vec{-2, 0, 0}},
   237  		{Vec{1, 2, 3}, Vec{1, 1, 1}, 2. / 3. * math.Pi, Vec{3, 1, 2}},
   238  	} {
   239  		got := Rotate(test.v, test.alpha, test.axis)
   240  		if !vecApproxEqual(got, test.want, tol) {
   241  			t.Errorf(
   242  				"quat rotate(%v, %v, %v)= %v, want=%v",
   243  				test.v, test.alpha, test.axis, got, test.want,
   244  			)
   245  		}
   246  
   247  		var gotv mat.VecDense
   248  		gotv.MulVec(NewRotation(test.alpha, test.axis).Mat(), vecDense(test.v))
   249  		got = vec(gotv)
   250  		if !vecApproxEqual(got, test.want, tol) {
   251  			t.Errorf(
   252  				"matrix rotate(%v, %v, %v)= %v, want=%v",
   253  				test.v, test.alpha, test.axis, got, test.want,
   254  			)
   255  		}
   256  	}
   257  }
   258  
   259  var vectorFields = []struct {
   260  	field      func(Vec) Vec
   261  	divergence func(Vec) float64
   262  	jacobian   func(Vec) *Mat
   263  }{
   264  	{
   265  		field: func(v Vec) Vec {
   266  			// (x*y*z, y*z, z*x)
   267  			return Vec{X: v.X * v.Y * v.Z, Y: v.Y * v.Z, Z: v.Z * v.X}
   268  		},
   269  		divergence: func(v Vec) float64 {
   270  			return v.X + v.Y*v.Z + v.Z
   271  		},
   272  		jacobian: func(v Vec) *Mat {
   273  			return NewMat([]float64{
   274  				v.Y * v.Z, v.X * v.Z, v.X * v.Y,
   275  				0, v.Z, v.Y,
   276  				v.Z, 0, v.X,
   277  			})
   278  		},
   279  	},
   280  	{
   281  		field: func(v Vec) Vec {
   282  			// (x*y*z*cos(y), y*z+sin(x), z*x*sin(y))
   283  			sx := math.Sin(v.X)
   284  			sy, cy := math.Sincos(v.Y)
   285  			return Vec{
   286  				X: v.X * v.Y * v.Z * cy,
   287  				Y: v.Y*v.Z + sx,
   288  				Z: v.Z * v.X * sy,
   289  			}
   290  		},
   291  		divergence: func(v Vec) float64 {
   292  			sy, cy := math.Sincos(v.Y)
   293  			return v.X*sy + v.Y*v.Z*cy + v.Z
   294  		},
   295  		jacobian: func(v Vec) *Mat {
   296  			cx := math.Cos(v.X)
   297  			sy, cy := math.Sincos(v.Y)
   298  			return NewMat([]float64{
   299  				v.Y * v.Z * cy, v.X*v.Z*cy - v.X*v.Y*v.Z*sy, v.X * v.Y * cy,
   300  				cx, v.Z, v.Y,
   301  				v.Z * sy, v.X * v.Z * cy, v.X * sy,
   302  			})
   303  		},
   304  	},
   305  }
   306  
   307  func TestDivergence(t *testing.T) {
   308  	const (
   309  		tol = 1e-10
   310  		h   = 1e-2
   311  	)
   312  	step := Vec{X: h, Y: h, Z: h}
   313  	rnd := rand.New(rand.NewSource(1))
   314  	for _, test := range vectorFields {
   315  		for i := 0; i < 30; i++ {
   316  			p := randomVec(rnd)
   317  			got := Divergence(p, step, test.field)
   318  			want := test.divergence(p)
   319  			if math.Abs(got-want) > tol {
   320  				t.Errorf("result out of tolerance. got %v, want %v", got, want)
   321  			}
   322  		}
   323  	}
   324  }
   325  
   326  func TestGradient(t *testing.T) {
   327  	const (
   328  		tol = 1e-6
   329  		h   = 1e-5
   330  	)
   331  	step := Vec{X: h, Y: h, Z: h}
   332  	rnd := rand.New(rand.NewSource(1))
   333  	for _, test := range scalarFields {
   334  		for i := 0; i < 30; i++ {
   335  			p := randomVec(rnd)
   336  			got := Gradient(p, step, test.field)
   337  			want := test.gradient(p)
   338  			if !vecApproxEqual(got, want, tol) {
   339  				t.Errorf("result out of tolerance. got %v, want %v", got, want)
   340  			}
   341  		}
   342  	}
   343  }
   344  
   345  func vecDense(v Vec) *mat.VecDense {
   346  	return mat.NewVecDense(3, []float64{v.X, v.Y, v.Z})
   347  }
   348  
   349  func vec(v mat.VecDense) Vec {
   350  	if v.Len() != 3 {
   351  		panic(mat.ErrShape)
   352  	}
   353  	return Vec{v.AtVec(0), v.AtVec(1), v.AtVec(2)}
   354  }
   355  
   356  func vecIsNaN(v Vec) bool {
   357  	return math.IsNaN(v.X) && math.IsNaN(v.Y) && math.IsNaN(v.Z)
   358  }
   359  
   360  func vecIsNaNAny(v Vec) bool {
   361  	return math.IsNaN(v.X) || math.IsNaN(v.Y) || math.IsNaN(v.Z)
   362  }
   363  
   364  func vecEqual(a, b Vec) bool {
   365  	if vecIsNaNAny(a) || vecIsNaNAny(b) {
   366  		return vecIsNaN(a) && vecIsNaN(b)
   367  	}
   368  	return a == b
   369  }
   370  
   371  func vecApproxEqual(a, b Vec, tol float64) bool {
   372  	if vecIsNaNAny(a) || vecIsNaNAny(b) {
   373  		return vecIsNaN(a) && vecIsNaN(b)
   374  	}
   375  	return scalar.EqualWithinAbs(a.X, b.X, tol) &&
   376  		scalar.EqualWithinAbs(a.Y, b.Y, tol) &&
   377  		scalar.EqualWithinAbs(a.Z, b.Z, tol)
   378  }