simplifications + more tests

This commit is contained in:
Martin Diehl 2020-07-01 08:48:30 +02:00
parent de8e9b5fc1
commit e18a5b8a1b
4 changed files with 79 additions and 111 deletions

View File

@ -1,7 +1,5 @@
import numpy as np import numpy as np
from . import Rotation
class Symmetry: class Symmetry:
""" """
@ -29,7 +27,6 @@ class Symmetry:
raise KeyError(f'Crystal system "{system}" is unknown') raise KeyError(f'Crystal system "{system}" is unknown')
self.system = system.lower() if isinstance(system,str) else system self.system = system.lower() if isinstance(system,str) else system
self.lattice = self.system # ToDo: for compatibility
def __copy__(self): def __copy__(self):
@ -219,6 +216,7 @@ class Symmetry:
return np.ones_like(rho[...,0],dtype=bool) return np.ones_like(rho[...,0],dtype=bool)
#ToDo: IPF color in separate function
def in_SST(self,vector,proper=False,color=False): def in_SST(self,vector,proper=False,color=False):
""" """
Check whether given vector falls into standard stereographic triangle of own symmetry. Check whether given vector falls into standard stereographic triangle of own symmetry.
@ -311,9 +309,9 @@ class Symmetry:
# ****************************************************************************************** # ******************************************************************************************
class Lattice: # ToDo: Make a subclass of Symmetry! class Lattice: # ToDo: Make a subclass of Symmetry!
""" """
Lattice system. Bravais lattice.
Currently, this contains only a mapping from Bravais lattice to symmetry This contains only a mapping from Bravais lattice to symmetry
and orientation relationships. It could include twin and slip systems. and orientation relationships. It could include twin and slip systems.
References References
@ -331,7 +329,7 @@ class Lattice: # ToDo: Make a subclass of Symmetry!
} }
def __init__(self, lattice): def __init__(self,lattice,c_over_a=None):
""" """
New lattice of given type. New lattice of given type.
@ -345,20 +343,20 @@ class Lattice: # ToDo: Make a subclass of Symmetry!
self.symmetry = Symmetry(self.lattices[lattice]['system']) self.symmetry = Symmetry(self.lattices[lattice]['system'])
# transition to subclass # transition to subclass
self.system = self.symmetry.system self.system = self.symmetry.system
self.in_SST = self.symmetry.in_SST self.in_SST = self.symmetry.in_SST
self.in_FZ = self.symmetry.in_FZ self.in_FZ = self.symmetry.in_FZ
self.in_disorientation_SST = self.symmetry.in_disorientation_SST
def __repr__(self): def __repr__(self):
"""Report basic lattice information.""" """Report basic lattice information."""
return 'Bravais lattice {} ({} symmetry)'.format(self.lattice,self.symmetry) return 'Bravais lattice {} ({} crystal system)'.format(self.lattice,self.symmetry)
# Kurdjomov--Sachs orientation relationship for fcc <-> bcc transformation # Kurdjomov--Sachs orientation relationship for fcc <-> bcc transformation
# from S. Morito et al., Journal of Alloys and Compounds 577:s587-s592, 2013 # 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 # also see K. Kitahara et al., Acta Materialia 54:1279-1288, 2006
KS = {'mapping':{'fcc':0,'bcc':1}, _KS = {'mapping':{'fcc':0,'bcc':1},
'planes': np.array([ 'planes': 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]],
@ -412,7 +410,7 @@ class Lattice: # ToDo: Make a subclass of Symmetry!
# Greninger--Troiano orientation relationship for fcc <-> bcc transformation # Greninger--Troiano orientation relationship for fcc <-> bcc transformation
# from Y. He et al., Journal of Applied Crystallography 39:72-81, 2006 # from Y. He et al., Journal of Applied Crystallography 39:72-81, 2006
GT = {'mapping':{'fcc':0,'bcc':1}, _GT = {'mapping':{'fcc':0,'bcc':1},
'planes': np.array([ 'planes': np.array([
[[ 1, 1, 1],[ 1, 0, 1]], [[ 1, 1, 1],[ 1, 0, 1]],
[[ 1, 1, 1],[ 1, 1, 0]], [[ 1, 1, 1],[ 1, 1, 0]],
@ -466,7 +464,7 @@ class Lattice: # ToDo: Make a subclass of Symmetry!
# Greninger--Troiano' orientation relationship for fcc <-> bcc transformation # Greninger--Troiano' orientation relationship for fcc <-> bcc transformation
# from Y. He et al., Journal of Applied Crystallography 39:72-81, 2006 # from Y. He et al., Journal of Applied Crystallography 39:72-81, 2006
GTprime = {'mapping':{'fcc':0,'bcc':1}, _GTprime = {'mapping':{'fcc':0,'bcc':1},
'planes': np.array([ 'planes': np.array([
[[ 7, 17, 17],[ 12, 5, 17]], [[ 7, 17, 17],[ 12, 5, 17]],
[[ 17, 7, 17],[ 17, 12, 5]], [[ 17, 7, 17],[ 17, 12, 5]],
@ -520,7 +518,7 @@ class Lattice: # ToDo: Make a subclass of Symmetry!
# Nishiyama--Wassermann orientation relationship for fcc <-> bcc transformation # Nishiyama--Wassermann orientation relationship for fcc <-> bcc transformation
# from H. Kitahara et al., Materials Characterization 54:378-386, 2005 # from H. Kitahara et al., Materials Characterization 54:378-386, 2005
NW = {'mapping':{'fcc':0,'bcc':1}, _NW = {'mapping':{'fcc':0,'bcc':1},
'planes': np.array([ 'planes': 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]],
@ -550,7 +548,7 @@ class Lattice: # ToDo: Make a subclass of Symmetry!
# Pitsch orientation relationship for fcc <-> bcc transformation # Pitsch orientation relationship for fcc <-> bcc transformation
# from Y. He et al., Acta Materialia 53:1179-1190, 2005 # from Y. He et al., Acta Materialia 53:1179-1190, 2005
Pitsch = {'mapping':{'fcc':0,'bcc':1}, _Pitsch = {'mapping':{'fcc':0,'bcc':1},
'planes': np.array([ 'planes': np.array([
[[ 0, 1, 0],[ -1, 0, 1]], [[ 0, 1, 0],[ -1, 0, 1]],
[[ 0, 0, 1],[ 1, -1, 0]], [[ 0, 0, 1],[ 1, -1, 0]],
@ -580,7 +578,7 @@ class Lattice: # ToDo: Make a subclass of Symmetry!
# Bain orientation relationship for fcc <-> bcc transformation # Bain orientation relationship for fcc <-> bcc transformation
# from Y. He et al., Journal of Applied Crystallography 39:72-81, 2006 # from Y. He et al., Journal of Applied Crystallography 39:72-81, 2006
Bain = {'mapping':{'fcc':0,'bcc':1}, _Bain = {'mapping':{'fcc':0,'bcc':1},
'planes': np.array([ 'planes': np.array([
[[ 1, 0, 0],[ 1, 0, 0]], [[ 1, 0, 0],[ 1, 0, 0]],
[[ 0, 1, 0],[ 0, 1, 0]], [[ 0, 1, 0],[ 0, 1, 0]],
@ -590,7 +588,8 @@ class Lattice: # ToDo: Make a subclass of Symmetry!
[[ 0, 0, 1],[ 1, 0, 1]], [[ 0, 0, 1],[ 1, 0, 1]],
[[ 1, 0, 0],[ 1, 1, 0]]],dtype='float')} [[ 1, 0, 0],[ 1, 1, 0]]],dtype='float')}
def relationOperations(self,model):
def relation_operations(self,model):
""" """
Crystallographic orientation relationships for phase transformations. Crystallographic orientation relationships for phase transformations.
@ -612,8 +611,8 @@ class Lattice: # ToDo: Make a subclass of Symmetry!
https://doi.org/10.1016/j.actamat.2004.11.021 https://doi.org/10.1016/j.actamat.2004.11.021
""" """
models={'KS':self.KS, 'GT':self.GT, 'GT_prime':self.GTprime, models={'KS':self._KS, 'GT':self._GT, 'GT_prime':self._GTprime,
'NW':self.NW, 'Pitsch': self.Pitsch, 'Bain':self.Bain} 'NW':self._NW, 'Pitsch': self._Pitsch, 'Bain':self._Bain}
try: try:
relationship = models[model] relationship = models[model]
except KeyError : except KeyError :
@ -639,6 +638,8 @@ class Lattice: # ToDo: Make a subclass of Symmetry!
otherDir = miller[otherDir_id]/ np.linalg.norm(miller[otherDir_id]) otherDir = miller[otherDir_id]/ np.linalg.norm(miller[otherDir_id])
otherMatrix = np.array([otherDir,np.cross(otherPlane,otherDir),otherPlane]) otherMatrix = np.array([otherDir,np.cross(otherPlane,otherDir),otherPlane])
r['rotations'].append(Rotation.from_matrix(np.dot(otherMatrix.T,myMatrix))) r['rotations'].append(np.dot(otherMatrix.T,myMatrix))
r['rotations'] = np.array(r['rotations'])
return r return r

View File

@ -71,8 +71,8 @@ class Orientation: # ToDo: make subclass of lattice and Rotation?
r = b*aInv r = b*aInv
for k in range(2): for k in range(2):
r.inverse() r.inverse()
breaker = self.in_FZ \ breaker = self.lattice.in_FZ(r.as_Rodrigues(vector=True)) \
and (not SST or other.lattice.symmetry.inDisorientationSST(r.as_Rodrigues(vector=True))) and (not SST or other.lattice.in_disorientation_SST(r.as_Rodrigues(vector=True)))
if breaker: break if breaker: break
if breaker: break if breaker: break
if breaker: break if breaker: break
@ -90,40 +90,36 @@ class Orientation: # ToDo: make subclass of lattice and Rotation?
@property @property
def equivalent(self): def equivalent(self):
""" """
Return orientations which are symmetrically equivalent. Orientations which are symmetrically equivalent.
One dimension (length according to symmetrically equivalent orientations) One dimension (length according to number of symmetrically equivalent orientations)
is added to the left of the Rotation array. is added to the left of the Rotation array.
""" """
s = self.lattice.symmetry.symmetry_operations o = self.lattice.symmetry.symmetry_operations
s = s.reshape(s.shape[:1]+(1,)*len(self.rotation.shape)+(4,)) o = o.reshape(o.shape[:1]+(1,)*len(self.rotation.shape)+(4,))
s = Rotation(np.broadcast_to(s,s.shape[:1]+self.rotation.quaternion.shape)) o = Rotation(np.broadcast_to(o,o.shape[:1]+self.rotation.quaternion.shape))
r = np.broadcast_to(self.rotation.quaternion,s.shape[:1]+self.rotation.quaternion.shape) s = np.broadcast_to(self.rotation.quaternion,o.shape[:1]+self.rotation.quaternion.shape)
return self.__class__(s@Rotation(r),self.lattice) return self.__class__(o@Rotation(s),self.lattice)
def relatedOrientations_vec(self,model): def related(self,model):
"""List of orientations related by the given orientation relationship.""" """
h = self.lattice.relationOperations(model) Orientations related by the given orientation relationship.
rot= h['rotations']
op=np.array([o.as_quaternion() for o in rot])
s = op.reshape(op.shape[:1]+(1,)*len(self.rotation.shape)+(4,)) One dimension (length according to number of related orientations)
s = Rotation(np.broadcast_to(s,s.shape[:1]+self.rotation.quaternion.shape)) is added to the left of the Rotation array.
r = np.broadcast_to(self.rotation.quaternion,s.shape[:1]+self.rotation.quaternion.shape) """
r = Rotation(r) o = Rotation.from_matrix(self.lattice.relation_operations(model)['rotations']).as_quaternion()
o = o.reshape(o.shape[:1]+(1,)*len(self.rotation.shape)+(4,))
o = Rotation(np.broadcast_to(o,o.shape[:1]+self.rotation.quaternion.shape))
return self.__class__(s@r,h['lattice']) s = np.broadcast_to(self.rotation.quaternion,o.shape[:1]+self.rotation.quaternion.shape)
return self.__class__(o@Rotation(s),self.lattice.relation_operations(model)['lattice'])
def relatedOrientations(self,model):
"""List of orientations related by the given orientation relationship."""
r = self.lattice.relationOperations(model)
return [self.__class__(o*self.rotation,r['lattice']) for o in r['rotations']]
@property @property
@ -136,26 +132,31 @@ class Orientation: # ToDo: make subclass of lattice and Rotation?
found = np.zeros_like(in_FZ[0],dtype=bool) found = np.zeros_like(in_FZ[0],dtype=bool)
q = self.rotation.quaternion[0] q = self.rotation.quaternion[0]
for s in range(in_FZ.shape[0]): for s in range(in_FZ.shape[0]):
#something fishy... why does q needs to be initialized?
q = np.where(np.expand_dims(np.logical_and(in_FZ[s],~found),-1),eq.rotation.quaternion[s],q) q = np.where(np.expand_dims(np.logical_and(in_FZ[s],~found),-1),eq.rotation.quaternion[s],q)
found = np.logical_or(in_FZ[s],found) found = np.logical_or(in_FZ[s],found)
return self.__class__(q,self.lattice) return self.__class__(q,self.lattice)
# ToDo: vectorize def inverse_pole(self,axis,proper=False,SST=True):
def inversePole(self,
axis,
proper = False,
SST = True):
"""Axis rotated according to orientation (using crystal symmetry to ensure location falls into SST).""" """Axis rotated according to orientation (using crystal symmetry to ensure location falls into SST)."""
if SST: # pole requested to be within SST if SST:
for i,o in enumerate(self.equivalent): # test all symmetric equivalent quaternions eq = self.equivalent
pole = o.rotation@axis # align crystal direction to axis pole = eq.rotation @ np.broadcast_to(axis/np.linalg.norm(axis),eq.rotation.shape+(3,))
if self.lattice.in_SST(pole,proper): break # found SST version in_SST = self.lattice.in_SST(pole,proper=proper)
else:
pole = self.rotation@axis # align crystal direction to axis # remove duplicates (occur for highly symmetric orientations)
found = np.zeros_like(in_SST[0],dtype=bool)
p = pole[0]
for s in range(in_SST.shape[0]):
p = np.where(np.expand_dims(np.logical_and(in_SST[s],~found),-1),pole[s],p)
found = np.logical_or(in_SST[s],found)
return p
else:
return self.rotation @ np.broadcast_to(axis/np.linalg.norm(axis),self.rotation.shape+(3,))
return (pole,i if SST else 0)
def IPF_color(self,axis): #ToDo axis or direction? def IPF_color(self,axis): #ToDo axis or direction?
@ -166,7 +167,7 @@ class Orientation: # ToDo: make subclass of lattice and Rotation?
# remove duplicates (occur for highly symmetric orientations) # remove duplicates (occur for highly symmetric orientations)
found = np.zeros_like(in_SST[0],dtype=bool) found = np.zeros_like(in_SST[0],dtype=bool)
c = np.empty(color.shape[1:]) c = color[0]
for s in range(in_SST.shape[0]): for s in range(in_SST.shape[0]):
c = np.where(np.expand_dims(np.logical_and(in_SST[s],~found),-1),color[s],c) c = np.where(np.expand_dims(np.logical_and(in_SST[s],~found),-1),color[s],c)
found = np.logical_or(in_SST[s],found) found = np.logical_or(in_SST[s],found)

View File

@ -28,6 +28,22 @@ def reference_dir(reference_dir_base):
class TestOrientation: class TestOrientation:
@pytest.mark.parametrize('model',['Bain','KS','GT','GT_prime','NW','Pitsch'])
@pytest.mark.parametrize('lattice',['fcc','bcc'])
def test_relationship_vectorize(self,set_of_quaternions,lattice,model):
result = Orientation(set_of_quaternions[:200].reshape(50,4,4),lattice).related(model)
ref_qu = result.rotation.quaternion.reshape(-1,200,4)
for i in range(200):
single = Orientation(set_of_quaternions[i],lattice).related(model).rotation.quaternion
assert np.allclose(ref_qu[:,i,:],single)
@pytest.mark.parametrize('lattice',Lattice.lattices)
def test_IPF_vectorize(self,set_of_quaternions,lattice):
direction = np.random.random(3)*2.0-1
oris = Orientation(Rotation(set_of_quaternions),lattice)[:200]
for i,color in enumerate(oris.IPF_color(direction)):
assert np.allclose(color,IPF_color(oris[i],direction))
@pytest.mark.parametrize('color',[{'label':'red', 'RGB':[1,0,0],'direction':[0,0,1]}, @pytest.mark.parametrize('color',[{'label':'red', 'RGB':[1,0,0],'direction':[0,0,1]},
{'label':'green','RGB':[0,1,0],'direction':[0,1,1]}, {'label':'green','RGB':[0,1,0],'direction':[0,1,1]},
{'label':'blue', 'RGB':[0,0,1],'direction':[1,1,1]}]) {'label':'blue', 'RGB':[0,0,1],'direction':[1,1,1]}])
@ -45,15 +61,6 @@ class TestOrientation:
for equivalent in ori.equivalent: for equivalent in ori.equivalent:
assert np.allclose(color,equivalent.IPF_color(direction)) assert np.allclose(color,equivalent.IPF_color(direction))
@pytest.mark.parametrize('lattice',Lattice.lattices)
def test_IPF_vectorize(self,set_of_quaternions,lattice):
direction = np.random.random(3)*2.0-1
oris = Orientation(Rotation(set_of_quaternions),lattice)[:200]
for i,color in enumerate(oris.IPF_color(direction)):
assert np.allclose(color,IPF_color(oris[i],direction))
@pytest.mark.parametrize('lattice',Lattice.lattices) @pytest.mark.parametrize('lattice',Lattice.lattices)
def test_reduced(self,set_of_quaternions,lattice): def test_reduced(self,set_of_quaternions,lattice):
oris = Orientation(Rotation(set_of_quaternions),lattice) oris = Orientation(Rotation(set_of_quaternions),lattice)
@ -65,8 +72,8 @@ class TestOrientation:
@pytest.mark.parametrize('lattice',['fcc','bcc']) @pytest.mark.parametrize('lattice',['fcc','bcc'])
def test_relationship_forward_backward(self,model,lattice): def test_relationship_forward_backward(self,model,lattice):
ori = Orientation(Rotation.from_random(),lattice) ori = Orientation(Rotation.from_random(),lattice)
for i,r in enumerate(ori.relatedOrientations(model)): for i,r in enumerate(ori.related(model)):
ori2 = r.relatedOrientations(model)[i] ori2 = r.related(model)[i]
misorientation = ori.rotation.misorientation(ori2.rotation) misorientation = ori.rotation.misorientation(ori2.rotation)
assert misorientation.asAxisAngle(degrees=True)[3]<1.0e-5 assert misorientation.asAxisAngle(degrees=True)[3]<1.0e-5
@ -75,7 +82,7 @@ class TestOrientation:
def test_relationship_reference(self,update,reference_dir,model,lattice): def test_relationship_reference(self,update,reference_dir,model,lattice):
reference = os.path.join(reference_dir,'{}_{}.txt'.format(lattice,model)) reference = os.path.join(reference_dir,'{}_{}.txt'.format(lattice,model))
ori = Orientation(Rotation(),lattice) ori = Orientation(Rotation(),lattice)
eu = np.array([o.rotation.as_Eulers(degrees=True) for o in ori.relatedOrientations(model)]) eu = np.array([o.rotation.as_Eulers(degrees=True) for o in ori.related(model)])
if update: if update:
coords = np.array([(1,i+1) for i,x in enumerate(eu)]) coords = np.array([(1,i+1) for i,x in enumerate(eu)])
table = damask.Table(eu,{'Eulers':(3,)}) table = damask.Table(eu,{'Eulers':(3,)})

View File

@ -1,41 +0,0 @@
import pytest
import numpy as np
from damask import Rotation
from damask import Orientation
from damask import Lattice
rot0= Rotation.from_random()
rot1= Rotation.from_random()
rot2= Rotation.from_random()
rot3= Rotation.from_random()
#disorientation
#fromaverage
#average
class TestOrientation_vec:
@pytest.mark.parametrize('model',['Bain','KS','GT','GT_prime','NW','Pitsch'])
@pytest.mark.parametrize('lattice',['fcc','bcc'])
def test_relatedOrientations_vec(self,model,lattice):
ori0=Orientation(rot0,lattice)
ori1=Orientation(rot1,lattice)
ori2=Orientation(rot2,lattice)
ori3=Orientation(rot3,lattice)
quat=np.array([rot0.as_quaternion(),rot1.as_quaternion(),rot2.as_quaternion(),rot3.as_quaternion()])
ori_vec=Orientation(quat,lattice)
for s in range(len(ori1.lattice.relationOperations(model)['rotations'])):
assert all(ori_vec.relatedOrientations_vec(model).rotation.as_Eulers()[s,0] == \
ori0.relatedOrientations(model)[s].rotation.as_Eulers())
assert all(ori_vec.relatedOrientations_vec(model).rotation.as_quaternion()[s,1] == \
ori1.relatedOrientations(model)[s].rotation.as_quaternion())
assert all(ori_vec.relatedOrientations_vec(model).rotation.as_Rodrigues()[s,2] == \
ori2.relatedOrientations(model)[s].rotation.as_Rodrigues())
assert all(ori_vec.relatedOrientations_vec(model).rotation.as_cubochoric()[s,3] == \
ori3.relatedOrientations(model)[s].rotation.as_cubochoric())