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 }