2015-05-08 19:44:44 +05:30
|
|
|
import numpy as np
|
2019-05-30 23:32:55 +05:30
|
|
|
|
2020-03-19 19:49:11 +05:30
|
|
|
from . import Lattice
|
|
|
|
from . import Rotation
|
2019-02-21 17:06:27 +05:30
|
|
|
|
2019-02-24 12:38:14 +05:30
|
|
|
class Orientation:
|
2020-03-22 21:33:28 +05:30
|
|
|
"""
|
|
|
|
Crystallographic orientation.
|
|
|
|
|
|
|
|
A crystallographic orientation contains a rotation and a lattice.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
__slots__ = ['rotation','lattice']
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
"""Report lattice type and orientation."""
|
|
|
|
return self.lattice.__repr__()+'\n'+self.rotation.__repr__()
|
|
|
|
|
|
|
|
def __init__(self, rotation, lattice):
|
|
|
|
"""
|
|
|
|
New orientation from rotation and lattice.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
rotation : Rotation
|
|
|
|
Rotation specifying the lattice orientation.
|
|
|
|
lattice : Lattice
|
|
|
|
Lattice type of the crystal.
|
|
|
|
|
|
|
|
"""
|
|
|
|
if isinstance(lattice, Lattice):
|
|
|
|
self.lattice = lattice
|
|
|
|
else:
|
|
|
|
self.lattice = Lattice(lattice) # assume string
|
|
|
|
|
|
|
|
if isinstance(rotation, Rotation):
|
|
|
|
self.rotation = rotation
|
|
|
|
else:
|
2020-05-16 14:47:12 +05:30
|
|
|
self.rotation = Rotation.from_quaternion(rotation) # assume quaternion
|
2020-03-22 21:33:28 +05:30
|
|
|
|
2020-06-04 20:09:24 +05:30
|
|
|
# if self.rotation.quaternion.shape != (4,):
|
|
|
|
# raise NotImplementedError('Support for multiple rotations missing')
|
2020-04-24 10:22:09 +05:30
|
|
|
|
2020-03-22 21:33:28 +05:30
|
|
|
def disorientation(self,
|
|
|
|
other,
|
|
|
|
SST = True,
|
|
|
|
symmetries = False):
|
|
|
|
"""
|
|
|
|
Disorientation between myself and given other orientation.
|
|
|
|
|
|
|
|
Rotation axis falls into SST if SST == True.
|
2020-04-12 19:04:29 +05:30
|
|
|
|
|
|
|
Currently requires same symmetry for both orientations.
|
|
|
|
Look into A. Heinz and P. Neumann 1991 for cases with differing sym.
|
|
|
|
|
2020-03-22 21:33:28 +05:30
|
|
|
"""
|
|
|
|
if self.lattice.symmetry != other.lattice.symmetry:
|
|
|
|
raise NotImplementedError('disorientation between different symmetry classes not supported yet.')
|
|
|
|
|
|
|
|
mySymEqs = self.equivalentOrientations() if SST else self.equivalentOrientations([0]) # take all or only first sym operation
|
|
|
|
otherSymEqs = other.equivalentOrientations()
|
|
|
|
|
|
|
|
for i,sA in enumerate(mySymEqs):
|
|
|
|
aInv = sA.rotation.inversed()
|
|
|
|
for j,sB in enumerate(otherSymEqs):
|
|
|
|
b = sB.rotation
|
|
|
|
r = b*aInv
|
|
|
|
for k in range(2):
|
|
|
|
r.inverse()
|
2020-05-21 03:20:08 +05:30
|
|
|
breaker = self.lattice.symmetry.inFZ(r.as_Rodrigues(vector=True)) \
|
|
|
|
and (not SST or other.lattice.symmetry.inDisorientationSST(r.as_Rodrigues(vector=True)))
|
2020-03-22 21:33:28 +05:30
|
|
|
if breaker: break
|
|
|
|
if breaker: break
|
|
|
|
if breaker: break
|
|
|
|
|
|
|
|
return (Orientation(r,self.lattice), i,j, k == 1) if symmetries else r # disorientation ...
|
2019-04-12 02:48:32 +05:30
|
|
|
# ... own sym, other sym,
|
|
|
|
# self-->other: True, self<--other: False
|
2020-03-22 21:33:28 +05:30
|
|
|
def inFZ(self):
|
2020-05-21 03:20:08 +05:30
|
|
|
return self.lattice.symmetry.inFZ(self.rotation.as_Rodrigues(vector=True))
|
2019-10-23 03:01:27 +05:30
|
|
|
|
2020-06-04 20:09:24 +05:30
|
|
|
def equivalent(self):
|
|
|
|
"""
|
2020-06-04 20:14:57 +05:30
|
|
|
|
2020-06-04 20:09:24 +05:30
|
|
|
List of orientations which are symmetrically equivalent.
|
|
|
|
Supported for multiple rotation with same lattice
|
|
|
|
Returns list [i] being i=range(24)
|
|
|
|
Returns list [i, num_rot] for multiple rotations
|
2020-06-04 20:14:57 +05:30
|
|
|
|
2020-06-04 20:09:24 +05:30
|
|
|
"""
|
|
|
|
if not self.rotation.shape:
|
|
|
|
return [self.__class__(q*self.rotation,self.lattice) \
|
|
|
|
for q in self.lattice.symmetry.symmetryOperations()]
|
|
|
|
else:
|
|
|
|
return np.reshape([self.__class__(q*Rotation.from_quaternion(self.rotation.as_quaternion()[l]),self.lattice) \
|
2020-06-04 20:13:28 +05:30
|
|
|
for q in self.lattice.symmetry.symmetryOperations() \
|
|
|
|
for l in range(self.rotation.shape[0])], (24,self.rotation.shape[0]))
|
2020-06-04 20:09:24 +05:30
|
|
|
|
2020-02-21 03:33:37 +05:30
|
|
|
|
2020-03-22 21:33:28 +05:30
|
|
|
def equivalentOrientations(self,members=[]):
|
|
|
|
"""List of orientations which are symmetrically equivalent."""
|
|
|
|
try:
|
|
|
|
iter(members) # asking for (even empty) list of members?
|
|
|
|
except TypeError:
|
|
|
|
return self.__class__(self.lattice.symmetry.symmetryOperations(members)*self.rotation,self.lattice) # no, return rotation object
|
|
|
|
else:
|
|
|
|
return [self.__class__(q*self.rotation,self.lattice) \
|
|
|
|
for q in self.lattice.symmetry.symmetryOperations(members)] # yes, return list of rotations
|
2020-02-21 03:33:37 +05:30
|
|
|
|
2020-03-22 21:33:28 +05:30
|
|
|
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']]
|
2019-10-23 03:01:27 +05:30
|
|
|
|
2020-02-21 03:33:37 +05:30
|
|
|
|
2020-03-22 21:33:28 +05:30
|
|
|
def reduced(self):
|
|
|
|
"""Transform orientation to fall into fundamental zone according to symmetry."""
|
|
|
|
for me in self.equivalentOrientations():
|
2020-05-21 03:20:08 +05:30
|
|
|
if self.lattice.symmetry.inFZ(me.rotation.as_Rodrigues(vector=True)): break
|
2019-02-21 17:06:27 +05:30
|
|
|
|
2020-03-22 21:33:28 +05:30
|
|
|
return self.__class__(me.rotation,self.lattice)
|
2020-02-21 03:33:37 +05:30
|
|
|
|
|
|
|
|
2020-03-22 21:33:28 +05:30
|
|
|
def inversePole(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.equivalentOrientations()): # test all symmetric equivalent quaternions
|
|
|
|
pole = o.rotation*axis # align crystal direction to axis
|
|
|
|
if self.lattice.symmetry.inSST(pole,proper): break # found SST version
|
|
|
|
else:
|
|
|
|
pole = self.rotation*axis # align crystal direction to axis
|
2015-03-28 13:13:49 +05:30
|
|
|
|
2020-03-22 21:33:28 +05:30
|
|
|
return (pole,i if SST else 0)
|
2020-02-21 03:33:37 +05:30
|
|
|
|
|
|
|
|
2020-03-22 21:33:28 +05:30
|
|
|
def IPFcolor(self,axis):
|
|
|
|
"""TSL color of inverse pole figure for given axis."""
|
|
|
|
color = np.zeros(3,'d')
|
2013-11-26 00:34:39 +05:30
|
|
|
|
2020-03-22 21:33:28 +05:30
|
|
|
for o in self.equivalentOrientations():
|
|
|
|
pole = o.rotation*axis # align crystal direction to axis
|
|
|
|
inSST,color = self.lattice.symmetry.inSST(pole,color=True)
|
|
|
|
if inSST: break
|
2013-11-26 00:34:39 +05:30
|
|
|
|
2020-03-22 21:33:28 +05:30
|
|
|
return color
|
2015-04-03 00:45:09 +05:30
|
|
|
|
2015-06-13 17:21:10 +05:30
|
|
|
|
2020-03-22 21:33:28 +05:30
|
|
|
@staticmethod
|
|
|
|
def fromAverage(orientations,
|
|
|
|
weights = []):
|
|
|
|
"""Create orientation from average of list of orientations."""
|
|
|
|
if not all(isinstance(item, Orientation) for item in orientations):
|
|
|
|
raise TypeError("Only instances of Orientation can be averaged.")
|
2019-04-12 02:48:32 +05:30
|
|
|
|
2020-03-22 21:33:28 +05:30
|
|
|
closest = []
|
|
|
|
ref = orientations[0]
|
|
|
|
for o in orientations:
|
|
|
|
closest.append(o.equivalentOrientations(
|
|
|
|
ref.disorientation(o,
|
|
|
|
SST = False, # select (o[ther]'s) sym orientation
|
|
|
|
symmetries = True)[2]).rotation) # with lowest misorientation
|
2019-04-12 02:48:32 +05:30
|
|
|
|
2020-03-22 21:33:28 +05:30
|
|
|
return Orientation(Rotation.fromAverage(closest,weights),ref.lattice)
|
2019-04-17 12:59:49 +05:30
|
|
|
|
|
|
|
|
2020-03-22 21:33:28 +05:30
|
|
|
def average(self,other):
|
|
|
|
"""Calculate the average rotation."""
|
|
|
|
return Orientation.fromAverage([self,other])
|