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
from . import Rotation
class Symmetry:
"""
@ -29,7 +27,6 @@ class Symmetry:
raise KeyError(f'Crystal system "{system}" is unknown')
self.system = system.lower() if isinstance(system,str) else system
self.lattice = self.system # ToDo: for compatibility
def __copy__(self):
@ -219,6 +216,7 @@ class Symmetry:
return np.ones_like(rho[...,0],dtype=bool)
#ToDo: IPF color in separate function
def in_SST(self,vector,proper=False,color=False):
"""
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!
"""
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.
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.
@ -345,20 +343,20 @@ class Lattice: # ToDo: Make a subclass of Symmetry!
self.symmetry = Symmetry(self.lattices[lattice]['system'])
# transition to subclass
self.system = self.symmetry.system
self.in_SST = self.symmetry.in_SST
self.in_FZ = self.symmetry.in_FZ
self.system = self.symmetry.system
self.in_SST = self.symmetry.in_SST
self.in_FZ = self.symmetry.in_FZ
self.in_disorientation_SST = self.symmetry.in_disorientation_SST
def __repr__(self):
"""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
# 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
KS = {'mapping':{'fcc':0,'bcc':1},
_KS = {'mapping':{'fcc':0,'bcc':1},
'planes': np.array([
[[ 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
# 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([
[[ 1, 1, 1],[ 1, 0, 1]],
[[ 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
# 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([
[[ 7, 17, 17],[ 12, 5, 17]],
[[ 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
# 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([
[[ 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
# 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([
[[ 0, 1, 0],[ -1, 0, 1]],
[[ 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
# 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([
[[ 1, 0, 0],[ 1, 0, 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]],
[[ 1, 0, 0],[ 1, 1, 0]]],dtype='float')}
def relationOperations(self,model):
def relation_operations(self,model):
"""
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
"""
models={'KS':self.KS, 'GT':self.GT, 'GT_prime':self.GTprime,
'NW':self.NW, 'Pitsch': self.Pitsch, 'Bain':self.Bain}
models={'KS':self._KS, 'GT':self._GT, 'GT_prime':self._GTprime,
'NW':self._NW, 'Pitsch': self._Pitsch, 'Bain':self._Bain}
try:
relationship = models[model]
except KeyError :
@ -639,6 +638,8 @@ class Lattice: # ToDo: Make a subclass of Symmetry!
otherDir = miller[otherDir_id]/ np.linalg.norm(miller[otherDir_id])
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

View File

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

View File

@ -28,6 +28,22 @@ def reference_dir(reference_dir_base):
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]},
{'label':'green','RGB':[0,1,0],'direction':[0,1,1]},
{'label':'blue', 'RGB':[0,0,1],'direction':[1,1,1]}])
@ -45,15 +61,6 @@ class TestOrientation:
for equivalent in ori.equivalent:
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)
def test_reduced(self,set_of_quaternions,lattice):
oris = Orientation(Rotation(set_of_quaternions),lattice)
@ -65,8 +72,8 @@ class TestOrientation:
@pytest.mark.parametrize('lattice',['fcc','bcc'])
def test_relationship_forward_backward(self,model,lattice):
ori = Orientation(Rotation.from_random(),lattice)
for i,r in enumerate(ori.relatedOrientations(model)):
ori2 = r.relatedOrientations(model)[i]
for i,r in enumerate(ori.related(model)):
ori2 = r.related(model)[i]
misorientation = ori.rotation.misorientation(ori2.rotation)
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):
reference = os.path.join(reference_dir,'{}_{}.txt'.format(lattice,model))
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:
coords = np.array([(1,i+1) for i,x in enumerate(eu)])
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())