```#!/usr/bin/env python

from __future__ import print_function, division, absolute_import
import os
import operator

import math
import numpy as np
import quaternion
from numpy import *
import pytest

try:
import scipy
has_scipy = True
except:
has_scipy = False

from sys import platform
on_windows = ('win' in platform.lower() and not 'darwin' in platform.lower())

eps = np.finfo(float).eps

def allclose(*args, **kwargs):
kwargs.update({'verbose': True})
return quaternion.allclose(*args, **kwargs)

def passer(b):
pass
# Change this to strict_assert = assert_ to check for missing tests
strict_assert = passer

def ufunc_binary_utility(array1, array2, op, rtol=2*eps, atol=0.0):
"""Make sure broadcasting is consistent with individual operations

Given two arrays, we expect a broadcast binary operation to be consistent with the individual operations.  This
utility function simply goes through and checks that that is true.  For example, if the input operation is `*`,
this function checks for each `i` that

array1[i] * array2  ==  np.array([array1[i]*array2[j] for j in range(len(array2))])

"""
for arg1 in array1:
assert allclose(op(arg1, array2),
np.array([op(arg1, arg2) for arg2 in array2]),
rtol=rtol, atol=atol)
for arg2 in array2:
assert allclose(op(array1, arg2),
np.array([op(arg1, arg2) for arg1 in array1]),
rtol=rtol, atol=atol)

if array1.shape == array2.shape:
assert allclose(op(array1, array2),
np.array([op(arg1, arg2) for arg1, arg2 in zip(array1, array2)]),
rtol=rtol, atol=atol)

# The following fixtures are used to establish some re-usable data
# for the tests; they need to be re-constructed because some of the
# tests will change the values, but we want the values to be constant
# on every entry into a test.

@pytest.fixture
def Qs():
return make_Qs()

def make_Qs():
q_nan1 = quaternion.quaternion(np.nan, 0., 0., 0.)
q_inf1 = quaternion.quaternion(np.inf, 0., 0., 0.)
q_minf1 = quaternion.quaternion(-np.inf, 0., 0., 0.)
q_0 = quaternion.quaternion(0., 0., 0., 0.)
q_1 = quaternion.quaternion(1., 0., 0., 0.)
x = quaternion.quaternion(0., 1., 0., 0.)
y = quaternion.quaternion(0., 0., 1., 0.)
z = quaternion.quaternion(0., 0., 0., 1.)
Q = quaternion.quaternion(1.1, 2.2, 3.3, 4.4)
Qneg = quaternion.quaternion(-1.1, -2.2, -3.3, -4.4)
Qbar = quaternion.quaternion(1.1, -2.2, -3.3, -4.4)
Qnormalized = quaternion.quaternion(0.18257418583505537115232326093360,
0.36514837167011074230464652186720,
0.54772255750516611345696978280080,
0.73029674334022148460929304373440)
Qlog = quaternion.quaternion(1.7959088706354, 0.515190292664085,
0.772785438996128, 1.03038058532817)
Qexp = quaternion.quaternion(2.81211398529184, -0.392521193481878,
-0.588781790222817, -0.785042386963756)
return np.array([q_nan1, q_inf1, q_minf1, q_0, q_1, x, y, z, Q, Qneg, Qbar, Qnormalized, Qlog, Qexp],
dtype=np.quaternion)

Qs_array = make_Qs()

q_nan1, q_inf1, q_minf1, q_0, q_1, x, y, z, Q, Qneg, Qbar, Qnormalized, Qlog, Qexp, = range(len(Qs_array))
Qs_zero = [i for i in range(len(Qs_array)) if not Qs_array[i].nonzero()]
Qs_nonzero = [i for i in range(len(Qs_array)) if Qs_array[i].nonzero()]
Qs_nan = [i for i in range(len(Qs_array)) if Qs_array[i].isnan()]
Qs_nonnan = [i for i in range(len(Qs_array)) if not Qs_array[i].isnan()]
Qs_nonnannonzero = [i for i in range(len(Qs_array)) if not Qs_array[i].isnan() and Qs_array[i].nonzero()]
Qs_inf = [i for i in range(len(Qs_array)) if Qs_array[i].isinf()]
Qs_noninf = [i for i in range(len(Qs_array)) if not Qs_array[i].isinf()]
Qs_noninfnonzero = [i for i in range(len(Qs_array)) if not Qs_array[i].isinf() and Qs_array[i].nonzero()]
Qs_finite = [i for i in range(len(Qs_array)) if Qs_array[i].isfinite()]
Qs_nonfinite = [i for i in range(len(Qs_array)) if not Qs_array[i].isfinite()]
Qs_finitenonzero = [i for i in range(len(Qs_array)) if Qs_array[i].isfinite() and Qs_array[i].nonzero()]

@pytest.fixture
def Rs():
ones = [0, -1., 1.]
rs = [np.quaternion(w, x, y, z).normalized() for w in ones for x in ones for y in ones for z in ones][1:]
np.random.seed(1842)
rs = rs + [r.normalized() for r in [np.quaternion(np.random.uniform(-1, 1), np.random.uniform(-1, 1),
np.random.uniform(-1, 1), np.random.uniform(-1, 1)) for i in range(20)]]
return np.array(rs)

def test_quaternion_members():
Q = quaternion.quaternion(1.1, 2.2, 3.3, 4.4)
assert Q.real == 1.1
assert Q.w == 1.1
assert Q.x == 2.2
assert Q.y == 3.3
assert Q.z == 4.4

def test_quaternion_constructors():
Q = quaternion.quaternion(2.2, 3.3, 4.4)
assert Q.real == 0.0
assert Q.w == 0.0
assert Q.x == 2.2
assert Q.y == 3.3
assert Q.z == 4.4

P = quaternion.quaternion(1.1, 2.2, 3.3, 4.4)
Q = quaternion.quaternion(P)
assert Q.real == 1.1
assert Q.w == 1.1
assert Q.x == 2.2
assert Q.y == 3.3
assert Q.z == 4.4

Q = quaternion.quaternion(1.1)
assert Q.real == 1.1
assert Q.w == 1.1
assert Q.x == 0.0
assert Q.y == 0.0
assert Q.z == 0.0

Q = quaternion.quaternion(0.0)
assert Q.real == 0.0
assert Q.w == 0.0
assert Q.x == 0.0
assert Q.y == 0.0
assert Q.z == 0.0

with pytest.raises(TypeError):
quaternion.quaternion(1.2, 3.4)

with pytest.raises(TypeError):
quaternion.quaternion(1.2, 3.4, 5.6, 7.8, 9.0)

def test_constants():
assert quaternion.one == np.quaternion(1.0, 0.0, 0.0, 0.0)
assert quaternion.x == np.quaternion(0.0, 1.0, 0.0, 0.0)
assert quaternion.y == np.quaternion(0.0, 0.0, 1.0, 0.0)
assert quaternion.z == np.quaternion(0.0, 0.0, 0.0, 1.0)

def test_isclose():
from quaternion import x, y

assert np.array_equal(quaternion.isclose([1e10*x, 1e-7*y], [1.00001e10*x, 1e-8*y], rtol=1.e-5, atol=2.e-8),
np.array([True, False]))
assert np.array_equal(quaternion.isclose([1e10*x, 1e-8*y], [1.00001e10*x, 1e-9*y], rtol=1.e-5, atol=2.e-8),
np.array([True, True]))
assert np.array_equal(quaternion.isclose([1e10*x, 1e-8*y], [1.0001e10*x, 1e-9*y], rtol=1.e-5, atol=2.e-8),
np.array([False, True]))
assert np.array_equal(quaternion.isclose([x, np.nan*y], [x, np.nan*y]),
np.array([True, False]))
assert np.array_equal(quaternion.isclose([x, np.nan*y], [x, np.nan*y], equal_nan=True),
np.array([True, True]))

np.random.seed(1234)
a = quaternion.as_quat_array(np.random.random((3, 5, 4)))
assert quaternion.allclose(1e10 * a, 1.00001e10 * a, rtol=1.e-5, atol=2.e-8, verbose=True) == True
assert quaternion.allclose(1e-7 * a, 1e-8 * a, rtol=1.e-5, atol=2.e-8) == False
assert quaternion.allclose(1e10 * a, 1.00001e10 * a, rtol=1.e-5, atol=2.e-8, verbose=True) == True
assert quaternion.allclose(1e-8 * a, 1e-9 * a, rtol=1.e-5, atol=2.e-8, verbose=True) == True
assert quaternion.allclose(1e10 * a, 1.0001e10 * a, rtol=1.e-5, atol=2.e-8) == False
assert quaternion.allclose(1e-8 * a, 1e-9 * a, rtol=1.e-5, atol=2.e-8, verbose=True) == True
assert quaternion.allclose(np.nan * a, np.nan * a) == False
assert quaternion.allclose(np.nan * a, np.nan * a, equal_nan=True, verbose=True) == True

@pytest.mark.parametrize("q", make_Qs())
with pytest.raises(TypeError):
s = int(q)
with pytest.raises(TypeError):
s = float(q)
with pytest.raises(TypeError):
a = np.zeros(3, dtype=int)
a[0] = q
with pytest.raises(TypeError):
a = np.zeros(3)
a[0] = q

def test_as_float_quat(Qs):
qs = Qs[Qs_nonnan]
for quats in [qs, np.vstack((qs,)*3), np.vstack((qs,)*(3*5)).reshape((3, 5)+qs.shape),
np.vstack((qs,)*(3*5*6)).reshape((3, 5, 6)+qs.shape)]:
floats = quaternion.as_float_array(quats)
assert floats.shape == quats.shape+(4,)
assert allclose(quaternion.as_quat_array(floats), quats)
assert allclose(quaternion.from_float_array(floats), quats)
# Test that we can handle a list just like an array
assert np.array_equal(quaternion.as_quat_array(floats), quaternion.as_quat_array(floats.tolist()))
a = np.arange(12).reshape(3, 4)
assert np.array_equal(quaternion.as_float_array(quaternion.as_quat_array(a)),
a.astype(float))
assert quaternion.as_float_array(quaternion.x).ndim == 1

def test_as_rotation_matrix(Rs):
def quat_mat(quat):
return np.array([(quat * v * quat.inverse()).vec for v in [quaternion.x, quaternion.y, quaternion.z]]).T

def quat_mat_vec(quats):
mat_vec = np.array([quaternion.as_float_array(quats * v * np.reciprocal(quats))[..., 1:]
for v in [quaternion.x, quaternion.y, quaternion.z]])
return np.transpose(mat_vec, tuple(range(mat_vec.ndim))[1:-1]+(-1, 0))

with pytest.raises(ZeroDivisionError):
quaternion.as_rotation_matrix(quaternion.zero)

for R in Rs:
# Test correctly normalized rotors:
assert allclose(quat_mat(R), quaternion.as_rotation_matrix(R), atol=2*eps)
# Test incorrectly normalized rotors:
assert allclose(quat_mat(R), quaternion.as_rotation_matrix(1.1*R), atol=2*eps)

Rs0 = Rs.copy()
Rs0[Rs.shape[0]//2] = quaternion.zero
with pytest.raises(ZeroDivisionError):
quaternion.as_rotation_matrix(Rs0)

# Test correctly normalized rotors:
assert allclose(quat_mat_vec(Rs), quaternion.as_rotation_matrix(Rs), atol=2*eps)
# Test incorrectly normalized rotors:
assert allclose(quat_mat_vec(Rs), quaternion.as_rotation_matrix(1.1*Rs), atol=2*eps)

# Simply test that this function succeeds and returns the right shape
assert quaternion.as_rotation_matrix(Rs.reshape((2, 5, 10))).shape == (2, 5, 10, 3, 3)

def test_from_rotation_matrix(Rs):
try:
from scipy import linalg
have_linalg = True
except ImportError:
have_linalg = False

for nonorthogonal in [True, False]:
if nonorthogonal and have_linalg:
rot_mat_eps = 10*eps
else:
rot_mat_eps = 5*eps
for i, R1 in enumerate(Rs):
R2 = quaternion.from_rotation_matrix(quaternion.as_rotation_matrix(R1), nonorthogonal=nonorthogonal)
d = quaternion.rotation_intrinsic_distance(R1, R2)
assert d < rot_mat_eps, (i, R1, R2, d)  # Can't use allclose here; we don't care about rotor sign

Rs2 = quaternion.from_rotation_matrix(quaternion.as_rotation_matrix(Rs), nonorthogonal=nonorthogonal)
for R1, R2 in zip(Rs, Rs2):
d = quaternion.rotation_intrinsic_distance(R1, R2)
assert d < rot_mat_eps, (R1, R2, d)  # Can't use allclose here; we don't care about rotor sign

Rs3 = Rs.reshape((2, 5, 10))
Rs4 = quaternion.from_rotation_matrix(quaternion.as_rotation_matrix(Rs3))
for R3, R4 in zip(Rs3.flatten(), Rs4.flatten()):
d = quaternion.rotation_intrinsic_distance(R3, R4)
assert d < rot_mat_eps, (R3, R4, d)  # Can't use allclose here; we don't care about rotor sign

def test_as_rotation_vector():
np.random.seed(1234)
n_tests = 1000
vecs = np.random.uniform(high=math.pi/math.sqrt(3), size=n_tests*3).reshape((n_tests, 3))
quats = np.zeros(vecs.shape[:-1]+(4,))
quats[..., 1:] = vecs[...]
quats = quaternion.as_quat_array(quats)
quats = np.exp(quats/2)
quat_vecs = quaternion.as_rotation_vector(quats)
assert allclose(quat_vecs, vecs)

def test_from_rotation_vector():
np.random.seed(1234)
n_tests = 1000
vecs = np.random.uniform(high=math.pi/math.sqrt(3), size=n_tests*3).reshape((n_tests, 3))
quats = np.zeros(vecs.shape[:-1]+(4,))
quats[..., 1:] = vecs[...]
quats = quaternion.as_quat_array(quats)
quats = np.exp(quats/2)
quat_vecs = quaternion.as_rotation_vector(quats)
quats2 = quaternion.from_rotation_vector(quat_vecs)
assert allclose(quats, quats2)

def test_rotate_vectors(Rs):
np.random.seed(1234)
# Test (1)*(1)
vecs = np.random.rand(3)
quats = quaternion.z
vecsprime = quaternion.rotate_vectors(quats, vecs)
assert np.allclose(vecsprime,
(quats * quaternion.quaternion(*vecs) * quats.inverse()).vec,
rtol=0.0, atol=0.0)
assert quats.shape + vecs.shape == vecsprime.shape, ("Out of shape!", quats.shape, vecs.shape, vecsprime.shape)
# Test (1)*(5)
vecs = np.random.rand(5, 3)
quats = quaternion.z
vecsprime = quaternion.rotate_vectors(quats, vecs)
for i, vec in enumerate(vecs):
assert np.allclose(vecsprime[i],
(quats * quaternion.quaternion(*vec) * quats.inverse()).vec,
rtol=0.0, atol=0.0)
assert quats.shape + vecs.shape == vecsprime.shape, ("Out of shape!", quats.shape, vecs.shape, vecsprime.shape)
# Test (1)*(5) inner axis
vecs = np.random.rand(3, 5)
quats = quaternion.z
vecsprime = quaternion.rotate_vectors(quats, vecs, axis=-2)
for i, vec in enumerate(vecs.T):
assert np.allclose(vecsprime[:, i],
(quats * quaternion.quaternion(*vec) * quats.inverse()).vec,
rtol=0.0, atol=0.0)
assert quats.shape + vecs.shape == vecsprime.shape, ("Out of shape!", quats.shape, vecs.shape, vecsprime.shape)
# Test (N)*(1)
vecs = np.random.rand(3)
quats = Rs
vecsprime = quaternion.rotate_vectors(quats, vecs)
assert np.allclose(vecsprime,
[vprime.vec for vprime in quats * quaternion.quaternion(*vecs) * ~quats],
rtol=1e-15, atol=1e-15)
assert quats.shape + vecs.shape == vecsprime.shape, ("Out of shape!", quats.shape, vecs.shape, vecsprime.shape)
# Test (N)*(5)
vecs = np.random.rand(5, 3)
quats = Rs
vecsprime = quaternion.rotate_vectors(quats, vecs)
for i, vec in enumerate(vecs):
assert np.allclose(vecsprime[:, i],
[vprime.vec for vprime in quats * quaternion.quaternion(*vec) * ~quats],
rtol=1e-15, atol=1e-15)
assert quats.shape + vecs.shape == vecsprime.shape, ("Out of shape!", quats.shape, vecs.shape, vecsprime.shape)
# Test (N)*(5) inner axis
vecs = np.random.rand(3, 5)
quats = Rs
vecsprime = quaternion.rotate_vectors(quats, vecs, axis=-2)
for i, vec in enumerate(vecs.T):
assert np.allclose(vecsprime[:, :, i],
[vprime.vec for vprime in quats * quaternion.quaternion(*vec) * ~quats],
rtol=1e-15, atol=1e-15)
assert quats.shape + vecs.shape == vecsprime.shape, ("Out of shape!", quats.shape, vecs.shape, vecsprime.shape)

def test_allclose(Qs):
for q in Qs[Qs_nonnan]:
assert quaternion.allclose(q, q, rtol=0.0, atol=0.0)
assert quaternion.allclose(Qs[Qs_nonnan], Qs[Qs_nonnan], rtol=0.0, atol=0.0)

for q in Qs[Qs_finitenonzero]:
assert quaternion.allclose(q, q*(1+1e-13), rtol=1.1e-13, atol=0.0)
assert ~quaternion.allclose(q, q*(1+1e-13), rtol=0.9e-13, atol=0.0)
for e in [quaternion.one, quaternion.x, quaternion.y, quaternion.z]:
assert quaternion.allclose(q, q+(1e-13*e), rtol=0.0, atol=1.1e-13)
assert ~quaternion.allclose(q, q+(1e-13*e), rtol=0.0, atol=0.9e-13)
assert quaternion.allclose(Qs[Qs_finitenonzero], Qs[Qs_finitenonzero]*(1+1e-13), rtol=1.1e-13, atol=0.0)
assert ~quaternion.allclose(Qs[Qs_finitenonzero], Qs[Qs_finitenonzero]*(1+1e-13), rtol=0.9e-13, atol=0.0)
for e in [quaternion.one, quaternion.x, quaternion.y, quaternion.z]:
assert quaternion.allclose(Qs[Qs_finite], Qs[Qs_finite]+(1e-13*e), rtol=0.0, atol=1.1e-13)
assert ~quaternion.allclose(Qs[Qs_finite], Qs[Qs_finite]+(1e-13*e), rtol=0.0, atol=0.9e-13)
assert quaternion.allclose(Qs[Qs_zero], Qs[Qs_zero]*2, rtol=0.0, atol=1.1e-13)

for qnan in Qs[Qs_nan]:
assert ~quaternion.allclose(qnan, qnan, rtol=1.0, atol=1.0)
for q in Qs:
assert ~quaternion.allclose(q, qnan, rtol=1.0, atol=1.0)

def test_from_spherical_coords():
np.random.seed(1843)
random_angles = [[np.random.uniform(-np.pi, np.pi), np.random.uniform(-np.pi, np.pi)]
for i in range(5000)]
for vartheta, varphi in random_angles:
q = quaternion.from_spherical_coords(vartheta, varphi)
assert abs((np.quaternion(0, 0, 0, varphi / 2.).exp() * np.quaternion(0, 0, vartheta / 2., 0).exp())
- q) < 1.e-15
xprime = q * quaternion.x * q.inverse()
yprime = q * quaternion.y * q.inverse()
zprime = q * quaternion.z * q.inverse()
nhat = np.quaternion(0.0, math.sin(vartheta)*math.cos(varphi), math.sin(vartheta)*math.sin(varphi),
math.cos(vartheta))
thetahat = np.quaternion(0.0, math.cos(vartheta)*math.cos(varphi), math.cos(vartheta)*math.sin(varphi),
-math.sin(vartheta))
phihat = np.quaternion(0.0, -math.sin(varphi), math.cos(varphi), 0.0)
assert abs(xprime - thetahat) < 1.e-15
assert abs(yprime - phihat) < 1.e-15
assert abs(zprime - nhat) < 1.e-15
assert np.max(np.abs(quaternion.from_spherical_coords(random_angles)
- np.array([quaternion.from_spherical_coords(vartheta, varphi)
for vartheta, varphi in random_angles]))) < 1.e-15

def test_as_spherical_coords(Rs):
np.random.seed(1843)
# First test on rotors that are precisely spherical-coordinate rotors
random_angles = [[np.random.uniform(0, np.pi), np.random.uniform(0, 2*np.pi)]
for i in range(5000)]
for vartheta, varphi in random_angles:
vartheta2, varphi2 = quaternion.as_spherical_coords(quaternion.from_spherical_coords(vartheta, varphi))
varphi2 = (varphi2 + 2*np.pi) if varphi2 < 0 else varphi2
assert abs(vartheta - vartheta2) < 1e-12, ((vartheta, varphi), (vartheta2, varphi2))
assert abs(varphi - varphi2) < 1e-12, ((vartheta, varphi), (vartheta2, varphi2))
# Now test that arbitrary rotors rotate z to the appropriate location
for R in Rs:
vartheta, varphi = quaternion.as_spherical_coords(R)
R2 = quaternion.from_spherical_coords(vartheta, varphi)
assert (R*quaternion.z*R.inverse() - R2*quaternion.z*R2.inverse()).abs() < 4e-15, (R, R2, (vartheta, varphi))

def test_from_euler_angles():
np.random.seed(1843)
random_angles = [[np.random.uniform(-np.pi, np.pi),
np.random.uniform(-np.pi, np.pi),
np.random.uniform(-np.pi, np.pi)]
for i in range(5000)]
for alpha, beta, gamma in random_angles:
assert abs((np.quaternion(0, 0, 0, alpha / 2.).exp()
* np.quaternion(0, 0, beta / 2., 0).exp()
* np.quaternion(0, 0, 0, gamma / 2.).exp()
)
- quaternion.from_euler_angles(alpha, beta, gamma)) < 1.e-15
assert np.max(np.abs(quaternion.from_euler_angles(random_angles)
- np.array([quaternion.from_euler_angles(alpha, beta, gamma)
for alpha, beta, gamma in random_angles]))) < 1.e-15

def test_as_euler_angles():
np.random.seed(1843)
random_angles = [[np.random.uniform(-np.pi, np.pi),
np.random.uniform(-np.pi, np.pi),
np.random.uniform(-np.pi, np.pi)]
for i in range(5000)]
for alpha, beta, gamma in random_angles:
R1 = quaternion.from_euler_angles(alpha, beta, gamma)
R2 = quaternion.from_euler_angles(*list(quaternion.as_euler_angles(R1)))
d = quaternion.rotation_intrinsic_distance(R1, R2)
assert d < 6e3*eps, ((alpha, beta, gamma), R1, R2, d)  # Can't use allclose here; we don't care about rotor sign
q0 = quaternion.quaternion(0, 0.6, 0.8, 0)
assert q0.norm() == 1.0
assert abs(q0 - quaternion.from_euler_angles(*list(quaternion.as_euler_angles(q0)))) < 1.e-15

# Unary bool returners
def test_quaternion_nonzero(Qs):
assert not Qs[q_0].nonzero()  # Do this one explicitly, to not use circular logic
assert Qs[q_1].nonzero()  # Do this one explicitly, to not use circular logic
for q in Qs[Qs_zero]:
assert not q.nonzero()
for q in Qs[Qs_nonzero]:
assert q.nonzero()

def test_quaternion_isnan(Qs):
assert not Qs[q_0].isnan()  # Do this one explicitly, to not use circular logic
assert not Qs[q_1].isnan()  # Do this one explicitly, to not use circular logic
assert Qs[q_nan1].isnan()  # Do this one explicitly, to not use circular logic
for q in Qs[Qs_nan]:
assert q.isnan()
for q in Qs[Qs_nonnan]:
assert not q.isnan()

def test_quaternion_isinf(Qs):
assert not Qs[q_0].isinf()  # Do this one explicitly, to not use circular logic
assert not Qs[q_1].isinf()  # Do this one explicitly, to not use circular logic
assert Qs[q_inf1].isinf()  # Do this one explicitly, to not use circular logic
assert Qs[q_minf1].isinf()  # Do this one explicitly, to not use circular logic
for q in Qs[Qs_inf]:
assert q.isinf()
for q in Qs[Qs_noninf]:
assert not q.isinf()

def test_quaternion_isfinite(Qs):
assert not Qs[q_nan1].isfinite()  # Do this one explicitly, to not use circular logic
assert not Qs[q_inf1].isfinite()  # Do this one explicitly, to not use circular logic
assert not Qs[q_minf1].isfinite()  # Do this one explicitly, to not use circular logic
assert Qs[q_0].isfinite()  # Do this one explicitly, to not use circular logic
for q in Qs[Qs_nonfinite]:
assert not q.isfinite()
for q in Qs[Qs_finite]:
assert q.isfinite()

# Binary bool returners
def test_quaternion_equal(Qs):
for j in Qs_nonnan:
assert Qs[j] == Qs[j]  # self equality
for k in range(len(Qs)):  # non-self inequality
assert (j == k) or (not (Qs[j] == Qs[k]))
for q in Qs:
for p in Qs[Qs_nan]:
assert not q == p  # nan should never equal anything

def test_quaternion_not_equal(Qs):
for j in Qs_nonnan:
assert not (Qs[j] != Qs[j])  # self non-not_equality
for k in Qs_nonnan:  # non-self not_equality
assert (j == k) or (Qs[j] != Qs[k])
for q in Qs:
for p in Qs[Qs_nan]:
assert q != p  # nan should never equal anything

def test_quaternion_richcompare(Qs):
for p in Qs:
for q in Qs[Qs_nan]:
assert not p < q
assert not q < p
assert not p <= q
assert not q <= p
assert not p.greater(q)
assert not q.greater(p)
assert not p.greater_equal(q)
assert not q.greater_equal(p)
for j in Qs_nonnan:
p = Qs[j]
assert (p < Qs[q_inf1]) or (j == q_inf1)
assert (p <= Qs[q_inf1])
assert (Qs[q_minf1] < p) or (j == q_minf1)
assert (Qs[q_minf1] <= p)
assert (Qs[q_inf1].greater(p)) or (j == q_inf1)
assert (Qs[q_inf1].greater_equal(p))
assert (p.greater(Qs[q_minf1])) or (j == q_minf1)
assert (p.greater_equal(Qs[q_minf1]))
for p in [Qs[q_1], Qs[x], Qs[y], Qs[z], Qs[Q], Qs[Qbar]]:
assert Qs[q_0] < p
assert Qs[q_0] <= p
assert p.greater(Qs[q_0])
assert p.greater_equal(Qs[q_0])
for p in [Qs[Qneg]]:
assert p < Qs[q_0]
assert p <= Qs[q_0]
assert Qs[q_0].greater(p)
assert Qs[q_0].greater_equal(p)
for p in [Qs[x], Qs[y], Qs[z]]:
assert p < Qs[q_1]
assert p <= Qs[q_1]
assert Qs[q_1].greater(p)
assert Qs[q_1].greater_equal(p)
for p in [Qs[Qlog], Qs[Qexp]]:
assert Qs[q_1] < p
assert Qs[q_1] <= p
assert p.greater(Qs[q_1])
assert p.greater_equal(Qs[q_1])

# Unary float returners
def test_quaternion_absolute(Qs):
for q in Qs[Qs_nan]:
assert np.isnan(q.abs())
for q in Qs[Qs_inf]:
if on_windows:
assert np.isinf(q.abs()) or np.isnan(q.abs())
else:
assert np.isinf(q.abs())
for q, a in [(Qs[q_0], 0.0), (Qs[q_1], 1.0), (Qs[x], 1.0), (Qs[y], 1.0), (Qs[z], 1.0),
(Qs[Q], np.sqrt(Qs[Q].w ** 2 + Qs[Q].x ** 2 + Qs[Q].y ** 2 + Qs[Q].z ** 2)),
(Qs[Qbar], np.sqrt(Qs[Q].w ** 2 + Qs[Q].x ** 2 + Qs[Q].y ** 2 + Qs[Q].z ** 2))]:
assert np.allclose(q.abs(), a)

def test_quaternion_norm(Qs):
for q in Qs[Qs_nan]:
assert np.isnan(q.norm())
for q in Qs[Qs_inf]:
if on_windows:
assert np.isinf(q.norm()) or np.isnan(q.norm())
else:
assert np.isinf(q.norm())
for q, a in [(Qs[q_0], 0.0), (Qs[q_1], 1.0), (Qs[x], 1.0), (Qs[y], 1.0), (Qs[z], 1.0),
(Qs[Q], Qs[Q].w ** 2 + Qs[Q].x ** 2 + Qs[Q].y ** 2 + Qs[Q].z ** 2),
(Qs[Qbar], Qs[Q].w ** 2 + Qs[Q].x ** 2 + Qs[Q].y ** 2 + Qs[Q].z ** 2)]:
assert np.allclose(q.norm(), a)

# Unary quaternion returners
def test_quaternion_negative(Qs):
assert -Qs[Q] == Qs[Qneg]
for q in Qs[Qs_finite]:
assert -q == -1.0 * q
for q in Qs[Qs_nonnan]:
assert -(-q) == q

def test_quaternion_conjugate(Qs):
assert Qs[Q].conjugate() == Qs[Qbar]
for q in Qs[Qs_nonnan]:
assert q.conjugate() == q.conj()
assert q.conjugate().conjugate() == q
c = q.conjugate()
assert c.w == q.w
assert c.x == -q.x
assert c.y == -q.y
assert c.z == -q.z

def test_quaternion_sqrt(Qs):
sqrt_precision = 2.e-15
for q in Qs[Qs_finitenonzero]:
assert allclose(q.sqrt() * q.sqrt(), q, rtol=sqrt_precision)
# Ensure that non-unit quaternions are handled correctly
for s in [1, -1, 2, -2, 3.4, -3.4]:
for r in [1, quaternion.x, quaternion.y, quaternion.z]:
srq = s*r*q
assert allclose(srq.sqrt() * srq.sqrt(), srq, rtol=sqrt_precision)
# Ensure that inputs close to zero are handled gracefully
sqrt_dbl_min = math.sqrt(np.finfo(float).tiny)
assert quaternion.quaternion(0, 0, 0, 2e-8*sqrt_dbl_min).sqrt() == quaternion.quaternion(0, 0, 0, 0)
assert quaternion.quaternion(0, 0, 0, 0.9999*sqrt_dbl_min).sqrt() == quaternion.quaternion(0, 0, 0, 0)
assert quaternion.quaternion(0, 0, 0, 1e-16*sqrt_dbl_min).sqrt() == quaternion.quaternion(0, 0, 0, 0)
assert quaternion.quaternion(0, 0, 0, 1.1*sqrt_dbl_min).sqrt() != quaternion.quaternion(0, 0, 0, 0)

def test_quaternion_square(Qs):
square_precision = 1.e-15
for q in Qs[Qs_finite]:
assert np.norm(q*q - q**2) < square_precision
a = np.array([q])
assert np.norm(a**2 - np.array([q**2])) < square_precision

def test_quaternion_log_exp(Qs):
qlogexp_precision = 4.e-15
assert (Qs[Q].log() - Qs[Qlog]).abs() < qlogexp_precision
assert (Qs[Q].exp() - Qs[Qexp]).abs() < qlogexp_precision
assert (Qs[Q].log().exp() - Qs[Q]).abs() < qlogexp_precision
assert (Qs[Q].exp().log() - Qs[Q]).abs() > qlogexp_precision  # Note order of operations!
assert quaternion.one.log() == quaternion.zero
assert quaternion.x.log() == (np.pi / 2) * quaternion.x
assert quaternion.y.log() == (np.pi / 2) * quaternion.y
assert quaternion.z.log() == (np.pi / 2) * quaternion.z
assert (-quaternion.one).log() == (np.pi) * quaternion.x
strict_assert(False)  # logs of interesting scalars * basis vectors
strict_assert(False)  # logs of negative scalars

def test_angle(Rs):
angle_precision = 4.e-15
unit_vecs = [quaternion.x, quaternion.y, quaternion.z,
-quaternion.x, -quaternion.y, -quaternion.z]
for u in unit_vecs:
for theta in linspace(-2 * np.pi, 2 * np.pi, num=50):
assert abs((theta * u / 2).exp().angle() - abs(theta)) < angle_precision

def test_quaternion_normalized(Qs):
assert abs(Qs[Q].normalized()-Qs[Qnormalized]) < 4e-16
for q in Qs[Qs_finitenonzero]:
assert abs(q.normalized().abs() - 1.0) < 1.e-15

def test_quaternion_parity_conjugates(Qs):
for q in Qs[Qs_finite]:
assert q.x_parity_conjugate() == np.quaternion(q.w, q.x, -q.y, -q.z)
assert q.y_parity_conjugate() == np.quaternion(q.w, -q.x, q.y, -q.z)
assert q.z_parity_conjugate() == np.quaternion(q.w, -q.x, -q.y, q.z)
assert q.parity_conjugate() == np.quaternion(q.w, q.x, q.y, q.z)
assert np.array_equal(np.x_parity_conjugate(Qs[Qs_finite]),
np.array([q.x_parity_conjugate() for q in Qs[Qs_finite]]))
assert np.array_equal(np.y_parity_conjugate(Qs[Qs_finite]),
np.array([q.y_parity_conjugate() for q in Qs[Qs_finite]]))
assert np.array_equal(np.z_parity_conjugate(Qs[Qs_finite]),
np.array([q.z_parity_conjugate() for q in Qs[Qs_finite]]))
assert np.array_equal(np.parity_conjugate(Qs[Qs_finite]), np.array([q.parity_conjugate() for q in Qs[Qs_finite]]))

# Quaternion-quaternion binary quaternion returners
@pytest.mark.xfail
def test_quaternion_copysign(Qs):
assert False

# Quaternion-quaternion, scalar-quaternion, or quaternion-scalar binary quaternion returners
for j in Qs_nonnan:
for k in Qs_nonnan:
q = Qs[j]
p = Qs[k]
assert (q + p == quaternion.quaternion(q.w + p.w, q.x + p.x, q.y + p.y, q.z + p.z)
or (j == q_inf1 and k == q_minf1)
or (k == q_inf1 and j == q_minf1))
for q in Qs[Qs_nonnan]:
for s in [-3, -2.3, -1.2, -1.0, 0.0, 0, 1.0, 1, 1.2, 2.3, 3]:
assert (q + s == quaternion.quaternion(q.w + s, q.x, q.y, q.z))
assert (s + q == quaternion.quaternion(q.w + s, q.x, q.y, q.z))

def test_quaternion_subtract(Qs):
for q in Qs[Qs_finite]:
for p in Qs[Qs_finite]:
assert q - p == quaternion.quaternion(q.w - p.w, q.x - p.x, q.y - p.y, q.z - p.z)
for q in Qs[Qs_nonnan]:
for s in [-3, -2.3, -1.2, -1.0, 0.0, 0, 1.0, 1, 1.2, 2.3, 3]:
assert (q - s == quaternion.quaternion(q.w - s, q.x, q.y, q.z))
assert (s - q == quaternion.quaternion(s - q.w, -q.x, -q.y, -q.z))

def test_quaternion_subtract_ufunc(Qs):
ufunc_binary_utility(Qs[Qs_finite], Qs[Qs_finite], operator.sub)

def test_quaternion_multiply(Qs):
# Check scalar multiplication
for q in Qs[Qs_finite]:
assert q * Qs[q_1] == q
for q in Qs[Qs_finite]:
assert q * 1.0 == q
assert q * 1 == q
assert 1.0 * q == q
assert 1 * q == q
for s in [-3, -2.3, -1.2, -1.0, 0.0, 0, 1.0, 1, 1.2, 2.3, 3]:
for q in Qs[Qs_finite]:
assert q * s == quaternion.quaternion(s * q.w, s * q.x, s * q.y, s * q.z)
assert s * q == q * s
for q in Qs[Qs_finite]:
assert 0.0 * q == Qs[q_0]
assert 0.0 * q == q * 0.0

# Check linearity
for q1 in Qs[Qs_finite]:
for q2 in Qs[Qs_finite]:
for q3 in Qs[Qs_finite]:
assert allclose(q1*(q2+q3), (q1*q2)+(q1*q3))
assert allclose((q1+q2)*q3, (q1*q3)+(q2*q3))

# Check the multiplication table
for q in [Qs[q_1], Qs[x], Qs[y], Qs[z]]:
assert Qs[q_1] * q == q
assert q * Qs[q_1] == q
assert Qs[x] * Qs[x] == -Qs[q_1]
assert Qs[x] * Qs[y] == Qs[z]
assert Qs[x] * Qs[z] == -Qs[y]
assert Qs[y] * Qs[x] == -Qs[z]
assert Qs[y] * Qs[y] == -Qs[q_1]
assert Qs[y] * Qs[z] == Qs[x]
assert Qs[z] * Qs[x] == Qs[y]
assert Qs[z] * Qs[y] == -Qs[x]
assert Qs[z] * Qs[z] == -Qs[q_1]

def test_quaternion_multiply_ufunc(Qs):
ufunc_binary_utility(np.array([quaternion.one]), Qs[Qs_finite], operator.mul)
ufunc_binary_utility(Qs[Qs_finite], np.array([quaternion.one]), operator.mul)
ufunc_binary_utility(np.array([1.0]), Qs[Qs_finite], operator.mul)
ufunc_binary_utility(Qs[Qs_finite], np.array([1.0]), operator.mul)
ufunc_binary_utility(np.array([1]), Qs[Qs_finite], operator.mul)
ufunc_binary_utility(Qs[Qs_finite], np.array([1]), operator.mul)
ufunc_binary_utility(np.array([0.0]), Qs[Qs_finite], operator.mul)
ufunc_binary_utility(Qs[Qs_finite], np.array([0.0]), operator.mul)
ufunc_binary_utility(np.array([0]), Qs[Qs_finite], operator.mul)
ufunc_binary_utility(Qs[Qs_finite], np.array([0]), operator.mul)

ufunc_binary_utility(np.array([-3, -2.3, -1.2, -1.0, 0.0, 0, 1.0, 1, 1.2, 2.3, 3]),
Qs[Qs_finite], operator.mul)
ufunc_binary_utility(Qs[Qs_finite],
np.array([-3, -2.3, -1.2, -1.0, 0.0, 0, 1.0, 1, 1.2, 2.3, 3]), operator.mul)

ufunc_binary_utility(Qs[Qs_finite], Qs[Qs_finite], operator.mul)

def test_quaternion_divide(Qs):
# Check identity between "inverse" and "reciprocal"
for q in Qs[Qs_finitenonzero]:
assert q.inverse() == q.reciprocal()

# Check scalar division
for q in Qs[Qs_finitenonzero]:
assert allclose(q / q, quaternion.one)
assert allclose(1 / q, q.inverse())
assert allclose(1.0 / q, q.inverse())
assert 0.0 / q == quaternion.zero
for s in [-3, -2.3, -1.2, -1.0, 0.0, 0, 1.0, 1, 1.2, 2.3, 3]:
assert allclose(s / q, s * (q.inverse()))
for q in Qs[Qs_nonnan]:
assert q / 1.0 == q
assert q / 1 == q
for s in [-3, -2.3, -1.2, -1.0, 1.0, 1, 1.2, 2.3, 3]:
assert allclose(q / s, q * (1.0/s))

# Check linearity
for q1 in Qs[Qs_finite]:
for q2 in Qs[Qs_finite]:
for q3 in Qs[Qs_finitenonzero]:
assert allclose((q1+q2)/q3, (q1/q3)+(q2/q3))

# Check the multiplication table
for q in [Qs[q_1], Qs[x], Qs[y], Qs[z]]:
assert Qs[q_1] / q == q.conj()
assert q / Qs[q_1] == q
assert Qs[x] / Qs[x] == Qs[q_1]
assert Qs[x] / Qs[y] == -Qs[z]
assert Qs[x] / Qs[z] == Qs[y]
assert Qs[y] / Qs[x] == Qs[z]
assert Qs[y] / Qs[y] == Qs[q_1]
assert Qs[y] / Qs[z] == -Qs[x]
assert Qs[z] / Qs[x] == -Qs[y]
assert Qs[z] / Qs[y] == Qs[x]
assert Qs[z] / Qs[z] == Qs[q_1]

def test_quaternion_divide_ufunc(Qs):
ufunc_binary_utility(np.array([quaternion.one]), Qs[Qs_finitenonzero], operator.truediv)
ufunc_binary_utility(Qs[Qs_finite], np.array([quaternion.one]), operator.truediv)
ufunc_binary_utility(np.array([1.0]), Qs[Qs_finitenonzero], operator.truediv)
ufunc_binary_utility(Qs[Qs_finite], np.array([1.0]), operator.truediv)
ufunc_binary_utility(np.array([1]), Qs[Qs_finitenonzero], operator.truediv)
ufunc_binary_utility(Qs[Qs_finite], np.array([1]), operator.truediv)
ufunc_binary_utility(np.array([0.0]), Qs[Qs_finitenonzero], operator.truediv)
ufunc_binary_utility(np.array([0]), Qs[Qs_finitenonzero], operator.truediv)

ufunc_binary_utility(np.array([-3, -2.3, -1.2, -1.0, 0.0, 0, 1.0, 1, 1.2, 2.3, 3]),
Qs[Qs_finitenonzero], operator.truediv)
ufunc_binary_utility(Qs[Qs_finitenonzero],
np.array([-3, -2.3, -1.2, -1.0, 1.0, 1, 1.2, 2.3, 3]), operator.truediv)

ufunc_binary_utility(Qs[Qs_finite], Qs[Qs_finitenonzero], operator.truediv)
ufunc_binary_utility(Qs[Qs_finite], Qs[Qs_finitenonzero], operator.floordiv)

ufunc_binary_utility(Qs[Qs_finitenonzero], Qs[Qs_finitenonzero], operator.truediv)
ufunc_binary_utility(Qs[Qs_finitenonzero], Qs[Qs_finitenonzero], operator.floordiv)

def test_quaternion_power(Qs):
import math
qpower_precision = 4*eps

# Test equivalence between scalar and real-quaternion exponentiation
for b in [0, 0.0, 1, 1.0, 2, 2.0, 5.6]:
for e in [0, 0.0, 1, 1.0, 2, 2.0, 4.5]:
be = np.quaternion(b**e, 0, 0, 0)
assert allclose(be, np.quaternion(b, 0, 0, 0)**np.quaternion(e, 0, 0, 0), rtol=qpower_precision)
assert allclose(be, b**np.quaternion(e, 0, 0, 0), rtol=qpower_precision)
assert allclose(be, np.quaternion(b, 0, 0, 0)**e, rtol=qpower_precision)
for q in [-3*quaternion.one, -2*quaternion.one, -quaternion.one, quaternion.zero, quaternion.one, 3*quaternion.one]:
for s in [-3, -2.3, -1.2, -1.0, 1.0, 1, 1.2, 2.3, 3]:
for t in [-3, -2.3, -1.2, -1.0, 1.0, 1, 1.2, 2.3, 3]:
assert allclose((s*t)**q, (s**q)*(t**q), rtol=2*qpower_precision)

# Test basic integer-exponent and additive-exponent properties
for q in Qs[Qs_finitenonzero]:
assert allclose(q ** 0, np.quaternion(1, 0, 0, 0), rtol=qpower_precision)
assert allclose(q ** 0.0, np.quaternion(1, 0, 0, 0), rtol=qpower_precision)
assert allclose(q ** np.quaternion(0, 0, 0, 0), np.quaternion(1, 0, 0, 0), rtol=qpower_precision)
assert allclose(((q ** 0.5) * (q ** 0.5)), q, rtol=qpower_precision)
assert allclose(q ** 1.0, q, rtol=qpower_precision)
assert allclose(q ** 1, q, rtol=qpower_precision)
assert allclose(q ** np.quaternion(1, 0, 0, 0), q, rtol=qpower_precision)
assert allclose(q ** 2.0, q * q, rtol=qpower_precision)
assert allclose(q ** 2, q * q, rtol=qpower_precision)
assert allclose(q ** np.quaternion(2, 0, 0, 0), q * q, rtol=qpower_precision)
assert allclose(q ** 3, q * q * q, rtol=qpower_precision)
assert allclose(q ** -1, q.inverse(), rtol=qpower_precision)
assert allclose(q ** -1.0, q.inverse(), rtol=qpower_precision)
for s in [-3, -2.3, -1.2, -1.0, 1.0, 1, 1.2, 2.3, 3]:
for t in [-3, -2.3, -1.2, -1.0, 1.0, 1, 1.2, 2.3, 3]:
assert allclose(q**(s+t), (q**s)*(q**t), rtol=2*qpower_precision)
assert allclose(q**(s-t), (q**s)/(q**t), rtol=2*qpower_precision)

# Check that exp(q) is the same as e**q
for q in Qs[Qs_finitenonzero]:
assert allclose(q.exp(), math.e**q, rtol=qpower_precision)
for s in [0, 0., 1.0, 1, 1.2, 2.3, 3]:
for t in [0, 0., 1.0, 1, 1.2, 2.3, 3]:
assert allclose((s*t)**q, (s**q)*(t**q), rtol=2*qpower_precision)
for s in [1.0, 1, 1.2, 2.3, 3]:
assert allclose(s**q, (q*math.log(s)).exp(), rtol=qpower_precision)

qinverse_precision = 2*eps
for q in Qs[Qs_finitenonzero]:
assert allclose((q ** -1.0) * q, Qs[q_1], rtol=qinverse_precision)
for q in Qs[Qs_finitenonzero]:
assert allclose((q ** -1) * q, Qs[q_1], rtol=qinverse_precision)
for q in Qs[Qs_finitenonzero]:
assert allclose((q ** Qs[q_1]), q, rtol=qpower_precision)
strict_assert(False)  # Try more edge cases

for q in [quaternion.x, quaternion.y, quaternion.z]:
assert allclose(quaternion.quaternion(math.exp(-math.pi/2), 0, 0, 0),
q**q, rtol=qpower_precision)
assert allclose(quaternion.quaternion(math.cos(math.pi/2), 0, 0, math.sin(math.pi/2)),
quaternion.x**quaternion.y, rtol=qpower_precision)
assert allclose(quaternion.quaternion(math.cos(math.pi/2), 0, -math.sin(math.pi/2), 0),
quaternion.x**quaternion.z, rtol=qpower_precision)
assert allclose(quaternion.quaternion(math.cos(math.pi/2), 0, 0, -math.sin(math.pi/2)),
quaternion.y**quaternion.x, rtol=qpower_precision)
assert allclose(quaternion.quaternion(math.cos(math.pi/2), math.sin(math.pi/2), 0, 0),
quaternion.y**quaternion.z, rtol=qpower_precision)
assert allclose(quaternion.quaternion(math.cos(math.pi/2), 0, math.sin(math.pi/2), 0),
quaternion.z**quaternion.x, rtol=qpower_precision)
assert allclose(quaternion.quaternion(math.cos(math.pi/2), -math.sin(math.pi/2), 0, 0),
quaternion.z**quaternion.y, rtol=qpower_precision)

def test_quaternion_getset(Qs):
# get parts a and b
for q in Qs[Qs_nonnan]:
assert q.a == q.w + 1j * q.z
assert q.b == q.y + 1j * q.x
# Check multiplication law for parts a and b
part_mul_precision = 1.e-14
for p in Qs[Qs_finite]:
for q in Qs[Qs_finite]:
assert abs((p * q).a - (p.a * q.a - p.b.conjugate() * q.b)) < part_mul_precision
assert abs((p * q).b - (p.b * q.a + p.a.conjugate() * q.b)) < part_mul_precision
# get components/vec
for q in Qs[Qs_nonnan]:
assert np.array_equal(q.components, np.array([q.w, q.x, q.y, q.z]))
assert np.array_equal(q.vec, np.array([q.x, q.y, q.z]))
assert np.array_equal(q.imag, np.array([q.x, q.y, q.z]))
# set components/vec from np.array, list, tuple
for q in Qs[Qs_nonnan]:
for seq_type in [np.array, list, tuple]:
p = np.quaternion(*q.components)
r = np.quaternion(*q.components)
s = np.quaternion(*q.components)
p.components = seq_type((-5.5, 6.6, -7.7, 8.8))
r.vec = seq_type((6.6, -7.7, 8.8))
s.imag = seq_type((6.6, -7.7, 8.8))
assert np.array_equal(p.components, np.array([-5.5, 6.6, -7.7, 8.8]))
assert np.array_equal(r.components, np.array([q.w, 6.6, -7.7, 8.8]))
assert np.array_equal(s.components, np.array([q.w, 6.6, -7.7, 8.8]))
# TypeError when setting components with the wrong type or size of thing
for q in Qs:
for seq_type in [np.array, list, tuple]:
p = np.quaternion(*q.components)
r = np.quaternion(*q.components)
s = np.quaternion(*q.components)
with pytest.raises(TypeError):
p.components = '1.1, 2.2, 3.3, 4.4'
with pytest.raises(TypeError):
p.components = seq_type([])
with pytest.raises(TypeError):
p.components = seq_type((-5.5,))
with pytest.raises(TypeError):
p.components = seq_type((-5.5, 6.6,))
with pytest.raises(TypeError):
p.components = seq_type((-5.5, 6.6, -7.7,))
with pytest.raises(TypeError):
p.components = seq_type((-5.5, 6.6, -7.7, 8.8, -9.9))
with pytest.raises(TypeError):
r.vec = '2.2, 3.3, 4.4'
with pytest.raises(TypeError):
r.vec = seq_type([])
with pytest.raises(TypeError):
r.vec = seq_type((-5.5,))
with pytest.raises(TypeError):
r.vec = seq_type((-5.5, 6.6))
with pytest.raises(TypeError):
r.vec = seq_type((-5.5, 6.6, -7.7, 8.8))
with pytest.raises(TypeError):
s.vec = '2.2, 3.3, 4.4'
with pytest.raises(TypeError):
s.vec = seq_type([])
with pytest.raises(TypeError):
s.vec = seq_type((-5.5,))
with pytest.raises(TypeError):
s.vec = seq_type((-5.5, 6.6))
with pytest.raises(TypeError):
s.vec = seq_type((-5.5, 6.6, -7.7, 8.8))

def test_metrics(Rs):
metric_precision = 4.e-15
intrinsic_funcs = (quaternion.rotor_intrinsic_distance, quaternion.rotation_intrinsic_distance)
chordal_funcs = (quaternion.rotor_chordal_distance, quaternion.rotation_chordal_distance)
metric_funcs = intrinsic_funcs + chordal_funcs
rotor_funcs = (quaternion.rotor_intrinsic_distance, quaternion.rotor_chordal_distance)
rotation_funcs = (quaternion.rotation_intrinsic_distance, quaternion.rotation_chordal_distance)
distance_dict = {func: func(Rs, Rs[:, np.newaxis]) for func in metric_funcs}

# Check non-negativity
for mat in distance_dict.values():
assert (mat >= 0.).all()

# Check discernibility
for func in metric_funcs:
if func in chordal_funcs:
eps = 0
else:
eps = 5.e-16
if func in rotor_funcs:
target = Rs != Rs[:, np.newaxis]
else:
target = np.logical_and(Rs != Rs[:, np.newaxis], Rs != - Rs[:, np.newaxis])
assert ((distance_dict[func] > eps) == target).all()

# Check symmetry
for mat in distance_dict.values():
assert np.allclose(mat, mat.T, atol=metric_precision, rtol=0)

# Check triangle inequality
for mat in distance_dict.values():
assert ((mat - metric_precision)[:, np.newaxis, :] <= mat[:, :, np.newaxis] + mat).all()

# Check distances from self or -self
for func in metric_funcs:
# All distances from self should be 0.0
if func in chordal_funcs:
eps = 0
else:
eps = 5.e-16
assert (np.diag(distance_dict[func]) <= eps).all()

# Chordal rotor distance from -self should be 2
assert (abs(quaternion.rotor_chordal_distance(Rs, -Rs) - 2.0) < metric_precision).all()
# Intrinsic rotor distance from -self should be 2pi
assert (abs(quaternion.rotor_intrinsic_distance(Rs, -Rs) - 2.0 * np.pi) < metric_precision).all()
# Rotation distances from -self should be 0
assert (quaternion.rotation_chordal_distance(Rs, -Rs) == 0.0).all()
assert (quaternion.rotation_intrinsic_distance(Rs, -Rs) < 5.e-16).all()

# We expect the chordal distance to be smaller than the intrinsic distance (or equal, if the distance is zero)
assert np.logical_or(quaternion.rotor_chordal_distance(quaternion.one, Rs)
< quaternion.rotor_intrinsic_distance(quaternion.one, Rs),
Rs == quaternion.one).all()
# Check invariance under overall rotations: d(R1, R2) = d(R3*R1, R3*R2) = d(R1*R3, R2*R3)
for func in quaternion.rotor_chordal_distance, quaternion.rotation_intrinsic_distance:
rotations = Rs[:, np.newaxis] * Rs
right_distances = func(rotations, rotations[:, np.newaxis])
assert (abs(distance_dict[func][:, :, np.newaxis] - right_distances) < metric_precision).all()
left_distances = func(rotations[:, :, np.newaxis], rotations[:, np.newaxis])
assert (abs(distance_dict[func] - left_distances) < metric_precision).all()

def test_slerp(Rs):
from quaternion import slerp_evaluate, slerp, allclose
slerp_precision = 4.e-15
ones = [quaternion.one, quaternion.x, quaternion.y, quaternion.z, -quaternion.x, -quaternion.y, -quaternion.z]
# Check extremes
for Q1 in ones:
assert quaternion.rotation_chordal_distance(slerp_evaluate(Q1, Q1, 0.0), Q1) < slerp_precision
assert quaternion.rotation_chordal_distance(slerp_evaluate(Q1, Q1, 1.0), Q1) < slerp_precision
assert quaternion.rotation_chordal_distance(slerp_evaluate(Q1, -Q1, 0.0), Q1) < slerp_precision
assert quaternion.rotation_chordal_distance(slerp_evaluate(Q1, -Q1, 1.0), Q1) < slerp_precision
for Q2 in ones:
assert quaternion.rotation_chordal_distance(slerp_evaluate(Q1, Q2, 0.0), Q1) < slerp_precision
assert quaternion.rotation_chordal_distance(slerp_evaluate(Q1, Q2, 1.0), Q2) < slerp_precision
assert quaternion.rotation_chordal_distance(slerp_evaluate(Q1, -Q2, 0.0), Q1) < slerp_precision
assert quaternion.rotation_chordal_distance(slerp_evaluate(Q1, -Q2, 1.0), -Q2) < slerp_precision
assert quaternion.rotation_chordal_distance(slerp_evaluate(Q2, Q1, 0.0), Q2) < slerp_precision
assert quaternion.rotation_chordal_distance(slerp_evaluate(Q2, Q1, 1.0), Q1) < slerp_precision
# Test simple increases in each dimension
for Q2 in ones[1:]:
for t in np.linspace(0.0, 1.0, num=100, endpoint=True):
assert quaternion.rotation_chordal_distance(slerp_evaluate(quaternion.one, Q2, t),
(np.cos(np.pi * t / 2) * quaternion.one + np.sin(
np.pi * t / 2) * Q2)) < slerp_precision
t = np.linspace(0.0, 1.0, num=100, endpoint=True)
assert allclose(slerp(quaternion.one, Q2, 0.0, 1.0, t),
np.cos(np.pi * t / 2) * quaternion.one + np.sin(np.pi * t / 2) * Q2, verbose=True)
assert allclose(slerp(quaternion.one, Q2, -10.0, 20.0, 30 * t - 10.0),
np.cos(np.pi * t / 2) * quaternion.one + np.sin(np.pi * t / 2) * Q2, verbose=True)
t = 1.5 * t - 0.125
assert allclose(slerp(quaternion.one, Q2, 0.0, 1.0, t),
np.cos(np.pi * t / 2) * quaternion.one + np.sin(np.pi * t / 2) * Q2, verbose=True)
# Test that (slerp of rotated rotors) is (rotated slerp of rotors)
for R in Rs:
for Q2 in ones[1:]:
for t in np.linspace(0.0, 1.0, num=100, endpoint=True):
assert quaternion.rotation_chordal_distance(R * slerp_evaluate(quaternion.one, Q2, t),
slerp_evaluate(R * quaternion.one, R * Q2,
t)) < slerp_precision
t = np.linspace(0.0, 1.0, num=100, endpoint=True)
assert allclose(R * slerp(quaternion.one, Q2, 0.0, 1.0, t),
slerp(R * quaternion.one, R * Q2, 0.0, 1.0, t),
verbose=True)

@pytest.mark.skipif(os.environ.get('FAST'), reason="Takes ~2 seconds")
from quaternion import slerp_evaluate
np.random.seed(1234)
ones = [quaternion.one, quaternion.x, quaternion.y, quaternion.z, -quaternion.x, -quaternion.y, -quaternion.z]
t_in = np.linspace(0.0, 1.0, num=13, endpoint=True)
t_out = np.linspace(0.0, 1.0, num=37, endpoint=True)
t_out2 = np.array(sorted([np.random.uniform(0.0, 1.0) for i in range(59)]))
# squad interpolated onto the inputs should be the identity
for R1 in Rs:
for R2 in Rs:
R_in = np.array([slerp_evaluate(R1, R2, t) for t in t_in])
# squad should be the same as slerp for linear interpolation
for R in ones:
R_in = np.array([slerp_evaluate(quaternion.one, R, t) for t in t_in])
R_out_slerp = np.array([slerp_evaluate(quaternion.one, R, t) for t in t_out])
# print(
#     R, "\n",
#     R_out_slerp[-6:],
# )
R,
)
R_out_slerp = np.array([slerp_evaluate(quaternion.one, R, t) for t in t_out2])
# assert False # Test unequal input time steps, and correct squad output [0,-2,-1]

for i in range(len(ones)):
R3 = np.roll(ones, i)[:3]
R_in = np.array([[slerp_evaluate(quaternion.one, R, t) for R in R3] for t in t_in])
R_out_slerp = np.array([[slerp_evaluate(quaternion.one, R, t) for R in R3] for t in t_out])
R,
)
R_out_slerp = np.array([[slerp_evaluate(quaternion.one, R, t) for R in R3] for t in t_out2])

@pytest.mark.xfail
def test_arrfuncs():
# nonzero
# copyswap
# copyswapn
# getitem
# setitem
# compare
# argmax
# fillwithscalar
assert False

def test_setitem_quat(Qs):
Ps = Qs.copy()
# setitem from quaternion
for j in range(len(Ps)):
Ps[j] = np.quaternion(1.3, 2.4, 3.5, 4.7)
for k in range(j + 1):
assert Ps[k] == np.quaternion(1.3, 2.4, 3.5, 4.7)
for k in range(j + 1, len(Ps)):
assert Ps[k] == Qs[k]
# setitem from np.array, list, or tuple
for seq_type in [np.array, list, tuple]:
Ps = Qs.copy()
with pytest.raises(TypeError):
Ps[0] = seq_type(())
with pytest.raises(TypeError):
Ps[0] = seq_type((1.3,))
with pytest.raises(TypeError):
Ps[0] = seq_type((1.3, 2.4,))
with pytest.raises(TypeError):
Ps[0] = seq_type((1.3, 2.4, 3.5))
with pytest.raises(TypeError):
Ps[0] = seq_type((1.3, 2.4, 3.5, 4.7, 5.9))
with pytest.raises(TypeError):
Ps[0] = seq_type((1.3, 2.4, 3.5, 4.7, 5.9, np.nan))
for j in range(len(Ps)):
Ps[j] = seq_type((1.3, 2.4, 3.5, 4.7))
for k in range(j + 1):
assert Ps[k] == np.quaternion(1.3, 2.4, 3.5, 4.7)
for k in range(j + 1, len(Ps)):
assert Ps[k] == Qs[k]
with pytest.raises(TypeError):
Ps[0] = 's'
with pytest.raises(TypeError):
Ps[0] = 's'

@pytest.mark.xfail
def test_arraydescr():
# new
# richcompare
# hash
# repr
# str
assert False

@pytest.mark.xfail
def test_casts():
# FLOAT, npy_float
# DOUBLE, npy_double
# LONGDOUBLE, npy_longdouble
# BOOL, npy_bool
# BYTE, npy_byte
# UBYTE, npy_ubyte
# SHORT, npy_short
# USHORT, npy_ushort
# INT, npy_int
# UINT, npy_uint
# LONG, npy_long
# ULONG, npy_ulong
# LONGLONG, npy_longlong
# ULONGLONG, npy_ulonglong
# CFLOAT, npy_float
# CDOUBLE, npy_double
# CLONGDOUBLE, npy_longdouble
assert False

def test_ufuncs(Rs, Qs):
np.random.seed(1234)
assert np.allclose(np.abs(Rs), np.ones(Rs.shape), atol=1.e-14, rtol=1.e-15)
assert np.allclose(np.abs(np.log(Rs) - np.array([r.log() for r in Rs])), np.zeros(Rs.shape), atol=1.e-14,
rtol=1.e-15)
assert np.allclose(np.abs(np.exp(Rs) - np.array([r.exp() for r in Rs])), np.zeros(Rs.shape), atol=1.e-14,
rtol=1.e-15)
assert np.allclose(np.abs(Rs - Rs), np.zeros(Rs.shape), atol=1.e-14, rtol=1.e-15)
assert np.allclose(np.abs(Rs + (-Rs)), np.zeros(Rs.shape), atol=1.e-14, rtol=1.e-15)
assert np.allclose(np.abs(np.conjugate(Rs) - np.array([r.conjugate() for r in Rs])), np.zeros(Rs.shape),
atol=1.e-14, rtol=1.e-15)
assert np.all(Rs == Rs)
assert np.all(Rs <= Rs)
for i in range(10):
x = np.random.uniform(-10, 10)
assert np.allclose(np.abs(Rs * x - np.array([r * x for r in Rs])), np.zeros(Rs.shape), atol=1.e-14, rtol=1.e-15)
# assert np.allclose( np.abs( x*Rs - np.array([r*x for r in Rs]) ), np.zeros(Rs.shape), atol=1.e-14, rtol=1.e-15)
strict_assert(False)
assert np.allclose(np.abs(Rs / x - np.array([r / x for r in Rs])), np.zeros(Rs.shape), atol=1.e-14, rtol=1.e-15)
assert np.allclose(np.abs(Rs ** x - np.array([r ** x for r in Rs])), np.zeros(Rs.shape), atol=1.e-14,
rtol=1.e-15)
assert np.allclose(
np.abs(Qs[Qs_finite] + Qs[Qs_finite] - np.array([q1 + q2 for q1, q2 in zip(Qs[Qs_finite], Qs[Qs_finite])])),
np.zeros(Qs[Qs_finite].shape), atol=1.e-14, rtol=1.e-15)
assert np.allclose(
np.abs(Qs[Qs_finite] - Qs[Qs_finite] - np.array([q1 - q2 for q1, q2 in zip(Qs[Qs_finite], Qs[Qs_finite])])),
np.zeros(Qs[Qs_finite].shape), atol=1.e-14, rtol=1.e-15)
assert np.allclose(
np.abs(Qs[Qs_finite] * Qs[Qs_finite] - np.array([q1 * q2 for q1, q2 in zip(Qs[Qs_finite], Qs[Qs_finite])])),
np.zeros(Qs[Qs_finite].shape), atol=1.e-14, rtol=1.e-15)
for Q in Qs[Qs_finite]:
assert np.allclose(np.abs(Qs[Qs_finite] * Q - np.array([q1 * Q for q1 in Qs[Qs_finite]])),
np.zeros(Qs[Qs_finite].shape), atol=1.e-14, rtol=1.e-15)
# assert np.allclose( np.abs( Q*Qs[Qs_finite] - np.array([Q*q1 for q1 in Qs[Qs_finite]]) ),
# np.zeros(Qs[Qs_finite].shape), atol=1.e-14, rtol=1.e-15)
assert np.allclose(np.abs(Qs[Qs_finitenonzero] / Qs[Qs_finitenonzero]
- np.array([q1 / q2 for q1, q2 in zip(Qs[Qs_finitenonzero], Qs[Qs_finitenonzero])])),
np.zeros(Qs[Qs_finitenonzero].shape), atol=1.e-14, rtol=1.e-15)
assert np.allclose(np.abs(Qs[Qs_finitenonzero] ** Qs[Qs_finitenonzero]
- np.array([q1 ** q2 for q1, q2 in zip(Qs[Qs_finitenonzero], Qs[Qs_finitenonzero])])),
np.zeros(Qs[Qs_finitenonzero].shape), atol=1.e-14, rtol=1.e-15)
assert np.allclose(np.abs(~Qs[Qs_finitenonzero]
- np.array([q.inverse() for q in Qs[Qs_finitenonzero]])),
np.zeros(Qs[Qs_finitenonzero].shape), atol=1.e-14, rtol=1.e-15)

@pytest.mark.parametrize(
("ufunc",),
[
# Complete list obtained from from https://docs.scipy.org/doc/numpy/reference/ufuncs.html on Sep 30, 2019
(np.subtract,),
(np.multiply,),
(np.divide,),
(np.true_divide,),
(np.floor_divide,),
(np.negative,),
(np.positive,),
(np.power,),
(np.absolute,),
(np.conj,),
(np.conjugate,),
(np.exp,),
(np.log,),
(np.sqrt,),
(np.square,),
(np.reciprocal,),
(np.invert,),
(np.less,),
(np.less_equal,),
(np.not_equal,),
(np.equal,),
(np.isfinite,),
(np.isinf,),
(np.isnan,),
(np.copysign,),
pytest.param(np.remainder, marks=pytest.mark.xfail),
pytest.param(np.mod, marks=pytest.mark.xfail),
pytest.param(np.fmod, marks=pytest.mark.xfail),
pytest.param(np.divmod, marks=pytest.mark.xfail),
pytest.param(np.fabs, marks=pytest.mark.xfail),
pytest.param(np.rint, marks=pytest.mark.xfail),
pytest.param(np.sign, marks=pytest.mark.xfail),
pytest.param(np.heaviside, marks=pytest.mark.xfail),
pytest.param(np.exp2, marks=pytest.mark.xfail),
pytest.param(np.log2, marks=pytest.mark.xfail),
pytest.param(np.log10, marks=pytest.mark.xfail),
pytest.param(np.expm1, marks=pytest.mark.xfail),
pytest.param(np.log1p, marks=pytest.mark.xfail),
pytest.param(np.cbrt, marks=pytest.mark.xfail),
pytest.param(np.gcd, marks=pytest.mark.xfail),
pytest.param(np.lcm, marks=pytest.mark.xfail),
pytest.param(np.sin, marks=pytest.mark.xfail),
pytest.param(np.cos, marks=pytest.mark.xfail),
pytest.param(np.tan, marks=pytest.mark.xfail),
pytest.param(np.arcsin, marks=pytest.mark.xfail),
pytest.param(np.arccos, marks=pytest.mark.xfail),
pytest.param(np.arctan, marks=pytest.mark.xfail),
pytest.param(np.arctan2, marks=pytest.mark.xfail),
pytest.param(np.hypot, marks=pytest.mark.xfail),
pytest.param(np.sinh, marks=pytest.mark.xfail),
pytest.param(np.cosh, marks=pytest.mark.xfail),
pytest.param(np.tanh, marks=pytest.mark.xfail),
pytest.param(np.arcsinh, marks=pytest.mark.xfail),
pytest.param(np.arccosh, marks=pytest.mark.xfail),
pytest.param(np.arctanh, marks=pytest.mark.xfail),
pytest.param(np.bitwise_and, marks=pytest.mark.xfail),
pytest.param(np.bitwise_or, marks=pytest.mark.xfail),
pytest.param(np.bitwise_xor, marks=pytest.mark.xfail),
pytest.param(np.left_shift, marks=pytest.mark.xfail),
pytest.param(np.right_shift, marks=pytest.mark.xfail),
pytest.param(np.greater, marks=pytest.mark.xfail),
pytest.param(np.greater_equal, marks=pytest.mark.xfail),
pytest.param(np.logical_and, marks=pytest.mark.xfail),
pytest.param(np.logical_or, marks=pytest.mark.xfail),
pytest.param(np.logical_xor, marks=pytest.mark.xfail),
pytest.param(np.logical_not, marks=pytest.mark.xfail),
pytest.param(np.maximum, marks=pytest.mark.xfail),
pytest.param(np.minimum, marks=pytest.mark.xfail),
pytest.param(np.fmax, marks=pytest.mark.xfail),
pytest.param(np.fmin, marks=pytest.mark.xfail),
pytest.param(np.isnat, marks=pytest.mark.xfail),
pytest.param(np.fabs, marks=pytest.mark.xfail),
pytest.param(np.signbit, marks=pytest.mark.xfail),
pytest.param(np.nextafter, marks=pytest.mark.xfail),
pytest.param(np.spacing, marks=pytest.mark.xfail),
pytest.param(np.modf, marks=pytest.mark.xfail),
pytest.param(np.ldexp, marks=pytest.mark.xfail),
pytest.param(np.frexp, marks=pytest.mark.xfail),
pytest.param(np.fmod, marks=pytest.mark.xfail),
pytest.param(np.floor, marks=pytest.mark.xfail),
pytest.param(np.ceil, marks=pytest.mark.xfail),
pytest.param(np.trunc, marks=pytest.mark.xfail),
],
ids=lambda uf:uf.__name__
)
def test_ufunc_existence(ufunc):
qarray = Qs_array[Qs_finitenonzero]
if ufunc.nin == 1:
result = ufunc(qarray)
elif ufunc.nin == 2:
result = ufunc(qarray, qarray)

def test_numpy_array_conversion(Qs):
"Check conversions between array as quaternions and array as floats"
# First, just check 1-d array
Q = Qs[Qs_nonnan][:12]  # Select first 3x4=12 non-nan elements in Qs
assert Q.dtype == np.dtype(np.quaternion)
q = quaternion.as_float_array(Q)  # View as array of floats
assert q.dtype == np.dtype(np.float)
assert q.shape == (12, 4)  # This is the expected shape
for j in range(12):
for k in range(4):  # Check each component individually
assert q[j][k] == Q[j].components[k]
assert np.array_equal(quaternion.as_quat_array(q), Q)  # Check that we can go backwards
# Next, see how that works if I flatten the q array
q = q.flatten()
assert q.dtype == np.dtype(np.float)
assert q.shape == (48,)
for j in range(48):
assert q[j] == Q[j // 4].components[j % 4]
assert np.array_equal(quaternion.as_quat_array(q), Q)  # Check that we can go backwards
# Now, reshape into 2-d array, and re-check
P = Q.reshape(3, 4)  # Reshape into 3x4 array of quaternions
p = quaternion.as_float_array(P)  # View as array of floats
assert p.shape == (3, 4, 4)  # This is the expected shape
for j in range(3):
for k in range(4):
for l in range(4):  # Check each component individually
assert p[j][k][l] == Q[4 * j + k].components[l]
assert np.array_equal(quaternion.as_quat_array(p), P)  # Check that we can go backwards
# Check that we get an exception if the final dimension is not divisible by 4
with pytest.raises(ValueError):
quaternion.as_quat_array(np.random.rand(4, 1))
with pytest.raises(ValueError):
quaternion.as_quat_array(np.random.rand(4, 2))
with pytest.raises(ValueError):
quaternion.as_quat_array(np.random.rand(4, 3))
with pytest.raises(ValueError):
quaternion.as_quat_array(np.random.rand(4, 5))
with pytest.raises(ValueError):
quaternion.as_quat_array(np.random.rand(4, 5, 3, 2, 1))
# Finally, check that it works on non-contiguous arrays, by adding random padding and then slicing
q = quaternion.as_float_array(Q)
q = np.concatenate((np.random.rand(q.shape[0], 3), q, np.random.rand(q.shape[0], 3)), axis=1)
assert np.array_equal(quaternion.as_quat_array(q[:, 3:7]), Q)

@pytest.mark.skipif(not has_scipy, reason="Scipy is not installed")
def test_integrate_angular_velocity():
import math
import numpy as np
import quaternion

t0 = 0.0
t2 = 10000.0
Omega_orb = 2 * math.pi * 100 / t2
Omega_prec = 2 * math.pi * 10 / t2
alpha = 0.125 * math.pi
alphadot = 2 * alpha / t2
nu = 0.2 * alpha
Omega_nu = Omega_prec
R0 = np.exp(-1.1 * alpha * quaternion.x / 2)

def R(t):
return (R0
* np.exp(Omega_prec * t * quaternion.z / 2) * np.exp((alpha + alphadot * t) * quaternion.x / 2)
* np.exp(-Omega_prec * t * quaternion.z / 2)
* np.exp(Omega_orb * t * quaternion.z / 2)
* np.exp(nu * np.cos(Omega_nu * t) * quaternion.y / 2))

def Rdot(t):
R_dynamic = R0.inverse() * R(t)
R_prec = np.exp(Omega_prec * t * quaternion.z / 2)
R_nu = np.exp(nu * np.cos(Omega_nu * t) * quaternion.y / 2)
return R0 * (0.5 * Omega_prec * quaternion.z * R_dynamic
+ 0.5 * alphadot * R_prec * quaternion.x * R_prec.conj() * R_dynamic
+ 0.5 * (Omega_orb - Omega_prec) * R_dynamic * R_nu.inverse() * quaternion.z * R_nu
+ 0.5 * (-Omega_nu * nu * np.sin(Omega_nu * t)) * R_dynamic * quaternion.y)

def Omega_tot(t):
Rotor = R(t)
RotorDot = Rdot(t)
return (2 * RotorDot * Rotor.inverse()).vec

# Test with exact Omega function
t, R_approx = quaternion.integrate_angular_velocity(Omega_tot, 0.0, t2, R0=R(t0))
R_exact = R(t)
phi_Delta = np.array([quaternion.rotation_intrinsic_distance(e, a) for e, a in zip(R_exact, R_approx)])
assert np.max(phi_Delta) < 1e-10, np.max(phi_Delta)

# Test with exact Omega function taking two arguments
t, R_approx = quaternion.integrate_angular_velocity(lambda t, R: Omega_tot(t), 0.0, t2, R0=R(t0))
R_exact = R(t)
phi_Delta = np.array([quaternion.rotation_intrinsic_distance(e, a) for e, a in zip(R_exact, R_approx)])
assert np.max(phi_Delta) < 1e-10, np.max(phi_Delta)

# Test with explicit values, given at the moments output above
v = np.array([Omega_tot(ti) for ti in t])
t, R_approx = quaternion.integrate_angular_velocity((t, v), 0.0, t2, R0=R(t0))
R_exact = R(t)
phi_Delta = np.array([quaternion.rotation_intrinsic_distance(e, a) for e, a in zip(R_exact, R_approx)])
assert np.max(phi_Delta) < 1e-4, np.max(phi_Delta)

def test_mean_rotor_in_chordal_metric():
# Test interpolation of some random constant quaternion
q = quaternion.quaternion(*np.random.rand(4)).normalized()
qs = np.array([q]*10)
ts = np.linspace(0.1, 23.4, num=10)
for length in range(1, 4):
mean1 = quaternion.mean_rotor_in_chordal_metric(qs[:length])
assert np.abs(q-mean1) < 1e-15, (q, mean1, length)
with pytest.raises(ValueError):
quaternion.mean_rotor_in_chordal_metric(qs[:length], ts[:length])
for length in range(4, 11):
mean1 = quaternion.mean_rotor_in_chordal_metric(qs[:length])
assert np.abs(q-mean1) < 1e-15, (q, mean1, length)
mean2 = quaternion.mean_rotor_in_chordal_metric(qs[:length], ts[:length])
assert np.abs(q-mean2) < 1e-15, (q, mean2, length)

import tempfile
a = quaternion.as_quat_array(np.random.rand(5,3,4))
with tempfile.TemporaryFile() as temp:
np.save(temp, a)
temp.seek(0)  # Only needed here to simulate closing & reopening file, per np.save docs
assert np.array_equal(a, b)

def test_pickle():
import pickle
a = quaternion.one