Rotation composition uses "*"; application of Rotation to object uses "@"; "apply()" works on both

This commit is contained in:
Philip Eisenlohr 2021-01-12 18:56:40 -05:00
parent f0351e403a
commit 2d6e6a2370
3 changed files with 65 additions and 45 deletions

View File

@ -242,24 +242,25 @@ class Orientation(Rotation):
return np.logical_not(self==other) return np.logical_not(self==other)
def __matmul__(self,other): def __mul__(self,other):
""" """
Rotation of vector, second or fourth order tensor, or rotation object. Compose this orientation with other.
Parameters Parameters
---------- ----------
other : numpy.ndarray, Rotation, or Orientation other : Rotation or Orientation
Vector, second or fourth order tensor, or rotation object that is rotated. Object for composition.
Returns Returns
------- -------
other_rot : numpy.ndarray or Rotation composition : Orientation
Rotated vector, second or fourth order tensor, or rotation object. Compound rotation self*other, i.e. first other then self rotation.
""" """
return self.copy(rotation=Rotation.__matmul__(self,Rotation(other.quaternion))) \ if isinstance(other,Orientation) or isinstance(other,Rotation):
if isinstance(other,self.__class__) else \ return self.copy(rotation=Rotation.__mul__(self,Rotation(other.quaternion)))
Rotation.__matmul__(self,other) else:
raise TypeError('Use "O@b", i.e. matmul, to apply Orientation "O" to object "b"')
@classmethod @classmethod
@ -440,7 +441,7 @@ class Orientation(Rotation):
raise ValueError('Missing crystal symmetry') raise ValueError('Missing crystal symmetry')
o = self.symmetry_operations.broadcast_to(self.symmetry_operations.shape+self.shape,mode='right') o = self.symmetry_operations.broadcast_to(self.symmetry_operations.shape+self.shape,mode='right')
return self.copy(rotation=o@Rotation(self.quaternion).broadcast_to(o.shape,mode='left')) return self.copy(rotation=o*Rotation(self.quaternion).broadcast_to(o.shape,mode='left'))
@property @property
@ -619,7 +620,7 @@ class Orientation(Rotation):
o,lattice = self.relation_operations(model,return_lattice=True) o,lattice = self.relation_operations(model,return_lattice=True)
target = Orientation(lattice=lattice) target = Orientation(lattice=lattice)
o = o.broadcast_to(o.shape+self.shape,mode='right') o = o.broadcast_to(o.shape+self.shape,mode='right')
return self.copy(rotation=o@Rotation(self.quaternion).broadcast_to(o.shape,mode='left'), return self.copy(rotation=o*Rotation(self.quaternion).broadcast_to(o.shape,mode='left'),
lattice=lattice, lattice=lattice,
b = self.b if target.ratio['b'] is None else self.a*target.ratio['b'], b = self.b if target.ratio['b'] is None else self.a*target.ratio['b'],
c = self.c if target.ratio['c'] is None else self.a*target.ratio['c'], c = self.c if target.ratio['c'] is None else self.a*target.ratio['c'],

View File

@ -35,6 +35,11 @@ class Rotation:
- b = Q @ a - b = Q @ a
- b = np.dot(Q.as_matrix(),a) - b = np.dot(Q.as_matrix(),a)
Compound rotations R1 (first) and R2 (second):
- R = R2 * R1
- R = Rotation.from_matrix(np.dot(R2.as_matrix(),R1.as_matrix())
References References
---------- ----------
D. Rowenhorst et al., Modelling and Simulation in Materials Science and Engineering 23:083501, 2015 D. Rowenhorst et al., Modelling and Simulation in Materials Science and Engineering 23:083501, 2015
@ -96,8 +101,8 @@ class Rotation:
""" """
Equal to other. Equal to other.
Equality is determined taking limited floating point precision into Equality is determined taking limited floating point precision into account.
account. See numpy.allclose for details. See numpy.allclose for details.
Parameters Parameters
---------- ----------
@ -182,14 +187,25 @@ class Rotation:
Parameters Parameters
---------- ----------
other : damask.Rotation of shape(self.shape) other : Rotation of shape(self.shape)
Rotation for comosition. Rotation for composition.
Returns
-------
composition : Rotation
Compound rotation self*other, i.e. first other then self rotation.
""" """
if isinstance(other,Rotation): if isinstance(other,Rotation):
return self@other q_m = self.quaternion[...,0:1]
p_m = self.quaternion[...,1:]
q_o = other.quaternion[...,0:1]
p_o = other.quaternion[...,1:]
q = (q_m*q_o - np.einsum('...i,...i',p_m,p_o).reshape(self.shape+(1,)))
p = q_m*p_o + q_o*p_m + _P * np.cross(p_m,p_o)
return Rotation(np.block([q,p]))._standardize()
else: else:
raise TypeError('Use "R@b", i.e. matmul, to apply rotation "R" to object "b"') raise TypeError('Use "R@b", i.e. matmul, to apply rotation "R" to object "b"')
def __imul__(self,other): def __imul__(self,other):
""" """
@ -197,8 +213,8 @@ class Rotation:
Parameters Parameters
---------- ----------
other : damask.Rotation of shape(self.shape) other : Rotation of shape(self.shape)
Rotation for comosition. Rotation for composition.
""" """
return self*other return self*other
@ -213,11 +229,16 @@ class Rotation:
other : damask.Rotation of shape (self.shape) other : damask.Rotation of shape (self.shape)
Rotation to inverse composition. Rotation to inverse composition.
Returns
-------
composition : Rotation
Compound rotation self*(~other), i.e. first inverse of other then self rotation.
""" """
if isinstance(other,Rotation): if isinstance(other,Rotation):
return self@~other return self*~other
else: else:
raise TypeError('Use "R@b", i.e. matmul, to apply rotation "R" to object "b"') raise TypeError('Use "R@b", i.e. matmul, to apply rotation "R" to object "b"')
def __itruediv__(self,other): def __itruediv__(self,other):
""" """
@ -225,7 +246,7 @@ class Rotation:
Parameters Parameters
---------- ----------
other : damask.Rotation of shape (self.shape) other : Rotation of shape (self.shape)
Rotation to inverse composition. Rotation to inverse composition.
""" """
@ -234,41 +255,39 @@ class Rotation:
def apply(self,other): def apply(self,other):
""" """
Apply rotation to vector, second or fourth order tensor, or rotation object. Apply rotation to Rotation, vector, second order tensor, or fourth order tensor.
Parameters Parameters
---------- ----------
other : numpy.ndarray of shape (...,3), (...,3,3), or (...,3,3,3,3) or Rotation other : Rotation or numpy.ndarray of shape (...,3), (...,3,3), or (...,3,3,3,3)
Vector, tensor, or rotation object on which to apply the rotation. Rotation, vector, or tensor on which to apply the rotation.
Returns
-------
rotated : Rotation or numpy.ndarray of shape (...,3), (...,3,3), or (...,3,3,3,3)
Composed rotation or rotated vector/tensor, i.e. transformed to frame defined by rotation.
""" """
return self@other return self*other if isinstance(other,Rotation) else self@other
def __matmul__(self,other): def __matmul__(self,other):
""" """
Rotation of vector, second or fourth order tensor, or rotation object. Rotation of vector, second order tensor, or fourth order tensor.
Parameters Parameters
---------- ----------
other : numpy.ndarray or Rotation other : numpy.ndarray of shape (...,3), (...,3,3), or (...,3,3,3,3)
Vector, second or fourth order tensor, or rotation object that is rotated. Vector or tensor on which to apply the rotation.
Returns Returns
------- -------
other_rot : numpy.ndarray or Rotation rotated : numpy.ndarray of shape (...,3), (...,3,3), or (...,3,3,3,3)
Rotated vector, second or fourth order tensor, or rotation object. Rotated vector or tensor, i.e. transformed to frame defined by rotation.
""" """
if isinstance(other,Rotation): if isinstance(other,Rotation):
q_m = self.quaternion[...,0:1] raise TypeError('Use "R1*R2", i.e. multiplication, to compose rotations "R1" and "R2"')
p_m = self.quaternion[...,1:]
q_o = other.quaternion[...,0:1]
p_o = other.quaternion[...,1:]
q = (q_m*q_o - np.einsum('...i,...i',p_m,p_o).reshape(self.shape+(1,)))
p = q_m*p_o + q_o*p_m + _P * np.cross(p_m,p_o)
return Rotation(np.block([q,p]))._standardize()
elif isinstance(other,np.ndarray): elif isinstance(other,np.ndarray):
if self.shape + (3,) == other.shape: if self.shape + (3,) == other.shape:
q_m = self.quaternion[...,0] q_m = self.quaternion[...,0]
@ -392,7 +411,7 @@ class Rotation:
Rotation to which the misorientation is computed. Rotation to which the misorientation is computed.
""" """
return other@~self return other*~self
################################################################################################ ################################################################################################
@ -915,7 +934,7 @@ class Rotation:
np.sqrt(1-u**2)*np.sin(Theta), np.sqrt(1-u**2)*np.sin(Theta),
u, omega]) u, omega])
return Rotation.from_axis_angle(p) @ center return Rotation.from_axis_angle(p) * center
@staticmethod @staticmethod
@ -966,8 +985,8 @@ class Rotation:
f[::2,:3] *= -1 # flip half the rotation axes to negative sense f[::2,:3] *= -1 # flip half the rotation axes to negative sense
return R_align.broadcast_to(N) \ return R_align.broadcast_to(N) \
@ Rotation.from_axis_angle(p,normalize=True) \ * Rotation.from_axis_angle(p,normalize=True) \
@ Rotation.from_axis_angle(f) * Rotation.from_axis_angle(f)
#################################################################################################### ####################################################################################################

View File

@ -956,7 +956,7 @@ class TestRotation:
def test_rotate_inverse(self): def test_rotate_inverse(self):
R = Rotation.from_random() R = Rotation.from_random()
assert np.allclose(np.eye(3),(~R@R).as_matrix()) assert np.allclose(np.eye(3),(~R*R).as_matrix())
@pytest.mark.parametrize('data',[np.random.rand(3), @pytest.mark.parametrize('data',[np.random.rand(3),
np.random.rand(3,3), np.random.rand(3,3),
@ -1024,7 +1024,7 @@ class TestRotation:
@pytest.mark.parametrize('item',[Rotation(),np.ones(3),np.ones((3,3)), np.ones((3,3,3,3))]) @pytest.mark.parametrize('item',[Rotation(),np.ones(3),np.ones((3,3)), np.ones((3,3,3,3))])
def test_apply(self,item): def test_apply(self,item):
r = Rotation.from_random() r = Rotation.from_random()
assert r.apply(item) == r@item if isinstance(item,Rotation) else (r.apply(item) == r@item).all() assert r.apply(item) == r*item if isinstance(item,Rotation) else (r.apply(item) == r@item).all()
@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):