Rotation composition uses "*"; application of Rotation to object uses "@"; "apply()" works on both
This commit is contained in:
parent
f0351e403a
commit
2d6e6a2370
|
@ -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'],
|
||||
|
|
|
@ -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,12 +187,23 @@ 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"')
|
||||
|
||||
|
@ -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,9 +229,14 @@ 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"')
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
||||
####################################################################################################
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue