DAMASK_EICMD/python/damask/_orientation.py

203 lines
7.8 KiB
Python
Raw Normal View History

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
2020-07-01 04:07:02 +05:30
class Orientation: # ToDo: make subclass of lattice and Rotation?
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-30 17:25:09 +05:30
def __getitem__(self,item):
2020-07-01 02:47:50 +05:30
"""Iterate over leading/leftmost dimension of Orientation array."""
2020-06-30 17:25:09 +05:30
return self.__class__(self.rotation[item],self.lattice)
2020-07-01 04:07:02 +05:30
# ToDo: Discuss vectorization/calling signature
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.')
2020-07-01 19:56:56 +05:30
mySymEqs = self.equivalent if SST else self.equivalent[0] #ToDo: This is just me! # take all or only first sym operation
2020-07-01 01:13:57 +05:30
otherSymEqs = other.equivalent
2020-03-22 21:33:28 +05:30
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-07-01 12:18:30 +05:30
breaker = self.lattice.in_FZ(r.as_Rodrigues(vector=True)) \
and (not SST or other.lattice.in_disorientation_SST(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 ...
# ... own sym, other sym,
# self-->other: True, self<--other: False
2019-10-23 03:01:27 +05:30
2020-07-01 04:07:02 +05:30
@property
2020-07-01 01:13:57 +05:30
def in_FZ(self):
"""Check if orientations fall into Fundamental Zone."""
return self.lattice.in_FZ(self.rotation.as_Rodrigues(vector=True))
2020-07-01 04:07:02 +05:30
@property
2020-06-30 17:25:09 +05:30
def equivalent(self):
"""
2020-07-01 12:18:30 +05:30
Orientations which are symmetrically equivalent.
2020-07-01 12:18:30 +05:30
One dimension (length according to number of symmetrically equivalent orientations)
2020-07-01 04:07:02 +05:30
is added to the left of the Rotation array.
"""
2020-07-01 12:18:30 +05:30
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))
2020-07-01 12:18:30 +05:30
s = np.broadcast_to(self.rotation.quaternion,o.shape[:1]+self.rotation.quaternion.shape)
2020-07-01 12:18:30 +05:30
return self.__class__(o@Rotation(s),self.lattice)
2020-07-01 12:18:30 +05:30
def related(self,model):
"""
Orientations related by the given orientation relationship.
2020-06-30 17:01:58 +05:30
2020-07-01 12:18:30 +05:30
One dimension (length according to number of related orientations)
is added to the left of the Rotation array.
2020-06-30 17:01:58 +05:30
2020-07-01 12:18:30 +05:30
"""
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))
2020-07-01 12:18:30 +05:30
s = np.broadcast_to(self.rotation.quaternion,o.shape[:1]+self.rotation.quaternion.shape)
2020-07-01 12:18:30 +05:30
return self.__class__(o@Rotation(s),self.lattice.relation_operations(model)['lattice'])
2019-10-23 03:01:27 +05:30
2020-06-29 21:55:45 +05:30
2020-07-01 04:07:02 +05:30
@property
2020-03-22 21:33:28 +05:30
def reduced(self):
"""Transform orientation to fall into fundamental zone according to symmetry."""
2020-07-01 04:07:02 +05:30
eq = self.equivalent
in_FZ = eq.in_FZ
# remove duplicates (occur for highly symmetric orientations)
found = np.zeros_like(in_FZ[0],dtype=bool)
q = self.rotation.quaternion[0]
for s in range(in_FZ.shape[0]):
2020-07-01 12:18:30 +05:30
#something fishy... why does q needs to be initialized?
2020-07-01 04:07:02 +05:30
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)
2019-02-21 17:06:27 +05:30
2020-07-01 04:07:02 +05:30
return self.__class__(q,self.lattice)
2020-07-01 12:18:30 +05:30
def inverse_pole(self,axis,proper=False,SST=True):
2020-03-22 21:33:28 +05:30
"""Axis rotated according to orientation (using crystal symmetry to ensure location falls into SST)."""
2020-07-01 12:18:30 +05:30
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
2020-03-22 21:33:28 +05:30
else:
2020-07-01 12:18:30 +05:30
return self.rotation @ np.broadcast_to(axis/np.linalg.norm(axis),self.rotation.shape+(3,))
2020-07-01 01:13:57 +05:30
def IPF_color(self,axis): #ToDo axis or direction?
2020-03-22 21:33:28 +05:30
"""TSL color of inverse pole figure for given axis."""
2020-06-30 17:25:09 +05:30
eq = self.equivalent
pole = eq.rotation @ np.broadcast_to(axis/np.linalg.norm(axis),eq.rotation.shape+(3,))
2020-06-30 17:25:09 +05:30
in_SST, color = self.lattice.in_SST(pole,color=True)
2020-07-01 04:07:02 +05:30
# remove duplicates (occur for highly symmetric orientations)
2020-07-01 01:13:57 +05:30
found = np.zeros_like(in_SST[0],dtype=bool)
2020-07-01 12:18:30 +05:30
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)
return c
2020-07-01 04:07:02 +05:30
# ToDo: Discuss vectorization/calling signature
2020-03-22 21:33:28 +05:30
@staticmethod
2020-07-01 19:56:56 +05:30
def from_average(orientations,
2020-03-22 21:33:28 +05:30
weights = []):
"""Create orientation from average of list of orientations."""
2020-07-01 01:13:57 +05:30
# further read: Orientation distribution analysis in deformed grains
# https://doi.org/10.1107/S0021889801003077
2020-03-22 21:33:28 +05:30
if not all(isinstance(item, Orientation) for item in orientations):
raise TypeError("Only instances of Orientation can be averaged.")
2020-03-22 21:33:28 +05:30
closest = []
ref = orientations[0]
for o in orientations:
2020-07-01 01:13:57 +05:30
closest.append(o.equivalent[
2020-03-22 21:33:28 +05:30
ref.disorientation(o,
SST = False, # select (o[ther]'s) sym orientation
2020-07-01 01:13:57 +05:30
symmetries = True)[2]].rotation) # with lowest misorientation
2020-07-01 19:56:56 +05:30
return Orientation(Rotation.from_average(closest,weights),ref.lattice)
2019-04-17 12:59:49 +05:30
2020-07-01 04:07:02 +05:30
# ToDo: Discuss vectorization/calling signature
2020-03-22 21:33:28 +05:30
def average(self,other):
"""Calculate the average rotation."""
2020-07-01 19:56:56 +05:30
return Orientation.from_average([self,other])