Merge branch 'vectorize_rotation' into 'development'
Vectorize rotation See merge request damask/DAMASK!162
This commit is contained in:
commit
6be1b63944
|
@ -172,7 +172,7 @@ for name in filenames:
|
||||||
|
|
||||||
elif inputtype == 'matrix':
|
elif inputtype == 'matrix':
|
||||||
d = representations['matrix'][1]
|
d = representations['matrix'][1]
|
||||||
o = damask.Rotation.fromMatrix(list(map(float,table.data[column:column+d])))
|
o = damask.Rotation.fromMatrix(np.array(list(map(float,table.data[column:column+d]))).reshape(3,3))
|
||||||
|
|
||||||
elif inputtype == 'frame':
|
elif inputtype == 'frame':
|
||||||
M = np.array(list(map(float,table.data[column[0]:column[0]+3] + \
|
M = np.array(list(map(float,table.data[column[0]:column[0]+3] + \
|
||||||
|
|
|
@ -214,7 +214,7 @@ for name in filenames:
|
||||||
|
|
||||||
outputAlive = True
|
outputAlive = True
|
||||||
while outputAlive and table.data_read(): # read next data line of ASCII table
|
while outputAlive and table.data_read(): # read next data line of ASCII table
|
||||||
o = damask.Rotation(list(map(float,table.data[column:column+4])))
|
o = damask.Rotation(np.array(list(map(float,table.data[column:column+4]))))
|
||||||
|
|
||||||
table.data_append( np.abs( np.sum(slip_direction * (o * force) ,axis=1) \
|
table.data_append( np.abs( np.sum(slip_direction * (o * force) ,axis=1) \
|
||||||
* np.sum(slip_normal * (o * normal),axis=1)))
|
* np.sum(slip_normal * (o * normal),axis=1)))
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
[run]
|
[run]
|
||||||
omit = tests/*
|
omit = tests/*
|
||||||
|
damask/_asciitable.py
|
||||||
|
damask/_test.py
|
||||||
|
damask/config/*
|
||||||
|
|
|
@ -38,6 +38,9 @@ class Orientation:
|
||||||
else:
|
else:
|
||||||
self.rotation = Rotation.fromQuaternion(rotation) # assume quaternion
|
self.rotation = Rotation.fromQuaternion(rotation) # assume quaternion
|
||||||
|
|
||||||
|
if self.rotation.quaternion.shape != (4,):
|
||||||
|
raise NotImplementedError('Support for multiple rotations missing')
|
||||||
|
|
||||||
def disorientation(self,
|
def disorientation(self,
|
||||||
other,
|
other,
|
||||||
SST = True,
|
SST = True,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from ._Lambert import ball_to_cube, cube_to_ball
|
from ._Lambert import ball_to_cube, cube_to_ball
|
||||||
|
from . import mechanics
|
||||||
|
|
||||||
_P = -1
|
_P = -1
|
||||||
|
|
||||||
|
@ -61,6 +62,8 @@ class Rotation:
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
"""Orientation displayed as unit quaternion, rotation matrix, and Bunge-Euler angles."""
|
"""Orientation displayed as unit quaternion, rotation matrix, and Bunge-Euler angles."""
|
||||||
|
if self.quaternion.shape != (4,):
|
||||||
|
raise NotImplementedError('Support for multiple rotations missing')
|
||||||
return '\n'.join([
|
return '\n'.join([
|
||||||
'Quaternion: (real={:.3f}, imag=<{:+.3f}, {:+.3f}, {:+.3f}>)'.format(*(self.quaternion)),
|
'Quaternion: (real={:.3f}, imag=<{:+.3f}, {:+.3f}, {:+.3f}>)'.format(*(self.quaternion)),
|
||||||
'Matrix:\n{}'.format(self.asMatrix()),
|
'Matrix:\n{}'.format(self.asMatrix()),
|
||||||
|
@ -83,6 +86,8 @@ class Rotation:
|
||||||
considere rotation of (3,3,3,3)-matrix
|
considere rotation of (3,3,3,3)-matrix
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if self.quaternion.shape != (4,):
|
||||||
|
raise NotImplementedError('Support for multiple rotations missing')
|
||||||
if isinstance(other, Rotation): # rotate a rotation
|
if isinstance(other, Rotation): # rotate a rotation
|
||||||
self_q = self.quaternion[0]
|
self_q = self.quaternion[0]
|
||||||
self_p = self.quaternion[1:]
|
self_p = self.quaternion[1:]
|
||||||
|
@ -107,7 +112,7 @@ class Rotation:
|
||||||
elif other.shape == (3,3,): # rotate a single (3x3)-matrix
|
elif other.shape == (3,3,): # rotate a single (3x3)-matrix
|
||||||
return np.dot(self.asMatrix(),np.dot(other,self.asMatrix().T))
|
return np.dot(self.asMatrix(),np.dot(other,self.asMatrix().T))
|
||||||
elif other.shape == (3,3,3,3,):
|
elif other.shape == (3,3,3,3,):
|
||||||
raise NotImplementedError
|
raise NotImplementedError('Support for rotation of 4th order tensors missing')
|
||||||
else:
|
else:
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
else:
|
else:
|
||||||
|
@ -116,7 +121,7 @@ class Rotation:
|
||||||
|
|
||||||
def inverse(self):
|
def inverse(self):
|
||||||
"""In-place inverse rotation/backward rotation."""
|
"""In-place inverse rotation/backward rotation."""
|
||||||
self.quaternion[1:] *= -1
|
self.quaternion[...,1:] *= -1
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def inversed(self):
|
def inversed(self):
|
||||||
|
@ -125,12 +130,12 @@ class Rotation:
|
||||||
|
|
||||||
|
|
||||||
def standardize(self):
|
def standardize(self):
|
||||||
"""In-place quaternion representation with positive q."""
|
"""In-place quaternion representation with positive real part."""
|
||||||
if self.quaternion[0] < 0.0: self.quaternion*=-1
|
self.quaternion[self.quaternion[...,0] < 0.0] *= -1
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def standardized(self):
|
def standardized(self):
|
||||||
"""Quaternion representation with positive q."""
|
"""Quaternion representation with positive real part."""
|
||||||
return self.copy().standardize()
|
return self.copy().standardize()
|
||||||
|
|
||||||
|
|
||||||
|
@ -157,15 +162,17 @@ class Rotation:
|
||||||
Rotation from which the average is rotated.
|
Rotation from which the average is rotated.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if self.quaternion.shape != (4,) or other.quaternion.shape != (4,):
|
||||||
|
raise NotImplementedError('Support for multiple rotations missing')
|
||||||
return Rotation.fromAverage([self,other])
|
return Rotation.fromAverage([self,other])
|
||||||
|
|
||||||
|
|
||||||
################################################################################################
|
################################################################################################
|
||||||
# convert to different orientation representations (numpy arrays)
|
# convert to different orientation representations (numpy arrays)
|
||||||
|
|
||||||
def asQuaternion(self):
|
def as_quaternion(self):
|
||||||
"""
|
"""
|
||||||
Unit quaternion [q, p_1, p_2, p_3] unless quaternion == True: damask.quaternion object.
|
Unit quaternion [q, p_1, p_2, p_3].
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
|
@ -175,8 +182,8 @@ class Rotation:
|
||||||
"""
|
"""
|
||||||
return self.quaternion
|
return self.quaternion
|
||||||
|
|
||||||
def asEulers(self,
|
def as_Eulers(self,
|
||||||
degrees = False):
|
degrees = False):
|
||||||
"""
|
"""
|
||||||
Bunge-Euler angles: (φ_1, ϕ, φ_2).
|
Bunge-Euler angles: (φ_1, ϕ, φ_2).
|
||||||
|
|
||||||
|
@ -190,9 +197,9 @@ class Rotation:
|
||||||
if degrees: eu = np.degrees(eu)
|
if degrees: eu = np.degrees(eu)
|
||||||
return eu
|
return eu
|
||||||
|
|
||||||
def asAxisAngle(self,
|
def as_axis_angle(self,
|
||||||
degrees = False,
|
degrees = False,
|
||||||
pair = False):
|
pair = False):
|
||||||
"""
|
"""
|
||||||
Axis angle representation [n_1, n_2, n_3, ω] unless pair == True: ([n_1, n_2, n_3], ω).
|
Axis angle representation [n_1, n_2, n_3, ω] unless pair == True: ([n_1, n_2, n_3], ω).
|
||||||
|
|
||||||
|
@ -205,15 +212,15 @@ class Rotation:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
ax = Rotation.qu2ax(self.quaternion)
|
ax = Rotation.qu2ax(self.quaternion)
|
||||||
if degrees: ax[3] = np.degrees(ax[3])
|
if degrees: ax[...,3] = np.degrees(ax[...,3])
|
||||||
return (ax[:3],ax[3]) if pair else ax
|
return (ax[...,:3],ax[...,3]) if pair else ax
|
||||||
|
|
||||||
def asMatrix(self):
|
def as_matrix(self):
|
||||||
"""Rotation matrix."""
|
"""Rotation matrix."""
|
||||||
return Rotation.qu2om(self.quaternion)
|
return Rotation.qu2om(self.quaternion)
|
||||||
|
|
||||||
def asRodrigues(self,
|
def as_Rodrigues(self,
|
||||||
vector = False):
|
vector = False):
|
||||||
"""
|
"""
|
||||||
Rodrigues-Frank vector representation [n_1, n_2, n_3, tan(ω/2)] unless vector == True: [n_1, n_2, n_3] * tan(ω/2).
|
Rodrigues-Frank vector representation [n_1, n_2, n_3, tan(ω/2)] unless vector == True: [n_1, n_2, n_3] * tan(ω/2).
|
||||||
|
|
||||||
|
@ -224,9 +231,9 @@ class Rotation:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
ro = Rotation.qu2ro(self.quaternion)
|
ro = Rotation.qu2ro(self.quaternion)
|
||||||
return ro[:3]*ro[3] if vector else ro
|
return ro[...,:3]*ro[...,3] if vector else ro
|
||||||
|
|
||||||
def asHomochoric(self):
|
def as_homochoric(self):
|
||||||
"""Homochoric vector: (h_1, h_2, h_3)."""
|
"""Homochoric vector: (h_1, h_2, h_3)."""
|
||||||
return Rotation.qu2ho(self.quaternion)
|
return Rotation.qu2ho(self.quaternion)
|
||||||
|
|
||||||
|
@ -234,7 +241,7 @@ class Rotation:
|
||||||
"""Cubochoric vector: (c_1, c_2, c_3)."""
|
"""Cubochoric vector: (c_1, c_2, c_3)."""
|
||||||
return Rotation.qu2cu(self.quaternion)
|
return Rotation.qu2cu(self.quaternion)
|
||||||
|
|
||||||
def asM(self):
|
def M(self): # ToDo not sure about the name: as_M or M? we do not have a from_M
|
||||||
"""
|
"""
|
||||||
Intermediate representation supporting quaternion averaging.
|
Intermediate representation supporting quaternion averaging.
|
||||||
|
|
||||||
|
@ -244,114 +251,133 @@ class Rotation:
|
||||||
https://doi.org/10.2514/1.28949
|
https://doi.org/10.2514/1.28949
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return np.outer(self.quaternion,self.quaternion)
|
return np.einsum('...i,...j',self.quaternion,self.quaternion)
|
||||||
|
|
||||||
|
# for compatibility (old names do not follow convention)
|
||||||
|
asM = M
|
||||||
|
asQuaternion = as_quaternion
|
||||||
|
asEulers = as_Eulers
|
||||||
|
asAxisAngle = as_axis_angle
|
||||||
|
asMatrix = as_matrix
|
||||||
|
asRodrigues = as_Rodrigues
|
||||||
|
asHomochoric = as_homochoric
|
||||||
|
|
||||||
################################################################################################
|
################################################################################################
|
||||||
# static constructors. The input data needs to follow the convention, options allow to
|
# Static constructors. The input data needs to follow the conventions, options allow to
|
||||||
# relax these convections
|
# relax the conventions.
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fromQuaternion(quaternion,
|
def from_quaternion(quaternion,
|
||||||
acceptHomomorph = False,
|
acceptHomomorph = False,
|
||||||
P = -1):
|
P = -1):
|
||||||
|
|
||||||
qu = quaternion if isinstance(quaternion,np.ndarray) and quaternion.dtype == np.dtype(float) \
|
qu = np.array(quaternion,dtype=float)
|
||||||
else np.array(quaternion,dtype=float)
|
if qu.shape[:-2:-1] != (4,):
|
||||||
if P > 0: qu[1:4] *= -1 # convert from P=1 to P=-1
|
raise ValueError('Invalid shape.')
|
||||||
if qu[0] < 0.0:
|
|
||||||
if acceptHomomorph:
|
if P > 0: qu[...,1:4] *= -1 # convert from P=1 to P=-1
|
||||||
qu *= -1.
|
if acceptHomomorph:
|
||||||
else:
|
qu[qu[...,0] < 0.0] *= -1
|
||||||
raise ValueError('Quaternion has negative first component: {}.'.format(qu[0]))
|
else:
|
||||||
if not np.isclose(np.linalg.norm(qu), 1.0):
|
if np.any(qu[...,0] < 0.0):
|
||||||
raise ValueError('Quaternion is not of unit length: {} {} {} {}.'.format(*qu))
|
raise ValueError('Quaternion with negative first (real) component.')
|
||||||
|
if not np.all(np.isclose(np.linalg.norm(qu,axis=-1), 1.0)):
|
||||||
|
raise ValueError('Quaternion is not of unit length.')
|
||||||
|
|
||||||
return Rotation(qu)
|
return Rotation(qu)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fromEulers(eulers,
|
def from_Eulers(eulers,
|
||||||
degrees = False):
|
degrees = False):
|
||||||
|
|
||||||
|
eu = np.array(eulers,dtype=float)
|
||||||
|
if eu.shape[:-2:-1] != (3,):
|
||||||
|
raise ValueError('Invalid shape.')
|
||||||
|
|
||||||
eu = eulers if isinstance(eulers, np.ndarray) and eulers.dtype == np.dtype(float) \
|
|
||||||
else np.array(eulers,dtype=float)
|
|
||||||
eu = np.radians(eu) if degrees else eu
|
eu = np.radians(eu) if degrees else eu
|
||||||
if np.any(eu < 0.0) or np.any(eu > 2.0*np.pi) or eu[1] > np.pi:
|
if np.any(eu < 0.0) or np.any(eu > 2.0*np.pi) or np.any(eu[...,1] > np.pi): # ToDo: No separate check for PHI
|
||||||
raise ValueError('Euler angles outside of [0..2π],[0..π],[0..2π]: {} {} {}.'.format(*eu))
|
raise ValueError('Euler angles outside of [0..2π],[0..π],[0..2π].')
|
||||||
|
|
||||||
return Rotation(Rotation.eu2qu(eu))
|
return Rotation(Rotation.eu2qu(eu))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fromAxisAngle(angleAxis,
|
def from_axis_angle(axis_angle,
|
||||||
degrees = False,
|
degrees = False,
|
||||||
normalise = False,
|
normalise = False,
|
||||||
P = -1):
|
P = -1):
|
||||||
|
|
||||||
ax = angleAxis if isinstance(angleAxis, np.ndarray) and angleAxis.dtype == np.dtype(float) \
|
ax = np.array(axis_angle,dtype=float)
|
||||||
else np.array(angleAxis,dtype=float)
|
if ax.shape[:-2:-1] != (4,):
|
||||||
if P > 0: ax[0:3] *= -1 # convert from P=1 to P=-1
|
raise ValueError('Invalid shape.')
|
||||||
if degrees: ax[ 3] = np.radians(ax[3])
|
|
||||||
if normalise: ax[0:3] /= np.linalg.norm(ax[0:3])
|
if P > 0: ax[...,0:3] *= -1 # convert from P=1 to P=-1
|
||||||
if ax[3] < 0.0 or ax[3] > np.pi:
|
if degrees: ax[..., 3] = np.radians(ax[...,3])
|
||||||
raise ValueError('Axis angle rotation angle outside of [0..π]: {}.'.format(ax[3]))
|
if normalise: ax[...,0:3] /= np.linalg.norm(ax[...,0:3],axis=-1)
|
||||||
if not np.isclose(np.linalg.norm(ax[0:3]), 1.0):
|
if np.any(ax[...,3] < 0.0) or np.any(ax[...,3] > np.pi):
|
||||||
raise ValueError('Axis angle rotation axis is not of unit length: {} {} {}.'.format(*ax[0:3]))
|
raise ValueError('Axis angle rotation angle outside of [0..π].')
|
||||||
|
if not np.all(np.isclose(np.linalg.norm(ax[...,0:3],axis=-1), 1.0)):
|
||||||
|
raise ValueError('Axis angle rotation axis is not of unit length.')
|
||||||
|
|
||||||
return Rotation(Rotation.ax2qu(ax))
|
return Rotation(Rotation.ax2qu(ax))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fromBasis(basis,
|
def from_basis(basis,
|
||||||
orthonormal = True,
|
orthonormal = True,
|
||||||
reciprocal = False,
|
reciprocal = False):
|
||||||
):
|
|
||||||
|
om = np.array(basis,dtype=float)
|
||||||
|
if om.shape[:-3:-1] != (3,3):
|
||||||
|
raise ValueError('Invalid shape.')
|
||||||
|
|
||||||
om = basis if isinstance(basis, np.ndarray) else np.array(basis).reshape(3,3)
|
|
||||||
if reciprocal:
|
if reciprocal:
|
||||||
om = np.linalg.inv(om.T/np.pi) # transform reciprocal basis set
|
om = np.linalg.inv(mechanics.transpose(om)/np.pi) # transform reciprocal basis set
|
||||||
orthonormal = False # contains stretch
|
orthonormal = False # contains stretch
|
||||||
if not orthonormal:
|
if not orthonormal:
|
||||||
(U,S,Vh) = np.linalg.svd(om) # singular value decomposition
|
(U,S,Vh) = np.linalg.svd(om) # singular value decomposition
|
||||||
om = np.dot(U,Vh)
|
om = np.einsum('...ij,...jl->...il',U,Vh)
|
||||||
if not np.isclose(np.linalg.det(om),1.0):
|
if not np.all(np.isclose(np.linalg.det(om),1.0)):
|
||||||
raise ValueError('matrix is not a proper rotation: {}.'.format(om))
|
raise ValueError('Orientation matrix has determinant ≠ 1.')
|
||||||
if not np.isclose(np.dot(om[0],om[1]), 0.0) \
|
if not np.all(np.isclose(np.einsum('...i,...i',om[...,0],om[...,1]), 0.0)) \
|
||||||
or not np.isclose(np.dot(om[1],om[2]), 0.0) \
|
or not np.all(np.isclose(np.einsum('...i,...i',om[...,1],om[...,2]), 0.0)) \
|
||||||
or not np.isclose(np.dot(om[2],om[0]), 0.0):
|
or not np.all(np.isclose(np.einsum('...i,...i',om[...,2],om[...,0]), 0.0)):
|
||||||
raise ValueError('matrix is not orthogonal: {}.'.format(om))
|
raise ValueError('Orientation matrix is not orthogonal.')
|
||||||
|
|
||||||
return Rotation(Rotation.om2qu(om))
|
return Rotation(Rotation.om2qu(om))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fromMatrix(om,
|
def from_matrix(om):
|
||||||
):
|
|
||||||
|
|
||||||
return Rotation.fromBasis(om)
|
return Rotation.from_basis(om)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fromRodrigues(rodrigues,
|
def from_Rodrigues(rodrigues,
|
||||||
normalise = False,
|
normalise = False,
|
||||||
P = -1):
|
P = -1):
|
||||||
|
|
||||||
ro = rodrigues if isinstance(rodrigues, np.ndarray) and rodrigues.dtype == np.dtype(float) \
|
ro = np.array(rodrigues,dtype=float)
|
||||||
else np.array(rodrigues,dtype=float)
|
if ro.shape[:-2:-1] != (4,):
|
||||||
if P > 0: ro[0:3] *= -1 # convert from P=1 to P=-1
|
raise ValueError('Invalid shape.')
|
||||||
if normalise: ro[0:3] /= np.linalg.norm(ro[0:3])
|
|
||||||
if not np.isclose(np.linalg.norm(ro[0:3]), 1.0):
|
if P > 0: ro[...,0:3] *= -1 # convert from P=1 to P=-1
|
||||||
raise ValueError('Rodrigues rotation axis is not of unit length: {} {} {}.'.format(*ro[0:3]))
|
if normalise: ro[...,0:3] /= np.linalg.norm(ro[...,0:3],axis=-1)
|
||||||
if ro[3] < 0.0:
|
if np.any(ro[...,3] < 0.0):
|
||||||
raise ValueError('Rodrigues rotation angle not positive: {}.'.format(ro[3]))
|
raise ValueError('Rodrigues vector rotation angle not positive.')
|
||||||
|
if not np.all(np.isclose(np.linalg.norm(ro[...,0:3],axis=-1), 1.0)):
|
||||||
|
raise ValueError('Rodrigues vector rotation axis is not of unit length.')
|
||||||
|
|
||||||
return Rotation(Rotation.ro2qu(ro))
|
return Rotation(Rotation.ro2qu(ro))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fromHomochoric(homochoric,
|
def from_homochoric(homochoric,
|
||||||
P = -1):
|
P = -1):
|
||||||
|
|
||||||
|
ho = np.array(homochoric,dtype=float)
|
||||||
|
if ho.shape[:-2:-1] != (3,):
|
||||||
|
raise ValueError('Invalid shape.')
|
||||||
|
|
||||||
ho = homochoric if isinstance(homochoric, np.ndarray) and homochoric.dtype == np.dtype(float) \
|
|
||||||
else np.array(homochoric,dtype=float)
|
|
||||||
if P > 0: ho *= -1 # convert from P=1 to P=-1
|
if P > 0: ho *= -1 # convert from P=1 to P=-1
|
||||||
|
|
||||||
if np.linalg.norm(ho) > (3.*np.pi/4.)**(1./3.)+1e-9:
|
if np.any(np.linalg.norm(ho,axis=-1) > (3.*np.pi/4.)**(1./3.)+1e-9):
|
||||||
raise ValueError('Coordinate outside of the sphere: {} {} {}.'.format(ho))
|
raise ValueError('Homochoric coordinate outside of the sphere.')
|
||||||
|
|
||||||
return Rotation(Rotation.ho2qu(ho))
|
return Rotation(Rotation.ho2qu(ho))
|
||||||
|
|
||||||
|
@ -359,11 +385,12 @@ class Rotation:
|
||||||
def fromCubochoric(cubochoric,
|
def fromCubochoric(cubochoric,
|
||||||
P = -1):
|
P = -1):
|
||||||
|
|
||||||
cu = cubochoric if isinstance(cubochoric, np.ndarray) and cubochoric.dtype == np.dtype(float) \
|
cu = np.array(cubochoric,dtype=float)
|
||||||
else np.array(cubochoric,dtype=float)
|
if cu.shape[:-2:-1] != (3,):
|
||||||
|
raise ValueError('Invalid shape.')
|
||||||
|
|
||||||
if np.abs(np.max(cu))>np.pi**(2./3.) * 0.5+1e-9:
|
if np.abs(np.max(cu))>np.pi**(2./3.) * 0.5+1e-9:
|
||||||
raise ValueError('Coordinate outside of the cube: {} {} {}.'.format(*cu))
|
raise ValueError('Cubochoric coordinate outside of the cube: {} {} {}.'.format(*cu))
|
||||||
|
|
||||||
ho = Rotation.cu2ho(cu)
|
ho = Rotation.cu2ho(cu)
|
||||||
if P > 0: ho *= -1 # convert from P=1 to P=-1
|
if P > 0: ho *= -1 # convert from P=1 to P=-1
|
||||||
|
@ -403,17 +430,34 @@ class Rotation:
|
||||||
|
|
||||||
return Rotation.fromQuaternion(np.real(vec.T[eig.argmax()]),acceptHomomorph = True)
|
return Rotation.fromQuaternion(np.real(vec.T[eig.argmax()]),acceptHomomorph = True)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fromRandom():
|
def from_random(shape=None):
|
||||||
r = np.random.random(3)
|
if shape is None:
|
||||||
A = np.sqrt(r[2])
|
r = np.random.random(3)
|
||||||
B = np.sqrt(1.0-r[2])
|
elif hasattr(shape, '__iter__'):
|
||||||
return Rotation(np.array([np.cos(2.0*np.pi*r[0])*A,
|
r = np.random.random(tuple(shape)+(3,))
|
||||||
np.sin(2.0*np.pi*r[1])*B,
|
else:
|
||||||
np.cos(2.0*np.pi*r[1])*B,
|
r = np.random.random((shape,3))
|
||||||
np.sin(2.0*np.pi*r[0])*A])).standardize()
|
|
||||||
|
|
||||||
|
A = np.sqrt(r[...,2])
|
||||||
|
B = np.sqrt(1.0-r[...,2])
|
||||||
|
q = np.stack([np.cos(2.0*np.pi*r[...,0])*A,
|
||||||
|
np.sin(2.0*np.pi*r[...,1])*B,
|
||||||
|
np.cos(2.0*np.pi*r[...,1])*B,
|
||||||
|
np.sin(2.0*np.pi*r[...,0])*A],axis=-1)
|
||||||
|
|
||||||
|
return Rotation(q.reshape(r.shape[:-1]+(4,)) if shape is not None else q).standardize()
|
||||||
|
|
||||||
|
|
||||||
|
# for compatibility (old names do not follow convention)
|
||||||
|
fromQuaternion = from_quaternion
|
||||||
|
fromEulers = from_Eulers
|
||||||
|
fromAxisAngle = from_axis_angle
|
||||||
|
fromBasis = from_basis
|
||||||
|
fromMatrix = from_matrix
|
||||||
|
fromRodrigues = from_Rodrigues
|
||||||
|
fromHomochoric = from_homochoric
|
||||||
|
fromRandom = from_random
|
||||||
|
|
||||||
####################################################################################################
|
####################################################################################################
|
||||||
# Code below available according to the following conditions on https://github.com/MarDiehl/3Drotations
|
# Code below available according to the following conditions on https://github.com/MarDiehl/3Drotations
|
||||||
|
@ -808,12 +852,11 @@ class Rotation:
|
||||||
c = np.cos(ax[3]*0.5)
|
c = np.cos(ax[3]*0.5)
|
||||||
s = np.sin(ax[3]*0.5)
|
s = np.sin(ax[3]*0.5)
|
||||||
qu = np.array([ c, ax[0]*s, ax[1]*s, ax[2]*s ])
|
qu = np.array([ c, ax[0]*s, ax[1]*s, ax[2]*s ])
|
||||||
return qu
|
|
||||||
else:
|
else:
|
||||||
c = np.cos(ax[...,3:4]*.5)
|
c = np.cos(ax[...,3:4]*.5)
|
||||||
s = np.sin(ax[...,3:4]*.5)
|
s = np.sin(ax[...,3:4]*.5)
|
||||||
qu = np.where(np.abs(ax[...,3:4])<1.e-6,[1.0, 0.0, 0.0, 0.0],np.block([c, ax[...,:3]*s]))
|
qu = np.where(np.abs(ax[...,3:4])<1.e-6,[1.0, 0.0, 0.0, 0.0],np.block([c, ax[...,:3]*s]))
|
||||||
return qu
|
return qu
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def ax2om(ax):
|
def ax2om(ax):
|
||||||
|
@ -859,7 +902,7 @@ class Rotation:
|
||||||
# 180 degree case
|
# 180 degree case
|
||||||
ro += [np.inf] if np.isclose(ax[3],np.pi,atol=1.0e-15,rtol=0.0) else \
|
ro += [np.inf] if np.isclose(ax[3],np.pi,atol=1.0e-15,rtol=0.0) else \
|
||||||
[np.tan(ax[3]*0.5)]
|
[np.tan(ax[3]*0.5)]
|
||||||
return np.array(ro)
|
ro = np.array(ro)
|
||||||
else:
|
else:
|
||||||
ro = np.block([ax[...,:3],
|
ro = np.block([ax[...,:3],
|
||||||
np.where(np.isclose(ax[...,3:4],np.pi,atol=1.e-15,rtol=.0),
|
np.where(np.isclose(ax[...,3:4],np.pi,atol=1.e-15,rtol=.0),
|
||||||
|
@ -867,7 +910,7 @@ class Rotation:
|
||||||
np.tan(ax[...,3:4]*0.5))
|
np.tan(ax[...,3:4]*0.5))
|
||||||
])
|
])
|
||||||
ro[np.abs(ax[...,3])<1.e-6] = [.0,.0,_P,.0]
|
ro[np.abs(ax[...,3])<1.e-6] = [.0,.0,_P,.0]
|
||||||
return ro
|
return ro
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def ax2ho(ax):
|
def ax2ho(ax):
|
||||||
|
@ -875,11 +918,10 @@ class Rotation:
|
||||||
if len(ax.shape) == 1:
|
if len(ax.shape) == 1:
|
||||||
f = (0.75 * ( ax[3] - np.sin(ax[3]) ))**(1.0/3.0)
|
f = (0.75 * ( ax[3] - np.sin(ax[3]) ))**(1.0/3.0)
|
||||||
ho = ax[0:3] * f
|
ho = ax[0:3] * f
|
||||||
return ho
|
|
||||||
else:
|
else:
|
||||||
f = (0.75 * ( ax[...,3:4] - np.sin(ax[...,3:4]) ))**(1.0/3.0)
|
f = (0.75 * ( ax[...,3:4] - np.sin(ax[...,3:4]) ))**(1.0/3.0)
|
||||||
ho = ax[...,:3] * f
|
ho = ax[...,:3] * f
|
||||||
return ho
|
return ho
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def ax2cu(ax):
|
def ax2cu(ax):
|
||||||
|
@ -936,7 +978,6 @@ class Rotation:
|
||||||
f = np.where(np.isfinite(ro[...,3:4]),2.0*np.arctan(ro[...,3:4]) -np.sin(2.0*np.arctan(ro[...,3:4])),np.pi)
|
f = np.where(np.isfinite(ro[...,3:4]),2.0*np.arctan(ro[...,3:4]) -np.sin(2.0*np.arctan(ro[...,3:4])),np.pi)
|
||||||
ho = np.where(np.broadcast_to(np.sum(ro[...,0:3]**2.0,axis=-1,keepdims=True) < 1.e-6,ro[...,0:3].shape),
|
ho = np.where(np.broadcast_to(np.sum(ro[...,0:3]**2.0,axis=-1,keepdims=True) < 1.e-6,ro[...,0:3].shape),
|
||||||
np.zeros(3), ro[...,0:3]* (0.75*f)**(1.0/3.0))
|
np.zeros(3), ro[...,0:3]* (0.75*f)**(1.0/3.0))
|
||||||
|
|
||||||
return ho
|
return ho
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -1010,7 +1051,7 @@ class Rotation:
|
||||||
if len(ho.shape) == 1:
|
if len(ho.shape) == 1:
|
||||||
return ball_to_cube(ho)
|
return ball_to_cube(ho)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError('Support for multiple rotations missing')
|
||||||
|
|
||||||
|
|
||||||
#---------- Cubochoric ----------
|
#---------- Cubochoric ----------
|
||||||
|
@ -1045,4 +1086,4 @@ class Rotation:
|
||||||
if len(cu.shape) == 1:
|
if len(cu.shape) == 1:
|
||||||
return cube_to_ball(cu)
|
return cube_to_ball(cu)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError('Support for multiple rotations missing')
|
||||||
|
|
|
@ -135,16 +135,16 @@ def PK2(P,F):
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
P : numpy.ndarray of shape (:,3,3) or (3,3)
|
P : numpy.ndarray of shape (...,3,3) or (3,3)
|
||||||
First Piola-Kirchhoff stress.
|
First Piola-Kirchhoff stress.
|
||||||
F : numpy.ndarray of shape (:,3,3) or (3,3)
|
F : numpy.ndarray of shape (...,3,3) or (3,3)
|
||||||
Deformation gradient.
|
Deformation gradient.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if _np.shape(F) == _np.shape(P) == (3,3):
|
if _np.shape(F) == _np.shape(P) == (3,3):
|
||||||
S = _np.dot(_np.linalg.inv(F),P)
|
S = _np.dot(_np.linalg.inv(F),P)
|
||||||
else:
|
else:
|
||||||
S = _np.einsum('ijk,ikl->ijl',_np.linalg.inv(F),P)
|
S = _np.einsum('...jk,...kl->...jl',_np.linalg.inv(F),P)
|
||||||
return symmetric(S)
|
return symmetric(S)
|
||||||
|
|
||||||
|
|
||||||
|
@ -241,7 +241,7 @@ def symmetric(T):
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
T : numpy.ndarray of shape (:,3,3) or (3,3)
|
T : numpy.ndarray of shape (...,3,3) or (3,3)
|
||||||
Tensor of which the symmetrized values are computed.
|
Tensor of which the symmetrized values are computed.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -254,12 +254,12 @@ def transpose(T):
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
T : numpy.ndarray of shape (:,3,3) or (3,3)
|
T : numpy.ndarray of shape (...,3,3) or (3,3)
|
||||||
Tensor of which the transpose is computed.
|
Tensor of which the transpose is computed.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return T.T if _np.shape(T) == (3,3) else \
|
return T.T if _np.shape(T) == (3,3) else \
|
||||||
_np.transpose(T,(0,2,1))
|
_np.swapaxes(T,axis2=-2,axis1=-1)
|
||||||
|
|
||||||
|
|
||||||
def _polar_decomposition(T,requested):
|
def _polar_decomposition(T,requested):
|
||||||
|
|
|
@ -157,6 +157,30 @@ class TestRotation:
|
||||||
print(m,o,rot.asQuaternion())
|
print(m,o,rot.asQuaternion())
|
||||||
assert ok and o.max() < np.pi**(2./3.)*0.5+1.e-9
|
assert ok and o.max() < np.pi**(2./3.)*0.5+1.e-9
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('function',[Rotation.from_quaternion,
|
||||||
|
Rotation.from_Eulers,
|
||||||
|
Rotation.from_axis_angle,
|
||||||
|
Rotation.from_matrix,
|
||||||
|
Rotation.from_Rodrigues,
|
||||||
|
Rotation.from_homochoric])
|
||||||
|
def test_invalid_shape(self,function):
|
||||||
|
invalid_shape = np.random.random(np.random.randint(8,32,(3)))
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
function(invalid_shape)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('function,invalid',[(Rotation.from_quaternion, np.array([-1,0,0,0])),
|
||||||
|
(Rotation.from_quaternion, np.array([1,1,1,0])),
|
||||||
|
(Rotation.from_Eulers, np.array([1,4,0])),
|
||||||
|
(Rotation.from_axis_angle, np.array([1,0,0,4])),
|
||||||
|
(Rotation.from_axis_angle, np.array([1,1,0,1])),
|
||||||
|
(Rotation.from_matrix, np.random.rand(3,3)),
|
||||||
|
(Rotation.from_Rodrigues, np.array([1,0,0,-1])),
|
||||||
|
(Rotation.from_Rodrigues, np.array([1,1,0,1])),
|
||||||
|
(Rotation.from_homochoric, np.array([2,2,2])) ])
|
||||||
|
def test_invalid(self,function,invalid):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
function(invalid)
|
||||||
|
|
||||||
@pytest.mark.parametrize('conversion',[Rotation.qu2om,
|
@pytest.mark.parametrize('conversion',[Rotation.qu2om,
|
||||||
Rotation.qu2eu,
|
Rotation.qu2eu,
|
||||||
Rotation.qu2ax,
|
Rotation.qu2ax,
|
||||||
|
|
Loading…
Reference in New Issue