restored orientation averaging capability

This commit is contained in:
Philip Eisenlohr 2019-04-11 17:18:32 -04:00
parent 598e8034b4
commit b8285d5749
1 changed files with 59 additions and 43 deletions

View File

@ -35,14 +35,6 @@ class Quaternion:
"""Components""" """Components"""
return iter(self.asList()) return iter(self.asList())
def asArray(self):
"""As numpy array"""
return np.array((self.q,self.p[0],self.p[1],self.p[2]))
def asList(self):
return [self.q]+list(self.p)
def __repr__(self): def __repr__(self):
"""Readable string""" """Readable string"""
return 'Quaternion: (real={q:+.6f}, imag=<{p[0]:+.6f}, {p[1]:+.6f}, {p[2]:+.6f}>)'.format(q=self.q,p=self.p) return 'Quaternion: (real={q:+.6f}, imag=<{p[0]:+.6f}, {p[1]:+.6f}, {p[2]:+.6f}>)'.format(q=self.q,p=self.p)
@ -116,7 +108,7 @@ class Quaternion:
return self return self
else: else:
return NotImplemented return NotImplemented
def __truediv__(self, other): def __truediv__(self, other):
"""Divsion with quaternion or scalar""" """Divsion with quaternion or scalar"""
@ -180,6 +172,17 @@ class Quaternion:
return not self.__eq__(other) return not self.__eq__(other)
def asM(self):
"""Intermediate representation useful for quaternion averaging (see F. Landis Markley et al.)"""
return np.outer(self.asArray(),self.asArray())
def asArray(self):
"""As numpy array"""
return np.array((self.q,self.p[0],self.p[1],self.p[2]))
def asList(self):
return [self.q]+list(self.p)
def normalize(self): def normalize(self):
d = self.magnitude() d = self.magnitude()
if d > 0.0: if d > 0.0:
@ -190,7 +193,7 @@ class Quaternion:
def normalized(self): def normalized(self):
return self.copy().normalize() return self.copy().normalize()
def conjugate(self): def conjugate(self):
self.p = -self.p self.p = -self.p
return self return self
@ -294,6 +297,10 @@ class Rotation:
def asCubochoric(self): def asCubochoric(self):
return qu2cu(self.quaternion.asArray()) return qu2cu(self.quaternion.asArray())
def asM(self):
"""Intermediate representation useful fro quaternion averaging (see F. Landis Markley et al.)"""
return self.quaternion.asM()
################################################################################################ ################################################################################################
# static constructors. The input data needs to follow the convention, options allow to # static constructors. The input data needs to follow the convention, options allow to
@ -503,7 +510,7 @@ class Symmetry:
otherOrder = Symmetry.lattices.index(other.lattice) otherOrder = Symmetry.lattices.index(other.lattice)
return (myOrder > otherOrder) - (myOrder < otherOrder) return (myOrder > otherOrder) - (myOrder < otherOrder)
def symmetryOperations(self): def symmetryOperations(self,who=[]):
"""List of symmetry operations as quaternions.""" """List of symmetry operations as quaternions."""
if self.lattice == 'cubic': if self.lattice == 'cubic':
symQuats = [ symQuats = [
@ -570,7 +577,8 @@ class Symmetry:
[ 1.0,0.0,0.0,0.0 ], [ 1.0,0.0,0.0,0.0 ],
] ]
return [Rotation(q) for q in symQuats] return list(map(Rotation,
np.array(symQuats)[np.atleast_1d(np.array(who)) if who != [] else range(len(symQuats))]))
def inFZ(self,R): def inFZ(self,R):
@ -579,6 +587,8 @@ class Symmetry:
Fundamental zone in Rodrigues space is point symmetric around origin. Fundamental zone in Rodrigues space is point symmetric around origin.
""" """
if np.any(R == np.inf): return False
Rabs = abs(R[0:3]*R[3]) Rabs = abs(R[0:3]*R[3])
if self.lattice == 'cubic': if self.lattice == 'cubic':
@ -1050,7 +1060,8 @@ class Orientation:
def disorientation(self, def disorientation(self,
other, other,
SST = True): SST = True,
symmetries = False):
""" """
Disorientation between myself and given other orientation. Disorientation between myself and given other orientation.
@ -1076,15 +1087,18 @@ class Orientation:
if breaker: break if breaker: break
if breaker: break if breaker: break
return r return (r, i,j, k == 1) if symmetries else r # disorientation ...
# ... own sym, other sym,
# self-->other: True, self<--other: False
def inFZ(self): def inFZ(self):
return self.lattice.symmetry.inFZ(self.rotation.asRodrigues()) return self.lattice.symmetry.inFZ(self.rotation.asRodrigues())
def equivalentOrientations(self): def equivalentOrientations(self, who=[]):
"""List of orientations which are symmetrically equivalent""" """List of orientations which are symmetrically equivalent"""
return [self.__class__(q*self.rotation,self.lattice) \ return [self.__class__(q*self.rotation,self.lattice) \
for q in self.lattice.symmetry.symmetryOperations()] for q in self.lattice.symmetry.symmetryOperations(who)]
def relatedOrientations(self,model): def relatedOrientations(self,model):
"""List of orientations related by the given orientation relationship""" """List of orientations related by the given orientation relationship"""
@ -1125,37 +1139,39 @@ class Orientation:
return color return color
# @classmethod @classmethod
# def average(cls, def average(cls,
# orientations, orientations,
# multiplicity = []): multiplicity = []):
# """ """
# Average orientation Average orientation
# ref: F. Landis Markley, Yang Cheng, John Lucas Crassidis, and Yaakov Oshman. ref: F. Landis Markley, Yang Cheng, John Lucas Crassidis, and Yaakov Oshman.
# Averaging Quaternions, Averaging Quaternions,
# Journal of Guidance, Control, and Dynamics, Vol. 30, No. 4 (2007), pp. 1193-1197. Journal of Guidance, Control, and Dynamics, Vol. 30, No. 4 (2007), pp. 1193-1197.
# doi: 10.2514/1.28949 doi: 10.2514/1.28949
# usage: usage:
# a = Orientation(Eulers=np.radians([10, 10, 0]), symmetry='hexagonal') a = Orientation(Eulers=np.radians([10, 10, 0]), symmetry='hexagonal')
# b = Orientation(Eulers=np.radians([20, 0, 0]), symmetry='hexagonal') b = Orientation(Eulers=np.radians([20, 0, 0]), symmetry='hexagonal')
# avg = Orientation.average([a,b]) avg = Orientation.average([a,b])
# """ """
# if not all(isinstance(item, Orientation) for item in orientations): if not all(isinstance(item, Orientation) for item in orientations):
# raise TypeError("Only instances of Orientation can be averaged.") print('got a list of {}'.format(orientations))
raise TypeError("Only instances of Orientation can be averaged.")
# N = len(orientations) N = len(orientations)
# if multiplicity == [] or not multiplicity: if multiplicity == [] or not multiplicity:
# multiplicity = np.ones(N,dtype='i') multiplicity = np.ones(N,dtype='i')
# reference = orientations[0] # take first as reference ref = orientations[0] # take first as reference
# for i,(o,n) in enumerate(zip(orientations,multiplicity)): for i,(o,n) in enumerate(zip(orientations,multiplicity)):
# closest = o.equivalentOrientations(reference.disorientation(o,SST = False)[2])[0] # select sym orientation with lowest misorientation closest = o.equivalentOrientations(ref.disorientation(o,SST=False,symmetries=True)[2])[0] # select sym orientation with lowest misorientation
# M = closest.quaternion.asM() * n if i == 0 else M + closest.quaternion.asM() * n # noqa add (multiples) of this orientation to average noqa M = closest.rotation.asM() * n if i == 0 \
# eig, vec = np.linalg.eig(M/N) else M + closest.rotation.asM() * n # noqa add (multiples) of this orientation to average noqa
eig, vec = np.linalg.eig(M/N)
# return Orientation(quaternion = Quaternion(quat = np.real(vec.T[eig.argmax()])), return Orientation(Rotation.fromQuaternion(np.real(vec.T[eig.argmax()]),acceptHomomorph = True),
# symmetry = reference.symmetry.lattice) ref.lattice)
#################################################################################################### ####################################################################################################