compatible with scipy.spatial.transform.Rotation

also introduced inplace variants and '/' as multiplicative inverse of
'*'
This commit is contained in:
Martin Diehl 2021-01-03 15:54:41 +01:00
parent 98723cb0ed
commit f48a446353
2 changed files with 122 additions and 2 deletions

View File

@ -144,10 +144,91 @@ class Rotation:
p = self.quaternion[...,1:]/np.linalg.norm(self.quaternion[...,1:],axis=-1,keepdims=True) p = self.quaternion[...,1:]/np.linalg.norm(self.quaternion[...,1:],axis=-1,keepdims=True)
return self.copy(rotation=Rotation(np.block([np.cos(pwr*phi),np.sin(pwr*phi)*p]))._standardize()) return self.copy(rotation=Rotation(np.block([np.cos(pwr*phi),np.sin(pwr*phi)*p]))._standardize())
def __ipow__(self,pwr):
"""
Raise quaternion to power (in-place).
Equivalent to performing the rotation 'pwr' times.
Parameters
----------
pwr : float
Power to raise quaternion to.
"""
return self**pwr
def __mul__(self,other): def __mul__(self,other):
"""Standard multiplication is not implemented.""" """
raise NotImplementedError('Use "R@b", i.e. matmul, to apply rotation "R" to object "b"') Compose this rotation with other.
Parameters
----------
other : damask.Rotation of shape(self.shape)
Rotation for comosition.
"""
if isinstance(other,Rotation):
return self@other
else:
raise TypeError('Use "R@b", i.e. matmul, to apply rotation "R" to object "b"')
def __imul__(self,other):
"""
Compose this rotation with other (in-place).
Parameters
----------
other : damask.Rotation of shape(self.shape)
Rotation for comosition.
"""
return self*other
def __truediv__(self,other):
"""
Compose this rotation with inverse of other.
Parameters
----------
other : damask.Rotation of shape (self.shape)
Rotation to inverse composition.
"""
if isinstance(other,Rotation):
return self@~other
else:
raise TypeError('Use "R@b", i.e. matmul, to apply rotation "R" to object "b"')
def __itruediv__(self,other):
"""
Compose this rotation with inverse of other (in-place).
Parameters
----------
other : damask.Rotation of shape (self.shape)
Rotation to inverse composition.
"""
return self/other
def apply(self,other):
"""
Apply rotation to vector or second/forth order tensor field.
Parameters
----------
other : numpy.ndarray of shape (...,3), (...,3,3), or (...,3,3,3,3)
Vector or tensor on which the rotation is apply
"""
if isinstance(other,np.ndarray):
return self@other
else:
raise TypeError('Use "R1*R2" or "R1/R2", to compose rotations')
def __matmul__(self,other): def __matmul__(self,other):

View File

@ -974,6 +974,45 @@ class TestRotation:
R_2 = Rotation.from_Euler_angles([360,0,0],degrees=True) R_2 = Rotation.from_Euler_angles([360,0,0],degrees=True)
assert np.allclose(R_1.misorientation(R_2).as_matrix(),np.eye(3)) assert np.allclose(R_1.misorientation(R_2).as_matrix(),np.eye(3))
def test_composition(self):
a,b = (Rotation.from_random(),Rotation.from_random())
c = a * b
a *= b
assert c == a
def test_composition_invalid(self):
with pytest.raises(TypeError):
Rotation()*np.ones(3)
def test_composition_inverse(self):
a,b = (Rotation.from_random(),Rotation.from_random())
c = a / b
a /= b
assert c == a
def test_composition_inverse_invalid(self):
with pytest.raises(TypeError):
Rotation()/np.ones(3)
def test_power(self):
a = Rotation.from_random()
r = (np.random.rand()-.5)*4
b = a**r
a **= r
assert a == b
def test_invariant(self):
R = Rotation.from_random()
assert R/R == R*R**(-1) == Rotation()
@pytest.mark.parametrize('vec',[np.ones(3),np.ones((3,3)), np.ones((3,3,3,3))])
def test_apply(self,vec):
assert (Rotation().from_random().apply(vec)).all()
def test_apply_invalid(self):
with pytest.raises(TypeError):
Rotation().apply(Rotation())
@pytest.mark.parametrize('angle',[10,20,30,40,50,60,70,80,90,100,120]) @pytest.mark.parametrize('angle',[10,20,30,40,50,60,70,80,90,100,120])
def test_average(self,angle): def test_average(self,angle):
R = Rotation.from_axis_angle([[0,0,1,10],[0,0,1,angle]],degrees=True) R = Rotation.from_axis_angle([[0,0,1,10],[0,0,1,angle]],degrees=True)