From cef885cfde9b3673f5241ad480558c38bb48273b Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Tue, 14 Dec 2021 17:05:00 +0100 Subject: [PATCH 01/30] added first typehints for rotation and orientation modules --- python/damask/_orientation.py | 136 ++++++++++-------- python/damask/_rotation.py | 251 ++++++++++++++++++---------------- 2 files changed, 211 insertions(+), 176 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index e727c54ae..6c0dd2fa3 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -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. diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index ac921d70a..0a4421090 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -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 axis–angle 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 Rodrigues–Frank 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 Rodrigues–Frank 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 axis–angle pair. @@ -1193,7 +1202,7 @@ class Rotation: return ax @staticmethod - def _qu2ro(qu): + def _qu2ro(qu: np.ndarray) -> np.ndarray: """Quaternion to Rodrigues–Frank 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 axis–angle 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 Rodrigues–Frank 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 axis–angle 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 Rodrigues–Frank 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: """Axis–angle 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: """Axis–angle pair to Rodrigues–Frank 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: """Axis–angle 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: """Axis–angle pair to cubochoric vector.""" return Rotation._ho2cu(Rotation._ax2ho(ax)) #---------- Rodrigues-Frank vector ---------- @staticmethod - def _ro2qu(ro): + def _ro2qu(ro: np.ndarray) -> np.ndarray: """Rodrigues–Frank vector to quaternion.""" return Rotation._ax2qu(Rotation._ro2ax(ro)) @staticmethod - def _ro2om(ro): + def _ro2om(ro: np.ndarray) -> np.ndarray: """Rodgrigues–Frank vector to rotation matrix.""" return Rotation._ax2om(Rotation._ro2ax(ro)) @staticmethod - def _ro2eu(ro): + def _ro2eu(ro: np.ndarray) -> np.ndarray: """Rodrigues–Frank vector to Bunge Euler angles.""" return Rotation._om2eu(Rotation._ro2om(ro)) @staticmethod - def _ro2ax(ro): + def _ro2ax(ro: np.ndarray) -> np.ndarray: """Rodrigues–Frank vector to axis–angle 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: """Rodrigues–Frank 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: """Rodrigues–Frank 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 axis–angle 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: """Axis–angle pair to Rodrigues–Frank 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 axis–angle pair.""" return Rotation._ho2ax(Rotation._cu2ho(cu)) @staticmethod - def _cu2ro(cu): + def _cu2ro(cu: np.ndarray) -> np.ndarray: """Cubochoric vector to Rodrigues–Frank 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. From 65633ee6b1f807d5d6b298ed188023eefea88590 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Tue, 14 Dec 2021 17:19:00 +0100 Subject: [PATCH 02/30] added typehints for rotation.average function [skip ci] --- python/damask/_rotation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 0a4421090..bf210a852 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -426,7 +426,7 @@ class Rotation: shape+(4,))) - def average(self,weights = None): + def average(self, weights: np.ndarray = None) -> "Rotation": """ Average along last array dimension. From 5702614c4fdc8125dafe064407a774d2281d24ab Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Mon, 3 Jan 2022 15:35:28 +0100 Subject: [PATCH 03/30] expanded rng_seed typehint to array_like type adjusted alpha and beta types for from_fiber_component to also accept lists removed superfluous kwargs argument in from_ODF and from_parallel functions --- python/damask/_rotation.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index bf210a852..aeac3c3e3 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -820,8 +820,7 @@ class Rotation: @staticmethod def from_parallel(a: np.ndarray, - b: np.ndarray, - **kwargs) -> "Rotation": + b: np.ndarray ) -> "Rotation": """ Initialize from pairs of two orthogonal lattice basis vectors. @@ -971,8 +970,7 @@ class Rotation: N: int = 500, degrees: bool = True, fractions: bool = True, - rng_seed: Union[None, int, Sequence[int], np.ndarray] = None, - **kwargs) -> "Rotation": + rng_seed: Union[None, int, Sequence[int], np.ndarray] = None) -> "Rotation": """ Sample discrete values from a binned orientation distribution function (ODF). @@ -1029,7 +1027,7 @@ class Rotation: sigma: float, N: int = 500, degrees: bool = True, - rng_seed: Union[None, int, Sequence[float], np.ndarray] = None) -> "Rotation": + rng_seed: Union[None, int, Sequence[int], np.ndarray] = None) -> "Rotation": """ Calculate set of rotations with Gaussian distribution around center. @@ -1060,12 +1058,12 @@ class Rotation: @staticmethod - def from_fiber_component(alpha: np.ndarray, - beta: np.ndarray, + def from_fiber_component(alpha: Union[Sequence[int], np.ndarray], + beta: Union[Sequence[int], np.ndarray], sigma: float = 0.0, N: int = 500, degrees: bool = True, - rng_seed: bool = None): + rng_seed: Union[None, int, Sequence[int], np.ndarray] = None): """ Calculate set of rotations with Gaussian distribution around direction. From 9a9ec11c29e681cd94f264e7e2a59c73f8bfe5d5 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Mon, 3 Jan 2022 16:44:27 +0100 Subject: [PATCH 04/30] added generator typehint to from_random function --- python/damask/_rotation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index aeac3c3e3..448917488 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -951,7 +951,7 @@ class Rotation: Defaults to None, i.e. unpredictable entropy will be pulled from the OS. """ - rng = np.random.default_rng(rng_seed) + rng: np.random.Generator = np.random.default_rng(rng_seed) r = rng.random(3 if shape is None else tuple(shape)+(3,) if hasattr(shape, '__iter__') else (shape,3)) A = np.sqrt(r[...,2]) From 0a31ff098050b08f4eb854a228913f1e2c5d0ba5 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Thu, 6 Jan 2022 14:33:41 +0100 Subject: [PATCH 05/30] added type:ignore statement to broken line --- python/damask/_rotation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 448917488..d50b25c54 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -952,7 +952,7 @@ class Rotation: """ rng: np.random.Generator = np.random.default_rng(rng_seed) - r = rng.random(3 if shape is None else tuple(shape)+(3,) if hasattr(shape, '__iter__') else (shape,3)) + r = rng.random(3 if shape is None else tuple(shape)+(3,) if hasattr(shape, '__iter__') else (shape,3)) #type: ignore A = np.sqrt(r[...,2]) B = np.sqrt(1.0-r[...,2]) From aabeee9de1aa38ccfe85c3c86c2df778a27f9e2c Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Fri, 14 Jan 2022 14:37:48 +0100 Subject: [PATCH 06/30] Replaced relevant Sequences with FloatSequence and IntSequence types --- python/damask/_orientation.py | 16 +++++++++------- python/damask/_rotation.py | 19 ++++++++++--------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index afe4e605d..61c63ed66 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -1,6 +1,5 @@ import inspect import copy -from typing import Union, Callable, Sequence, Dict, Any, Tuple, List import numpy as np @@ -9,6 +8,9 @@ from . import Crystal from . import util from . import tensor +from typing import Union, Callable, Sequence, Dict, Any, Tuple, List +from ._typehints import FloatSequence, IntSequence + _parameter_doc = \ """ @@ -94,7 +96,7 @@ class Orientation(Rotation,Crystal): @util.extend_docstring(_parameter_doc) def __init__(self, - rotation: Union[Sequence[float], np.ndarray, Rotation] = np.array([1.,0.,0.,0.]), *, + rotation: Union[FloatSequence, Rotation] = np.array([1.,0.,0.,0.]), *, family: str = None, lattice: str = None, a: float = None, b: float = None, c: float = None, @@ -121,7 +123,7 @@ class Orientation(Rotation,Crystal): return '\n'.join([Crystal.__repr__(self), Rotation.__repr__(self)]) - def __copy__(self,rotation: Union[Sequence[float], np.ndarray, Rotation] = None) -> "Orientation": + def __copy__(self,rotation: Union[FloatSequence, Rotation] = None) -> "Orientation": """Create deep copy.""" dup = copy.deepcopy(self) if rotation is not None: @@ -360,8 +362,8 @@ class Orientation(Rotation,Crystal): @classmethod @util.extend_docstring(_parameter_doc) def from_directions(cls, - uvw: Union[Sequence[float], np.ndarray], - hkl: Union[Sequence[float], np.ndarray], + uvw: FloatSequence, + hkl: FloatSequence, **kwargs) -> "Orientation": """ Initialize orientation object from two crystallographic directions. @@ -874,8 +876,8 @@ class Orientation(Rotation,Crystal): def Schmid(self, *, - N_slip: Sequence[int] = None, - N_twin: Sequence[int] = None) -> np.ndarray: + N_slip: IntSequence = None, + N_twin: IntSequence = None) -> np.ndarray: u""" Calculate Schmid matrix P = d ⨂ n in the lab frame for selected deformation systems. diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index a0bf15e88..fa4f2ec14 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -7,6 +7,7 @@ from . import util from . import grid_filters from typing import Union, Sequence, Tuple, Literal, List +from ._typehints import FloatSequence, IntSequence _P = -1 @@ -63,7 +64,7 @@ class Rotation: __slots__ = ['quaternion'] - def __init__(self, rotation: Union[Sequence[float], np.ndarray, "Rotation"] = np.array([1.0,0.0,0.0,0.0])): + def __init__(self, rotation: Union[FloatSequence, "Rotation"] = np.array([1.0,0.0,0.0,0.0])): """ New rotation. @@ -90,7 +91,7 @@ class Rotation: + str(self.quaternion) - def __copy__(self, rotation: Union[Sequence[float], np.ndarray, "Rotation"] = None) -> "Rotation": + def __copy__(self, rotation: Union[FloatSequence, "Rotation"] = None) -> "Rotation": """Create deep copy.""" dup = copy.deepcopy(self) if rotation is not None: @@ -668,7 +669,7 @@ class Rotation: # Static constructors. The input data needs to follow the conventions, options allow to # relax the conventions. @staticmethod - def from_quaternion(q: Union[Sequence[Sequence[float]], np.ndarray], + def from_quaternion(q: Union[Sequence[FloatSequence], np.ndarray], accept_homomorph: bool = False, P: Literal[1, -1] = -1) -> "Rotation": """ @@ -936,7 +937,7 @@ class Rotation: @staticmethod def from_random(shape: Tuple[int, ...] = None, - rng_seed: Union[None, int, Sequence[int], np.ndarray] = None) -> "Rotation": + rng_seed: Union[int, IntSequence] = None) -> "Rotation": """ Initialize with random rotation. @@ -970,7 +971,7 @@ class Rotation: N: int = 500, degrees: bool = True, fractions: bool = True, - rng_seed: Union[None, int, Sequence[int], np.ndarray] = None) -> "Rotation": + rng_seed: Union[None, int, IntSequence] = None) -> "Rotation": """ Sample discrete values from a binned orientation distribution function (ODF). @@ -1027,7 +1028,7 @@ class Rotation: sigma: float, N: int = 500, degrees: bool = True, - rng_seed: Union[None, int, Sequence[int], np.ndarray] = None) -> "Rotation": + rng_seed: Union[None, int, IntSequence] = None) -> "Rotation": """ Calculate set of rotations with Gaussian distribution around center. @@ -1058,12 +1059,12 @@ class Rotation: @staticmethod - def from_fiber_component(alpha: Union[Sequence[int], np.ndarray], - beta: Union[Sequence[int], np.ndarray], + def from_fiber_component(alpha: IntSequence, + beta: IntSequence, sigma: float = 0.0, N: int = 500, degrees: bool = True, - rng_seed: Union[None, int, Sequence[int], np.ndarray] = None): + rng_seed: Union[int, IntSequence] = None): """ Calculate set of rotations with Gaussian distribution around direction. From 25513d572bacc2d65073e0199a917f6769ccf99a Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Fri, 14 Jan 2022 14:55:08 +0100 Subject: [PATCH 07/30] minor type adjustment to rotation module removed superfluous Sequence type from orientation module --- python/damask/_orientation.py | 2 +- python/damask/_rotation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 61c63ed66..3a1154a64 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -8,7 +8,7 @@ from . import Crystal from . import util from . import tensor -from typing import Union, Callable, Sequence, Dict, Any, Tuple, List +from typing import Union, Callable, Dict, Any, Tuple, List from ._typehints import FloatSequence, IntSequence diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index fa4f2ec14..17369c36a 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -535,7 +535,7 @@ class Rotation: def as_axis_angle(self, degrees: bool = False, - pair: bool = False) -> Union[Tuple[float, ...], np.ndarray]: + pair: bool = False) -> Union[Tuple[np.ndarray, np.ndarray], np.ndarray]: """ Represent as axis–angle pair. From 0fe51f58a8686e6ab56d4297eda5a7e64971f229 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Wed, 26 Jan 2022 15:09:09 +0100 Subject: [PATCH 08/30] [skip ci] changed almost all function definition lines to multiline --- python/damask/_orientation.py | 35 ++++++++++++++++++------- python/damask/_rotation.py | 49 +++++++++++++++++++++++++---------- 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 3a1154a64..35de041f8 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -123,7 +123,8 @@ class Orientation(Rotation,Crystal): return '\n'.join([Crystal.__repr__(self), Rotation.__repr__(self)]) - def __copy__(self,rotation: Union[FloatSequence, Rotation] = None) -> "Orientation": + def __copy__(self, + rotation: Union[FloatSequence, Rotation] = None) -> "Orientation": """Create deep copy.""" dup = copy.deepcopy(self) if rotation is not None: @@ -134,7 +135,8 @@ class Orientation(Rotation,Crystal): - def __eq__(self, other: object) -> bool: + def __eq__(self, + other: object) -> bool: """ Equal to other. @@ -151,7 +153,8 @@ class Orientation(Rotation,Crystal): self.parameters == other.parameters return np.logical_and(matching_type,super(self.__class__,self.reduced).__eq__(other.reduced)) - def __ne__(self, other: object) -> bool: + def __ne__(self, + other: object) -> bool: """ Not equal to other. @@ -230,7 +233,8 @@ class Orientation(Rotation,Crystal): return np.all(self.isclose(other,rtol,atol,equal_nan)) - def __mul__(self, other: Union[Rotation, "Orientation"]) -> "Orientation": + def __mul__(self, + other: Union[Rotation, "Orientation"]) -> "Orientation": """ Compose this orientation with other. @@ -252,7 +256,8 @@ class Orientation(Rotation,Crystal): @staticmethod - def _split_kwargs(kwargs: Dict[str, Any], target: Callable) -> Tuple[Dict[str, Any], ...]: + 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. @@ -492,7 +497,9 @@ 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. @@ -576,7 +583,9 @@ 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. @@ -659,7 +668,9 @@ class Orientation(Rotation,Crystal): ) - def in_SST(self, vector: np.ndarray, proper: bool = False) -> Union[np.bool_, np.ndarray]: + 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. @@ -700,7 +711,10 @@ class Orientation(Rotation,Crystal): return np.all(components >= 0.0,axis=-1) - def IPF_color(self, vector: np.ndarray, in_SST: bool = True, proper: bool = False) -> np.ndarray: + 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. @@ -925,7 +939,8 @@ class Orientation(Rotation,Crystal): @ np.broadcast_to(P.reshape(util.shapeshifter(P.shape,shape)),shape) - def related(self, model: str) -> "Orientation": + def related(self, + model: str) -> "Orientation": """ Orientations derived from the given relationship. diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 17369c36a..3266f4f41 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -64,7 +64,8 @@ class Rotation: __slots__ = ['quaternion'] - def __init__(self, rotation: Union[FloatSequence, "Rotation"] = np.array([1.0,0.0,0.0,0.0])): + def __init__(self, + rotation: Union[FloatSequence, "Rotation"] = np.array([1.0,0.0,0.0,0.0])): """ New rotation. @@ -91,7 +92,8 @@ class Rotation: + str(self.quaternion) - def __copy__(self, rotation: Union[FloatSequence, "Rotation"] = None) -> "Rotation": + def __copy__(self, + rotation: Union[FloatSequence, "Rotation"] = None) -> "Rotation": """Create deep copy.""" dup = copy.deepcopy(self) if rotation is not None: @@ -101,7 +103,8 @@ class Rotation: copy = __copy__ - def __getitem__(self, item: Union[Tuple[int], int, bool, np.bool_, np.ndarray]): + 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 \ @@ -139,7 +142,11 @@ class Rotation: return np.logical_not(self==other) - def isclose(self, other: "Rotation", rtol: float = 1e-5, atol: float = 1e-8, equal_nan: bool = True) -> bool: + 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. @@ -166,7 +173,11 @@ class Rotation: np.all(np.isclose(s,-1.0*o,rtol,atol,equal_nan),axis=-1)) - def allclose(self, other: "Rotation", rtol: float = 1e-5, atol: float = 1e-8, equal_nan: bool = True) -> Union[np.bool_, bool]: + 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. @@ -330,7 +341,7 @@ class Rotation: Rotated vector or tensor, i.e. transformed to frame defined by rotation. """ - if isinstance(other,np.ndarray): + if isinstance(other, np.ndarray): if self.shape + (3,) == other.shape: q_m = self.quaternion[...,0] p_m = self.quaternion[...,1:] @@ -350,7 +361,7 @@ class Rotation: return np.einsum('...im,...jn,...ko,...lp,...mnop',R,R,R,R,other) else: raise ValueError('Can only rotate vectors, 2nd order tensors, and 4th order tensors') - elif isinstance(other,Rotation): + elif isinstance(other, Rotation): raise TypeError('Use "R1*R2", i.e. multiplication, to compose rotations "R1" and "R2"') else: raise TypeError(f'Cannot rotate {type(other)}') @@ -364,7 +375,9 @@ class Rotation: return self - def append(self, other: Union["Rotation", List["Rotation"]]) -> "Rotation": + def append(self, + other: Union["Rotation", + List["Rotation"]]) -> "Rotation": """ Extend array along first dimension with other array(s). @@ -377,7 +390,8 @@ class Rotation: [self]+other if isinstance(other,list) else [self,other])))) - def flatten(self, order: Literal['C','F','A'] = 'C') -> "Rotation": + def flatten(self, + order: Literal['C','F','A'] = 'C') -> "Rotation": """ Flatten array. @@ -390,7 +404,9 @@ class Rotation: return self.copy(rotation=self.quaternion.reshape((-1,4),order=order)) - def reshape(self,shape: Union[int, Tuple[int, ...]], order: Literal['C','F','A'] = 'C') -> "Rotation": + def reshape(self, + shape: Union[int, Tuple[int, ...]], + order: Literal['C','F','A'] = 'C') -> "Rotation": """ Reshape array. @@ -404,7 +420,9 @@ class Rotation: return self.copy(rotation=self.quaternion.reshape(tuple(shape)+(4,),order=order)) - def broadcast_to(self, shape: Union[int, Tuple[int, ...]], mode: Literal["left", "right"] = 'right') -> "Rotation": + def broadcast_to(self, + shape: Union[int, Tuple[int, ...]], + mode: Literal["left", "right"] = 'right') -> "Rotation": """ Broadcast array. @@ -427,7 +445,8 @@ class Rotation: shape+(4,))) - def average(self, weights: np.ndarray = None) -> "Rotation": + def average(self, + weights: np.ndarray = None) -> "Rotation": """ Average along last array dimension. @@ -466,7 +485,8 @@ class Rotation: accept_homomorph = True) - def misorientation(self, other: "Rotation") -> "Rotation": + def misorientation(self, + other: "Rotation") -> "Rotation": """ Calculate misorientation to other Rotation. @@ -1649,7 +1669,8 @@ class Rotation: @staticmethod - def _get_pyramid_order(xyz: np.ndarray, direction: Literal['forward', 'backward']) -> np.ndarray: + def _get_pyramid_order(xyz: np.ndarray, + direction: Literal['forward', 'backward']) -> np.ndarray: """ Get order of the coordinates. From 81ef865525f12c57c3f8ec3e92cf67ef1d55b56a Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Fri, 28 Jan 2022 10:11:34 +0100 Subject: [PATCH 09/30] [skip ci] moved asterisk to seperate line --- python/damask/_orientation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 35de041f8..f84ca9952 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -96,7 +96,8 @@ class Orientation(Rotation,Crystal): @util.extend_docstring(_parameter_doc) def __init__(self, - rotation: Union[FloatSequence, Rotation] = np.array([1.,0.,0.,0.]), *, + rotation: Union[FloatSequence, Rotation] = np.array([1.,0.,0.,0.]), + *, family: str = None, lattice: str = None, a: float = None, b: float = None, c: float = None, From 92ac01848614450d80a1d0d8e8b262cdb5e96c99 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Fri, 28 Jan 2022 13:34:42 +0100 Subject: [PATCH 10/30] removed superfluous break in function definition in rotation --- python/damask/_rotation.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 3266f4f41..676df5c03 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -376,8 +376,7 @@ class Rotation: def append(self, - other: Union["Rotation", - List["Rotation"]]) -> "Rotation": + other: Union["Rotation", List["Rotation"]]) -> "Rotation": """ Extend array along first dimension with other array(s). From 53a0de2271c2ea33618aa733b64e0118de4fc83c Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Fri, 28 Jan 2022 13:40:37 +0100 Subject: [PATCH 11/30] Adjusted docstrings in rotation rewrote map(np.radians()) line to one line tuple --- python/damask/_rotation.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 676df5c03..468fef2c5 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -382,7 +382,7 @@ class Rotation: Parameters ---------- - other : damask.Rotation + other : (list of) damask.Rotation """ return self.copy(rotation=np.vstack(tuple(map(lambda x:x.quaternion, @@ -427,7 +427,7 @@ class Rotation: Parameters ---------- - shape : tuple + shape : int, tuple Shape of broadcasted array. mode : str, optional Where to preferentially locate missing dimensions. @@ -451,7 +451,7 @@ class Rotation: Parameters ---------- - weights : list of floats, optional + weights : numpy.ndarray, optional Relative weight of each rotation. Returns @@ -1107,7 +1107,7 @@ class Rotation: """ rng = np.random.default_rng(rng_seed) 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 + sigma_,alpha_,beta_ = (np.radians(coordinate for coordinate in (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])]) From 33731e4948078e79f6710bdbc979730ecb3a7997 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Fri, 28 Jan 2022 15:15:20 +0100 Subject: [PATCH 12/30] adjusted bracket error in from_fiber_component --- python/damask/_rotation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 468fef2c5..b55f178f7 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -1107,7 +1107,7 @@ class Rotation: """ rng = np.random.default_rng(rng_seed) sigma_: np.ndarray; alpha_: np.ndarray; beta_: np.ndarray - sigma_,alpha_,beta_ = (np.radians(coordinate for coordinate in (sigma,alpha,beta))) if degrees else (sigma,alpha,beta) #type: ignore + sigma_,alpha_,beta_ = (np.radians(coordinate) for coordinate in (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])]) From cb1143a47215d8327085cd975e45135b42fd9e57 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Tue, 1 Feb 2022 18:38:49 +0100 Subject: [PATCH 13/30] changed return type of util.shapeblender and util.shapeshifter from Sequence[SupportsIndex] to Tuple[SupportsIndex, ...] ignored lines in orientation module that attempt to pass objects of type Tuple[SupportsIndex, ...] to np.broadcast_to() --- python/damask/_orientation.py | 5 ++--- python/damask/util.py | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index f84ca9952..d1488c14e 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -657,7 +657,7 @@ class Orientation(Rotation,Crystal): """ eq = self.equivalent blend = util.shapeblender(eq.shape,np.array(vector).shape[:-1]) - poles = eq.broadcast_to(blend,mode='right') @ np.broadcast_to(np.array(vector),blend+(3,)) + poles = eq.broadcast_to(blend,mode='right') @ np.broadcast_to(np.array(vector),blend+(3,)) #type: ignore ok = self.in_SST(poles,proper=proper) ok &= np.cumsum(ok,axis=0) == 1 loc = np.where(ok) @@ -886,8 +886,7 @@ class Orientation(Rotation,Crystal): blend += sym_ops.shape v = sym_ops.broadcast_to(shape) \ @ np.broadcast_to(v.reshape(util.shapeshifter(v.shape,shape+(3,))),shape+(3,)) - return ~(self.broadcast_to(blend)) \ - @ np.broadcast_to(v,blend+(3,)) + return ~(self.broadcast_to(blend))@ np.broadcast_to(v,blend+(3,)) #type: ignore def Schmid(self, *, diff --git a/python/damask/util.py b/python/damask/util.py index b4c287884..3b0af7177 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -9,7 +9,7 @@ import re import fractions from collections import abc from functools import reduce -from typing import Union, Tuple, Iterable, Callable, Dict, List, Any, Literal, SupportsIndex, Sequence +from typing import Union, Tuple, Iterable, Callable, Dict, List, Any, Literal, SupportsIndex from pathlib import Path import numpy as np @@ -427,7 +427,7 @@ def hybrid_IA(dist: np.ndarray, def shapeshifter(fro: Tuple[int, ...], to: Tuple[int, ...], mode: Literal['left','right'] = 'left', - keep_ones: bool = False) -> Sequence[SupportsIndex]: + keep_ones: bool = False) -> Tuple[SupportsIndex, ...]: """ Return dimensions that reshape 'fro' to become broadcastable to 'to'. @@ -490,7 +490,7 @@ def shapeshifter(fro: Tuple[int, ...], def shapeblender(a: Tuple[int, ...], - b: Tuple[int, ...]) -> Sequence[SupportsIndex]: + b: Tuple[int, ...]) -> Tuple[SupportsIndex, ...]: """ Return a shape that overlaps the rightmost entries of 'a' with the leftmost of 'b'. From 71bc92fed054539fde49c6dee208f72a0d2bd0dc Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Wed, 2 Feb 2022 11:11:59 +0100 Subject: [PATCH 14/30] changed quote layout to single quote Added NotImplemented returnvalue to __eq__ functions --- python/damask/_orientation.py | 36 +++++++++---------- python/damask/_rotation.py | 66 +++++++++++++++++------------------ 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index d1488c14e..8a560e756 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -125,7 +125,7 @@ class Orientation(Rotation,Crystal): Rotation.__repr__(self)]) def __copy__(self, - rotation: Union[FloatSequence, Rotation] = None) -> "Orientation": + rotation: Union[FloatSequence, Rotation] = None) -> 'Orientation': """Create deep copy.""" dup = copy.deepcopy(self) if rotation is not None: @@ -148,7 +148,7 @@ class Orientation(Rotation,Crystal): """ if not isinstance(other, Orientation): - raise TypeError + raise NotImplemented matching_type = self.family == other.family and \ self.lattice == other.lattice and \ self.parameters == other.parameters @@ -235,7 +235,7 @@ class Orientation(Rotation,Crystal): def __mul__(self, - other: Union[Rotation, "Orientation"]) -> "Orientation": + other: Union[Rotation, 'Orientation']) -> 'Orientation': """ Compose this orientation with other. @@ -290,77 +290,77 @@ class Orientation(Rotation,Crystal): @classmethod @util.extended_docstring(Rotation.from_random, _parameter_doc) - def from_random(cls, **kwargs) -> "Orientation": + 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) -> "Orientation": + 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) -> "Orientation": + 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) -> "Orientation": + 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) -> "Orientation": + 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) -> "Orientation": + 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) -> "Orientation": + 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) -> "Orientation": + 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) -> "Orientation": + 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) -> "Orientation": + 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) -> "Orientation": + 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) @@ -370,7 +370,7 @@ class Orientation(Rotation,Crystal): def from_directions(cls, uvw: FloatSequence, hkl: FloatSequence, - **kwargs) -> "Orientation": + **kwargs) -> 'Orientation': """ Initialize orientation object from two crystallographic directions. @@ -390,7 +390,7 @@ class Orientation(Rotation,Crystal): @property - def equivalent(self) -> "Orientation": + def equivalent(self) -> 'Orientation': """ Orientations that are symmetrically equivalent. @@ -404,7 +404,7 @@ class Orientation(Rotation,Crystal): @property - def reduced(self) -> "Orientation": + def reduced(self) -> 'Orientation': """Select symmetrically equivalent orientation that falls into fundamental zone according to symmetry.""" eq = self.equivalent ok = eq.in_FZ @@ -940,7 +940,7 @@ class Orientation(Rotation,Crystal): def related(self, - model: str) -> "Orientation": + model: str) -> 'Orientation': """ Orientations derived from the given relationship. diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index b55f178f7..6e01cae65 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -65,7 +65,7 @@ class Rotation: __slots__ = ['quaternion'] def __init__(self, - rotation: Union[FloatSequence, "Rotation"] = np.array([1.0,0.0,0.0,0.0])): + rotation: Union[FloatSequence, 'Rotation'] = np.array([1.0,0.0,0.0,0.0])): """ New rotation. @@ -83,7 +83,7 @@ class Rotation: elif np.array(rotation).shape[-1] == 4: self.quaternion = np.array(rotation) else: - raise TypeError('"rotation" is neither a Rotation nor a quaternion') + raise TypeError('Rotation is neither a Rotation nor a quaternion') def __repr__(self) -> str: @@ -93,7 +93,7 @@ class Rotation: def __copy__(self, - rotation: Union[FloatSequence, "Rotation"] = None) -> "Rotation": + rotation: Union[FloatSequence, 'Rotation'] = None) -> 'Rotation': """Create deep copy.""" dup = copy.deepcopy(self) if rotation is not None: @@ -122,7 +122,7 @@ class Rotation: """ if not isinstance(other, Rotation): - raise TypeError + return NotImplemented return np.logical_or(np.all(self.quaternion == other.quaternion,axis=-1), np.all(self.quaternion == -1.0*other.quaternion,axis=-1)) @@ -143,7 +143,7 @@ class Rotation: def isclose(self, - other: "Rotation", + other: 'Rotation', rtol: float = 1e-5, atol: float = 1e-8, equal_nan: bool = True) -> bool: @@ -174,7 +174,7 @@ class Rotation: def allclose(self, - other: "Rotation", + other: 'Rotation', rtol: float = 1e-5, atol: float = 1e-8, equal_nan: bool = True) -> Union[np.bool_, bool]: @@ -220,14 +220,14 @@ class Rotation: return 0 if self.shape == () else self.shape[0] - def __invert__(self) -> "Rotation": + def __invert__(self) -> 'Rotation': """Inverse rotation (backward rotation).""" dup = self.copy() dup.quaternion[...,1:] *= -1 return dup - def __pow__(self, exp: int) -> "Rotation": + def __pow__(self, exp: int) -> 'Rotation': """ Perform the rotation 'exp' times. @@ -241,7 +241,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: int) -> "Rotation": + def __ipow__(self, exp: int) -> 'Rotation': """ Perform the rotation 'exp' times (in-place). @@ -254,7 +254,7 @@ class Rotation: return self**exp - def __mul__(self, other: "Rotation") -> "Rotation": + def __mul__(self, other: 'Rotation') -> 'Rotation': """ Compose with other. @@ -280,7 +280,7 @@ class Rotation: else: raise TypeError('Use "R@b", i.e. matmul, to apply rotation "R" to object "b"') - def __imul__(self, other: "Rotation") -> "Rotation": + def __imul__(self, other: 'Rotation') -> 'Rotation': """ Compose with other (in-place). @@ -293,7 +293,7 @@ class Rotation: return self*other - def __truediv__(self, other: "Rotation") -> "Rotation": + def __truediv__(self, other: 'Rotation') -> 'Rotation': """ Compose with inverse of other. @@ -313,7 +313,7 @@ class Rotation: else: raise TypeError('Use "R@b", i.e. matmul, to apply rotation "R" to object "b"') - def __itruediv__(self, other: "Rotation") -> "Rotation": + def __itruediv__(self, other: 'Rotation') -> 'Rotation': """ Compose with inverse of other (in-place). @@ -369,14 +369,14 @@ class Rotation: apply = __matmul__ - def _standardize(self) -> "Rotation": + def _standardize(self) -> 'Rotation': """Standardize quaternion (ensure positive real hemisphere).""" self.quaternion[self.quaternion[...,0] < 0.0] *= -1 return self def append(self, - other: Union["Rotation", List["Rotation"]]) -> "Rotation": + other: Union['Rotation', List['Rotation']]) -> 'Rotation': """ Extend array along first dimension with other array(s). @@ -390,7 +390,7 @@ class Rotation: def flatten(self, - order: Literal['C','F','A'] = 'C') -> "Rotation": + order: Literal['C','F','A'] = 'C') -> 'Rotation': """ Flatten array. @@ -405,7 +405,7 @@ class Rotation: def reshape(self, shape: Union[int, Tuple[int, ...]], - order: Literal['C','F','A'] = 'C') -> "Rotation": + order: Literal['C','F','A'] = 'C') -> 'Rotation': """ Reshape array. @@ -421,7 +421,7 @@ class Rotation: def broadcast_to(self, shape: Union[int, Tuple[int, ...]], - mode: Literal["left", "right"] = 'right') -> "Rotation": + mode: Literal['left', 'right'] = 'right') -> 'Rotation': """ Broadcast array. @@ -445,7 +445,7 @@ class Rotation: def average(self, - weights: np.ndarray = None) -> "Rotation": + weights: np.ndarray = None) -> 'Rotation': """ Average along last array dimension. @@ -485,7 +485,7 @@ class Rotation: def misorientation(self, - other: "Rotation") -> "Rotation": + other: 'Rotation') -> 'Rotation': """ Calculate misorientation to other Rotation. @@ -690,7 +690,7 @@ class Rotation: @staticmethod def from_quaternion(q: Union[Sequence[FloatSequence], np.ndarray], accept_homomorph: bool = False, - P: Literal[1, -1] = -1) -> "Rotation": + P: Literal[1, -1] = -1) -> 'Rotation': """ Initialize from quaternion. @@ -724,7 +724,7 @@ class Rotation: @staticmethod def from_Euler_angles(phi: np.ndarray, - degrees: bool = False) -> "Rotation": + degrees: bool = False) -> 'Rotation': """ Initialize from Bunge Euler angles. @@ -755,7 +755,7 @@ class Rotation: def from_axis_angle(axis_angle: np.ndarray, degrees:bool = False, normalize: bool = False, - P: Literal[1, -1] = -1) -> "Rotation": + P: Literal[1, -1] = -1) -> 'Rotation': """ Initialize from Axis angle pair. @@ -792,7 +792,7 @@ class Rotation: @staticmethod def from_basis(basis: np.ndarray, orthonormal: bool = True, - reciprocal: bool = False) -> "Rotation": + reciprocal: bool = False) -> 'Rotation': """ Initialize from lattice basis vectors. @@ -826,7 +826,7 @@ class Rotation: return Rotation(Rotation._om2qu(om)) @staticmethod - def from_matrix(R: np.ndarray) -> "Rotation": + def from_matrix(R: np.ndarray) -> 'Rotation': """ Initialize from rotation matrix. @@ -840,7 +840,7 @@ class Rotation: @staticmethod def from_parallel(a: np.ndarray, - b: np.ndarray ) -> "Rotation": + b: np.ndarray ) -> 'Rotation': """ Initialize from pairs of two orthogonal lattice basis vectors. @@ -870,7 +870,7 @@ class Rotation: @staticmethod def from_Rodrigues_vector(rho: np.ndarray, normalize: bool = False, - P: Literal[1, -1] = -1) -> "Rotation": + P: Literal[1, -1] = -1) -> 'Rotation': """ Initialize from Rodrigues–Frank vector (angle separated from axis). @@ -901,7 +901,7 @@ class Rotation: @staticmethod def from_homochoric(h: np.ndarray, - P: Literal[1, -1] = -1) -> "Rotation": + P: Literal[1, -1] = -1) -> 'Rotation': """ Initialize from homochoric vector. @@ -928,7 +928,7 @@ class Rotation: @staticmethod def from_cubochoric(x: np.ndarray, - P: Literal[1, -1] = -1) -> "Rotation": + P: Literal[1, -1] = -1) -> 'Rotation': """ Initialize from cubochoric vector. @@ -956,7 +956,7 @@ class Rotation: @staticmethod def from_random(shape: Tuple[int, ...] = None, - rng_seed: Union[int, IntSequence] = None) -> "Rotation": + rng_seed: Union[int, IntSequence] = None) -> 'Rotation': """ Initialize with random rotation. @@ -990,7 +990,7 @@ class Rotation: N: int = 500, degrees: bool = True, fractions: bool = True, - rng_seed: Union[None, int, IntSequence] = None) -> "Rotation": + rng_seed: Union[None, int, IntSequence] = None) -> 'Rotation': """ Sample discrete values from a binned orientation distribution function (ODF). @@ -1043,11 +1043,11 @@ class Rotation: @staticmethod - def from_spherical_component(center: "Rotation", + def from_spherical_component(center: 'Rotation', sigma: float, N: int = 500, degrees: bool = True, - rng_seed: Union[None, int, IntSequence] = None) -> "Rotation": + rng_seed: Union[None, int, IntSequence] = None) -> 'Rotation': """ Calculate set of rotations with Gaussian distribution around center. From 3df411469b5cf2a68a845e54bfa6affa1d45f7fb Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Wed, 2 Feb 2022 12:14:00 +0100 Subject: [PATCH 15/30] Added generic type to rotation functions not overwritten by orientation --- python/damask/_rotation.py | 43 ++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 6e01cae65..a04b20e62 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -6,7 +6,7 @@ from . import tensor from . import util from . import grid_filters -from typing import Union, Sequence, Tuple, Literal, List +from typing import Union, Sequence, Tuple, Literal, List, TypeVar from ._typehints import FloatSequence, IntSequence _P = -1 @@ -16,6 +16,8 @@ _sc = np.pi**(1./6.)/6.**(1./6.) _beta = np.pi**(5./6.)/6.**(1./6.)/2. _R1 = (3.*np.pi/4.)**(1./3.) +MyType = TypeVar('MyType', bound='Rotation') + class Rotation: u""" Rotation with functionality for conversion between different representations. @@ -92,8 +94,8 @@ class Rotation: + str(self.quaternion) - def __copy__(self, - rotation: Union[FloatSequence, 'Rotation'] = None) -> 'Rotation': + def __copy__(self: MyType, + rotation: Union[FloatSequence, 'Rotation'] = None) -> MyType: """Create deep copy.""" dup = copy.deepcopy(self) if rotation is not None: @@ -220,14 +222,15 @@ class Rotation: return 0 if self.shape == () else self.shape[0] - def __invert__(self) -> 'Rotation': + def __invert__(self: MyType) -> MyType: """Inverse rotation (backward rotation).""" dup = self.copy() dup.quaternion[...,1:] *= -1 return dup - def __pow__(self, exp: int) -> 'Rotation': + def __pow__(self: MyType, + exp: int) -> MyType: """ Perform the rotation 'exp' times. @@ -241,7 +244,8 @@ 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: int) -> 'Rotation': + def __ipow__(self: MyType, + exp: int) -> MyType: """ Perform the rotation 'exp' times (in-place). @@ -280,7 +284,8 @@ class Rotation: else: raise TypeError('Use "R@b", i.e. matmul, to apply rotation "R" to object "b"') - def __imul__(self, other: 'Rotation') -> 'Rotation': + def __imul__(self, + other: 'Rotation') -> 'Rotation': """ Compose with other (in-place). @@ -293,7 +298,8 @@ class Rotation: return self*other - def __truediv__(self, other: 'Rotation') -> 'Rotation': + def __truediv__(self: 'Rotation', + other: 'Rotation') -> 'Rotation': """ Compose with inverse of other. @@ -313,7 +319,8 @@ class Rotation: else: raise TypeError('Use "R@b", i.e. matmul, to apply rotation "R" to object "b"') - def __itruediv__(self, other: 'Rotation') -> 'Rotation': + def __itruediv__(self: 'Rotation', + other: 'Rotation') -> 'Rotation': """ Compose with inverse of other (in-place). @@ -369,14 +376,14 @@ class Rotation: apply = __matmul__ - def _standardize(self) -> 'Rotation': + def _standardize(self: MyType) -> MyType: """Standardize quaternion (ensure positive real hemisphere).""" self.quaternion[self.quaternion[...,0] < 0.0] *= -1 return self - def append(self, - other: Union['Rotation', List['Rotation']]) -> 'Rotation': + def append(self: MyType, + other: Union[MyType, List[MyType]]) -> MyType: """ Extend array along first dimension with other array(s). @@ -389,8 +396,8 @@ class Rotation: [self]+other if isinstance(other,list) else [self,other])))) - def flatten(self, - order: Literal['C','F','A'] = 'C') -> 'Rotation': + def flatten(self: MyType, + order: Literal['C','F','A'] = 'C') -> MyType: """ Flatten array. @@ -403,9 +410,9 @@ class Rotation: return self.copy(rotation=self.quaternion.reshape((-1,4),order=order)) - def reshape(self, + def reshape(self: MyType, shape: Union[int, Tuple[int, ...]], - order: Literal['C','F','A'] = 'C') -> 'Rotation': + order: Literal['C','F','A'] = 'C') -> MyType: """ Reshape array. @@ -419,9 +426,9 @@ class Rotation: return self.copy(rotation=self.quaternion.reshape(tuple(shape)+(4,),order=order)) - def broadcast_to(self, + def broadcast_to(self: MyType, shape: Union[int, Tuple[int, ...]], - mode: Literal['left', 'right'] = 'right') -> 'Rotation': + mode: Literal['left', 'right'] = 'right') -> MyType: """ Broadcast array. From fc8cd6322c52eebc13c1485aea64e79328e1b8e6 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Wed, 2 Feb 2022 12:16:57 +0100 Subject: [PATCH 16/30] adjusted rng_seed type in rotation adjusted NotImplemented error return in orientation --- python/damask/_orientation.py | 2 +- python/damask/_rotation.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 8a560e756..dff380dcf 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -148,7 +148,7 @@ class Orientation(Rotation,Crystal): """ if not isinstance(other, Orientation): - raise NotImplemented + return NotImplemented matching_type = self.family == other.family and \ self.lattice == other.lattice and \ self.parameters == other.parameters diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index a04b20e62..361dba7c1 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -997,7 +997,7 @@ class Rotation: N: int = 500, degrees: bool = True, fractions: bool = True, - rng_seed: Union[None, int, IntSequence] = None) -> 'Rotation': + rng_seed: Union[int, IntSequence] = None) -> 'Rotation': """ Sample discrete values from a binned orientation distribution function (ODF). @@ -1054,7 +1054,7 @@ class Rotation: sigma: float, N: int = 500, degrees: bool = True, - rng_seed: Union[None, int, IntSequence] = None) -> 'Rotation': + rng_seed: Union[int, IntSequence] = None) -> 'Rotation': """ Calculate set of rotations with Gaussian distribution around center. From f80de7d0b36b8dcd63e876a43763e4a897fdd01e Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Thu, 3 Feb 2022 12:04:31 +0100 Subject: [PATCH 17/30] added NumpyRngSeed type to rotation rng_seed objects --- python/damask/_rotation.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 361dba7c1..d919c9029 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -7,7 +7,7 @@ from . import util from . import grid_filters from typing import Union, Sequence, Tuple, Literal, List, TypeVar -from ._typehints import FloatSequence, IntSequence +from ._typehints import FloatSequence, IntSequence, NumpyRngSeed _P = -1 @@ -963,7 +963,7 @@ class Rotation: @staticmethod def from_random(shape: Tuple[int, ...] = None, - rng_seed: Union[int, IntSequence] = None) -> 'Rotation': + rng_seed: NumpyRngSeed = None) -> 'Rotation': """ Initialize with random rotation. @@ -997,7 +997,7 @@ class Rotation: N: int = 500, degrees: bool = True, fractions: bool = True, - rng_seed: Union[int, IntSequence] = None) -> 'Rotation': + rng_seed: NumpyRngSeed = None) -> 'Rotation': """ Sample discrete values from a binned orientation distribution function (ODF). @@ -1054,7 +1054,7 @@ class Rotation: sigma: float, N: int = 500, degrees: bool = True, - rng_seed: Union[int, IntSequence] = None) -> 'Rotation': + rng_seed: NumpyRngSeed = None) -> 'Rotation': """ Calculate set of rotations with Gaussian distribution around center. @@ -1090,7 +1090,7 @@ class Rotation: sigma: float = 0.0, N: int = 500, degrees: bool = True, - rng_seed: Union[int, IntSequence] = None): + rng_seed: NumpyRngSeed = None): """ Calculate set of rotations with Gaussian distribution around direction. From d1f9e98e3ce071c9e24488675bc6ed9b579fb524 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Thu, 3 Feb 2022 16:11:09 +0100 Subject: [PATCH 18/30] moved typecheck of __ne__ functions to __eq__ added initial empty runtimeerror to Schmid function minor corrections --- python/damask/_orientation.py | 13 ++++++++----- python/damask/_rotation.py | 7 ++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index dff380dcf..780a5929e 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -165,8 +165,10 @@ class Orientation(Rotation,Crystal): Orientation to check for equality. """ - if not isinstance(other, Orientation): - raise TypeError + + eq = self.__eq__(other) + if not isinstance(eq, bool): + return eq return np.logical_not(self==other) @@ -557,7 +559,7 @@ class Orientation(Rotation,Crystal): if self.family != other.family: raise NotImplementedError('disorientation between different crystal families') - blend: Tuple[int, ...] = util.shapeblender(self.shape,other.shape) + blend = util.shapeblender(self.shape,other.shape) s = self.equivalent o = other.equivalent @@ -764,7 +766,7 @@ class Orientation(Rotation,Crystal): np.broadcast_to(self.standard_triangle['improper'], vector_.shape+(3,)), vector_), 12) in_SST_ = np.all(components_proper >= 0.0,axis=-1) \ - | np.all(components_improper >= 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_proper,components_improper) else: @@ -928,7 +930,8 @@ class Orientation(Rotation,Crystal): (self.kinematics('twin'),N_twin) if active == '*': active = [len(a) for a in kinematics['direction']] - assert active + if not active: + raise RuntimeError 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), diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index d919c9029..c3f6407f2 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -85,7 +85,7 @@ class Rotation: elif np.array(rotation).shape[-1] == 4: self.quaternion = np.array(rotation) else: - raise TypeError('Rotation is neither a Rotation nor a quaternion') + raise TypeError('"rotation" is neither a Rotation nor a quaternion') def __repr__(self) -> str: @@ -139,8 +139,9 @@ class Rotation: Rotation to check for inequality. """ - if not isinstance(other, Rotation): - raise TypeError + eq = self.__eq__(other) + if not isinstance(eq, bool): + return eq return np.logical_not(self==other) From 9dad54304c8886596bd84bac5722466d5c923eb8 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Fri, 4 Feb 2022 09:57:42 +0100 Subject: [PATCH 19/30] added generic types to remaining non-overwritten rotation functions (exception __mul__) --- python/damask/_orientation.py | 11 +++++------ python/damask/_rotation.py | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 780a5929e..2dedc13e4 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -165,7 +165,6 @@ class Orientation(Rotation,Crystal): Orientation to check for equality. """ - eq = self.__eq__(other) if not isinstance(eq, bool): return eq @@ -501,8 +500,8 @@ class Orientation(Rotation,Crystal): return np.ones_like(rho[...,0],dtype=bool) def disorientation(self, - other, - return_operators = False): + other: "Orientation", + return_operators: bool = False) -> object: """ Calculate disorientation between myself and given other orientation. @@ -575,9 +574,9 @@ class Orientation(Rotation,Crystal): r = np.where(np.any(forward[...,np.newaxis],axis=(0,1),keepdims=True), r_.quaternion, _r.quaternion) - 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,)) + loc = np.where(ok) + sort = 0 if len(loc) == 2 else np.lexsort(loc[:1:-1]) + quat = r[ok][sort].reshape(blend+(4,)) return ( (self.copy(rotation=quat), (np.vstack(loc[:2]).T)[sort].reshape(blend+(2,))) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index c3f6407f2..d40b48669 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -259,7 +259,7 @@ class Rotation: return self**exp - def __mul__(self, other: 'Rotation') -> 'Rotation': + def __mul__(self: MyType, other: MyType) -> MyType: """ Compose with other. @@ -281,12 +281,12 @@ class Rotation: 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() + return Rotation(np.block([q,p]))._standardize() #type: ignore else: raise TypeError('Use "R@b", i.e. matmul, to apply rotation "R" to object "b"') - def __imul__(self, - other: 'Rotation') -> 'Rotation': + def __imul__(self: MyType, + other: MyType) -> MyType: """ Compose with other (in-place). @@ -299,8 +299,8 @@ class Rotation: return self*other - def __truediv__(self: 'Rotation', - other: 'Rotation') -> 'Rotation': + def __truediv__(self: MyType, + other: MyType) -> MyType: """ Compose with inverse of other. @@ -320,8 +320,8 @@ class Rotation: else: raise TypeError('Use "R@b", i.e. matmul, to apply rotation "R" to object "b"') - def __itruediv__(self: 'Rotation', - other: 'Rotation') -> 'Rotation': + def __itruediv__(self: MyType, + other: MyType) -> MyType: """ Compose with inverse of other (in-place). @@ -492,8 +492,8 @@ class Rotation: accept_homomorph = True) - def misorientation(self, - other: 'Rotation') -> 'Rotation': + def misorientation(self: MyType, + other: MyType) -> MyType: """ Calculate misorientation to other Rotation. From 7a405125daa7f29b3b9d13d7b5c1fdb59d435bfd Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Fri, 4 Feb 2022 10:57:24 +0100 Subject: [PATCH 20/30] added type:ignore statements to Tuple Supportsindex addition in Orientation.disorientation function --- python/damask/_orientation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 2dedc13e4..4817ddb6f 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -562,8 +562,8 @@ class Orientation(Rotation,Crystal): 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') - o_ = o.reshape((1,o.shape[0])+other.shape).broadcast_to((s.shape[0],o.shape[0])+blend,mode='right') + s_ = s.reshape((s.shape[0],1)+ self.shape).broadcast_to((s.shape[0],o.shape[0])+blend,mode='right') #type: ignore + o_ = o.reshape((1,o.shape[0])+other.shape).broadcast_to((s.shape[0],o.shape[0])+blend,mode='right') #type: ignore r_ = s_.misorientation(o_) _r = ~r_ From 019ae1c536c26c5ed6662dbddad4d12736c7f5be Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Fri, 4 Feb 2022 11:43:35 +0100 Subject: [PATCH 21/30] adjusted typecheck in __eq__ and __ne__ functions --- python/damask/_orientation.py | 6 ++---- python/damask/_rotation.py | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 4817ddb6f..2221af8a4 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -148,7 +148,7 @@ class Orientation(Rotation,Crystal): """ if not isinstance(other, Orientation): - return NotImplemented + raise NotImplementedError matching_type = self.family == other.family and \ self.lattice == other.lattice and \ self.parameters == other.parameters @@ -165,9 +165,7 @@ class Orientation(Rotation,Crystal): Orientation to check for equality. """ - eq = self.__eq__(other) - if not isinstance(eq, bool): - return eq + self.__eq__(other) return np.logical_not(self==other) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index d40b48669..5efaf9d9e 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -124,7 +124,7 @@ class Rotation: """ if not isinstance(other, Rotation): - return NotImplemented + raise NotImplementedError return np.logical_or(np.all(self.quaternion == other.quaternion,axis=-1), np.all(self.quaternion == -1.0*other.quaternion,axis=-1)) @@ -139,9 +139,7 @@ class Rotation: Rotation to check for inequality. """ - eq = self.__eq__(other) - if not isinstance(eq, bool): - return eq + self.__eq__(other) return np.logical_not(self==other) From c1c23366383f96182fee8ff8411d83ab58b94b79 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Fri, 4 Feb 2022 16:57:25 +0100 Subject: [PATCH 22/30] reverted __eq__ and __ne type verification to return NotImplemented constant changed rotation.average input type to FloatSequence minor adjustments --- python/damask/_orientation.py | 10 ++++------ python/damask/_rotation.py | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 2221af8a4..d90cc70e6 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -148,7 +148,7 @@ class Orientation(Rotation,Crystal): """ if not isinstance(other, Orientation): - raise NotImplementedError + return NotImplemented matching_type = self.family == other.family and \ self.lattice == other.lattice and \ self.parameters == other.parameters @@ -165,8 +165,7 @@ class Orientation(Rotation,Crystal): Orientation to check for equality. """ - self.__eq__(other) - return np.logical_not(self==other) + return np.logical_not(self==other) if isinstance(other, Orientation) else NotImplemented def isclose(self, @@ -498,7 +497,7 @@ class Orientation(Rotation,Crystal): return np.ones_like(rho[...,0],dtype=bool) def disorientation(self, - other: "Orientation", + other: 'Orientation', return_operators: bool = False) -> object: """ Calculate disorientation between myself and given other orientation. @@ -612,8 +611,7 @@ class Orientation(Rotation,Crystal): """ eq = self.equivalent m = eq.misorientation(self[...,0].reshape((1,)+self.shape[:-1]+(1,)) - .broadcast_to(eq.shape))\ - .as_axis_angle()[...,3] + .broadcast_to(eq.shape)).as_axis_angle()[...,3] r = Rotation(np.squeeze(np.take_along_axis(eq.quaternion, np.argmin(m,axis=0)[np.newaxis,...,np.newaxis], axis=0), diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 5efaf9d9e..53902def7 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -124,12 +124,13 @@ class Rotation: """ if not isinstance(other, Rotation): - raise NotImplementedError + return NotImplemented 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: object) -> bool: + def __ne__(self, + other: object) -> bool: """ Not equal to other. @@ -139,9 +140,7 @@ class Rotation: Rotation to check for inequality. """ - self.__eq__(other) - return np.logical_not(self==other) - + return np.logical_not(self==other) if isinstance(other, Rotation) else NotImplemented def isclose(self, other: 'Rotation', @@ -257,7 +256,8 @@ class Rotation: return self**exp - def __mul__(self: MyType, other: MyType) -> MyType: + def __mul__(self: MyType, + other: MyType) -> MyType: """ Compose with other. @@ -332,7 +332,8 @@ class Rotation: return self/other - def __matmul__(self, other: np.ndarray) -> np.ndarray: + def __matmul__(self, + other: np.ndarray) -> np.ndarray: """ Rotate vector, second order tensor, or fourth order tensor. @@ -451,7 +452,7 @@ class Rotation: def average(self, - weights: np.ndarray = None) -> 'Rotation': + weights: FloatSequence = None) -> 'Rotation': """ Average along last array dimension. @@ -475,11 +476,10 @@ class Rotation: """Intermediate representation supporting quaternion averaging.""" return np.einsum('...i,...j',quat,quat) - if weights is None: - weights = np.ones(self.shape,dtype=float) + weights_ = np.ones(self.shape,dtype=float) if weights is None else np.array(weights,float) - eig, vec = np.linalg.eig(np.sum(_M(self.quaternion) * weights[...,np.newaxis,np.newaxis],axis=-3) \ - /np.sum( weights[...,np.newaxis,np.newaxis],axis=-3)) + eig, vec = np.linalg.eig(np.sum(_M(self.quaternion) * weights_[...,np.newaxis,np.newaxis],axis=-3) \ + /np.sum( weights_[...,np.newaxis,np.newaxis],axis=-3)) return Rotation.from_quaternion(np.real( np.squeeze( From 72978df099a7c86958d4c315facbfe1c90c91e69 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Fri, 4 Feb 2022 17:41:29 +0100 Subject: [PATCH 23/30] minor correction --- python/damask/_rotation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 53902def7..226d66f69 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -458,7 +458,7 @@ class Rotation: Parameters ---------- - weights : numpy.ndarray, optional + weights : FloatSequence, optional Relative weight of each rotation. Returns From a6e83c70ece1de13e989b49419daa62c6459d075 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 6 Feb 2022 17:11:18 +0100 Subject: [PATCH 24/30] adjustments to follow de-facto standard in other parts of the python library --- python/damask/_crystal.py | 5 +++-- python/damask/_orientation.py | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/python/damask/_crystal.py b/python/damask/_crystal.py index 0b1c00458..ad5b9ed31 100644 --- a/python/damask/_crystal.py +++ b/python/damask/_crystal.py @@ -2,6 +2,7 @@ from typing import Union, Dict, List, Tuple import numpy as np +from ._typehints import FloatSequence from . import util from . import Rotation @@ -341,8 +342,8 @@ class Crystal(): def to_frame(self, *, - uvw: np.ndarray = None, - hkl: np.ndarray = None) -> np.ndarray: + uvw: FloatSequence = None, + hkl: FloatSequence = None) -> np.ndarray: """ Calculate crystal frame vector along lattice direction [uvw] or plane normal (hkl). diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index d90cc70e6..c28332012 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -1,15 +1,15 @@ import inspect import copy +from typing import Union, Callable, Dict, Any, Tuple import numpy as np +from ._typehints import FloatSequence, IntSequence from . import Rotation from . import Crystal from . import util from . import tensor -from typing import Union, Callable, Dict, Any, Tuple, List -from ._typehints import FloatSequence, IntSequence _parameter_doc = \ @@ -165,7 +165,7 @@ class Orientation(Rotation,Crystal): Orientation to check for equality. """ - return np.logical_not(self==other) if isinstance(other, Orientation) else NotImplemented + return np.logical_not(self==other) def isclose(self, @@ -206,7 +206,7 @@ class Orientation(Rotation,Crystal): other: object, rtol: float = 1e-5, atol: float = 1e-8, - equal_nan: bool = True) -> Union[bool, np.bool_]: + equal_nan: bool = True) -> bool: """ Test whether all values are approximately equal to corresponding ones of other Orientation. @@ -227,9 +227,7 @@ 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)) + return bool(np.all(self.isclose(other,rtol,atol,equal_nan))) def __mul__(self, @@ -411,6 +409,7 @@ 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) -> Union[np.bool_, np.ndarray]: """ @@ -574,6 +573,7 @@ class Orientation(Rotation,Crystal): loc = np.where(ok) sort = 0 if len(loc) == 2 else np.lexsort(loc[:1:-1]) quat = r[ok][sort].reshape(blend+(4,)) + return ( (self.copy(rotation=quat), (np.vstack(loc[:2]).T)[sort].reshape(blend+(2,))) @@ -783,7 +783,7 @@ class Orientation(Rotation,Crystal): @property def symmetry_operations(self) -> Rotation: """Symmetry operations as Rotations.""" - _symmetry_operations: Dict[str, List[List]] = { + _symmetry_operations = { 'cubic': [ [ 1.0, 0.0, 0.0, 0.0 ], [ 0.0, 1.0, 0.0, 0.0 ], @@ -854,8 +854,8 @@ class Orientation(Rotation,Crystal): # functions that require lattice, not just family def to_pole(self, *, - uvw: np.ndarray = None, - hkl: np.ndarray = None, + uvw: FloatSequence = None, + hkl: FloatSequence = None, with_symmetry: bool = False) -> np.ndarray: """ Calculate lab frame vector along lattice direction [uvw] or plane normal (hkl). @@ -926,7 +926,7 @@ class Orientation(Rotation,Crystal): if active == '*': active = [len(a) for a in kinematics['direction']] if not active: - raise RuntimeError + raise RuntimeError # ToDo 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), From 8c6225794d3953a22f7828c9354b6a8489e73de3 Mon Sep 17 00:00:00 2001 From: Daniel Otto de Mentock Date: Tue, 8 Feb 2022 14:47:23 +0100 Subject: [PATCH 25/30] adjusted return of Orientation.__ne__ function to return NotImplemented in case of wrong input type --- python/damask/_orientation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index c28332012..06caf9b4c 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -165,7 +165,7 @@ class Orientation(Rotation,Crystal): Orientation to check for equality. """ - return np.logical_not(self==other) + return np.logical_not(self==other) if isinstance(other, Orientation) else NotImplemented def isclose(self, From 0a52ae3b6f3e338ffedff35deaff4bab578f115b Mon Sep 17 00:00:00 2001 From: Philip Eisenlohr Date: Fri, 11 Feb 2022 12:10:29 -0500 Subject: [PATCH 26/30] polishing of help and style; relax to FloatSequence type where appropriate but keep doc at np.ndarray --- python/damask/_crystal.py | 9 ++- python/damask/_orientation.py | 84 +++++++++++++------------- python/damask/_rotation.py | 109 ++++++++++++++++++++-------------- python/damask/_table.py | 2 +- 4 files changed, 113 insertions(+), 91 deletions(-) diff --git a/python/damask/_crystal.py b/python/damask/_crystal.py index ad5b9ed31..a63cb0c81 100644 --- a/python/damask/_crystal.py +++ b/python/damask/_crystal.py @@ -131,9 +131,8 @@ class Crystal(): Crystal to check for equality. """ - if not isinstance(other, Crystal): - return NotImplemented - return self.lattice == other.lattice and \ + return NotImplemented if not isinstance(other, Crystal) else \ + self.lattice == other.lattice and \ self.parameters == other.parameters and \ self.family == other.family @@ -316,8 +315,8 @@ class Crystal(): self.lattice[-1],None),dtype=float) def to_lattice(self, *, - direction: np.ndarray = None, - plane: np.ndarray = None) -> np.ndarray: + direction: FloatSequence = None, + plane: FloatSequence = None) -> np.ndarray: """ Calculate lattice vector corresponding to crystal frame direction or plane normal. diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 06caf9b4c..5a9b7b141 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -189,7 +189,7 @@ class Orientation(Rotation,Crystal): Returns ------- - mask : numpy.ndarray bool + mask : numpy.ndarray of bool Mask indicating where corresponding orientations are close. """ @@ -372,10 +372,10 @@ class Orientation(Rotation,Crystal): Parameters ---------- - uvw : list, numpy.ndarray of shape (...,3) - lattice direction aligned with lab frame x-direction. - hkl : list, numpy.ndarray of shape (...,3) - lattice plane normal aligned with lab frame z-direction. + uvw : numpy.ndarray, shape (...,3) + Lattice direction aligned with lab frame x-direction. + hkl : numpy.ndarray, shape (...,3) + Lattice plane normal aligned with lab frame z-direction. """ o = cls(**kwargs) @@ -417,7 +417,7 @@ class Orientation(Rotation,Crystal): Returns ------- - in : numpy.ndarray of bool, quaternion.shape + in : numpy.ndarray of bool, shape (self.shape) Whether Rodrigues-Frank vector falls into fundamental zone. Notes @@ -461,7 +461,7 @@ class Orientation(Rotation,Crystal): Returns ------- - in : numpy.ndarray of bool, quaternion.shape + in : numpy.ndarray of bool, shape (self.shape) Whether Rodrigues-Frank vector falls into disorientation FZ. References @@ -515,7 +515,7 @@ class Orientation(Rotation,Crystal): ------- disorientation : Orientation Disorientation between self and other. - operators : numpy.ndarray int of shape (...,2), conditional + operators : numpy.ndarray of int, shape (...,2), conditional Index of symmetrically equivalent orientation that rotated vector to the SST. Notes @@ -583,14 +583,14 @@ class Orientation(Rotation,Crystal): def average(self, - weights = None, - return_cloud = False): + weights: FloatSequence = None, + return_cloud: bool = False): """ Return orientation average over last dimension. Parameters ---------- - weights : numpy.ndarray, optional + weights : numpy.ndarray, shape (self.shape), optional Relative weights of orientations. return_cloud : bool, optional Return the set of symmetrically equivalent orientations that was used in averaging. @@ -610,8 +610,8 @@ class Orientation(Rotation,Crystal): """ eq = self.equivalent - m = eq.misorientation(self[...,0].reshape((1,)+self.shape[:-1]+(1,)) - .broadcast_to(eq.shape)).as_axis_angle()[...,3] + m = eq.misorientation(self[...,0].reshape((1,)+self.shape[:-1]+(1,)) # type: ignore + .broadcast_to(eq.shape)).as_axis_angle()[...,3] # type: ignore r = Rotation(np.squeeze(np.take_along_axis(eq.quaternion, np.argmin(m,axis=0)[np.newaxis,...,np.newaxis], axis=0), @@ -625,7 +625,7 @@ class Orientation(Rotation,Crystal): def to_SST(self, - vector: np.ndarray, + vector: FloatSequence, proper: bool = False, return_operators: bool = False) -> np.ndarray: """ @@ -633,10 +633,10 @@ class Orientation(Rotation,Crystal): Parameters ---------- - vector : numpy.ndarray of shape (...,3) + vector : numpy.ndarray, shape (...,3) Lab frame vector to align with crystal frame direction. Shape of vector blends with shape of own rotation array. - For example, a rotation array of shape (3,2) and a (2,4) vector array result in (3,2,4) outputs. + For example, a rotation array of shape (3,2) and a vector array of shape (2,4) result in (3,2,4) outputs. proper : bool, optional Consider only vectors with z >= 0, hence combine two neighboring SSTs. Defaults to False. @@ -646,15 +646,18 @@ class Orientation(Rotation,Crystal): Returns ------- - vector_SST : numpy.ndarray of shape (...,3) + vector_SST : numpy.ndarray, shape (...,3) Rotated vector falling into SST. - operators : numpy.ndarray int of shape (...), conditional + operators : numpy.ndarray of int, shape (...), conditional Index of symmetrically equivalent orientation that rotated vector to SST. """ + vector_ = np.array(vector,float) + if vector_.shape[-1] != 3: + raise ValueError('input is not a field of three-dimensional vectors') eq = self.equivalent - blend = util.shapeblender(eq.shape,np.array(vector).shape[:-1]) - poles = eq.broadcast_to(blend,mode='right') @ np.broadcast_to(np.array(vector),blend+(3,)) #type: ignore + blend = util.shapeblender(eq.shape,vector_.shape[:-1]) + poles = eq.broadcast_to(blend,mode='right') @ np.broadcast_to(vector_,blend+(3,)) #type: ignore ok = self.in_SST(poles,proper=proper) ok &= np.cumsum(ok,axis=0) == 1 loc = np.where(ok) @@ -667,14 +670,14 @@ class Orientation(Rotation,Crystal): def in_SST(self, - vector: np.ndarray, + vector: FloatSequence, proper: bool = False) -> Union[np.bool_, np.ndarray]: """ Check whether given crystal frame vector falls into standard stereographic triangle of own symmetry. Parameters ---------- - vector : numpy.ndarray of shape (...,3) + vector : numpy.ndarray, shape (...,3) Vector to check. proper : bool, optional Consider only vectors with z >= 0, hence combine two neighboring SSTs. @@ -686,31 +689,32 @@ class Orientation(Rotation,Crystal): Whether vector falls into SST. """ - if not isinstance(vector,np.ndarray) or vector.shape[-1] != 3: + vector_ = np.array(vector,float) + if vector_.shape[-1] != 3: raise ValueError('input is not a field of three-dimensional vectors') if self.standard_triangle is None: # direct exit for no symmetry - return np.ones_like(vector[...,0],bool) + return np.ones_like(vector_[...,0],bool) if proper: components_proper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.standard_triangle['proper'], vector.shape+(3,)), - vector), 12) + np.broadcast_to(self.standard_triangle['proper'], vector_.shape+(3,)), + vector_), 12) components_improper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.standard_triangle['improper'], vector.shape+(3,)), - vector), 12) + np.broadcast_to(self.standard_triangle['improper'], vector_.shape+(3,)), + vector_), 12) return np.all(components_proper >= 0.0,axis=-1) \ | np.all(components_improper >= 0.0,axis=-1) 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) + np.broadcast_to(self.standard_triangle['improper'], vector_.shape+(3,)), + np.block([vector_[...,:2],np.abs(vector_[...,2:3])])), 12) return np.all(components >= 0.0,axis=-1) def IPF_color(self, - vector: np.ndarray, + vector: FloatSequence, in_SST: bool = True, proper: bool = False) -> np.ndarray: """ @@ -718,10 +722,10 @@ class Orientation(Rotation,Crystal): Parameters ---------- - vector : numpy.ndarray of shape (...,3) + vector : numpy.ndarray, shape (...,3) Vector to colorize. Shape of vector blends with shape of own rotation array. - For example, a rotation array of shape (3,2) and a (2,4) vector array result in (3,2,4) outputs. + For example, a rotation array of shape (3,2) and a vector array of shape (2,4) result in (3,2,4) outputs. in_SST : bool, optional Consider symmetrically equivalent orientations such that poles are located in SST. Defaults to True. @@ -731,7 +735,7 @@ class Orientation(Rotation,Crystal): Returns ------- - rgb : numpy.ndarray of shape (...,3) + rgb : numpy.ndarray, shape (...,3) RGB array of IPF colors. Examples @@ -755,7 +759,7 @@ class Orientation(Rotation,Crystal): if proper: components_proper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.standard_triangle['proper'], vector_.shape+(3,)), + np.broadcast_to(self.standard_triangle['proper'], vector_.shape+(3,)), vector_), 12) components_improper = np.around(np.einsum('...ji,...i', np.broadcast_to(self.standard_triangle['improper'], vector_.shape+(3,)), @@ -862,16 +866,16 @@ class Orientation(Rotation,Crystal): Parameters ---------- - uvw|hkl : numpy.ndarray of shape (...,3) + uvw|hkl : numpy.ndarray, shape (...,3) Miller indices of crystallographic direction or plane normal. Shape of vector blends with shape of own rotation array. - For example, a rotation array of shape (3,2) and a (2,4) vector array result in (3,2,4) outputs. + For example, a rotation array, shape (3,2) and a vector array of shape (2,4) result in (3,2,4) outputs. with_symmetry : bool, optional Calculate all N symmetrically equivalent vectors. Returns ------- - vector : numpy.ndarray of shape (...,3) or (...,N,3) + vector : numpy.ndarray, shape (...,3) or (...,N,3) Lab frame vector (or vectors if with_symmetry) along [uvw] direction or (hkl) plane normal. """ @@ -894,13 +898,13 @@ class Orientation(Rotation,Crystal): Parameters ---------- - N_slip|N_twin : iterable of int + N_slip|N_twin : '*' or iterable of int Number of deformation systems per family of the deformation system. Use '*' to select all. Returns ------- - P : numpy.ndarray of shape (N,...,3,3) + P : numpy.ndarray, shape (N,...,3,3) Schmid matrix for each of the N deformation systems. Examples diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 226d66f69..273cf8e92 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -108,12 +108,12 @@ class Rotation: 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 \ + 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: object) -> bool: + def __eq__(self, + other: object) -> bool: """ Equal to other. @@ -123,9 +123,8 @@ class Rotation: Rotation to check for equality. """ - if not isinstance(other, Rotation): - return NotImplemented - return np.logical_or(np.all(self.quaternion == other.quaternion,axis=-1), + return NotImplemented if not isinstance(other, Rotation) else \ + np.logical_or(np.all(self.quaternion == other.quaternion,axis=-1), np.all(self.quaternion == -1.0*other.quaternion,axis=-1)) @@ -163,7 +162,7 @@ class Rotation: Returns ------- - mask : numpy.ndarray bool + mask : numpy.ndarray of bool Mask indicating where corresponding rotations are close. """ @@ -228,13 +227,13 @@ class Rotation: def __pow__(self: MyType, - exp: int) -> MyType: + exp: Union[float, int]) -> MyType: """ Perform the rotation 'exp' times. Parameters ---------- - exp : float + exp : scalar Exponent. """ @@ -243,13 +242,13 @@ class Rotation: return self.copy(rotation=Rotation(np.block([np.cos(exp*phi),np.sin(exp*phi)*p]))._standardize()) def __ipow__(self: MyType, - exp: int) -> MyType: + exp: Union[float, int]) -> MyType: """ Perform the rotation 'exp' times (in-place). Parameters ---------- - exp : float + exp : scalar Exponent. """ @@ -263,7 +262,7 @@ class Rotation: Parameters ---------- - other : Rotation of shape (self.shape) + other : Rotation, shape (self.shape) Rotation for composition. Returns @@ -290,7 +289,7 @@ class Rotation: Parameters ---------- - other : Rotation of shape (self.shape) + other : Rotation, shape (self.shape) Rotation for composition. """ @@ -304,7 +303,7 @@ class Rotation: Parameters ---------- - other : damask.Rotation of shape (self.shape) + other : damask.Rotation, shape (self.shape) Rotation to invert for composition. Returns @@ -325,7 +324,7 @@ class Rotation: Parameters ---------- - other : Rotation of shape (self.shape) + other : Rotation, shape (self.shape) Rotation to invert for composition. """ @@ -339,12 +338,12 @@ class Rotation: Parameters ---------- - other : numpy.ndarray of shape (...,3), (...,3,3), or (...,3,3,3,3) + other : numpy.ndarray, shape (...,3), (...,3,3), or (...,3,3,3,3) Vector or tensor on which to apply the rotation. Returns ------- - rotated : numpy.ndarray of shape (...,3), (...,3,3), or (...,3,3,3,3) + rotated : numpy.ndarray, shape (...,3), (...,3,3), or (...,3,3,3,3) Rotated vector or tensor, i.e. transformed to frame defined by rotation. """ @@ -401,6 +400,15 @@ class Rotation: """ Flatten array. + Parameters + ---------- + order : {'C', 'F', 'A'}, optional + 'C' flattens in row-major (C-style) order. + 'F' flattens in column-major (Fortran-style) order. + 'A' flattens in column-major order if object is Fortran contiguous in memory, + row-major order otherwise. + Defaults to 'C'. + Returns ------- flattened : damask.Rotation @@ -416,6 +424,18 @@ class Rotation: """ Reshape array. + Parameters + ---------- + shape : int or tuple of ints + The new shape should be compatible with the original shape. + If an integer is supplied, then the result will be a 1-D array of that length. + order : {'C', 'F', 'A'}, optional + 'C' flattens in row-major (C-style) order. + 'F' flattens in column-major (Fortran-style) order. + 'A' flattens in column-major order if object is Fortran contiguous in memory, + row-major order otherwise. + Defaults to 'C'. + Returns ------- reshaped : damask.Rotation @@ -434,7 +454,7 @@ class Rotation: Parameters ---------- - shape : int, tuple + shape : int or tuple of ints Shape of broadcasted array. mode : str, optional Where to preferentially locate missing dimensions. @@ -458,7 +478,7 @@ class Rotation: Parameters ---------- - weights : FloatSequence, optional + weights : numpy.ndarray, optional Relative weight of each rotation. Returns @@ -518,7 +538,7 @@ class Rotation: Returns ------- - q : numpy.ndarray of shape (...,4) + q : numpy.ndarray, shape (...,4) Unit quaternion (q_0, q_1, q_2, q_3) in positive real hemisphere, i.e. ǀqǀ = 1, q_0 ≥ 0. """ @@ -536,7 +556,7 @@ class Rotation: Returns ------- - phi : numpy.ndarray of shape (...,3) + phi : numpy.ndarray, shape (...,3) Bunge Euler angles (φ_1 ∈ [0,2π], ϕ ∈ [0,π], φ_2 ∈ [0,2π]) or (φ_1 ∈ [0,360], ϕ ∈ [0,180], φ_2 ∈ [0,360]) if degrees == True. @@ -555,8 +575,7 @@ class Rotation: """ eu = Rotation._qu2eu(self.quaternion) - if degrees: eu = np.degrees(eu) - return eu + return np.degrees(eu) if degrees else eu def as_axis_angle(self, degrees: bool = False, @@ -573,7 +592,7 @@ class Rotation: Returns ------- - axis_angle : numpy.ndarray of shape (...,4) or tuple ((...,3), (...)) if pair == True + axis_angle : numpy.ndarray, shape (...,4) or tuple ((...,3), (...)) if pair == True Axis and angle [n_1, n_2, n_3, ω] with ǀnǀ = 1 and ω ∈ [0,π] or ω ∈ [0,180] if degrees == True. @@ -597,7 +616,7 @@ class Rotation: Returns ------- - R : numpy.ndarray of shape (...,3,3) + R : numpy.ndarray, shape (...,3,3) Rotation matrix R with det(R) = 1, R.T ∙ R = I. Examples @@ -627,7 +646,7 @@ class Rotation: Returns ------- - rho : numpy.ndarray of shape (...,4) or (...,3) if compact == True + rho : numpy.ndarray, shape (...,4) or (...,3) if compact == True Rodrigues–Frank vector [n_1, n_2, n_3, tan(ω/2)] with ǀnǀ = 1 and ω ∈ [0,π] or [n_1, n_2, n_3] with ǀnǀ = tan(ω/2) and ω ∈ [0,π] if compact == True. @@ -654,7 +673,7 @@ class Rotation: Returns ------- - h : numpy.ndarray of shape (...,3) + h : numpy.ndarray, shape (...,3) Homochoric vector (h_1, h_2, h_3) with ǀhǀ < (3/4*π)^(1/3). Examples @@ -675,7 +694,7 @@ class Rotation: Returns ------- - x : numpy.ndarray of shape (...,3) + x : numpy.ndarray, shape (...,3) Cubochoric vector (x_1, x_2, x_3) with max(x_i) < 1/2*π^(2/3). Examples @@ -702,7 +721,7 @@ class Rotation: Parameters ---------- - q : numpy.ndarray of shape (...,4) + q : numpy.ndarray, shape (...,4) Unit quaternion (q_0, q_1, q_2, q_3) in positive real hemisphere, i.e. ǀqǀ = 1, q_0 ≥ 0. accept_homomorph : bool, optional Allow homomorphic variants, i.e. q_0 < 0 (negative real hemisphere). @@ -736,7 +755,7 @@ class Rotation: Parameters ---------- - phi : numpy.ndarray of shape (...,3) + phi : numpy.ndarray, shape (...,3) Euler angles (φ_1 ∈ [0,2π], ϕ ∈ [0,π], φ_2 ∈ [0,2π]) or (φ_1 ∈ [0,360], ϕ ∈ [0,180], φ_2 ∈ [0,360]) if degrees == True. degrees : bool, optional @@ -767,7 +786,7 @@ class Rotation: Parameters ---------- - axis_angle : numpy.ndarray of shape (...,4) + axis_angle : numpy.ndarray, shape (...,4) Axis and angle (n_1, n_2, n_3, ω) with ǀnǀ = 1 and ω ∈ [0,π] or ω ∈ [0,180] if degrees == True. degrees : bool, optional @@ -804,7 +823,7 @@ class Rotation: Parameters ---------- - basis : numpy.ndarray of shape (...,3,3) + basis : numpy.ndarray, shape (...,3,3) Three three-dimensional lattice basis vectors. orthonormal : bool, optional Basis is strictly orthonormal, i.e. is free of stretch components. Defaults to True. @@ -838,7 +857,7 @@ class Rotation: Parameters ---------- - R : numpy.ndarray of shape (...,3,3) + R : numpy.ndarray, shape (...,3,3) Rotation matrix with det(R) = 1, R.T ∙ R = I. """ @@ -852,9 +871,9 @@ class Rotation: Parameters ---------- - a : numpy.ndarray of shape (...,2,3) + a : numpy.ndarray, shape (...,2,3) Two three-dimensional lattice vectors of first orthogonal basis. - b : numpy.ndarray of shape (...,2,3) + b : numpy.ndarray, shape (...,2,3) Corresponding three-dimensional lattice vectors of second basis. """ @@ -882,7 +901,7 @@ class Rotation: Parameters ---------- - rho : numpy.ndarray of shape (...,4) + rho : numpy.ndarray, shape (...,4) Rodrigues–Frank vector (n_1, n_2, n_3, tan(ω/2)) with ǀnǀ = 1 and ω ∈ [0,π]. normalize : bool, optional Allow ǀnǀ ≠ 1. Defaults to False. @@ -913,7 +932,7 @@ class Rotation: Parameters ---------- - h : numpy.ndarray of shape (...,3) + h : numpy.ndarray, shape (...,3) Homochoric vector (h_1, h_2, h_3) with ǀhǀ < (3/4*π)^(1/3). P : int ∈ {-1,1}, optional Sign convention. Defaults to -1. @@ -940,7 +959,7 @@ class Rotation: Parameters ---------- - x : numpy.ndarray of shape (...,3) + x : numpy.ndarray, shape (...,3) Cubochoric vector (x_1, x_2, x_3) with max(x_i) < 1/2*π^(2/3). P : int ∈ {-1,1}, optional Sign convention. Defaults to -1. @@ -1002,9 +1021,9 @@ class Rotation: Parameters ---------- - weights : numpy.ndarray of shape (n) + weights : numpy.ndarray, shape (n) Texture intensity values (probability density or volume fraction) at Euler space grid points. - phi : numpy.ndarray of shape (n,3) + phi : numpy.ndarray, shape (n,3) Grid coordinates in Euler space at which weights are defined. N : integer, optional Number of discrete orientations to be sampled from the given ODF. @@ -1020,14 +1039,14 @@ class Rotation: Returns ------- - samples : damask.Rotation of shape (N) - Array of sampled rotations closely representing the input ODF. + samples : damask.Rotation, shape (N) + Array of sampled rotations that approximate the input ODF. Notes ----- Due to the distortion of Euler space in the vicinity of ϕ = 0, probability densities, p, defined on grid points with ϕ = 0 will never result in reconstructed orientations as their dV/V = p dγ = p × 0. - Hence, it is recommended to transform any such dataset to cell centers that avoid grid points at ϕ = 0. + Hence, it is recommended to transform any such dataset to a cell-centered version, which avoids grid points at ϕ = 0. References ---------- @@ -1095,9 +1114,9 @@ class Rotation: Parameters ---------- - alpha : numpy.ndarray of shape (2) + alpha : numpy.ndarray, shape (2) Polar coordinates (phi from x, theta from z) of fiber direction in crystal frame. - beta : numpy.ndarray of shape (2) + beta : numpy.ndarray, shape (2) Polar coordinates (phi from x, theta from z) of fiber direction in sample frame. sigma : float, optional Standard deviation of (Gaussian) misorientation distribution. diff --git a/python/damask/_table.py b/python/damask/_table.py index 1572c4f76..189f46d6b 100644 --- a/python/damask/_table.py +++ b/python/damask/_table.py @@ -185,7 +185,7 @@ class Table: Returns ------- - mask : numpy.ndarray bool + mask : numpy.ndarray of bool Mask indicating where corresponding table values are close. """ From c6a188a1fe1b96da102afbf714c1472f3be9b876 Mon Sep 17 00:00:00 2001 From: Philip Eisenlohr Date: Fri, 11 Feb 2022 15:40:14 -0500 Subject: [PATCH 27/30] added CrystalFamily, CrystalLattice, CrystalKinematics typehints --- python/damask/_crystal.py | 26 +++++++++++++------------- python/damask/_orientation.py | 10 +++++----- python/damask/_typehints.py | 6 +++++- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/python/damask/_crystal.py b/python/damask/_crystal.py index a63cb0c81..6da13f679 100644 --- a/python/damask/_crystal.py +++ b/python/damask/_crystal.py @@ -2,11 +2,11 @@ from typing import Union, Dict, List, Tuple import numpy as np -from ._typehints import FloatSequence +from ._typehints import FloatSequence, CrystalFamily, CrystalLattice, CrystalKinematics from . import util from . import Rotation -lattice_symmetries = { +lattice_symmetries: Dict[CrystalLattice, CrystalFamily] = { 'aP': 'triclinic', 'mP': 'monoclinic', @@ -32,8 +32,8 @@ class Crystal(): """Crystal lattice.""" def __init__(self,*, - family = None, - lattice = None, + family: CrystalFamily = None, + lattice: CrystalLattice = None, a: float = None, b: float = None, c: float = None, alpha: float = None, beta: float = None, gamma: float = None, degrees: bool = False): @@ -208,7 +208,7 @@ class Crystal(): ... } """ - _basis = { + _basis: Dict[CrystalFamily, Dict[str, np.ndarray]] = { 'cubic': {'improper':np.array([ [-1. , 0. , 1. ], [ np.sqrt(2.) , -np.sqrt(2.) , 0. ], [ 0. , np.sqrt(3.) , 0. ] ]), @@ -322,12 +322,12 @@ class Crystal(): Parameters ---------- - direction|plane : numpy.ndarray of shape (...,3) + direction|plane : numpy.ndarray, shape (...,3) Vector along direction or plane normal. Returns ------- - Miller : numpy.ndarray of shape (...,3) + Miller : numpy.ndarray, shape (...,3) Lattice vector of direction or plane. Use util.scale_to_coprime to convert to (integer) Miller indices. @@ -348,12 +348,12 @@ class Crystal(): Parameters ---------- - uvw|hkl : numpy.ndarray of shape (...,3) + uvw|hkl : numpy.ndarray, shape (...,3) Miller indices of crystallographic direction or plane normal. Returns ------- - vector : numpy.ndarray of shape (...,3) + vector : numpy.ndarray, shape (...,3) Crystal frame vector along [uvw] direction or (hkl) plane normal. """ @@ -366,7 +366,7 @@ class Crystal(): def kinematics(self, - mode: str) -> Dict[str, List[np.ndarray]]: + mode: CrystalKinematics) -> Dict[str, List[np.ndarray]]: """ Return crystal kinematics systems. @@ -381,7 +381,7 @@ class Crystal(): Directions and planes of deformation mode families. """ - _kinematics = { + _kinematics: Dict[CrystalLattice, Dict[CrystalKinematics, List[np.ndarray]]] = { 'cF': { 'slip': [np.array([ [+0,+1,-1, +1,+1,+1], @@ -626,7 +626,7 @@ class Crystal(): def relation_operations(self, - model: str) -> Tuple[str, Rotation]: + model: str) -> Tuple[CrystalLattice, Rotation]: """ Crystallographic orientation relationships for phase transformations. @@ -658,7 +658,7 @@ class Crystal(): https://doi.org/10.1016/j.actamat.2004.11.021 """ - _orientation_relationships = { + _orientation_relationships: Dict[str, Dict[CrystalLattice,np.ndarray]] = { 'KS': { 'cF' : np.array([ [[-1, 0, 1],[ 1, 1, 1]], diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 5a9b7b141..54ffb6728 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -1,10 +1,10 @@ import inspect import copy -from typing import Union, Callable, Dict, Any, Tuple +from typing import Union, Callable, List, Dict, Any, Tuple import numpy as np -from ._typehints import FloatSequence, IntSequence +from ._typehints import FloatSequence, IntSequence, CrystalFamily, CrystalLattice from . import Rotation from . import Crystal from . import util @@ -98,8 +98,8 @@ class Orientation(Rotation,Crystal): def __init__(self, rotation: Union[FloatSequence, Rotation] = np.array([1.,0.,0.,0.]), *, - family: str = None, - lattice: str = None, + family: CrystalFamily = None, + lattice: CrystalLattice = None, a: float = None, b: float = None, c: float = None, alpha: float = None, beta: float = None, gamma: float = None, degrees: bool = False): @@ -787,7 +787,7 @@ class Orientation(Rotation,Crystal): @property def symmetry_operations(self) -> Rotation: """Symmetry operations as Rotations.""" - _symmetry_operations = { + _symmetry_operations: Dict[CrystalFamily, List] = { 'cubic': [ [ 1.0, 0.0, 0.0, 0.0 ], [ 0.0, 1.0, 0.0, 0.0 ], diff --git a/python/damask/_typehints.py b/python/damask/_typehints.py index 0b4a56a69..674f54721 100644 --- a/python/damask/_typehints.py +++ b/python/damask/_typehints.py @@ -1,6 +1,6 @@ """Functionality for typehints.""" -from typing import Sequence, Union, TextIO +from typing import Sequence, Union, Literal, TextIO from pathlib import Path import numpy as np @@ -10,5 +10,9 @@ FloatSequence = Union[np.ndarray,Sequence[float]] IntSequence = Union[np.ndarray,Sequence[int]] FileHandle = Union[TextIO, str, Path] NumpyRngSeed = Union[int, IntSequence, np.random.SeedSequence, np.random.Generator] +CrystalFamily = Union[None,Literal['triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic']] +CrystalLattice = Union[None,Literal['aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF']] +CrystalKinematics = Literal['slip', 'twin'] + # BitGenerator does not exists in older numpy versions #NumpyRngSeed = Union[int, IntSequence, np.random.SeedSequence, np.random.BitGenerator, np.random.Generator] From 2907facfd34c4ad79b3f89fb7609c536c892d63a Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 13 Feb 2022 01:24:02 +0100 Subject: [PATCH 28/30] polishing classes should return 'MyType' for inheritance without hassle --- python/damask/_crystal.py | 2 +- python/damask/_orientation.py | 39 ++++++++++++------------- python/damask/_rotation.py | 55 +++++++++++++++++------------------ python/damask/_typehints.py | 3 +- 4 files changed, 47 insertions(+), 52 deletions(-) diff --git a/python/damask/_crystal.py b/python/damask/_crystal.py index 6da13f679..077d2e13a 100644 --- a/python/damask/_crystal.py +++ b/python/damask/_crystal.py @@ -31,7 +31,7 @@ lattice_symmetries: Dict[CrystalLattice, CrystalFamily] = { class Crystal(): """Crystal lattice.""" - def __init__(self,*, + def __init__(self, *, family: CrystalFamily = None, lattice: CrystalLattice = None, a: float = None, b: float = None, c: float = None, diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 54ffb6728..16df11984 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -1,6 +1,6 @@ import inspect import copy -from typing import Union, Callable, List, Dict, Any, Tuple +from typing import Union, Callable, List, Dict, Any, Tuple, TypeVar import numpy as np @@ -11,7 +11,6 @@ from . import util from . import tensor - _parameter_doc = \ """ family : {'triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic'}, optional. @@ -36,6 +35,7 @@ _parameter_doc = \ """ +MyType = TypeVar('MyType', bound='Orientation') class Orientation(Rotation,Crystal): """ @@ -124,8 +124,8 @@ class Orientation(Rotation,Crystal): return '\n'.join([Crystal.__repr__(self), Rotation.__repr__(self)]) - def __copy__(self, - rotation: Union[FloatSequence, Rotation] = None) -> 'Orientation': + def __copy__(self: MyType, + rotation: Union[FloatSequence, Rotation] = None) -> MyType: """Create deep copy.""" dup = copy.deepcopy(self) if rotation is not None: @@ -189,7 +189,7 @@ class Orientation(Rotation,Crystal): Returns ------- - mask : numpy.ndarray of bool + mask : numpy.ndarray of bool, shape (self.shape) Mask indicating where corresponding orientations are close. """ @@ -230,8 +230,8 @@ class Orientation(Rotation,Crystal): return bool(np.all(self.isclose(other,rtol,atol,equal_nan))) - def __mul__(self, - other: Union[Rotation, 'Orientation']) -> 'Orientation': + def __mul__(self: MyType, + other: Union[Rotation, 'Orientation']) -> MyType: """ Compose this orientation with other. @@ -246,8 +246,8 @@ class Orientation(Rotation,Crystal): Compound rotation self*other, i.e. first other then self rotation. """ - if isinstance(other,Orientation) or isinstance(other,Rotation): - return self.copy(rotation=Rotation.__mul__(self,Rotation(other.quaternion))) + if isinstance(other, (Orientation,Rotation)): + return self.copy(Rotation(self.quaternion)*Rotation(other.quaternion)) else: raise TypeError('use "O@b", i.e. matmul, to apply Orientation "O" to object "b"') @@ -382,11 +382,11 @@ class Orientation(Rotation,Crystal): x = o.to_frame(uvw=uvw) z = o.to_frame(hkl=hkl) om = np.stack([x,np.cross(z,x),z],axis=-2) - return o.copy(rotation=Rotation.from_matrix(tensor.transpose(om/np.linalg.norm(om,axis=-1,keepdims=True)))) + return o.copy(Rotation.from_matrix(tensor.transpose(om/np.linalg.norm(om,axis=-1,keepdims=True)))) @property - def equivalent(self) -> 'Orientation': + def equivalent(self: MyType) -> MyType: """ Orientations that are symmetrically equivalent. @@ -396,11 +396,11 @@ class Orientation(Rotation,Crystal): """ sym_ops = self.symmetry_operations o = sym_ops.broadcast_to(sym_ops.shape+self.shape,mode='right') - return self.copy(rotation=o*Rotation(self.quaternion).broadcast_to(o.shape,mode='left')) + return self.copy(o*Rotation(self.quaternion).broadcast_to(o.shape,mode='left')) @property - def reduced(self) -> 'Orientation': + def reduced(self: MyType) -> MyType: """Select symmetrically equivalent orientation that falls into fundamental zone according to symmetry.""" eq = self.equivalent ok = eq.in_FZ @@ -616,11 +616,8 @@ class Orientation(Rotation,Crystal): np.argmin(m,axis=0)[np.newaxis,...,np.newaxis], axis=0), axis=0)) - return ( - (self.copy(rotation=Rotation(r).average(weights)), - self.copy(rotation=Rotation(r))) - if return_cloud else - self.copy(rotation=Rotation(r).average(weights)) + return ((self.copy(Rotation(r).average(weights)),self.copy(Rotation(r))) if return_cloud else + self.copy(Rotation(r).average(weights)) ) @@ -930,7 +927,7 @@ class Orientation(Rotation,Crystal): if active == '*': active = [len(a) for a in kinematics['direction']] if not active: - raise RuntimeError # ToDo + raise ValueError('Schmid matrix not defined') 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), @@ -941,8 +938,8 @@ class Orientation(Rotation,Crystal): @ np.broadcast_to(P.reshape(util.shapeshifter(P.shape,shape)),shape) - def related(self, - model: str) -> 'Orientation': + def related(self: MyType, + model: str) -> MyType: """ Orientations derived from the given relationship. diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 273cf8e92..b11147482 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -1,14 +1,13 @@ import copy +from typing import Union, Sequence, Tuple, Literal, List, TypeVar import numpy as np +from ._typehints import FloatSequence, IntSequence, NumpyRngSeed from . import tensor from . import util from . import grid_filters -from typing import Union, Sequence, Tuple, Literal, List, TypeVar -from ._typehints import FloatSequence, IntSequence, NumpyRngSeed - _P = -1 # parameters for conversion from/to cubochoric @@ -109,7 +108,7 @@ class Rotation: 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]) + self.copy(self.quaternion[item+(slice(None),)] if isinstance(item,tuple) else self.quaternion[item]) def __eq__(self, @@ -162,7 +161,7 @@ class Rotation: Returns ------- - mask : numpy.ndarray of bool + mask : numpy.ndarray of bool, shape (self.shape) Mask indicating where corresponding rotations are close. """ @@ -233,13 +232,13 @@ class Rotation: Parameters ---------- - exp : scalar + exp : float Exponent. """ phi = np.arccos(self.quaternion[...,0:1]) 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()) + return self.copy(Rotation(np.block([np.cos(exp*phi),np.sin(exp*phi)*p]))._standardize()) def __ipow__(self: MyType, exp: Union[float, int]) -> MyType: @@ -248,7 +247,7 @@ class Rotation: Parameters ---------- - exp : scalar + exp : float Exponent. """ @@ -278,7 +277,7 @@ class Rotation: 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() #type: ignore + return self.copy(Rotation(np.block([q,p]))._standardize()) else: raise TypeError('Use "R@b", i.e. matmul, to apply rotation "R" to object "b"') @@ -391,8 +390,8 @@ class Rotation: other : (list of) damask.Rotation """ - return self.copy(rotation=np.vstack(tuple(map(lambda x:x.quaternion, - [self]+other if isinstance(other,list) else [self,other])))) + return self.copy(np.vstack(tuple(map(lambda x:x.quaternion, + [self]+other if isinstance(other,list) else [self,other])))) def flatten(self: MyType, @@ -415,7 +414,7 @@ class Rotation: Rotation flattened to single dimension. """ - return self.copy(rotation=self.quaternion.reshape((-1,4),order=order)) + return self.copy(self.quaternion.reshape((-1,4),order=order)) def reshape(self: MyType, @@ -443,7 +442,7 @@ class Rotation: """ if isinstance(shape,(int,np.integer)): shape = (shape,) - return self.copy(rotation=self.quaternion.reshape(tuple(shape)+(4,),order=order)) + return self.copy(self.quaternion.reshape(tuple(shape)+(4,),order=order)) def broadcast_to(self: MyType, @@ -467,18 +466,18 @@ class Rotation: """ if isinstance(shape,(int,np.integer)): shape = (shape,) - return self.copy(rotation=np.broadcast_to(self.quaternion.reshape(util.shapeshifter(self.shape,shape,mode)+(4,)), + return self.copy(np.broadcast_to(self.quaternion.reshape(util.shapeshifter(self.shape,shape,mode)+(4,)), shape+(4,))) - def average(self, - weights: FloatSequence = None) -> 'Rotation': + def average(self: MyType, + weights: FloatSequence = None) -> MyType: """ Average along last array dimension. Parameters ---------- - weights : numpy.ndarray, optional + weights : numpy.ndarray, shape (self.shape), optional Relative weight of each rotation. Returns @@ -501,13 +500,13 @@ class Rotation: eig, vec = np.linalg.eig(np.sum(_M(self.quaternion) * weights_[...,np.newaxis,np.newaxis],axis=-3) \ /np.sum( weights_[...,np.newaxis,np.newaxis],axis=-3)) - return Rotation.from_quaternion(np.real( - np.squeeze( - np.take_along_axis(vec, - eig.argmax(axis=-1)[...,np.newaxis,np.newaxis], - axis=-1), - axis=-1)), - accept_homomorph = True) + return self.copy(Rotation.from_quaternion(np.real( + np.squeeze( + np.take_along_axis(vec, + eig.argmax(axis=-1)[...,np.newaxis,np.newaxis], + axis=-1), + axis=-1)), + accept_homomorph = True)) def misorientation(self: MyType, @@ -730,7 +729,7 @@ class Rotation: Sign convention. Defaults to -1. """ - qu: np.ndarray = np.array(q,dtype=float) + qu = np.array(q,dtype=float) if qu.shape[:-2:-1] != (4,): raise ValueError('Invalid shape.') if abs(P) != 1: @@ -996,7 +995,7 @@ class Rotation: Defaults to None, i.e. unpredictable entropy will be pulled from the OS. """ - rng: np.random.Generator = np.random.default_rng(rng_seed) + rng = np.random.default_rng(rng_seed) r = rng.random(3 if shape is None else tuple(shape)+(3,) if hasattr(shape, '__iter__') else (shape,3)) #type: ignore A = np.sqrt(r[...,2]) @@ -1131,8 +1130,8 @@ class Rotation: """ rng = np.random.default_rng(rng_seed) - sigma_: np.ndarray; alpha_: np.ndarray; beta_: np.ndarray - sigma_,alpha_,beta_ = (np.radians(coordinate) for coordinate in (sigma,alpha,beta)) if degrees else (sigma,alpha,beta) #type: ignore + sigma_,alpha_,beta_ = (np.radians(coordinate) for coordinate in (sigma,alpha,beta)) if degrees else \ + map(np.array, (sigma,alpha,beta)) 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])]) diff --git a/python/damask/_typehints.py b/python/damask/_typehints.py index 674f54721..5fcf39a41 100644 --- a/python/damask/_typehints.py +++ b/python/damask/_typehints.py @@ -9,10 +9,9 @@ import numpy as np FloatSequence = Union[np.ndarray,Sequence[float]] IntSequence = Union[np.ndarray,Sequence[int]] FileHandle = Union[TextIO, str, Path] -NumpyRngSeed = Union[int, IntSequence, np.random.SeedSequence, np.random.Generator] CrystalFamily = Union[None,Literal['triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic']] CrystalLattice = Union[None,Literal['aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF']] CrystalKinematics = Literal['slip', 'twin'] - +NumpyRngSeed = Union[int, IntSequence, np.random.SeedSequence, np.random.Generator] # BitGenerator does not exists in older numpy versions #NumpyRngSeed = Union[int, IntSequence, np.random.SeedSequence, np.random.BitGenerator, np.random.Generator] From b1922c9fc03c295446b320281c44da0eda212225 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 13 Feb 2022 10:41:10 +0100 Subject: [PATCH 29/30] return most specific type --- python/damask/_orientation.py | 8 ++++---- python/damask/util.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 16df11984..c3afd0bed 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -558,8 +558,8 @@ class Orientation(Rotation,Crystal): 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') #type: ignore - o_ = o.reshape((1,o.shape[0])+other.shape).broadcast_to((s.shape[0],o.shape[0])+blend,mode='right') #type: ignore + s_ = s.reshape((s.shape[0],1)+ self.shape).broadcast_to((s.shape[0],o.shape[0])+blend,mode='right') + o_ = o.reshape((1,o.shape[0])+other.shape).broadcast_to((s.shape[0],o.shape[0])+blend,mode='right') r_ = s_.misorientation(o_) _r = ~r_ @@ -654,7 +654,7 @@ class Orientation(Rotation,Crystal): raise ValueError('input is not a field of three-dimensional vectors') eq = self.equivalent blend = util.shapeblender(eq.shape,vector_.shape[:-1]) - poles = eq.broadcast_to(blend,mode='right') @ np.broadcast_to(vector_,blend+(3,)) #type: ignore + poles = eq.broadcast_to(blend,mode='right') @ np.broadcast_to(vector_,blend+(3,)) ok = self.in_SST(poles,proper=proper) ok &= np.cumsum(ok,axis=0) == 1 loc = np.where(ok) @@ -884,7 +884,7 @@ class Orientation(Rotation,Crystal): blend += sym_ops.shape v = sym_ops.broadcast_to(shape) \ @ np.broadcast_to(v.reshape(util.shapeshifter(v.shape,shape+(3,))),shape+(3,)) - return ~(self.broadcast_to(blend))@ np.broadcast_to(v,blend+(3,)) #type: ignore + return ~(self.broadcast_to(blend))@ np.broadcast_to(v,blend+(3,)) def Schmid(self, *, diff --git a/python/damask/util.py b/python/damask/util.py index cc5b8906e..ab34df7c0 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -9,7 +9,7 @@ import re import fractions from collections import abc from functools import reduce -from typing import Union, Tuple, Iterable, Callable, Dict, List, Any, Literal, SupportsIndex +from typing import Union, Tuple, Iterable, Callable, Dict, List, Any, Literal from pathlib import Path import numpy as np @@ -427,7 +427,7 @@ def hybrid_IA(dist: np.ndarray, def shapeshifter(fro: Tuple[int, ...], to: Tuple[int, ...], mode: Literal['left','right'] = 'left', - keep_ones: bool = False) -> Tuple[SupportsIndex, ...]: + keep_ones: bool = False) -> Tuple[int, ...]: """ Return dimensions that reshape 'fro' to become broadcastable to 'to'. @@ -490,7 +490,7 @@ def shapeshifter(fro: Tuple[int, ...], def shapeblender(a: Tuple[int, ...], - b: Tuple[int, ...]) -> Tuple[SupportsIndex, ...]: + b: Tuple[int, ...]) -> Tuple[int, ...]: """ Return a shape that overlaps the rightmost entries of 'a' with the leftmost of 'b'. From 35caed305fce4f0c049a01370ad29d0a8ae8d0ca Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 15 Feb 2022 22:08:12 +0100 Subject: [PATCH 30/30] consistently define allclose for own type only --- python/damask/_orientation.py | 10 ++++------ python/damask/_rotation.py | 8 ++++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index c3afd0bed..c7fbbbb85 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -168,8 +168,8 @@ class Orientation(Rotation,Crystal): return np.logical_not(self==other) if isinstance(other, Orientation) else NotImplemented - def isclose(self, - other: object, + def isclose(self: MyType, + other: MyType, rtol: float = 1e-5, atol: float = 1e-8, equal_nan: bool = True) -> bool: @@ -193,8 +193,6 @@ 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 @@ -202,8 +200,8 @@ class Orientation(Rotation,Crystal): - def allclose(self, - other: object, + def allclose(self: MyType, + other: MyType, rtol: float = 1e-5, atol: float = 1e-8, equal_nan: bool = True) -> bool: diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index b11147482..9c6bde2dc 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -140,8 +140,8 @@ class Rotation: """ return np.logical_not(self==other) if isinstance(other, Rotation) else NotImplemented - def isclose(self, - other: 'Rotation', + def isclose(self: MyType, + other: MyType, rtol: float = 1e-5, atol: float = 1e-8, equal_nan: bool = True) -> bool: @@ -171,8 +171,8 @@ class Rotation: np.all(np.isclose(s,-1.0*o,rtol,atol,equal_nan),axis=-1)) - def allclose(self, - other: 'Rotation', + def allclose(self: MyType, + other: MyType, rtol: float = 1e-5, atol: float = 1e-8, equal_nan: bool = True) -> Union[np.bool_, bool]: