From 684016f3e3e2e4a1ac9c504b12e02cda9b09df07 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 1 Jun 2021 21:29:35 +0200 Subject: [PATCH 01/27] utility function without connection to an object --- python/damask/_orientation.py | 12 +++---- python/damask/util.py | 57 ++++++++++++++++++++++++++++++++ python/tests/test_Orientation.py | 1 + python/tests/test_lattice.py | 34 ------------------- python/tests/test_util.py | 30 +++++++++++++++++ 5 files changed, 94 insertions(+), 40 deletions(-) delete mode 100644 python/tests/test_lattice.py diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 72bc8a4d8..349079bb6 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -666,10 +666,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 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]) + p_[...,0,:] = m[...,0,:] if m.shape[-1] == 3 else util.Bravais_to_Miller(uvtw=m[...,0,0:4]) + p_[...,1,:] = m[...,1,:] if m.shape[-1] == 3 else util.Bravais_to_Miller(hkil=m[...,1,0:4]) + _p[...,0,:] = o[...,0,:] if o.shape[-1] == 3 else util.Bravais_to_Miller(uvtw=o[...,0,0:4]) + _p[...,1,:] = o[...,1,:] if o.shape[-1] == 3 else util.Bravais_to_Miller(hkil=o[...,1,0:4]) return (Rotation.from_parallel(p_,_p),ol) \ if return_lattice else \ @@ -1254,8 +1254,8 @@ class Orientation(Rotation): 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])} + {'direction':util.Bravais_to_Miller(uvtw=master[:,0:4]), + 'plane': util.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) diff --git a/python/damask/util.py b/python/damask/util.py index 529b63d20..7ad788bdf 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -27,6 +27,7 @@ __all__=[ 'execution_stamp', 'shapeshifter', 'shapeblender', 'extend_docstring', 'extended_docstring', + 'Bravais_to_Miller', 'Miller_to_Bravais', 'DREAM3D_base_group', 'DREAM3D_cell_data_group', 'dict_prune', 'dict_flatten' ] @@ -499,6 +500,62 @@ def DREAM3D_cell_data_group(fname): return cell_data_group +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) + + def dict_prune(d): """ Recursively remove empty dictionaries. diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index 63caca34e..39d511d68 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -484,6 +484,7 @@ class TestOrientation: assert np.allclose(vector, L.to_frame(**{keyFrame:L.to_lattice(**{keyLattice: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 deleted file mode 100644 index 4060025a1..000000000 --- a/python/tests/test_lattice.py +++ /dev/null @@ -1,34 +0,0 @@ -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})})) diff --git a/python/tests/test_util.py b/python/tests/test_util.py index 3ed3bf908..9af609ad0 100644 --- a/python/tests/test_util.py +++ b/python/tests/test_util.py @@ -158,3 +158,33 @@ class TestUtil: ({'A':{'B':{},'C':'D'}}, {'B':{},'C':'D'})]) def test_flatten(self,full,reduced): assert util.dict_flatten(full) == reduced + + + def test_double_Bravais_to_Miller(self): + with pytest.raises(KeyError): + util.Bravais_to_Miller(uvtw=np.ones(4),hkil=np.ones(4)) + + def test_double_Miller_to_Bravais(self): + with pytest.raises(KeyError): + util.Miller_to_Bravais(uvw=np.ones(4),hkl=np.ones(4)) + + + @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 == util.Bravais_to_Miller(**{kw_Bravais:util.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 == util.Miller_to_Bravais(**{kw_Miller:util.Bravais_to_Miller(**{kw_Bravais:vector})})) From e281d8384fc39e153c5fc2fb97b8a6b8eeb27b76 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 1 Jun 2021 21:51:28 +0200 Subject: [PATCH 02/27] distinguish 'family' and 'lattice' this requires the user to specify a keyword ('*' notation in __init__). Since all 'from_xxx' methods anyway require a keyword, it does not causes much more work (one explicit keyword in case of cube orientation when using __init__ directly). --- python/damask/_orientation.py | 52 +++++---- python/tests/test_Orientation.py | 192 ++++++++++++++++--------------- 2 files changed, 126 insertions(+), 118 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 349079bb6..f3919d1ff 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -30,10 +30,12 @@ lattice_symmetries = { } _parameter_doc = \ - """lattice : str - Either a crystal family out of {triclinic, monoclinic, orthorhombic, tetragonal, hexagonal, cubic} - or a Bravais lattice out of {aP, mP, mS, oP, oS, oI, oF, tP, tI, hP, cP, cI, cF}. - When specifying a Bravais lattice, additional lattice parameters might be required. + """ + family : {'triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic'} + Crystal family. Mutual exclusive with 'lattice' parameter. + lattice : {'aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF'}. + Bravais lattice in Pearson notation. + Mutual exclusive with 'family' parameter. a : float, optional Length of lattice parameter 'a'. b : float, optional @@ -110,7 +112,8 @@ class Orientation(Rotation): @util.extend_docstring(_parameter_doc) def __init__(self, - rotation = None, + 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, @@ -126,9 +129,16 @@ class Orientation(Rotation): Defaults to no rotation. """ - Rotation.__init__(self) if rotation is None else Rotation.__init__(self,rotation=rotation) + Rotation.__init__(self,rotation=rotation) - if lattice in lattice_symmetries: + if family in set(lattice_symmetries.values()) and lattice is None: + self.family = family + self.lattice = None + + self.a = self.b = self.c = None + self.alpha = self.beta = self.gamma = None + + elif lattice in lattice_symmetries: self.family = lattice_symmetries[lattice] self.lattice = lattice @@ -169,14 +179,8 @@ 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') - elif lattice in set(lattice_symmetries.values()): - self.family = lattice - self.lattice = None - - self.a = self.b = self.c = None - self.alpha = self.beta = self.gamma = None else: - raise KeyError(f'Lattice "{lattice}" is unknown') + raise KeyError(f'no valid family or lattice') def __repr__(self): @@ -188,16 +192,16 @@ class Orientation(Rotation): def __copy__(self,**kwargs): """Create deep copy.""" - return self.__class__(rotation=kwargs['rotation'] if 'rotation' in kwargs else self.quaternion, - lattice =kwargs['lattice'] if 'lattice' in kwargs else self.lattice - if self.lattice is not None else self.family, - a =kwargs['a'] if 'a' in kwargs else self.a, - b =kwargs['b'] if 'b' in kwargs else self.b, - c =kwargs['c'] if 'c' in kwargs else self.c, - alpha =kwargs['alpha'] if 'alpha' in kwargs else self.alpha, - beta =kwargs['beta'] if 'beta' in kwargs else self.beta, - gamma =kwargs['gamma'] if 'gamma' in kwargs else self.gamma, - degrees =kwargs['degrees'] if 'degrees' in kwargs else None, + return self.__class__(rotation= kwargs['rotation'] if 'rotation' in kwargs else self.quaternion, + family = kwargs['family'] if 'family' in kwargs else self.family, + lattice = kwargs['lattice'] if 'lattice' in kwargs else self.lattice, + a = kwargs['a'] if 'a' in kwargs else self.a, + b = kwargs['b'] if 'b' in kwargs else self.b, + c = kwargs['c'] if 'c' in kwargs else self.c, + alpha = kwargs['alpha'] if 'alpha' in kwargs else self.alpha, + beta = kwargs['beta'] if 'beta' in kwargs else self.beta, + gamma = kwargs['gamma'] if 'gamma' in kwargs else self.gamma, + degrees = kwargs['degrees'] if 'degrees' in kwargs else None, ) copy = __copy__ diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index 39d511d68..7fc20a3e6 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -25,38 +25,42 @@ def set_of_rodrigues(set_of_quaternions): class TestOrientation: - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('shape',[None,5,(4,6)]) - def test_equal(self,lattice,shape): + def test_equal(self,family,shape): R = Rotation.from_random(shape) - assert Orientation(R,lattice) == Orientation(R,lattice) if shape is None else \ - (Orientation(R,lattice) == Orientation(R,lattice)).all() + assert Orientation(R,family=family) == Orientation(R,family=family) if shape is None else \ + (Orientation(R,family=family) == Orientation(R,family=family)).all() - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('shape',[None,5,(4,6)]) - def test_unequal(self,lattice,shape): + def test_unequal(self,family,shape): R = Rotation.from_random(shape) - assert not ( Orientation(R,lattice) != Orientation(R,lattice) if shape is None else \ - (Orientation(R,lattice) != Orientation(R,lattice)).any()) + assert not ( Orientation(R,family=family) != Orientation(R,family=family) if shape is None else \ + (Orientation(R,family=family) != Orientation(R,family=family)).any()) - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('shape',[None,5,(4,6)]) - def test_close(self,lattice,shape): - R = Orientation.from_random(lattice=lattice,shape=shape) + def test_close(self,family,shape): + R = Orientation.from_random(family=family,shape=shape) assert R.isclose(R.reduced).all() and R.allclose(R.reduced) @pytest.mark.parametrize('a,b',[ - (dict(rotation=[1,0,0,0],lattice='triclinic'), - dict(rotation=[0.5,0.5,0.5,0.5],lattice='triclinic')), + (dict(rotation=[1,0,0,0],family='triclinic'), + dict(rotation=[0.5,0.5,0.5,0.5],family='triclinic')), - (dict(rotation=[1,0,0,0],lattice='cubic'), - dict(rotation=[1,0,0,0],lattice='hexagonal')), + (dict(rotation=[1,0,0,0],family='cubic'), + dict(rotation=[1,0,0,0],family='hexagonal')), + ]) + def test_unequal_family(self,a,b): + assert Orientation(**a) != Orientation(**b) + @pytest.mark.parametrize('a,b',[ (dict(rotation=[1,0,0,0],lattice='cF',a=1), dict(rotation=[1,0,0,0],lattice='cF',a=2)), ]) - def test_nonequal(self,a,b): + def test_unequal_lattice(self,a,b): assert Orientation(**a) != Orientation(**b) @pytest.mark.parametrize('kwargs',[ @@ -100,47 +104,47 @@ class TestOrientation: assert o != p def test_from_quaternion(self): - assert np.all(Orientation.from_quaternion(q=np.array([1,0,0,0]),lattice='triclinic').as_matrix() + assert np.all(Orientation.from_quaternion(q=np.array([1,0,0,0]),family='triclinic').as_matrix() == np.eye(3)) def test_from_Euler_angles(self): - assert np.all(Orientation.from_Euler_angles(phi=np.zeros(3),lattice='triclinic').as_matrix() + assert np.all(Orientation.from_Euler_angles(phi=np.zeros(3),family='triclinic').as_matrix() == np.eye(3)) def test_from_axis_angle(self): - assert np.all(Orientation.from_axis_angle(axis_angle=[1,0,0,0],lattice='triclinic').as_matrix() + assert np.all(Orientation.from_axis_angle(axis_angle=[1,0,0,0],family='triclinic').as_matrix() == np.eye(3)) def test_from_basis(self): - assert np.all(Orientation.from_basis(basis=np.eye(3),lattice='triclinic').as_matrix() + assert np.all(Orientation.from_basis(basis=np.eye(3),family='triclinic').as_matrix() == np.eye(3)) def test_from_matrix(self): - assert np.all(Orientation.from_matrix(R=np.eye(3),lattice='triclinic').as_matrix() + assert np.all(Orientation.from_matrix(R=np.eye(3),family='triclinic').as_matrix() == np.eye(3)) def test_from_Rodrigues_vector(self): - assert np.all(Orientation.from_Rodrigues_vector(rho=np.array([0,0,1,0]),lattice='triclinic').as_matrix() + assert np.all(Orientation.from_Rodrigues_vector(rho=np.array([0,0,1,0]),family='triclinic').as_matrix() == np.eye(3)) def test_from_homochoric(self): - assert np.all(Orientation.from_homochoric(h=np.zeros(3),lattice='triclinic').as_matrix() + assert np.all(Orientation.from_homochoric(h=np.zeros(3),family='triclinic').as_matrix() == np.eye(3)) def test_from_cubochoric(self): - assert np.all(Orientation.from_cubochoric(x=np.zeros(3),lattice='triclinic').as_matrix() + assert np.all(Orientation.from_cubochoric(x=np.zeros(3),family='triclinic').as_matrix() == np.eye(3)) def test_from_spherical_component(self): assert np.all(Orientation.from_spherical_component(center=Rotation(), - sigma=0.0,N=1,lattice='triclinic').as_matrix() + sigma=0.0,N=1,family='triclinic').as_matrix() == np.eye(3)) def test_from_fiber_component(self): r = Rotation.from_fiber_component(alpha=np.zeros(2),beta=np.zeros(2), sigma=0.0,N=1,rng_seed=0) assert np.all(Orientation.from_fiber_component(alpha=np.zeros(2),beta=np.zeros(2), - sigma=0.0,N=1,rng_seed=0,lattice='triclinic').quaternion + sigma=0.0,N=1,rng_seed=0,family='triclinic').quaternion == r.quaternion) @pytest.mark.parametrize('kwargs',[ @@ -185,26 +189,26 @@ class TestOrientation: with pytest.raises(ValueError): Orientation(lattice='aP',a=1,b=2,c=3,alpha=45,beta=45,gamma=90.0001,degrees=True) # noqa - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('angle',[10,20,30,40]) - def test_average(self,angle,lattice): - o = Orientation.from_axis_angle(lattice=lattice,axis_angle=[[0,0,1,10],[0,0,1,angle]],degrees=True) + def test_average(self,angle,family): + o = Orientation.from_axis_angle(family=family,axis_angle=[[0,0,1,10],[0,0,1,angle]],degrees=True) avg_angle = o.average().as_axis_angle(degrees=True,pair=True)[1] assert np.isclose(avg_angle,10+(angle-10)/2.) - @pytest.mark.parametrize('lattice',crystal_families) - def test_reduced_equivalent(self,lattice): - i = Orientation(lattice=lattice) - o = Orientation.from_random(lattice=lattice) + @pytest.mark.parametrize('family',crystal_families) + def test_reduced_equivalent(self,family): + i = Orientation(family=family) + o = Orientation.from_random(family=family) eq = o.equivalent FZ = np.argmin(abs(eq.misorientation(i.broadcast_to(len(eq))).as_axis_angle(pair=True)[1])) assert o.reduced == eq[FZ] - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('N',[1,8,32]) - def test_disorientation(self,lattice,N): - o = Orientation.from_random(lattice=lattice,shape=N) - p = Orientation.from_random(lattice=lattice,shape=N) + def test_disorientation(self,family,N): + o = Orientation.from_random(family=family,shape=N) + p = Orientation.from_random(family=family,shape=N) d,ops = o.disorientation(p,return_operators=True) @@ -218,72 +222,72 @@ class TestOrientation: .misorientation(p[n].equivalent[ops[n][1]]) .as_quaternion()) - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('a,b',[ ((2,3,2),(2,3,2)), ((2,2),(4,4)), ((3,1),(1,3)), (None,None), ]) - def test_disorientation_blending(self,lattice,a,b): - o = Orientation.from_random(lattice=lattice,shape=a) - p = Orientation.from_random(lattice=lattice,shape=b) + def test_disorientation_blending(self,family,a,b): + o = Orientation.from_random(family=family,shape=a) + p = Orientation.from_random(family=family,shape=b) blend = util.shapeblender(o.shape,p.shape) for loc in np.random.randint(0,blend,(10,len(blend))): assert o[tuple(loc[:len(o.shape)])].disorientation(p[tuple(loc[-len(p.shape):])]) \ .isclose(o.disorientation(p)[tuple(loc)]) - @pytest.mark.parametrize('lattice',crystal_families) - def test_disorientation360(self,lattice): - o_1 = Orientation(Rotation(),lattice) - o_2 = Orientation.from_Euler_angles(lattice=lattice,phi=[360,0,0],degrees=True) + @pytest.mark.parametrize('family',crystal_families) + def test_disorientation360(self,family): + o_1 = Orientation(Rotation(),family=family) + o_2 = Orientation.from_Euler_angles(family=family,phi=[360,0,0],degrees=True) assert np.allclose((o_1.disorientation(o_2)).as_matrix(),np.eye(3)) - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('shape',[(1),(2,3),(4,3,2)]) - def test_reduced_vectorization(self,lattice,shape): - o = Orientation.from_random(lattice=lattice,shape=shape) + def test_reduced_vectorization(self,family,shape): + o = Orientation.from_random(family=family,shape=shape) for r, theO in zip(o.reduced.flatten(),o.flatten()): assert r == theO.reduced - @pytest.mark.parametrize('lattice',crystal_families) - def test_reduced_corner_cases(self,lattice): + @pytest.mark.parametrize('family',crystal_families) + def test_reduced_corner_cases(self,family): # test whether there is always a sym-eq rotation that falls into the FZ N = np.random.randint(10,40) size = np.ones(3)*np.pi**(2./3.) grid = grid_filters.coordinates0_node([N+1,N+1,N+1],size,-size*.5) - evenly_distributed = Orientation.from_cubochoric(x=grid[:-2,:-2,:-2],lattice=lattice) + evenly_distributed = Orientation.from_cubochoric(x=grid[:-2,:-2,:-2],family=family) assert evenly_distributed.shape == evenly_distributed.reduced.shape - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('shape',[(1),(2,3),(4,3,2)]) @pytest.mark.parametrize('vector',np.array([[1,0,0],[1,2,3],[-1,1,-1]])) @pytest.mark.parametrize('proper',[True,False]) - def test_to_SST_vectorization(self,lattice,shape,vector,proper): - o = Orientation.from_random(lattice=lattice,shape=shape) + def test_to_SST_vectorization(self,family,shape,vector,proper): + o = Orientation.from_random(family=family,shape=shape) for r, theO in zip(o.to_SST(vector=vector,proper=proper).reshape((-1,3)),o.flatten()): assert np.allclose(r,theO.to_SST(vector=vector,proper=proper)) - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('shape',[(1),(2,3),(4,3,2)]) @pytest.mark.parametrize('vector',np.array([[1,0,0],[1,2,3],[-1,1,-1]])) @pytest.mark.parametrize('proper',[True,False]) @pytest.mark.parametrize('in_SST',[True,False]) - def test_IPF_color_vectorization(self,lattice,shape,vector,proper,in_SST): - o = Orientation.from_random(lattice=lattice,shape=shape) + def test_IPF_color_vectorization(self,family,shape,vector,proper,in_SST): + o = Orientation.from_random(family=family,shape=shape) for r, theO in zip(o.IPF_color(vector,in_SST=in_SST,proper=proper).reshape((-1,3)),o.flatten()): assert np.allclose(r,theO.IPF_color(vector,in_SST=in_SST,proper=proper)) - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('a,b',[ ((2,3,2),(2,3,2)), ((2,2),(4,4)), ((3,1),(1,3)), (None,(3,)), ]) - def test_to_SST_blending(self,lattice,a,b): - o = Orientation.from_random(lattice=lattice,shape=a) + def test_to_SST_blending(self,family,a,b): + o = Orientation.from_random(family=family,shape=a) v = np.random.random(b+(3,)) blend = util.shapeblender(o.shape,b) for loc in np.random.randint(0,blend,(10,len(blend))): @@ -298,55 +302,55 @@ class TestOrientation: {'label':'blue', 'RGB':[0,0,1],'direction':[1,1,1]}]) @pytest.mark.parametrize('proper',[True,False]) def test_IPF_cubic(self,color,proper): - cube = Orientation(lattice='cubic') + cube = Orientation(family='cubic') for direction in set(permutations(np.array(color['direction']))): assert np.allclose(np.array(color['RGB']), cube.IPF_color(vector=np.array(direction),proper=proper)) - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('proper',[True,False]) - def test_IPF_equivalent(self,set_of_quaternions,lattice,proper): + def test_IPF_equivalent(self,set_of_quaternions,family,proper): direction = np.random.random(3)*2.0-1.0 - o = Orientation(rotation=set_of_quaternions,lattice=lattice).equivalent + o = Orientation(rotation=set_of_quaternions,family=family).equivalent color = o.IPF_color(vector=direction,proper=proper) assert np.allclose(np.broadcast_to(color[0,...],color.shape),color) - @pytest.mark.parametrize('lattice',crystal_families) - def test_in_FZ_vectorization(self,set_of_rodrigues,lattice): - result = Orientation.from_Rodrigues_vector(rho=set_of_rodrigues.reshape((-1,4,4)),lattice=lattice).in_FZ.reshape(-1) + @pytest.mark.parametrize('family',crystal_families) + def test_in_FZ_vectorization(self,set_of_rodrigues,family): + result = Orientation.from_Rodrigues_vector(rho=set_of_rodrigues.reshape((-1,4,4)),family=family).in_FZ.reshape(-1) for r,rho in zip(result,set_of_rodrigues[:len(result)]): - assert r == Orientation.from_Rodrigues_vector(rho=rho,lattice=lattice).in_FZ + assert r == Orientation.from_Rodrigues_vector(rho=rho,family=family).in_FZ - @pytest.mark.parametrize('lattice',crystal_families) - def test_in_disorientation_FZ_vectorization(self,set_of_rodrigues,lattice): + @pytest.mark.parametrize('family',crystal_families) + def test_in_disorientation_FZ_vectorization(self,set_of_rodrigues,family): result = Orientation.from_Rodrigues_vector(rho=set_of_rodrigues.reshape((-1,4,4)), - lattice=lattice).in_disorientation_FZ.reshape(-1) + family=family).in_disorientation_FZ.reshape(-1) for r,rho in zip(result,set_of_rodrigues[:len(result)]): - assert r == Orientation.from_Rodrigues_vector(rho=rho,lattice=lattice).in_disorientation_FZ + assert r == Orientation.from_Rodrigues_vector(rho=rho,family=family).in_disorientation_FZ @pytest.mark.parametrize('proper',[True,False]) - @pytest.mark.parametrize('lattice',crystal_families) - def test_in_SST_vectorization(self,lattice,proper): + @pytest.mark.parametrize('family',crystal_families) + def test_in_SST_vectorization(self,family,proper): vecs = np.random.rand(20,4,3) - result = Orientation(lattice=lattice).in_SST(vecs,proper).flatten() + result = Orientation(family=family).in_SST(vecs,proper).flatten() for r,v in zip(result,vecs.reshape((-1,3))): - assert np.all(r == Orientation(lattice=lattice).in_SST(v,proper)) + assert np.all(r == Orientation(family=family).in_SST(v,proper)) - @pytest.mark.parametrize('invalid_lattice',['fcc','bcc','hello']) - def test_invalid_lattice_init(self,invalid_lattice): + @pytest.mark.parametrize('invalid_family',['fcc','bcc','hello']) + def test_invalid_lattice_init(self,invalid_family): with pytest.raises(KeyError): - Orientation(lattice=invalid_lattice) # noqa + Orientation(family=invalid_family) # noqa @pytest.mark.parametrize('invalid_family',[None,'fcc','bcc','hello']) def test_invalid_symmetry_family(self,invalid_family): with pytest.raises(KeyError): - o = Orientation(lattice='cubic') + o = Orientation(family='cubic') o.family = invalid_family o.symmetry_operations # noqa def test_invalid_rot(self): with pytest.raises(TypeError): - Orientation.from_random(lattice='cubic') * np.ones(3) + Orientation.from_random(family='cubic') * np.ones(3) def test_missing_symmetry_immutable(self): with pytest.raises(KeyError): @@ -388,14 +392,14 @@ class TestOrientation: a=a,b=b,c=c, alpha=alpha,beta=beta,gamma=gamma).related(relation) # noqa - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('proper',[True,False]) - def test_in_SST(self,lattice,proper): - assert Orientation(lattice=lattice).in_SST(np.zeros(3),proper) + def test_in_SST(self,family,proper): + assert Orientation(family=family).in_SST(np.zeros(3),proper) @pytest.mark.parametrize('function',['in_SST','IPF_color']) def test_invalid_argument(self,function): - o = Orientation(lattice='cubic') # noqa + o = Orientation(family='cubic') # noqa with pytest.raises(ValueError): eval(f'o.{function}(np.ones(4))') @@ -409,7 +413,7 @@ class TestOrientation: def test_relationship_vectorize(self,set_of_quaternions,lattice,model): r = Orientation(rotation=set_of_quaternions[:200].reshape((50,4,4)),lattice=lattice).related(model) for i in range(200): - assert (r.reshape((-1,200))[:,i] == Orientation(set_of_quaternions[i],lattice).related(model)).all() + assert (r.reshape((-1,200))[:,i] == Orientation(set_of_quaternions[i],lattice=lattice).related(model)).all() @pytest.mark.parametrize('model',['Bain','KS','GT','GT_prime','NW','Pitsch']) @pytest.mark.parametrize('lattice',['cF','cI']) @@ -512,12 +516,12 @@ class TestOrientation: == o.shape + (o.symmetry_operations.shape if with_symmetry else ()) + vector.shape @pytest.mark.parametrize('lattice',['hP','cI','cF']) - @pytest.mark.parametrize('mode',['slip','twin']) - def test_Schmid(self,update,ref_path,lattice,mode): + def test_Schmid(self,update,ref_path,lattice): L = Orientation(lattice=lattice) - 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')) + for mode in ['slip','twin']: # ToDo test tI + 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')) From b55d51491df055747ca5deb2b520029ad926bc2d Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 1 Jun 2021 23:23:04 +0200 Subject: [PATCH 03/27] separating functionality --- python/damask/__init__.py | 1 + python/damask/_lattice_family.py | 192 +++++++++++++++++++++++ python/damask/_orientation.py | 258 +++---------------------------- python/damask/lattice.py | 57 ------- python/tests/test_Orientation.py | 11 +- 5 files changed, 220 insertions(+), 299 deletions(-) create mode 100644 python/damask/_lattice_family.py diff --git a/python/damask/__init__.py b/python/damask/__init__.py index ad46d454f..5cdfbe899 100644 --- a/python/damask/__init__.py +++ b/python/damask/__init__.py @@ -18,6 +18,7 @@ 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 +from ._lattice_family import LatticeFamily # noqa from ._orientation import Orientation # noqa from ._table import Table # noqa from ._vtk import VTK # noqa diff --git a/python/damask/_lattice_family.py b/python/damask/_lattice_family.py new file mode 100644 index 000000000..8f352e8df --- /dev/null +++ b/python/damask/_lattice_family.py @@ -0,0 +1,192 @@ +import numpy as np + +from . import Rotation + +class LatticeFamily(): + + def __init__(self,family): + """Symmetry-related operations for crystal families.""" + if family not in self._immutable.keys(): + raise KeyError(f'invalid lattice family "{family}"') + self.family = family + + + @property + def symmetry_operations(self): + """Symmetry operations as Rotations.""" + return Rotation.from_quaternion(self._symmetry_operations[self.family],accept_homomorph=True) + + + @property + def immutable(self): + """Return immutable parameters lattice parameters.""" + return self._immutable[self.family] + + + @property + def basis(self): + """ + Corners of the standard triangle. + + Not yet defined for monoclinic. + + + References + ---------- + Bases are computed from + + >>> basis = { + ... 'cubic' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,1.]/np.sqrt(2.), # green + ... [1.,1.,1.]/np.sqrt(3.)]).T), # blue + ... 'hexagonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [np.sqrt(3.),1.,0.]/np.sqrt(4.)]).T), # blue + ... 'tetragonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [1.,1.,0.]/np.sqrt(2.)]).T), # blue + ... 'orthorhombic': np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [0.,1.,0.]]).T), # blue + ... } + + """ + return self._basis.get(self.family,None) + + + _symmetry_operations = { + 'cubic': [ + [ 1.0, 0.0, 0.0, 0.0 ], + [ 0.0, 1.0, 0.0, 0.0 ], + [ 0.0, 0.0, 1.0, 0.0 ], + [ 0.0, 0.0, 0.0, 1.0 ], + [ 0.0, 0.0, 0.5*np.sqrt(2), 0.5*np.sqrt(2) ], + [ 0.0, 0.0, 0.5*np.sqrt(2),-0.5*np.sqrt(2) ], + [ 0.0, 0.5*np.sqrt(2), 0.0, 0.5*np.sqrt(2) ], + [ 0.0, 0.5*np.sqrt(2), 0.0, -0.5*np.sqrt(2) ], + [ 0.0, 0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0 ], + [ 0.0, -0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0 ], + [ 0.5, 0.5, 0.5, 0.5 ], + [-0.5, 0.5, 0.5, 0.5 ], + [-0.5, 0.5, 0.5, -0.5 ], + [-0.5, 0.5, -0.5, 0.5 ], + [-0.5, -0.5, 0.5, 0.5 ], + [-0.5, -0.5, 0.5, -0.5 ], + [-0.5, -0.5, -0.5, 0.5 ], + [-0.5, 0.5, -0.5, -0.5 ], + [-0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], + [ 0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], + [-0.5*np.sqrt(2), 0.0, 0.5*np.sqrt(2), 0.0 ], + [-0.5*np.sqrt(2), 0.0, -0.5*np.sqrt(2), 0.0 ], + [-0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0, 0.0 ], + [-0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0, 0.0 ], + ], + 'hexagonal': [ + [ 1.0, 0.0, 0.0, 0.0 ], + [-0.5*np.sqrt(3), 0.0, 0.0, -0.5 ], + [ 0.5, 0.0, 0.0, 0.5*np.sqrt(3) ], + [ 0.0, 0.0, 0.0, 1.0 ], + [-0.5, 0.0, 0.0, 0.5*np.sqrt(3) ], + [-0.5*np.sqrt(3), 0.0, 0.0, 0.5 ], + [ 0.0, 1.0, 0.0, 0.0 ], + [ 0.0, -0.5*np.sqrt(3), 0.5, 0.0 ], + [ 0.0, 0.5, -0.5*np.sqrt(3), 0.0 ], + [ 0.0, 0.0, 1.0, 0.0 ], + [ 0.0, -0.5, -0.5*np.sqrt(3), 0.0 ], + [ 0.0, 0.5*np.sqrt(3), 0.5, 0.0 ], + ], + 'tetragonal': [ + [ 1.0, 0.0, 0.0, 0.0 ], + [ 0.0, 1.0, 0.0, 0.0 ], + [ 0.0, 0.0, 1.0, 0.0 ], + [ 0.0, 0.0, 0.0, 1.0 ], + [ 0.0, 0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0 ], + [ 0.0, -0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0 ], + [ 0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], + [-0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], + ], + 'orthorhombic': [ + [ 1.0,0.0,0.0,0.0 ], + [ 0.0,1.0,0.0,0.0 ], + [ 0.0,0.0,1.0,0.0 ], + [ 0.0,0.0,0.0,1.0 ], + ], + 'monoclinic': [ + [ 1.0,0.0,0.0,0.0 ], + [ 0.0,0.0,1.0,0.0 ], + ], + 'triclinic': [ + [ 1.0,0.0,0.0,0.0 ], + ]} + + + _immutable = { + 'cubic': + { + 'b': 1.0, + 'c': 1.0, + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': np.pi/2., + }, + 'hexagonal': + { + 'b': 1.0, + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': 2.*np.pi/3., + }, + 'tetragonal': + { + 'b': 1.0, + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': np.pi/2., + }, + 'orthorhombic': + { + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': np.pi/2., + }, + 'monoclinic': + { + 'alpha': np.pi/2., + 'gamma': np.pi/2., + }, + 'triclinic': {} + } + + + _basis = { + 'cubic': {'improper':np.array([ [-1. , 0. , 1. ], + [ np.sqrt(2.) , -np.sqrt(2.) , 0. ], + [ 0. , np.sqrt(3.) , 0. ] ]), + 'proper':np.array([ [ 0. , -1. , 1. ], + [-np.sqrt(2.) , np.sqrt(2.) , 0. ], + [ np.sqrt(3.) , 0. , 0. ] ]), + }, + 'hexagonal': + {'improper':np.array([ [ 0. , 0. , 1. ], + [ 1. , -np.sqrt(3.) , 0. ], + [ 0. , 2. , 0. ] ]), + 'proper':np.array([ [ 0. , 0. , 1. ], + [-1. , np.sqrt(3.) , 0. ], + [ np.sqrt(3.) , -1. , 0. ] ]), + }, + 'tetragonal': + {'improper':np.array([ [ 0. , 0. , 1. ], + [ 1. , -1. , 0. ], + [ 0. , np.sqrt(2.) , 0. ] ]), + 'proper':np.array([ [ 0. , 0. , 1. ], + [-1. , 1. , 0. ], + [ np.sqrt(2.) , 0. , 0. ] ]), + }, + 'orthorhombic': + {'improper':np.array([ [ 0., 0., 1.], + [ 1., 0., 0.], + [ 0., 1., 0.] ]), + 'proper':np.array([ [ 0., 0., 1.], + [-1., 0., 0.], + [ 0., 1., 0.] ]), + }} diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index f3919d1ff..8a3f9fe24 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -3,6 +3,7 @@ import inspect import numpy as np from . import Rotation +from . import LatticeFamily from . import util from . import tensor from . import lattice as lattice_ @@ -132,16 +133,27 @@ class Orientation(Rotation): Rotation.__init__(self,rotation=rotation) if family in set(lattice_symmetries.values()) and lattice is None: - self.family = family + self.family = family self.lattice = None + l = LatticeFamily(self.family) + self.immutable = l.immutable + self.basis = l.basis + self.symmetry_operations = l.symmetry_operations + self.a = self.b = self.c = None self.alpha = self.beta = self.gamma = None elif lattice in lattice_symmetries: + self.family = lattice_symmetries[lattice] self.lattice = lattice + l = LatticeFamily(self.family) + self.immutable = l.immutable + self.basis = l.basis + self.symmetry_operations = l.symmetry_operations + self.a = 1 if a is None else a self.b = b self.c = c @@ -201,7 +213,6 @@ class Orientation(Rotation): alpha = kwargs['alpha'] if 'alpha' in kwargs else self.alpha, beta = kwargs['beta'] if 'beta' in kwargs else self.beta, gamma = kwargs['gamma'] if 'gamma' in kwargs else self.gamma, - degrees = kwargs['degrees'] if 'degrees' in kwargs else None, ) copy = __copy__ @@ -435,84 +446,6 @@ class Orientation(Rotation): return o.copy(rotation=Rotation.from_matrix(tensor.transpose(om/np.linalg.norm(om,axis=-1,keepdims=True)))) - @property - def symmetry_operations(self): - """Symmetry operations as Rotations.""" - if self.family == 'cubic': - sym_quats = [ - [ 1.0, 0.0, 0.0, 0.0 ], - [ 0.0, 1.0, 0.0, 0.0 ], - [ 0.0, 0.0, 1.0, 0.0 ], - [ 0.0, 0.0, 0.0, 1.0 ], - [ 0.0, 0.0, 0.5*np.sqrt(2), 0.5*np.sqrt(2) ], - [ 0.0, 0.0, 0.5*np.sqrt(2),-0.5*np.sqrt(2) ], - [ 0.0, 0.5*np.sqrt(2), 0.0, 0.5*np.sqrt(2) ], - [ 0.0, 0.5*np.sqrt(2), 0.0, -0.5*np.sqrt(2) ], - [ 0.0, 0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0 ], - [ 0.0, -0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0 ], - [ 0.5, 0.5, 0.5, 0.5 ], - [-0.5, 0.5, 0.5, 0.5 ], - [-0.5, 0.5, 0.5, -0.5 ], - [-0.5, 0.5, -0.5, 0.5 ], - [-0.5, -0.5, 0.5, 0.5 ], - [-0.5, -0.5, 0.5, -0.5 ], - [-0.5, -0.5, -0.5, 0.5 ], - [-0.5, 0.5, -0.5, -0.5 ], - [-0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], - [ 0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], - [-0.5*np.sqrt(2), 0.0, 0.5*np.sqrt(2), 0.0 ], - [-0.5*np.sqrt(2), 0.0, -0.5*np.sqrt(2), 0.0 ], - [-0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0, 0.0 ], - [-0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0, 0.0 ], - ] - elif self.family == 'hexagonal': - sym_quats = [ - [ 1.0, 0.0, 0.0, 0.0 ], - [-0.5*np.sqrt(3), 0.0, 0.0, -0.5 ], - [ 0.5, 0.0, 0.0, 0.5*np.sqrt(3) ], - [ 0.0, 0.0, 0.0, 1.0 ], - [-0.5, 0.0, 0.0, 0.5*np.sqrt(3) ], - [-0.5*np.sqrt(3), 0.0, 0.0, 0.5 ], - [ 0.0, 1.0, 0.0, 0.0 ], - [ 0.0, -0.5*np.sqrt(3), 0.5, 0.0 ], - [ 0.0, 0.5, -0.5*np.sqrt(3), 0.0 ], - [ 0.0, 0.0, 1.0, 0.0 ], - [ 0.0, -0.5, -0.5*np.sqrt(3), 0.0 ], - [ 0.0, 0.5*np.sqrt(3), 0.5, 0.0 ], - ] - elif self.family == 'tetragonal': - sym_quats = [ - [ 1.0, 0.0, 0.0, 0.0 ], - [ 0.0, 1.0, 0.0, 0.0 ], - [ 0.0, 0.0, 1.0, 0.0 ], - [ 0.0, 0.0, 0.0, 1.0 ], - [ 0.0, 0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0 ], - [ 0.0, -0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0 ], - [ 0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], - [-0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], - ] - elif self.family == 'orthorhombic': - sym_quats = [ - [ 1.0,0.0,0.0,0.0 ], - [ 0.0,1.0,0.0,0.0 ], - [ 0.0,0.0,1.0,0.0 ], - [ 0.0,0.0,0.0,1.0 ], - ] - elif self.family == 'monoclinic': - sym_quats = [ - [ 1.0,0.0,0.0,0.0 ], - [ 0.0,0.0,1.0,0.0 ], - ] - elif self.family == 'triclinic': - sym_quats = [ - [ 1.0,0.0,0.0,0.0 ], - ] - else: - raise KeyError(f'unknown crystal family "{self.family}"') - - return Rotation.from_quaternion(sym_quats,accept_homomorph=True) - - @property def equivalent(self): """ @@ -707,46 +640,6 @@ class Orientation(Rotation): return (self.a,self.b,self.c,self.alpha,self.beta,self.gamma) - @property - def immutable(self): - """Return immutable parameters of own lattice.""" - if self.family == 'triclinic': - return {} - if self.family == 'monoclinic': - return { - 'alpha': np.pi/2., - 'gamma': np.pi/2., - } - if self.family == 'orthorhombic': - return { - 'alpha': np.pi/2., - 'beta': np.pi/2., - 'gamma': np.pi/2., - } - if self.family == 'tetragonal': - return { - 'b': 1.0, - 'alpha': np.pi/2., - 'beta': np.pi/2., - 'gamma': np.pi/2., - } - if self.family == 'hexagonal': - return { - 'b': 1.0, - 'alpha': np.pi/2., - 'beta': np.pi/2., - 'gamma': 2.*np.pi/3., - } - if self.family == 'cubic': - return { - 'b': 1.0, - 'c': 1.0, - 'alpha': np.pi/2., - 'beta': np.pi/2., - 'gamma': np.pi/2., - } - - @property def ratio(self): """Return axes ratios of own lattice.""" @@ -808,76 +701,25 @@ class Orientation(Rotation): in : numpy.ndarray of shape (...) Boolean array indicating whether vector falls into SST. - References - ---------- - Bases are computed from - - >>> basis = { - ... 'cubic' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,1.]/np.sqrt(2.), # green - ... [1.,1.,1.]/np.sqrt(3.)]).T), # blue - ... 'hexagonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [np.sqrt(3.),1.,0.]/np.sqrt(4.)]).T), # blue - ... 'tetragonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [1.,1.,0.]/np.sqrt(2.)]).T), # blue - ... 'orthorhombic': np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [0.,1.,0.]]).T), # blue - ... } - """ if not isinstance(vector,np.ndarray) or vector.shape[-1] != 3: raise ValueError('input is not a field of three-dimensional vectors') - if self.family == 'cubic': - basis = {'improper':np.array([ [-1. , 0. , 1. ], - [ np.sqrt(2.) , -np.sqrt(2.) , 0. ], - [ 0. , np.sqrt(3.) , 0. ] ]), - 'proper':np.array([ [ 0. , -1. , 1. ], - [-np.sqrt(2.) , np.sqrt(2.) , 0. ], - [ np.sqrt(3.) , 0. , 0. ] ]), - } - elif self.family == 'hexagonal': - basis = {'improper':np.array([ [ 0. , 0. , 1. ], - [ 1. , -np.sqrt(3.) , 0. ], - [ 0. , 2. , 0. ] ]), - 'proper':np.array([ [ 0. , 0. , 1. ], - [-1. , np.sqrt(3.) , 0. ], - [ np.sqrt(3.) , -1. , 0. ] ]), - } - elif self.family == 'tetragonal': - basis = {'improper':np.array([ [ 0. , 0. , 1. ], - [ 1. , -1. , 0. ], - [ 0. , np.sqrt(2.) , 0. ] ]), - 'proper':np.array([ [ 0. , 0. , 1. ], - [-1. , 1. , 0. ], - [ np.sqrt(2.) , 0. , 0. ] ]), - } - elif self.family == 'orthorhombic': - basis = {'improper':np.array([ [ 0., 0., 1.], - [ 1., 0., 0.], - [ 0., 1., 0.] ]), - 'proper':np.array([ [ 0., 0., 1.], - [-1., 0., 0.], - [ 0., 1., 0.] ]), - } - else: # direct exit for unspecified symmetry + if self.basis is None: # direct exit for no symmetry return np.ones_like(vector[...,0],bool) if proper: components_proper = np.around(np.einsum('...ji,...i', - np.broadcast_to(basis['proper'], vector.shape+(3,)), + np.broadcast_to(self.basis['proper'], vector.shape+(3,)), vector), 12) components_improper = np.around(np.einsum('...ji,...i', - np.broadcast_to(basis['improper'], vector.shape+(3,)), + np.broadcast_to(self.basis['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(basis['improper'], vector.shape+(3,)), + np.broadcast_to(self.basis['improper'], vector.shape+(3,)), np.block([vector[...,:2],np.abs(vector[...,2:3])])), 12) return np.all(components >= 0.0,axis=-1) @@ -911,25 +753,6 @@ class Orientation(Rotation): >>> o.IPF_color([0,0,1]) array([1., 0., 0.]) - References - ---------- - Bases are computed from - - >>> basis = { - ... 'cubic' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,1.]/np.sqrt(2.), # green - ... [1.,1.,1.]/np.sqrt(3.)]).T), # blue - ... 'hexagonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [np.sqrt(3.),1.,0.]/np.sqrt(4.)]).T), # blue - ... 'tetragonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [1.,1.,0.]/np.sqrt(2.)]).T), # blue - ... 'orthorhombic': np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [0.,1.,0.]]).T), # blue - ... } - """ if np.array(vector).shape[-1] != 3: raise ValueError('input is not a field of three-dimensional vectors') @@ -937,47 +760,15 @@ class Orientation(Rotation): vector_ = self.to_SST(vector,proper) if in_SST else \ self @ np.broadcast_to(vector,self.shape+(3,)) - if self.family == 'cubic': - basis = {'improper':np.array([ [-1. , 0. , 1. ], - [ np.sqrt(2.) , -np.sqrt(2.) , 0. ], - [ 0. , np.sqrt(3.) , 0. ] ]), - 'proper':np.array([ [ 0. , -1. , 1. ], - [-np.sqrt(2.) , np.sqrt(2.) , 0. ], - [ np.sqrt(3.) , 0. , 0. ] ]), - } - elif self.family == 'hexagonal': - basis = {'improper':np.array([ [ 0. , 0. , 1. ], - [ 1. , -np.sqrt(3.) , 0. ], - [ 0. , 2. , 0. ] ]), - 'proper':np.array([ [ 0. , 0. , 1. ], - [-1. , np.sqrt(3.) , 0. ], - [ np.sqrt(3.) , -1. , 0. ] ]), - } - elif self.family == 'tetragonal': - basis = {'improper':np.array([ [ 0. , 0. , 1. ], - [ 1. , -1. , 0. ], - [ 0. , np.sqrt(2.) , 0. ] ]), - 'proper':np.array([ [ 0. , 0. , 1. ], - [-1. , 1. , 0. ], - [ np.sqrt(2.) , 0. , 0. ] ]), - } - elif self.family == 'orthorhombic': - basis = {'improper':np.array([ [ 0., 0., 1.], - [ 1., 0., 0.], - [ 0., 1., 0.] ]), - 'proper':np.array([ [ 0., 0., 1.], - [-1., 0., 0.], - [ 0., 1., 0.] ]), - } - else: # direct exit for unspecified symmetry + if self.basis is None: # direct exit for no symmetry return np.zeros_like(vector_) if proper: components_proper = np.around(np.einsum('...ji,...i', - np.broadcast_to(basis['proper'], vector_.shape+(3,)), + np.broadcast_to(self.basis['proper'], vector_.shape+(3,)), vector_), 12) components_improper = np.around(np.einsum('...ji,...i', - np.broadcast_to(basis['improper'], vector_.shape+(3,)), + np.broadcast_to(self.basis['improper'], vector_.shape+(3,)), vector_), 12) in_SST = np.all(components_proper >= 0.0,axis=-1) \ | np.all(components_improper >= 0.0,axis=-1) @@ -985,16 +776,17 @@ class Orientation(Rotation): components_proper,components_improper) else: components = np.around(np.einsum('...ji,...i', - np.broadcast_to(basis['improper'], vector_.shape+(3,)), + np.broadcast_to(self.basis['improper'], vector_.shape+(3,)), np.block([vector_[...,:2],np.abs(vector_[...,2:3])])), 12) 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 = (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 + return rgb diff --git a/python/damask/lattice.py b/python/damask/lattice.py index e335fc85b..5b4621f3d 100644 --- a/python/damask/lattice.py +++ b/python/damask/lattice.py @@ -1,62 +1,5 @@ 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 7fc20a3e6..471dada58 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -336,17 +336,10 @@ class TestOrientation: for r,v in zip(result,vecs.reshape((-1,3))): assert np.all(r == Orientation(family=family).in_SST(v,proper)) - @pytest.mark.parametrize('invalid_family',['fcc','bcc','hello']) + @pytest.mark.parametrize('invalid_family',[None,'fcc','bcc','hello']) def test_invalid_lattice_init(self,invalid_family): with pytest.raises(KeyError): - Orientation(family=invalid_family) # noqa - - @pytest.mark.parametrize('invalid_family',[None,'fcc','bcc','hello']) - def test_invalid_symmetry_family(self,invalid_family): - with pytest.raises(KeyError): - o = Orientation(family='cubic') - o.family = invalid_family - o.symmetry_operations # noqa + Orientation(family=invalid_family) def test_invalid_rot(self): with pytest.raises(TypeError): From 302da1f76a43caa09c273db125d4a4fd93b5c27f Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 2 Jun 2021 09:28:27 +0200 Subject: [PATCH 04/27] separating functionality --- python/damask/__init__.py | 2 +- python/damask/_lattice.py | 651 +++++++++++++++++++++++++++++++ python/damask/_lattice_family.py | 29 +- python/damask/_orientation.py | 169 ++------ python/damask/lattice.py | 444 --------------------- python/tests/test_Lattice.py | 64 +++ python/tests/test_Orientation.py | 70 ---- 7 files changed, 755 insertions(+), 674 deletions(-) create mode 100644 python/damask/_lattice.py delete mode 100644 python/damask/lattice.py create mode 100644 python/tests/test_Lattice.py diff --git a/python/damask/__init__.py b/python/damask/__init__.py index 5cdfbe899..eb333487d 100644 --- a/python/damask/__init__.py +++ b/python/damask/__init__.py @@ -14,11 +14,11 @@ 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 from ._lattice_family import LatticeFamily # noqa +from ._lattice import Lattice # noqa from ._orientation import Orientation # noqa from ._table import Table # noqa from ._vtk import VTK # noqa diff --git a/python/damask/_lattice.py b/python/damask/_lattice.py new file mode 100644 index 000000000..c9303968c --- /dev/null +++ b/python/damask/_lattice.py @@ -0,0 +1,651 @@ +import numpy as np + +from . import util +from . import LatticeFamily + +lattice_symmetries = { + 'aP': 'triclinic', + + 'mP': 'monoclinic', + 'mS': 'monoclinic', + + 'oP': 'orthorhombic', + 'oS': 'orthorhombic', + 'oI': 'orthorhombic', + 'oF': 'orthorhombic', + + 'tP': 'tetragonal', + 'tI': 'tetragonal', + + 'hP': 'hexagonal', + + 'cP': 'cubic', + 'cI': 'cubic', + 'cF': 'cubic', + } + + +class Lattice(LatticeFamily): + """Lattice.""" + + def __init__(self, + lattice = None, + a = None,b = None,c = None, + alpha = None,beta = None,gamma = None, + degrees = False): + """ + Lattice. + + Parameters + ---------- + lattice : {'aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF'}. + Name of the Bravais lattice in Pearson notation. + a : float, optional + Length of lattice parameter 'a'. + b : float, optional + Length of lattice parameter 'b'. + c : float, optional + Length of lattice parameter 'c'. + alpha : float, optional + Angle between b and c lattice basis. + beta : float, optional + Angle between c and a lattice basis. + gamma : float, optional + Angle between a and b lattice basis. + degrees : bool, optional + Angles are given in degrees. Defaults to False. + + """ + super().__init__(lattice_symmetries[lattice]) + self.lattice = lattice + + + self.a = 1 if a is None else a + self.b = b + self.c = c + self.a = float(self.a) if self.a is not None else \ + (self.b / self.ratio['b'] if self.b is not None and self.ratio['b'] is not None else + self.c / self.ratio['c'] if self.c is not None and self.ratio['c'] is not None else None) + self.b = float(self.b) if self.b is not None else \ + (self.a * self.ratio['b'] if self.a is not None and self.ratio['b'] is not None else + self.c / self.ratio['c'] * self.ratio['b'] + if self.c is not None and self.ratio['b'] is not None and self.ratio['c'] is not None else None) + self.c = float(self.c) if self.c is not None else \ + (self.a * self.ratio['c'] if self.a is not None and self.ratio['c'] is not None else + self.b / self.ratio['b'] * self.ratio['c'] + if self.c is not None and self.ratio['b'] is not None and self.ratio['c'] is not None else None) + + self.alpha = np.radians(alpha) if degrees and alpha is not None else alpha + self.beta = np.radians(beta) if degrees and beta is not None else beta + self.gamma = np.radians(gamma) if degrees and gamma is not None else gamma + if self.alpha is None and 'alpha' in self.immutable: self.alpha = self.immutable['alpha'] + if self.beta is None and 'beta' in self.immutable: self.beta = self.immutable['beta'] + if self.gamma is None and 'gamma' in self.immutable: self.gamma = self.immutable['gamma'] + + if \ + (self.a is None) \ + or (self.b is None or ('b' in self.immutable and self.b != self.immutable['b'] * self.a)) \ + or (self.c is None or ('c' in self.immutable and self.c != self.immutable['c'] * self.b)) \ + or (self.alpha is None or ('alpha' in self.immutable and self.alpha != self.immutable['alpha'])) \ + or (self.beta is None or ( 'beta' in self.immutable and self.beta != self.immutable['beta'])) \ + or (self.gamma is None or ('gamma' in self.immutable and self.gamma != self.immutable['gamma'])): + raise ValueError (f'Incompatible parameters {self.parameters} for crystal family {self.family}') + + if np.any(np.array([self.alpha,self.beta,self.gamma]) <= 0): + raise ValueError ('Lattice angles must be positive') + if np.any([np.roll([self.alpha,self.beta,self.gamma],r)[0] + > 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') + + + @property + def parameters(self): + """Return lattice parameters a, b, c, alpha, beta, gamma.""" + return (self.a,self.b,self.c,self.alpha,self.beta,self.gamma) + + + @property + def ratio(self): + """Return axes ratios of own lattice.""" + _ratio = { 'hexagonal': {'c': np.sqrt(8./3.)}} + + return dict(b = self.immutable['b'] + if 'b' in self.immutable else + _ratio[self.family]['b'] if self.family in _ratio and 'b' in _ratio[self.family] else None, + c = self.immutable['c'] + if 'c' in self.immutable else + _ratio[self.family]['c'] if self.family in _ratio and 'c' in _ratio[self.family] else None, + ) + + + @property + def basis_real(self): + """ + Calculate orthogonal real space crystal basis. + + References + ---------- + C.T. Young and J.L. Lytton, Journal of Applied Physics 43:1408–1417, 1972 + https://doi.org/10.1063/1.1661333 + + """ + if None in self.parameters: + raise KeyError('missing crystal lattice parameters') + return np.array([ + [1,0,0], + [np.cos(self.gamma),np.sin(self.gamma),0], + [np.cos(self.beta), + (np.cos(self.alpha)-np.cos(self.beta)*np.cos(self.gamma)) /np.sin(self.gamma), + np.sqrt(1 - np.cos(self.alpha)**2 - np.cos(self.beta)**2 - np.cos(self.gamma)**2 + + 2 * np.cos(self.alpha) * np.cos(self.beta) * np.cos(self.gamma))/np.sin(self.gamma)], + ],dtype=float).T \ + * np.array([self.a,self.b,self.c]) + + + @property + def basis_reciprocal(self): + """Calculate reciprocal (dual) crystal basis.""" + return np.linalg.inv(self.basis_real.T) + + + def to_lattice(self,*,direction=None,plane=None): + """ + Calculate lattice vector corresponding to crystal frame direction or plane normal. + + Parameters + ---------- + direction|normal : numpy.ndarray of shape (...,3) + Vector along direction or plane normal. + + Returns + ------- + Miller : numpy.ndarray of shape (...,3) + lattice vector of direction or plane. + Use util.scale_to_coprime to convert to (integer) Miller indices. + + """ + if (direction is not None) ^ (plane is None): + 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) + return np.einsum('il,...l',basis,axis) + + + def to_frame(self,*,uvw=None,hkl=None): + """ + Calculate crystal frame vector along lattice direction [uvw] or plane normal (hkl). + + Parameters + ---------- + uvw|hkl : numpy.ndarray of shape (...,3) + Miller indices of crystallographic direction or plane normal. + + Returns + ------- + vector : numpy.ndarray of shape (...,3) or (N,...,3) + Crystal frame vector (or vectors if with_symmetry) along [uvw] direction or (hkl) plane normal. + + """ + if (uvw is not None) ^ (hkl is None): + 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) + return np.einsum('il,...l',basis,axis) + + + def kinematics(self,mode): + master = self._kinematics[self.lattice][mode] + if self.lattice == 'hP': + return {'direction':util.Bravais_to_Miller(uvtw=master[:,0:4]), + 'plane': util.Bravais_to_Miller(hkil=master[:,4:8])} + else: + return {'direction':master[:,0:3], + 'plane': master[:,3:6]} + + + @property + def orientation_relationships(self): + return {k:v for k,v in self._orientation_relationships.items() if self.lattice in v} + + + _kinematics = { + 'cF': { + 'slip' : np.array([ + [+0,+1,-1, +1,+1,+1], + [-1,+0,+1, +1,+1,+1], + [+1,-1,+0, +1,+1,+1], + [+0,-1,-1, -1,-1,+1], + [+1,+0,+1, -1,-1,+1], + [-1,+1,+0, -1,-1,+1], + [+0,-1,+1, +1,-1,-1], + [-1,+0,-1, +1,-1,-1], + [+1,+1,+0, +1,-1,-1], + [+0,+1,+1, -1,+1,-1], + [+1,+0,-1, -1,+1,-1], + [-1,-1,+0, -1,+1,-1], + [+1,+1,+0, +1,-1,+0], + [+1,-1,+0, +1,+1,+0], + [+1,+0,+1, +1,+0,-1], + [+1,+0,-1, +1,+0,+1], + [+0,+1,+1, +0,+1,-1], + [+0,+1,-1, +0,+1,+1], + ],'d'), + 'twin' : np.array([ + [-2, 1, 1, 1, 1, 1], + [ 1,-2, 1, 1, 1, 1], + [ 1, 1,-2, 1, 1, 1], + [ 2,-1, 1, -1,-1, 1], + [-1, 2, 1, -1,-1, 1], + [-1,-1,-2, -1,-1, 1], + [-2,-1,-1, 1,-1,-1], + [ 1, 2,-1, 1,-1,-1], + [ 1,-1, 2, 1,-1,-1], + [ 2, 1,-1, -1, 1,-1], + [-1,-2,-1, -1, 1,-1], + [-1, 1, 2, -1, 1,-1], + ],dtype=float), + }, + 'cI': { + 'slip' : np.array([ + [+1,-1,+1, +0,+1,+1], + [-1,-1,+1, +0,+1,+1], + [+1,+1,+1, +0,-1,+1], + [-1,+1,+1, +0,-1,+1], + [-1,+1,+1, +1,+0,+1], + [-1,-1,+1, +1,+0,+1], + [+1,+1,+1, -1,+0,+1], + [+1,-1,+1, -1,+0,+1], + [-1,+1,+1, +1,+1,+0], + [-1,+1,-1, +1,+1,+0], + [+1,+1,+1, -1,+1,+0], + [+1,+1,-1, -1,+1,+0], + [-1,+1,+1, +2,+1,+1], + [+1,+1,+1, -2,+1,+1], + [+1,+1,-1, +2,-1,+1], + [+1,-1,+1, +2,+1,-1], + [+1,-1,+1, +1,+2,+1], + [+1,+1,-1, -1,+2,+1], + [+1,+1,+1, +1,-2,+1], + [-1,+1,+1, +1,+2,-1], + [+1,+1,-1, +1,+1,+2], + [+1,-1,+1, -1,+1,+2], + [-1,+1,+1, +1,-1,+2], + [+1,+1,+1, +1,+1,-2], + [+1,+1,-1, +1,+2,+3], + [+1,-1,+1, -1,+2,+3], + [-1,+1,+1, +1,-2,+3], + [+1,+1,+1, +1,+2,-3], + [+1,-1,+1, +1,+3,+2], + [+1,+1,-1, -1,+3,+2], + [+1,+1,+1, +1,-3,+2], + [-1,+1,+1, +1,+3,-2], + [+1,+1,-1, +2,+1,+3], + [+1,-1,+1, -2,+1,+3], + [-1,+1,+1, +2,-1,+3], + [+1,+1,+1, +2,+1,-3], + [+1,-1,+1, +2,+3,+1], + [+1,+1,-1, -2,+3,+1], + [+1,+1,+1, +2,-3,+1], + [-1,+1,+1, +2,+3,-1], + [-1,+1,+1, +3,+1,+2], + [+1,+1,+1, -3,+1,+2], + [+1,+1,-1, +3,-1,+2], + [+1,-1,+1, +3,+1,-2], + [-1,+1,+1, +3,+2,+1], + [+1,+1,+1, -3,+2,+1], + [+1,+1,-1, +3,-2,+1], + [+1,-1,+1, +3,+2,-1], + ],'d'), + 'twin' : np.array([ + [-1, 1, 1, 2, 1, 1], + [ 1, 1, 1, -2, 1, 1], + [ 1, 1,-1, 2,-1, 1], + [ 1,-1, 1, 2, 1,-1], + [ 1,-1, 1, 1, 2, 1], + [ 1, 1,-1, -1, 2, 1], + [ 1, 1, 1, 1,-2, 1], + [-1, 1, 1, 1, 2,-1], + [ 1, 1,-1, 1, 1, 2], + [ 1,-1, 1, -1, 1, 2], + [-1, 1, 1, 1,-1, 2], + [ 1, 1, 1, 1, 1,-2], + ],dtype=float), + }, + 'hP': { + 'slip' : np.array([ + [+2,-1,-1,+0, +0,+0,+0,+1], + [-1,+2,-1,+0, +0,+0,+0,+1], + [-1,-1,+2,+0, +0,+0,+0,+1], + [+2,-1,-1,+0, +0,+1,-1,+0], + [-1,+2,-1,+0, -1,+0,+1,+0], + [-1,-1,+2,+0, +1,-1,+0,+0], + [-1,+1,+0,+0, +1,+1,-2,+0], + [+0,-1,+1,+0, -2,+1,+1,+0], + [+1,+0,-1,+0, +1,-2,+1,+0], + [-1,+2,-1,+0, +1,+0,-1,+1], + [-2,+1,+1,+0, +0,+1,-1,+1], + [-1,-1,+2,+0, -1,+1,+0,+1], + [+1,-2,+1,+0, -1,+0,+1,+1], + [+2,-1,-1,+0, +0,-1,+1,+1], + [+1,+1,-2,+0, +1,-1,+0,+1], + [-2,+1,+1,+3, +1,+0,-1,+1], + [-1,-1,+2,+3, +1,+0,-1,+1], + [-1,-1,+2,+3, +0,+1,-1,+1], + [+1,-2,+1,+3, +0,+1,-1,+1], + [+1,-2,+1,+3, -1,+1,+0,+1], + [+2,-1,-1,+3, -1,+1,+0,+1], + [+2,-1,-1,+3, -1,+0,+1,+1], + [+1,+1,-2,+3, -1,+0,+1,+1], + [+1,+1,-2,+3, +0,-1,+1,+1], + [-1,+2,-1,+3, +0,-1,+1,+1], + [-1,+2,-1,+3, +1,-1,+0,+1], + [-2,+1,+1,+3, +1,-1,+0,+1], + [-1,-1,+2,+3, +1,+1,-2,+2], + [+1,-2,+1,+3, -1,+2,-1,+2], + [+2,-1,-1,+3, -2,+1,+1,+2], + [+1,+1,-2,+3, -1,-1,+2,+2], + [-1,+2,-1,+3, +1,-2,+1,+2], + [-2,+1,+1,+3, +2,-1,-1,+2], + ],'d'), + 'twin' : np.array([ + [-1, 0, 1, 1, 1, 0,-1, 2], # shear = (3-(c/a)^2)/(sqrt(3) c/a) <-10.1>{10.2} + [ 0,-1, 1, 1, 0, 1,-1, 2], + [ 1,-1, 0, 1, -1, 1, 0, 2], + [ 1, 0,-1, 1, -1, 0, 1, 2], + [ 0, 1,-1, 1, 0,-1, 1, 2], + [-1, 1, 0, 1, 1,-1, 0, 2], + [-1,-1, 2, 6, 1, 1,-2, 1], # shear = 1/(c/a) <11.6>{-1-1.1} + [ 1,-2, 1, 6, -1, 2,-1, 1], + [ 2,-1,-1, 6, -2, 1, 1, 1], + [ 1, 1,-2, 6, -1,-1, 2, 1], + [-1, 2,-1, 6, 1,-2, 1, 1], + [-2, 1, 1, 6, 2,-1,-1, 1], + [ 1, 0,-1,-2, 1, 0,-1, 1], # shear = (4(c/a)^2-9)/(4 sqrt(3) c/a) <10.-2>{10.1} + [ 0, 1,-1,-2, 0, 1,-1, 1], + [-1, 1, 0,-2, -1, 1, 0, 1], + [-1, 0, 1,-2, -1, 0, 1, 1], + [ 0,-1, 1,-2, 0,-1, 1, 1], + [ 1,-1, 0,-2, 1,-1, 0, 1], + [ 1, 1,-2,-3, 1, 1,-2, 2], # shear = 2((c/a)^2-2)/(3 c/a) <11.-3>{11.2} + [-1, 2,-1,-3, -1, 2,-1, 2], + [-2, 1, 1,-3, -2, 1, 1, 2], + [-1,-1, 2,-3, -1,-1, 2, 2], + [ 1,-2, 1,-3, 1,-2, 1, 2], + [ 2,-1,-1,-3, 2,-1,-1, 2], + ],dtype=float), + }, + } + + + _orientation_relationships = { + 'KS': { + 'cF' : np.array([ + [[-1, 0, 1],[ 1, 1, 1]], + [[-1, 0, 1],[ 1, 1, 1]], + [[ 0, 1,-1],[ 1, 1, 1]], + [[ 0, 1,-1],[ 1, 1, 1]], + [[ 1,-1, 0],[ 1, 1, 1]], + [[ 1,-1, 0],[ 1, 1, 1]], + [[ 1, 0,-1],[ 1,-1, 1]], + [[ 1, 0,-1],[ 1,-1, 1]], + [[-1,-1, 0],[ 1,-1, 1]], + [[-1,-1, 0],[ 1,-1, 1]], + [[ 0, 1, 1],[ 1,-1, 1]], + [[ 0, 1, 1],[ 1,-1, 1]], + [[ 0,-1, 1],[-1, 1, 1]], + [[ 0,-1, 1],[-1, 1, 1]], + [[-1, 0,-1],[-1, 1, 1]], + [[-1, 0,-1],[-1, 1, 1]], + [[ 1, 1, 0],[-1, 1, 1]], + [[ 1, 1, 0],[-1, 1, 1]], + [[-1, 1, 0],[ 1, 1,-1]], + [[-1, 1, 0],[ 1, 1,-1]], + [[ 0,-1,-1],[ 1, 1,-1]], + [[ 0,-1,-1],[ 1, 1,-1]], + [[ 1, 0, 1],[ 1, 1,-1]], + [[ 1, 0, 1],[ 1, 1,-1]], + ],dtype=float), + 'cI' : np.array([ + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + ],dtype=float), + }, + 'GT': { + 'cF' : np.array([ + [[ -5,-12, 17],[ 1, 1, 1]], + [[ 17, -5,-12],[ 1, 1, 1]], + [[-12, 17, -5],[ 1, 1, 1]], + [[ 5, 12, 17],[ -1, -1, 1]], + [[-17, 5,-12],[ -1, -1, 1]], + [[ 12,-17, -5],[ -1, -1, 1]], + [[ -5, 12,-17],[ -1, 1, 1]], + [[ 17, 5, 12],[ -1, 1, 1]], + [[-12,-17, 5],[ -1, 1, 1]], + [[ 5,-12,-17],[ 1, -1, 1]], + [[-17, -5, 12],[ 1, -1, 1]], + [[ 12, 17, 5],[ 1, -1, 1]], + [[ -5, 17,-12],[ 1, 1, 1]], + [[-12, -5, 17],[ 1, 1, 1]], + [[ 17,-12, -5],[ 1, 1, 1]], + [[ 5,-17,-12],[ -1, -1, 1]], + [[ 12, 5, 17],[ -1, -1, 1]], + [[-17, 12, -5],[ -1, -1, 1]], + [[ -5,-17, 12],[ -1, 1, 1]], + [[-12, 5,-17],[ -1, 1, 1]], + [[ 17, 12, 5],[ -1, 1, 1]], + [[ 5, 17, 12],[ 1, -1, 1]], + [[ 12, -5,-17],[ 1, -1, 1]], + [[-17,-12, 5],[ 1, -1, 1]], + ],dtype=float), + 'cI' : np.array([ + [[-17, -7, 17],[ 1, 0, 1]], + [[ 17,-17, -7],[ 1, 1, 0]], + [[ -7, 17,-17],[ 0, 1, 1]], + [[ 17, 7, 17],[ -1, 0, 1]], + [[-17, 17, -7],[ -1, -1, 0]], + [[ 7,-17,-17],[ 0, -1, 1]], + [[-17, 7,-17],[ -1, 0, 1]], + [[ 17, 17, 7],[ -1, 1, 0]], + [[ -7,-17, 17],[ 0, 1, 1]], + [[ 17, -7,-17],[ 1, 0, 1]], + [[-17,-17, 7],[ 1, -1, 0]], + [[ 7, 17, 17],[ 0, -1, 1]], + [[-17, 17, -7],[ 1, 1, 0]], + [[ -7,-17, 17],[ 0, 1, 1]], + [[ 17, -7,-17],[ 1, 0, 1]], + [[ 17,-17, -7],[ -1, -1, 0]], + [[ 7, 17, 17],[ 0, -1, 1]], + [[-17, 7,-17],[ -1, 0, 1]], + [[-17,-17, 7],[ -1, 1, 0]], + [[ -7, 17,-17],[ 0, 1, 1]], + [[ 17, 7, 17],[ -1, 0, 1]], + [[ 17, 17, 7],[ 1, -1, 0]], + [[ 7,-17,-17],[ 0, -1, 1]], + [[-17, -7, 17],[ 1, 0, 1]], + ],dtype=float), + }, + 'GT_prime': { + 'cF' : np.array([ + [[ 0, 1, -1],[ 7, 17, 17]], + [[ -1, 0, 1],[ 17, 7, 17]], + [[ 1, -1, 0],[ 17, 17, 7]], + [[ 0, -1, -1],[ -7,-17, 17]], + [[ 1, 0, 1],[-17, -7, 17]], + [[ 1, -1, 0],[-17,-17, 7]], + [[ 0, 1, -1],[ 7,-17,-17]], + [[ 1, 0, 1],[ 17, -7,-17]], + [[ -1, -1, 0],[ 17,-17, -7]], + [[ 0, -1, -1],[ -7, 17,-17]], + [[ -1, 0, 1],[-17, 7,-17]], + [[ -1, -1, 0],[-17, 17, -7]], + [[ 0, -1, 1],[ 7, 17, 17]], + [[ 1, 0, -1],[ 17, 7, 17]], + [[ -1, 1, 0],[ 17, 17, 7]], + [[ 0, 1, 1],[ -7,-17, 17]], + [[ -1, 0, -1],[-17, -7, 17]], + [[ -1, 1, 0],[-17,-17, 7]], + [[ 0, -1, 1],[ 7,-17,-17]], + [[ -1, 0, -1],[ 17, -7,-17]], + [[ 1, 1, 0],[ 17,-17, -7]], + [[ 0, 1, 1],[ -7, 17,-17]], + [[ 1, 0, -1],[-17, 7,-17]], + [[ 1, 1, 0],[-17, 17, -7]], + ],dtype=float), + 'cI' : np.array([ + [[ 1, 1, -1],[ 12, 5, 17]], + [[ -1, 1, 1],[ 17, 12, 5]], + [[ 1, -1, 1],[ 5, 17, 12]], + [[ -1, -1, -1],[-12, -5, 17]], + [[ 1, -1, 1],[-17,-12, 5]], + [[ 1, -1, -1],[ -5,-17, 12]], + [[ -1, 1, -1],[ 12, -5,-17]], + [[ 1, 1, 1],[ 17,-12, -5]], + [[ -1, -1, 1],[ 5,-17,-12]], + [[ 1, -1, -1],[-12, 5,-17]], + [[ -1, -1, 1],[-17, 12, -5]], + [[ -1, -1, -1],[ -5, 17,-12]], + [[ 1, -1, 1],[ 12, 17, 5]], + [[ 1, 1, -1],[ 5, 12, 17]], + [[ -1, 1, 1],[ 17, 5, 12]], + [[ -1, 1, 1],[-12,-17, 5]], + [[ -1, -1, -1],[ -5,-12, 17]], + [[ -1, 1, -1],[-17, -5, 12]], + [[ -1, -1, 1],[ 12,-17, -5]], + [[ -1, 1, -1],[ 5,-12,-17]], + [[ 1, 1, 1],[ 17, -5,-12]], + [[ 1, 1, 1],[-12, 17, -5]], + [[ 1, -1, -1],[ -5, 12,-17]], + [[ 1, 1, -1],[-17, 5,-12]], + ],dtype=float), + }, + 'NW': { + 'cF' : np.array([ + [[ 2, -1, -1],[ 1, 1, 1]], + [[ -1, 2, -1],[ 1, 1, 1]], + [[ -1, -1, 2],[ 1, 1, 1]], + [[ -2, -1, -1],[ -1, 1, 1]], + [[ 1, 2, -1],[ -1, 1, 1]], + [[ 1, -1, 2],[ -1, 1, 1]], + [[ 2, 1, -1],[ 1, -1, 1]], + [[ -1, -2, -1],[ 1, -1, 1]], + [[ -1, 1, 2],[ 1, -1, 1]], + [[ 2, -1, 1],[ -1, -1, 1]], + [[ -1, 2, 1],[ -1, -1, 1]], + [[ -1, -1, -2],[ -1, -1, 1]], + ],dtype=float), + 'cI' : np.array([ + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + ],dtype=float), + }, + 'Pitsch': { + 'cF' : np.array([ + [[ 1, 0, 1],[ 0, 1, 0]], + [[ 1, 1, 0],[ 0, 0, 1]], + [[ 0, 1, 1],[ 1, 0, 0]], + [[ 0, 1, -1],[ 1, 0, 0]], + [[ -1, 0, 1],[ 0, 1, 0]], + [[ 1, -1, 0],[ 0, 0, 1]], + [[ 1, 0, -1],[ 0, 1, 0]], + [[ -1, 1, 0],[ 0, 0, 1]], + [[ 0, -1, 1],[ 1, 0, 0]], + [[ 0, 1, 1],[ 1, 0, 0]], + [[ 1, 0, 1],[ 0, 1, 0]], + [[ 1, 1, 0],[ 0, 0, 1]], + ],dtype=float), + 'cI' : np.array([ + [[ 1, -1, 1],[ -1, 0, 1]], + [[ 1, 1, -1],[ 1, -1, 0]], + [[ -1, 1, 1],[ 0, 1, -1]], + [[ -1, 1, -1],[ 0, -1, -1]], + [[ -1, -1, 1],[ -1, 0, -1]], + [[ 1, -1, -1],[ -1, -1, 0]], + [[ 1, -1, -1],[ -1, 0, -1]], + [[ -1, 1, -1],[ -1, -1, 0]], + [[ -1, -1, 1],[ 0, -1, -1]], + [[ -1, 1, 1],[ 0, -1, 1]], + [[ 1, -1, 1],[ 1, 0, -1]], + [[ 1, 1, -1],[ -1, 1, 0]], + ],dtype=float), + }, + 'Bain': { + 'cF' : np.array([ + [[ 0, 1, 0],[ 1, 0, 0]], + [[ 0, 0, 1],[ 0, 1, 0]], + [[ 1, 0, 0],[ 0, 0, 1]], + ],dtype=float), + 'cI' : np.array([ + [[ 0, 1, 1],[ 1, 0, 0]], + [[ 1, 0, 1],[ 0, 1, 0]], + [[ 1, 1, 0],[ 0, 0, 1]], + ],dtype=float), + }, + 'Burgers' : { + 'cI' : np.array([ + [[ -1, 1, 1],[ 1, 1, 0]], + [[ -1, 1, -1],[ 1, 1, 0]], + [[ 1, 1, 1],[ 1, -1, 0]], + [[ 1, 1, -1],[ 1, -1, 0]], + + [[ 1, 1, -1],[ 1, 0, 1]], + [[ -1, 1, 1],[ 1, 0, 1]], + [[ 1, 1, 1],[ -1, 0, 1]], + [[ 1, -1, 1],[ -1, 0, 1]], + + [[ -1, 1, -1],[ 0, 1, 1]], + [[ 1, 1, -1],[ 0, 1, 1]], + [[ -1, 1, 1],[ 0, -1, 1]], + [[ 1, 1, 1],[ 0, -1, 1]], + ],dtype=float), + 'hP' : np.array([ + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + ],dtype=float), + }, + } diff --git a/python/damask/_lattice_family.py b/python/damask/_lattice_family.py index 8f352e8df..d6288cff0 100644 --- a/python/damask/_lattice_family.py +++ b/python/damask/_lattice_family.py @@ -5,7 +5,15 @@ from . import Rotation class LatticeFamily(): def __init__(self,family): - """Symmetry-related operations for crystal families.""" + """ + Symmetry-related operations for crystal families. + + Parameters + ---------- + family : {'triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic'} + Name of the crystal family. + + """ if family not in self._immutable.keys(): raise KeyError(f'invalid lattice family "{family}"') self.family = family @@ -121,41 +129,36 @@ class LatticeFamily(): _immutable = { - 'cubic': - { + 'cubic': { 'b': 1.0, 'c': 1.0, 'alpha': np.pi/2., 'beta': np.pi/2., 'gamma': np.pi/2., }, - 'hexagonal': - { + 'hexagonal': { 'b': 1.0, 'alpha': np.pi/2., 'beta': np.pi/2., 'gamma': 2.*np.pi/3., }, - 'tetragonal': - { + 'tetragonal': { 'b': 1.0, 'alpha': np.pi/2., 'beta': np.pi/2., 'gamma': np.pi/2., }, - 'orthorhombic': - { + 'orthorhombic': { 'alpha': np.pi/2., 'beta': np.pi/2., 'gamma': np.pi/2., }, - 'monoclinic': - { + 'monoclinic': { 'alpha': np.pi/2., 'gamma': np.pi/2., }, - 'triclinic': {} - } + 'triclinic': {} + } _basis = { diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 8a3f9fe24..b2ee9628b 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -4,9 +4,9 @@ import numpy as np from . import Rotation from . import LatticeFamily +from . import Lattice from . import util from . import tensor -from . import lattice as lattice_ lattice_symmetries = { @@ -130,13 +130,13 @@ class Orientation(Rotation): Defaults to no rotation. """ - Rotation.__init__(self,rotation=rotation) + super().__init__(rotation) if family in set(lattice_symmetries.values()) and lattice is None: self.family = family self.lattice = None - l = LatticeFamily(self.family) + l = LatticeFamily(self.family) # noqa self.immutable = l.immutable self.basis = l.basis self.symmetry_operations = l.symmetry_operations @@ -149,48 +149,25 @@ class Orientation(Rotation): self.family = lattice_symmetries[lattice] self.lattice = lattice - l = LatticeFamily(self.family) + l = Lattice(self.lattice, a,b,c, alpha,beta,gamma, degrees) # noqa self.immutable = l.immutable self.basis = l.basis self.symmetry_operations = l.symmetry_operations - self.a = 1 if a is None else a - self.b = b - self.c = c - self.a = float(self.a) if self.a is not None else \ - (self.b / self.ratio['b'] if self.b is not None and self.ratio['b'] is not None else - self.c / self.ratio['c'] if self.c is not None and self.ratio['c'] is not None else None) - self.b = float(self.b) if self.b is not None else \ - (self.a * self.ratio['b'] if self.a is not None and self.ratio['b'] is not None else - self.c / self.ratio['c'] * self.ratio['b'] - if self.c is not None and self.ratio['b'] is not None and self.ratio['c'] is not None else None) - self.c = float(self.c) if self.c is not None else \ - (self.a * self.ratio['c'] if self.a is not None and self.ratio['c'] is not None else - self.b / self.ratio['b'] * self.ratio['c'] - if self.c is not None and self.ratio['b'] is not None and self.ratio['c'] is not None else None) + self.a = l.a + self.b = l.b + self.c = l.c - self.alpha = np.radians(alpha) if degrees and alpha is not None else alpha - self.beta = np.radians(beta) if degrees and beta is not None else beta - self.gamma = np.radians(gamma) if degrees and gamma is not None else gamma - if self.alpha is None and 'alpha' in self.immutable: self.alpha = self.immutable['alpha'] - if self.beta is None and 'beta' in self.immutable: self.beta = self.immutable['beta'] - if self.gamma is None and 'gamma' in self.immutable: self.gamma = self.immutable['gamma'] + self.alpha = l.alpha + self.beta = l.beta + self.gamma = l.gamma - if \ - (self.a is None) \ - or (self.b is None or ('b' in self.immutable and self.b != self.immutable['b'] * self.a)) \ - or (self.c is None or ('c' in self.immutable and self.c != self.immutable['c'] * self.b)) \ - or (self.alpha is None or ('alpha' in self.immutable and self.alpha != self.immutable['alpha'])) \ - or (self.beta is None or ( 'beta' in self.immutable and self.beta != self.immutable['beta'])) \ - or (self.gamma is None or ('gamma' in self.immutable and self.gamma != self.immutable['gamma'])): - raise ValueError (f'Incompatible parameters {self.parameters} for crystal family {self.family}') + self.ratio = l.ratio - if np.any(np.array([self.alpha,self.beta,self.gamma]) <= 0): - raise ValueError ('Lattice angles must be positive') - if np.any([np.roll([self.alpha,self.beta,self.gamma],r)[0] - > 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') + self.to_frame = l.to_frame + self.kinematics = l.kinematics + self.orientation_relationships = l.orientation_relationships else: raise KeyError(f'no valid family or lattice') @@ -590,12 +567,9 @@ class Orientation(Rotation): https://doi.org/10.1016/j.actamat.2004.11.021 """ - if model not in lattice_.relations: + if model not in self.orientation_relationships: 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}"') + r = self.orientation_relationships[model] sl = self.lattice ol = (set(r)-{sl}).pop() @@ -640,50 +614,6 @@ class Orientation(Rotation): return (self.a,self.b,self.c,self.alpha,self.beta,self.gamma) - @property - def ratio(self): - """Return axes ratios of own lattice.""" - _ratio = { 'hexagonal': {'c': np.sqrt(8./3.)}} - - return dict(b = self.immutable['b'] - if 'b' in self.immutable else - _ratio[self.family]['b'] if self.family in _ratio and 'b' in _ratio[self.family] else None, - c = self.immutable['c'] - if 'c' in self.immutable else - _ratio[self.family]['c'] if self.family in _ratio and 'c' in _ratio[self.family] else None, - ) - - - @property - def basis_real(self): - """ - Calculate orthogonal real space crystal basis. - - References - ---------- - C.T. Young and J.L. Lytton, Journal of Applied Physics 43:1408–1417, 1972 - https://doi.org/10.1063/1.1661333 - - """ - if None in self.parameters: - raise KeyError('missing crystal lattice parameters') - return np.array([ - [1,0,0], - [np.cos(self.gamma),np.sin(self.gamma),0], - [np.cos(self.beta), - (np.cos(self.alpha)-np.cos(self.beta)*np.cos(self.gamma)) /np.sin(self.gamma), - np.sqrt(1 - np.cos(self.alpha)**2 - np.cos(self.beta)**2 - np.cos(self.gamma)**2 - + 2 * np.cos(self.alpha) * np.cos(self.beta) * np.cos(self.gamma))/np.sin(self.gamma)], - ],dtype=float).T \ - * np.array([self.a,self.b,self.c]) - - - @property - def basis_reciprocal(self): - """Calculate reciprocal (dual) crystal basis.""" - return np.linalg.inv(self.basis_real.T) - - def in_SST(self,vector,proper=False): """ Check whether given crystal frame vector falls into standard stereographic triangle of own symmetry. @@ -944,58 +874,6 @@ class Orientation(Rotation): ) - def to_lattice(self,*,direction=None,plane=None): - """ - Calculate lattice vector corresponding to crystal frame direction or plane normal. - - Parameters - ---------- - direction|normal : numpy.ndarray of shape (...,3) - Vector along direction or plane normal. - - Returns - ------- - Miller : numpy.ndarray of shape (...,3) - lattice vector of direction or plane. - Use util.scale_to_coprime to convert to (integer) Miller indices. - - """ - if (direction is not None) ^ (plane is None): - 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) - return np.einsum('il,...l',basis,axis) - - - def to_frame(self,*,uvw=None,hkl=None,with_symmetry=False): - """ - Calculate crystal frame vector along lattice direction [uvw] or plane normal (hkl). - - Parameters - ---------- - uvw|hkl : numpy.ndarray of shape (...,3) - Miller indices of crystallographic direction or plane normal. - with_symmetry : bool, optional - Calculate all N symmetrically equivalent vectors. - - Returns - ------- - vector : numpy.ndarray of shape (...,3) or (N,...,3) - Crystal frame vector (or vectors if with_symmetry) along [uvw] direction or (hkl) plane normal. - - """ - if (uvw is not None) ^ (hkl is None): - 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) - return (self.symmetry_operations.broadcast_to(self.symmetry_operations.shape+axis.shape[:-1],mode='right') - @ np.broadcast_to(np.einsum('il,...l',basis,axis),self.symmetry_operations.shape+axis.shape) - if with_symmetry else - np.einsum('il,...l',basis,axis)) - - def to_pole(self,*,uvw=None,hkl=None,with_symmetry=False): """ Calculate lab frame vector along lattice direction [uvw] or plane normal (hkl). @@ -1013,7 +891,11 @@ class Orientation(Rotation): Lab frame vector (or vectors if with_symmetry) along [uvw] direction or (hkl) plane normal. """ - v = self.to_frame(uvw=uvw,hkl=hkl,with_symmetry=with_symmetry) + # ToDo: simplify 'with_symmetry' + v = self.to_frame(uvw=uvw,hkl=hkl) + if with_symmetry: + v = self.symmetry_operations.broadcast_to(self.symmetry_operations.shape+v.shape[:-1],mode='right') \ + @ np.broadcast_to(v,self.symmetry_operations.shape+v.shape) return ~(self if self.shape+v.shape[:-1] == () else self.broadcast_to(self.shape+v.shape[:-1],mode='right')) \ @ np.broadcast_to(v,self.shape+v.shape) @@ -1047,15 +929,10 @@ class Orientation(Rotation): """ try: - master = lattice_.kinematics[self.lattice][mode] - kinematics = {'direction':master[:,0:3],'plane':master[:,3:6]} \ - if master.shape[-1] == 6 else \ - {'direction':util.Bravais_to_Miller(uvtw=master[:,0:4]), - 'plane': util.Bravais_to_Miller(hkil=master[:,4:8])} + d = self.to_frame(uvw=self.kinematics(mode)['direction']) + p = self.to_frame(hkl=self.kinematics(mode)['plane']) 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/damask/lattice.py b/python/damask/lattice.py deleted file mode 100644 index 5b4621f3d..000000000 --- a/python/damask/lattice.py +++ /dev/null @@ -1,444 +0,0 @@ -import numpy as _np - -kinematics = { - 'cF': { - 'slip' : _np.array([ - [+0,+1,-1 , +1,+1,+1], - [-1,+0,+1 , +1,+1,+1], - [+1,-1,+0 , +1,+1,+1], - [+0,-1,-1 , -1,-1,+1], - [+1,+0,+1 , -1,-1,+1], - [-1,+1,+0 , -1,-1,+1], - [+0,-1,+1 , +1,-1,-1], - [-1,+0,-1 , +1,-1,-1], - [+1,+1,+0 , +1,-1,-1], - [+0,+1,+1 , -1,+1,-1], - [+1,+0,-1 , -1,+1,-1], - [-1,-1,+0 , -1,+1,-1], - [+1,+1,+0 , +1,-1,+0], - [+1,-1,+0 , +1,+1,+0], - [+1,+0,+1 , +1,+0,-1], - [+1,+0,-1 , +1,+0,+1], - [+0,+1,+1 , +0,+1,-1], - [+0,+1,-1 , +0,+1,+1], - ],'d'), - 'twin' : _np.array([ - [-2, 1, 1, 1, 1, 1], - [ 1,-2, 1, 1, 1, 1], - [ 1, 1,-2, 1, 1, 1], - [ 2,-1, 1, -1,-1, 1], - [-1, 2, 1, -1,-1, 1], - [-1,-1,-2, -1,-1, 1], - [-2,-1,-1, 1,-1,-1], - [ 1, 2,-1, 1,-1,-1], - [ 1,-1, 2, 1,-1,-1], - [ 2, 1,-1, -1, 1,-1], - [-1,-2,-1, -1, 1,-1], - [-1, 1, 2, -1, 1,-1], - ],dtype=float), - }, - 'cI': { - 'slip' : _np.array([ - [+1,-1,+1 , +0,+1,+1], - [-1,-1,+1 , +0,+1,+1], - [+1,+1,+1 , +0,-1,+1], - [-1,+1,+1 , +0,-1,+1], - [-1,+1,+1 , +1,+0,+1], - [-1,-1,+1 , +1,+0,+1], - [+1,+1,+1 , -1,+0,+1], - [+1,-1,+1 , -1,+0,+1], - [-1,+1,+1 , +1,+1,+0], - [-1,+1,-1 , +1,+1,+0], - [+1,+1,+1 , -1,+1,+0], - [+1,+1,-1 , -1,+1,+0], - [-1,+1,+1 , +2,+1,+1], - [+1,+1,+1 , -2,+1,+1], - [+1,+1,-1 , +2,-1,+1], - [+1,-1,+1 , +2,+1,-1], - [+1,-1,+1 , +1,+2,+1], - [+1,+1,-1 , -1,+2,+1], - [+1,+1,+1 , +1,-2,+1], - [-1,+1,+1 , +1,+2,-1], - [+1,+1,-1 , +1,+1,+2], - [+1,-1,+1 , -1,+1,+2], - [-1,+1,+1 , +1,-1,+2], - [+1,+1,+1 , +1,+1,-2], - [+1,+1,-1 , +1,+2,+3], - [+1,-1,+1 , -1,+2,+3], - [-1,+1,+1 , +1,-2,+3], - [+1,+1,+1 , +1,+2,-3], - [+1,-1,+1 , +1,+3,+2], - [+1,+1,-1 , -1,+3,+2], - [+1,+1,+1 , +1,-3,+2], - [-1,+1,+1 , +1,+3,-2], - [+1,+1,-1 , +2,+1,+3], - [+1,-1,+1 , -2,+1,+3], - [-1,+1,+1 , +2,-1,+3], - [+1,+1,+1 , +2,+1,-3], - [+1,-1,+1 , +2,+3,+1], - [+1,+1,-1 , -2,+3,+1], - [+1,+1,+1 , +2,-3,+1], - [-1,+1,+1 , +2,+3,-1], - [-1,+1,+1 , +3,+1,+2], - [+1,+1,+1 , -3,+1,+2], - [+1,+1,-1 , +3,-1,+2], - [+1,-1,+1 , +3,+1,-2], - [-1,+1,+1 , +3,+2,+1], - [+1,+1,+1 , -3,+2,+1], - [+1,+1,-1 , +3,-2,+1], - [+1,-1,+1 , +3,+2,-1], - ],'d'), - 'twin' : _np.array([ - [-1, 1, 1, 2, 1, 1], - [ 1, 1, 1, -2, 1, 1], - [ 1, 1,-1, 2,-1, 1], - [ 1,-1, 1, 2, 1,-1], - [ 1,-1, 1, 1, 2, 1], - [ 1, 1,-1, -1, 2, 1], - [ 1, 1, 1, 1,-2, 1], - [-1, 1, 1, 1, 2,-1], - [ 1, 1,-1, 1, 1, 2], - [ 1,-1, 1, -1, 1, 2], - [-1, 1, 1, 1,-1, 2], - [ 1, 1, 1, 1, 1,-2], - ],dtype=float), - }, - 'hP': { - 'slip' : _np.array([ - [+2,-1,-1,+0 , +0,+0,+0,+1], - [-1,+2,-1,+0 , +0,+0,+0,+1], - [-1,-1,+2,+0 , +0,+0,+0,+1], - [+2,-1,-1,+0 , +0,+1,-1,+0], - [-1,+2,-1,+0 , -1,+0,+1,+0], - [-1,-1,+2,+0 , +1,-1,+0,+0], - [-1,+1,+0,+0 , +1,+1,-2,+0], - [+0,-1,+1,+0 , -2,+1,+1,+0], - [+1,+0,-1,+0 , +1,-2,+1,+0], - [-1,+2,-1,+0 , +1,+0,-1,+1], - [-2,+1,+1,+0 , +0,+1,-1,+1], - [-1,-1,+2,+0 , -1,+1,+0,+1], - [+1,-2,+1,+0 , -1,+0,+1,+1], - [+2,-1,-1,+0 , +0,-1,+1,+1], - [+1,+1,-2,+0 , +1,-1,+0,+1], - [-2,+1,+1,+3 , +1,+0,-1,+1], - [-1,-1,+2,+3 , +1,+0,-1,+1], - [-1,-1,+2,+3 , +0,+1,-1,+1], - [+1,-2,+1,+3 , +0,+1,-1,+1], - [+1,-2,+1,+3 , -1,+1,+0,+1], - [+2,-1,-1,+3 , -1,+1,+0,+1], - [+2,-1,-1,+3 , -1,+0,+1,+1], - [+1,+1,-2,+3 , -1,+0,+1,+1], - [+1,+1,-2,+3 , +0,-1,+1,+1], - [-1,+2,-1,+3 , +0,-1,+1,+1], - [-1,+2,-1,+3 , +1,-1,+0,+1], - [-2,+1,+1,+3 , +1,-1,+0,+1], - [-1,-1,+2,+3 , +1,+1,-2,+2], - [+1,-2,+1,+3 , -1,+2,-1,+2], - [+2,-1,-1,+3 , -2,+1,+1,+2], - [+1,+1,-2,+3 , -1,-1,+2,+2], - [-1,+2,-1,+3 , +1,-2,+1,+2], - [-2,+1,+1,+3 , +2,-1,-1,+2], - ],'d'), - 'twin' : _np.array([ - [-1, 0, 1, 1, 1, 0, -1, 2], # shear = (3-(c/a)^2)/(sqrt(3) c/a) <-10.1>{10.2} - [ 0, -1, 1, 1, 0, 1, -1, 2], - [ 1, -1, 0, 1, -1, 1, 0, 2], - [ 1, 0, -1, 1, -1, 0, 1, 2], - [ 0, 1, -1, 1, 0, -1, 1, 2], - [-1, 1, 0, 1, 1, -1, 0, 2], - [-1, -1, 2, 6, 1, 1, -2, 1], # shear = 1/(c/a) <11.6>{-1-1.1} - [ 1, -2, 1, 6, -1, 2, -1, 1], - [ 2, -1, -1, 6, -2, 1, 1, 1], - [ 1, 1, -2, 6, -1, -1, 2, 1], - [-1, 2, -1, 6, 1, -2, 1, 1], - [-2, 1, 1, 6, 2, -1, -1, 1], - [ 1, 0, -1, -2, 1, 0, -1, 1], # shear = (4(c/a)^2-9)/(4 sqrt(3) c/a) <10.-2>{10.1} - [ 0, 1, -1, -2, 0, 1, -1, 1], - [-1, 1, 0, -2, -1, 1, 0, 1], - [-1, 0, 1, -2, -1, 0, 1, 1], - [ 0, -1, 1, -2, 0, -1, 1, 1], - [ 1, -1, 0, -2, 1, -1, 0, 1], - [ 1, 1, -2, -3, 1, 1, -2, 2], # shear = 2((c/a)^2-2)/(3 c/a) <11.-3>{11.2} - [-1, 2, -1, -3, -1, 2, -1, 2], - [-2, 1, 1, -3, -2, 1, 1, 2], - [-1, -1, 2, -3, -1, -1, 2, 2], - [ 1, -2, 1, -3, 1, -2, 1, 2], - [ 2, -1, -1, -3, 2, -1, -1, 2], - ],dtype=float), - }, -} - -# Kurdjomov--Sachs orientation relationship for fcc <-> bcc transformation -# from S. Morito et al., Journal of Alloys and Compounds 577:s587-s592, 2013 -# also see K. Kitahara et al., Acta Materialia 54:1279-1288, 2006 - -relations = { - 'KS': { - 'cF' : _np.array([ - [[ -1, 0, 1],[ 1, 1, 1]], - [[ -1, 0, 1],[ 1, 1, 1]], - [[ 0, 1, -1],[ 1, 1, 1]], - [[ 0, 1, -1],[ 1, 1, 1]], - [[ 1, -1, 0],[ 1, 1, 1]], - [[ 1, -1, 0],[ 1, 1, 1]], - [[ 1, 0, -1],[ 1, -1, 1]], - [[ 1, 0, -1],[ 1, -1, 1]], - [[ -1, -1, 0],[ 1, -1, 1]], - [[ -1, -1, 0],[ 1, -1, 1]], - [[ 0, 1, 1],[ 1, -1, 1]], - [[ 0, 1, 1],[ 1, -1, 1]], - [[ 0, -1, 1],[ -1, 1, 1]], - [[ 0, -1, 1],[ -1, 1, 1]], - [[ -1, 0, -1],[ -1, 1, 1]], - [[ -1, 0, -1],[ -1, 1, 1]], - [[ 1, 1, 0],[ -1, 1, 1]], - [[ 1, 1, 0],[ -1, 1, 1]], - [[ -1, 1, 0],[ 1, 1, -1]], - [[ -1, 1, 0],[ 1, 1, -1]], - [[ 0, -1, -1],[ 1, 1, -1]], - [[ 0, -1, -1],[ 1, 1, -1]], - [[ 1, 0, 1],[ 1, 1, -1]], - [[ 1, 0, 1],[ 1, 1, -1]], - ],dtype=float), - 'cI' : _np.array([ - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - ],dtype=float), - }, - 'GT': { - 'cF' : _np.array([ - [[ -5,-12, 17],[ 1, 1, 1]], - [[ 17, -5,-12],[ 1, 1, 1]], - [[-12, 17, -5],[ 1, 1, 1]], - [[ 5, 12, 17],[ -1, -1, 1]], - [[-17, 5,-12],[ -1, -1, 1]], - [[ 12,-17, -5],[ -1, -1, 1]], - [[ -5, 12,-17],[ -1, 1, 1]], - [[ 17, 5, 12],[ -1, 1, 1]], - [[-12,-17, 5],[ -1, 1, 1]], - [[ 5,-12,-17],[ 1, -1, 1]], - [[-17, -5, 12],[ 1, -1, 1]], - [[ 12, 17, 5],[ 1, -1, 1]], - [[ -5, 17,-12],[ 1, 1, 1]], - [[-12, -5, 17],[ 1, 1, 1]], - [[ 17,-12, -5],[ 1, 1, 1]], - [[ 5,-17,-12],[ -1, -1, 1]], - [[ 12, 5, 17],[ -1, -1, 1]], - [[-17, 12, -5],[ -1, -1, 1]], - [[ -5,-17, 12],[ -1, 1, 1]], - [[-12, 5,-17],[ -1, 1, 1]], - [[ 17, 12, 5],[ -1, 1, 1]], - [[ 5, 17, 12],[ 1, -1, 1]], - [[ 12, -5,-17],[ 1, -1, 1]], - [[-17,-12, 5],[ 1, -1, 1]], - ],dtype=float), - 'cI' : _np.array([ - [[-17, -7, 17],[ 1, 0, 1]], - [[ 17,-17, -7],[ 1, 1, 0]], - [[ -7, 17,-17],[ 0, 1, 1]], - [[ 17, 7, 17],[ -1, 0, 1]], - [[-17, 17, -7],[ -1, -1, 0]], - [[ 7,-17,-17],[ 0, -1, 1]], - [[-17, 7,-17],[ -1, 0, 1]], - [[ 17, 17, 7],[ -1, 1, 0]], - [[ -7,-17, 17],[ 0, 1, 1]], - [[ 17, -7,-17],[ 1, 0, 1]], - [[-17,-17, 7],[ 1, -1, 0]], - [[ 7, 17, 17],[ 0, -1, 1]], - [[-17, 17, -7],[ 1, 1, 0]], - [[ -7,-17, 17],[ 0, 1, 1]], - [[ 17, -7,-17],[ 1, 0, 1]], - [[ 17,-17, -7],[ -1, -1, 0]], - [[ 7, 17, 17],[ 0, -1, 1]], - [[-17, 7,-17],[ -1, 0, 1]], - [[-17,-17, 7],[ -1, 1, 0]], - [[ -7, 17,-17],[ 0, 1, 1]], - [[ 17, 7, 17],[ -1, 0, 1]], - [[ 17, 17, 7],[ 1, -1, 0]], - [[ 7,-17,-17],[ 0, -1, 1]], - [[-17, -7, 17],[ 1, 0, 1]], - ],dtype=float), - }, - 'GT_prime': { - 'cF' : _np.array([ - [[ 0, 1, -1],[ 7, 17, 17]], - [[ -1, 0, 1],[ 17, 7, 17]], - [[ 1, -1, 0],[ 17, 17, 7]], - [[ 0, -1, -1],[ -7,-17, 17]], - [[ 1, 0, 1],[-17, -7, 17]], - [[ 1, -1, 0],[-17,-17, 7]], - [[ 0, 1, -1],[ 7,-17,-17]], - [[ 1, 0, 1],[ 17, -7,-17]], - [[ -1, -1, 0],[ 17,-17, -7]], - [[ 0, -1, -1],[ -7, 17,-17]], - [[ -1, 0, 1],[-17, 7,-17]], - [[ -1, -1, 0],[-17, 17, -7]], - [[ 0, -1, 1],[ 7, 17, 17]], - [[ 1, 0, -1],[ 17, 7, 17]], - [[ -1, 1, 0],[ 17, 17, 7]], - [[ 0, 1, 1],[ -7,-17, 17]], - [[ -1, 0, -1],[-17, -7, 17]], - [[ -1, 1, 0],[-17,-17, 7]], - [[ 0, -1, 1],[ 7,-17,-17]], - [[ -1, 0, -1],[ 17, -7,-17]], - [[ 1, 1, 0],[ 17,-17, -7]], - [[ 0, 1, 1],[ -7, 17,-17]], - [[ 1, 0, -1],[-17, 7,-17]], - [[ 1, 1, 0],[-17, 17, -7]], - ],dtype=float), - 'cI' : _np.array([ - [[ 1, 1, -1],[ 12, 5, 17]], - [[ -1, 1, 1],[ 17, 12, 5]], - [[ 1, -1, 1],[ 5, 17, 12]], - [[ -1, -1, -1],[-12, -5, 17]], - [[ 1, -1, 1],[-17,-12, 5]], - [[ 1, -1, -1],[ -5,-17, 12]], - [[ -1, 1, -1],[ 12, -5,-17]], - [[ 1, 1, 1],[ 17,-12, -5]], - [[ -1, -1, 1],[ 5,-17,-12]], - [[ 1, -1, -1],[-12, 5,-17]], - [[ -1, -1, 1],[-17, 12, -5]], - [[ -1, -1, -1],[ -5, 17,-12]], - [[ 1, -1, 1],[ 12, 17, 5]], - [[ 1, 1, -1],[ 5, 12, 17]], - [[ -1, 1, 1],[ 17, 5, 12]], - [[ -1, 1, 1],[-12,-17, 5]], - [[ -1, -1, -1],[ -5,-12, 17]], - [[ -1, 1, -1],[-17, -5, 12]], - [[ -1, -1, 1],[ 12,-17, -5]], - [[ -1, 1, -1],[ 5,-12,-17]], - [[ 1, 1, 1],[ 17, -5,-12]], - [[ 1, 1, 1],[-12, 17, -5]], - [[ 1, -1, -1],[ -5, 12,-17]], - [[ 1, 1, -1],[-17, 5,-12]], - ],dtype=float), - }, - 'NW': { - 'cF' : _np.array([ - [[ 2, -1, -1],[ 1, 1, 1]], - [[ -1, 2, -1],[ 1, 1, 1]], - [[ -1, -1, 2],[ 1, 1, 1]], - [[ -2, -1, -1],[ -1, 1, 1]], - [[ 1, 2, -1],[ -1, 1, 1]], - [[ 1, -1, 2],[ -1, 1, 1]], - [[ 2, 1, -1],[ 1, -1, 1]], - [[ -1, -2, -1],[ 1, -1, 1]], - [[ -1, 1, 2],[ 1, -1, 1]], - [[ 2, -1, 1],[ -1, -1, 1]], - [[ -1, 2, 1],[ -1, -1, 1]], - [[ -1, -1, -2],[ -1, -1, 1]], - ],dtype=float), - 'cI' : _np.array([ - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - ],dtype=float), - }, - 'Pitsch': { - 'cF' : _np.array([ - [[ 1, 0, 1],[ 0, 1, 0]], - [[ 1, 1, 0],[ 0, 0, 1]], - [[ 0, 1, 1],[ 1, 0, 0]], - [[ 0, 1, -1],[ 1, 0, 0]], - [[ -1, 0, 1],[ 0, 1, 0]], - [[ 1, -1, 0],[ 0, 0, 1]], - [[ 1, 0, -1],[ 0, 1, 0]], - [[ -1, 1, 0],[ 0, 0, 1]], - [[ 0, -1, 1],[ 1, 0, 0]], - [[ 0, 1, 1],[ 1, 0, 0]], - [[ 1, 0, 1],[ 0, 1, 0]], - [[ 1, 1, 0],[ 0, 0, 1]], - ],dtype=float), - 'cI' : _np.array([ - [[ 1, -1, 1],[ -1, 0, 1]], - [[ 1, 1, -1],[ 1, -1, 0]], - [[ -1, 1, 1],[ 0, 1, -1]], - [[ -1, 1, -1],[ 0, -1, -1]], - [[ -1, -1, 1],[ -1, 0, -1]], - [[ 1, -1, -1],[ -1, -1, 0]], - [[ 1, -1, -1],[ -1, 0, -1]], - [[ -1, 1, -1],[ -1, -1, 0]], - [[ -1, -1, 1],[ 0, -1, -1]], - [[ -1, 1, 1],[ 0, -1, 1]], - [[ 1, -1, 1],[ 1, 0, -1]], - [[ 1, 1, -1],[ -1, 1, 0]], - ],dtype=float), - }, - 'Bain': { - 'cF' : _np.array([ - [[ 0, 1, 0],[ 1, 0, 0]], - [[ 0, 0, 1],[ 0, 1, 0]], - [[ 1, 0, 0],[ 0, 0, 1]], - ],dtype=float), - 'cI' : _np.array([ - [[ 0, 1, 1],[ 1, 0, 0]], - [[ 1, 0, 1],[ 0, 1, 0]], - [[ 1, 1, 0],[ 0, 0, 1]], - ],dtype=float), - }, - 'Burgers' : { - 'cI' : _np.array([ - [[ -1, 1, 1],[ 1, 1, 0]], - [[ -1, 1, -1],[ 1, 1, 0]], - [[ 1, 1, 1],[ 1, -1, 0]], - [[ 1, 1, -1],[ 1, -1, 0]], - - [[ 1, 1, -1],[ 1, 0, 1]], - [[ -1, 1, 1],[ 1, 0, 1]], - [[ 1, 1, 1],[ -1, 0, 1]], - [[ 1, -1, 1],[ -1, 0, 1]], - - [[ -1, 1, -1],[ 0, 1, 1]], - [[ 1, 1, -1],[ 0, 1, 1]], - [[ -1, 1, 1],[ 0, -1, 1]], - [[ 1, 1, 1],[ 0, -1, 1]], - ],dtype=float), - 'hP' : _np.array([ - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - ],dtype=float), - }, -} diff --git a/python/tests/test_Lattice.py b/python/tests/test_Lattice.py new file mode 100644 index 000000000..c9593301c --- /dev/null +++ b/python/tests/test_Lattice.py @@ -0,0 +1,64 @@ +import pytest +import numpy as np + +from damask import Lattice + +class TestLattice: + + def test_double_to_lattice(self): + L = Lattice('cF') + with pytest.raises(KeyError): + L.to_lattice(direction=np.ones(3),plane=np.ones(3)) + + def test_double_to_frame(self): + L = Lattice('cF') + with pytest.raises(KeyError): + L.to_frame(uvw=np.ones(3),hkl=np.ones(3)) + + @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', + [ + ('aP',0.5,2.0,3.0,0.8,0.5,1.2), + ('mP',1.0,2.0,3.0,np.pi/2,0.5,np.pi/2), + ('oI',0.5,1.5,3.0,np.pi/2,np.pi/2,np.pi/2), + ('tP',0.5,0.5,3.0,np.pi/2,np.pi/2,np.pi/2), + ('hP',1.0,None,1.6,np.pi/2,np.pi/2,2*np.pi/3), + ('cF',1.0,1.0,None,np.pi/2,np.pi/2,np.pi/2), + ]) + def test_bases_contraction(self,lattice,a,b,c,alpha,beta,gamma): + L = Lattice(lattice=lattice, + a=a,b=b,c=c, + alpha=alpha,beta=beta,gamma=gamma) + assert np.allclose(np.eye(3),np.einsum('ik,jk',L.basis_real,L.basis_reciprocal)) + + + @pytest.mark.parametrize('keyFrame,keyLattice',[('uvw','direction'),('hkl','plane'),]) + @pytest.mark.parametrize('vector',np.array([ + [1.,1.,1.], + [-2.,3.,0.5], + [0.,0.,1.], + [1.,1.,1.], + [2.,2.,2.], + [0.,1.,1.], + ])) + @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', + [ + ('aP',0.5,2.0,3.0,0.8,0.5,1.2), + ('mP',1.0,2.0,3.0,np.pi/2,0.5,np.pi/2), + ('oI',0.5,1.5,3.0,np.pi/2,np.pi/2,np.pi/2), + ('tP',0.5,0.5,3.0,np.pi/2,np.pi/2,np.pi/2), + ('hP',1.0,1.0,1.6,np.pi/2,np.pi/2,2*np.pi/3), + ('cF',1.0,1.0,1.0,np.pi/2,np.pi/2,np.pi/2), + ]) + def test_to_frame_to_lattice(self,lattice,a,b,c,alpha,beta,gamma,vector,keyFrame,keyLattice): + L = Lattice(lattice=lattice, + a=a,b=b,c=c, + alpha=alpha,beta=beta,gamma=gamma) + assert np.allclose(vector, + L.to_frame(**{keyFrame:L.to_lattice(**{keyLattice:vector})})) + + + @pytest.mark.parametrize('model',['Bain','KS','GT','GT_prime','NW','Pitsch','Burgers']) + def test_relationship_definition(self,model): + m,o = list(Lattice._orientation_relationships[model]) + assert Lattice._orientation_relationships[model][m].shape[:-1] == \ + Lattice._orientation_relationships[model][o].shape[:-1] diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index 471dada58..a1302bcb7 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -7,7 +7,6 @@ from damask import Orientation from damask import Table from damask import util from damask import grid_filters -from damask import lattice from damask import _orientation crystal_families = set(_orientation.lattice_symmetries.values()) @@ -341,29 +340,6 @@ class TestOrientation: with pytest.raises(KeyError): Orientation(family=invalid_family) - def test_invalid_rot(self): - with pytest.raises(TypeError): - Orientation.from_random(family='cubic') * np.ones(3) - - def test_missing_symmetry_immutable(self): - with pytest.raises(KeyError): - Orientation(lattice=None).immutable # noqa - - def test_missing_symmetry_basis_real(self): - with pytest.raises(KeyError): - Orientation(lattice=None).basis_real # noqa - - def test_missing_symmetry_basis_reciprocal(self): - with pytest.raises(KeyError): - Orientation(lattice=None).basis_reciprocal # noqa - - def test_double_to_lattice(self): - with pytest.raises(KeyError): - Orientation().to_lattice(direction=np.ones(3),plane=np.ones(3)) # noqa - - def test_double_to_frame(self): - with pytest.raises(KeyError): - Orientation().to_frame(uvw=np.ones(3),hkl=np.ones(3)) # noqa @pytest.mark.parametrize('relation',[None,'Peter','Paul']) def test_unknown_relation(self,relation): @@ -395,12 +371,6 @@ class TestOrientation: o = Orientation(family='cubic') # noqa with pytest.raises(ValueError): eval(f'o.{function}(np.ones(4))') - - @pytest.mark.parametrize('model',lattice.relations) - def test_relationship_definition(self,model): - m,o = list(lattice.relations[model]) - assert lattice.relations[model][m].shape[:-1] == lattice.relations[model][o].shape[:-1] - @pytest.mark.parametrize('model',['Bain','KS','GT','GT_prime','NW','Pitsch']) @pytest.mark.parametrize('lattice',['cF','cI']) def test_relationship_vectorize(self,set_of_quaternions,lattice,model): @@ -441,46 +411,6 @@ class TestOrientation: ) assert np.allclose(o.to_frame(uvw=np.eye(3)),basis), 'Lattice basis disagrees with initialization' - @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', - [ - ('aP',0.5,2.0,3.0,0.8,0.5,1.2), - ('mP',1.0,2.0,3.0,np.pi/2,0.5,np.pi/2), - ('oI',0.5,1.5,3.0,np.pi/2,np.pi/2,np.pi/2), - ('tP',0.5,0.5,3.0,np.pi/2,np.pi/2,np.pi/2), - ('hP',1.0,None,1.6,np.pi/2,np.pi/2,2*np.pi/3), - ('cF',1.0,1.0,None,np.pi/2,np.pi/2,np.pi/2), - ]) - def test_bases_contraction(self,lattice,a,b,c,alpha,beta,gamma): - L = Orientation(lattice=lattice, - a=a,b=b,c=c, - alpha=alpha,beta=beta,gamma=gamma) - assert np.allclose(np.eye(3),np.einsum('ik,jk',L.basis_real,L.basis_reciprocal)) - - @pytest.mark.parametrize('keyFrame,keyLattice',[('uvw','direction'),('hkl','plane'),]) - @pytest.mark.parametrize('vector',np.array([ - [1.,1.,1.], - [-2.,3.,0.5], - [0.,0.,1.], - [1.,1.,1.], - [2.,2.,2.], - [0.,1.,1.], - ])) - @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', - [ - ('aP',0.5,2.0,3.0,0.8,0.5,1.2), - ('mP',1.0,2.0,3.0,np.pi/2,0.5,np.pi/2), - ('oI',0.5,1.5,3.0,np.pi/2,np.pi/2,np.pi/2), - ('tP',0.5,0.5,3.0,np.pi/2,np.pi/2,np.pi/2), - ('hP',1.0,1.0,1.6,np.pi/2,np.pi/2,2*np.pi/3), - ('cF',1.0,1.0,1.0,np.pi/2,np.pi/2,np.pi/2), - ]) - def test_to_frame_to_lattice(self,lattice,a,b,c,alpha,beta,gamma,vector,keyFrame,keyLattice): - L = Orientation(lattice=lattice, - a=a,b=b,c=c, - alpha=alpha,beta=beta,gamma=gamma) - assert np.allclose(vector, - L.to_frame(**{keyFrame:L.to_lattice(**{keyLattice:vector})})) - @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', [ From 87e94b6cf4341d1add582eb8bfc460b40120888c Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 2 Jun 2021 17:25:24 +0200 Subject: [PATCH 05/27] sorting most functions require only lattice family, functions that require full lattice details are at the end --- python/damask/_orientation.py | 377 +++++++++++++++++----------------- 1 file changed, 189 insertions(+), 188 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index b2ee9628b..0810c6624 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -132,7 +132,7 @@ class Orientation(Rotation): """ super().__init__(rotation) - if family in set(lattice_symmetries.values()) and lattice is None: + if family in set(lattice_symmetries.values()) and lattice is None: self.family = family self.lattice = None @@ -178,6 +178,11 @@ class Orientation(Rotation): + ([f'Crystal family {self.family}']) + [super().__repr__()]) + @property + def parameters(self): + """Return lattice parameters a, b, c, alpha, beta, gamma.""" + return (self.a,self.b,self.c,self.alpha,self.beta,self.gamma) + def __copy__(self,**kwargs): """Create deep copy.""" @@ -533,193 +538,6 @@ class Orientation(Rotation): return np.ones_like(rho[...,0],dtype=bool) - def relation_operations(self,model,return_lattice=False): - """ - Crystallographic orientation relationships for phase transformations. - - Parameters - ---------- - model : str - Name of orientation relationship. - return_lattice : bool, optional - Return the target lattice in addition. - - Returns - ------- - operations : Rotations - Rotations characterizing the orientation relationship. - - References - ---------- - S. Morito et al., Journal of Alloys and Compounds 577:s587-s592, 2013 - https://doi.org/10.1016/j.jallcom.2012.02.004 - - K. Kitahara et al., Acta Materialia 54(5):1279-1288, 2006 - https://doi.org/10.1016/j.actamat.2005.11.001 - - Y. He et al., Journal of Applied Crystallography 39:72-81, 2006 - https://doi.org/10.1107/S0021889805038276 - - H. Kitahara et al., Materials Characterization 54(4-5):378-386, 2005 - https://doi.org/10.1016/j.matchar.2004.12.015 - - Y. He et al., Acta Materialia 53(4):1179-1190, 2005 - https://doi.org/10.1016/j.actamat.2004.11.021 - - """ - if model not in self.orientation_relationships: - raise KeyError(f'unknown orientation relationship "{model}"') - r = self.orientation_relationships[model] - - sl = self.lattice - ol = (set(r)-{sl}).pop() - m = r[sl] - 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 util.Bravais_to_Miller(uvtw=m[...,0,0:4]) - p_[...,1,:] = m[...,1,:] if m.shape[-1] == 3 else util.Bravais_to_Miller(hkil=m[...,1,0:4]) - _p[...,0,:] = o[...,0,:] if o.shape[-1] == 3 else util.Bravais_to_Miller(uvtw=o[...,0,0:4]) - _p[...,1,:] = o[...,1,:] if o.shape[-1] == 3 else util.Bravais_to_Miller(hkil=o[...,1,0:4]) - - return (Rotation.from_parallel(p_,_p),ol) \ - if return_lattice else \ - Rotation.from_parallel(p_,_p) - - - def related(self,model): - """ - Orientations derived from the given relationship. - - One dimension (length according to number of related orientations) - is added to the left of the Rotation array. - - """ - o,lattice = self.relation_operations(model,return_lattice=True) - target = Orientation(lattice=lattice) - o = o.broadcast_to(o.shape+self.shape,mode='right') - return self.copy(rotation=o*Rotation(self.quaternion).broadcast_to(o.shape,mode='left'), - lattice=lattice, - b = self.b if target.ratio['b'] is None else self.a*target.ratio['b'], - c = self.c if target.ratio['c'] is None else self.a*target.ratio['c'], - alpha = None if 'alpha' in target.immutable else self.alpha, - beta = None if 'beta' in target.immutable else self.beta, - gamma = None if 'gamma' in target.immutable else self.gamma, - ) - - - @property - def parameters(self): - """Return lattice parameters a, b, c, alpha, beta, gamma.""" - return (self.a,self.b,self.c,self.alpha,self.beta,self.gamma) - - - def in_SST(self,vector,proper=False): - """ - Check whether given crystal frame vector falls into standard stereographic triangle of own symmetry. - - Parameters - ---------- - vector : numpy.ndarray of shape (...,3) - Vector to check. - proper : bool, optional - Consider only vectors with z >= 0, hence combine two neighboring SSTs. - Defaults to False. - - Returns - ------- - in : numpy.ndarray of shape (...) - Boolean array indicating whether vector falls into SST. - - """ - if not isinstance(vector,np.ndarray) or vector.shape[-1] != 3: - raise ValueError('input is not a field of three-dimensional vectors') - - if self.basis is None: # direct exit for no symmetry - return np.ones_like(vector[...,0],bool) - - if proper: - components_proper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.basis['proper'], vector.shape+(3,)), - vector), 12) - components_improper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.basis['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.basis['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,in_SST=True,proper=False): - """ - Map vector to RGB color within standard stereographic triangle of own symmetry. - - Parameters - ---------- - vector : numpy.ndarray of shape (...,3) - Vector to colorize. - in_SST : bool, optional - Consider symmetrically equivalent orientations such that poles are located in SST. - Defaults to True. - proper : bool, optional - Consider only vectors with z >= 0, hence combine two neighboring SSTs (with mirrored colors). - Defaults to False. - - Returns - ------- - rgb : numpy.ndarray of shape (...,3) - RGB array of IPF colors. - - Examples - -------- - Inverse pole figure color of the e_3 direction for a crystal in "Cube" orientation with cubic symmetry: - - >>> o = damask.Orientation(lattice='cubic') - >>> o.IPF_color([0,0,1]) - array([1., 0., 0.]) - - """ - if np.array(vector).shape[-1] != 3: - 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,)) - - if self.basis is None: # direct exit for no symmetry - return np.zeros_like(vector_) - - if proper: - components_proper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.basis['proper'], vector_.shape+(3,)), - vector_), 12) - components_improper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.basis['improper'], vector_.shape+(3,)), - vector_), 12) - 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_proper,components_improper) - else: - components = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.basis['improper'], vector_.shape+(3,)), - np.block([vector_[...,:2],np.abs(vector_[...,2:3])])), 12) - - 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 - - return rgb - - def disorientation(self,other,return_operators=False): """ Calculate disorientation between myself and given other orientation. @@ -874,6 +692,114 @@ class Orientation(Rotation): ) + def in_SST(self,vector,proper=False): + """ + Check whether given crystal frame vector falls into standard stereographic triangle of own symmetry. + + Parameters + ---------- + vector : numpy.ndarray of shape (...,3) + Vector to check. + proper : bool, optional + Consider only vectors with z >= 0, hence combine two neighboring SSTs. + Defaults to False. + + Returns + ------- + in : numpy.ndarray of shape (...) + Boolean array indicating whether vector falls into SST. + + """ + if not isinstance(vector,np.ndarray) or vector.shape[-1] != 3: + raise ValueError('input is not a field of three-dimensional vectors') + + if self.basis is None: # direct exit for no symmetry + return np.ones_like(vector[...,0],bool) + + if proper: + components_proper = np.around(np.einsum('...ji,...i', + np.broadcast_to(self.basis['proper'], vector.shape+(3,)), + vector), 12) + components_improper = np.around(np.einsum('...ji,...i', + np.broadcast_to(self.basis['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.basis['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,in_SST=True,proper=False): + """ + Map vector to RGB color within standard stereographic triangle of own symmetry. + + Parameters + ---------- + vector : numpy.ndarray of shape (...,3) + Vector to colorize. + in_SST : bool, optional + Consider symmetrically equivalent orientations such that poles are located in SST. + Defaults to True. + proper : bool, optional + Consider only vectors with z >= 0, hence combine two neighboring SSTs (with mirrored colors). + Defaults to False. + + Returns + ------- + rgb : numpy.ndarray of shape (...,3) + RGB array of IPF colors. + + Examples + -------- + Inverse pole figure color of the e_3 direction for a crystal in "Cube" orientation with cubic symmetry: + + >>> o = damask.Orientation(lattice='cubic') + >>> o.IPF_color([0,0,1]) + array([1., 0., 0.]) + + """ + if np.array(vector).shape[-1] != 3: + 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,)) + + if self.basis is None: # direct exit for no symmetry + return np.zeros_like(vector_) + + if proper: + components_proper = np.around(np.einsum('...ji,...i', + np.broadcast_to(self.basis['proper'], vector_.shape+(3,)), + vector_), 12) + components_improper = np.around(np.einsum('...ji,...i', + np.broadcast_to(self.basis['improper'], vector_.shape+(3,)), + vector_), 12) + 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_proper,components_improper) + else: + components = np.around(np.einsum('...ji,...i', + np.broadcast_to(self.basis['improper'], vector_.shape+(3,)), + np.block([vector_[...,:2],np.abs(vector_[...,2:3])])), 12) + + 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 + + return rgb + + + # functions that require lattice, not just family + def to_pole(self,*,uvw=None,hkl=None,with_symmetry=False): """ Calculate lab frame vector along lattice direction [uvw] or plane normal (hkl). @@ -938,3 +864,78 @@ class Orientation(Rotation): return ~self.broadcast_to( self.shape+P.shape[:-2],mode='right') \ @ np.broadcast_to(P,self.shape+P.shape) + + + def relation_operations(self,model,return_lattice=False): + """ + Crystallographic orientation relationships for phase transformations. + + Parameters + ---------- + model : str + Name of orientation relationship. + return_lattice : bool, optional + Return the target lattice in addition. + + Returns + ------- + operations : Rotations + Rotations characterizing the orientation relationship. + + References + ---------- + S. Morito et al., Journal of Alloys and Compounds 577:s587-s592, 2013 + https://doi.org/10.1016/j.jallcom.2012.02.004 + + K. Kitahara et al., Acta Materialia 54(5):1279-1288, 2006 + https://doi.org/10.1016/j.actamat.2005.11.001 + + Y. He et al., Journal of Applied Crystallography 39:72-81, 2006 + https://doi.org/10.1107/S0021889805038276 + + H. Kitahara et al., Materials Characterization 54(4-5):378-386, 2005 + https://doi.org/10.1016/j.matchar.2004.12.015 + + Y. He et al., Acta Materialia 53(4):1179-1190, 2005 + https://doi.org/10.1016/j.actamat.2004.11.021 + + """ + if model not in self.orientation_relationships: + raise KeyError(f'unknown orientation relationship "{model}"') + r = self.orientation_relationships[model] + + sl = self.lattice + ol = (set(r)-{sl}).pop() + m = r[sl] + 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 util.Bravais_to_Miller(uvtw=m[...,0,0:4]) + p_[...,1,:] = m[...,1,:] if m.shape[-1] == 3 else util.Bravais_to_Miller(hkil=m[...,1,0:4]) + _p[...,0,:] = o[...,0,:] if o.shape[-1] == 3 else util.Bravais_to_Miller(uvtw=o[...,0,0:4]) + _p[...,1,:] = o[...,1,:] if o.shape[-1] == 3 else util.Bravais_to_Miller(hkil=o[...,1,0:4]) + + return (Rotation.from_parallel(p_,_p),ol) \ + if return_lattice else \ + Rotation.from_parallel(p_,_p) + + + def related(self,model): + """ + Orientations derived from the given relationship. + + One dimension (length according to number of related orientations) + is added to the left of the Rotation array. + + """ + o,lattice = self.relation_operations(model,return_lattice=True) + target = Orientation(lattice=lattice) + o = o.broadcast_to(o.shape+self.shape,mode='right') + return self.copy(rotation=o*Rotation(self.quaternion).broadcast_to(o.shape,mode='left'), + lattice=lattice, + b = self.b if target.ratio['b'] is None else self.a*target.ratio['b'], + c = self.c if target.ratio['c'] is None else self.a*target.ratio['c'], + alpha = None if 'alpha' in target.immutable else self.alpha, + beta = None if 'beta' in target.immutable else self.beta, + gamma = None if 'gamma' in target.immutable else self.gamma, + ) From 3b150ddbea0aabc2d748a3efa06aeff49fb1e393 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 3 Jun 2021 09:15:12 +0200 Subject: [PATCH 06/27] simplified/separated --- python/damask/_lattice.py | 59 ++++++++++++++++++++++++++++++++--- python/damask/_orientation.py | 58 ++-------------------------------- 2 files changed, 57 insertions(+), 60 deletions(-) diff --git a/python/damask/_lattice.py b/python/damask/_lattice.py index c9303968c..de72e7a50 100644 --- a/python/damask/_lattice.py +++ b/python/damask/_lattice.py @@ -2,6 +2,7 @@ import numpy as np from . import util from . import LatticeFamily +from . import Rotation lattice_symmetries = { 'aP': 'triclinic', @@ -205,6 +206,56 @@ class Lattice(LatticeFamily): 'plane': master[:,3:6]} + def relation_operations(self,model): + """ + Crystallographic orientation relationships for phase transformations. + + Parameters + ---------- + model : str + Name of orientation relationship. + + Returns + ------- + operations : (string, damask.Rotation) + Rotations characterizing the orientation relationship. + + References + ---------- + S. Morito et al., Journal of Alloys and Compounds 577:s587-s592, 2013 + https://doi.org/10.1016/j.jallcom.2012.02.004 + + K. Kitahara et al., Acta Materialia 54(5):1279-1288, 2006 + https://doi.org/10.1016/j.actamat.2005.11.001 + + Y. He et al., Journal of Applied Crystallography 39:72-81, 2006 + https://doi.org/10.1107/S0021889805038276 + + H. Kitahara et al., Materials Characterization 54(4-5):378-386, 2005 + https://doi.org/10.1016/j.matchar.2004.12.015 + + Y. He et al., Acta Materialia 53(4):1179-1190, 2005 + https://doi.org/10.1016/j.actamat.2004.11.021 + + """ + if model not in self.orientation_relationships: + raise KeyError(f'unknown orientation relationship "{model}"') + r = self.orientation_relationships[model] + + sl = self.lattice + ol = (set(r)-{sl}).pop() + m = r[sl] + 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 util.Bravais_to_Miller(uvtw=m[...,0,0:4]) + p_[...,1,:] = m[...,1,:] if m.shape[-1] == 3 else util.Bravais_to_Miller(hkil=m[...,1,0:4]) + _p[...,0,:] = o[...,0,:] if o.shape[-1] == 3 else util.Bravais_to_Miller(uvtw=o[...,0,0:4]) + _p[...,1,:] = o[...,1,:] if o.shape[-1] == 3 else util.Bravais_to_Miller(hkil=o[...,1,0:4]) + + return (ol,Rotation.from_parallel(p_,_p)) + + @property def orientation_relationships(self): return {k:v for k,v in self._orientation_relationships.items() if self.lattice in v} @@ -620,12 +671,12 @@ class Lattice(LatticeFamily): [[ -1, 1, -1],[ 1, 1, 0]], [[ 1, 1, 1],[ 1, -1, 0]], [[ 1, 1, -1],[ 1, -1, 0]], - + [[ 1, 1, -1],[ 1, 0, 1]], [[ -1, 1, 1],[ 1, 0, 1]], [[ 1, 1, 1],[ -1, 0, 1]], [[ 1, -1, 1],[ -1, 0, 1]], - + [[ -1, 1, -1],[ 0, 1, 1]], [[ 1, 1, -1],[ 0, 1, 1]], [[ -1, 1, 1],[ 0, -1, 1]], @@ -636,12 +687,12 @@ class Lattice(LatticeFamily): [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 0810c6624..bcadea06c 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -167,7 +167,7 @@ class Orientation(Rotation): self.to_frame = l.to_frame self.kinematics = l.kinematics - self.orientation_relationships = l.orientation_relationships + self.relation_operations = l.relation_operations else: raise KeyError(f'no valid family or lattice') @@ -866,60 +866,6 @@ class Orientation(Rotation): @ np.broadcast_to(P,self.shape+P.shape) - def relation_operations(self,model,return_lattice=False): - """ - Crystallographic orientation relationships for phase transformations. - - Parameters - ---------- - model : str - Name of orientation relationship. - return_lattice : bool, optional - Return the target lattice in addition. - - Returns - ------- - operations : Rotations - Rotations characterizing the orientation relationship. - - References - ---------- - S. Morito et al., Journal of Alloys and Compounds 577:s587-s592, 2013 - https://doi.org/10.1016/j.jallcom.2012.02.004 - - K. Kitahara et al., Acta Materialia 54(5):1279-1288, 2006 - https://doi.org/10.1016/j.actamat.2005.11.001 - - Y. He et al., Journal of Applied Crystallography 39:72-81, 2006 - https://doi.org/10.1107/S0021889805038276 - - H. Kitahara et al., Materials Characterization 54(4-5):378-386, 2005 - https://doi.org/10.1016/j.matchar.2004.12.015 - - Y. He et al., Acta Materialia 53(4):1179-1190, 2005 - https://doi.org/10.1016/j.actamat.2004.11.021 - - """ - if model not in self.orientation_relationships: - raise KeyError(f'unknown orientation relationship "{model}"') - r = self.orientation_relationships[model] - - sl = self.lattice - ol = (set(r)-{sl}).pop() - m = r[sl] - 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 util.Bravais_to_Miller(uvtw=m[...,0,0:4]) - p_[...,1,:] = m[...,1,:] if m.shape[-1] == 3 else util.Bravais_to_Miller(hkil=m[...,1,0:4]) - _p[...,0,:] = o[...,0,:] if o.shape[-1] == 3 else util.Bravais_to_Miller(uvtw=o[...,0,0:4]) - _p[...,1,:] = o[...,1,:] if o.shape[-1] == 3 else util.Bravais_to_Miller(hkil=o[...,1,0:4]) - - return (Rotation.from_parallel(p_,_p),ol) \ - if return_lattice else \ - Rotation.from_parallel(p_,_p) - - def related(self,model): """ Orientations derived from the given relationship. @@ -928,7 +874,7 @@ class Orientation(Rotation): is added to the left of the Rotation array. """ - o,lattice = self.relation_operations(model,return_lattice=True) + lattice,o = self.relation_operations(model) target = Orientation(lattice=lattice) o = o.broadcast_to(o.shape+self.shape,mode='right') return self.copy(rotation=o*Rotation(self.quaternion).broadcast_to(o.shape,mode='left'), From 03d3f362e670357bcb1c56d7b8967f2804cfc386 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 3 Jun 2021 09:31:24 +0200 Subject: [PATCH 07/27] equality checks note: doing this type of comparison means: - LatticeFamily('cubic') == Lattice('cF') - Lattice('cF') != LatticeFamily('cubic') we have the same behavior for comparison between Orientation and Rotation --- python/damask/_lattice.py | 12 ++++++++++++ python/damask/_lattice_family.py | 13 +++++++++++++ python/damask/_orientation.py | 12 ++++++------ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/python/damask/_lattice.py b/python/damask/_lattice.py index de72e7a50..1aa3d6e65 100644 --- a/python/damask/_lattice.py +++ b/python/damask/_lattice.py @@ -99,6 +99,18 @@ class Lattice(LatticeFamily): raise ValueError ('Each lattice angle must be less than sum of others') + def __eq__(self,other): + """ + Equal to other. + + Parameters + ---------- + other : Lattice + Lattice to check for equality. + + """ + return self.lattice == other.lattice and self.parameters == other.parameters + @property def parameters(self): """Return lattice parameters a, b, c, alpha, beta, gamma.""" diff --git a/python/damask/_lattice_family.py b/python/damask/_lattice_family.py index d6288cff0..24d91490d 100644 --- a/python/damask/_lattice_family.py +++ b/python/damask/_lattice_family.py @@ -19,6 +19,19 @@ class LatticeFamily(): self.family = family + def __eq__(self,other): + """ + Equal to other. + + Parameters + ---------- + other : LatticeFamily + Lattice family to check for equality. + + """ + return self.family == other.family + + @property def symmetry_operations(self): """Symmetry operations as Rotations.""" diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index bcadea06c..a3aa44564 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -136,7 +136,7 @@ class Orientation(Rotation): self.family = family self.lattice = None - l = LatticeFamily(self.family) # noqa + self.structure = l = LatticeFamily(self.family) # noqa self.immutable = l.immutable self.basis = l.basis self.symmetry_operations = l.symmetry_operations @@ -149,7 +149,7 @@ class Orientation(Rotation): self.family = lattice_symmetries[lattice] self.lattice = lattice - l = Lattice(self.lattice, a,b,c, alpha,beta,gamma, degrees) # noqa + self.structure = l = Lattice(self.lattice, a,b,c, alpha,beta,gamma, degrees) # noqa self.immutable = l.immutable self.basis = l.basis self.symmetry_operations = l.symmetry_operations @@ -210,8 +210,8 @@ class Orientation(Rotation): Orientation to check for equality. """ - matching_type = all([hasattr(other,attr) and getattr(self,attr) == getattr(other,attr) - for attr in ['family','lattice','parameters']]) + matching_type = self.structure == other.structure if hasattr(other,'structure') else \ + False return np.logical_and(matching_type,super(self.__class__,self.reduced).__eq__(other.reduced)) def __ne__(self,other): @@ -248,8 +248,8 @@ class Orientation(Rotation): Mask indicating where corresponding orientations are close. """ - matching_type = all([hasattr(other,attr) and getattr(self,attr) == getattr(other,attr) - for attr in ['family','lattice','parameters']]) + matching_type = self.structure == other.structure if hasattr(other,'structure') else \ + False return np.logical_and(matching_type,super(self.__class__,self.reduced).isclose(other.reduced)) From 4701eea10f450ea61627eb5b4118a5f1060b75ea Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 3 Jun 2021 10:02:49 +0200 Subject: [PATCH 08/27] decoupling Orientation and Lattice/LatticeFamily --- python/damask/_orientation.py | 19 +++++++------------ python/tests/test_Orientation.py | 4 ++-- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index a3aa44564..d661dd475 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -32,9 +32,9 @@ lattice_symmetries = { _parameter_doc = \ """ - family : {'triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic'} + family : {'triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic'} Crystal family. Mutual exclusive with 'lattice' parameter. - lattice : {'aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF'}. + lattice : {'aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF'}. Bravais lattice in Pearson notation. Mutual exclusive with 'family' parameter. a : float, optional @@ -139,7 +139,6 @@ class Orientation(Rotation): self.structure = l = LatticeFamily(self.family) # noqa self.immutable = l.immutable self.basis = l.basis - self.symmetry_operations = l.symmetry_operations self.a = self.b = self.c = None self.alpha = self.beta = self.gamma = None @@ -152,7 +151,6 @@ class Orientation(Rotation): self.structure = l = Lattice(self.lattice, a,b,c, alpha,beta,gamma, degrees) # noqa self.immutable = l.immutable self.basis = l.basis - self.symmetry_operations = l.symmetry_operations self.a = l.a self.b = l.b @@ -178,11 +176,6 @@ class Orientation(Rotation): + ([f'Crystal family {self.family}']) + [super().__repr__()]) - @property - def parameters(self): - """Return lattice parameters a, b, c, alpha, beta, gamma.""" - return (self.a,self.b,self.c,self.alpha,self.beta,self.gamma) - def __copy__(self,**kwargs): """Create deep copy.""" @@ -437,7 +430,8 @@ class Orientation(Rotation): is added to the left of the Rotation array. """ - o = self.symmetry_operations.broadcast_to(self.symmetry_operations.shape+self.shape,mode='right') + sym_ops = self.structure.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')) @@ -817,11 +811,12 @@ class Orientation(Rotation): Lab frame vector (or vectors if with_symmetry) along [uvw] direction or (hkl) plane normal. """ + sym_ops = self.structure.symmetry_operations # ToDo: simplify 'with_symmetry' v = self.to_frame(uvw=uvw,hkl=hkl) if with_symmetry: - v = self.symmetry_operations.broadcast_to(self.symmetry_operations.shape+v.shape[:-1],mode='right') \ - @ np.broadcast_to(v,self.symmetry_operations.shape+v.shape) + v = sym_ops.broadcast_to(sym_ops.shape+v.shape[:-1],mode='right') \ + @ np.broadcast_to(v,sym_ops.shape+v.shape) return ~(self if self.shape+v.shape[:-1] == () else self.broadcast_to(self.shape+v.shape[:-1],mode='right')) \ @ np.broadcast_to(v,self.shape+v.shape) diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index a1302bcb7..ced7022f1 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -75,7 +75,7 @@ class TestOrientation: ]) def test_invalid_init(self,kwargs): with pytest.raises(ValueError): - Orientation(**kwargs).parameters # noqa + Orientation(**kwargs) @pytest.mark.parametrize('kwargs',[ dict(lattice='aP',a=1.0,b=1.1,c=1.2,alpha=np.pi/4,beta=np.pi/3,gamma=np.pi/2), @@ -436,7 +436,7 @@ class TestOrientation: a=a,b=b,c=c, alpha=alpha,beta=beta,gamma=gamma) assert o.to_pole(**{kw:vector,'with_symmetry':with_symmetry}).shape \ - == o.shape + (o.symmetry_operations.shape if with_symmetry else ()) + vector.shape + == o.shape + (o.structure.symmetry_operations.shape if with_symmetry else ()) + vector.shape @pytest.mark.parametrize('lattice',['hP','cI','cF']) def test_Schmid(self,update,ref_path,lattice): From 3c8d06b6f19e4e122878c2e688c2422984efd907 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 3 Jun 2021 10:18:27 +0200 Subject: [PATCH 09/27] encapsulating all lattice(-family) related data --- python/damask/_orientation.py | 94 +++++++++++--------------------- python/tests/test_Orientation.py | 2 +- 2 files changed, 33 insertions(+), 63 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index d661dd475..9edacd790 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -1,4 +1,5 @@ import inspect +import copy import numpy as np @@ -135,37 +136,11 @@ class Orientation(Rotation): if family in set(lattice_symmetries.values()) and lattice is None: self.family = family self.lattice = None - - self.structure = l = LatticeFamily(self.family) # noqa - self.immutable = l.immutable - self.basis = l.basis - - self.a = self.b = self.c = None - self.alpha = self.beta = self.gamma = None - + self.structure = LatticeFamily(self.family) elif lattice in lattice_symmetries: - self.family = lattice_symmetries[lattice] self.lattice = lattice - - self.structure = l = Lattice(self.lattice, a,b,c, alpha,beta,gamma, degrees) # noqa - self.immutable = l.immutable - self.basis = l.basis - - self.a = l.a - self.b = l.b - self.c = l.c - - self.alpha = l.alpha - self.beta = l.beta - self.gamma = l.gamma - - self.ratio = l.ratio - - self.to_frame = l.to_frame - - self.kinematics = l.kinematics - self.relation_operations = l.relation_operations + self.structure = l = Lattice(self.lattice, a,b,c, alpha,beta,gamma, degrees) else: raise KeyError(f'no valid family or lattice') @@ -177,18 +152,12 @@ class Orientation(Rotation): + [super().__repr__()]) - def __copy__(self,**kwargs): + def __copy__(self,rotation=None): """Create deep copy.""" - return self.__class__(rotation= kwargs['rotation'] if 'rotation' in kwargs else self.quaternion, - family = kwargs['family'] if 'family' in kwargs else self.family, - lattice = kwargs['lattice'] if 'lattice' in kwargs else self.lattice, - a = kwargs['a'] if 'a' in kwargs else self.a, - b = kwargs['b'] if 'b' in kwargs else self.b, - c = kwargs['c'] if 'c' in kwargs else self.c, - alpha = kwargs['alpha'] if 'alpha' in kwargs else self.alpha, - beta = kwargs['beta'] if 'beta' in kwargs else self.beta, - gamma = kwargs['gamma'] if 'gamma' in kwargs else self.gamma, - ) + dup = copy.deepcopy(self) + if rotation is not None: + dup.quaternion = Orientation(rotation,family='cubic').quaternion + return dup copy = __copy__ @@ -415,8 +384,8 @@ class Orientation(Rotation): """ o = cls(**kwargs) - x = o.to_frame(uvw=uvw) - z = o.to_frame(hkl=hkl) + x = o.structure.to_frame(uvw=uvw) + z = o.structure.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)))) @@ -707,21 +676,21 @@ 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') - if self.basis is None: # direct exit for no symmetry + if self.structure.basis is None: # direct exit for no symmetry return np.ones_like(vector[...,0],bool) if proper: components_proper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.basis['proper'], vector.shape+(3,)), + np.broadcast_to(self.structure.basis['proper'], vector.shape+(3,)), vector), 12) components_improper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.basis['improper'], vector.shape+(3,)), + np.broadcast_to(self.structure.basis['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.basis['improper'], vector.shape+(3,)), + np.broadcast_to(self.structure.basis['improper'], vector.shape+(3,)), np.block([vector[...,:2],np.abs(vector[...,2:3])])), 12) return np.all(components >= 0.0,axis=-1) @@ -762,15 +731,15 @@ class Orientation(Rotation): vector_ = self.to_SST(vector,proper) if in_SST else \ self @ np.broadcast_to(vector,self.shape+(3,)) - if self.basis is None: # direct exit for no symmetry + if self.structure.basis is None: # direct exit for no symmetry return np.zeros_like(vector_) if proper: components_proper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.basis['proper'], vector_.shape+(3,)), + np.broadcast_to(self.structure.basis['proper'], vector_.shape+(3,)), vector_), 12) components_improper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.basis['improper'], vector_.shape+(3,)), + np.broadcast_to(self.structure.basis['improper'], vector_.shape+(3,)), vector_), 12) in_SST = np.all(components_proper >= 0.0,axis=-1) \ | np.all(components_improper >= 0.0,axis=-1) @@ -778,7 +747,7 @@ class Orientation(Rotation): components_proper,components_improper) else: components = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.basis['improper'], vector_.shape+(3,)), + np.broadcast_to(self.structure.basis['improper'], vector_.shape+(3,)), np.block([vector_[...,:2],np.abs(vector_[...,2:3])])), 12) in_SST = np.all(components >= 0.0,axis=-1) @@ -813,7 +782,7 @@ class Orientation(Rotation): """ sym_ops = self.structure.symmetry_operations # ToDo: simplify 'with_symmetry' - v = self.to_frame(uvw=uvw,hkl=hkl) + v = self.structure.to_frame(uvw=uvw,hkl=hkl) if with_symmetry: v = sym_ops.broadcast_to(sym_ops.shape+v.shape[:-1],mode='right') \ @ np.broadcast_to(v,sym_ops.shape+v.shape) @@ -850,8 +819,8 @@ class Orientation(Rotation): """ try: - d = self.to_frame(uvw=self.kinematics(mode)['direction']) - p = self.to_frame(hkl=self.kinematics(mode)['plane']) + d = self.structure.to_frame(uvw=self.structure.kinematics(mode)['direction']) + p = self.structure.to_frame(hkl=self.structure.kinematics(mode)['plane']) except KeyError: raise (f'"{mode}" not defined for lattice "{self.lattice}"') P = np.einsum('...i,...j',d/np.linalg.norm(d,axis=-1,keepdims=True), @@ -869,14 +838,15 @@ class Orientation(Rotation): is added to the left of the Rotation array. """ - lattice,o = self.relation_operations(model) - target = Orientation(lattice=lattice) + lattice,o = self.structure.relation_operations(model) + target = Lattice(lattice=lattice) + struct = self.structure o = o.broadcast_to(o.shape+self.shape,mode='right') - return self.copy(rotation=o*Rotation(self.quaternion).broadcast_to(o.shape,mode='left'), - lattice=lattice, - b = self.b if target.ratio['b'] is None else self.a*target.ratio['b'], - c = self.c if target.ratio['c'] is None else self.a*target.ratio['c'], - alpha = None if 'alpha' in target.immutable else self.alpha, - beta = None if 'beta' in target.immutable else self.beta, - gamma = None if 'gamma' in target.immutable else self.gamma, - ) + return Orientation(rotation=o*Rotation(self.quaternion).broadcast_to(o.shape,mode='left'), + lattice=lattice, + b = struct.b if target.ratio['b'] is None else struct.a*target.ratio['b'], + c = struct.c if target.ratio['c'] is None else struct.a*target.ratio['c'], + alpha = None if 'alpha' in target.immutable else struct.alpha, + beta = None if 'beta' in target.immutable else struct.beta, + gamma = None if 'gamma' in target.immutable else struct.gamma, + ) diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index ced7022f1..5317ada19 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -409,7 +409,7 @@ class TestOrientation: **dict(zip(['a','b','c'],lengths)), **dict(zip(['alpha','beta','gamma'],np.arccos(cosines))), ) - assert np.allclose(o.to_frame(uvw=np.eye(3)),basis), 'Lattice basis disagrees with initialization' + assert np.allclose(o.structure.to_frame(uvw=np.eye(3)),basis), 'Lattice basis disagrees with initialization' @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', From 0722f4f7543be36704e582fee4eed9a72bc34d30 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Thu, 3 Jun 2021 11:20:38 +0200 Subject: [PATCH 10/27] disable if not possible --- 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 9edacd790..ff419117c 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -137,10 +137,11 @@ class Orientation(Rotation): self.family = family self.lattice = None self.structure = LatticeFamily(self.family) + self.related = self.Schmid = self.to_pole = None elif lattice in lattice_symmetries: self.family = lattice_symmetries[lattice] self.lattice = lattice - self.structure = l = Lattice(self.lattice, a,b,c, alpha,beta,gamma, degrees) + self.structure = Lattice(self.lattice, a,b,c, alpha,beta,gamma, degrees) else: raise KeyError(f'no valid family or lattice') From 41e5f0c06ce4ee33836c47b74d82d663ff44f0b1 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 6 Jun 2021 19:49:29 +0200 Subject: [PATCH 11/27] re-introduced multiple inheritance --- python/damask/_lattice.py | 77 +++++++++++++++++--------------- python/damask/_orientation.py | 72 +++++++++++++---------------- python/tests/test_Lattice.py | 4 +- python/tests/test_Orientation.py | 4 +- 4 files changed, 76 insertions(+), 81 deletions(-) diff --git a/python/damask/_lattice.py b/python/damask/_lattice.py index 1aa3d6e65..51e29c282 100644 --- a/python/damask/_lattice.py +++ b/python/damask/_lattice.py @@ -29,7 +29,8 @@ lattice_symmetries = { class Lattice(LatticeFamily): """Lattice.""" - def __init__(self, + def __init__(self,*, + family = None, lattice = None, a = None,b = None,c = None, alpha = None,beta = None,gamma = None, @@ -57,46 +58,50 @@ class Lattice(LatticeFamily): Angles are given in degrees. Defaults to False. """ - super().__init__(lattice_symmetries[lattice]) + super().__init__(family = lattice_symmetries[lattice] if family is None else family) + self.lattice = lattice + if self.lattice is not None: + self.a = 1 if a is None else a + self.b = b + self.c = c + self.a = float(self.a) if self.a is not None else \ + (self.b / self.ratio['b'] if self.b is not None and self.ratio['b'] is not None else + self.c / self.ratio['c'] if self.c is not None and self.ratio['c'] is not None else None) + self.b = float(self.b) if self.b is not None else \ + (self.a * self.ratio['b'] if self.a is not None and self.ratio['b'] is not None else + self.c / self.ratio['c'] * self.ratio['b'] + if self.c is not None and self.ratio['b'] is not None and self.ratio['c'] is not None else None) + self.c = float(self.c) if self.c is not None else \ + (self.a * self.ratio['c'] if self.a is not None and self.ratio['c'] is not None else + self.b / self.ratio['b'] * self.ratio['c'] + if self.c is not None and self.ratio['b'] is not None and self.ratio['c'] is not None else None) - self.a = 1 if a is None else a - self.b = b - self.c = c - self.a = float(self.a) if self.a is not None else \ - (self.b / self.ratio['b'] if self.b is not None and self.ratio['b'] is not None else - self.c / self.ratio['c'] if self.c is not None and self.ratio['c'] is not None else None) - self.b = float(self.b) if self.b is not None else \ - (self.a * self.ratio['b'] if self.a is not None and self.ratio['b'] is not None else - self.c / self.ratio['c'] * self.ratio['b'] - if self.c is not None and self.ratio['b'] is not None and self.ratio['c'] is not None else None) - self.c = float(self.c) if self.c is not None else \ - (self.a * self.ratio['c'] if self.a is not None and self.ratio['c'] is not None else - self.b / self.ratio['b'] * self.ratio['c'] - if self.c is not None and self.ratio['b'] is not None and self.ratio['c'] is not None else None) + self.alpha = np.radians(alpha) if degrees and alpha is not None else alpha + self.beta = np.radians(beta) if degrees and beta is not None else beta + self.gamma = np.radians(gamma) if degrees and gamma is not None else gamma + if self.alpha is None and 'alpha' in self.immutable: self.alpha = self.immutable['alpha'] + if self.beta is None and 'beta' in self.immutable: self.beta = self.immutable['beta'] + if self.gamma is None and 'gamma' in self.immutable: self.gamma = self.immutable['gamma'] - self.alpha = np.radians(alpha) if degrees and alpha is not None else alpha - self.beta = np.radians(beta) if degrees and beta is not None else beta - self.gamma = np.radians(gamma) if degrees and gamma is not None else gamma - if self.alpha is None and 'alpha' in self.immutable: self.alpha = self.immutable['alpha'] - if self.beta is None and 'beta' in self.immutable: self.beta = self.immutable['beta'] - if self.gamma is None and 'gamma' in self.immutable: self.gamma = self.immutable['gamma'] + if \ + (self.a is None) \ + or (self.b is None or ('b' in self.immutable and self.b != self.immutable['b'] * self.a)) \ + or (self.c is None or ('c' in self.immutable and self.c != self.immutable['c'] * self.b)) \ + or (self.alpha is None or ('alpha' in self.immutable and self.alpha != self.immutable['alpha'])) \ + or (self.beta is None or ( 'beta' in self.immutable and self.beta != self.immutable['beta'])) \ + or (self.gamma is None or ('gamma' in self.immutable and self.gamma != self.immutable['gamma'])): + raise ValueError (f'Incompatible parameters {self.parameters} for crystal family {self.family}') - if \ - (self.a is None) \ - or (self.b is None or ('b' in self.immutable and self.b != self.immutable['b'] * self.a)) \ - or (self.c is None or ('c' in self.immutable and self.c != self.immutable['c'] * self.b)) \ - or (self.alpha is None or ('alpha' in self.immutable and self.alpha != self.immutable['alpha'])) \ - or (self.beta is None or ( 'beta' in self.immutable and self.beta != self.immutable['beta'])) \ - or (self.gamma is None or ('gamma' in self.immutable and self.gamma != self.immutable['gamma'])): - raise ValueError (f'Incompatible parameters {self.parameters} for crystal family {self.family}') - - if np.any(np.array([self.alpha,self.beta,self.gamma]) <= 0): - raise ValueError ('Lattice angles must be positive') - if np.any([np.roll([self.alpha,self.beta,self.gamma],r)[0] - > 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 np.any(np.array([self.alpha,self.beta,self.gamma]) <= 0): + raise ValueError ('Lattice angles must be positive') + if np.any([np.roll([self.alpha,self.beta,self.gamma],r)[0] + > 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') + else: + self.a = self.b = self.c = None + self.alpha = self.beta = self.gamma = None def __eq__(self,other): diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index ff419117c..c26c0b5f9 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -4,7 +4,6 @@ import copy import numpy as np from . import Rotation -from . import LatticeFamily from . import Lattice from . import util from . import tensor @@ -56,7 +55,7 @@ _parameter_doc = \ """ -class Orientation(Rotation): +class Orientation(Rotation,Lattice): """ Representation of crystallographic orientation as combination of rotation and either crystal family or Bravais lattice. @@ -131,19 +130,9 @@ class Orientation(Rotation): Defaults to no rotation. """ - super().__init__(rotation) - - if family in set(lattice_symmetries.values()) and lattice is None: - self.family = family - self.lattice = None - self.structure = LatticeFamily(self.family) - self.related = self.Schmid = self.to_pole = None - elif lattice in lattice_symmetries: - self.family = lattice_symmetries[lattice] - self.lattice = lattice - self.structure = Lattice(self.lattice, a,b,c, alpha,beta,gamma, degrees) - else: - raise KeyError(f'no valid family or lattice') + Rotation.__init__(self,rotation) + Lattice.__init__(self,family=family, lattice=lattice, + a=a,b=b,c=c, alpha=alpha,beta=beta,gamma=gamma, degrees=degrees) def __repr__(self): @@ -173,8 +162,9 @@ class Orientation(Rotation): Orientation to check for equality. """ - matching_type = self.structure == other.structure if hasattr(other,'structure') else \ - False + 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): @@ -211,8 +201,9 @@ class Orientation(Rotation): Mask indicating where corresponding orientations are close. """ - matching_type = self.structure == other.structure if hasattr(other,'structure') else \ - False + 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).isclose(other.reduced)) @@ -385,8 +376,8 @@ class Orientation(Rotation): """ o = cls(**kwargs) - x = o.structure.to_frame(uvw=uvw) - z = o.structure.to_frame(hkl=hkl) + 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)))) @@ -400,7 +391,7 @@ class Orientation(Rotation): is added to the left of the Rotation array. """ - sym_ops = self.structure.symmetry_operations + 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')) @@ -677,21 +668,21 @@ 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') - if self.structure.basis is None: # direct exit for no symmetry + if self.basis is None: # direct exit for no symmetry return np.ones_like(vector[...,0],bool) if proper: components_proper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.structure.basis['proper'], vector.shape+(3,)), + np.broadcast_to(self.basis['proper'], vector.shape+(3,)), vector), 12) components_improper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.structure.basis['improper'], vector.shape+(3,)), + np.broadcast_to(self.basis['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.structure.basis['improper'], vector.shape+(3,)), + np.broadcast_to(self.basis['improper'], vector.shape+(3,)), np.block([vector[...,:2],np.abs(vector[...,2:3])])), 12) return np.all(components >= 0.0,axis=-1) @@ -732,15 +723,15 @@ class Orientation(Rotation): vector_ = self.to_SST(vector,proper) if in_SST else \ self @ np.broadcast_to(vector,self.shape+(3,)) - if self.structure.basis is None: # direct exit for no symmetry + if self.basis is None: # direct exit for no symmetry return np.zeros_like(vector_) if proper: components_proper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.structure.basis['proper'], vector_.shape+(3,)), + np.broadcast_to(self.basis['proper'], vector_.shape+(3,)), vector_), 12) components_improper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.structure.basis['improper'], vector_.shape+(3,)), + np.broadcast_to(self.basis['improper'], vector_.shape+(3,)), vector_), 12) in_SST = np.all(components_proper >= 0.0,axis=-1) \ | np.all(components_improper >= 0.0,axis=-1) @@ -748,7 +739,7 @@ class Orientation(Rotation): components_proper,components_improper) else: components = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.structure.basis['improper'], vector_.shape+(3,)), + np.broadcast_to(self .basis['improper'], vector_.shape+(3,)), np.block([vector_[...,:2],np.abs(vector_[...,2:3])])), 12) in_SST = np.all(components >= 0.0,axis=-1) @@ -781,9 +772,9 @@ class Orientation(Rotation): Lab frame vector (or vectors if with_symmetry) along [uvw] direction or (hkl) plane normal. """ - sym_ops = self.structure.symmetry_operations + sym_ops = self.symmetry_operations # ToDo: simplify 'with_symmetry' - v = self.structure.to_frame(uvw=uvw,hkl=hkl) + v = self.to_frame(uvw=uvw,hkl=hkl) if with_symmetry: v = sym_ops.broadcast_to(sym_ops.shape+v.shape[:-1],mode='right') \ @ np.broadcast_to(v,sym_ops.shape+v.shape) @@ -820,8 +811,8 @@ class Orientation(Rotation): """ try: - d = self.structure.to_frame(uvw=self.structure.kinematics(mode)['direction']) - p = self.structure.to_frame(hkl=self.structure.kinematics(mode)['plane']) + d = self.to_frame(uvw=self.kinematics(mode)['direction']) + p = self.to_frame(hkl=self.kinematics(mode)['plane']) except KeyError: raise (f'"{mode}" not defined for lattice "{self.lattice}"') P = np.einsum('...i,...j',d/np.linalg.norm(d,axis=-1,keepdims=True), @@ -839,15 +830,14 @@ class Orientation(Rotation): is added to the left of the Rotation array. """ - lattice,o = self.structure.relation_operations(model) + lattice,o = self.relation_operations(model) target = Lattice(lattice=lattice) - struct = self.structure o = o.broadcast_to(o.shape+self.shape,mode='right') return Orientation(rotation=o*Rotation(self.quaternion).broadcast_to(o.shape,mode='left'), lattice=lattice, - b = struct.b if target.ratio['b'] is None else struct.a*target.ratio['b'], - c = struct.c if target.ratio['c'] is None else struct.a*target.ratio['c'], - alpha = None if 'alpha' in target.immutable else struct.alpha, - beta = None if 'beta' in target.immutable else struct.beta, - gamma = None if 'gamma' in target.immutable else struct.gamma, + b = self.b if target.ratio['b'] is None else self.a*target.ratio['b'], + c = self.c if target.ratio['c'] is None else self.a*target.ratio['c'], + alpha = None if 'alpha' in target.immutable else self.alpha, + beta = None if 'beta' in target.immutable else self.beta, + gamma = None if 'gamma' in target.immutable else self.gamma, ) diff --git a/python/tests/test_Lattice.py b/python/tests/test_Lattice.py index c9593301c..0e820e1ce 100644 --- a/python/tests/test_Lattice.py +++ b/python/tests/test_Lattice.py @@ -6,12 +6,12 @@ from damask import Lattice class TestLattice: def test_double_to_lattice(self): - L = Lattice('cF') + L = Lattice(lattice='cF') with pytest.raises(KeyError): L.to_lattice(direction=np.ones(3),plane=np.ones(3)) def test_double_to_frame(self): - L = Lattice('cF') + L = Lattice(lattice='cF') with pytest.raises(KeyError): L.to_frame(uvw=np.ones(3),hkl=np.ones(3)) diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index 5317ada19..38559c662 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -409,7 +409,7 @@ class TestOrientation: **dict(zip(['a','b','c'],lengths)), **dict(zip(['alpha','beta','gamma'],np.arccos(cosines))), ) - assert np.allclose(o.structure.to_frame(uvw=np.eye(3)),basis), 'Lattice basis disagrees with initialization' + assert np.allclose(o.to_frame(uvw=np.eye(3)),basis), 'Lattice basis disagrees with initialization' @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', @@ -436,7 +436,7 @@ class TestOrientation: a=a,b=b,c=c, alpha=alpha,beta=beta,gamma=gamma) assert o.to_pole(**{kw:vector,'with_symmetry':with_symmetry}).shape \ - == o.shape + (o.structure.symmetry_operations.shape if with_symmetry else ()) + vector.shape + == 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): From 5202da13ea4976833d3ca03630595cc89327671e Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 7 Jun 2021 21:49:04 +0200 Subject: [PATCH 12/27] better not have a "Lattice" object with "lattice" parameter --- python/damask/__init__.py | 2 +- python/damask/{_lattice.py => _crystal.py} | 2 +- python/damask/_orientation.py | 8 +++--- .../{test_Lattice.py => test_Crystal.py} | 26 +++++++++---------- 4 files changed, 19 insertions(+), 19 deletions(-) rename python/damask/{_lattice.py => _crystal.py} (99%) rename python/tests/{test_Lattice.py => test_Crystal.py} (78%) diff --git a/python/damask/__init__.py b/python/damask/__init__.py index eb333487d..de87444b3 100644 --- a/python/damask/__init__.py +++ b/python/damask/__init__.py @@ -18,7 +18,7 @@ from . import grid_filters # noqa #For example, '_colormap' containsa class called 'Colormap' which is imported as 'damask.Colormap'. from ._rotation import Rotation # noqa from ._lattice_family import LatticeFamily # noqa -from ._lattice import Lattice # noqa +from ._crystal import Crystal # noqa from ._orientation import Orientation # noqa from ._table import Table # noqa from ._vtk import VTK # noqa diff --git a/python/damask/_lattice.py b/python/damask/_crystal.py similarity index 99% rename from python/damask/_lattice.py rename to python/damask/_crystal.py index 51e29c282..9d76bc3ab 100644 --- a/python/damask/_lattice.py +++ b/python/damask/_crystal.py @@ -26,7 +26,7 @@ lattice_symmetries = { } -class Lattice(LatticeFamily): +class Crystal(LatticeFamily): """Lattice.""" def __init__(self,*, diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index c26c0b5f9..fec73c551 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -4,7 +4,7 @@ import copy import numpy as np from . import Rotation -from . import Lattice +from . import Crystal from . import util from . import tensor @@ -55,7 +55,7 @@ _parameter_doc = \ """ -class Orientation(Rotation,Lattice): +class Orientation(Rotation,Crystal): """ Representation of crystallographic orientation as combination of rotation and either crystal family or Bravais lattice. @@ -131,7 +131,7 @@ class Orientation(Rotation,Lattice): """ Rotation.__init__(self,rotation) - Lattice.__init__(self,family=family, lattice=lattice, + Crystal.__init__(self,family=family, lattice=lattice, a=a,b=b,c=c, alpha=alpha,beta=beta,gamma=gamma, degrees=degrees) @@ -831,7 +831,7 @@ class Orientation(Rotation,Lattice): """ lattice,o = self.relation_operations(model) - target = Lattice(lattice=lattice) + target = Crystal(lattice=lattice) o = o.broadcast_to(o.shape+self.shape,mode='right') return Orientation(rotation=o*Rotation(self.quaternion).broadcast_to(o.shape,mode='left'), lattice=lattice, diff --git a/python/tests/test_Lattice.py b/python/tests/test_Crystal.py similarity index 78% rename from python/tests/test_Lattice.py rename to python/tests/test_Crystal.py index 0e820e1ce..8142e03c5 100644 --- a/python/tests/test_Lattice.py +++ b/python/tests/test_Crystal.py @@ -1,19 +1,19 @@ import pytest import numpy as np -from damask import Lattice +from damask import Crystal -class TestLattice: +class TestCrystal: def test_double_to_lattice(self): - L = Lattice(lattice='cF') + c = Crystal(lattice='cF') with pytest.raises(KeyError): - L.to_lattice(direction=np.ones(3),plane=np.ones(3)) + c.to_lattice(direction=np.ones(3),plane=np.ones(3)) def test_double_to_frame(self): - L = Lattice(lattice='cF') + c = Crystal(lattice='cF') with pytest.raises(KeyError): - L.to_frame(uvw=np.ones(3),hkl=np.ones(3)) + c.to_frame(uvw=np.ones(3),hkl=np.ones(3)) @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', [ @@ -25,10 +25,10 @@ class TestLattice: ('cF',1.0,1.0,None,np.pi/2,np.pi/2,np.pi/2), ]) def test_bases_contraction(self,lattice,a,b,c,alpha,beta,gamma): - L = Lattice(lattice=lattice, + c = Crystal(lattice=lattice, a=a,b=b,c=c, alpha=alpha,beta=beta,gamma=gamma) - assert np.allclose(np.eye(3),np.einsum('ik,jk',L.basis_real,L.basis_reciprocal)) + assert np.allclose(np.eye(3),np.einsum('ik,jk',c.basis_real,c.basis_reciprocal)) @pytest.mark.parametrize('keyFrame,keyLattice',[('uvw','direction'),('hkl','plane'),]) @@ -50,15 +50,15 @@ class TestLattice: ('cF',1.0,1.0,1.0,np.pi/2,np.pi/2,np.pi/2), ]) def test_to_frame_to_lattice(self,lattice,a,b,c,alpha,beta,gamma,vector,keyFrame,keyLattice): - L = Lattice(lattice=lattice, + c = Crystal(lattice=lattice, a=a,b=b,c=c, alpha=alpha,beta=beta,gamma=gamma) assert np.allclose(vector, - L.to_frame(**{keyFrame:L.to_lattice(**{keyLattice:vector})})) + c.to_frame(**{keyFrame:c.to_lattice(**{keyLattice:vector})})) @pytest.mark.parametrize('model',['Bain','KS','GT','GT_prime','NW','Pitsch','Burgers']) def test_relationship_definition(self,model): - m,o = list(Lattice._orientation_relationships[model]) - assert Lattice._orientation_relationships[model][m].shape[:-1] == \ - Lattice._orientation_relationships[model][o].shape[:-1] + m,o = list(Crystal._orientation_relationships[model]) + assert Crystal._orientation_relationships[model][m].shape[:-1] == \ + Crystal._orientation_relationships[model][o].shape[:-1] From 92ca010b7c8afa8fa1f5151fb294504edb88ea47 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 4 Jul 2021 23:25:00 +0200 Subject: [PATCH 13/27] Philip's corrections --- python/damask/_crystal.py | 10 +++++----- python/damask/_lattice_family.py | 6 +++--- python/damask/_orientation.py | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/python/damask/_crystal.py b/python/damask/_crystal.py index 9d76bc3ab..3df338a9e 100644 --- a/python/damask/_crystal.py +++ b/python/damask/_crystal.py @@ -90,7 +90,7 @@ class Crystal(LatticeFamily): or (self.b is None or ('b' in self.immutable and self.b != self.immutable['b'] * self.a)) \ or (self.c is None or ('c' in self.immutable and self.c != self.immutable['c'] * self.b)) \ or (self.alpha is None or ('alpha' in self.immutable and self.alpha != self.immutable['alpha'])) \ - or (self.beta is None or ( 'beta' in self.immutable and self.beta != self.immutable['beta'])) \ + or (self.beta is None or ('beta' in self.immutable and self.beta != self.immutable['beta'])) \ or (self.gamma is None or ('gamma' in self.immutable and self.gamma != self.immutable['gamma'])): raise ValueError (f'Incompatible parameters {self.parameters} for crystal family {self.family}') @@ -139,7 +139,7 @@ class Crystal(LatticeFamily): @property def basis_real(self): """ - Calculate orthogonal real space crystal basis. + Return orthogonal real space crystal basis. References ---------- @@ -162,7 +162,7 @@ class Crystal(LatticeFamily): @property def basis_reciprocal(self): - """Calculate reciprocal (dual) crystal basis.""" + """Return reciprocal (dual) crystal basis.""" return np.linalg.inv(self.basis_real.T) @@ -172,13 +172,13 @@ class Crystal(LatticeFamily): Parameters ---------- - direction|normal : numpy.ndarray of shape (...,3) + direction|plane : numpy.ndarray of shape (...,3) Vector along direction or plane normal. Returns ------- Miller : numpy.ndarray of shape (...,3) - lattice vector of direction or plane. + Lattice vector of direction or plane. Use util.scale_to_coprime to convert to (integer) Miller indices. """ diff --git a/python/damask/_lattice_family.py b/python/damask/_lattice_family.py index 24d91490d..e34c02e10 100644 --- a/python/damask/_lattice_family.py +++ b/python/damask/_lattice_family.py @@ -6,7 +6,7 @@ class LatticeFamily(): def __init__(self,family): """ - Symmetry-related operations for crystal families. + Symmetry-related operations for crystal family. Parameters ---------- @@ -15,7 +15,7 @@ class LatticeFamily(): """ if family not in self._immutable.keys(): - raise KeyError(f'invalid lattice family "{family}"') + raise KeyError(f'invalid crystal family "{family}"') self.family = family @@ -40,7 +40,7 @@ class LatticeFamily(): @property def immutable(self): - """Return immutable parameters lattice parameters.""" + """Return immutable lattice parameters.""" return self._immutable[self.family] diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index fec73c551..d1a5e9c7a 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -33,10 +33,10 @@ lattice_symmetries = { _parameter_doc = \ """ family : {'triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic'} - Crystal family. Mutual exclusive with 'lattice' parameter. + Crystal family. Mutually exclusive with 'lattice' parameter. lattice : {'aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF'}. Bravais lattice in Pearson notation. - Mutual exclusive with 'family' parameter. + Mutually exclusive with 'family' parameter. a : float, optional Length of lattice parameter 'a'. b : float, optional @@ -76,7 +76,7 @@ class Orientation(Rotation,Crystal): - triclinic - aP : primitive - - monoclininic + - monoclinic - mP : primitive - mS : base-centered From 2826e61ea171cdbf45fd9d0cfda916caaac5bd09 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 13 Jul 2021 00:14:13 +0200 Subject: [PATCH 14/27] no need to split code over two files --- python/damask/__init__.py | 1 - python/damask/_crystal.py | 114 ++++++++++++++++- python/damask/_lattice_family.py | 208 ------------------------------- python/damask/_orientation.py | 71 +++++++++++ 4 files changed, 181 insertions(+), 213 deletions(-) delete mode 100644 python/damask/_lattice_family.py diff --git a/python/damask/__init__.py b/python/damask/__init__.py index de87444b3..761fd5621 100644 --- a/python/damask/__init__.py +++ b/python/damask/__init__.py @@ -17,7 +17,6 @@ from . import grid_filters # 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 -from ._lattice_family import LatticeFamily # noqa from ._crystal import Crystal # noqa from ._orientation import Orientation # noqa from ._table import Table # noqa diff --git a/python/damask/_crystal.py b/python/damask/_crystal.py index 3df338a9e..525ce9682 100644 --- a/python/damask/_crystal.py +++ b/python/damask/_crystal.py @@ -1,7 +1,6 @@ import numpy as np from . import util -from . import LatticeFamily from . import Rotation lattice_symmetries = { @@ -26,7 +25,7 @@ lattice_symmetries = { } -class Crystal(LatticeFamily): +class Crystal(): """Lattice.""" def __init__(self,*, @@ -42,6 +41,8 @@ class Crystal(LatticeFamily): ---------- lattice : {'aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF'}. Name of the Bravais lattice in Pearson notation. + family : {'triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic'} + Name of the crystal family. a : float, optional Length of lattice parameter 'a'. b : float, optional @@ -58,8 +59,10 @@ class Crystal(LatticeFamily): Angles are given in degrees. Defaults to False. """ - super().__init__(family = lattice_symmetries[lattice] if family is None else family) + if family not in [None] + list(self._immutable.keys()): + raise KeyError(f'invalid crystal family "{family}"') + self.family = lattice_symmetries[lattice] if family is None else family self.lattice = lattice if self.lattice is not None: @@ -114,7 +117,9 @@ class Crystal(LatticeFamily): Lattice to check for equality. """ - return self.lattice == other.lattice and self.parameters == other.parameters + return self.lattice == other.lattice and \ + self.parameters == other.parameters and \ + self.family == other.family @property def parameters(self): @@ -122,6 +127,75 @@ class Crystal(LatticeFamily): return (self.a,self.b,self.c,self.alpha,self.beta,self.gamma) + @property + def immutable(self): + """Return immutable lattice parameters.""" + return self._immutable[self.family] + + + @property + def basis(self): + """ + Corners of the standard triangle. + + Not yet defined for monoclinic. + + + References + ---------- + Bases are computed from + + >>> basis = { + ... 'cubic' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,1.]/np.sqrt(2.), # green + ... [1.,1.,1.]/np.sqrt(3.)]).T), # blue + ... 'hexagonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [np.sqrt(3.),1.,0.]/np.sqrt(4.)]).T), # blue + ... 'tetragonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [1.,1.,0.]/np.sqrt(2.)]).T), # blue + ... 'orthorhombic': np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [0.,1.,0.]]).T), # blue + ... } + + """ + _basis = { + 'cubic': {'improper':np.array([ [-1. , 0. , 1. ], + [ np.sqrt(2.) , -np.sqrt(2.) , 0. ], + [ 0. , np.sqrt(3.) , 0. ] ]), + 'proper':np.array([ [ 0. , -1. , 1. ], + [-np.sqrt(2.) , np.sqrt(2.) , 0. ], + [ np.sqrt(3.) , 0. , 0. ] ]), + }, + 'hexagonal': + {'improper':np.array([ [ 0. , 0. , 1. ], + [ 1. , -np.sqrt(3.) , 0. ], + [ 0. , 2. , 0. ] ]), + 'proper':np.array([ [ 0. , 0. , 1. ], + [-1. , np.sqrt(3.) , 0. ], + [ np.sqrt(3.) , -1. , 0. ] ]), + }, + 'tetragonal': + {'improper':np.array([ [ 0. , 0. , 1. ], + [ 1. , -1. , 0. ], + [ 0. , np.sqrt(2.) , 0. ] ]), + 'proper':np.array([ [ 0. , 0. , 1. ], + [-1. , 1. , 0. ], + [ np.sqrt(2.) , 0. , 0. ] ]), + }, + 'orthorhombic': + {'improper':np.array([ [ 0., 0., 1.], + [ 1., 0., 0.], + [ 0., 1., 0.] ]), + 'proper':np.array([ [ 0., 0., 1.], + [-1., 0., 0.], + [ 0., 1., 0.] ]), + }} + return _basis.get(self.family,None) + + @property def ratio(self): """Return axes ratios of own lattice.""" @@ -717,3 +791,35 @@ class Crystal(LatticeFamily): ],dtype=float), }, } + + _immutable = { + 'cubic': { + 'b': 1.0, + 'c': 1.0, + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': np.pi/2., + }, + 'hexagonal': { + 'b': 1.0, + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': 2.*np.pi/3., + }, + 'tetragonal': { + 'b': 1.0, + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': np.pi/2., + }, + 'orthorhombic': { + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': np.pi/2., + }, + 'monoclinic': { + 'alpha': np.pi/2., + 'gamma': np.pi/2., + }, + 'triclinic': {} + } diff --git a/python/damask/_lattice_family.py b/python/damask/_lattice_family.py deleted file mode 100644 index e34c02e10..000000000 --- a/python/damask/_lattice_family.py +++ /dev/null @@ -1,208 +0,0 @@ -import numpy as np - -from . import Rotation - -class LatticeFamily(): - - def __init__(self,family): - """ - Symmetry-related operations for crystal family. - - Parameters - ---------- - family : {'triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic'} - Name of the crystal family. - - """ - if family not in self._immutable.keys(): - raise KeyError(f'invalid crystal family "{family}"') - self.family = family - - - def __eq__(self,other): - """ - Equal to other. - - Parameters - ---------- - other : LatticeFamily - Lattice family to check for equality. - - """ - return self.family == other.family - - - @property - def symmetry_operations(self): - """Symmetry operations as Rotations.""" - return Rotation.from_quaternion(self._symmetry_operations[self.family],accept_homomorph=True) - - - @property - def immutable(self): - """Return immutable lattice parameters.""" - return self._immutable[self.family] - - - @property - def basis(self): - """ - Corners of the standard triangle. - - Not yet defined for monoclinic. - - - References - ---------- - Bases are computed from - - >>> basis = { - ... 'cubic' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,1.]/np.sqrt(2.), # green - ... [1.,1.,1.]/np.sqrt(3.)]).T), # blue - ... 'hexagonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [np.sqrt(3.),1.,0.]/np.sqrt(4.)]).T), # blue - ... 'tetragonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [1.,1.,0.]/np.sqrt(2.)]).T), # blue - ... 'orthorhombic': np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [0.,1.,0.]]).T), # blue - ... } - - """ - return self._basis.get(self.family,None) - - - _symmetry_operations = { - 'cubic': [ - [ 1.0, 0.0, 0.0, 0.0 ], - [ 0.0, 1.0, 0.0, 0.0 ], - [ 0.0, 0.0, 1.0, 0.0 ], - [ 0.0, 0.0, 0.0, 1.0 ], - [ 0.0, 0.0, 0.5*np.sqrt(2), 0.5*np.sqrt(2) ], - [ 0.0, 0.0, 0.5*np.sqrt(2),-0.5*np.sqrt(2) ], - [ 0.0, 0.5*np.sqrt(2), 0.0, 0.5*np.sqrt(2) ], - [ 0.0, 0.5*np.sqrt(2), 0.0, -0.5*np.sqrt(2) ], - [ 0.0, 0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0 ], - [ 0.0, -0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0 ], - [ 0.5, 0.5, 0.5, 0.5 ], - [-0.5, 0.5, 0.5, 0.5 ], - [-0.5, 0.5, 0.5, -0.5 ], - [-0.5, 0.5, -0.5, 0.5 ], - [-0.5, -0.5, 0.5, 0.5 ], - [-0.5, -0.5, 0.5, -0.5 ], - [-0.5, -0.5, -0.5, 0.5 ], - [-0.5, 0.5, -0.5, -0.5 ], - [-0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], - [ 0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], - [-0.5*np.sqrt(2), 0.0, 0.5*np.sqrt(2), 0.0 ], - [-0.5*np.sqrt(2), 0.0, -0.5*np.sqrt(2), 0.0 ], - [-0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0, 0.0 ], - [-0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0, 0.0 ], - ], - 'hexagonal': [ - [ 1.0, 0.0, 0.0, 0.0 ], - [-0.5*np.sqrt(3), 0.0, 0.0, -0.5 ], - [ 0.5, 0.0, 0.0, 0.5*np.sqrt(3) ], - [ 0.0, 0.0, 0.0, 1.0 ], - [-0.5, 0.0, 0.0, 0.5*np.sqrt(3) ], - [-0.5*np.sqrt(3), 0.0, 0.0, 0.5 ], - [ 0.0, 1.0, 0.0, 0.0 ], - [ 0.0, -0.5*np.sqrt(3), 0.5, 0.0 ], - [ 0.0, 0.5, -0.5*np.sqrt(3), 0.0 ], - [ 0.0, 0.0, 1.0, 0.0 ], - [ 0.0, -0.5, -0.5*np.sqrt(3), 0.0 ], - [ 0.0, 0.5*np.sqrt(3), 0.5, 0.0 ], - ], - 'tetragonal': [ - [ 1.0, 0.0, 0.0, 0.0 ], - [ 0.0, 1.0, 0.0, 0.0 ], - [ 0.0, 0.0, 1.0, 0.0 ], - [ 0.0, 0.0, 0.0, 1.0 ], - [ 0.0, 0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0 ], - [ 0.0, -0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0 ], - [ 0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], - [-0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], - ], - 'orthorhombic': [ - [ 1.0,0.0,0.0,0.0 ], - [ 0.0,1.0,0.0,0.0 ], - [ 0.0,0.0,1.0,0.0 ], - [ 0.0,0.0,0.0,1.0 ], - ], - 'monoclinic': [ - [ 1.0,0.0,0.0,0.0 ], - [ 0.0,0.0,1.0,0.0 ], - ], - 'triclinic': [ - [ 1.0,0.0,0.0,0.0 ], - ]} - - - _immutable = { - 'cubic': { - 'b': 1.0, - 'c': 1.0, - 'alpha': np.pi/2., - 'beta': np.pi/2., - 'gamma': np.pi/2., - }, - 'hexagonal': { - 'b': 1.0, - 'alpha': np.pi/2., - 'beta': np.pi/2., - 'gamma': 2.*np.pi/3., - }, - 'tetragonal': { - 'b': 1.0, - 'alpha': np.pi/2., - 'beta': np.pi/2., - 'gamma': np.pi/2., - }, - 'orthorhombic': { - 'alpha': np.pi/2., - 'beta': np.pi/2., - 'gamma': np.pi/2., - }, - 'monoclinic': { - 'alpha': np.pi/2., - 'gamma': np.pi/2., - }, - 'triclinic': {} - } - - - _basis = { - 'cubic': {'improper':np.array([ [-1. , 0. , 1. ], - [ np.sqrt(2.) , -np.sqrt(2.) , 0. ], - [ 0. , np.sqrt(3.) , 0. ] ]), - 'proper':np.array([ [ 0. , -1. , 1. ], - [-np.sqrt(2.) , np.sqrt(2.) , 0. ], - [ np.sqrt(3.) , 0. , 0. ] ]), - }, - 'hexagonal': - {'improper':np.array([ [ 0. , 0. , 1. ], - [ 1. , -np.sqrt(3.) , 0. ], - [ 0. , 2. , 0. ] ]), - 'proper':np.array([ [ 0. , 0. , 1. ], - [-1. , np.sqrt(3.) , 0. ], - [ np.sqrt(3.) , -1. , 0. ] ]), - }, - 'tetragonal': - {'improper':np.array([ [ 0. , 0. , 1. ], - [ 1. , -1. , 0. ], - [ 0. , np.sqrt(2.) , 0. ] ]), - 'proper':np.array([ [ 0. , 0. , 1. ], - [-1. , 1. , 0. ], - [ np.sqrt(2.) , 0. , 0. ] ]), - }, - 'orthorhombic': - {'improper':np.array([ [ 0., 0., 1.], - [ 1., 0., 0.], - [ 0., 1., 0.] ]), - 'proper':np.array([ [ 0., 0., 1.], - [-1., 0., 0.], - [ 0., 1., 0.] ]), - }} diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index d1a5e9c7a..f6f489b44 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -753,6 +753,77 @@ class Orientation(Rotation,Crystal): return rgb + @property + def symmetry_operations(self): + """Symmetry operations as Rotations.""" + _symmetry_operations = { + 'cubic': [ + [ 1.0, 0.0, 0.0, 0.0 ], + [ 0.0, 1.0, 0.0, 0.0 ], + [ 0.0, 0.0, 1.0, 0.0 ], + [ 0.0, 0.0, 0.0, 1.0 ], + [ 0.0, 0.0, 0.5*np.sqrt(2), 0.5*np.sqrt(2) ], + [ 0.0, 0.0, 0.5*np.sqrt(2),-0.5*np.sqrt(2) ], + [ 0.0, 0.5*np.sqrt(2), 0.0, 0.5*np.sqrt(2) ], + [ 0.0, 0.5*np.sqrt(2), 0.0, -0.5*np.sqrt(2) ], + [ 0.0, 0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0 ], + [ 0.0, -0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0 ], + [ 0.5, 0.5, 0.5, 0.5 ], + [-0.5, 0.5, 0.5, 0.5 ], + [-0.5, 0.5, 0.5, -0.5 ], + [-0.5, 0.5, -0.5, 0.5 ], + [-0.5, -0.5, 0.5, 0.5 ], + [-0.5, -0.5, 0.5, -0.5 ], + [-0.5, -0.5, -0.5, 0.5 ], + [-0.5, 0.5, -0.5, -0.5 ], + [-0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], + [ 0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], + [-0.5*np.sqrt(2), 0.0, 0.5*np.sqrt(2), 0.0 ], + [-0.5*np.sqrt(2), 0.0, -0.5*np.sqrt(2), 0.0 ], + [-0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0, 0.0 ], + [-0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0, 0.0 ], + ], + 'hexagonal': [ + [ 1.0, 0.0, 0.0, 0.0 ], + [-0.5*np.sqrt(3), 0.0, 0.0, -0.5 ], + [ 0.5, 0.0, 0.0, 0.5*np.sqrt(3) ], + [ 0.0, 0.0, 0.0, 1.0 ], + [-0.5, 0.0, 0.0, 0.5*np.sqrt(3) ], + [-0.5*np.sqrt(3), 0.0, 0.0, 0.5 ], + [ 0.0, 1.0, 0.0, 0.0 ], + [ 0.0, -0.5*np.sqrt(3), 0.5, 0.0 ], + [ 0.0, 0.5, -0.5*np.sqrt(3), 0.0 ], + [ 0.0, 0.0, 1.0, 0.0 ], + [ 0.0, -0.5, -0.5*np.sqrt(3), 0.0 ], + [ 0.0, 0.5*np.sqrt(3), 0.5, 0.0 ], + ], + 'tetragonal': [ + [ 1.0, 0.0, 0.0, 0.0 ], + [ 0.0, 1.0, 0.0, 0.0 ], + [ 0.0, 0.0, 1.0, 0.0 ], + [ 0.0, 0.0, 0.0, 1.0 ], + [ 0.0, 0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0 ], + [ 0.0, -0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0 ], + [ 0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], + [-0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], + ], + 'orthorhombic': [ + [ 1.0,0.0,0.0,0.0 ], + [ 0.0,1.0,0.0,0.0 ], + [ 0.0,0.0,1.0,0.0 ], + [ 0.0,0.0,0.0,1.0 ], + ], + 'monoclinic': [ + [ 1.0,0.0,0.0,0.0 ], + [ 0.0,0.0,1.0,0.0 ], + ], + 'triclinic': [ + [ 1.0,0.0,0.0,0.0 ], + ]} + return Rotation.from_quaternion(_symmetry_operations[self.family],accept_homomorph=True) + + +#################################################################################################### # functions that require lattice, not just family def to_pole(self,*,uvw=None,hkl=None,with_symmetry=False): From 2217c3d1437572240fba996713eef627a29630bb Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 18 Jul 2021 15:42:36 +0200 Subject: [PATCH 15/27] unified help --- python/damask/_crystal.py | 11 +++++++---- python/damask/_orientation.py | 10 +++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/python/damask/_crystal.py b/python/damask/_crystal.py index 525ce9682..5d3fa24ea 100644 --- a/python/damask/_crystal.py +++ b/python/damask/_crystal.py @@ -35,14 +35,15 @@ class Crystal(): alpha = None,beta = None,gamma = None, degrees = False): """ - Lattice. + Representation of crystal in terms of crystal family or Bravais lattice. Parameters ---------- - lattice : {'aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF'}. - Name of the Bravais lattice in Pearson notation. - family : {'triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic'} + family : {'triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic'}, optional. Name of the crystal family. + Will be infered if 'lattice' is given. + lattice : {'aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF'}, optional. + Name of the Bravais lattice in Pearson notation. a : float, optional Length of lattice parameter 'a'. b : float, optional @@ -61,6 +62,8 @@ class Crystal(): """ if family not in [None] + list(self._immutable.keys()): raise KeyError(f'invalid crystal family "{family}"') + if lattice is not None and family is not None and family != lattice_symmetries[lattice]: + raise KeyError(f'incompatible family "{family}" for lattice "{lattice}"') self.family = lattice_symmetries[lattice] if family is None else family self.lattice = lattice diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index f6f489b44..a78b6a189 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -32,11 +32,11 @@ lattice_symmetries = { _parameter_doc = \ """ - family : {'triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic'} - Crystal family. Mutually exclusive with 'lattice' parameter. - lattice : {'aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF'}. - Bravais lattice in Pearson notation. - Mutually exclusive with 'family' parameter. + family : {'triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic'}, optional. + Name of the crystal family. + Will be infered if 'lattice' is given. + lattice : {'aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF'}, optional. + Name of the Bravais lattice in Pearson notation. a : float, optional Length of lattice parameter 'a'. b : float, optional From 212a4ed63c344cf4102c691fbb3d9beebc4e663a Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 18 Jul 2021 15:57:51 +0200 Subject: [PATCH 16/27] don't test implementation details --- python/damask/_crystal.py | 67 ++++++++++++++++++------------------ python/tests/test_Crystal.py | 7 ---- 2 files changed, 34 insertions(+), 40 deletions(-) diff --git a/python/damask/_crystal.py b/python/damask/_crystal.py index 5d3fa24ea..4c9a5cc34 100644 --- a/python/damask/_crystal.py +++ b/python/damask/_crystal.py @@ -60,7 +60,7 @@ class Crystal(): Angles are given in degrees. Defaults to False. """ - if family not in [None] + list(self._immutable.keys()): + if family not in [None] + list(lattice_symmetries.values()): raise KeyError(f'invalid crystal family "{family}"') if lattice is not None and family is not None and family != lattice_symmetries[lattice]: raise KeyError(f'incompatible family "{family}" for lattice "{lattice}"') @@ -133,7 +133,38 @@ class Crystal(): @property def immutable(self): """Return immutable lattice parameters.""" - return self._immutable[self.family] + _immutable = { + 'cubic': { + 'b': 1.0, + 'c': 1.0, + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': np.pi/2., + }, + 'hexagonal': { + 'b': 1.0, + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': 2.*np.pi/3., + }, + 'tetragonal': { + 'b': 1.0, + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': np.pi/2., + }, + 'orthorhombic': { + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': np.pi/2., + }, + 'monoclinic': { + 'alpha': np.pi/2., + 'gamma': np.pi/2., + }, + 'triclinic': {} + } + return _immutable[self.family] @property @@ -795,34 +826,4 @@ class Crystal(): }, } - _immutable = { - 'cubic': { - 'b': 1.0, - 'c': 1.0, - 'alpha': np.pi/2., - 'beta': np.pi/2., - 'gamma': np.pi/2., - }, - 'hexagonal': { - 'b': 1.0, - 'alpha': np.pi/2., - 'beta': np.pi/2., - 'gamma': 2.*np.pi/3., - }, - 'tetragonal': { - 'b': 1.0, - 'alpha': np.pi/2., - 'beta': np.pi/2., - 'gamma': np.pi/2., - }, - 'orthorhombic': { - 'alpha': np.pi/2., - 'beta': np.pi/2., - 'gamma': np.pi/2., - }, - 'monoclinic': { - 'alpha': np.pi/2., - 'gamma': np.pi/2., - }, - 'triclinic': {} - } + diff --git a/python/tests/test_Crystal.py b/python/tests/test_Crystal.py index 8142e03c5..5481003c9 100644 --- a/python/tests/test_Crystal.py +++ b/python/tests/test_Crystal.py @@ -55,10 +55,3 @@ class TestCrystal: alpha=alpha,beta=beta,gamma=gamma) assert np.allclose(vector, c.to_frame(**{keyFrame:c.to_lattice(**{keyLattice:vector})})) - - - @pytest.mark.parametrize('model',['Bain','KS','GT','GT_prime','NW','Pitsch','Burgers']) - def test_relationship_definition(self,model): - m,o = list(Crystal._orientation_relationships[model]) - assert Crystal._orientation_relationships[model][m].shape[:-1] == \ - Crystal._orientation_relationships[model][o].shape[:-1] From 86a60283c37eba182e8e20c51c1e33fcc5e7ce05 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 18 Jul 2021 16:02:05 +0200 Subject: [PATCH 17/27] keep data close to where it is used --- python/damask/_crystal.py | 918 +++++++++++++++++++------------------- 1 file changed, 455 insertions(+), 463 deletions(-) diff --git a/python/damask/_crystal.py b/python/damask/_crystal.py index 4c9a5cc34..5769fc9c2 100644 --- a/python/damask/_crystal.py +++ b/python/damask/_crystal.py @@ -172,6 +172,8 @@ class Crystal(): """ Corners of the standard triangle. + Notes + ----- Not yet defined for monoclinic. @@ -180,18 +182,18 @@ class Crystal(): Bases are computed from >>> basis = { - ... 'cubic' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,1.]/np.sqrt(2.), # green - ... [1.,1.,1.]/np.sqrt(3.)]).T), # blue - ... 'hexagonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [np.sqrt(3.),1.,0.]/np.sqrt(4.)]).T), # blue - ... 'tetragonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [1.,1.,0.]/np.sqrt(2.)]).T), # blue - ... 'orthorhombic': np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [0.,1.,0.]]).T), # blue + ... 'cubic' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,1.]/np.sqrt(2.), # green + ... [1.,1.,1.]/np.sqrt(3.)]).T), # blue + ... 'hexagonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [np.sqrt(3.),1.,0.]/np.sqrt(4.)]).T), # blue + ... 'tetragonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [1.,1.,0.]/np.sqrt(2.)]).T), # blue + ... 'orthorhombic': np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [0.,1.,0.]]).T), # blue ... } """ @@ -322,7 +324,174 @@ class Crystal(): def kinematics(self,mode): - master = self._kinematics[self.lattice][mode] + _kinematics = { + 'cF': { + 'slip' : np.array([ + [+0,+1,-1, +1,+1,+1], + [-1,+0,+1, +1,+1,+1], + [+1,-1,+0, +1,+1,+1], + [+0,-1,-1, -1,-1,+1], + [+1,+0,+1, -1,-1,+1], + [-1,+1,+0, -1,-1,+1], + [+0,-1,+1, +1,-1,-1], + [-1,+0,-1, +1,-1,-1], + [+1,+1,+0, +1,-1,-1], + [+0,+1,+1, -1,+1,-1], + [+1,+0,-1, -1,+1,-1], + [-1,-1,+0, -1,+1,-1], + [+1,+1,+0, +1,-1,+0], + [+1,-1,+0, +1,+1,+0], + [+1,+0,+1, +1,+0,-1], + [+1,+0,-1, +1,+0,+1], + [+0,+1,+1, +0,+1,-1], + [+0,+1,-1, +0,+1,+1], + ],dtype=float), + 'twin' : np.array([ + [-2, 1, 1, 1, 1, 1], + [ 1,-2, 1, 1, 1, 1], + [ 1, 1,-2, 1, 1, 1], + [ 2,-1, 1, -1,-1, 1], + [-1, 2, 1, -1,-1, 1], + [-1,-1,-2, -1,-1, 1], + [-2,-1,-1, 1,-1,-1], + [ 1, 2,-1, 1,-1,-1], + [ 1,-1, 2, 1,-1,-1], + [ 2, 1,-1, -1, 1,-1], + [-1,-2,-1, -1, 1,-1], + [-1, 1, 2, -1, 1,-1], + ],dtype=float), + }, + 'cI': { + 'slip' : np.array([ + [+1,-1,+1, +0,+1,+1], + [-1,-1,+1, +0,+1,+1], + [+1,+1,+1, +0,-1,+1], + [-1,+1,+1, +0,-1,+1], + [-1,+1,+1, +1,+0,+1], + [-1,-1,+1, +1,+0,+1], + [+1,+1,+1, -1,+0,+1], + [+1,-1,+1, -1,+0,+1], + [-1,+1,+1, +1,+1,+0], + [-1,+1,-1, +1,+1,+0], + [+1,+1,+1, -1,+1,+0], + [+1,+1,-1, -1,+1,+0], + [-1,+1,+1, +2,+1,+1], + [+1,+1,+1, -2,+1,+1], + [+1,+1,-1, +2,-1,+1], + [+1,-1,+1, +2,+1,-1], + [+1,-1,+1, +1,+2,+1], + [+1,+1,-1, -1,+2,+1], + [+1,+1,+1, +1,-2,+1], + [-1,+1,+1, +1,+2,-1], + [+1,+1,-1, +1,+1,+2], + [+1,-1,+1, -1,+1,+2], + [-1,+1,+1, +1,-1,+2], + [+1,+1,+1, +1,+1,-2], + [+1,+1,-1, +1,+2,+3], + [+1,-1,+1, -1,+2,+3], + [-1,+1,+1, +1,-2,+3], + [+1,+1,+1, +1,+2,-3], + [+1,-1,+1, +1,+3,+2], + [+1,+1,-1, -1,+3,+2], + [+1,+1,+1, +1,-3,+2], + [-1,+1,+1, +1,+3,-2], + [+1,+1,-1, +2,+1,+3], + [+1,-1,+1, -2,+1,+3], + [-1,+1,+1, +2,-1,+3], + [+1,+1,+1, +2,+1,-3], + [+1,-1,+1, +2,+3,+1], + [+1,+1,-1, -2,+3,+1], + [+1,+1,+1, +2,-3,+1], + [-1,+1,+1, +2,+3,-1], + [-1,+1,+1, +3,+1,+2], + [+1,+1,+1, -3,+1,+2], + [+1,+1,-1, +3,-1,+2], + [+1,-1,+1, +3,+1,-2], + [-1,+1,+1, +3,+2,+1], + [+1,+1,+1, -3,+2,+1], + [+1,+1,-1, +3,-2,+1], + [+1,-1,+1, +3,+2,-1], + ],dtype=float), + 'twin' : np.array([ + [-1, 1, 1, 2, 1, 1], + [ 1, 1, 1, -2, 1, 1], + [ 1, 1,-1, 2,-1, 1], + [ 1,-1, 1, 2, 1,-1], + [ 1,-1, 1, 1, 2, 1], + [ 1, 1,-1, -1, 2, 1], + [ 1, 1, 1, 1,-2, 1], + [-1, 1, 1, 1, 2,-1], + [ 1, 1,-1, 1, 1, 2], + [ 1,-1, 1, -1, 1, 2], + [-1, 1, 1, 1,-1, 2], + [ 1, 1, 1, 1, 1,-2], + ],dtype=float), + }, + 'hP': { + 'slip' : np.array([ + [+2,-1,-1,+0, +0,+0,+0,+1], + [-1,+2,-1,+0, +0,+0,+0,+1], + [-1,-1,+2,+0, +0,+0,+0,+1], + [+2,-1,-1,+0, +0,+1,-1,+0], + [-1,+2,-1,+0, -1,+0,+1,+0], + [-1,-1,+2,+0, +1,-1,+0,+0], + [-1,+1,+0,+0, +1,+1,-2,+0], + [+0,-1,+1,+0, -2,+1,+1,+0], + [+1,+0,-1,+0, +1,-2,+1,+0], + [-1,+2,-1,+0, +1,+0,-1,+1], + [-2,+1,+1,+0, +0,+1,-1,+1], + [-1,-1,+2,+0, -1,+1,+0,+1], + [+1,-2,+1,+0, -1,+0,+1,+1], + [+2,-1,-1,+0, +0,-1,+1,+1], + [+1,+1,-2,+0, +1,-1,+0,+1], + [-2,+1,+1,+3, +1,+0,-1,+1], + [-1,-1,+2,+3, +1,+0,-1,+1], + [-1,-1,+2,+3, +0,+1,-1,+1], + [+1,-2,+1,+3, +0,+1,-1,+1], + [+1,-2,+1,+3, -1,+1,+0,+1], + [+2,-1,-1,+3, -1,+1,+0,+1], + [+2,-1,-1,+3, -1,+0,+1,+1], + [+1,+1,-2,+3, -1,+0,+1,+1], + [+1,+1,-2,+3, +0,-1,+1,+1], + [-1,+2,-1,+3, +0,-1,+1,+1], + [-1,+2,-1,+3, +1,-1,+0,+1], + [-2,+1,+1,+3, +1,-1,+0,+1], + [-1,-1,+2,+3, +1,+1,-2,+2], + [+1,-2,+1,+3, -1,+2,-1,+2], + [+2,-1,-1,+3, -2,+1,+1,+2], + [+1,+1,-2,+3, -1,-1,+2,+2], + [-1,+2,-1,+3, +1,-2,+1,+2], + [-2,+1,+1,+3, +2,-1,-1,+2], + ],dtype=float), + 'twin' : np.array([ + [-1, 0, 1, 1, 1, 0,-1, 2], # shear = (3-(c/a)^2)/(sqrt(3) c/a) <-10.1>{10.2} + [ 0,-1, 1, 1, 0, 1,-1, 2], + [ 1,-1, 0, 1, -1, 1, 0, 2], + [ 1, 0,-1, 1, -1, 0, 1, 2], + [ 0, 1,-1, 1, 0,-1, 1, 2], + [-1, 1, 0, 1, 1,-1, 0, 2], + [-1,-1, 2, 6, 1, 1,-2, 1], # shear = 1/(c/a) <11.6>{-1-1.1} + [ 1,-2, 1, 6, -1, 2,-1, 1], + [ 2,-1,-1, 6, -2, 1, 1, 1], + [ 1, 1,-2, 6, -1,-1, 2, 1], + [-1, 2,-1, 6, 1,-2, 1, 1], + [-2, 1, 1, 6, 2,-1,-1, 1], + [ 1, 0,-1,-2, 1, 0,-1, 1], # shear = (4(c/a)^2-9)/(4 sqrt(3) c/a) <10.-2>{10.1} + [ 0, 1,-1,-2, 0, 1,-1, 1], + [-1, 1, 0,-2, -1, 1, 0, 1], + [-1, 0, 1,-2, -1, 0, 1, 1], + [ 0,-1, 1,-2, 0,-1, 1, 1], + [ 1,-1, 0,-2, 1,-1, 0, 1], + [ 1, 1,-2,-3, 1, 1,-2, 2], # shear = 2((c/a)^2-2)/(3 c/a) <11.-3>{11.2} + [-1, 2,-1,-3, -1, 2,-1, 2], + [-2, 1, 1,-3, -2, 1, 1, 2], + [-1,-1, 2,-3, -1,-1, 2, 2], + [ 1,-2, 1,-3, 1,-2, 1, 2], + [ 2,-1,-1,-3, 2,-1,-1, 2], + ],dtype=float), + }, + } + master = _kinematics[self.lattice][mode] if self.lattice == 'hP': return {'direction':util.Bravais_to_Miller(uvtw=master[:,0:4]), 'plane': util.Bravais_to_Miller(hkil=master[:,4:8])} @@ -363,9 +532,280 @@ class Crystal(): https://doi.org/10.1016/j.actamat.2004.11.021 """ - if model not in self.orientation_relationships: + _orientation_relationships = { + 'KS': { + 'cF' : np.array([ + [[-1, 0, 1],[ 1, 1, 1]], + [[-1, 0, 1],[ 1, 1, 1]], + [[ 0, 1,-1],[ 1, 1, 1]], + [[ 0, 1,-1],[ 1, 1, 1]], + [[ 1,-1, 0],[ 1, 1, 1]], + [[ 1,-1, 0],[ 1, 1, 1]], + [[ 1, 0,-1],[ 1,-1, 1]], + [[ 1, 0,-1],[ 1,-1, 1]], + [[-1,-1, 0],[ 1,-1, 1]], + [[-1,-1, 0],[ 1,-1, 1]], + [[ 0, 1, 1],[ 1,-1, 1]], + [[ 0, 1, 1],[ 1,-1, 1]], + [[ 0,-1, 1],[-1, 1, 1]], + [[ 0,-1, 1],[-1, 1, 1]], + [[-1, 0,-1],[-1, 1, 1]], + [[-1, 0,-1],[-1, 1, 1]], + [[ 1, 1, 0],[-1, 1, 1]], + [[ 1, 1, 0],[-1, 1, 1]], + [[-1, 1, 0],[ 1, 1,-1]], + [[-1, 1, 0],[ 1, 1,-1]], + [[ 0,-1,-1],[ 1, 1,-1]], + [[ 0,-1,-1],[ 1, 1,-1]], + [[ 1, 0, 1],[ 1, 1,-1]], + [[ 1, 0, 1],[ 1, 1,-1]], + ],dtype=float), + 'cI' : np.array([ + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + ],dtype=float), + }, + 'GT': { + 'cF' : np.array([ + [[ -5,-12, 17],[ 1, 1, 1]], + [[ 17, -5,-12],[ 1, 1, 1]], + [[-12, 17, -5],[ 1, 1, 1]], + [[ 5, 12, 17],[ -1, -1, 1]], + [[-17, 5,-12],[ -1, -1, 1]], + [[ 12,-17, -5],[ -1, -1, 1]], + [[ -5, 12,-17],[ -1, 1, 1]], + [[ 17, 5, 12],[ -1, 1, 1]], + [[-12,-17, 5],[ -1, 1, 1]], + [[ 5,-12,-17],[ 1, -1, 1]], + [[-17, -5, 12],[ 1, -1, 1]], + [[ 12, 17, 5],[ 1, -1, 1]], + [[ -5, 17,-12],[ 1, 1, 1]], + [[-12, -5, 17],[ 1, 1, 1]], + [[ 17,-12, -5],[ 1, 1, 1]], + [[ 5,-17,-12],[ -1, -1, 1]], + [[ 12, 5, 17],[ -1, -1, 1]], + [[-17, 12, -5],[ -1, -1, 1]], + [[ -5,-17, 12],[ -1, 1, 1]], + [[-12, 5,-17],[ -1, 1, 1]], + [[ 17, 12, 5],[ -1, 1, 1]], + [[ 5, 17, 12],[ 1, -1, 1]], + [[ 12, -5,-17],[ 1, -1, 1]], + [[-17,-12, 5],[ 1, -1, 1]], + ],dtype=float), + 'cI' : np.array([ + [[-17, -7, 17],[ 1, 0, 1]], + [[ 17,-17, -7],[ 1, 1, 0]], + [[ -7, 17,-17],[ 0, 1, 1]], + [[ 17, 7, 17],[ -1, 0, 1]], + [[-17, 17, -7],[ -1, -1, 0]], + [[ 7,-17,-17],[ 0, -1, 1]], + [[-17, 7,-17],[ -1, 0, 1]], + [[ 17, 17, 7],[ -1, 1, 0]], + [[ -7,-17, 17],[ 0, 1, 1]], + [[ 17, -7,-17],[ 1, 0, 1]], + [[-17,-17, 7],[ 1, -1, 0]], + [[ 7, 17, 17],[ 0, -1, 1]], + [[-17, 17, -7],[ 1, 1, 0]], + [[ -7,-17, 17],[ 0, 1, 1]], + [[ 17, -7,-17],[ 1, 0, 1]], + [[ 17,-17, -7],[ -1, -1, 0]], + [[ 7, 17, 17],[ 0, -1, 1]], + [[-17, 7,-17],[ -1, 0, 1]], + [[-17,-17, 7],[ -1, 1, 0]], + [[ -7, 17,-17],[ 0, 1, 1]], + [[ 17, 7, 17],[ -1, 0, 1]], + [[ 17, 17, 7],[ 1, -1, 0]], + [[ 7,-17,-17],[ 0, -1, 1]], + [[-17, -7, 17],[ 1, 0, 1]], + ],dtype=float), + }, + 'GT_prime': { + 'cF' : np.array([ + [[ 0, 1, -1],[ 7, 17, 17]], + [[ -1, 0, 1],[ 17, 7, 17]], + [[ 1, -1, 0],[ 17, 17, 7]], + [[ 0, -1, -1],[ -7,-17, 17]], + [[ 1, 0, 1],[-17, -7, 17]], + [[ 1, -1, 0],[-17,-17, 7]], + [[ 0, 1, -1],[ 7,-17,-17]], + [[ 1, 0, 1],[ 17, -7,-17]], + [[ -1, -1, 0],[ 17,-17, -7]], + [[ 0, -1, -1],[ -7, 17,-17]], + [[ -1, 0, 1],[-17, 7,-17]], + [[ -1, -1, 0],[-17, 17, -7]], + [[ 0, -1, 1],[ 7, 17, 17]], + [[ 1, 0, -1],[ 17, 7, 17]], + [[ -1, 1, 0],[ 17, 17, 7]], + [[ 0, 1, 1],[ -7,-17, 17]], + [[ -1, 0, -1],[-17, -7, 17]], + [[ -1, 1, 0],[-17,-17, 7]], + [[ 0, -1, 1],[ 7,-17,-17]], + [[ -1, 0, -1],[ 17, -7,-17]], + [[ 1, 1, 0],[ 17,-17, -7]], + [[ 0, 1, 1],[ -7, 17,-17]], + [[ 1, 0, -1],[-17, 7,-17]], + [[ 1, 1, 0],[-17, 17, -7]], + ],dtype=float), + 'cI' : np.array([ + [[ 1, 1, -1],[ 12, 5, 17]], + [[ -1, 1, 1],[ 17, 12, 5]], + [[ 1, -1, 1],[ 5, 17, 12]], + [[ -1, -1, -1],[-12, -5, 17]], + [[ 1, -1, 1],[-17,-12, 5]], + [[ 1, -1, -1],[ -5,-17, 12]], + [[ -1, 1, -1],[ 12, -5,-17]], + [[ 1, 1, 1],[ 17,-12, -5]], + [[ -1, -1, 1],[ 5,-17,-12]], + [[ 1, -1, -1],[-12, 5,-17]], + [[ -1, -1, 1],[-17, 12, -5]], + [[ -1, -1, -1],[ -5, 17,-12]], + [[ 1, -1, 1],[ 12, 17, 5]], + [[ 1, 1, -1],[ 5, 12, 17]], + [[ -1, 1, 1],[ 17, 5, 12]], + [[ -1, 1, 1],[-12,-17, 5]], + [[ -1, -1, -1],[ -5,-12, 17]], + [[ -1, 1, -1],[-17, -5, 12]], + [[ -1, -1, 1],[ 12,-17, -5]], + [[ -1, 1, -1],[ 5,-12,-17]], + [[ 1, 1, 1],[ 17, -5,-12]], + [[ 1, 1, 1],[-12, 17, -5]], + [[ 1, -1, -1],[ -5, 12,-17]], + [[ 1, 1, -1],[-17, 5,-12]], + ],dtype=float), + }, + 'NW': { + 'cF' : np.array([ + [[ 2, -1, -1],[ 1, 1, 1]], + [[ -1, 2, -1],[ 1, 1, 1]], + [[ -1, -1, 2],[ 1, 1, 1]], + [[ -2, -1, -1],[ -1, 1, 1]], + [[ 1, 2, -1],[ -1, 1, 1]], + [[ 1, -1, 2],[ -1, 1, 1]], + [[ 2, 1, -1],[ 1, -1, 1]], + [[ -1, -2, -1],[ 1, -1, 1]], + [[ -1, 1, 2],[ 1, -1, 1]], + [[ 2, -1, 1],[ -1, -1, 1]], + [[ -1, 2, 1],[ -1, -1, 1]], + [[ -1, -1, -2],[ -1, -1, 1]], + ],dtype=float), + 'cI' : np.array([ + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + ],dtype=float), + }, + 'Pitsch': { + 'cF' : np.array([ + [[ 1, 0, 1],[ 0, 1, 0]], + [[ 1, 1, 0],[ 0, 0, 1]], + [[ 0, 1, 1],[ 1, 0, 0]], + [[ 0, 1, -1],[ 1, 0, 0]], + [[ -1, 0, 1],[ 0, 1, 0]], + [[ 1, -1, 0],[ 0, 0, 1]], + [[ 1, 0, -1],[ 0, 1, 0]], + [[ -1, 1, 0],[ 0, 0, 1]], + [[ 0, -1, 1],[ 1, 0, 0]], + [[ 0, 1, 1],[ 1, 0, 0]], + [[ 1, 0, 1],[ 0, 1, 0]], + [[ 1, 1, 0],[ 0, 0, 1]], + ],dtype=float), + 'cI' : np.array([ + [[ 1, -1, 1],[ -1, 0, 1]], + [[ 1, 1, -1],[ 1, -1, 0]], + [[ -1, 1, 1],[ 0, 1, -1]], + [[ -1, 1, -1],[ 0, -1, -1]], + [[ -1, -1, 1],[ -1, 0, -1]], + [[ 1, -1, -1],[ -1, -1, 0]], + [[ 1, -1, -1],[ -1, 0, -1]], + [[ -1, 1, -1],[ -1, -1, 0]], + [[ -1, -1, 1],[ 0, -1, -1]], + [[ -1, 1, 1],[ 0, -1, 1]], + [[ 1, -1, 1],[ 1, 0, -1]], + [[ 1, 1, -1],[ -1, 1, 0]], + ],dtype=float), + }, + 'Bain': { + 'cF' : np.array([ + [[ 0, 1, 0],[ 1, 0, 0]], + [[ 0, 0, 1],[ 0, 1, 0]], + [[ 1, 0, 0],[ 0, 0, 1]], + ],dtype=float), + 'cI' : np.array([ + [[ 0, 1, 1],[ 1, 0, 0]], + [[ 1, 0, 1],[ 0, 1, 0]], + [[ 1, 1, 0],[ 0, 0, 1]], + ],dtype=float), + }, + 'Burgers' : { + 'cI' : np.array([ + [[ -1, 1, 1],[ 1, 1, 0]], + [[ -1, 1, -1],[ 1, 1, 0]], + [[ 1, 1, 1],[ 1, -1, 0]], + [[ 1, 1, -1],[ 1, -1, 0]], + + [[ 1, 1, -1],[ 1, 0, 1]], + [[ -1, 1, 1],[ 1, 0, 1]], + [[ 1, 1, 1],[ -1, 0, 1]], + [[ 1, -1, 1],[ -1, 0, 1]], + + [[ -1, 1, -1],[ 0, 1, 1]], + [[ 1, 1, -1],[ 0, 1, 1]], + [[ -1, 1, 1],[ 0, -1, 1]], + [[ 1, 1, 1],[ 0, -1, 1]], + ],dtype=float), + 'hP' : np.array([ + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + ],dtype=float), + }, + } + orientation_relationships = {k:v for k,v in _orientation_relationships.items() if self.lattice in v} + if model not in orientation_relationships: raise KeyError(f'unknown orientation relationship "{model}"') - r = self.orientation_relationships[model] + r = orientation_relationships[model] sl = self.lattice ol = (set(r)-{sl}).pop() @@ -379,451 +819,3 @@ class Crystal(): _p[...,1,:] = o[...,1,:] if o.shape[-1] == 3 else util.Bravais_to_Miller(hkil=o[...,1,0:4]) return (ol,Rotation.from_parallel(p_,_p)) - - - @property - def orientation_relationships(self): - return {k:v for k,v in self._orientation_relationships.items() if self.lattice in v} - - - _kinematics = { - 'cF': { - 'slip' : np.array([ - [+0,+1,-1, +1,+1,+1], - [-1,+0,+1, +1,+1,+1], - [+1,-1,+0, +1,+1,+1], - [+0,-1,-1, -1,-1,+1], - [+1,+0,+1, -1,-1,+1], - [-1,+1,+0, -1,-1,+1], - [+0,-1,+1, +1,-1,-1], - [-1,+0,-1, +1,-1,-1], - [+1,+1,+0, +1,-1,-1], - [+0,+1,+1, -1,+1,-1], - [+1,+0,-1, -1,+1,-1], - [-1,-1,+0, -1,+1,-1], - [+1,+1,+0, +1,-1,+0], - [+1,-1,+0, +1,+1,+0], - [+1,+0,+1, +1,+0,-1], - [+1,+0,-1, +1,+0,+1], - [+0,+1,+1, +0,+1,-1], - [+0,+1,-1, +0,+1,+1], - ],'d'), - 'twin' : np.array([ - [-2, 1, 1, 1, 1, 1], - [ 1,-2, 1, 1, 1, 1], - [ 1, 1,-2, 1, 1, 1], - [ 2,-1, 1, -1,-1, 1], - [-1, 2, 1, -1,-1, 1], - [-1,-1,-2, -1,-1, 1], - [-2,-1,-1, 1,-1,-1], - [ 1, 2,-1, 1,-1,-1], - [ 1,-1, 2, 1,-1,-1], - [ 2, 1,-1, -1, 1,-1], - [-1,-2,-1, -1, 1,-1], - [-1, 1, 2, -1, 1,-1], - ],dtype=float), - }, - 'cI': { - 'slip' : np.array([ - [+1,-1,+1, +0,+1,+1], - [-1,-1,+1, +0,+1,+1], - [+1,+1,+1, +0,-1,+1], - [-1,+1,+1, +0,-1,+1], - [-1,+1,+1, +1,+0,+1], - [-1,-1,+1, +1,+0,+1], - [+1,+1,+1, -1,+0,+1], - [+1,-1,+1, -1,+0,+1], - [-1,+1,+1, +1,+1,+0], - [-1,+1,-1, +1,+1,+0], - [+1,+1,+1, -1,+1,+0], - [+1,+1,-1, -1,+1,+0], - [-1,+1,+1, +2,+1,+1], - [+1,+1,+1, -2,+1,+1], - [+1,+1,-1, +2,-1,+1], - [+1,-1,+1, +2,+1,-1], - [+1,-1,+1, +1,+2,+1], - [+1,+1,-1, -1,+2,+1], - [+1,+1,+1, +1,-2,+1], - [-1,+1,+1, +1,+2,-1], - [+1,+1,-1, +1,+1,+2], - [+1,-1,+1, -1,+1,+2], - [-1,+1,+1, +1,-1,+2], - [+1,+1,+1, +1,+1,-2], - [+1,+1,-1, +1,+2,+3], - [+1,-1,+1, -1,+2,+3], - [-1,+1,+1, +1,-2,+3], - [+1,+1,+1, +1,+2,-3], - [+1,-1,+1, +1,+3,+2], - [+1,+1,-1, -1,+3,+2], - [+1,+1,+1, +1,-3,+2], - [-1,+1,+1, +1,+3,-2], - [+1,+1,-1, +2,+1,+3], - [+1,-1,+1, -2,+1,+3], - [-1,+1,+1, +2,-1,+3], - [+1,+1,+1, +2,+1,-3], - [+1,-1,+1, +2,+3,+1], - [+1,+1,-1, -2,+3,+1], - [+1,+1,+1, +2,-3,+1], - [-1,+1,+1, +2,+3,-1], - [-1,+1,+1, +3,+1,+2], - [+1,+1,+1, -3,+1,+2], - [+1,+1,-1, +3,-1,+2], - [+1,-1,+1, +3,+1,-2], - [-1,+1,+1, +3,+2,+1], - [+1,+1,+1, -3,+2,+1], - [+1,+1,-1, +3,-2,+1], - [+1,-1,+1, +3,+2,-1], - ],'d'), - 'twin' : np.array([ - [-1, 1, 1, 2, 1, 1], - [ 1, 1, 1, -2, 1, 1], - [ 1, 1,-1, 2,-1, 1], - [ 1,-1, 1, 2, 1,-1], - [ 1,-1, 1, 1, 2, 1], - [ 1, 1,-1, -1, 2, 1], - [ 1, 1, 1, 1,-2, 1], - [-1, 1, 1, 1, 2,-1], - [ 1, 1,-1, 1, 1, 2], - [ 1,-1, 1, -1, 1, 2], - [-1, 1, 1, 1,-1, 2], - [ 1, 1, 1, 1, 1,-2], - ],dtype=float), - }, - 'hP': { - 'slip' : np.array([ - [+2,-1,-1,+0, +0,+0,+0,+1], - [-1,+2,-1,+0, +0,+0,+0,+1], - [-1,-1,+2,+0, +0,+0,+0,+1], - [+2,-1,-1,+0, +0,+1,-1,+0], - [-1,+2,-1,+0, -1,+0,+1,+0], - [-1,-1,+2,+0, +1,-1,+0,+0], - [-1,+1,+0,+0, +1,+1,-2,+0], - [+0,-1,+1,+0, -2,+1,+1,+0], - [+1,+0,-1,+0, +1,-2,+1,+0], - [-1,+2,-1,+0, +1,+0,-1,+1], - [-2,+1,+1,+0, +0,+1,-1,+1], - [-1,-1,+2,+0, -1,+1,+0,+1], - [+1,-2,+1,+0, -1,+0,+1,+1], - [+2,-1,-1,+0, +0,-1,+1,+1], - [+1,+1,-2,+0, +1,-1,+0,+1], - [-2,+1,+1,+3, +1,+0,-1,+1], - [-1,-1,+2,+3, +1,+0,-1,+1], - [-1,-1,+2,+3, +0,+1,-1,+1], - [+1,-2,+1,+3, +0,+1,-1,+1], - [+1,-2,+1,+3, -1,+1,+0,+1], - [+2,-1,-1,+3, -1,+1,+0,+1], - [+2,-1,-1,+3, -1,+0,+1,+1], - [+1,+1,-2,+3, -1,+0,+1,+1], - [+1,+1,-2,+3, +0,-1,+1,+1], - [-1,+2,-1,+3, +0,-1,+1,+1], - [-1,+2,-1,+3, +1,-1,+0,+1], - [-2,+1,+1,+3, +1,-1,+0,+1], - [-1,-1,+2,+3, +1,+1,-2,+2], - [+1,-2,+1,+3, -1,+2,-1,+2], - [+2,-1,-1,+3, -2,+1,+1,+2], - [+1,+1,-2,+3, -1,-1,+2,+2], - [-1,+2,-1,+3, +1,-2,+1,+2], - [-2,+1,+1,+3, +2,-1,-1,+2], - ],'d'), - 'twin' : np.array([ - [-1, 0, 1, 1, 1, 0,-1, 2], # shear = (3-(c/a)^2)/(sqrt(3) c/a) <-10.1>{10.2} - [ 0,-1, 1, 1, 0, 1,-1, 2], - [ 1,-1, 0, 1, -1, 1, 0, 2], - [ 1, 0,-1, 1, -1, 0, 1, 2], - [ 0, 1,-1, 1, 0,-1, 1, 2], - [-1, 1, 0, 1, 1,-1, 0, 2], - [-1,-1, 2, 6, 1, 1,-2, 1], # shear = 1/(c/a) <11.6>{-1-1.1} - [ 1,-2, 1, 6, -1, 2,-1, 1], - [ 2,-1,-1, 6, -2, 1, 1, 1], - [ 1, 1,-2, 6, -1,-1, 2, 1], - [-1, 2,-1, 6, 1,-2, 1, 1], - [-2, 1, 1, 6, 2,-1,-1, 1], - [ 1, 0,-1,-2, 1, 0,-1, 1], # shear = (4(c/a)^2-9)/(4 sqrt(3) c/a) <10.-2>{10.1} - [ 0, 1,-1,-2, 0, 1,-1, 1], - [-1, 1, 0,-2, -1, 1, 0, 1], - [-1, 0, 1,-2, -1, 0, 1, 1], - [ 0,-1, 1,-2, 0,-1, 1, 1], - [ 1,-1, 0,-2, 1,-1, 0, 1], - [ 1, 1,-2,-3, 1, 1,-2, 2], # shear = 2((c/a)^2-2)/(3 c/a) <11.-3>{11.2} - [-1, 2,-1,-3, -1, 2,-1, 2], - [-2, 1, 1,-3, -2, 1, 1, 2], - [-1,-1, 2,-3, -1,-1, 2, 2], - [ 1,-2, 1,-3, 1,-2, 1, 2], - [ 2,-1,-1,-3, 2,-1,-1, 2], - ],dtype=float), - }, - } - - - _orientation_relationships = { - 'KS': { - 'cF' : np.array([ - [[-1, 0, 1],[ 1, 1, 1]], - [[-1, 0, 1],[ 1, 1, 1]], - [[ 0, 1,-1],[ 1, 1, 1]], - [[ 0, 1,-1],[ 1, 1, 1]], - [[ 1,-1, 0],[ 1, 1, 1]], - [[ 1,-1, 0],[ 1, 1, 1]], - [[ 1, 0,-1],[ 1,-1, 1]], - [[ 1, 0,-1],[ 1,-1, 1]], - [[-1,-1, 0],[ 1,-1, 1]], - [[-1,-1, 0],[ 1,-1, 1]], - [[ 0, 1, 1],[ 1,-1, 1]], - [[ 0, 1, 1],[ 1,-1, 1]], - [[ 0,-1, 1],[-1, 1, 1]], - [[ 0,-1, 1],[-1, 1, 1]], - [[-1, 0,-1],[-1, 1, 1]], - [[-1, 0,-1],[-1, 1, 1]], - [[ 1, 1, 0],[-1, 1, 1]], - [[ 1, 1, 0],[-1, 1, 1]], - [[-1, 1, 0],[ 1, 1,-1]], - [[-1, 1, 0],[ 1, 1,-1]], - [[ 0,-1,-1],[ 1, 1,-1]], - [[ 0,-1,-1],[ 1, 1,-1]], - [[ 1, 0, 1],[ 1, 1,-1]], - [[ 1, 0, 1],[ 1, 1,-1]], - ],dtype=float), - 'cI' : np.array([ - [[-1,-1, 1],[ 0, 1, 1]], - [[-1, 1,-1],[ 0, 1, 1]], - [[-1,-1, 1],[ 0, 1, 1]], - [[-1, 1,-1],[ 0, 1, 1]], - [[-1,-1, 1],[ 0, 1, 1]], - [[-1, 1,-1],[ 0, 1, 1]], - [[-1,-1, 1],[ 0, 1, 1]], - [[-1, 1,-1],[ 0, 1, 1]], - [[-1,-1, 1],[ 0, 1, 1]], - [[-1, 1,-1],[ 0, 1, 1]], - [[-1,-1, 1],[ 0, 1, 1]], - [[-1, 1,-1],[ 0, 1, 1]], - [[-1,-1, 1],[ 0, 1, 1]], - [[-1, 1,-1],[ 0, 1, 1]], - [[-1,-1, 1],[ 0, 1, 1]], - [[-1, 1,-1],[ 0, 1, 1]], - [[-1,-1, 1],[ 0, 1, 1]], - [[-1, 1,-1],[ 0, 1, 1]], - [[-1,-1, 1],[ 0, 1, 1]], - [[-1, 1,-1],[ 0, 1, 1]], - [[-1,-1, 1],[ 0, 1, 1]], - [[-1, 1,-1],[ 0, 1, 1]], - [[-1,-1, 1],[ 0, 1, 1]], - [[-1, 1,-1],[ 0, 1, 1]], - ],dtype=float), - }, - 'GT': { - 'cF' : np.array([ - [[ -5,-12, 17],[ 1, 1, 1]], - [[ 17, -5,-12],[ 1, 1, 1]], - [[-12, 17, -5],[ 1, 1, 1]], - [[ 5, 12, 17],[ -1, -1, 1]], - [[-17, 5,-12],[ -1, -1, 1]], - [[ 12,-17, -5],[ -1, -1, 1]], - [[ -5, 12,-17],[ -1, 1, 1]], - [[ 17, 5, 12],[ -1, 1, 1]], - [[-12,-17, 5],[ -1, 1, 1]], - [[ 5,-12,-17],[ 1, -1, 1]], - [[-17, -5, 12],[ 1, -1, 1]], - [[ 12, 17, 5],[ 1, -1, 1]], - [[ -5, 17,-12],[ 1, 1, 1]], - [[-12, -5, 17],[ 1, 1, 1]], - [[ 17,-12, -5],[ 1, 1, 1]], - [[ 5,-17,-12],[ -1, -1, 1]], - [[ 12, 5, 17],[ -1, -1, 1]], - [[-17, 12, -5],[ -1, -1, 1]], - [[ -5,-17, 12],[ -1, 1, 1]], - [[-12, 5,-17],[ -1, 1, 1]], - [[ 17, 12, 5],[ -1, 1, 1]], - [[ 5, 17, 12],[ 1, -1, 1]], - [[ 12, -5,-17],[ 1, -1, 1]], - [[-17,-12, 5],[ 1, -1, 1]], - ],dtype=float), - 'cI' : np.array([ - [[-17, -7, 17],[ 1, 0, 1]], - [[ 17,-17, -7],[ 1, 1, 0]], - [[ -7, 17,-17],[ 0, 1, 1]], - [[ 17, 7, 17],[ -1, 0, 1]], - [[-17, 17, -7],[ -1, -1, 0]], - [[ 7,-17,-17],[ 0, -1, 1]], - [[-17, 7,-17],[ -1, 0, 1]], - [[ 17, 17, 7],[ -1, 1, 0]], - [[ -7,-17, 17],[ 0, 1, 1]], - [[ 17, -7,-17],[ 1, 0, 1]], - [[-17,-17, 7],[ 1, -1, 0]], - [[ 7, 17, 17],[ 0, -1, 1]], - [[-17, 17, -7],[ 1, 1, 0]], - [[ -7,-17, 17],[ 0, 1, 1]], - [[ 17, -7,-17],[ 1, 0, 1]], - [[ 17,-17, -7],[ -1, -1, 0]], - [[ 7, 17, 17],[ 0, -1, 1]], - [[-17, 7,-17],[ -1, 0, 1]], - [[-17,-17, 7],[ -1, 1, 0]], - [[ -7, 17,-17],[ 0, 1, 1]], - [[ 17, 7, 17],[ -1, 0, 1]], - [[ 17, 17, 7],[ 1, -1, 0]], - [[ 7,-17,-17],[ 0, -1, 1]], - [[-17, -7, 17],[ 1, 0, 1]], - ],dtype=float), - }, - 'GT_prime': { - 'cF' : np.array([ - [[ 0, 1, -1],[ 7, 17, 17]], - [[ -1, 0, 1],[ 17, 7, 17]], - [[ 1, -1, 0],[ 17, 17, 7]], - [[ 0, -1, -1],[ -7,-17, 17]], - [[ 1, 0, 1],[-17, -7, 17]], - [[ 1, -1, 0],[-17,-17, 7]], - [[ 0, 1, -1],[ 7,-17,-17]], - [[ 1, 0, 1],[ 17, -7,-17]], - [[ -1, -1, 0],[ 17,-17, -7]], - [[ 0, -1, -1],[ -7, 17,-17]], - [[ -1, 0, 1],[-17, 7,-17]], - [[ -1, -1, 0],[-17, 17, -7]], - [[ 0, -1, 1],[ 7, 17, 17]], - [[ 1, 0, -1],[ 17, 7, 17]], - [[ -1, 1, 0],[ 17, 17, 7]], - [[ 0, 1, 1],[ -7,-17, 17]], - [[ -1, 0, -1],[-17, -7, 17]], - [[ -1, 1, 0],[-17,-17, 7]], - [[ 0, -1, 1],[ 7,-17,-17]], - [[ -1, 0, -1],[ 17, -7,-17]], - [[ 1, 1, 0],[ 17,-17, -7]], - [[ 0, 1, 1],[ -7, 17,-17]], - [[ 1, 0, -1],[-17, 7,-17]], - [[ 1, 1, 0],[-17, 17, -7]], - ],dtype=float), - 'cI' : np.array([ - [[ 1, 1, -1],[ 12, 5, 17]], - [[ -1, 1, 1],[ 17, 12, 5]], - [[ 1, -1, 1],[ 5, 17, 12]], - [[ -1, -1, -1],[-12, -5, 17]], - [[ 1, -1, 1],[-17,-12, 5]], - [[ 1, -1, -1],[ -5,-17, 12]], - [[ -1, 1, -1],[ 12, -5,-17]], - [[ 1, 1, 1],[ 17,-12, -5]], - [[ -1, -1, 1],[ 5,-17,-12]], - [[ 1, -1, -1],[-12, 5,-17]], - [[ -1, -1, 1],[-17, 12, -5]], - [[ -1, -1, -1],[ -5, 17,-12]], - [[ 1, -1, 1],[ 12, 17, 5]], - [[ 1, 1, -1],[ 5, 12, 17]], - [[ -1, 1, 1],[ 17, 5, 12]], - [[ -1, 1, 1],[-12,-17, 5]], - [[ -1, -1, -1],[ -5,-12, 17]], - [[ -1, 1, -1],[-17, -5, 12]], - [[ -1, -1, 1],[ 12,-17, -5]], - [[ -1, 1, -1],[ 5,-12,-17]], - [[ 1, 1, 1],[ 17, -5,-12]], - [[ 1, 1, 1],[-12, 17, -5]], - [[ 1, -1, -1],[ -5, 12,-17]], - [[ 1, 1, -1],[-17, 5,-12]], - ],dtype=float), - }, - 'NW': { - 'cF' : np.array([ - [[ 2, -1, -1],[ 1, 1, 1]], - [[ -1, 2, -1],[ 1, 1, 1]], - [[ -1, -1, 2],[ 1, 1, 1]], - [[ -2, -1, -1],[ -1, 1, 1]], - [[ 1, 2, -1],[ -1, 1, 1]], - [[ 1, -1, 2],[ -1, 1, 1]], - [[ 2, 1, -1],[ 1, -1, 1]], - [[ -1, -2, -1],[ 1, -1, 1]], - [[ -1, 1, 2],[ 1, -1, 1]], - [[ 2, -1, 1],[ -1, -1, 1]], - [[ -1, 2, 1],[ -1, -1, 1]], - [[ -1, -1, -2],[ -1, -1, 1]], - ],dtype=float), - 'cI' : np.array([ - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - ],dtype=float), - }, - 'Pitsch': { - 'cF' : np.array([ - [[ 1, 0, 1],[ 0, 1, 0]], - [[ 1, 1, 0],[ 0, 0, 1]], - [[ 0, 1, 1],[ 1, 0, 0]], - [[ 0, 1, -1],[ 1, 0, 0]], - [[ -1, 0, 1],[ 0, 1, 0]], - [[ 1, -1, 0],[ 0, 0, 1]], - [[ 1, 0, -1],[ 0, 1, 0]], - [[ -1, 1, 0],[ 0, 0, 1]], - [[ 0, -1, 1],[ 1, 0, 0]], - [[ 0, 1, 1],[ 1, 0, 0]], - [[ 1, 0, 1],[ 0, 1, 0]], - [[ 1, 1, 0],[ 0, 0, 1]], - ],dtype=float), - 'cI' : np.array([ - [[ 1, -1, 1],[ -1, 0, 1]], - [[ 1, 1, -1],[ 1, -1, 0]], - [[ -1, 1, 1],[ 0, 1, -1]], - [[ -1, 1, -1],[ 0, -1, -1]], - [[ -1, -1, 1],[ -1, 0, -1]], - [[ 1, -1, -1],[ -1, -1, 0]], - [[ 1, -1, -1],[ -1, 0, -1]], - [[ -1, 1, -1],[ -1, -1, 0]], - [[ -1, -1, 1],[ 0, -1, -1]], - [[ -1, 1, 1],[ 0, -1, 1]], - [[ 1, -1, 1],[ 1, 0, -1]], - [[ 1, 1, -1],[ -1, 1, 0]], - ],dtype=float), - }, - 'Bain': { - 'cF' : np.array([ - [[ 0, 1, 0],[ 1, 0, 0]], - [[ 0, 0, 1],[ 0, 1, 0]], - [[ 1, 0, 0],[ 0, 0, 1]], - ],dtype=float), - 'cI' : np.array([ - [[ 0, 1, 1],[ 1, 0, 0]], - [[ 1, 0, 1],[ 0, 1, 0]], - [[ 1, 1, 0],[ 0, 0, 1]], - ],dtype=float), - }, - 'Burgers' : { - 'cI' : np.array([ - [[ -1, 1, 1],[ 1, 1, 0]], - [[ -1, 1, -1],[ 1, 1, 0]], - [[ 1, 1, 1],[ 1, -1, 0]], - [[ 1, 1, -1],[ 1, -1, 0]], - - [[ 1, 1, -1],[ 1, 0, 1]], - [[ -1, 1, 1],[ 1, 0, 1]], - [[ 1, 1, 1],[ -1, 0, 1]], - [[ 1, -1, 1],[ -1, 0, 1]], - - [[ -1, 1, -1],[ 0, 1, 1]], - [[ 1, 1, -1],[ 0, 1, 1]], - [[ -1, 1, 1],[ 0, -1, 1]], - [[ 1, 1, 1],[ 0, -1, 1]], - ],dtype=float), - 'hP' : np.array([ - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - ],dtype=float), - }, - } - - From 19ca99e0331273d2714e1718fc4a276c0ef10e1c Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 18 Jul 2021 16:39:52 +0200 Subject: [PATCH 18/27] matching name --- python/damask/_crystal.py | 2 +- python/damask/_orientation.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/python/damask/_crystal.py b/python/damask/_crystal.py index 5769fc9c2..09e480709 100644 --- a/python/damask/_crystal.py +++ b/python/damask/_crystal.py @@ -168,7 +168,7 @@ class Crystal(): @property - def basis(self): + def standard_triangle(self): """ Corners of the standard triangle. diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index a78b6a189..545205c0a 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -668,21 +668,21 @@ class Orientation(Rotation,Crystal): if not isinstance(vector,np.ndarray) or vector.shape[-1] != 3: raise ValueError('input is not a field of three-dimensional vectors') - if self.basis is None: # direct exit for no symmetry + if self.standard_triangle is None: # direct exit for no symmetry return np.ones_like(vector[...,0],bool) if proper: components_proper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.basis['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.basis['improper'], vector.shape+(3,)), + 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.basis['improper'], vector.shape+(3,)), + 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) @@ -723,15 +723,15 @@ class Orientation(Rotation,Crystal): vector_ = self.to_SST(vector,proper) if in_SST else \ self @ np.broadcast_to(vector,self.shape+(3,)) - if self.basis is None: # direct exit for no symmetry + if self.standard_triangle is None: # direct exit for no symmetry return np.zeros_like(vector_) if proper: components_proper = np.around(np.einsum('...ji,...i', - np.broadcast_to(self.basis['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.basis['improper'], vector_.shape+(3,)), + 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) @@ -739,7 +739,7 @@ class Orientation(Rotation,Crystal): components_proper,components_improper) else: components = np.around(np.einsum('...ji,...i', - np.broadcast_to(self .basis['improper'], vector_.shape+(3,)), + 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) From de428efca5104616192cb90a4434b6ef860dbe36 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 18 Jul 2021 18:03:36 +0200 Subject: [PATCH 19/27] add_pole is working again --- python/damask/_orientation.py | 3 +- python/damask/_result.py | 68 +++++++++++++++-------------------- python/tests/test_Result.py | 21 +++++------ 3 files changed, 38 insertions(+), 54 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 545205c0a..448ba3159 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -843,10 +843,9 @@ class Orientation(Rotation,Crystal): Lab frame vector (or vectors if with_symmetry) along [uvw] direction or (hkl) plane normal. """ - sym_ops = self.symmetry_operations - # ToDo: simplify 'with_symmetry' v = self.to_frame(uvw=uvw,hkl=hkl) if with_symmetry: + sym_ops = self.symmetry_operations v = sym_ops.broadcast_to(sym_ops.shape+v.shape[:-1],mode='right') \ @ np.broadcast_to(v,sym_ops.shape+v.shape) return ~(self if self.shape+v.shape[:-1] == () else self.broadcast_to(self.shape+v.shape[:-1],mode='right')) \ diff --git a/python/damask/_result.py b/python/damask/_result.py index 1fa376f63..03bb34926 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -979,47 +979,35 @@ class Result: self._add_generic_pointwise(self._add_stress_second_Piola_Kirchhoff,{'P':P,'F':F}) -# The add_pole functionality needs discussion. -# The new Crystal object can perform such a calculation but the outcome depends on the lattice parameters -# as well as on whether a direction or plane is concerned (see the DAMASK_examples/pole_figure notebook). -# Below code appears to be too simplistic. - # @staticmethod - # def _add_pole(q,p,polar): - # pole = np.array(p) - # unit_pole = pole/np.linalg.norm(pole) - # m = util.scale_to_coprime(pole) - # rot = Rotation(q['data'].view(np.double).reshape(-1,4)) - # - # rotatedPole = rot @ np.broadcast_to(unit_pole,rot.shape+(3,)) # rotate pole according to crystal orientation - # xy = rotatedPole[:,0:2]/(1.+abs(unit_pole[2])) # stereographic projection - # coords = xy if not polar else \ - # np.block([np.sqrt(xy[:,0:1]*xy[:,0:1]+xy[:,1:2]*xy[:,1:2]),np.arctan2(xy[:,1:2],xy[:,0:1])]) - # return { - # 'data': coords, - # 'label': 'p^{}_[{} {} {})'.format(u'rφ' if polar else 'xy',*m), - # 'meta' : { - # 'unit': '1', - # 'description': '{} coordinates of stereographic projection of pole (direction/plane) in crystal frame'\ - # .format('Polar' if polar else 'Cartesian'), - # 'creator': 'add_pole' - # } - # } - # def add_pole(self,q,p,polar=False): - # """ - # Add coordinates of stereographic projection of given pole in crystal frame. - # - # Parameters - # ---------- - # q : str - # Name of the dataset containing the crystallographic orientation as quaternions. - # p : numpy.array of shape (3) - # Crystallographic direction or plane. - # polar : bool, optional - # Give pole in polar coordinates. Defaults to False. - # - # """ - # self._add_generic_pointwise(self._add_pole,{'q':q},{'p':p,'polar':polar}) + @staticmethod + def _add_pole(q,uvw,hkl): + c = q['meta']['c/a'] if 'c/a' in q['meta'] else 1 + pole = Orientation(q['data'], lattice=q['meta']['lattice'], a=1, c=c).to_pole(uvw=uvw,hkl=hkl) + + return { + 'data': pole, + 'label': 'p^[{} {} {}]'.format(*uvw) if uvw else 'p^({} {} {})'.format(*hkl), + 'meta' : { + 'unit': '1', + 'description': f"lab frame vector along lattice {'direction' if uvw else 'plane'}", + 'creator': 'add_pole' + } + } + def add_pole(self,q='O',*,uvw=None,hkl=None): + """ + Add lab frame vector along lattice direction [uvw] or plane normal (hkl). + + Parameters + ---------- + q : str + Name of the dataset containing the crystallographic orientation as quaternions. + Defaults to 'O'. + uvw|hkl : numpy.ndarray of shape (...,3) + Miller indices of crystallographic direction or plane normal. + + """ + self._add_generic_pointwise(self._add_pole,{'q':q},{'uvw':uvw,'hkl':hkl}) @staticmethod diff --git a/python/tests/test_Result.py b/python/tests/test_Result.py index 5f543861d..7126b4ab0 100644 --- a/python/tests/test_Result.py +++ b/python/tests/test_Result.py @@ -12,7 +12,6 @@ import vtk import numpy as np from damask import Result -from damask import Rotation from damask import Orientation from damask import tensor from damask import mechanics @@ -220,17 +219,15 @@ class TestResult: in_file = default.place('S') assert np.allclose(in_memory,in_file) - @pytest.mark.skip(reason='requires rework of lattice.f90') - @pytest.mark.parametrize('polar',[True,False]) - def test_add_pole(self,default,polar): - pole = np.array([1.,0.,0.]) - default.add_pole('O',pole,polar) - rot = Rotation(default.place('O')) - rotated_pole = rot * np.broadcast_to(pole,rot.shape+(3,)) - xy = rotated_pole[:,0:2]/(1.+abs(pole[2])) - in_memory = xy if not polar else \ - np.block([np.sqrt(xy[:,0:1]*xy[:,0:1]+xy[:,1:2]*xy[:,1:2]),np.arctan2(xy[:,1:2],xy[:,0:1])]) - in_file = default.place('p^{}_[1 0 0)'.format(u'rφ' if polar else 'xy')) + @pytest.mark.parametrize('options',[{'uvw':[1,0,0]},{'hkl':[0,1,1]}]) + def test_add_pole(self,default,options): + default.add_pole(**options) + rot = default.place('O') + in_memory = Orientation(rot,lattice=rot.dtype.metadata['lattice']).to_pole(**options) + brackets = ['[[]','[]]'] if 'uvw' in options.keys() else ['(',')'] # escape fnmatch + label = '{}{} {} {}{}'.format(brackets[0],*(list(options.values())[0]),brackets[1]) + in_file = default.place(f'p^{label}') + print(in_file - in_memory) assert np.allclose(in_memory,in_file) def test_add_rotation(self,default): From d1c3d767cc22399d72bd708ea99dbd43ee04b223 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 25 Jul 2021 19:31:48 +0200 Subject: [PATCH 20/27] documenting --- python/damask/_crystal.py | 14 ++++++++++++++ python/damask/_orientation.py | 19 +++++++++---------- python/damask/util.py | 20 +++++++++++++++++++- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/python/damask/_crystal.py b/python/damask/_crystal.py index 09e480709..6caa51fec 100644 --- a/python/damask/_crystal.py +++ b/python/damask/_crystal.py @@ -324,6 +324,20 @@ class Crystal(): def kinematics(self,mode): + """ + Return kinematic sytems. + + Parameters + ---------- + mode : {'slip','twin'} + Deformation mode. + + Returns + ------- + direction_plane : dictionary + Direction and plane of deformation mode. + + """ _kinematics = { 'cF': { 'slip' : np.array([ diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 448ba3159..48f3b3e46 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -107,7 +107,8 @@ class Orientation(Rotation,Crystal): -------- An array of 3 x 5 random orientations reduced to the fundamental zone of tetragonal symmetry: - >>> damask.Orientation.from_random(shape=(3,5),lattice='tetragonal').reduced + >>> import damask + >>> o=damask.Orientation.from_random(shape=(3,5),lattice='tetragonal').reduced """ @@ -712,6 +713,7 @@ class Orientation(Rotation,Crystal): -------- Inverse pole figure color of the e_3 direction for a crystal in "Cube" orientation with cubic symmetry: + >>> import damask >>> o = damask.Orientation(lattice='cubic') >>> o.IPF_color([0,0,1]) array([1., 0., 0.]) @@ -859,7 +861,7 @@ class Orientation(Rotation,Crystal): Parameters ---------- mode : {'slip', 'twin'} - Type of kinematics. + Deformation mode. Returns ------- @@ -868,7 +870,7 @@ class Orientation(Rotation,Crystal): Examples -------- - Schmid matrix (in lab frame) of slip systems of a face-centered + Schmid matrix (in lab frame) of first slip system of a face-centered cubic crystal in "Goss" orientation. >>> import damask @@ -880,13 +882,10 @@ class Orientation(Rotation,Crystal): [ 0.000, 0.000, 0.000]]) """ - try: - d = self.to_frame(uvw=self.kinematics(mode)['direction']) - p = self.to_frame(hkl=self.kinematics(mode)['plane']) - except KeyError: - raise (f'"{mode}" not defined for lattice "{self.lattice}"') - P = np.einsum('...i,...j',d/np.linalg.norm(d,axis=-1,keepdims=True), - p/np.linalg.norm(p,axis=-1,keepdims=True)) + d = self.to_frame(uvw=self.kinematics(mode)['direction']) + p = self.to_frame(hkl=self.kinematics(mode)['plane']) + P = np.einsum('...i,...j',d/np.linalg.norm(d,axis=1,keepdims=True), + p/np.linalg.norm(p,axis=1,keepdims=True)) return ~self.broadcast_to( self.shape+P.shape[:-2],mode='right') \ @ np.broadcast_to(P,self.shape+P.shape) diff --git a/python/damask/util.py b/python/damask/util.py index 18eb376da..d59b41b16 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -287,6 +287,8 @@ def project_stereographic(vector,direction='z',normalize=True,keepdims=False): Examples -------- + >>> import damask + >>> import numpy as np >>> project_stereographic(np.ones(3)) [0.3660254, 0.3660254] >>> project_stereographic(np.ones(3),direction='x',normalize=False,keepdims=True) @@ -339,7 +341,7 @@ def hybrid_IA(dist,N,rng_seed=None): def shapeshifter(fro,to,mode='left',keep_ones=False): """ - Return a tuple that reshapes 'fro' to become broadcastable to 'to'. + Return dimensions that reshape 'fro' to become broadcastable to 'to'. Parameters ---------- @@ -356,6 +358,22 @@ def shapeshifter(fro,to,mode='left',keep_ones=False): Treat '1' in fro as literal value instead of dimensional placeholder. Defaults to False. + Returns + ------- + new_dims : tuple + Dimensions for reshape. + + Example + ------- + >>> import numpy as np + >>> from damask import util + >>> a = np.ones((3,4,2)) + >>> b = np.ones(4) + >>> b_extended = b.reshape(util.shapeshifter(b.shape,a.shape)) + >>> (a * np.broadcast_to(b_extended,a.shape)).shape + (3,4,2) + + """ beg = dict(left ='(^.*\\b)', right='(^.*?\\b)') From e95157f039528446acc5967138d2277b65feff54 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 25 Jul 2021 20:32:24 +0200 Subject: [PATCH 21/27] unified behavior extra dimensions (here: slip systems) are added to the left, inline with .equivalent and .related --- python/damask/_orientation.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 48f3b3e46..0c730ac06 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -865,7 +865,7 @@ class Orientation(Rotation,Crystal): Returns ------- - P : numpy.ndarray of shape (...,N,3,3) + P : numpy.ndarray of shape (N,...,3,3) Schmid matrix for each of the N deformation systems. Examples @@ -887,8 +887,9 @@ class Orientation(Rotation,Crystal): P = np.einsum('...i,...j',d/np.linalg.norm(d,axis=1,keepdims=True), p/np.linalg.norm(p,axis=1,keepdims=True)) - return ~self.broadcast_to( self.shape+P.shape[:-2],mode='right') \ - @ np.broadcast_to(P,self.shape+P.shape) + shape = P.shape[0:1]+self.shape+(3,3) + return ~self.broadcast_to(shape[:-2]) \ + @ np.broadcast_to(P.reshape(util.shapeshifter(P.shape,shape)),shape) def related(self,model): From dd2d569482bf3de14aad1660f4537859d62da8aa Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 25 Jul 2021 21:28:35 +0200 Subject: [PATCH 22/27] ensure that vectorization works --- python/tests/test_Orientation.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index 38559c662..aa9897312 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -438,13 +438,22 @@ class TestOrientation: assert o.to_pole(**{kw:vector,'with_symmetry':with_symmetry}).shape \ == o.shape + (o.symmetry_operations.shape if with_symmetry else ()) + vector.shape - @pytest.mark.parametrize('lattice',['hP','cI','cF']) + @pytest.mark.parametrize('lattice',['hP','cI','cF']) #tI not included yet def test_Schmid(self,update,ref_path,lattice): L = Orientation(lattice=lattice) - for mode in ['slip','twin']: # ToDo test tI + for mode in ['slip','twin']: 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')) + + @pytest.mark.parametrize('lattice',['hP','cI','cF']) # tI not included yet + def test_Schmid_vectorize(self,lattice): + O = Orientation.from_random(shape=4,lattice=lattice) # noqa + for mode in ['slip','twin']: + P = O.Schmid(mode) + print(P.shape) + for i in range(4): + assert np.allclose(Orientation(rotation=O[i],lattice=lattice).Schmid(mode),P[:,i]) From 96767e75a3f01dbd9d15e4d6b77a6ebeb4f120a9 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Mon, 26 Jul 2021 00:19:03 +0200 Subject: [PATCH 23/27] can be done directly in the python library: P = damask.Orientation(lattice='cF').Schmid('slip') sigma = np.array([[1,0,0],[0,0,0],[0,0,0]]) sigma_extended = np.broadcast_to(sigma.reshape(damask.util.shapeshifter(sigma.shape,P.shape)), P.shape) tau = np.einsum('i...jk,i...jk->i...',sigma_extended,P) --- processing/legacy/addSchmidfactors.py | 117 -------------------------- 1 file changed, 117 deletions(-) delete mode 100755 processing/legacy/addSchmidfactors.py diff --git a/processing/legacy/addSchmidfactors.py b/processing/legacy/addSchmidfactors.py deleted file mode 100755 index 8e1e4ffb7..000000000 --- a/processing/legacy/addSchmidfactors.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env python3 - -import os -import sys -from io import StringIO -from optparse import OptionParser - -import numpy as np - -import damask - - -scriptName = os.path.splitext(os.path.basename(__file__))[0] -scriptID = ' '.join([scriptName,damask.version]) - -slipSystems = { -'fcc': damask.lattice.kinematics['cF']['slip'][:12], -'bcc': damask.lattice.kinematics['cI']['slip'], -'hex': damask.lattice.kinematics['hP']['slip'], -} - -# -------------------------------------------------------------------- -# MAIN -# -------------------------------------------------------------------- - -parser = OptionParser(usage='%prog options [ASCIItable(s)]', description = """ -Add columns listing Schmid factors (and optional trace vector of selected system) for given Euler angles. - -""", version = scriptID) - -lattice_choices = list(slipSystems.keys()) -parser.add_option('-l', - '--lattice', - dest = 'lattice', type = 'choice', choices = lattice_choices, metavar='string', - help = 'type of lattice structure [%default] {}'.format(lattice_choices)) -parser.add_option('--covera', - dest = 'CoverA', type = 'float', metavar = 'float', - help = 'C over A ratio for hexagonal systems [%default]') -parser.add_option('-f', - '--force', - dest = 'force', - type = 'float', nargs = 3, metavar = 'float float float', - help = 'force direction in lab frame [%default]') -parser.add_option('-n', - '--normal', - dest = 'normal', - type = 'float', nargs = 3, metavar = 'float float float', - help = 'stress plane normal in lab frame, per default perpendicular to the force') -parser.add_option('-o', - '--orientation', - dest = 'quaternion', - metavar = 'string', - help = 'label of crystal orientation given as unit quaternion [%default]') - -parser.set_defaults(force = (0.0,0.0,1.0), - quaternion='orientation', - normal = None, - lattice = lattice_choices[0], - CoverA = np.sqrt(8./3.), - ) - -(options, filenames) = parser.parse_args() -if filenames == []: filenames = [None] - -force = np.array(options.force)/np.linalg.norm(options.force) - -if options.normal is not None: - normal = np.array(options.normal)/np.linalg.norm(options.ormal) - if abs(np.dot(force,normal)) > 1e-3: - parser.error('stress plane normal not orthogonal to force direction') -else: - normal = force - - -if options.lattice in ['bcc','fcc']: - slip_direction = slipSystems[options.lattice][:,:3] - slip_normal = slipSystems[options.lattice][:,3:] -elif options.lattice == 'hex': - slip_direction = np.zeros((len(slipSystems['hex']),3),'d') - slip_normal = np.zeros_like(slip_direction) - # convert 4 Miller index notation of hex to orthogonal 3 Miller index notation - for i in range(len(slip_direction)): - slip_direction[i] = np.array([slipSystems['hex'][i,0]*1.5, - (slipSystems['hex'][i,0] + 2.*slipSystems['hex'][i,1])*0.5*np.sqrt(3), - slipSystems['hex'][i,3]*options.CoverA, - ]) - slip_normal[i] = np.array([slipSystems['hex'][i,4], - (slipSystems['hex'][i,4] + 2.*slipSystems['hex'][i,5])/np.sqrt(3), - slipSystems['hex'][i,7]/options.CoverA, - ]) - -slip_direction /= np.linalg.norm(slip_direction,axis=1,keepdims=True) -slip_normal /= np.linalg.norm(slip_normal, axis=1,keepdims=True) - -labels = ['S[{direction[0]:.1g}_{direction[1]:.1g}_{direction[2]:.1g}]' - '({normal[0]:.1g}_{normal[1]:.1g}_{normal[2]:.1g})'\ - .format(normal = theNormal, direction = theDirection, - ) for theNormal,theDirection in zip(slip_normal,slip_direction)] - -for name in filenames: - damask.util.report(scriptName,name) - - table = damask.Table.load(StringIO(''.join(sys.stdin.read())) if name is None else name) - - o = damask.Rotation.from_quaternion(table.get(options.quaternion)) - - force = np.broadcast_to(force, o.shape+(3,)) - normal = np.broadcast_to(normal,o.shape+(3,)) - slip_direction = np.broadcast_to(slip_direction,o.shape+slip_direction.shape) - slip_normal = np.broadcast_to(slip_normal, o.shape+slip_normal.shape) - S = np.abs(np.einsum('ijk,ik->ij',slip_direction,(o@force))* - np.einsum('ijk,ik->ij',slip_normal, (o@normal))) - - for i,label in enumerate(labels): - table = table.add(label,S[:,i],scriptID+' '+' '.join(sys.argv[1:])) - - table.save((sys.stdout if name is None else name)) From bb5db3e79cdf1618fbb0cf9818bdf13ae71b7227 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 4 Aug 2021 16:06:28 +0200 Subject: [PATCH 24/27] structured according to family --- python/damask/_crystal.py | 331 +++++++++++++++++----------------- python/damask/_orientation.py | 4 +- 2 files changed, 170 insertions(+), 165 deletions(-) diff --git a/python/damask/_crystal.py b/python/damask/_crystal.py index 6caa51fec..7fc2e39b6 100644 --- a/python/damask/_crystal.py +++ b/python/damask/_crystal.py @@ -340,178 +340,183 @@ class Crystal(): """ _kinematics = { 'cF': { - 'slip' : np.array([ - [+0,+1,-1, +1,+1,+1], - [-1,+0,+1, +1,+1,+1], - [+1,-1,+0, +1,+1,+1], - [+0,-1,-1, -1,-1,+1], - [+1,+0,+1, -1,-1,+1], - [-1,+1,+0, -1,-1,+1], - [+0,-1,+1, +1,-1,-1], - [-1,+0,-1, +1,-1,-1], - [+1,+1,+0, +1,-1,-1], - [+0,+1,+1, -1,+1,-1], - [+1,+0,-1, -1,+1,-1], - [-1,-1,+0, -1,+1,-1], - [+1,+1,+0, +1,-1,+0], - [+1,-1,+0, +1,+1,+0], - [+1,+0,+1, +1,+0,-1], - [+1,+0,-1, +1,+0,+1], - [+0,+1,+1, +0,+1,-1], - [+0,+1,-1, +0,+1,+1], - ],dtype=float), - 'twin' : np.array([ - [-2, 1, 1, 1, 1, 1], - [ 1,-2, 1, 1, 1, 1], - [ 1, 1,-2, 1, 1, 1], - [ 2,-1, 1, -1,-1, 1], - [-1, 2, 1, -1,-1, 1], - [-1,-1,-2, -1,-1, 1], - [-2,-1,-1, 1,-1,-1], - [ 1, 2,-1, 1,-1,-1], - [ 1,-1, 2, 1,-1,-1], - [ 2, 1,-1, -1, 1,-1], - [-1,-2,-1, -1, 1,-1], - [-1, 1, 2, -1, 1,-1], - ],dtype=float), + 'slip' :[np.array([ + [+0,+1,-1, +1,+1,+1], + [-1,+0,+1, +1,+1,+1], + [+1,-1,+0, +1,+1,+1], + [+0,-1,-1, -1,-1,+1], + [+1,+0,+1, -1,-1,+1], + [-1,+1,+0, -1,-1,+1], + [+0,-1,+1, +1,-1,-1], + [-1,+0,-1, +1,-1,-1], + [+1,+1,+0, +1,-1,-1], + [+0,+1,+1, -1,+1,-1], + [+1,+0,-1, -1,+1,-1], + [-1,-1,+0, -1,+1,-1]]), + np.array([ + [+1,+1,+0, +1,-1,+0], + [+1,-1,+0, +1,+1,+0], + [+1,+0,+1, +1,+0,-1], + [+1,+0,-1, +1,+0,+1], + [+0,+1,+1, +0,+1,-1], + [+0,+1,-1, +0,+1,+1]])], + 'twin' :[np.array([ + [-2, 1, 1, 1, 1, 1], + [ 1,-2, 1, 1, 1, 1], + [ 1, 1,-2, 1, 1, 1], + [ 2,-1, 1, -1,-1, 1], + [-1, 2, 1, -1,-1, 1], + [-1,-1,-2, -1,-1, 1], + [-2,-1,-1, 1,-1,-1], + [ 1, 2,-1, 1,-1,-1], + [ 1,-1, 2, 1,-1,-1], + [ 2, 1,-1, -1, 1,-1], + [-1,-2,-1, -1, 1,-1], + [-1, 1, 2, -1, 1,-1]])] }, 'cI': { - 'slip' : np.array([ - [+1,-1,+1, +0,+1,+1], - [-1,-1,+1, +0,+1,+1], - [+1,+1,+1, +0,-1,+1], - [-1,+1,+1, +0,-1,+1], - [-1,+1,+1, +1,+0,+1], - [-1,-1,+1, +1,+0,+1], - [+1,+1,+1, -1,+0,+1], - [+1,-1,+1, -1,+0,+1], - [-1,+1,+1, +1,+1,+0], - [-1,+1,-1, +1,+1,+0], - [+1,+1,+1, -1,+1,+0], - [+1,+1,-1, -1,+1,+0], - [-1,+1,+1, +2,+1,+1], - [+1,+1,+1, -2,+1,+1], - [+1,+1,-1, +2,-1,+1], - [+1,-1,+1, +2,+1,-1], - [+1,-1,+1, +1,+2,+1], - [+1,+1,-1, -1,+2,+1], - [+1,+1,+1, +1,-2,+1], - [-1,+1,+1, +1,+2,-1], - [+1,+1,-1, +1,+1,+2], - [+1,-1,+1, -1,+1,+2], - [-1,+1,+1, +1,-1,+2], - [+1,+1,+1, +1,+1,-2], - [+1,+1,-1, +1,+2,+3], - [+1,-1,+1, -1,+2,+3], - [-1,+1,+1, +1,-2,+3], - [+1,+1,+1, +1,+2,-3], - [+1,-1,+1, +1,+3,+2], - [+1,+1,-1, -1,+3,+2], - [+1,+1,+1, +1,-3,+2], - [-1,+1,+1, +1,+3,-2], - [+1,+1,-1, +2,+1,+3], - [+1,-1,+1, -2,+1,+3], - [-1,+1,+1, +2,-1,+3], - [+1,+1,+1, +2,+1,-3], - [+1,-1,+1, +2,+3,+1], - [+1,+1,-1, -2,+3,+1], - [+1,+1,+1, +2,-3,+1], - [-1,+1,+1, +2,+3,-1], - [-1,+1,+1, +3,+1,+2], - [+1,+1,+1, -3,+1,+2], - [+1,+1,-1, +3,-1,+2], - [+1,-1,+1, +3,+1,-2], - [-1,+1,+1, +3,+2,+1], - [+1,+1,+1, -3,+2,+1], - [+1,+1,-1, +3,-2,+1], - [+1,-1,+1, +3,+2,-1], - ],dtype=float), - 'twin' : np.array([ - [-1, 1, 1, 2, 1, 1], - [ 1, 1, 1, -2, 1, 1], - [ 1, 1,-1, 2,-1, 1], - [ 1,-1, 1, 2, 1,-1], - [ 1,-1, 1, 1, 2, 1], - [ 1, 1,-1, -1, 2, 1], - [ 1, 1, 1, 1,-2, 1], - [-1, 1, 1, 1, 2,-1], - [ 1, 1,-1, 1, 1, 2], - [ 1,-1, 1, -1, 1, 2], - [-1, 1, 1, 1,-1, 2], - [ 1, 1, 1, 1, 1,-2], - ],dtype=float), + 'slip' :[np.array([ + [+1,-1,+1, +0,+1,+1], + [-1,-1,+1, +0,+1,+1], + [+1,+1,+1, +0,-1,+1], + [-1,+1,+1, +0,-1,+1], + [-1,+1,+1, +1,+0,+1], + [-1,-1,+1, +1,+0,+1], + [+1,+1,+1, -1,+0,+1], + [+1,-1,+1, -1,+0,+1], + [-1,+1,+1, +1,+1,+0], + [-1,+1,-1, +1,+1,+0], + [+1,+1,+1, -1,+1,+0], + [+1,+1,-1, -1,+1,+0]]), + np.array([ + [-1,+1,+1, +2,+1,+1], + [+1,+1,+1, -2,+1,+1], + [+1,+1,-1, +2,-1,+1], + [+1,-1,+1, +2,+1,-1], + [+1,-1,+1, +1,+2,+1], + [+1,+1,-1, -1,+2,+1], + [+1,+1,+1, +1,-2,+1], + [-1,+1,+1, +1,+2,-1], + [+1,+1,-1, +1,+1,+2], + [+1,-1,+1, -1,+1,+2], + [-1,+1,+1, +1,-1,+2], + [+1,+1,+1, +1,+1,-2]]), + np.array([ + [+1,+1,-1, +1,+2,+3], + [+1,-1,+1, -1,+2,+3], + [-1,+1,+1, +1,-2,+3], + [+1,+1,+1, +1,+2,-3], + [+1,-1,+1, +1,+3,+2], + [+1,+1,-1, -1,+3,+2], + [+1,+1,+1, +1,-3,+2], + [-1,+1,+1, +1,+3,-2], + [+1,+1,-1, +2,+1,+3], + [+1,-1,+1, -2,+1,+3], + [-1,+1,+1, +2,-1,+3], + [+1,+1,+1, +2,+1,-3], + [+1,-1,+1, +2,+3,+1], + [+1,+1,-1, -2,+3,+1], + [+1,+1,+1, +2,-3,+1], + [-1,+1,+1, +2,+3,-1], + [-1,+1,+1, +3,+1,+2], + [+1,+1,+1, -3,+1,+2], + [+1,+1,-1, +3,-1,+2], + [+1,-1,+1, +3,+1,-2], + [-1,+1,+1, +3,+2,+1], + [+1,+1,+1, -3,+2,+1], + [+1,+1,-1, +3,-2,+1], + [+1,-1,+1, +3,+2,-1]])], + 'twin' :[np.array([ + [-1, 1, 1, 2, 1, 1], + [ 1, 1, 1, -2, 1, 1], + [ 1, 1,-1, 2,-1, 1], + [ 1,-1, 1, 2, 1,-1], + [ 1,-1, 1, 1, 2, 1], + [ 1, 1,-1, -1, 2, 1], + [ 1, 1, 1, 1,-2, 1], + [-1, 1, 1, 1, 2,-1], + [ 1, 1,-1, 1, 1, 2], + [ 1,-1, 1, -1, 1, 2], + [-1, 1, 1, 1,-1, 2], + [ 1, 1, 1, 1, 1,-2]])] }, 'hP': { - 'slip' : np.array([ - [+2,-1,-1,+0, +0,+0,+0,+1], - [-1,+2,-1,+0, +0,+0,+0,+1], - [-1,-1,+2,+0, +0,+0,+0,+1], - [+2,-1,-1,+0, +0,+1,-1,+0], - [-1,+2,-1,+0, -1,+0,+1,+0], - [-1,-1,+2,+0, +1,-1,+0,+0], - [-1,+1,+0,+0, +1,+1,-2,+0], - [+0,-1,+1,+0, -2,+1,+1,+0], - [+1,+0,-1,+0, +1,-2,+1,+0], - [-1,+2,-1,+0, +1,+0,-1,+1], - [-2,+1,+1,+0, +0,+1,-1,+1], - [-1,-1,+2,+0, -1,+1,+0,+1], - [+1,-2,+1,+0, -1,+0,+1,+1], - [+2,-1,-1,+0, +0,-1,+1,+1], - [+1,+1,-2,+0, +1,-1,+0,+1], - [-2,+1,+1,+3, +1,+0,-1,+1], - [-1,-1,+2,+3, +1,+0,-1,+1], - [-1,-1,+2,+3, +0,+1,-1,+1], - [+1,-2,+1,+3, +0,+1,-1,+1], - [+1,-2,+1,+3, -1,+1,+0,+1], - [+2,-1,-1,+3, -1,+1,+0,+1], - [+2,-1,-1,+3, -1,+0,+1,+1], - [+1,+1,-2,+3, -1,+0,+1,+1], - [+1,+1,-2,+3, +0,-1,+1,+1], - [-1,+2,-1,+3, +0,-1,+1,+1], - [-1,+2,-1,+3, +1,-1,+0,+1], - [-2,+1,+1,+3, +1,-1,+0,+1], - [-1,-1,+2,+3, +1,+1,-2,+2], - [+1,-2,+1,+3, -1,+2,-1,+2], - [+2,-1,-1,+3, -2,+1,+1,+2], - [+1,+1,-2,+3, -1,-1,+2,+2], - [-1,+2,-1,+3, +1,-2,+1,+2], - [-2,+1,+1,+3, +2,-1,-1,+2], - ],dtype=float), - 'twin' : np.array([ - [-1, 0, 1, 1, 1, 0,-1, 2], # shear = (3-(c/a)^2)/(sqrt(3) c/a) <-10.1>{10.2} - [ 0,-1, 1, 1, 0, 1,-1, 2], - [ 1,-1, 0, 1, -1, 1, 0, 2], - [ 1, 0,-1, 1, -1, 0, 1, 2], - [ 0, 1,-1, 1, 0,-1, 1, 2], - [-1, 1, 0, 1, 1,-1, 0, 2], - [-1,-1, 2, 6, 1, 1,-2, 1], # shear = 1/(c/a) <11.6>{-1-1.1} - [ 1,-2, 1, 6, -1, 2,-1, 1], - [ 2,-1,-1, 6, -2, 1, 1, 1], - [ 1, 1,-2, 6, -1,-1, 2, 1], - [-1, 2,-1, 6, 1,-2, 1, 1], - [-2, 1, 1, 6, 2,-1,-1, 1], - [ 1, 0,-1,-2, 1, 0,-1, 1], # shear = (4(c/a)^2-9)/(4 sqrt(3) c/a) <10.-2>{10.1} - [ 0, 1,-1,-2, 0, 1,-1, 1], - [-1, 1, 0,-2, -1, 1, 0, 1], - [-1, 0, 1,-2, -1, 0, 1, 1], - [ 0,-1, 1,-2, 0,-1, 1, 1], - [ 1,-1, 0,-2, 1,-1, 0, 1], - [ 1, 1,-2,-3, 1, 1,-2, 2], # shear = 2((c/a)^2-2)/(3 c/a) <11.-3>{11.2} - [-1, 2,-1,-3, -1, 2,-1, 2], - [-2, 1, 1,-3, -2, 1, 1, 2], - [-1,-1, 2,-3, -1,-1, 2, 2], - [ 1,-2, 1,-3, 1,-2, 1, 2], - [ 2,-1,-1,-3, 2,-1,-1, 2], - ],dtype=float), + 'slip' :[np.array([ + [+2,-1,-1,+0, +0,+0,+0,+1], + [-1,+2,-1,+0, +0,+0,+0,+1], + [-1,-1,+2,+0, +0,+0,+0,+1]]), + np.array([ + [+2,-1,-1,+0, +0,+1,-1,+0], + [-1,+2,-1,+0, -1,+0,+1,+0], + [-1,-1,+2,+0, +1,-1,+0,+0]]), + np.array([ + [-1,+1,+0,+0, +1,+1,-2,+0], + [+0,-1,+1,+0, -2,+1,+1,+0], + [+1,+0,-1,+0, +1,-2,+1,+0]]), + np.array([ + [-1,+2,-1,+0, +1,+0,-1,+1], + [-2,+1,+1,+0, +0,+1,-1,+1], + [-1,-1,+2,+0, -1,+1,+0,+1], + [+1,-2,+1,+0, -1,+0,+1,+1], + [+2,-1,-1,+0, +0,-1,+1,+1], + [+1,+1,-2,+0, +1,-1,+0,+1]]), + np.array([ + [-2,+1,+1,+3, +1,+0,-1,+1], + [-1,-1,+2,+3, +1,+0,-1,+1], + [-1,-1,+2,+3, +0,+1,-1,+1], + [+1,-2,+1,+3, +0,+1,-1,+1], + [+1,-2,+1,+3, -1,+1,+0,+1], + [+2,-1,-1,+3, -1,+1,+0,+1], + [+2,-1,-1,+3, -1,+0,+1,+1], + [+1,+1,-2,+3, -1,+0,+1,+1], + [+1,+1,-2,+3, +0,-1,+1,+1], + [-1,+2,-1,+3, +0,-1,+1,+1], + [-1,+2,-1,+3, +1,-1,+0,+1], + [-2,+1,+1,+3, +1,-1,+0,+1]]), + np.array([ + [-1,-1,+2,+3, +1,+1,-2,+2], + [+1,-2,+1,+3, -1,+2,-1,+2], + [+2,-1,-1,+3, -2,+1,+1,+2], + [+1,+1,-2,+3, -1,-1,+2,+2], + [-1,+2,-1,+3, +1,-2,+1,+2], + [-2,+1,+1,+3, +2,-1,-1,+2]])], + 'twin' :[np.array([ + [-1, 0, 1, 1, 1, 0,-1, 2], # shear = (3-(c/a)^2)/(sqrt(3) c/a) <-10.1>{10.2} + [ 0,-1, 1, 1, 0, 1,-1, 2], + [ 1,-1, 0, 1, -1, 1, 0, 2], + [ 1, 0,-1, 1, -1, 0, 1, 2], + [ 0, 1,-1, 1, 0,-1, 1, 2], + [-1, 1, 0, 1, 1,-1, 0, 2]]), + np.array([ + [-1,-1, 2, 6, 1, 1,-2, 1], # shear = 1/(c/a) <11.6>{-1-1.1} + [ 1,-2, 1, 6, -1, 2,-1, 1], + [ 2,-1,-1, 6, -2, 1, 1, 1], + [ 1, 1,-2, 6, -1,-1, 2, 1], + [-1, 2,-1, 6, 1,-2, 1, 1], + [-2, 1, 1, 6, 2,-1,-1, 1]]), + np.array([ + [ 1, 0,-1,-2, 1, 0,-1, 1], # shear = (4(c/a)^2-9)/(4 sqrt(3) c/a) <10.-2>{10.1} + [ 0, 1,-1,-2, 0, 1,-1, 1], + [-1, 1, 0,-2, -1, 1, 0, 1], + [-1, 0, 1,-2, -1, 0, 1, 1], + [ 0,-1, 1,-2, 0,-1, 1, 1], + [ 1,-1, 0,-2, 1,-1, 0, 1]]), + np.array([ + [ 1, 1,-2,-3, 1, 1,-2, 2], # shear = 2((c/a)^2-2)/(3 c/a) <11.-3>{11.2} + [-1, 2,-1,-3, -1, 2,-1, 2], + [-2, 1, 1,-3, -2, 1, 1, 2], + [-1,-1, 2,-3, -1,-1, 2, 2], + [ 1,-2, 1,-3, 1,-2, 1, 2], + [ 2,-1,-1,-3, 2,-1,-1, 2]])] }, } master = _kinematics[self.lattice][mode] if self.lattice == 'hP': - return {'direction':util.Bravais_to_Miller(uvtw=master[:,0:4]), - 'plane': util.Bravais_to_Miller(hkil=master[:,4:8])} + return {'direction':[util.Bravais_to_Miller(uvtw=m[:,0:4]) for m in master], + 'plane': [util.Bravais_to_Miller(hkil=m[:,4:8]) for m in master]} else: - return {'direction':master[:,0:3], - 'plane': master[:,3:6]} + return {'direction':[m[:,0:3] for m in master], + 'plane': [m[:,3:6] for m in master]} def relation_operations(self,model): diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 0c730ac06..80e5d3929 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -882,8 +882,8 @@ class Orientation(Rotation,Crystal): [ 0.000, 0.000, 0.000]]) """ - d = self.to_frame(uvw=self.kinematics(mode)['direction']) - p = self.to_frame(hkl=self.kinematics(mode)['plane']) + d = self.to_frame(uvw=np.vstack(self.kinematics(mode)['direction'])) + p = self.to_frame(hkl=np.vstack(self.kinematics(mode)['plane'])) P = np.einsum('...i,...j',d/np.linalg.norm(d,axis=1,keepdims=True), p/np.linalg.norm(p,axis=1,keepdims=True)) From b5be4c3fa51ef1a2bd5b521d62bbd2c23946b532 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Wed, 4 Aug 2021 17:45:25 +0200 Subject: [PATCH 25/27] compatible with material.yaml --- python/damask/_orientation.py | 23 ++++++++++++++++------- python/tests/test_Orientation.py | 7 +++---- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 80e5d3929..749115566 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -854,14 +854,15 @@ class Orientation(Rotation,Crystal): @ np.broadcast_to(v,self.shape+v.shape) - def Schmid(self,mode): + def Schmid(self,*,N_slip=None,N_twin=None): u""" - Calculate Schmid matrix P = d ⨂ n in the lab frame for given lattice shear kinematics. + Calculate Schmid matrix P = d ⨂ n in the lab frame for selected deformation systems. Parameters ---------- - mode : {'slip', 'twin'} - Deformation mode. + N_slip|N_twin : iterable of int + Number of deformation systems per deformation system. + Use '*'. to select all. Returns ------- @@ -876,14 +877,22 @@ class Orientation(Rotation,Crystal): >>> import damask >>> import numpy as np >>> np.set_printoptions(3,suppress=True,floatmode='fixed') - >>> damask.Orientation.from_Eulers(phi=[0,45,0],degrees=True,lattice='cF').Schmid('slip')[0] + >>> O = damask.Orientation.from_Euler_angles(phi=[0,45,0],degrees=True,lattice='cF') + >>> O.Schmid(N_slip=[1]) array([[ 0.000, 0.000, 0.000], [ 0.577, -0.000, 0.816], [ 0.000, 0.000, 0.000]]) """ - d = self.to_frame(uvw=np.vstack(self.kinematics(mode)['direction'])) - p = self.to_frame(hkl=np.vstack(self.kinematics(mode)['plane'])) + if (N_slip is not None) ^ (N_twin is None): + raise KeyError('Specify either "N_slip" or "N_twin"') + + kinematics = self.kinematics('slip' if N_twin is None else 'twin') + active = N_slip if N_twin is None else N_twin + if active == '*': active = [len(a) for a in kinematics] + + 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), p/np.linalg.norm(p,axis=1,keepdims=True)) diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index aa9897312..7d820b2bf 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -440,10 +440,10 @@ class TestOrientation: @pytest.mark.parametrize('lattice',['hP','cI','cF']) #tI not included yet def test_Schmid(self,update,ref_path,lattice): - L = Orientation(lattice=lattice) + O = Orientation(lattice=lattice) # noqa for mode in ['slip','twin']: reference = ref_path/f'{lattice}_{mode}.txt' - P = L.Schmid(mode) + P = O.Schmid(N_slip='*') if mode == 'slip' else O.Schmid(N_twin='*') if update: table = Table(P.reshape(-1,9),{'Schmid':(3,3,)}) table.save(reference) @@ -453,7 +453,6 @@ class TestOrientation: def test_Schmid_vectorize(self,lattice): O = Orientation.from_random(shape=4,lattice=lattice) # noqa for mode in ['slip','twin']: - P = O.Schmid(mode) - print(P.shape) + P = O.Schmid(N_slip='*') if mode == 'slip' else O.Schmid(N_twin='*') for i in range(4): assert np.allclose(Orientation(rotation=O[i],lattice=lattice).Schmid(mode),P[:,i]) From b1dab5f398aa133c0c3df6f4dd4dd20997b8e236 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Sun, 8 Aug 2021 10:44:38 +0200 Subject: [PATCH 26/27] tests working now --- python/damask/_orientation.py | 6 +++--- python/tests/test_Orientation.py | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 749115566..1bc28bc13 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -861,7 +861,7 @@ class Orientation(Rotation,Crystal): Parameters ---------- N_slip|N_twin : iterable of int - Number of deformation systems per deformation system. + Number of deformation systems per family of the deformation system. Use '*'. to select all. Returns @@ -871,7 +871,7 @@ class Orientation(Rotation,Crystal): Examples -------- - Schmid matrix (in lab frame) of first slip system of a face-centered + Schmid matrix (in lab frame) of first octahedral slip system of a face-centered cubic crystal in "Goss" orientation. >>> import damask @@ -889,7 +889,7 @@ class Orientation(Rotation,Crystal): kinematics = self.kinematics('slip' if N_twin is None else 'twin') active = N_slip if N_twin is None else N_twin - if active == '*': active = [len(a) for a in kinematics] + if active == '*': active = [len(a) for a in kinematics['direction']] 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)])) diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index 7d820b2bf..0c735c20f 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -453,6 +453,7 @@ class TestOrientation: def test_Schmid_vectorize(self,lattice): O = Orientation.from_random(shape=4,lattice=lattice) # noqa for mode in ['slip','twin']: - P = O.Schmid(N_slip='*') if mode == 'slip' else O.Schmid(N_twin='*') + Ps = O.Schmid(N_slip='*') if mode == 'slip' else O.Schmid(N_twin='*') for i in range(4): - assert np.allclose(Orientation(rotation=O[i],lattice=lattice).Schmid(mode),P[:,i]) + P = O[i].Schmid(N_slip='*') if mode == 'slip' else O[i].Schmid(N_twin='*') + assert np.allclose(P,Ps[:,i]) From 5292f9b414488f977970f90afe4f4fc90107a762 Mon Sep 17 00:00:00 2001 From: Philip Eisenlohr Date: Sun, 8 Aug 2021 17:56:54 -0400 Subject: [PATCH 27/27] added crystal repr(); fixed typos; extended help --- python/damask/_crystal.py | 23 ++++++++++++++++------- python/damask/_orientation.py | 13 ++++++------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/python/damask/_crystal.py b/python/damask/_crystal.py index 7fc2e39b6..73d9f031c 100644 --- a/python/damask/_crystal.py +++ b/python/damask/_crystal.py @@ -26,7 +26,7 @@ lattice_symmetries = { class Crystal(): - """Lattice.""" + """Crystal lattice.""" def __init__(self,*, family = None, @@ -41,7 +41,7 @@ class Crystal(): ---------- family : {'triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic'}, optional. Name of the crystal family. - Will be infered if 'lattice' is given. + Will be inferred if 'lattice' is given. lattice : {'aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF'}, optional. Name of the Bravais lattice in Pearson notation. a : float, optional @@ -110,14 +110,23 @@ class Crystal(): self.alpha = self.beta = self.gamma = None + def __repr__(self): + """Represent.""" + return '\n'.join([f'Crystal family {self.family}'] + + ([] if self.lattice is None else [f'Bravais lattice {self.lattice}']+ + list(map(lambda x:f'{x[0]}:{x[1]:.5g}', + zip(['a','b','c','alpha','beta','gamma',], + self.parameters)))) + ) + def __eq__(self,other): """ Equal to other. Parameters ---------- - other : Lattice - Lattice to check for equality. + other : Crystal + Crystal to check for equality. """ return self.lattice == other.lattice and \ @@ -325,7 +334,7 @@ class Crystal(): def kinematics(self,mode): """ - Return kinematic sytems. + Return crystal kinematics systems. Parameters ---------- @@ -335,7 +344,7 @@ class Crystal(): Returns ------- direction_plane : dictionary - Direction and plane of deformation mode. + Directions and planes of deformation mode families. """ _kinematics = { @@ -531,7 +540,7 @@ class Crystal(): Returns ------- operations : (string, damask.Rotation) - Rotations characterizing the orientation relationship. + Resulting lattice and rotations characterizing the orientation relationship. References ---------- diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 1bc28bc13..7007951da 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -108,7 +108,7 @@ class Orientation(Rotation,Crystal): An array of 3 x 5 random orientations reduced to the fundamental zone of tetragonal symmetry: >>> import damask - >>> o=damask.Orientation.from_random(shape=(3,5),lattice='tetragonal').reduced + >>> o=damask.Orientation.from_random(shape=(3,5),family='tetragonal').reduced """ @@ -138,9 +138,8 @@ class Orientation(Rotation,Crystal): def __repr__(self): """Represent.""" - return '\n'.join(([] if self.lattice is None else [f'Bravais lattice {self.lattice}']) - + ([f'Crystal family {self.family}']) - + [super().__repr__()]) + return '\n'.join([Crystal.__repr__(self), + Rotation.__repr__(self)]) def __copy__(self,rotation=None): @@ -862,7 +861,7 @@ class Orientation(Rotation,Crystal): ---------- N_slip|N_twin : iterable of int Number of deformation systems per family of the deformation system. - Use '*'. to select all. + Use '*' to select all. Returns ------- @@ -887,8 +886,8 @@ class Orientation(Rotation,Crystal): if (N_slip is not None) ^ (N_twin is None): raise KeyError('Specify either "N_slip" or "N_twin"') - kinematics = self.kinematics('slip' if N_twin is None else 'twin') - active = N_slip if N_twin is None else N_twin + kinematics,active = (self.kinematics('slip'),N_slip) if N_twin is None else \ + (self.kinematics('twin'),N_twin) if active == '*': active = [len(a) for a in kinematics['direction']] d = self.to_frame(uvw=np.vstack([kinematics['direction'][i][:n] for i,n in enumerate(active)]))