add option to normalize quaternions

This commit is contained in:
Philip Eisenlohr 2022-11-15 16:11:29 -05:00
parent c8e5e9e34a
commit 23d2337fb2
2 changed files with 13 additions and 7 deletions

View File

@ -751,6 +751,7 @@ class Rotation:
@staticmethod @staticmethod
def from_quaternion(q: Union[Sequence[FloatSequence], np.ndarray], def from_quaternion(q: Union[Sequence[FloatSequence], np.ndarray],
accept_homomorph: bool = False, accept_homomorph: bool = False,
normalize: bool = False,
P: Literal[1, -1] = -1) -> 'Rotation': P: Literal[1, -1] = -1) -> 'Rotation':
""" """
Initialize from quaternion. Initialize from quaternion.
@ -762,6 +763,8 @@ class Rotation:
accept_homomorph : bool, optional accept_homomorph : bool, optional
Allow homomorphic variants, i.e. q_0 < 0 (negative real hemisphere). Allow homomorphic variants, i.e. q_0 < 0 (negative real hemisphere).
Defaults to False. Defaults to False.
normalize: bool, optional
Allow ǀqǀ 1. Defaults to False.
P : int {-1,1}, optional P : int {-1,1}, optional
Sign convention. Defaults to -1. Sign convention. Defaults to -1.
@ -773,12 +776,14 @@ class Rotation:
raise ValueError('P ∉ {-1,1}') raise ValueError('P ∉ {-1,1}')
qu[...,1:4] *= -P qu[...,1:4] *= -P
if accept_homomorph: if accept_homomorph:
qu[qu[...,0] < 0.0] *= -1 qu[qu[...,0] < 0.0] *= -1
else: elif np.any(qu[...,0] < 0.0):
if np.any(qu[...,0] < 0.0):
raise ValueError('quaternion with negative first (real) component') raise ValueError('quaternion with negative first (real) component')
if not np.all(np.isclose(np.linalg.norm(qu,axis=-1), 1.0,rtol=0.0)): if normalize:
qu /= np.linalg.norm(qu,axis=-1,keepdims=True)
elif not np.all(np.isclose(np.linalg.norm(qu,axis=-1), 1.0,rtol=1e-8)):
raise ValueError('quaternion is not of unit length') raise ValueError('quaternion is not of unit length')
return Rotation(qu) return Rotation(qu)

View File

@ -760,11 +760,12 @@ class TestRotation:
@pytest.mark.parametrize('P',[1,-1]) @pytest.mark.parametrize('P',[1,-1])
@pytest.mark.parametrize('accept_homomorph',[True,False]) @pytest.mark.parametrize('accept_homomorph',[True,False])
def test_quaternion(self,set_of_rotations,P,accept_homomorph): @pytest.mark.parametrize('normalize',[True,False])
c = np.array([1,P*-1,P*-1,P*-1]) * (-1 if accept_homomorph else 1) def test_quaternion(self,set_of_rotations,P,accept_homomorph,normalize):
c = np.array([1,P*-1,P*-1,P*-1]) * (-1 if accept_homomorph else 1) * (0.9 if normalize else 1.0)
for rot in set_of_rotations: for rot in set_of_rotations:
m = rot.as_cubochoric() m = rot.as_cubochoric()
o = Rotation.from_quaternion(rot.as_quaternion()*c,accept_homomorph,P).as_cubochoric() o = Rotation.from_quaternion(rot.as_quaternion()*c,accept_homomorph,normalize,P).as_cubochoric()
ok = np.allclose(m,o,atol=atol) ok = np.allclose(m,o,atol=atol)
if np.count_nonzero(np.isclose(np.abs(o),np.pi**(2./3.)*.5)): if np.count_nonzero(np.isclose(np.abs(o),np.pi**(2./3.)*.5)):
ok |= np.allclose(m*-1.,o,atol=atol) ok |= np.allclose(m*-1.,o,atol=atol)