result (and object interpretation) "flows" from rotation and data shapes

This commit is contained in:
Philip Eisenlohr 2023-09-19 17:50:28 -04:00
parent b3f98ab877
commit 0704f1122e
2 changed files with 29 additions and 21 deletions

View File

@ -375,6 +375,11 @@ class Rotation:
Return self@other. Return self@other.
Rotate vector, second-order tensor, or fourth-order tensor. Rotate vector, second-order tensor, or fourth-order tensor.
`other` is interpreted as an array of tensor quantities with the highest-possible order
considering the shape of `self`.
For instance, shapes of (2,) and (3,3) for `self` and `other` prompt interpretation of
`other` as a second-rank tensor and result in (2,) rotated tensors, whereas
shapes of (2,1) and (3,3) for `self` and `other` result in (2,3) rotated vectors.
Parameters Parameters
---------- ----------
@ -388,27 +393,30 @@ class Rotation:
""" """
if isinstance(other, np.ndarray): if isinstance(other, np.ndarray):
if self.shape + (3,) == other.shape: obs = util.shapeblender(self.shape,other.shape,keep_ones=False)[len(self.shape):]
q_m = self.quaternion[...,0] for l in [4,2,1]:
p_m = self.quaternion[...,1:] if obs[-l:] == l*(3,):
A = q_m**2 - np.einsum('...i,...i',p_m,p_m) bs = util.shapeblender(self.shape,other.shape[:-l],False)
B = 2. * np.einsum('...i,...i',p_m,other) self_ = self.broadcast_to(bs) if self.shape != bs else self
C = 2. * _P * q_m if l==1:
return np.block([(A * other[...,i]).reshape(self.shape+(1,)) + q_m = self_.quaternion[...,0]
(B * p_m[...,i]).reshape(self.shape+(1,)) + p_m = self_.quaternion[...,1:]
(C * ( p_m[...,(i+1)%3]*other[...,(i+2)%3]\ A = q_m**2 - np.einsum('...i,...i',p_m,p_m)
- p_m[...,(i+2)%3]*other[...,(i+1)%3])).reshape(self.shape+(1,)) B = 2. * np.einsum('...i,...i',p_m,other)
for i in [0,1,2]]) C = 2. * _P * q_m
if self.shape + (3,3) == other.shape: return np.block([(A * other[...,i]) +
R = self.as_matrix() (B * p_m[...,i]) +
return np.einsum('...im,...jn,...mn',R,R,other) (C * ( p_m[...,(i+1)%3]*other[...,(i+2)%3]
if self.shape + (3,3,3,3) == other.shape: - p_m[...,(i+2)%3]*other[...,(i+1)%3]))
R = self.as_matrix() for i in [0,1,2]]).reshape(bs+(3,),order='F')
return np.einsum('...im,...jn,...ko,...lp,...mnop',R,R,R,R,other) else:
else: return np.einsum({2: '...im,...jn,...mn',
raise ValueError('can only rotate vectors, second-order tensors, and fourth-order tensors') 4: '...im,...jn,...ko,...lp,...mnop'}[l],
*l*[self_.as_matrix()],
other)
raise ValueError('can only rotate vectors, second-order tensors, and fourth-order tensors')
elif isinstance(other, Rotation): elif isinstance(other, Rotation):
raise TypeError('use "R1*R2", i.e. multiplication, to compose rotations "R1" and "R2"') raise TypeError('use "R2*R1", i.e. multiplication, to compose rotations "R1" and "R2"')
else: else:
raise TypeError(f'cannot rotate "{type(other)}"') raise TypeError(f'cannot rotate "{type(other)}"')

View File

@ -1065,7 +1065,7 @@ class TestRotation:
@pytest.mark.parametrize('data',[np.random.rand(4), @pytest.mark.parametrize('data',[np.random.rand(4),
np.random.rand(3,2), np.random.rand(3,2),
np.random.rand(3,2,3,3)]) np.random.rand(3,3,3,1)])
def test_rotate_invalid_shape(self,data): def test_rotate_invalid_shape(self,data):
R = Rotation.from_random() R = Rotation.from_random()
with pytest.raises(ValueError): with pytest.raises(ValueError):