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)
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
----------
other : numpy.ndarray, Rotation, or Orientation
Vector, second or fourth order tensor, or rotation object that is rotated.
other : Rotation or Orientation
Object for composition.
Returns
-------
other_rot : numpy.ndarray or Rotation
Rotated vector, second or fourth order tensor, or rotation object.
composition : Orientation
Compound rotation self*other, i.e. first other then self rotation.
"""
return self.copy(rotation=Rotation.__matmul__(self,Rotation(other.quaternion))) \
if isinstance(other,self.__class__) else \
Rotation.__matmul__(self,other)
if isinstance(other,Orientation) or isinstance(other,Rotation):
return self.copy(rotation=Rotation.__mul__(self,Rotation(other.quaternion)))
else:
raise TypeError('Use "O@b", i.e. matmul, to apply Orientation "O" to object "b"')
@classmethod
@ -440,7 +441,7 @@ class Orientation(Rotation):
raise ValueError('Missing crystal symmetry')
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
@ -619,7 +620,7 @@ class Orientation(Rotation):
o,lattice = self.relation_operations(model,return_lattice=True)
target = Orientation(lattice=lattice)
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,
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'],

View File

@ -35,6 +35,11 @@ class Rotation:
- b = Q @ 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
----------
D. Rowenhorst et al., Modelling and Simulation in Materials Science and Engineering 23:083501, 2015
@ -96,8 +101,8 @@ class Rotation:
"""
Equal to other.
Equality is determined taking limited floating point precision into
account. See numpy.allclose for details.
Equality is determined taking limited floating point precision into account.
See numpy.allclose for details.
Parameters
----------
@ -182,14 +187,25 @@ class Rotation:
Parameters
----------
other : damask.Rotation of shape(self.shape)
Rotation for comosition.
other : Rotation of shape(self.shape)
Rotation for composition.
Returns
-------
composition : Rotation
Compound rotation self*other, i.e. first other then self 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:
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):
"""
@ -197,8 +213,8 @@ class Rotation:
Parameters
----------
other : damask.Rotation of shape(self.shape)
Rotation for comosition.
other : Rotation of shape(self.shape)
Rotation for composition.
"""
return self*other
@ -213,11 +229,16 @@ class Rotation:
other : damask.Rotation of shape (self.shape)
Rotation to inverse composition.
Returns
-------
composition : Rotation
Compound rotation self*(~other), i.e. first inverse of other then self rotation.
"""
if isinstance(other,Rotation):
return self@~other
return self*~other
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):
"""
@ -225,7 +246,7 @@ class Rotation:
Parameters
----------
other : damask.Rotation of shape (self.shape)
other : Rotation of shape (self.shape)
Rotation to inverse composition.
"""
@ -234,41 +255,39 @@ class Rotation:
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
----------
other : numpy.ndarray of shape (...,3), (...,3,3), or (...,3,3,3,3) or Rotation
Vector, tensor, or rotation object on which to apply the rotation.
other : Rotation or numpy.ndarray of shape (...,3), (...,3,3), or (...,3,3,3,3)
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):
"""
Rotation of vector, second or fourth order tensor, or rotation object.
Rotation of vector, second order tensor, or fourth order tensor.
Parameters
----------
other : numpy.ndarray or Rotation
Vector, second or fourth order tensor, or rotation object that is rotated.
other : numpy.ndarray of shape (...,3), (...,3,3), or (...,3,3,3,3)
Vector or tensor on which to apply the rotation.
Returns
-------
other_rot : numpy.ndarray or Rotation
Rotated vector, second or fourth order tensor, or rotation object.
rotated : numpy.ndarray of shape (...,3), (...,3,3), or (...,3,3,3,3)
Rotated vector or tensor, i.e. transformed to frame defined by rotation.
"""
if isinstance(other,Rotation):
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()
raise TypeError('Use "R1*R2", i.e. multiplication, to compose rotations "R1" and "R2"')
elif isinstance(other,np.ndarray):
if self.shape + (3,) == other.shape:
q_m = self.quaternion[...,0]
@ -392,7 +411,7 @@ class Rotation:
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),
u, omega])
return Rotation.from_axis_angle(p) @ center
return Rotation.from_axis_angle(p) * center
@staticmethod
@ -966,8 +985,8 @@ class Rotation:
f[::2,:3] *= -1 # flip half the rotation axes to negative sense
return R_align.broadcast_to(N) \
@ Rotation.from_axis_angle(p,normalize=True) \
@ Rotation.from_axis_angle(f)
* Rotation.from_axis_angle(p,normalize=True) \
* Rotation.from_axis_angle(f)
####################################################################################################

View File

@ -956,7 +956,7 @@ class TestRotation:
def test_rotate_inverse(self):
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),
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))])
def test_apply(self,item):
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])
def test_average(self,angle):