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)
|
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'],
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue