def test_from_axis_angle(self): # Returns a rotation quaternion given the axis and the angle of rotation. # Tested with positive integers, negative integers, variables, zero and nan a1 = 2*pi/3 v1 = (sqrt(3)/3, sqrt(3)/3, sqrt(3)/3) a2 = -pi v2 = -x a3 = 0 v3 = 0 a4 = float("nan") v4 = float("nan") self.assertEqual(Quaternion.from_axis_angle( v1, a1), Quaternion(1/2, 1/2, 1/2, 1/2)) self.assertRaises( TypeError, Quaternion.from_axis_angle, v2, a2) self.assertRaises( TypeError, Quaternion.from_axis_angle, v3, a3) self.assertRaises( TypeError, Quaternion.from_axis_angle, v4, a4)
def test_add(self): #Testing if add function works. And also that addition without the add function works as well. Testing with negative inputs as well. q = Quaternion(w, x, y, z) self.assertEqual(q + q, Quaternion(2 * w, 2 * x, 2 * y, 2 * z)) self.assertEqual(q.add(q), (q + q)) self.assertEqual(q.add(-q), (q - q)) self.assertEqual((-q).add(-q), (-q - q))
def test_Quaternion_str_printer(): q = Quaternion(x, y, z, t) assert str(q) == "x + y*i + z*j + t*k" q = Quaternion(x,y,z,x*t) assert str(q) == "x + y*i + z*j + t*x*k" q = Quaternion(x,y,z,x+t) assert str(q) == "x + y*i + z*j + (t + x)*k"
def test_quaternion_initialize(self): q = Quaternion(w, x, y, z) with self.assertRaises(NameError): q = Quaternion(w, x, y, i) with self.assertRaises(AttributeError): q = Quaternion([1, 4, 5]) self.assertEqual(q + q, Quaternion(w * 2, x * 2, y * 2, z * 2))
def test_quaternion_initialize(self): #Test if initializition of quaternions work. If correct errors are raised when wrong inputs are put in to the function. q = Quaternion(w, x, y, z) with self.assertRaises(NameError): q = Quaternion(w, x, y, i) with self.assertRaises(AttributeError): q = Quaternion([1, 4, 5]) self.assertEqual(q + q, Quaternion(w * 2, x * 2, y * 2, z * 2))
def test_inverse(self): q = Quaternion(w, x, y, z) self.assertEqual( q.inverse(), Quaternion(w, -x, -y, -z) / (w**2 + x**2 + y**2 + z**2)) self.assertNotEqual( q.inverse(), Quaternion(w, -x, -y, -z) / w**2 + x**2 + y**2 + z**3) self.assertEqual(q.inverse(), q.pow(-1))
def test_inverse(self): #Test inverse of quaternions. Inverse is defined as Quaternion(w, -x, -y, -z) / (w**2 + x**2 + y**2 + z**2). Also tested with pow(-1) since this is equivalent to the inverse. q = Quaternion(w, x, y, z) self.assertEqual( q.inverse(), Quaternion(w, -x, -y, -z) / (w**2 + x**2 + y**2 + z**2)) self.assertNotEqual( q.inverse(), Quaternion(w, -x, -y, -z) / (w**2 + x**2 + y**2 + z**3)) self.assertEqual(q.inverse(), q.pow(-1))
def test_to_rotation_matrix(self): #Test the to_rotation_matrix function. #The first test checks whether the to_ration_matrix returns an equal matrix as the one expected #The second test checks whether the function returns a different matrix than the one expected #The third test sends in a quaternion with variables and it is supposed to be equal to the values in the matrix, but there code does not handle variables. Therefor we made it raise an exception. q = Quaternion(1, 2, 3, 4) q1 = Quaternion(w, x, y, z) self.assertEqual( q.to_rotation_matrix(), Matrix([[Rational(-2, 3), Rational(2, 15), Rational(11, 15)], [Rational(2, 3), Rational(-1, 3), Rational(2, 3)], [Rational(1, 3), Rational(14, 15), Rational(2, 15)]])) self.assertNotEqual( q.to_rotation_matrix(), Matrix([[Rational(2, 3), Rational(2, 15), Rational(-11, 15)], [Rational(2, 3), Rational(-1, 3), Rational(2, 3)], [Rational(1, 3), Rational(2, 15), Rational(5, 15)]])) with self.assertRaises(ValueError): Matrix([ 1 - 2 * (y * y + z * z), 2 * (x * y - z * w), 2 * (x * z + y * w) ], [ 2 * (x * y + z * w), 1 - 2 * (x * x + z * z), 2 * (y * z - x * w) ], [ 2 * (x * z - y * w), 2 * (y * z + x * w), 1 - 2 * (x * x + y * y) ])
def test_rotate_point(self): # 1. testing that points cannot rotate around undefined quateunion # 2. trying to break function with to many parameters # 3. No rotation should lead to same quaternion # 4. "Normal" random rotation test q_var = Quaternion(w, x, y, z) q1 = Quaternion(1, 0, 0, 0) q2 = Quaternion(1, 2, 3, 4) with self.assertRaises(AttributeError): Quaternion.rotate_point((1, 1, 1), q_var) with self.assertRaises(TypeError): Quaternion.rotate_point((1, 1, 1), q1, 666) self.assertEqual(Quaternion.rotate_point((2, 2, 2), q1), (2, 2, 2)) self.assertEqual(Quaternion.rotate_point((1, 1, 1), q2), (S.One / 5, 1, S(7) / 5))
def rotate(tensor, axis, angle): """ Rotate symbolic vector or tensor about an arbitrary axis :arg: 3-vector or (3x3)-matrix :arg: rotation axis (normal 3-vector) :arg: rotation angle (in radians) :return: rotated tensor (of original type) """ # Quaternion-Matrix Multiplication def mul(*args): if isinstance(args[0], list): q, M = args[1], args[0] for i, col in enumerate(M): M[i] = col * q else: q, M = args[0], args[1] for i, col in enumerate(M): M[i] = q * col return M # Rotation Quaternion (Axis, Angle) q = quat.from_axis_angle(axis, angle) if isinstance(tensor[0], list): tensor = Matrix(tensor) if tensor.shape != (3, 3): raise Exception('Invalid Matrix Size') # Rotation Formula: M' = (q.(q.M.q*)^T.q*)^T M = [quat(0, *tensor[:, i]) for i in range(tensor.shape[1])] M = mul(q, mul(M, q.conjugate())) for i in range(tensor.shape[1]): tensor[:, i] = [M[i].b, M[i].c, M[i].d] M = [quat(0, *tensor[i, :]) for i in range(tensor.shape[0])] M = mul(q, mul(M, q.conjugate())) for i in range(tensor.shape[0]): tensor[i, :] = [[M[i].b, M[i].c, M[i].d]] return tensor.tolist() if isinstance(tensor, list): if len(tensor) != 3: raise Exception('Invalid Vector Length') # Rotation Formula: v' = q.v.q* v = q * quat(0, *tensor) * q.conjugate() return [v.b, v.c, v.d] raise Exception('Unsupported Tensor Type')
def test_from_rotation_matrix(self): #Test the from_rotation_matrix function #The first test checks that a normal matrix becomes a correct quaternion. #The second test checks that the outcome from our test is incorrect with the normalization function. #The third test is with variables and checks that the formula of how to calculate from rotation matrix is accurate a = 2 b = 2 c = 2 d = 2 e = 2 f = 2 g = 2 h = 2 i = 2 m = Matrix([[cos(phi), -sin(phi), 0], [sin(phi), cos(phi), 0], [0, 0, 1]]) m1 = Matrix([[1, 0, 0], [0, 0, -1], [0, 1, 0]]) m2 = Matrix([[a, b, c], [d, e, f], [g, h, i]]) q = trigsimp(Quaternion.from_rotation_matrix(m)) q1 = trigsimp(Quaternion.from_rotation_matrix(m1)) q2 = trigsimp(Quaternion.from_rotation_matrix(m2)) self.assertEqual( q, Quaternion( sqrt(2) * sqrt(cos(phi) + 1) / 2, 0, 0, sqrt(-2 * cos(phi) + 2) / 2)) self.assertNotEqual( q1, Quaternion( sqrt(1) * sqrt(cos(phi) + 2), 0, 0, sqrt(-1 * cos(phi) + 1))) self.assertEqual( q2, Quaternion( sqrt(a + e + i) / 2, (h - f) / (2 * (sqrt(a + e + i))), (c - g) / (2 * (sqrt(a + e + i))), (d - b) / (2 * (sqrt(a + e + i)))))
def test_pow(self): q = Quaternion(2, 2, 2, 2) # Inverse self.assertEqual(q.pow(-1), q.inverse()) # Not integer self.assertEqual(q.pow(1.5), NotImplemented) # Do the loop 1 time and do the if inside the loop self.assertEqual(q.pow(1), q) # Test negative power. Do the loop twice and not the if inside the loop self.assertEqual(q.pow(-2), Quaternion(-1 / 32, -1 / 32, -1 / 32, -1 / 32)) # Test the loop twice and only do the if inside loop the the first time self.assertEqual(q.pow(2), Quaternion(-8, 8, 8, 8)) # Don't test loop self.assertEqual(q.pow(0), 1) # Test loop three times and the if inside the loop twice self.assertEqual(q.pow(3), Quaternion(-64, 0, 0, 0)) # Test loop three times and the if inside the loop only once self.assertEqual(q.pow(4), Quaternion(-128, -128, -128, -128))
def test_normalize(self): q = Quaternion(w, x, y, z) q0 = Quaternion(0, 0, 0, 0) q1 = Quaternion(1, 2, 3, 4) self.assertEqual(q1.normalize(), Quaternion(1, 2, 3, 4) * (1 / q1.norm())) self.assertEqual( q.normalize(), Quaternion(w, x, y, z) / sqrt(w**2 + x**2 + y**2 + z**2)) self.assertNotEqual( q.normalize(), Quaternion(w, x, y, z) / sqrt(w**4 + x**4 + y**4 + z**4)) self.assertNotEqual( q0.normalize(), Quaternion(0, 0, 0, 0) / sqrt(0**2 + 0**2 + 0**2 + 0**2))
def test_ln(self): #Tests a quaternion with both real part as well as the quaternion units being zero. self.assertEqual(self.q0._ln(), Quaternion(zoo, nan, nan, nan)) #Tests a quaternion with real part and quaternion units being non-zero values. self.assertEqual( self.q1._ln(), Quaternion(log(2), sqrt(3) * pi / 9, sqrt(3) * pi / 9, sqrt(3) * pi / 9)) #Tests a quaternion with zero valued real part and non-zero valued quaternion units. self.assertEqual( self.q2._ln(), Quaternion(log(sqrt(14)), sqrt(14) * pi / 28, sqrt(14) * pi / 14, 3 * sqrt(14) * pi / 28)) #Tests a quaternion with non-zero valued real part but zero valued quaternion units. self.assertEqual(self.q3._ln(), Quaternion(0, nan, nan, nan)) #Tests a quaternion with a symbolic varibale as real part value. The quaternion units are zero. self.assertEqual(self.q4._ln(), Quaternion(log(sqrt(symbols('a')**2)), nan, nan, nan)) #Tests a quaternion that has other values > 1 as quaternion units. self.assertEqual( self.q5._ln(), Quaternion(log(sqrt(30)), 2 * sqrt(29) * acos(sqrt(30) / 30) / 29, 3 * sqrt(29) * acos(sqrt(30) / 30) / 29, 4 * sqrt(29) * acos(sqrt(30) / 30) / 29)) #Tests a quaternion where all parameters a symbolic variables. self.assertEqual( self.q6._ln(), Quaternion( log( sqrt( symbols('a')**2 + symbols('b')**2 + symbols('c')**2 + symbols('d')**2)), symbols('b') * acos( symbols('a') / sqrt( symbols('a')**2 + symbols('b')**2 + symbols('c')**2 + symbols('d')**2)) / sqrt(symbols('b')**2 + symbols('c')**2 + symbols('d')**2), symbols('c') * acos( symbols('a') / sqrt( symbols('a')**2 + symbols('b')**2 + symbols('c')**2 + symbols('d')**2)) / sqrt(symbols('b')**2 + symbols('c')**2 + symbols('d')**2), symbols('d') * acos( symbols('a') / sqrt( symbols('a')**2 + symbols('b')**2 + symbols('c')**2 + symbols('d')**2)) / sqrt(symbols('b')**2 + symbols('c')**2 + symbols('d')**2))) #Tests a quaternion where the real part is a string. self.assertEqual( self.q7._ln(), Quaternion( log( sqrt( symbols( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' )**2 + 29)), 2 * sqrt(29) * acos( symbols( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ) / sqrt( symbols( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' )**2 + 29)) / 29, 3 * sqrt(29) * acos( symbols( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ) / sqrt( symbols( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' )**2 + 29)) / 29, 4 * sqrt(29) * acos( symbols( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ) / sqrt( symbols( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' )**2 + 29)) / 29)) #Tests a quaternion with one of the quaternion units being a symbolic variable and the rest zero. self.assertEqual( self.q8._ln(), Quaternion(log(sqrt(symbols('a')**2)), 0, 0, pi * symbols('a') / (2 * sqrt(symbols('a')**2))))
def setUp(self): self.q0 = Quaternion(0, 0, 0, 0) self.q1 = Quaternion(1, 1, 1, 1) self.q2 = Quaternion(0, 1, 2, 3) self.q3 = Quaternion(1, 0, 0, 0) self.q4 = Quaternion('a', 0, 0, 0) self.q5 = Quaternion(1, 2, 3, 4) self.q6 = Quaternion('a', 'b', 'c', 'd') self.q7 = Quaternion( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 2, 3, 4) self.q8 = Quaternion(0, 0, 0, 'a')
class TestLn(unittest.TestCase): #The tests several different quaternions the _ln function either should be able to proccess or throw an error def setUp(self): self.q0 = Quaternion(0, 0, 0, 0) self.q1 = Quaternion(1, 1, 1, 1) self.q2 = Quaternion(0, 1, 2, 3) self.q3 = Quaternion(1, 0, 0, 0) self.q4 = Quaternion('a', 0, 0, 0) self.q5 = Quaternion(1, 2, 3, 4) self.q6 = Quaternion('a', 'b', 'c', 'd') self.q7 = Quaternion( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 2, 3, 4) self.q8 = Quaternion(0, 0, 0, 'a') def tearDown(self): pass def test_ln(self): #Tests a quaternion with both real part as well as the quaternion units being zero. self.assertEqual(self.q0._ln(), Quaternion(zoo, nan, nan, nan)) #Tests a quaternion with real part and quaternion units being non-zero values. self.assertEqual( self.q1._ln(), Quaternion(log(2), sqrt(3) * pi / 9, sqrt(3) * pi / 9, sqrt(3) * pi / 9)) #Tests a quaternion with zero valued real part and non-zero valued quaternion units. self.assertEqual( self.q2._ln(), Quaternion(log(sqrt(14)), sqrt(14) * pi / 28, sqrt(14) * pi / 14, 3 * sqrt(14) * pi / 28)) #Tests a quaternion with non-zero valued real part but zero valued quaternion units. self.assertEqual(self.q3._ln(), Quaternion(0, nan, nan, nan)) #Tests a quaternion with a symbolic varibale as real part value. The quaternion units are zero. self.assertEqual(self.q4._ln(), Quaternion(log(sqrt(symbols('a')**2)), nan, nan, nan)) #Tests a quaternion that has other values > 1 as quaternion units. self.assertEqual( self.q5._ln(), Quaternion(log(sqrt(30)), 2 * sqrt(29) * acos(sqrt(30) / 30) / 29, 3 * sqrt(29) * acos(sqrt(30) / 30) / 29, 4 * sqrt(29) * acos(sqrt(30) / 30) / 29)) #Tests a quaternion where all parameters a symbolic variables. self.assertEqual( self.q6._ln(), Quaternion( log( sqrt( symbols('a')**2 + symbols('b')**2 + symbols('c')**2 + symbols('d')**2)), symbols('b') * acos( symbols('a') / sqrt( symbols('a')**2 + symbols('b')**2 + symbols('c')**2 + symbols('d')**2)) / sqrt(symbols('b')**2 + symbols('c')**2 + symbols('d')**2), symbols('c') * acos( symbols('a') / sqrt( symbols('a')**2 + symbols('b')**2 + symbols('c')**2 + symbols('d')**2)) / sqrt(symbols('b')**2 + symbols('c')**2 + symbols('d')**2), symbols('d') * acos( symbols('a') / sqrt( symbols('a')**2 + symbols('b')**2 + symbols('c')**2 + symbols('d')**2)) / sqrt(symbols('b')**2 + symbols('c')**2 + symbols('d')**2))) #Tests a quaternion where the real part is a string. self.assertEqual( self.q7._ln(), Quaternion( log( sqrt( symbols( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' )**2 + 29)), 2 * sqrt(29) * acos( symbols( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ) / sqrt( symbols( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' )**2 + 29)) / 29, 3 * sqrt(29) * acos( symbols( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ) / sqrt( symbols( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' )**2 + 29)) / 29, 4 * sqrt(29) * acos( symbols( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ) / sqrt( symbols( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' )**2 + 29)) / 29)) #Tests a quaternion with one of the quaternion units being a symbolic variable and the rest zero. self.assertEqual( self.q8._ln(), Quaternion(log(sqrt(symbols('a')**2)), 0, 0, pi * symbols('a') / (2 * sqrt(symbols('a')**2))))
def test_norm(self): #By definition ||q|| = sqrt(w^2 + x^2 + y^2 + z^2). Tested from definition with both sqrt and raise to the power 0.5. q = Quaternion(w, x, y, z) self.assertEqual(q.norm(), sqrt(w**2 + x**2 + y**2 + z**2)) self.assertEqual(q.norm(), (w**2 + x**2 + y**2 + z**2)**0.5)
def test_pow_cos_sin(self): # 1. Testing to square a complex quaternion # 2. Something raised to 0 should be 1 # 3. Something raised to 1 should be the same # 4. Testing to square again # 5. Testing to raise to the third power # 6. Should not be possible to raise a quaternion by a quaternion q1 = Quaternion(1, 2, 3, 4) q2 = Quaternion(0, 0, 0, 1) self.assertTrue( q1.pow_cos_sin(2) == Quaternion( 30 * cos(2 * acos(sqrt(30) / 30)), 60 * sqrt(29) * sin(2 * acos(sqrt(30) / 30)) / 29, 90 * sqrt(29) * sin(2 * acos(sqrt(30) / 30)) / 29, 120 * sqrt(29) * sin(2 * acos(sqrt(30) / 30)) / 29)) self.assertEqual(q2.pow_cos_sin(0), Quaternion(1, 0, 0, 0)) self.assertEqual(q2.pow_cos_sin(1), Quaternion(0, 0, 0, 1)) self.assertEqual(q2.pow_cos_sin(2), Quaternion(-1, 0, 0, 0)) self.assertEqual(q2.pow_cos_sin(3), Quaternion(0, 0, 0, -1)) with self.assertRaises(ValueError): q1.pow_cos_sin(q2)
def test_to_axis_angle(self): # 1. Testing to see if simple quaternion has 0 angle # 2. and 3. testing more complex quaternion to test axis and angle # 4. Should not be possible to calculate axis and angle of undefined quaternion # 5. Another random test to see if axis and angle are correct q = Quaternion(1, 0, 0, 0) q1 = Quaternion(0, 1, 1, 1) q2 = Quaternion(1, 2, 3, 4) q_var = Quaternion(1, x, y, z) (axis, angle) = q.to_axis_angle() self.assertEqual(angle, 0) (axis, angle) = q1.to_axis_angle() self.assertEqual(axis, (sqrt(3) / 3, sqrt(3) / 3, sqrt(3) / 3)) self.assertEqual(angle, pi) with self.assertRaises(AttributeError): (axis, angle) = q_var.to_axis_angle() self.assertTrue(q2.to_axis_angle() == ((2 * sqrt(29) / 29, 3 * sqrt(29) / 29, 4 * sqrt(29) / 29), 2 * acos(sqrt(30) / 30)))
def test_eval_conjugate(self): # Calculates the conjugate of a Quaternion. # Tested with integers, variables, infinity, and nan q1 = Quaternion(1, 2, 3, 4) q2 = Quaternion(1, x, 1, y) q3 = Quaternion(0, 0, 0, 0) q4 = Quaternion(float("inf"), float("inf"), float("inf"), float("inf")) q5 = Quaternion(float("nan"), float("nan"), float("nan"), float("nan")) self.assertEqual(q1._eval_conjugate(), Quaternion(1, -2, -3, -4)) self.assertEqual(q2._eval_conjugate(), Quaternion(1, -x, -1, -y)) self.assertEqual(q3._eval_conjugate(), Quaternion(0, 0, 0, 0)) self.assertEqual(q4._eval_conjugate(), Quaternion( float("inf"), -float("inf"), -float("inf"), -float("inf"))) self.assertEqual(q5._eval_conjugate(), Quaternion( float("nan"), float("nan"), float("nan"), float("nan"))) self.assertRaises(TypeError, q1._eval_conjugate, 1)
def test_exp(self): # Calculates the exponential of a Quaternion q (e^q). # Tested with integers, variables, infinity, and nan q1 = Quaternion(1, 2, 3, 4) q2 = Quaternion(1, x, 1, y) q3 = Quaternion(0, 0, 0, 0) q4 = Quaternion(float("inf"), float("inf"), float("inf"), float("inf")) q5 = Quaternion(float("nan"), float("nan"), float("nan"), float("nan")) self.assertEqual(q1.exp(), Quaternion(E*cos(sqrt(29)), 2*sqrt(29)*E*sin( sqrt(29))/29, 3*sqrt(29)*E*sin(sqrt(29))/29, 4*sqrt(29)*E*sin(sqrt(29))/29)) self.assertEqual(q2.exp(), Quaternion(E*cos(sqrt(x**2 + y**2 + 1)), E*x*sin(sqrt(x**2 + y**2 + 1))/sqrt(x**2 + y**2 + 1), E*sin(sqrt(x**2 + y**2 + 1))/sqrt(x**2 + y**2 + 1), E*y*sin(sqrt(x**2 + y**2 + 1))/sqrt(x**2 + y**2 + 1))) self.assertEqual(q3.exp(), Quaternion( 1, float("nan"), float("nan"), float("nan"))) self.assertEqual(q4.exp(), Quaternion( float("nan"), float("nan"), float("nan"), float("nan"))) self.assertEqual(q5.exp(), Quaternion( float("nan"), float("nan"), float("nan"), float("nan"))) self.assertRaises(TypeError, q1.exp, "a")