test_quaternion.py
Go to the documentation of this file.
1 #!/usr/bin python
2 # -*- coding: utf-8 -*-
3 """
4 This file is part of the pyquaternion python module
5 
6 Author: Kieran Wynn
7 Website: https://github.com/KieranWynn/pyquaternion
8 Documentation: http://kieranwynn.github.io/pyquaternion/
9 
10 Version: 1.0.0
11 License: The MIT License (MIT)
12 
13 Copyright (c) 2015 Kieran Wynn
14 
15 Permission is hereby granted, free of charge, to any person obtaining a copy
16 of this software and associated documentation files (the "Software"), to deal
17 in the Software without restriction, including without limitation the rights
18 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19 copies of the Software, and to permit persons to whom the Software is
20 furnished to do so, subject to the following conditions:
21 
22 The above copyright notice and this permission notice shall be included in all
23 copies or substantial portions of the Software.
24 
25 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31 SOFTWARE.
32 
33 test_quaternion.py - Unit test for quaternion module
34 
35 """
36 
37 import unittest
38 from math import pi, sin, cos
39 from random import random
40 
41 import numpy as np
42 
43 from pyquaternion import Quaternion
44 
45 
46 ALMOST_EQUAL_TOLERANCE = 13
47 
49  return tuple(np.random.uniform(-1, 1, 4))
50 
51 class TestQuaternionInitialisation(unittest.TestCase):
52 
53  def test_init_default(self):
54  q = Quaternion()
55  self.assertIsInstance(q, Quaternion)
56  self.assertEqual(q, Quaternion(1., 0., 0., 0.))
57 
58  def test_init_copy(self):
59  q1 = Quaternion.random()
60  q2 = Quaternion(q1)
61  self.assertIsInstance(q2, Quaternion)
62  self.assertEqual(q2, q1)
63  with self.assertRaises(TypeError):
64  q3 = Quaternion(None)
65  with self.assertRaises(ValueError):
66  q4 = Quaternion("String")
67 
68  def test_init_random(self):
69  r1 = Quaternion.random()
70  r2 = Quaternion.random()
71  self.assertAlmostEqual(r1.norm, 1.0, ALMOST_EQUAL_TOLERANCE)
72  self.assertIsInstance(r1, Quaternion)
73  #self.assertNotEqual(r1, r2) #TODO, this *may* fail at random
74 
76  s = random()
77  q1 = Quaternion(s)
78  q2 = Quaternion(repr(s))
79  self.assertIsInstance(q1, Quaternion)
80  self.assertIsInstance(q2, Quaternion)
81  self.assertEqual(q1, Quaternion(s, 0.0, 0.0, 0.0))
82  self.assertEqual(q2, Quaternion(s, 0.0, 0.0, 0.0))
83  with self.assertRaises(TypeError):
84  q = Quaternion(None)
85  with self.assertRaises(ValueError):
86  q = Quaternion("String")
87 
89  a, b, c, d = randomElements()
90  q1 = Quaternion(a, b, c, d)
91  q2 = Quaternion(repr(a), repr(b), repr(c), repr(d))
92  q3 = Quaternion(a, repr(b), c, d)
93  self.assertIsInstance(q1, Quaternion)
94  self.assertIsInstance(q2, Quaternion)
95  self.assertIsInstance(q3, Quaternion)
96  self.assertTrue(np.array_equal(q1.q, [a, b, c, d]))
97  self.assertEqual(q1, q2)
98  self.assertEqual(q2, q3)
99  with self.assertRaises(TypeError):
100  q = Quaternion(None, b, c, d)
101  with self.assertRaises(ValueError):
102  q = Quaternion(a, b, "String", d)
103  with self.assertRaises(ValueError):
104  q = Quaternion(a, b, c)
105  with self.assertRaises(ValueError):
106  q = Quaternion(a, b, c, d, random())
107 
109  r = randomElements()
110  a = np.array(r)
111  q = Quaternion(a)
112  self.assertIsInstance(q, Quaternion)
113  self.assertEqual(q, Quaternion(*r))
114  with self.assertRaises(ValueError):
115  q = Quaternion(a[1:4]) # 3-vector
116  with self.assertRaises(ValueError):
117  q = Quaternion(np.hstack((a, a))) # 8-vector
118  with self.assertRaises(ValueError):
119  q = Quaternion(np.array([a, a])) # 2x4-
120  with self.assertRaises(TypeError):
121  q = Quaternion(np.array([None, None, None, None]))
122 
124  t = randomElements()
125  q = Quaternion(t)
126  self.assertIsInstance(q, Quaternion)
127  self.assertEqual(q, Quaternion(*t))
128  with self.assertRaises(ValueError):
129  q = Quaternion(t[1:4]) # 3-tuple
130  with self.assertRaises(ValueError):
131  q = Quaternion(t + t) # 8-tuple
132  with self.assertRaises(ValueError):
133  q = Quaternion((t, t)) # 2x4-tuple
134  with self.assertRaises(TypeError):
135  q = Quaternion((None, None, None, None))
136 
138  r = randomElements()
139  l = list(r)
140  q = Quaternion(l)
141  self.assertIsInstance(q, Quaternion)
142  self.assertEqual(q, Quaternion(*l))
143  with self.assertRaises(ValueError):
144  q = Quaternion(l[1:4]) # 3-list
145  with self.assertRaises(ValueError):
146  q = Quaternion(l + l) # 8-list
147  with self.assertRaises(ValueError):
148  q = Quaternion((l, l)) # 2x4-list
149  with self.assertRaises(TypeError):
150  q = Quaternion([None, None, None, None])
151 
153  e1, e2, e3, e4 = randomElements()
154  q1 = Quaternion(w=e1, x=e2, y=e3, z=e4)
155  q2 = Quaternion(a=e1, b=repr(e2), c=e3, d=e4)
156  q3 = Quaternion(a=e1, i=e2, j=e3, k=e4)
157  q4 = Quaternion(a=e1)
158  self.assertIsInstance(q1, Quaternion)
159  self.assertIsInstance(q2, Quaternion)
160  self.assertIsInstance(q3, Quaternion)
161  self.assertIsInstance(q4, Quaternion)
162  self.assertEqual(q1, Quaternion(e1, e2, e3, e4))
163  self.assertEqual(q1, q2)
164  self.assertEqual(q2, q3)
165  self.assertEqual(q4, Quaternion(e1))
166  with self.assertRaises(TypeError):
167  q = Quaternion(a=None, b=e2, c=e3, d=e4)
168  with self.assertRaises(ValueError):
169  q = Quaternion(a=e1, b=e2, c="String", d=e4)
170  with self.assertRaises(ValueError):
171  q = Quaternion(w=e1, x=e2)
172  with self.assertRaises(ValueError):
173  q = Quaternion(a=e1, b=e2, c=e3, d=e4, e=e1)
174 
176  a, b, c, d = randomElements()
177 
178  # Using 'real' & 'imaginary' notation
179  q1 = Quaternion(real=a, imaginary=(b, c, d))
180  q2 = Quaternion(real=a, imaginary=[b, c, d])
181  q3 = Quaternion(real=a, imaginary=np.array([b, c, d]))
182  q4 = Quaternion(real=a)
183  q5 = Quaternion(imaginary=np.array([b, c, d]))
184  q6 = Quaternion(real=None, imaginary=np.array([b, c, d]))
185  self.assertIsInstance(q1, Quaternion)
186  self.assertIsInstance(q2, Quaternion)
187  self.assertIsInstance(q3, Quaternion)
188  self.assertIsInstance(q4, Quaternion)
189  self.assertIsInstance(q5, Quaternion)
190  self.assertIsInstance(q6, Quaternion)
191  self.assertEqual(q1, Quaternion(a, b, c, d))
192  self.assertEqual(q1, q2)
193  self.assertEqual(q2, q3)
194  self.assertEqual(q4, Quaternion(a, 0, 0, 0))
195  self.assertEqual(q5, Quaternion(0, b, c, d))
196  self.assertEqual(q5, q6)
197  with self.assertRaises(ValueError):
198  q = Quaternion(real=a, imaginary=[b, c])
199  with self.assertRaises(ValueError):
200  q = Quaternion(real=a, imaginary=(b, c, d, d))
201 
202  # Using 'scalar' & 'vector' notation
203  q1 = Quaternion(scalar=a, vector=(b, c, d))
204  q2 = Quaternion(scalar=a, vector=[b, c, d])
205  q3 = Quaternion(scalar=a, vector=np.array([b, c, d]))
206  q4 = Quaternion(scalar=a)
207  q5 = Quaternion(vector=np.array([b, c, d]))
208  q6 = Quaternion(scalar=None, vector=np.array([b, c, d]))
209  self.assertIsInstance(q1, Quaternion)
210  self.assertIsInstance(q2, Quaternion)
211  self.assertIsInstance(q3, Quaternion)
212  self.assertIsInstance(q4, Quaternion)
213  self.assertIsInstance(q5, Quaternion)
214  self.assertIsInstance(q6, Quaternion)
215  self.assertEqual(q1, Quaternion(a, b, c, d))
216  self.assertEqual(q1, q2)
217  self.assertEqual(q2, q3)
218  self.assertEqual(q4, Quaternion(a, 0, 0, 0))
219  self.assertEqual(q5, Quaternion(0, b, c, d))
220  self.assertEqual(q5, q6)
221  with self.assertRaises(ValueError):
222  q = Quaternion(scalar=a, vector=[b, c])
223  with self.assertRaises(ValueError):
224  q = Quaternion(scalar=a, vector=(b, c, d, d))
225 
227  vx = random()
228  vy = random()
229  vz = random()
230  theta = random() * 2.0 * pi
231 
232  v1 = (vx, vy, vz) # tuple format
233  v2 = [vx, vy, vz] # list format
234  v3 = np.array(v2) # array format
235 
236  q1 = Quaternion(axis=v1, angle=theta)
237  q2 = Quaternion(axis=v2, radians=theta)
238  q3 = Quaternion(axis=v3, degrees=theta / pi * 180)
239 
240  # normalise v to a unit vector
241  v3 = v3 / np.linalg.norm(v3)
242 
243  q4 = Quaternion(angle=theta, axis=v3)
244 
245  # Construct the true quaternion
246  t = theta / 2.0
247 
248  a = cos(t)
249  b = v3[0] * sin(t)
250  c = v3[1] * sin(t)
251  d = v3[2] * sin(t)
252 
253  truth = Quaternion(a, b, c, d)
254 
255  self.assertEqual(q1, truth)
256  self.assertEqual(q2, truth)
257  self.assertEqual(q3, truth)
258  self.assertEqual(q4, truth)
259  self.assertEqual(Quaternion(axis=v3, angle=0), Quaternion())
260  self.assertEqual(Quaternion(axis=v3, radians=0), Quaternion())
261  self.assertEqual(Quaternion(axis=v3, degrees=0), Quaternion())
262  self.assertEqual(Quaternion(axis=v3), Quaternion())
263 
264  # Result should be a versor (Unit Quaternion)
265  self.assertAlmostEqual(q1.norm, 1.0, ALMOST_EQUAL_TOLERANCE)
266  self.assertAlmostEqual(q2.norm, 1.0, ALMOST_EQUAL_TOLERANCE)
267  self.assertAlmostEqual(q3.norm, 1.0, ALMOST_EQUAL_TOLERANCE)
268  self.assertAlmostEqual(q4.norm, 1.0, ALMOST_EQUAL_TOLERANCE)
269 
270 
271  with self.assertRaises(ValueError):
272  q = Quaternion(angle=theta)
273  with self.assertRaises(ValueError):
274  q = Quaternion(axis=[b, c], angle=theta)
275  with self.assertRaises(ValueError):
276  q = Quaternion(axis=(b, c, d, d), angle=theta)
277  with self.assertRaises(ZeroDivisionError):
278  q = Quaternion(axis=[0., 0., 0.], angle=theta)
279 
281 
282  def R_z(theta):
283  """
284  Generate a rotation matrix describing a rotation of theta degrees about the z-axis
285  """
286  c = cos(theta)
287  s = sin(theta)
288  return np.array([
289  [c,-s, 0],
290  [s, c, 0],
291  [0, 0, 1]])
292 
293  v = np.array([1, 0, 0])
294  for angle in [0, pi/6, pi/4, pi/2, pi, 4*pi/3, 3*pi/2, 2*pi]:
295  R = R_z(angle) # rotation matrix describing rotation of 90 about +z
296  v_prime_r = np.dot(R, v)
297 
298  q1 = Quaternion(axis=[0,0,1], angle=angle)
299  v_prime_q1 = q1.rotate(v)
300 
301  np.testing.assert_almost_equal(v_prime_r, v_prime_q1, decimal=ALMOST_EQUAL_TOLERANCE)
302 
303  q2 = Quaternion(matrix=R)
304  v_prime_q2 = q2.rotate(v)
305 
306  np.testing.assert_almost_equal(v_prime_q2, v_prime_r, decimal=ALMOST_EQUAL_TOLERANCE)
307 
308  R = np.matrix(np.eye(3))
309  q3 = Quaternion(matrix=R)
310  v_prime_q3 = q3.rotate(v)
311  np.testing.assert_almost_equal(v, v_prime_q3, decimal=ALMOST_EQUAL_TOLERANCE)
312  self.assertEqual(q3, Quaternion())
313 
314  R[0,1] += 3 # introduce error to make matrix non-orthogonal
315  with self.assertRaises(ValueError):
316  q4 = Quaternion(matrix=R)
317 
319  """
320  The matrix defined in this test is orthogonal was carefully crafted
321  such that it's orthogonal to a precision of 1e-07, but not to a precision
322  of 1e-08. The default value for numpy's atol function is 1e-08, but
323  developers should have the option to use a lower precision if they choose
324  to.
325 
326  Reference: https://docs.scipy.org/doc/numpy/reference/generated/numpy.allclose.html
327  """
328  m = [[ 0.73297226, -0.16524626, -0.65988294, -0.07654548],
329  [ 0.13108627, 0.98617666, -0.10135052, -0.04878795],
330  [ 0.66750896, -0.01221443, 0.74450167, -0.05474513],
331  [ 0, 0, 0, 1, ]]
332  npm = np.matrix(m)
333 
334  with self.assertRaises(ValueError):
335  Quaternion(matrix=npm)
336 
337  try:
338  Quaternion(matrix=npm, atol=1e-07)
339  except ValueError:
340  self.fail("Quaternion() raised ValueError unexpectedly!")
341 
343  r = randomElements()
344  a = np.array(r)
345  q = Quaternion(array=a)
346  self.assertIsInstance(q, Quaternion)
347  self.assertEqual(q, Quaternion(*r))
348  with self.assertRaises(ValueError):
349  q = Quaternion(array=a[1:4]) # 3-vector
350  with self.assertRaises(ValueError):
351  q = Quaternion(array=np.hstack((a, a))) # 8-vector
352  with self.assertRaises(ValueError):
353  q = Quaternion(array=np.array([a, a])) # 2x4-matrix
354  with self.assertRaises(TypeError):
355  q = Quaternion(array=np.array([None, None, None, None]))
356 
358  a, b, c, d = randomElements()
359  q = Quaternion(a, b, c, d)
360  self.assertEqual(q, Quaternion(q))
361  self.assertEqual(q, Quaternion(np.array([a, b, c, d])))
362  self.assertEqual(q, Quaternion((a, b, c, d)))
363  self.assertEqual(q, Quaternion([a, b, c, d]))
364  self.assertEqual(q, Quaternion(w=a, x=b, y=c, z=d))
365  self.assertEqual(q, Quaternion(array=np.array([a, b, c, d])))
366 
367 class TestQuaternionRepresentation(unittest.TestCase):
368 
369  def test_str(self):
370  a, b, c, d = randomElements()
371  q = Quaternion(a, b, c, d)
372  string = "{:.3f} {:+.3f}i {:+.3f}j {:+.3f}k".format(a, b, c, d)
373  self.assertEqual(string, str(q))
374 
375  def test_format(self):
376  a, b, c, d = randomElements()
377  q = Quaternion(a, b, c, d)
378  for s in ['.3f', '+.14f', '.6e', 'g']:
379  individual_fmt = '{:' + s + '} {:' + s + '}i {:' + s + '}j {:' + s + '}k'
380  quaternion_fmt = '{:' + s + '}'
381  self.assertEqual(individual_fmt.format(a, b, c, d), quaternion_fmt.format(q))
382 
383  def test_repr(self):
384  a, b, c, d = np.array(randomElements()) # Numpy seems to increase precision of floats (C magic?)
385  q = Quaternion(a, b, c, d)
386  string = "Quaternion(" + repr(a) + ", " + repr(b) + ", " + repr(c) + ", " + repr(d) + ")"
387  self.assertEqual(string, repr(q))
388 
389 class TestQuaternionTypeConversions(unittest.TestCase):
390 
391  def test_bool(self):
392  self.assertTrue(Quaternion())
393  self.assertFalse(Quaternion(scalar=0.0))
394  self.assertTrue(~Quaternion(scalar=0.0))
395  self.assertFalse(~Quaternion())
396 
397  def test_float(self):
398  a, b, c, d = randomElements()
399  q = Quaternion(a, b, c, d)
400  self.assertEqual(float(q), a)
401 
402  def test_int(self):
403  a, b, c, d = randomElements()
404  q = Quaternion(a, b, c, d)
405  self.assertEqual(int(q), int(a))
406  self.assertEqual(int(Quaternion(6.28)), 6)
407  self.assertEqual(int(Quaternion(6.78)), 6)
408  self.assertEqual(int(Quaternion(-4.87)), -4)
409  self.assertEqual(int(round(float(Quaternion(-4.87)))), -5)
410 
411  def test_complex(self):
412  a, b, c, d = randomElements()
413  q = Quaternion(a, b, c, d)
414  self.assertEqual(complex(q), complex(a, b))
415 
416 
417 class TestQuaternionArithmetic(unittest.TestCase):
418 
419  def test_equality(self):
420  r = randomElements()
421  self.assertEqual(Quaternion(*r), Quaternion(*r))
422  q = Quaternion(*r)
423  self.assertEqual(q, q)
424  # Equality should work with other types, if they can be interpreted as quaternions
425  self.assertEqual(q, r)
426  self.assertEqual(Quaternion(1., 0., 0., 0.), 1.0)
427  self.assertEqual(Quaternion(1., 0., 0., 0.), "1.0")
428  self.assertNotEqual(q, q + Quaternion(0.0, 0.002, 0.0, 0.0))
429 
430  # Equality should also cover small rounding and floating point errors
431  self.assertEqual(Quaternion(1., 0., 0., 0.), Quaternion(1.0 - 1e-14, 0., 0., 0.))
432  self.assertNotEqual(Quaternion(1., 0., 0., 0.), Quaternion(1.0 - 1e-12, 0., 0., 0.))
433  self.assertNotEqual(Quaternion(160., 0., 0., 0.), Quaternion(160.0 - 1e-10, 0., 0., 0.))
434  self.assertNotEqual(Quaternion(1600., 0., 0., 0.), Quaternion(1600.0 - 1e-9, 0., 0., 0.))
435 
436  with self.assertRaises(TypeError):
437  q == None
438  with self.assertRaises(ValueError):
439  q == 's'
440 
441  def test_assignment(self):
442  a, b, c, d = randomElements()
443  q1 = Quaternion(a, b, c, d)
444  q2 = Quaternion(a, b*0.1, c+0.3, d)
445  self.assertNotEqual(q1, q2)
446  q2 = q1
447  self.assertEqual(q1, q2)
448 
449  def test_unary_minus(self):
450  a, b, c, d = randomElements()
451  q = Quaternion(a, b, c, d)
452  self.assertEqual(-q, Quaternion(-a, -b, -c, -d))
453 
454  def test_add(self):
455  r1 = randomElements()
456  r2 = randomElements()
457  r = random()
458  n = None
459 
460  q1 = Quaternion(*r1)
461  q2 = Quaternion(*r2)
462  q3 = Quaternion(array= np.array(r1) + np.array(r2))
463  q4 = Quaternion(array= np.array(r2) + np.array([r, 0.0, 0.0, 0.0]))
464  self.assertEqual(q1 + q2, q3)
465  q1 += q2
466  self.assertEqual(q1, q3)
467  self.assertEqual(q2 + r, q4)
468  self.assertEqual(r + q2, q4)
469 
470  with self.assertRaises(TypeError):
471  q1 += n
472  with self.assertRaises(TypeError):
473  n += q1
474 
475  def test_subtract(self):
476  r1 = randomElements()
477  r2 = randomElements()
478  r = random()
479  n = None
480 
481  q1 = Quaternion(*r1)
482  q2 = Quaternion(*r2)
483  q3 = Quaternion(array= np.array(r1) - np.array(r2))
484  q4 = Quaternion(array= np.array(r2) - np.array([r, 0.0, 0.0, 0.0]))
485  self.assertEqual(q1 - q2, q3)
486  q1 -= q2
487  self.assertEqual(q1, q3)
488  self.assertEqual(q2 - r, q4)
489  self.assertEqual(r - q2, -q4)
490 
491  with self.assertRaises(TypeError):
492  q1 -= n
493  with self.assertRaises(TypeError):
494  n -= q1
495 
497  one = Quaternion(1.0, 0.0, 0.0, 0.0)
498  i = Quaternion(0.0, 1.0, 0.0, 0.0)
499  j = Quaternion(0.0, 0.0, 1.0, 0.0)
500  k = Quaternion(0.0, 0.0, 0.0, 1.0)
501 
502  self.assertEqual(i * i, j * j)
503  self.assertEqual(j * j, k * k)
504  self.assertEqual(k * k, i * j * k)
505  self.assertEqual(i * j * k, -one)
506 
507  self.assertEqual(i * j, k)
508  self.assertEqual(i * i, -one)
509  self.assertEqual(i * k, -j)
510  self.assertEqual(j * i, -k)
511  self.assertEqual(j * j, -one)
512  self.assertEqual(j * k, i)
513  self.assertEqual(k * i, j)
514  self.assertEqual(k * j, -i)
515  self.assertEqual(k * k, -one)
516  self.assertEqual(i * j * k, -one)
517 
519  a, b, c, d = randomElements()
520  q1 = Quaternion(a, b, c, d)
521  for s in [30.0, 0.3, -2, -4.7, 0]:
522  q2 = Quaternion(s*a, s*b, s*c, s*d)
523  q3 = q1
524  self.assertEqual(q1 * s, q2) # post-multiply by scalar
525  self.assertEqual(s * q1, q2) # pre-multiply by scalar
526  q3 *= repr(s)
527  self.assertEqual(q3, q2)
528 
530  q = Quaternion()
531  with self.assertRaises(TypeError):
532  a = q * None
533  with self.assertRaises(ValueError):
534  b = q * [1, 1, 1, 1, 1]
535  with self.assertRaises(ValueError):
536  c = q * np.array([[1, 2, 3], [4, 5, 6]])
537  with self.assertRaises(ValueError):
538  d = q * 's'
539 
540  def test_divide(self):
541  r = randomElements()
542  q = Quaternion(*r)
543  if q:
544  self.assertEqual(q / q, Quaternion())
545  self.assertEqual(q / r, Quaternion())
546  else:
547  with self.assertRaises(ZeroDivisionError):
548  q / q
549 
550  with self.assertRaises(ZeroDivisionError):
551  q / Quaternion(0.0)
552  with self.assertRaises(TypeError):
553  q / None
554  with self.assertRaises(ValueError):
555  q / [1, 1, 1, 1, 1]
556  with self.assertRaises(ValueError):
557  q / np.array([[1, 2, 3], [4, 5, 6]])
558  with self.assertRaises(ValueError):
559  q / 's'
560 
562  one = Quaternion(1.0, 0.0, 0.0, 0.0)
563  i = Quaternion(0.0, 1.0, 0.0, 0.0)
564  j = Quaternion(0.0, 0.0, 1.0, 0.0)
565  k = Quaternion(0.0, 0.0, 0.0, 1.0)
566 
567  self.assertEqual(i / i, j / j)
568  self.assertEqual(j / j, k / k)
569  self.assertEqual(k / k, one)
570  self.assertEqual(k / -k, -one)
571 
572  self.assertEqual(i / j, -k)
573  self.assertEqual(i / i, one)
574  self.assertEqual(i / k, j)
575  self.assertEqual(j / i, k)
576  self.assertEqual(j / j, one)
577  self.assertEqual(j / k, -i)
578  self.assertEqual(k / i, -j)
579  self.assertEqual(k / j, i)
580  self.assertEqual(k / k, one)
581  self.assertEqual(i / -j, k)
582 
584  a, b, c, d = randomElements()
585  q1 = Quaternion(a, b, c, d)
586  for s in [30.0, 0.3, -2, -4.7]:
587  q2 = Quaternion(a/s, b/s, c/s, d/s)
588  q3 = q1
589  self.assertEqual(q1 / s, q2)
590  if q1:
591  self.assertEqual(s / q1, q2.inverse)
592  else:
593  with self.assertRaises(ZeroDivisionError):
594  s / q1
595 
596  q3 /= repr(s)
597  self.assertEqual(q3, q2)
598 
599  with self.assertRaises(ZeroDivisionError):
600  q4 = q1 / 0.0
601  with self.assertRaises(TypeError):
602  q4 = q1 / None
603  with self.assertRaises(ValueError):
604  q4 = q1 / 's'
605 
606  def test_squared(self):
607  one = Quaternion(1.0, 0.0, 0.0, 0.0)
608  i = Quaternion(0.0, 1.0, 0.0, 0.0)
609  j = Quaternion(0.0, 0.0, 1.0, 0.0)
610  k = Quaternion(0.0, 0.0, 0.0, 1.0)
611 
612  self.assertEqual(i**2, j**2)
613  self.assertEqual(j**2, k**2)
614  self.assertEqual(k**2, -one)
615 
616  def test_power(self):
617  q1 = Quaternion.random()
618  q2 = Quaternion(q1)
619  self.assertEqual(q1 ** 0, Quaternion())
620  self.assertEqual(q1 ** 1, q1)
621  q2 **= 4
622  self.assertEqual(q2, q1 * q1 * q1 * q1)
623  self.assertEqual((q1 ** 0.5) * (q1 ** 0.5), q1)
624  self.assertEqual(q1 ** -1, q1.inverse)
625  self.assertEqual(4 ** Quaternion(2), Quaternion(16))
626  with self.assertRaises(TypeError):
627  q1 ** None
628  with self.assertRaises(ValueError):
629  q1 ** 's'
630  q3 = Quaternion()
631  self.assertEqual(q3 ** 0.5, q3) # Identity behaves as an identity
632  self.assertEqual(q3 ** 5, q3)
633  self.assertEqual(q3 ** 3.4, q3)
634  q4 = Quaternion(scalar=5) # real number behaves as any other real number would
635  self.assertEqual(q4 ** 4, Quaternion(scalar=5 ** 4))
636 
637  def test_distributive(self):
638  q1 = Quaternion.random()
639  q2 = Quaternion.random()
640  q3 = Quaternion.random()
641  self.assertEqual(q1 * ( q2 + q3 ), q1 * q2 + q1 * q3)
642 
644  q1 = Quaternion.random()
645  q2 = Quaternion.random()
646  if not q1 == q2: # Small chance of this happening with random initialisation
647  self.assertNotEqual(q1 * q2, q2 * q1)
648 
649 
650 class TestQuaternionFeatures(unittest.TestCase):
651 
652  def test_conjugate(self):
653  a, b, c, d = randomElements()
654  q1 = Quaternion(a, b, c, d)
655  q2 = Quaternion.random()
656  self.assertEqual(q1.conjugate, Quaternion(a, -b, -c, -d))
657 
658  self.assertEqual((q1 * q2).conjugate, q2.conjugate * q1.conjugate)
659  self.assertEqual((q1 + q1.conjugate) / 2, Quaternion(scalar=q1.scalar))
660  self.assertEqual((q1 - q1.conjugate) / 2, Quaternion(vector=q1.vector))
661 
663  q = Quaternion.random()
664  self.assertEqual(q, q.conjugate.conjugate)
665 
666  def test_norm(self):
667  r = randomElements()
668  q1 = Quaternion(*r)
669  q2 = Quaternion.random()
670  self.assertEqual(q1.norm, np.linalg.norm(np.array(r)))
671  self.assertEqual(q1.magnitude, np.linalg.norm(np.array(r)))
672  # Multiplicative norm
673  self.assertAlmostEqual((q1 * q2).norm, q1.norm * q2.norm, ALMOST_EQUAL_TOLERANCE)
674  # Scaled norm
675  for s in [30.0, 0.3, -2, -4.7]:
676  self.assertAlmostEqual((q1 * s).norm, q1.norm * abs(s), ALMOST_EQUAL_TOLERANCE)
677 
678  def test_inverse(self):
679  q1 = Quaternion(randomElements())
680  q2 = Quaternion.random()
681  if q1:
682  self.assertEqual(q1 * q1.inverse, Quaternion(1.0, 0.0, 0.0, 0.0))
683  else:
684  with self.assertRaises(ZeroDivisionError):
685  q1 * q1.inverse
686 
687  self.assertEqual(q2 * q2.inverse, Quaternion(1.0, 0.0, 0.0, 0.0))
688 
689  def test_normalisation(self): # normalise to unit quaternion
690  r = randomElements()
691  q1 = Quaternion(*r)
692  v = q1.unit
693  n = q1.normalised
694 
695  if q1 == Quaternion(0): # small chance with random generation
696  return # a 0 quaternion does not normalise
697 
698  # Test normalised objects are unit quaternions
699  np.testing.assert_almost_equal(v.q, q1.elements / q1.norm, decimal=ALMOST_EQUAL_TOLERANCE)
700  np.testing.assert_almost_equal(n.q, q1.elements / q1.norm, decimal=ALMOST_EQUAL_TOLERANCE)
701  self.assertAlmostEqual(v.norm, 1.0, ALMOST_EQUAL_TOLERANCE)
702  self.assertAlmostEqual(n.norm, 1.0, ALMOST_EQUAL_TOLERANCE)
703  # Test axis and angle remain the same
704  np.testing.assert_almost_equal(q1.axis, v.axis, decimal=ALMOST_EQUAL_TOLERANCE)
705  np.testing.assert_almost_equal(q1.axis, n.axis, decimal=ALMOST_EQUAL_TOLERANCE)
706  self.assertAlmostEqual(q1.angle, v.angle, ALMOST_EQUAL_TOLERANCE)
707  self.assertAlmostEqual(q1.angle, n.angle, ALMOST_EQUAL_TOLERANCE)
708  # Test special case where q is zero
709  q2 = Quaternion(0)
710  self.assertEqual(q2, q2.normalised)
711 
712  def test_is_unit(self):
713  q1 = Quaternion()
714  q2 = Quaternion(1.0, 0, 0, 0.0001)
715  self.assertTrue(q1.is_unit())
716  self.assertFalse(q2.is_unit())
717  self.assertTrue(q2.is_unit(0.001))
718 
719  def test_q_matrix(self):
720  a, b, c, d = randomElements()
721  q = Quaternion(a, b, c, d)
722  M = np.array([
723  [a, -b, -c, -d],
724  [b, a, -d, c],
725  [c, d, a, -b],
726  [d, -c, b, a]])
727  self.assertTrue(np.array_equal(q._q_matrix(), M))
728 
729  def test_q_bar_matrix(self):
730  a, b, c, d = randomElements()
731  q = Quaternion(a, b, c, d)
732  M = np.array([
733  [a, -b, -c, -d],
734  [b, a, d, -c],
735  [c, -d, a, b],
736  [d, c, -b, a]])
737  self.assertTrue(np.array_equal(q._q_bar_matrix(), M))
738 
740  a, b, c, d = randomElements()
741  q = Quaternion(a, b, c, d)
742  # Test scalar
743  self.assertEqual(q.scalar, a)
744  self.assertEqual(q.real, a)
745  # Test vector
746  self.assertTrue(np.array_equal(q.vector, [b, c, d]))
747  self.assertTrue(np.array_equal(q.imaginary, [b, c, d]))
748  self.assertEqual(tuple(q.vector), (b, c, d))
749  self.assertEqual(list(q.imaginary), [b, c, d])
750  self.assertEqual(q.w, a)
751  self.assertEqual(q.x, b)
752  self.assertEqual(q.y, c)
753  self.assertEqual(q.z, d)
754 
756  r = randomElements()
757  q = Quaternion(*r)
758  self.assertEqual(tuple(q.elements), r)
759 
761  r = randomElements()
762  q = Quaternion(*r)
763  self.assertEqual(q[0], r[0])
764  self.assertEqual(q[1], r[1])
765  self.assertEqual(q[2], r[2])
766  self.assertEqual(q[3], r[3])
767  self.assertEqual(q[-1], r[3])
768  self.assertEqual(q[-4], r[0])
769  with self.assertRaises(TypeError):
770  q[None]
771  with self.assertRaises(IndexError):
772  q[4]
773  with self.assertRaises(IndexError):
774  q[-5]
775 
777  q = Quaternion()
778  self.assertEqual(q[1], 0.0)
779  q[1] = 10.0
780  self.assertEqual(q[1], 10.0)
781  self.assertEqual(q, Quaternion(1.0, 10.0, 0.0, 0.0))
782  with self.assertRaises(TypeError):
783  q[2] = None
784  with self.assertRaises(ValueError):
785  q[2] = 's'
786 
787  def test_rotate(self):
788  q = Quaternion(axis=[1,1,1], angle=2*pi/3)
789  q2 = Quaternion(axis=[1, 0, 0], angle=-pi)
790  q3 = Quaternion(axis=[1, 0, 0], angle=pi)
791  precision = ALMOST_EQUAL_TOLERANCE
792  for r in [1, 3.8976, -69.7, -0.000001]:
793  # use np.testing.assert_almost_equal() to compare float sequences
794  np.testing.assert_almost_equal(q.rotate((r, 0, 0)), (0, r, 0), decimal=ALMOST_EQUAL_TOLERANCE)
795  np.testing.assert_almost_equal(q.rotate([0, r, 0]), [0, 0, r], decimal=ALMOST_EQUAL_TOLERANCE)
796  np.testing.assert_almost_equal(q.rotate(np.array([0, 0, r])), np.array([r, 0, 0]), decimal=ALMOST_EQUAL_TOLERANCE)
797  self.assertEqual(q.rotate(Quaternion(vector=[-r, 0, 0])), Quaternion(vector=[0, -r, 0]))
798  np.testing.assert_almost_equal(q.rotate([0, -r, 0]), [0, 0, -r], decimal=ALMOST_EQUAL_TOLERANCE)
799  self.assertEqual(q.rotate(Quaternion(vector=[0, 0, -r])), Quaternion(vector=[-r, 0, 0]))
800 
801  np.testing.assert_almost_equal(q2.rotate((r, 0, 0)), q3.rotate((r, 0, 0)), decimal=ALMOST_EQUAL_TOLERANCE)
802  np.testing.assert_almost_equal(q2.rotate((0, r, 0)), q3.rotate((0, r, 0)), decimal=ALMOST_EQUAL_TOLERANCE)
803  np.testing.assert_almost_equal(q2.rotate((0, 0, r)), q3.rotate((0, 0, r)), decimal=ALMOST_EQUAL_TOLERANCE)
804 
806  q = Quaternion.random()
807  a, b, c, d = tuple(q.elements)
808  R = np.array([
809  [a**2 + b**2 - c**2 - d**2, 2 * (b * c - a * d), 2 * (a * c + b * d)],
810  [2 * (b * c + a * d), a**2 - b**2 + c**2 - d**2, 2 * (c * d - a * b)],
811  [2 * (b * d - a * c), 2 * (a * b + c * d), a**2 - b**2 - c**2 + d**2]])
812  t = np.array([[0],[0],[0]])
813  T = np.vstack([np.hstack([R,t]), np.array([0,0,0,1])])
814  np.testing.assert_almost_equal(R, q.rotation_matrix, decimal=ALMOST_EQUAL_TOLERANCE)
815  np.testing.assert_almost_equal(T, q.transformation_matrix, decimal=ALMOST_EQUAL_TOLERANCE)
816 
817  # Test no scaling of rotated vectors
818  v1 = np.array([1, 0, 0])
819  v2 = np.hstack((np.random.uniform(-10, 10, 3), 1.0))
820  v1_ = np.dot(q.rotation_matrix, v1)
821  v2_ = np.dot(q.transformation_matrix, v2)
822  self.assertAlmostEqual(np.linalg.norm(v1_), 1.0, ALMOST_EQUAL_TOLERANCE)
823  self.assertAlmostEqual(np.linalg.norm(v2_), np.linalg.norm(v2), ALMOST_EQUAL_TOLERANCE)
824 
825  # Test transformation of vectors is equivalent for quaternion & matrix
826  np.testing.assert_almost_equal(v1_, q.rotate(v1), decimal=ALMOST_EQUAL_TOLERANCE)
827  np.testing.assert_almost_equal(v2_[0:3], q.rotate(v2[0:3]), decimal=ALMOST_EQUAL_TOLERANCE)
828 
830 
831  def R_x(theta):
832  c = cos(theta)
833  s = sin(theta)
834  return np.array([
835  [1, 0, 0],
836  [0, c,-s],
837  [0, s, c]])
838 
839  def R_y(theta):
840  c = cos(theta)
841  s = sin(theta)
842  return np.array([
843  [ c, 0, s],
844  [ 0, 1, 0],
845  [-s, 0, c]])
846 
847  def R_z(theta):
848  c = cos(theta)
849  s = sin(theta)
850  return np.array([
851  [ c,-s, 0],
852  [ s, c, 0],
853  [ 0, 0, 1]])
854 
855  p = np.random.randn(3)
856  q = Quaternion.random()
857  yaw, pitch, roll = q.yaw_pitch_roll
858 
859  p_q = q.rotate(p)
860  R_q = q.rotation_matrix
861 
862  # build rotation matrix, R = R_z(yaw)*R_y(pitch)*R_x(roll)
863  R_ypr = np.dot(R_x(roll), np.dot(R_y(pitch), R_z(yaw)))
864  p_ypr = np.dot(R_ypr, p)
865 
866  np.testing.assert_almost_equal(p_q , p_ypr, decimal=ALMOST_EQUAL_TOLERANCE)
867  np.testing.assert_almost_equal(R_q , R_ypr, decimal=ALMOST_EQUAL_TOLERANCE)
868 
869  def test_matrix_io(self):
870  v = np.random.uniform(-100, 100, 3)
871 
872  for i in range(10):
873  q0 = Quaternion.random()
874  R = q0.rotation_matrix
875  q1 = Quaternion(matrix=R)
876  np.testing.assert_almost_equal(q0.rotate(v), np.dot(R, v), decimal=ALMOST_EQUAL_TOLERANCE)
877  np.testing.assert_almost_equal(q0.rotate(v), q1.rotate(v), decimal=ALMOST_EQUAL_TOLERANCE)
878  np.testing.assert_almost_equal(q1.rotate(v), np.dot(R, v), decimal=ALMOST_EQUAL_TOLERANCE)
879 
880  self.assertTrue((q0 == q1) or (q0 == -q1)) # q1 and -q1 are equivalent rotations
881 
882  def validate_axis_angle(self, axis, angle):
883 
884  def wrap_angle(theta):
885  """ Wrap any angle to lie between -pi and pi
886 
887  Odd multiples of pi are wrapped to +pi (as opposed to -pi)
888  """
889  result = ((theta + pi) % (2*pi)) - pi
890  if result == -pi: result = pi
891  return result
892 
893  theta = wrap_angle(angle)
894  v = axis
895 
896  q = Quaternion(angle=theta, axis=v)
897 
898  v_ = q.axis
899  theta_ = q.angle
900 
901  if theta == 0.0: # axis is irrelevant (check defaults to x=y=z)
902  np.testing.assert_almost_equal(theta_, 0.0, decimal=ALMOST_EQUAL_TOLERANCE)
903  np.testing.assert_almost_equal(v_, np.zeros(3), decimal=ALMOST_EQUAL_TOLERANCE)
904  return
905  elif abs(theta) == pi: # rotation in either direction is equivalent
906  self.assertTrue(
907  np.isclose(theta, pi) or np.isclose(theta, -pi)
908  and
909  np.isclose(v, v_).all() or np.isclose(v, -v_).all()
910  )
911  else:
912  self.assertTrue(
913  np.isclose(theta, theta_) and np.isclose(v, v_).all()
914  or
915  np.isclose(theta, -theta_) and np.isclose(v, -v_).all()
916  )
917  # Ensure the returned axis is a unit vector
918  np.testing.assert_almost_equal(np.linalg.norm(v_), 1.0, decimal=ALMOST_EQUAL_TOLERANCE)
919 
921  random_axis = np.random.uniform(-1, 1, 3)
922  random_axis /= np.linalg.norm(random_axis)
923 
924  angles = np.array([-3, -2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2, 3]) * pi
925  axes = [np.array([1, 0, 0]), np.array([0, 1, 0]), np.array([0, 0, 1]), random_axis]
926 
927  for v in axes:
928  for theta in angles:
929  self.validate_axis_angle(v, theta)
930 
931 
932 
934  for i in range(20):
935  v = np.random.uniform(-1, 1, 3)
936  v /= np.linalg.norm(v)
937  theta = float(np.random.uniform(-2,2, 1)) * pi
938  self.validate_axis_angle(v, theta)
939 
940  def test_exp(self):
941  from math import exp
942  q = Quaternion(axis=[1,0,0], angle=pi)
943  exp_q = Quaternion.exp(q)
944  self.assertEqual(exp_q, exp(0) * Quaternion(scalar=cos(1.0), vector=[sin(1.0), 0,0]))
945 
946  def test_log(self):
947  from math import log
948  q = Quaternion(axis=[1,0,0], angle=pi)
949  log_q = Quaternion.log(q)
950  self.assertEqual(log_q, Quaternion(scalar=0, vector=[pi/2,0,0]))
951 
952  def test_distance(self):
953  q = Quaternion(scalar=0, vector=[1,0,0])
954  p = Quaternion(scalar=0, vector=[0,1,0])
955  self.assertEqual(pi/2, Quaternion.distance(q,p))
956  q = Quaternion(angle=pi/2, axis=[1,0,0])
957  p = Quaternion(angle=pi/2, axis=[0,1,0])
958  self.assertEqual(pi/3, Quaternion.distance(q,p))
959  q = Quaternion(scalar=1, vector=[1,1,1])
960  p = Quaternion(scalar=-1, vector=[-1,-1,-1])
961  p._normalise()
962  q._normalise()
963  self.assertAlmostEqual(0, Quaternion.distance(q,p), places=8)
964 
966  q = Quaternion(scalar=0, vector=[1,0,0])
967  p = Quaternion(scalar=0, vector=[0,1,0])
968  self.assertEqual((q-p).norm, Quaternion.absolute_distance(q,p))
969  q = Quaternion(angle=pi/2, axis=[1,0,0])
970  p = Quaternion(angle=pi/2, axis=[0,1,0])
971  self.assertEqual((q-p).norm, Quaternion.absolute_distance(q,p))
972  q = Quaternion(scalar=0, vector=[1,0,0])
973  p = Quaternion(scalar=-1, vector=[0,-1,0])
974  self.assertEqual((q+p).norm, Quaternion.absolute_distance(q,p))
975  q = Quaternion(scalar=1, vector=[1,1,1])
976  p = Quaternion(scalar=-1, vector=[-1,-1,-1])
977  p._normalise()
978  q._normalise()
979  self.assertAlmostEqual(0, Quaternion.absolute_distance(q,p), places=8)
980 
981  def test_sym_distance(self):
982  q = Quaternion(scalar=0, vector=[1,0,0])
983  p = Quaternion(scalar=0, vector=[0,1,0])
984  self.assertEqual(pi/2, Quaternion.sym_distance(q,p))
985  q = Quaternion(angle=pi/2, axis=[1,0,0])
986  p = Quaternion(angle=pi/2, axis=[0,1,0])
987  self.assertAlmostEqual(pi/3, Quaternion.sym_distance(q,p), places=6)
988  q = Quaternion(scalar=0, vector=[1,0,0])
989  p = Quaternion(scalar=0, vector=[0,-1,0])
990  self.assertEqual(pi/2, Quaternion.sym_distance(q,p))
991  q = Quaternion(scalar=1, vector=[1,1,1])
992  p = Quaternion(scalar=-1, vector=[-1,-1,-1])
993  p._normalise()
994  q._normalise()
995  self.assertAlmostEqual(pi, Quaternion.sym_distance(q,p), places=8)
996 
997  def test_slerp(self):
998  q1 = Quaternion(axis=[1, 0, 0], angle=0.0)
999  q2 = Quaternion(axis=[1, 0, 0], angle=pi/2)
1000  q3 = Quaternion.slerp(q1, q2, 0.5)
1001  self.assertEqual(q3, Quaternion(axis=[1,0,0], angle=pi/4))
1002 
1004  for axis in [[1, 0, 0], [0, 1, 0], [0, 0, 1]]:
1005  q1 = Quaternion(axis=axis, angle=0.0)
1006  q2 = Quaternion(axis=axis, angle=pi/2.0)
1007  q3 = Quaternion(axis=axis, angle=pi*3.0/2.0)
1008  for t in np.arange(0.1, 1, 0.1):
1009  q4 = Quaternion.slerp(q1, q2, t)
1010  q5 = Quaternion.slerp(q1, q3, t)
1011  q6 = Quaternion(axis=axis, angle=t*pi/2)
1012  q7 = Quaternion(axis=axis, angle=-t*pi/2)
1013  assert q4 == q6 or q4 == -q6
1014  assert q5 == q7 or q5 == -q7
1015 
1016  def test_interpolate(self):
1017  q1 = Quaternion(axis=[1, 0, 0], angle=0.0)
1018  q2 = Quaternion(axis=[1, 0, 0], angle=2*pi/3)
1019  num_intermediates = 3
1020  base = pi/6
1021  list1 = list(Quaternion.intermediates(q1, q2, num_intermediates, include_endpoints=False))
1022  list2 = list(Quaternion.intermediates(q1, q2, num_intermediates, include_endpoints=True))
1023  self.assertEqual(len(list1), num_intermediates)
1024  self.assertEqual(len(list2), num_intermediates+2)
1025  self.assertEqual(list1[0], list2[1])
1026  self.assertEqual(list1[1], list2[2])
1027  self.assertEqual(list1[2], list2[3])
1028 
1029  self.assertEqual(list2[0], q1)
1030  self.assertEqual(list2[1], Quaternion(axis=[1, 0, 0], angle=base))
1031  self.assertEqual(list2[2], Quaternion(axis=[1, 0, 0], angle=2*base))
1032  self.assertEqual(list2[3], Quaternion(axis=[1, 0, 0], angle=3*base))
1033  self.assertEqual(list2[4], q2)
1034 
1036  q = Quaternion.random()
1037  omega = np.random.uniform(-1, 1, 3) # Random angular velocity
1038 
1039  q_dash = 0.5 * q * Quaternion(vector=omega)
1040 
1041  self.assertEqual(q_dash, q.derivative(omega))
1042 
1043  def test_integration(self):
1044  rotation_rate = [0, 0, 2*pi] # one rev per sec around z
1045  v = [1, 0, 0] # test vector
1046  for dt in [0, 0.25, 0.5, 0.75, 1, 2, 10, 1e-10, random()*10]: # time step in seconds
1047  qt = Quaternion() # no rotation
1048  qt.integrate(rotation_rate, dt)
1049  q_truth = Quaternion(axis=[0,0,1], angle=dt*2*pi)
1050  a = qt.rotate(v)
1051  b = q_truth.rotate(v)
1052  np.testing.assert_almost_equal(a, b, decimal=ALMOST_EQUAL_TOLERANCE)
1053  self.assertTrue(qt.is_unit())
1054  # Check integrate() is norm-preserving over many calls
1055  q = Quaternion()
1056  for i in range(1000):
1057  q.integrate([pi, 0, 0], 0.001)
1058  self.assertTrue(q.is_unit())
1059 
1060 
1061 class TestQuaternionUtilities(unittest.TestCase):
1062  def test_copy(self):
1063  from copy import copy
1064  q = Quaternion.random()
1065  q2 = copy(q)
1066  self.assertEqual(q, q2)
1067  self.assertFalse(q is q2)
1068  self.assertTrue(all(q.q == q2.q))
1069 
1070  def test_deep_copy(self):
1071  from copy import deepcopy
1072  q = Quaternion.random()
1073  q2 = deepcopy(q)
1074  self.assertEqual(q, q2)
1075  self.assertFalse(q is q2)
1076  self.assertFalse(q.q is q2.q)
1077 
1078 class TestQuaternionHashing(unittest.TestCase):
1080  q1 = Quaternion(1, 0, 0, 0)
1081  q2 = Quaternion(1, 0, 0, 0)
1082 
1083  self.assertEqual(hash(q1), hash(q2))
1084 
1086  q1 = Quaternion(1, 0, 0, 0)
1087  q2 = Quaternion(0, 1, 0, 0)
1088 
1089  self.assertNotEqual(hash(q1), hash(q2))
1090 
1091 
1092 if __name__ == '__main__':
1093  unittest.main()


pyquaternion
Author(s): achille
autogenerated on Sun Mar 15 2020 03:13:33