From 2907facfd34c4ad79b3f89fb7609c536c892d63a Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 13 Feb 2022 01:24:02 +0100 Subject: [PATCH] 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]