added first typehints for rotation and orientation modules

This commit is contained in:
Daniel Otto de Mentock 2021-12-14 17:05:00 +01:00
parent a89eff2991
commit cef885cfde
2 changed files with 211 additions and 176 deletions

View File

@ -1,5 +1,6 @@
import inspect
import copy
from typing import Union, Callable, Sequence, Dict, Any, Tuple, List
import numpy as np
@ -93,12 +94,12 @@ class Orientation(Rotation,Crystal):
@util.extend_docstring(_parameter_doc)
def __init__(self,
rotation = np.array([1.0,0.0,0.0,0.0]), *,
family = None,
lattice = None,
a = None,b = None,c = None,
alpha = None,beta = None,gamma = None,
degrees = False):
rotation: Union[Sequence[float], np.ndarray, Rotation] = np.array([1.,0.,0.,0.]), *,
family: str = None,
lattice: str = None,
a: float = None, b: float = None, c: float = None,
alpha: float = None, beta: float = None, gamma: float = None,
degrees: bool = False):
"""
New orientation.
@ -115,13 +116,12 @@ class Orientation(Rotation,Crystal):
a=a,b=b,c=c, alpha=alpha,beta=beta,gamma=gamma, degrees=degrees)
def __repr__(self):
def __repr__(self) -> str:
"""Represent."""
return '\n'.join([Crystal.__repr__(self),
Rotation.__repr__(self)])
def __copy__(self,rotation=None):
def __copy__(self,rotation: Union[Sequence[float], np.ndarray, Rotation] = None) -> "Orientation":
"""Create deep copy."""
dup = copy.deepcopy(self)
if rotation is not None:
@ -131,7 +131,8 @@ class Orientation(Rotation,Crystal):
copy = __copy__
def __eq__(self,other):
def __eq__(self, other: object) -> bool:
"""
Equal to other.
@ -141,12 +142,14 @@ class Orientation(Rotation,Crystal):
Orientation to check for equality.
"""
if not isinstance(other, Orientation):
raise TypeError
matching_type = self.family == other.family and \
self.lattice == other.lattice and \
self.parameters == other.parameters
return np.logical_and(matching_type,super(self.__class__,self.reduced).__eq__(other.reduced))
def __ne__(self,other):
def __ne__(self, other: object) -> bool:
"""
Not equal to other.
@ -156,10 +159,16 @@ class Orientation(Rotation,Crystal):
Orientation to check for equality.
"""
if not isinstance(other, Orientation):
raise TypeError
return np.logical_not(self==other)
def isclose(self,other,rtol=1e-5,atol=1e-8,equal_nan=True):
def isclose(self,
other: object,
rtol: float = 1e-5,
atol: float = 1e-8,
equal_nan: bool = True) -> bool:
"""
Report where values are approximately equal to corresponding ones of other Orientation.
@ -180,6 +189,8 @@ class Orientation(Rotation,Crystal):
Mask indicating where corresponding orientations are close.
"""
if not isinstance(other, Orientation):
raise TypeError
matching_type = self.family == other.family and \
self.lattice == other.lattice and \
self.parameters == other.parameters
@ -187,7 +198,11 @@ class Orientation(Rotation,Crystal):
def allclose(self,other,rtol=1e-5,atol=1e-8,equal_nan=True):
def allclose(self,
other: object,
rtol: float = 1e-5,
atol: float = 1e-8,
equal_nan: bool = True) -> Union[bool, np.bool_]:
"""
Test whether all values are approximately equal to corresponding ones of other Orientation.
@ -208,10 +223,12 @@ class Orientation(Rotation,Crystal):
Whether all values are close between both orientations.
"""
if not isinstance(other, Orientation):
raise TypeError
return np.all(self.isclose(other,rtol,atol,equal_nan))
def __mul__(self,other):
def __mul__(self, other: Union[Rotation, "Orientation"]) -> "Orientation":
"""
Compose this orientation with other.
@ -233,7 +250,7 @@ class Orientation(Rotation,Crystal):
@staticmethod
def _split_kwargs(kwargs,target):
def _split_kwargs(kwargs: Dict[str, Any], target: Callable) -> Tuple[Dict[str, Any], ...]:
"""
Separate keyword arguments in 'kwargs' targeted at 'target' from general keyword arguments of Orientation objects.
@ -252,7 +269,7 @@ class Orientation(Rotation,Crystal):
Valid keyword arguments of Orientation object.
"""
kws = ()
kws: Tuple[Dict[str, Any], ...] = ()
for t in (target,Orientation.__init__):
kws += ({key: kwargs[key] for key in set(inspect.signature(t).parameters) & set(kwargs)},)
@ -264,85 +281,88 @@ class Orientation(Rotation,Crystal):
@classmethod
@util.extended_docstring(Rotation.from_random,_parameter_doc)
def from_random(cls,**kwargs):
@util.extended_docstring(Rotation.from_random, _parameter_doc)
def from_random(cls, **kwargs) -> "Orientation":
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_random)
return cls(rotation=Rotation.from_random(**kwargs_rot),**kwargs_ori)
@classmethod
@util.extended_docstring(Rotation.from_quaternion,_parameter_doc)
def from_quaternion(cls,**kwargs):
def from_quaternion(cls, **kwargs) -> "Orientation":
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_quaternion)
return cls(rotation=Rotation.from_quaternion(**kwargs_rot),**kwargs_ori)
@classmethod
@util.extended_docstring(Rotation.from_Euler_angles,_parameter_doc)
def from_Euler_angles(cls,**kwargs):
def from_Euler_angles(cls, **kwargs) -> "Orientation":
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_Euler_angles)
return cls(rotation=Rotation.from_Euler_angles(**kwargs_rot),**kwargs_ori)
@classmethod
@util.extended_docstring(Rotation.from_axis_angle,_parameter_doc)
def from_axis_angle(cls,**kwargs):
def from_axis_angle(cls, **kwargs) -> "Orientation":
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_axis_angle)
return cls(rotation=Rotation.from_axis_angle(**kwargs_rot),**kwargs_ori)
@classmethod
@util.extended_docstring(Rotation.from_basis,_parameter_doc)
def from_basis(cls,**kwargs):
def from_basis(cls, **kwargs) -> "Orientation":
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_basis)
return cls(rotation=Rotation.from_basis(**kwargs_rot),**kwargs_ori)
@classmethod
@util.extended_docstring(Rotation.from_matrix,_parameter_doc)
def from_matrix(cls,**kwargs):
def from_matrix(cls, **kwargs) -> "Orientation":
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_matrix)
return cls(rotation=Rotation.from_matrix(**kwargs_rot),**kwargs_ori)
@classmethod
@util.extended_docstring(Rotation.from_Rodrigues_vector,_parameter_doc)
def from_Rodrigues_vector(cls,**kwargs):
def from_Rodrigues_vector(cls, **kwargs) -> "Orientation":
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_Rodrigues_vector)
return cls(rotation=Rotation.from_Rodrigues_vector(**kwargs_rot),**kwargs_ori)
@classmethod
@util.extended_docstring(Rotation.from_homochoric,_parameter_doc)
def from_homochoric(cls,**kwargs):
def from_homochoric(cls, **kwargs) -> "Orientation":
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_homochoric)
return cls(rotation=Rotation.from_homochoric(**kwargs_rot),**kwargs_ori)
@classmethod
@util.extended_docstring(Rotation.from_cubochoric,_parameter_doc)
def from_cubochoric(cls,**kwargs):
def from_cubochoric(cls, **kwargs) -> "Orientation":
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_cubochoric)
return cls(rotation=Rotation.from_cubochoric(**kwargs_rot),**kwargs_ori)
@classmethod
@util.extended_docstring(Rotation.from_spherical_component,_parameter_doc)
def from_spherical_component(cls,**kwargs):
def from_spherical_component(cls, **kwargs) -> "Orientation":
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_spherical_component)
return cls(rotation=Rotation.from_spherical_component(**kwargs_rot),**kwargs_ori)
@classmethod
@util.extended_docstring(Rotation.from_fiber_component,_parameter_doc)
def from_fiber_component(cls,**kwargs):
def from_fiber_component(cls, **kwargs) -> "Orientation":
kwargs_rot,kwargs_ori = Orientation._split_kwargs(kwargs,Rotation.from_fiber_component)
return cls(rotation=Rotation.from_fiber_component(**kwargs_rot),**kwargs_ori)
@classmethod
@util.extend_docstring(_parameter_doc)
def from_directions(cls,uvw,hkl,**kwargs):
def from_directions(cls,
uvw: Union[Sequence[float], np.ndarray],
hkl: Union[Sequence[float], np.ndarray],
**kwargs) -> "Orientation":
"""
Initialize orientation object from two crystallographic directions.
@ -362,7 +382,7 @@ class Orientation(Rotation,Crystal):
@property
def equivalent(self):
def equivalent(self) -> "Orientation":
"""
Orientations that are symmetrically equivalent.
@ -376,7 +396,7 @@ class Orientation(Rotation,Crystal):
@property
def reduced(self):
def reduced(self) -> "Orientation":
"""Select symmetrically equivalent orientation that falls into fundamental zone according to symmetry."""
eq = self.equivalent
ok = eq.in_FZ
@ -385,9 +405,8 @@ class Orientation(Rotation,Crystal):
sort = 0 if len(loc) == 1 else np.lexsort(loc[:0:-1])
return eq[ok][sort].reshape(self.shape)
@property
def in_FZ(self):
def in_FZ(self) -> Union[np.bool_, np.ndarray]:
"""
Check whether orientation falls into fundamental zone of own symmetry.
@ -431,7 +450,7 @@ class Orientation(Rotation,Crystal):
@property
def in_disorientation_FZ(self):
def in_disorientation_FZ(self) -> np.ndarray:
"""
Check whether orientation falls into fundamental zone of disorientations.
@ -471,8 +490,7 @@ class Orientation(Rotation,Crystal):
else:
return np.ones_like(rho[...,0],dtype=bool)
def disorientation(self,other,return_operators=False):
def disorientation(self, other, return_operators = False):
"""
Calculate disorientation between myself and given other orientation.
@ -518,8 +536,8 @@ class Orientation(Rotation,Crystal):
if self.family != other.family:
raise NotImplementedError('disorientation between different crystal families')
blend = util.shapeblender(self.shape,other.shape)
s = self.equivalent
blend: Tuple[int, ...] = util.shapeblender(self.shape,other.shape)
s = self.equivalent
o = other.equivalent
s_ = s.reshape((s.shape[0],1)+ self.shape).broadcast_to((s.shape[0],o.shape[0])+blend,mode='right')
@ -534,10 +552,9 @@ class Orientation(Rotation,Crystal):
r = np.where(np.any(forward[...,np.newaxis],axis=(0,1),keepdims=True),
r_.quaternion,
_r.quaternion)
loc = np.where(ok)
sort = 0 if len(loc) == 2 else np.lexsort(loc[:1:-1])
quat = r[ok][sort].reshape(blend+(4,))
loc: Tuple[float] = np.where(ok)
sort: np.ndarray = 0 if len(loc) == 2 else np.lexsort(loc[:1:-1])
quat: np.ndarray = r[ok][sort].reshape(blend+(4,))
return (
(self.copy(rotation=quat),
(np.vstack(loc[:2]).T)[sort].reshape(blend+(2,)))
@ -546,7 +563,7 @@ class Orientation(Rotation,Crystal):
)
def average(self,weights=None,return_cloud=False):
def average(self, weights = None, return_cloud = False):
"""
Return orientation average over last dimension.
@ -587,7 +604,10 @@ class Orientation(Rotation,Crystal):
)
def to_SST(self,vector,proper=False,return_operators=False):
def to_SST(self,
vector: np.ndarray,
proper: bool = False,
return_operators: bool = False) -> np.ndarray:
"""
Rotate vector to ensure it falls into (improper or proper) standard stereographic triangle of crystal symmetry.
@ -626,7 +646,7 @@ class Orientation(Rotation,Crystal):
)
def in_SST(self,vector,proper=False):
def in_SST(self, vector: np.ndarray, proper: bool = False) -> Union[np.bool_, np.ndarray]:
"""
Check whether given crystal frame vector falls into standard stereographic triangle of own symmetry.
@ -667,7 +687,7 @@ class Orientation(Rotation,Crystal):
return np.all(components >= 0.0,axis=-1)
def IPF_color(self,vector,in_SST=True,proper=False):
def IPF_color(self, vector: np.ndarray, in_SST: bool = True, proper: bool = False) -> np.ndarray:
"""
Map vector to RGB color within standard stereographic triangle of own symmetry.
@ -715,30 +735,30 @@ class Orientation(Rotation,Crystal):
components_improper = np.around(np.einsum('...ji,...i',
np.broadcast_to(self.standard_triangle['improper'], vector_.shape+(3,)),
vector_), 12)
in_SST = np.all(components_proper >= 0.0,axis=-1) \
in_SST_ = np.all(components_proper >= 0.0,axis=-1) \
| np.all(components_improper >= 0.0,axis=-1)
components = np.where((in_SST & np.all(components_proper >= 0.0,axis=-1))[...,np.newaxis],
components = np.where((in_SST_ & np.all(components_proper >= 0.0,axis=-1))[...,np.newaxis],
components_proper,components_improper)
else:
components = np.around(np.einsum('...ji,...i',
np.broadcast_to(self .standard_triangle['improper'], vector_.shape+(3,)),
np.block([vector_[...,:2],np.abs(vector_[...,2:3])])), 12)
in_SST = np.all(components >= 0.0,axis=-1)
in_SST_ = np.all(components >= 0.0,axis=-1)
with np.errstate(invalid='ignore',divide='ignore'):
rgb = (components/np.linalg.norm(components,axis=-1,keepdims=True))**0.5 # smoothen color ramps
rgb = np.clip(rgb,0.,1.) # clip intensity
rgb /= np.max(rgb,axis=-1,keepdims=True) # normalize to (HS)V = 1
rgb[np.broadcast_to(~in_SST[...,np.newaxis],rgb.shape)] = 0.0
rgb[np.broadcast_to(~in_SST_[...,np.newaxis],rgb.shape)] = 0.0
return rgb
@property
def symmetry_operations(self):
def symmetry_operations(self) -> Rotation:
"""Symmetry operations as Rotations."""
_symmetry_operations = {
_symmetry_operations: Dict[str, List[List]] = {
'cubic': [
[ 1.0, 0.0, 0.0, 0.0 ],
[ 0.0, 1.0, 0.0, 0.0 ],
@ -808,7 +828,10 @@ class Orientation(Rotation,Crystal):
####################################################################################################
# functions that require lattice, not just family
def to_pole(self,*,uvw=None,hkl=None,with_symmetry=False):
def to_pole(self, *,
uvw: np.ndarray = None,
hkl: np.ndarray = None,
with_symmetry: bool = False) -> np.ndarray:
"""
Calculate lab frame vector along lattice direction [uvw] or plane normal (hkl).
@ -839,7 +862,9 @@ class Orientation(Rotation,Crystal):
@ np.broadcast_to(v,blend+(3,))
def Schmid(self,*,N_slip=None,N_twin=None):
def Schmid(self, *,
N_slip: Sequence[int] = None,
N_twin: Sequence[int] = None) -> np.ndarray:
u"""
Calculate Schmid matrix P = d n in the lab frame for selected deformation systems.
@ -876,6 +901,7 @@ class Orientation(Rotation,Crystal):
(self.kinematics('twin'),N_twin)
if active == '*': active = [len(a) for a in kinematics['direction']]
assert active
d = self.to_frame(uvw=np.vstack([kinematics['direction'][i][:n] for i,n in enumerate(active)]))
p = self.to_frame(hkl=np.vstack([kinematics['plane'][i][:n] for i,n in enumerate(active)]))
P = np.einsum('...i,...j',d/np.linalg.norm(d,axis=1,keepdims=True),
@ -886,7 +912,7 @@ class Orientation(Rotation,Crystal):
@ np.broadcast_to(P.reshape(util.shapeshifter(P.shape,shape)),shape)
def related(self,model):
def related(self, model: str) -> "Orientation":
"""
Orientations derived from the given relationship.

View File

@ -6,6 +6,8 @@ from . import tensor
from . import util
from . import grid_filters
from typing import Union, Sequence, Tuple, Literal, List
_P = -1
# parameters for conversion from/to cubochoric
@ -61,7 +63,7 @@ class Rotation:
__slots__ = ['quaternion']
def __init__(self,rotation = np.array([1.0,0.0,0.0,0.0])):
def __init__(self, rotation: Union[Sequence[float], np.ndarray, "Rotation"] = np.array([1.0,0.0,0.0,0.0])):
"""
New rotation.
@ -73,6 +75,7 @@ class Rotation:
Defaults to no rotation.
"""
self.quaternion: np.ndarray
if isinstance(rotation,Rotation):
self.quaternion = rotation.quaternion.copy()
elif np.array(rotation).shape[-1] == 4:
@ -81,13 +84,13 @@ class Rotation:
raise TypeError('"rotation" is neither a Rotation nor a quaternion')
def __repr__(self):
def __repr__(self) -> str:
"""Represent rotation as unit quaternion(s)."""
return f'Quaternion{" " if self.quaternion.shape == (4,) else "s of shape "+str(self.quaternion.shape[:-1])+chr(10)}'\
+ str(self.quaternion)
def __copy__(self,rotation=None):
def __copy__(self, rotation: Union[Sequence[float], np.ndarray, "Rotation"] = None) -> "Rotation":
"""Create deep copy."""
dup = copy.deepcopy(self)
if rotation is not None:
@ -97,14 +100,14 @@ class Rotation:
copy = __copy__
def __getitem__(self,item):
def __getitem__(self, item: Union[Tuple[int], int, bool, np.bool_, np.ndarray]):
"""Return slice according to item."""
return self.copy() \
if self.shape == () else \
self.copy(rotation=self.quaternion[item+(slice(None),)] if isinstance(item,tuple) else self.quaternion[item])
def __eq__(self,other):
def __eq__(self, other: object) -> bool:
"""
Equal to other.
@ -114,11 +117,13 @@ class Rotation:
Rotation to check for equality.
"""
if not isinstance(other, Rotation):
raise TypeError
return np.logical_or(np.all(self.quaternion == other.quaternion,axis=-1),
np.all(self.quaternion == -1.0*other.quaternion,axis=-1))
def __ne__(self,other):
def __ne__(self, other: object) -> bool:
"""
Not equal to other.
@ -128,10 +133,12 @@ class Rotation:
Rotation to check for inequality.
"""
if not isinstance(other, Rotation):
raise TypeError
return np.logical_not(self==other)
def isclose(self,other,rtol=1e-5,atol=1e-8,equal_nan=True):
def isclose(self, other: "Rotation", rtol: float = 1e-5, atol: float = 1e-8, equal_nan: bool = True) -> bool:
"""
Report where values are approximately equal to corresponding ones of other Rotation.
@ -158,7 +165,7 @@ class Rotation:
np.all(np.isclose(s,-1.0*o,rtol,atol,equal_nan),axis=-1))
def allclose(self,other,rtol=1e-5,atol=1e-8,equal_nan=True):
def allclose(self, other: "Rotation", rtol: float = 1e-5, atol: float = 1e-8, equal_nan: bool = True) -> Union[np.bool_, bool]:
"""
Test whether all values are approximately equal to corresponding ones of other Rotation.
@ -188,27 +195,27 @@ class Rotation:
@property
def size(self):
def size(self) -> int:
return self.quaternion[...,0].size
@property
def shape(self):
def shape(self) -> Tuple[int, ...]:
return self.quaternion[...,0].shape
def __len__(self):
def __len__(self) -> int:
"""Length of leading/leftmost dimension of array."""
return 0 if self.shape == () else self.shape[0]
def __invert__(self):
def __invert__(self) -> "Rotation":
"""Inverse rotation (backward rotation)."""
dup = self.copy()
dup.quaternion[...,1:] *= -1
return dup
def __pow__(self,exp):
def __pow__(self, exp: int) -> "Rotation":
"""
Perform the rotation 'exp' times.
@ -222,7 +229,7 @@ class Rotation:
p = self.quaternion[...,1:]/np.linalg.norm(self.quaternion[...,1:],axis=-1,keepdims=True)
return self.copy(rotation=Rotation(np.block([np.cos(exp*phi),np.sin(exp*phi)*p]))._standardize())
def __ipow__(self,exp):
def __ipow__(self, exp: int) -> "Rotation":
"""
Perform the rotation 'exp' times (in-place).
@ -235,7 +242,7 @@ class Rotation:
return self**exp
def __mul__(self,other):
def __mul__(self, other: "Rotation") -> "Rotation":
"""
Compose with other.
@ -261,7 +268,7 @@ class Rotation:
else:
raise TypeError('Use "R@b", i.e. matmul, to apply rotation "R" to object "b"')
def __imul__(self,other):
def __imul__(self, other: "Rotation") -> "Rotation":
"""
Compose with other (in-place).
@ -274,7 +281,7 @@ class Rotation:
return self*other
def __truediv__(self,other):
def __truediv__(self, other: "Rotation") -> "Rotation":
"""
Compose with inverse of other.
@ -294,7 +301,7 @@ class Rotation:
else:
raise TypeError('Use "R@b", i.e. matmul, to apply rotation "R" to object "b"')
def __itruediv__(self,other):
def __itruediv__(self, other: "Rotation") -> "Rotation":
"""
Compose with inverse of other (in-place).
@ -307,7 +314,7 @@ class Rotation:
return self/other
def __matmul__(self,other):
def __matmul__(self, other: np.ndarray) -> np.ndarray:
"""
Rotate vector, second order tensor, or fourth order tensor.
@ -350,13 +357,13 @@ class Rotation:
apply = __matmul__
def _standardize(self):
def _standardize(self) -> "Rotation":
"""Standardize quaternion (ensure positive real hemisphere)."""
self.quaternion[self.quaternion[...,0] < 0.0] *= -1
return self
def append(self,other):
def append(self, other: Union["Rotation", List["Rotation"]]) -> "Rotation":
"""
Extend array along first dimension with other array(s).
@ -369,7 +376,7 @@ class Rotation:
[self]+other if isinstance(other,list) else [self,other]))))
def flatten(self,order = 'C'):
def flatten(self, order: Literal['C','F','A'] = 'C') -> "Rotation":
"""
Flatten array.
@ -382,7 +389,7 @@ class Rotation:
return self.copy(rotation=self.quaternion.reshape((-1,4),order=order))
def reshape(self,shape,order = 'C'):
def reshape(self,shape: Union[int, Tuple[int, ...]], order: Literal['C','F','A'] = 'C') -> "Rotation":
"""
Reshape array.
@ -396,7 +403,7 @@ class Rotation:
return self.copy(rotation=self.quaternion.reshape(tuple(shape)+(4,),order=order))
def broadcast_to(self,shape,mode = 'right'):
def broadcast_to(self, shape: Union[int, Tuple[int, ...]], mode: Literal["left", "right"] = 'right') -> "Rotation":
"""
Broadcast array.
@ -458,7 +465,7 @@ class Rotation:
accept_homomorph = True)
def misorientation(self,other):
def misorientation(self, other: "Rotation") -> "Rotation":
"""
Calculate misorientation to other Rotation.
@ -479,7 +486,7 @@ class Rotation:
################################################################################################
# convert to different orientation representations (numpy arrays)
def as_quaternion(self):
def as_quaternion(self) -> np.ndarray:
"""
Represent as unit quaternion.
@ -492,7 +499,7 @@ class Rotation:
return self.quaternion.copy()
def as_Euler_angles(self,
degrees = False):
degrees: bool = False) -> np.ndarray:
"""
Represent as Bunge Euler angles.
@ -526,8 +533,8 @@ class Rotation:
return eu
def as_axis_angle(self,
degrees = False,
pair = False):
degrees: bool = False,
pair: bool = False) -> Union[Tuple[float, ...], np.ndarray]:
"""
Represent as axisangle pair.
@ -554,11 +561,11 @@ class Rotation:
(array([0., 0., 1.]), array(0.))
"""
ax = Rotation._qu2ax(self.quaternion)
ax: np.ndarray = Rotation._qu2ax(self.quaternion)
if degrees: ax[...,3] = np.degrees(ax[...,3])
return (ax[...,:3],ax[...,3]) if pair else ax
def as_matrix(self):
def as_matrix(self) -> np.ndarray:
"""
Represent as rotation matrix.
@ -582,7 +589,7 @@ class Rotation:
return Rotation._qu2om(self.quaternion)
def as_Rodrigues_vector(self,
compact = False):
compact: bool = False) -> np.ndarray:
"""
Represent as RodriguesFrank vector with separate axis and angle argument.
@ -615,7 +622,7 @@ class Rotation:
else:
return ro
def as_homochoric(self):
def as_homochoric(self) -> np.ndarray:
"""
Represent as homochoric vector.
@ -636,7 +643,7 @@ class Rotation:
"""
return Rotation._qu2ho(self.quaternion)
def as_cubochoric(self):
def as_cubochoric(self) -> np.ndarray:
"""
Represent as cubochoric vector.
@ -661,9 +668,9 @@ class Rotation:
# Static constructors. The input data needs to follow the conventions, options allow to
# relax the conventions.
@staticmethod
def from_quaternion(q,
accept_homomorph = False,
P = -1):
def from_quaternion(q: Union[Sequence[Sequence[float]], np.ndarray],
accept_homomorph: bool = False,
P: Literal[1, -1] = -1) -> "Rotation":
"""
Initialize from quaternion.
@ -678,7 +685,7 @@ class Rotation:
Sign convention. Defaults to -1.
"""
qu = np.array(q,dtype=float)
qu: np.ndarray = np.array(q,dtype=float)
if qu.shape[:-2:-1] != (4,):
raise ValueError('Invalid shape.')
if abs(P) != 1:
@ -696,8 +703,8 @@ class Rotation:
return Rotation(qu)
@staticmethod
def from_Euler_angles(phi,
degrees = False):
def from_Euler_angles(phi: np.ndarray,
degrees: bool = False) -> "Rotation":
"""
Initialize from Bunge Euler angles.
@ -725,10 +732,10 @@ class Rotation:
return Rotation(Rotation._eu2qu(eu))
@staticmethod
def from_axis_angle(axis_angle,
degrees = False,
normalize = False,
P = -1):
def from_axis_angle(axis_angle: np.ndarray,
degrees:bool = False,
normalize: bool = False,
P: Literal[1, -1] = -1) -> "Rotation":
"""
Initialize from Axis angle pair.
@ -763,9 +770,9 @@ class Rotation:
return Rotation(Rotation._ax2qu(ax))
@staticmethod
def from_basis(basis,
orthonormal = True,
reciprocal = False):
def from_basis(basis: np.ndarray,
orthonormal: bool = True,
reciprocal: bool = False) -> "Rotation":
"""
Initialize from lattice basis vectors.
@ -799,7 +806,7 @@ class Rotation:
return Rotation(Rotation._om2qu(om))
@staticmethod
def from_matrix(R):
def from_matrix(R: np.ndarray) -> "Rotation":
"""
Initialize from rotation matrix.
@ -812,8 +819,9 @@ class Rotation:
return Rotation.from_basis(R)
@staticmethod
def from_parallel(a,b,
**kwargs):
def from_parallel(a: np.ndarray,
b: np.ndarray,
**kwargs) -> "Rotation":
"""
Initialize from pairs of two orthogonal lattice basis vectors.
@ -841,9 +849,9 @@ class Rotation:
@staticmethod
def from_Rodrigues_vector(rho,
normalize = False,
P = -1):
def from_Rodrigues_vector(rho: np.ndarray,
normalize: bool = False,
P: Literal[1, -1] = -1) -> "Rotation":
"""
Initialize from RodriguesFrank vector (angle separated from axis).
@ -873,8 +881,8 @@ class Rotation:
return Rotation(Rotation._ro2qu(ro))
@staticmethod
def from_homochoric(h,
P = -1):
def from_homochoric(h: np.ndarray,
P: Literal[1, -1] = -1) -> "Rotation":
"""
Initialize from homochoric vector.
@ -900,8 +908,8 @@ class Rotation:
return Rotation(Rotation._ho2qu(ho))
@staticmethod
def from_cubochoric(x,
P = -1):
def from_cubochoric(x: np.ndarray,
P: Literal[1, -1] = -1) -> "Rotation":
"""
Initialize from cubochoric vector.
@ -928,8 +936,8 @@ class Rotation:
@staticmethod
def from_random(shape = None,
rng_seed = None):
def from_random(shape: Tuple[int, ...] = None,
rng_seed: Union[None, int, Sequence[int], np.ndarray] = None) -> "Rotation":
"""
Initialize with random rotation.
@ -958,13 +966,13 @@ class Rotation:
@staticmethod
def from_ODF(weights,
phi,
N = 500,
degrees = True,
fractions = True,
rng_seed = None,
**kwargs):
def from_ODF(weights: np.ndarray,
phi: np.ndarray,
N: int = 500,
degrees: bool = True,
fractions: bool = True,
rng_seed: Union[None, int, Sequence[int], np.ndarray] = None,
**kwargs) -> "Rotation":
"""
Sample discrete values from a binned orientation distribution function (ODF).
@ -1017,11 +1025,11 @@ class Rotation:
@staticmethod
def from_spherical_component(center,
sigma,
N = 500,
degrees = True,
rng_seed = None):
def from_spherical_component(center: "Rotation",
sigma: float,
N: int = 500,
degrees: bool = True,
rng_seed: Union[None, int, Sequence[float], np.ndarray] = None) -> "Rotation":
"""
Calculate set of rotations with Gaussian distribution around center.
@ -1052,12 +1060,12 @@ class Rotation:
@staticmethod
def from_fiber_component(alpha,
beta,
sigma = 0.0,
N = 500,
degrees = True,
rng_seed = None):
def from_fiber_component(alpha: np.ndarray,
beta: np.ndarray,
sigma: float = 0.0,
N: int = 500,
degrees: bool = True,
rng_seed: bool = None):
"""
Calculate set of rotations with Gaussian distribution around direction.
@ -1080,7 +1088,8 @@ class Rotation:
"""
rng = np.random.default_rng(rng_seed)
sigma_,alpha_,beta_ = map(np.radians,(sigma,alpha,beta)) if degrees else (sigma,alpha,beta)
sigma_: np.ndarray; alpha_: np.ndarray; beta_: np.ndarray
sigma_,alpha_,beta_ = map(np.radians,(sigma,alpha,beta)) if degrees else (sigma,alpha,beta) # type: ignore
d_cr = np.array([np.sin(alpha_[0])*np.cos(alpha_[1]), np.sin(alpha_[0])*np.sin(alpha_[1]), np.cos(alpha_[0])])
d_lab = np.array([np.sin( beta_[0])*np.cos( beta_[1]), np.sin( beta_[0])*np.sin( beta_[1]), np.cos( beta_[0])])
@ -1134,7 +1143,7 @@ class Rotation:
####################################################################################################
#---------- Quaternion ----------
@staticmethod
def _qu2om(qu):
def _qu2om(qu: np.ndarray) -> np.ndarray:
qq = qu[...,0:1]**2-(qu[...,1:2]**2 + qu[...,2:3]**2 + qu[...,3:4]**2)
om = np.block([qq + 2.0*qu[...,1:2]**2,
2.0*(qu[...,2:3]*qu[...,1:2]-_P*qu[...,0:1]*qu[...,3:4]),
@ -1149,7 +1158,7 @@ class Rotation:
return om
@staticmethod
def _qu2eu(qu):
def _qu2eu(qu: np.ndarray) -> np.ndarray:
"""Quaternion to Bunge Euler angles."""
q02 = qu[...,0:1]*qu[...,2:3]
q13 = qu[...,1:2]*qu[...,3:4]
@ -1177,7 +1186,7 @@ class Rotation:
return eu
@staticmethod
def _qu2ax(qu):
def _qu2ax(qu: np.ndarray) -> np.ndarray:
"""
Quaternion to axisangle pair.
@ -1193,7 +1202,7 @@ class Rotation:
return ax
@staticmethod
def _qu2ro(qu):
def _qu2ro(qu: np.ndarray) -> np.ndarray:
"""Quaternion to RodriguesFrank vector."""
with np.errstate(invalid='ignore',divide='ignore'):
s = np.linalg.norm(qu[...,1:4],axis=-1,keepdims=True)
@ -1207,7 +1216,7 @@ class Rotation:
return ro
@staticmethod
def _qu2ho(qu):
def _qu2ho(qu: np.ndarray) -> np.ndarray:
"""Quaternion to homochoric vector."""
with np.errstate(invalid='ignore'):
omega = 2.0 * np.arccos(np.clip(qu[...,0:1],-1.0,1.0))
@ -1218,14 +1227,14 @@ class Rotation:
return ho
@staticmethod
def _qu2cu(qu):
def _qu2cu(qu: np.ndarray) -> np.ndarray:
"""Quaternion to cubochoric vector."""
return Rotation._ho2cu(Rotation._qu2ho(qu))
#---------- Rotation matrix ----------
@staticmethod
def _om2qu(om):
def _om2qu(om: np.ndarray) -> np.ndarray:
"""
Rotation matrix to quaternion.
@ -1267,7 +1276,7 @@ class Rotation:
return qu
@staticmethod
def _om2eu(om):
def _om2eu(om: np.ndarray) -> np.ndarray:
"""Rotation matrix to Bunge Euler angles."""
with np.errstate(invalid='ignore',divide='ignore'):
zeta = 1.0/np.sqrt(1.0-om[...,2,2:3]**2)
@ -1286,7 +1295,7 @@ class Rotation:
return eu
@staticmethod
def _om2ax(om):
def _om2ax(om: np.ndarray) -> np.ndarray:
"""Rotation matrix to axisangle pair."""
diag_delta = -_P*np.block([om[...,1,2:3]-om[...,2,1:2],
om[...,2,0:1]-om[...,0,2:3],
@ -1307,24 +1316,24 @@ class Rotation:
return ax
@staticmethod
def _om2ro(om):
def _om2ro(om: np.ndarray) -> np.ndarray:
"""Rotation matrix to RodriguesFrank vector."""
return Rotation._eu2ro(Rotation._om2eu(om))
@staticmethod
def _om2ho(om):
def _om2ho(om: np.ndarray) -> np.ndarray:
"""Rotation matrix to homochoric vector."""
return Rotation._ax2ho(Rotation._om2ax(om))
@staticmethod
def _om2cu(om):
def _om2cu(om: np.ndarray) -> np.ndarray:
"""Rotation matrix to cubochoric vector."""
return Rotation._ho2cu(Rotation._om2ho(om))
#---------- Bunge Euler angles ----------
@staticmethod
def _eu2qu(eu):
def _eu2qu(eu: np.ndarray) -> np.ndarray:
"""Bunge Euler angles to quaternion."""
ee = 0.5*eu
cPhi = np.cos(ee[...,1:2])
@ -1337,7 +1346,7 @@ class Rotation:
return qu
@staticmethod
def _eu2om(eu):
def _eu2om(eu: np.ndarray) -> np.ndarray:
"""Bunge Euler angles to rotation matrix."""
c = np.cos(eu)
s = np.sin(eu)
@ -1355,7 +1364,7 @@ class Rotation:
return om
@staticmethod
def _eu2ax(eu):
def _eu2ax(eu: np.ndarray) -> np.ndarray:
"""Bunge Euler angles to axisangle pair."""
t = np.tan(eu[...,1:2]*0.5)
sigma = 0.5*(eu[...,0:1]+eu[...,2:3])
@ -1374,7 +1383,7 @@ class Rotation:
return ax
@staticmethod
def _eu2ro(eu):
def _eu2ro(eu: np.ndarray) -> np.ndarray:
"""Bunge Euler angles to RodriguesFrank vector."""
ax = Rotation._eu2ax(eu)
ro = np.block([ax[...,:3],np.tan(ax[...,3:4]*.5)])
@ -1383,19 +1392,19 @@ class Rotation:
return ro
@staticmethod
def _eu2ho(eu):
def _eu2ho(eu: np.ndarray) -> np.ndarray:
"""Bunge Euler angles to homochoric vector."""
return Rotation._ax2ho(Rotation._eu2ax(eu))
@staticmethod
def _eu2cu(eu):
def _eu2cu(eu: np.ndarray) -> np.ndarray:
"""Bunge Euler angles to cubochoric vector."""
return Rotation._ho2cu(Rotation._eu2ho(eu))
#---------- Axis angle pair ----------
@staticmethod
def _ax2qu(ax):
def _ax2qu(ax: np.ndarray) -> np.ndarray:
"""Axisangle pair to quaternion."""
c = np.cos(ax[...,3:4]*.5)
s = np.sin(ax[...,3:4]*.5)
@ -1403,7 +1412,7 @@ class Rotation:
return qu
@staticmethod
def _ax2om(ax):
def _ax2om(ax: np.ndarray) -> np.ndarray:
"""Axis-angle pair to rotation matrix."""
c = np.cos(ax[...,3:4])
s = np.sin(ax[...,3:4])
@ -1420,12 +1429,12 @@ class Rotation:
return om if _P < 0.0 else np.swapaxes(om,-1,-2)
@staticmethod
def _ax2eu(ax):
def _ax2eu(ax: np.ndarray) -> np.ndarray:
"""Rotation matrix to Bunge Euler angles."""
return Rotation._om2eu(Rotation._ax2om(ax))
@staticmethod
def _ax2ro(ax):
def _ax2ro(ax: np.ndarray) -> np.ndarray:
"""Axisangle pair to RodriguesFrank vector."""
ro = np.block([ax[...,:3],
np.where(np.isclose(ax[...,3:4],np.pi,atol=1.e-15,rtol=.0),
@ -1436,36 +1445,36 @@ class Rotation:
return ro
@staticmethod
def _ax2ho(ax):
def _ax2ho(ax: np.ndarray) -> np.ndarray:
"""Axisangle pair to homochoric vector."""
f = (0.75 * ( ax[...,3:4] - np.sin(ax[...,3:4]) ))**(1.0/3.0)
ho = ax[...,:3] * f
return ho
@staticmethod
def _ax2cu(ax):
def _ax2cu(ax: np.ndarray) -> np.ndarray:
"""Axisangle pair to cubochoric vector."""
return Rotation._ho2cu(Rotation._ax2ho(ax))
#---------- Rodrigues-Frank vector ----------
@staticmethod
def _ro2qu(ro):
def _ro2qu(ro: np.ndarray) -> np.ndarray:
"""RodriguesFrank vector to quaternion."""
return Rotation._ax2qu(Rotation._ro2ax(ro))
@staticmethod
def _ro2om(ro):
def _ro2om(ro: np.ndarray) -> np.ndarray:
"""RodgriguesFrank vector to rotation matrix."""
return Rotation._ax2om(Rotation._ro2ax(ro))
@staticmethod
def _ro2eu(ro):
def _ro2eu(ro: np.ndarray) -> np.ndarray:
"""RodriguesFrank vector to Bunge Euler angles."""
return Rotation._om2eu(Rotation._ro2om(ro))
@staticmethod
def _ro2ax(ro):
def _ro2ax(ro: np.ndarray) -> np.ndarray:
"""RodriguesFrank vector to axisangle pair."""
with np.errstate(invalid='ignore',divide='ignore'):
ax = np.where(np.isfinite(ro[...,3:4]),
@ -1475,7 +1484,7 @@ class Rotation:
return ax
@staticmethod
def _ro2ho(ro):
def _ro2ho(ro: np.ndarray) -> np.ndarray:
"""RodriguesFrank vector to homochoric vector."""
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-8,ro[...,0:3].shape),
@ -1483,29 +1492,29 @@ class Rotation:
return ho
@staticmethod
def _ro2cu(ro):
def _ro2cu(ro: np.ndarray) -> np.ndarray:
"""RodriguesFrank vector to cubochoric vector."""
return Rotation._ho2cu(Rotation._ro2ho(ro))
#---------- Homochoric vector----------
@staticmethod
def _ho2qu(ho):
def _ho2qu(ho: np.ndarray) -> np.ndarray:
"""Homochoric vector to quaternion."""
return Rotation._ax2qu(Rotation._ho2ax(ho))
@staticmethod
def _ho2om(ho):
def _ho2om(ho: np.ndarray) -> np.ndarray:
"""Homochoric vector to rotation matrix."""
return Rotation._ax2om(Rotation._ho2ax(ho))
@staticmethod
def _ho2eu(ho):
def _ho2eu(ho: np.ndarray) -> np.ndarray:
"""Homochoric vector to Bunge Euler angles."""
return Rotation._ax2eu(Rotation._ho2ax(ho))
@staticmethod
def _ho2ax(ho):
def _ho2ax(ho: np.ndarray) -> np.ndarray:
"""Homochoric vector to axisangle pair."""
tfit = np.array([+1.0000000000018852, -0.5000000002194847,
-0.024999992127593126, -0.003928701544781374,
@ -1528,12 +1537,12 @@ class Rotation:
return ax
@staticmethod
def _ho2ro(ho):
def _ho2ro(ho: np.ndarray) -> np.ndarray:
"""Axisangle pair to RodriguesFrank vector."""
return Rotation._ax2ro(Rotation._ho2ax(ho))
@staticmethod
def _ho2cu(ho):
def _ho2cu(ho: np.ndarray) -> np.ndarray:
"""
Homochoric vector to cubochoric vector.
@ -1573,32 +1582,32 @@ class Rotation:
#---------- Cubochoric ----------
@staticmethod
def _cu2qu(cu):
def _cu2qu(cu: np.ndarray) -> np.ndarray:
"""Cubochoric vector to quaternion."""
return Rotation._ho2qu(Rotation._cu2ho(cu))
@staticmethod
def _cu2om(cu):
def _cu2om(cu: np.ndarray) -> np.ndarray:
"""Cubochoric vector to rotation matrix."""
return Rotation._ho2om(Rotation._cu2ho(cu))
@staticmethod
def _cu2eu(cu):
def _cu2eu(cu: np.ndarray) -> np.ndarray:
"""Cubochoric vector to Bunge Euler angles."""
return Rotation._ho2eu(Rotation._cu2ho(cu))
@staticmethod
def _cu2ax(cu):
def _cu2ax(cu: np.ndarray) -> np.ndarray:
"""Cubochoric vector to axisangle pair."""
return Rotation._ho2ax(Rotation._cu2ho(cu))
@staticmethod
def _cu2ro(cu):
def _cu2ro(cu: np.ndarray) -> np.ndarray:
"""Cubochoric vector to RodriguesFrank vector."""
return Rotation._ho2ro(Rotation._cu2ho(cu))
@staticmethod
def _cu2ho(cu):
def _cu2ho(cu: np.ndarray) -> np.ndarray:
"""
Cubochoric vector to homochoric vector.
@ -1641,7 +1650,7 @@ class Rotation:
@staticmethod
def _get_pyramid_order(xyz,direction=None):
def _get_pyramid_order(xyz: np.ndarray, direction: Literal['forward', 'backward']) -> np.ndarray:
"""
Get order of the coordinates.