From 545a085c9333b076c3c9fc872f0f8e2fd345bd63 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 27 May 2021 18:41:42 +0200 Subject: [PATCH 1/4] no need to hide, just add suffix to avoid clash with string argument --- python/damask/__init__.py | 1 + python/damask/_orientation.py | 30 +++++++++++------------ python/damask/{_lattice.py => lattice.py} | 0 python/tests/test_Orientation.py | 4 +-- 4 files changed, 18 insertions(+), 17 deletions(-) rename python/damask/{_lattice.py => lattice.py} (100%) diff --git a/python/damask/__init__.py b/python/damask/__init__.py index 060c5e295..ad46d454f 100644 --- a/python/damask/__init__.py +++ b/python/damask/__init__.py @@ -14,6 +14,7 @@ from . import tensor # noqa from . import mechanics # noqa from . import solver # noqa from . import grid_filters # noqa +from . import lattice # noqa #Modules that contain only one class (of the same name), are prefixed by a '_'. #For example, '_colormap' containsa class called 'Colormap' which is imported as 'damask.Colormap'. from ._rotation import Rotation # noqa diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 919d9e518..a57ac5d84 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -5,16 +5,16 @@ import numpy as np from . import Rotation from . import util from . import tensor -from . import _lattice +from . import lattice as lattice_ -_crystal_families = ['triclinic', - 'monoclinic', - 'orthorhombic', - 'tetragonal', - 'hexagonal', - 'cubic'] +crystal_families = ['triclinic', + 'monoclinic', + 'orthorhombic', + 'tetragonal', + 'hexagonal', + 'cubic'] -_lattice_symmetries = { +lattice_symmetries = { 'aP': 'triclinic', 'mP': 'monoclinic', @@ -136,8 +136,8 @@ class Orientation(Rotation): self.kinematics = None - if lattice in _lattice_symmetries: - self.family = _lattice_symmetries[lattice] + if lattice in lattice_symmetries: + self.family = lattice_symmetries[lattice] self.lattice = lattice self.a = 1 if a is None else a @@ -177,15 +177,15 @@ class Orientation(Rotation): > np.sum(np.roll([self.alpha,self.beta,self.gamma],r)[1:]) for r in range(3)]): raise ValueError ('Each lattice angle must be less than sum of others') - if self.lattice in _lattice.kinematics: - master = _lattice.kinematics[self.lattice] + if self.lattice in lattice_.kinematics: + master = lattice_.kinematics[self.lattice] self.kinematics = {} for m in master: self.kinematics[m] = {'direction':master[m][:,0:3],'plane':master[m][:,3:6]} \ if master[m].shape[-1] == 6 else \ {'direction':self.Bravais_to_Miller(uvtw=master[m][:,0:4]), 'plane': self.Bravais_to_Miller(hkil=master[m][:,4:8])} - elif lattice in _crystal_families: + elif lattice in crystal_families: self.family = lattice self.lattice = None @@ -669,9 +669,9 @@ class Orientation(Rotation): https://doi.org/10.1016/j.actamat.2004.11.021 """ - if model not in _lattice.relations: + if model not in lattice_.relations: raise KeyError(f'Orientation relationship "{model}" is unknown') - r = _lattice.relations[model] + r = lattice_.relations[model] if self.lattice not in r: raise KeyError(f'Relationship "{model}" not supported for lattice "{self.lattice}"') diff --git a/python/damask/_lattice.py b/python/damask/lattice.py similarity index 100% rename from python/damask/_lattice.py rename to python/damask/lattice.py diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index 3c4e5bbff..184976749 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -7,8 +7,8 @@ from damask import Orientation from damask import Table from damask import util from damask import grid_filters -from damask import _lattice as lattice -from damask._orientation import _crystal_families as crystal_families +from damask import lattice +from damask._orientation import crystal_families @pytest.fixture From f7e1aad147a8ecaff3913a8fd15d87baceab4a14 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 27 May 2021 18:44:20 +0200 Subject: [PATCH 2/4] single source of truth --- python/damask/_orientation.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index a57ac5d84..864ed56a3 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -7,12 +7,6 @@ from . import util from . import tensor from . import lattice as lattice_ -crystal_families = ['triclinic', - 'monoclinic', - 'orthorhombic', - 'tetragonal', - 'hexagonal', - 'cubic'] lattice_symmetries = { 'aP': 'triclinic', @@ -185,7 +179,7 @@ class Orientation(Rotation): if master[m].shape[-1] == 6 else \ {'direction':self.Bravais_to_Miller(uvtw=master[m][:,0:4]), 'plane': self.Bravais_to_Miller(hkil=master[m][:,4:8])} - elif lattice in crystal_families: + elif lattice in set(lattice_symmetries.values()): self.family = lattice self.lattice = None From bfc3fe410f565f4e69f832fdef582f8b6956d64c Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 27 May 2021 19:10:23 +0200 Subject: [PATCH 3/4] no direct connection to Orientation object --- python/damask/_orientation.py | 70 +++----------------------------- python/damask/lattice.py | 57 ++++++++++++++++++++++++++ python/tests/test_Orientation.py | 32 ++------------- python/tests/test_lattice.py | 34 ++++++++++++++++ 4 files changed, 100 insertions(+), 93 deletions(-) create mode 100644 python/tests/test_lattice.py diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 864ed56a3..0ea886390 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -177,8 +177,8 @@ class Orientation(Rotation): for m in master: self.kinematics[m] = {'direction':master[m][:,0:3],'plane':master[m][:,3:6]} \ if master[m].shape[-1] == 6 else \ - {'direction':self.Bravais_to_Miller(uvtw=master[m][:,0:4]), - 'plane': self.Bravais_to_Miller(hkil=master[m][:,4:8])} + {'direction':lattice_.Bravais_to_Miller(uvtw=master[m][:,0:4]), + 'plane': lattice_.Bravais_to_Miller(hkil=master[m][:,4:8])} elif lattice in set(lattice_symmetries.values()): self.family = lattice self.lattice = None @@ -676,10 +676,10 @@ class Orientation(Rotation): o = r[ol] p_,_p = np.zeros(m.shape[:-1]+(3,)),np.zeros(o.shape[:-1]+(3,)) - p_[...,0,:] = m[...,0,:] if m.shape[-1] == 3 else self.Bravais_to_Miller(uvtw=m[...,0,0:4]) - p_[...,1,:] = m[...,1,:] if m.shape[-1] == 3 else self.Bravais_to_Miller(hkil=m[...,1,0:4]) - _p[...,0,:] = o[...,0,:] if o.shape[-1] == 3 else self.Bravais_to_Miller(uvtw=o[...,0,0:4]) - _p[...,1,:] = o[...,1,:] if o.shape[-1] == 3 else self.Bravais_to_Miller(hkil=o[...,1,0:4]) + p_[...,0,:] = m[...,0,:] if m.shape[-1] == 3 else lattice_.Bravais_to_Miller(uvtw=m[...,0,0:4]) + p_[...,1,:] = m[...,1,:] if m.shape[-1] == 3 else lattice_.Bravais_to_Miller(hkil=m[...,1,0:4]) + _p[...,0,:] = o[...,0,:] if o.shape[-1] == 3 else lattice_.Bravais_to_Miller(uvtw=o[...,0,0:4]) + _p[...,1,:] = o[...,1,:] if o.shape[-1] == 3 else lattice_.Bravais_to_Miller(hkil=o[...,1,0:4]) return (Rotation.from_parallel(p_,_p),ol) \ if return_lattice else \ @@ -1158,64 +1158,6 @@ class Orientation(Rotation): ) - @classmethod - def Bravais_to_Miller(cls,*,uvtw=None,hkil=None): - """ - Transform 4 Miller–Bravais indices to 3 Miller indices of crystal direction [uvw] or plane normal (hkl). - - Parameters - ---------- - uvtw|hkil : numpy.ndarray of shape (...,4) - Miller–Bravais indices of crystallographic direction [uvtw] or plane normal (hkil). - - Returns - ------- - uvw|hkl : numpy.ndarray of shape (...,3) - Miller indices of [uvw] direction or (hkl) plane normal. - - """ - if (uvtw is not None) ^ (hkil is None): - raise KeyError('Specify either "uvtw" or "hkil"') - axis,basis = (np.array(uvtw),np.array([[1,0,-1,0], - [0,1,-1,0], - [0,0, 0,1]])) \ - if hkil is None else \ - (np.array(hkil),np.array([[1,0,0,0], - [0,1,0,0], - [0,0,0,1]])) - return np.einsum('il,...l',basis,axis) - - - @classmethod - def Miller_to_Bravais(cls,*,uvw=None,hkl=None): - """ - Transform 3 Miller indices to 4 Miller–Bravais indices of crystal direction [uvtw] or plane normal (hkil). - - Parameters - ---------- - uvw|hkl : numpy.ndarray of shape (...,3) - Miller indices of crystallographic direction [uvw] or plane normal (hkl). - - Returns - ------- - uvtw|hkil : numpy.ndarray of shape (...,4) - Miller–Bravais indices of [uvtw] direction or (hkil) plane normal. - - """ - if (uvw is not None) ^ (hkl is None): - raise KeyError('Specify either "uvw" or "hkl"') - axis,basis = (np.array(uvw),np.array([[ 2,-1, 0], - [-1, 2, 0], - [-1,-1, 0], - [ 0, 0, 3]])/3) \ - if hkl is None else \ - (np.array(hkl),np.array([[ 1, 0, 0], - [ 0, 1, 0], - [-1,-1, 0], - [ 0, 0, 1]])) - return np.einsum('il,...l',basis,axis) - - def to_lattice(self,*,direction=None,plane=None): """ Calculate lattice vector corresponding to crystal frame direction or plane normal. diff --git a/python/damask/lattice.py b/python/damask/lattice.py index 5b4621f3d..e335fc85b 100644 --- a/python/damask/lattice.py +++ b/python/damask/lattice.py @@ -1,5 +1,62 @@ import numpy as _np + +def Bravais_to_Miller(*,uvtw=None,hkil=None): + """ + Transform 4 Miller–Bravais indices to 3 Miller indices of crystal direction [uvw] or plane normal (hkl). + + Parameters + ---------- + uvtw|hkil : numpy.ndarray of shape (...,4) + Miller–Bravais indices of crystallographic direction [uvtw] or plane normal (hkil). + + Returns + ------- + uvw|hkl : numpy.ndarray of shape (...,3) + Miller indices of [uvw] direction or (hkl) plane normal. + + """ + if (uvtw is not None) ^ (hkil is None): + raise KeyError('Specify either "uvtw" or "hkil"') + axis,basis = (_np.array(uvtw),_np.array([[1,0,-1,0], + [0,1,-1,0], + [0,0, 0,1]])) \ + if hkil is None else \ + (_np.array(hkil),_np.array([[1,0,0,0], + [0,1,0,0], + [0,0,0,1]])) + return _np.einsum('il,...l',basis,axis) + + +def Miller_to_Bravais(*,uvw=None,hkl=None): + """ + Transform 3 Miller indices to 4 Miller–Bravais indices of crystal direction [uvtw] or plane normal (hkil). + + Parameters + ---------- + uvw|hkl : numpy.ndarray of shape (...,3) + Miller indices of crystallographic direction [uvw] or plane normal (hkl). + + Returns + ------- + uvtw|hkil : numpy.ndarray of shape (...,4) + Miller–Bravais indices of [uvtw] direction or (hkil) plane normal. + + """ + if (uvw is not None) ^ (hkl is None): + raise KeyError('Specify either "uvw" or "hkl"') + axis,basis = (_np.array(uvw),_np.array([[ 2,-1, 0], + [-1, 2, 0], + [-1,-1, 0], + [ 0, 0, 3]])/3) \ + if hkl is None else \ + (_np.array(hkl),_np.array([[ 1, 0, 0], + [ 0, 1, 0], + [-1,-1, 0], + [ 0, 0, 1]])) + return _np.einsum('il,...l',basis,axis) + + kinematics = { 'cF': { 'slip' : _np.array([ diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index 184976749..cf4d477ee 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -8,7 +8,9 @@ from damask import Table from damask import util from damask import grid_filters from damask import lattice -from damask._orientation import crystal_families +from damask import _orientation + +crystal_families = set(_orientation.lattice_symmetries.values()) @pytest.fixture @@ -358,14 +360,6 @@ class TestOrientation: with pytest.raises(KeyError): Orientation(lattice=None).basis_reciprocal # noqa - def test_double_Bravais_to_Miller(self): - with pytest.raises(KeyError): - Orientation.Bravais_to_Miller(uvtw=np.ones(4),hkil=np.ones(4)) # noqa - - def test_double_Miller_to_Bravais(self): - with pytest.raises(KeyError): - Orientation.Miller_to_Bravais(uvw=np.ones(4),hkl=np.ones(4)) # noqa - def test_double_to_lattice(self): with pytest.raises(KeyError): Orientation().to_lattice(direction=np.ones(3),plane=np.ones(3)) # noqa @@ -490,26 +484,6 @@ class TestOrientation: assert np.allclose(vector, L.to_frame(**{keyFrame:L.to_lattice(**{keyLattice:vector})})) - @pytest.mark.parametrize('vector',np.array([ - [1,0,0], - [1,1,0], - [1,1,1], - [1,0,-2], - ])) - @pytest.mark.parametrize('kw_Miller,kw_Bravais',[('uvw','uvtw'),('hkl','hkil')]) - def test_Miller_Bravais_Miller(self,vector,kw_Miller,kw_Bravais): - assert np.all(vector == Orientation.Bravais_to_Miller(**{kw_Bravais:Orientation.Miller_to_Bravais(**{kw_Miller:vector})})) - - @pytest.mark.parametrize('vector',np.array([ - [1,0,-1,2], - [1,-1,0,3], - [1,1,-2,-3], - [0,0,0,1], - ])) - @pytest.mark.parametrize('kw_Miller,kw_Bravais',[('uvw','uvtw'),('hkl','hkil')]) - def test_Bravais_Miller_Bravais(self,vector,kw_Miller,kw_Bravais): - assert np.all(vector == Orientation.Miller_to_Bravais(**{kw_Miller:Orientation.Bravais_to_Miller(**{kw_Bravais:vector})})) - @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', [ ('aP',0.5,2.0,3.0,0.8,0.5,1.2), diff --git a/python/tests/test_lattice.py b/python/tests/test_lattice.py new file mode 100644 index 000000000..4060025a1 --- /dev/null +++ b/python/tests/test_lattice.py @@ -0,0 +1,34 @@ +import pytest +import numpy as np + +from damask import lattice + +class TestLattice: + + def test_double_Bravais_to_Miller(self): + with pytest.raises(KeyError): + lattice.Bravais_to_Miller(uvtw=np.ones(4),hkil=np.ones(4)) # noqa + + def test_double_Miller_to_Bravais(self): + with pytest.raises(KeyError): + lattice.Miller_to_Bravais(uvw=np.ones(4),hkl=np.ones(4)) # noqa + + @pytest.mark.parametrize('vector',np.array([ + [1,0,0], + [1,1,0], + [1,1,1], + [1,0,-2], + ])) + @pytest.mark.parametrize('kw_Miller,kw_Bravais',[('uvw','uvtw'),('hkl','hkil')]) + def test_Miller_Bravais_Miller(self,vector,kw_Miller,kw_Bravais): + assert np.all(vector == lattice.Bravais_to_Miller(**{kw_Bravais:lattice.Miller_to_Bravais(**{kw_Miller:vector})})) + + @pytest.mark.parametrize('vector',np.array([ + [1,0,-1,2], + [1,-1,0,3], + [1,1,-2,-3], + [0,0,0,1], + ])) + @pytest.mark.parametrize('kw_Miller,kw_Bravais',[('uvw','uvtw'),('hkl','hkil')]) + def test_Bravais_Miller_Bravais(self,vector,kw_Miller,kw_Bravais): + assert np.all(vector == lattice.Miller_to_Bravais(**{kw_Miller:lattice.Bravais_to_Miller(**{kw_Bravais:vector})})) From 89fc2f05cfd28204958c572532c607631159ac85 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 27 May 2021 20:12:20 +0200 Subject: [PATCH 4/4] no need to attach static data if needed, a 'kinematics' function that provides slip direction and plane normal rotated according to the orientation would be helpful --- python/damask/_orientation.py | 44 +++++++++++++++----------------- python/tests/test_Orientation.py | 16 ++++++------ 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 0ea886390..72bc8a4d8 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -128,8 +128,6 @@ class Orientation(Rotation): """ Rotation.__init__(self) if rotation is None else Rotation.__init__(self,rotation=rotation) - self.kinematics = None - if lattice in lattice_symmetries: self.family = lattice_symmetries[lattice] self.lattice = lattice @@ -171,14 +169,6 @@ class Orientation(Rotation): > np.sum(np.roll([self.alpha,self.beta,self.gamma],r)[1:]) for r in range(3)]): raise ValueError ('Each lattice angle must be less than sum of others') - if self.lattice in lattice_.kinematics: - master = lattice_.kinematics[self.lattice] - self.kinematics = {} - for m in master: - self.kinematics[m] = {'direction':master[m][:,0:3],'plane':master[m][:,3:6]} \ - if master[m].shape[-1] == 6 else \ - {'direction':lattice_.Bravais_to_Miller(uvtw=master[m][:,0:4]), - 'plane': lattice_.Bravais_to_Miller(hkil=master[m][:,4:8])} elif lattice in set(lattice_symmetries.values()): self.family = lattice self.lattice = None @@ -309,7 +299,7 @@ class Orientation(Rotation): if isinstance(other,Orientation) or isinstance(other,Rotation): return self.copy(rotation=Rotation.__mul__(self,Rotation(other.quaternion))) else: - raise TypeError('Use "O@b", i.e. matmul, to apply Orientation "O" to object "b"') + raise TypeError('use "O@b", i.e. matmul, to apply Orientation "O" to object "b"') @staticmethod @@ -514,7 +504,7 @@ class Orientation(Rotation): [ 1.0,0.0,0.0,0.0 ], ] else: - raise KeyError(f'Crystal family "{self.family}" is unknown') + raise KeyError(f'unknown crystal family "{self.family}"') return Rotation.from_quaternion(sym_quats,accept_homomorph=True) @@ -664,11 +654,11 @@ class Orientation(Rotation): """ if model not in lattice_.relations: - raise KeyError(f'Orientation relationship "{model}" is unknown') + raise KeyError(f'unknown orientation relationship "{model}"') r = lattice_.relations[model] if self.lattice not in r: - raise KeyError(f'Relationship "{model}" not supported for lattice "{self.lattice}"') + raise KeyError(f'relationship "{model}" not supported for lattice "{self.lattice}"') sl = self.lattice ol = (set(r)-{sl}).pop() @@ -779,7 +769,7 @@ class Orientation(Rotation): """ if None in self.parameters: - raise KeyError('Missing crystal lattice parameters') + raise KeyError('missing crystal lattice parameters') return np.array([ [1,0,0], [np.cos(self.gamma),np.sin(self.gamma),0], @@ -835,7 +825,7 @@ class Orientation(Rotation): """ if not isinstance(vector,np.ndarray) or vector.shape[-1] != 3: - raise ValueError('Input is not a field of three-dimensional vectors.') + raise ValueError('input is not a field of three-dimensional vectors') if self.family == 'cubic': basis = {'improper':np.array([ [-1. , 0. , 1. ], @@ -938,7 +928,7 @@ class Orientation(Rotation): """ if np.array(vector).shape[-1] != 3: - raise ValueError('Input is not a field of three-dimensional vectors.') + raise ValueError('input is not a field of three-dimensional vectors') vector_ = self.to_SST(vector,proper) if in_SST else \ self @ np.broadcast_to(vector,self.shape+(3,)) @@ -1175,7 +1165,7 @@ class Orientation(Rotation): """ if (direction is not None) ^ (plane is None): - raise KeyError('Specify either "direction" or "plane"') + raise KeyError('specify either "direction" or "plane"') axis,basis = (np.array(direction),self.basis_reciprocal.T) \ if plane is None else \ (np.array(plane),self.basis_real.T) @@ -1200,7 +1190,7 @@ class Orientation(Rotation): """ if (uvw is not None) ^ (hkl is None): - raise KeyError('Specify either "uvw" or "hkl"') + raise KeyError('specify either "uvw" or "hkl"') axis,basis = (np.array(uvw),self.basis_real) \ if hkl is None else \ (np.array(hkl),self.basis_reciprocal) @@ -1238,8 +1228,8 @@ class Orientation(Rotation): Parameters ---------- - mode : str - Type of kinematics, i.e. 'slip' or 'twin'. + mode : {'slip', 'twin'} + Type of kinematics. Returns ------- @@ -1260,8 +1250,16 @@ class Orientation(Rotation): [ 0.000, 0.000, 0.000]]) """ - d = self.to_frame(uvw=self.kinematics[mode]['direction'],with_symmetry=False) - p = self.to_frame(hkl=self.kinematics[mode]['plane'] ,with_symmetry=False) + try: + master = lattice_.kinematics[self.lattice][mode] + kinematics = {'direction':master[:,0:3],'plane':master[:,3:6]} \ + if master.shape[-1] == 6 else \ + {'direction':lattice_.Bravais_to_Miller(uvtw=master[:,0:4]), + 'plane': lattice_.Bravais_to_Miller(hkil=master[:,4:8])} + except KeyError: + raise (f'"{mode}" not defined for lattice "{self.lattice}"') + d = self.to_frame(uvw=kinematics['direction'],with_symmetry=False) + p = self.to_frame(hkl=kinematics['plane'] ,with_symmetry=False) P = np.einsum('...i,...j',d/np.linalg.norm(d,axis=-1,keepdims=True), p/np.linalg.norm(p,axis=-1,keepdims=True)) diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index cf4d477ee..63caca34e 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -511,12 +511,12 @@ class TestOrientation: == o.shape + (o.symmetry_operations.shape if with_symmetry else ()) + vector.shape @pytest.mark.parametrize('lattice',['hP','cI','cF']) - def test_Schmid(self,update,ref_path,lattice): + @pytest.mark.parametrize('mode',['slip','twin']) + def test_Schmid(self,update,ref_path,lattice,mode): L = Orientation(lattice=lattice) - for mode in L.kinematics: - reference = ref_path/f'{lattice}_{mode}.txt' - P = L.Schmid(mode) - if update: - table = Table(P.reshape(-1,9),{'Schmid':(3,3,)}) - table.save(reference) - assert np.allclose(P,Table.load(reference).get('Schmid')) + reference = ref_path/f'{lattice}_{mode}.txt' + P = L.Schmid(mode) + if update: + table = Table(P.reshape(-1,9),{'Schmid':(3,3,)}) + table.save(reference) + assert np.allclose(P,Table.load(reference).get('Schmid'))