From 49d448dcede646f8a231d4befc9da386fea01cd6 Mon Sep 17 00:00:00 2001 From: Martin Diehl Date: Tue, 30 Jun 2020 21:43:57 +0200 Subject: [PATCH] vectorized and cleaned --- python/damask/_lattice.py | 260 ++++++------------------------- python/damask/_orientation.py | 67 ++------ python/damask/_result.py | 6 +- python/damask/_rotation.py | 2 +- python/tests/test_Lattice.py | 122 ++++++++++++++- python/tests/test_Orientation.py | 9 +- python/tests/test_Result.py | 6 +- python/tests/test_ori_vec.py | 38 ----- 8 files changed, 194 insertions(+), 316 deletions(-) diff --git a/python/damask/_lattice.py b/python/damask/_lattice.py index ee718adcd..74ef61a50 100644 --- a/python/damask/_lattice.py +++ b/python/damask/_lattice.py @@ -29,7 +29,7 @@ class Symmetry: raise KeyError(f'Crystal system "{system}" is unknown') self.system = system.lower() if isinstance(system,str) else system - self.lattice = self.system # for compatibility + self.lattice = self.system # ToDo: for compatibility def __copy__(self): @@ -82,85 +82,10 @@ class Symmetry: otherOrder = self.crystal_systems.index(other.system) return (myOrder > otherOrder) - (myOrder < otherOrder) - def symmetryOperations(self,members=[]): - """List (or single element) of symmetry operations as rotations.""" - if self.system == 'cubic': - symQuats = [ - [ 1.0, 0.0, 0.0, 0.0 ], - [ 0.0, 1.0, 0.0, 0.0 ], - [ 0.0, 0.0, 1.0, 0.0 ], - [ 0.0, 0.0, 0.0, 1.0 ], - [ 0.0, 0.0, 0.5*np.sqrt(2), 0.5*np.sqrt(2) ], - [ 0.0, 0.0, 0.5*np.sqrt(2),-0.5*np.sqrt(2) ], - [ 0.0, 0.5*np.sqrt(2), 0.0, 0.5*np.sqrt(2) ], - [ 0.0, 0.5*np.sqrt(2), 0.0, -0.5*np.sqrt(2) ], - [ 0.0, 0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0 ], - [ 0.0, -0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0 ], - [ 0.5, 0.5, 0.5, 0.5 ], - [-0.5, 0.5, 0.5, 0.5 ], - [-0.5, 0.5, 0.5, -0.5 ], - [-0.5, 0.5, -0.5, 0.5 ], - [-0.5, -0.5, 0.5, 0.5 ], - [-0.5, -0.5, 0.5, -0.5 ], - [-0.5, -0.5, -0.5, 0.5 ], - [-0.5, 0.5, -0.5, -0.5 ], - [-0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], - [ 0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], - [-0.5*np.sqrt(2), 0.0, 0.5*np.sqrt(2), 0.0 ], - [-0.5*np.sqrt(2), 0.0, -0.5*np.sqrt(2), 0.0 ], - [-0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0, 0.0 ], - [-0.5*np.sqrt(2),-0.5*np.sqrt(2), 0.0, 0.0 ], - ] - elif self.system == 'hexagonal': - symQuats = [ - [ 1.0, 0.0, 0.0, 0.0 ], - [-0.5*np.sqrt(3), 0.0, 0.0, -0.5 ], - [ 0.5, 0.0, 0.0, 0.5*np.sqrt(3) ], - [ 0.0, 0.0, 0.0, 1.0 ], - [-0.5, 0.0, 0.0, 0.5*np.sqrt(3) ], - [-0.5*np.sqrt(3), 0.0, 0.0, 0.5 ], - [ 0.0, 1.0, 0.0, 0.0 ], - [ 0.0, -0.5*np.sqrt(3), 0.5, 0.0 ], - [ 0.0, 0.5, -0.5*np.sqrt(3), 0.0 ], - [ 0.0, 0.0, 1.0, 0.0 ], - [ 0.0, -0.5, -0.5*np.sqrt(3), 0.0 ], - [ 0.0, 0.5*np.sqrt(3), 0.5, 0.0 ], - ] - elif self.system == 'tetragonal': - symQuats = [ - [ 1.0, 0.0, 0.0, 0.0 ], - [ 0.0, 1.0, 0.0, 0.0 ], - [ 0.0, 0.0, 1.0, 0.0 ], - [ 0.0, 0.0, 0.0, 1.0 ], - [ 0.0, 0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0 ], - [ 0.0, -0.5*np.sqrt(2), 0.5*np.sqrt(2), 0.0 ], - [ 0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], - [-0.5*np.sqrt(2), 0.0, 0.0, 0.5*np.sqrt(2) ], - ] - elif self.system == 'orthorhombic': - symQuats = [ - [ 1.0,0.0,0.0,0.0 ], - [ 0.0,1.0,0.0,0.0 ], - [ 0.0,0.0,1.0,0.0 ], - [ 0.0,0.0,0.0,1.0 ], - ] - else: - symQuats = [ - [ 1.0,0.0,0.0,0.0 ], - ] - - symOps = list(map(Rotation, - np.array(symQuats)[np.atleast_1d(members) if members != [] else range(len(symQuats))])) - try: - iter(members) # asking for (even empty) list of members? - except TypeError: - return symOps[0] # no, return rotation object - else: - return symOps # yes, return list of rotations @property def symmetry_operations(self): - """Symmetry operations as Rotations.""" + """Symmetry operations as Quaternions.""" if self.system == 'cubic': symQuats = [ [ 1.0, 0.0, 0.0, 0.0 ], @@ -228,42 +153,40 @@ class Symmetry: return np.array(symQuats) - def inFZ(self,rodrigues): + def in_FZ(self,rho): """ - Check whether given Rodrigues-Frank vector falls into fundamental zone of own symmetry. + Check whether given Rodrigues-Frank vector falls into fundamental zone. Fundamental zone in Rodrigues space is point symmetric around origin. """ - if (len(rodrigues) != 3): - raise ValueError('Input is not a Rodrigues-Frank vector.\n') + if(rho.shape[-1] != 3): + raise ValueError('Input is not a Rodrigues-Frank vector.') - if np.any(rodrigues == np.inf): return False # ToDo: MD: not sure if needed + rho_abs = np.abs(rho) - Rabs = abs(rodrigues) - - if self.system == 'cubic': - return np.sqrt(2.0)-1.0 >= Rabs[0] \ - and np.sqrt(2.0)-1.0 >= Rabs[1] \ - and np.sqrt(2.0)-1.0 >= Rabs[2] \ - and 1.0 >= Rabs[0] + Rabs[1] + Rabs[2] - elif self.system == 'hexagonal': - return 1.0 >= Rabs[0] and 1.0 >= Rabs[1] and 1.0 >= Rabs[2] \ - and 2.0 >= np.sqrt(3)*Rabs[0] + Rabs[1] \ - and 2.0 >= np.sqrt(3)*Rabs[1] + Rabs[0] \ - and 2.0 >= np.sqrt(3) + Rabs[2] - elif self.system == 'tetragonal': - return 1.0 >= Rabs[0] and 1.0 >= Rabs[1] \ - and np.sqrt(2.0) >= Rabs[0] + Rabs[1] \ - and np.sqrt(2.0) >= Rabs[2] + 1.0 - elif self.system == 'orthorhombic': - return 1.0 >= Rabs[0] and 1.0 >= Rabs[1] and 1.0 >= Rabs[2] - else: - return True + with np.errstate(invalid='ignore'): + # using '*'/prod for 'and' + if self.system == 'cubic': + return np.where(np.prod(np.sqrt(2)-1. >= rho_abs,axis=-1) * \ + (1. >= np.sum(rho_abs,axis=-1)),True,False) + elif self.system == 'hexagonal': + return np.where(np.prod(1. >= rho_abs,axis=-1) * \ + (2. >= np.sqrt(3)*rho_abs[...,0] + rho_abs[...,1]) * \ + (2. >= np.sqrt(3)*rho_abs[...,1] + rho_abs[...,0]) * \ + (2. >= np.sqrt(3) + rho_abs[...,2]),True,False) + elif self.system == 'tetragonal': + return np.where(np.prod(1. >= rho_abs[...,:2],axis=-1) * \ + (np.sqrt(2) >= rho_abs[...,0] + rho_abs[...,1]) * \ + (np.sqrt(2) >= rho_abs[...,2] + 1.),True,False) + elif self.system == 'orthorhombic': + return np.where(np.prod(1. >= rho_abs,axis=-1),True,False) + else: + return np.where(np.all(np.isfinite(rho_abs),axis=-1),True,False) - def inDisorientationSST(self,rodrigues): + def in_disorientation_SST(self,rho): """ - Check whether given Rodrigues-Frank vector (of misorientation) falls into standard stereographic triangle of own symmetry. + Check whether given Rodrigues-Frank vector (of misorientation) falls into standard stereographic triangle. References ---------- @@ -271,115 +194,32 @@ class Symmetry: https://doi.org/10.1107/S0108767391006864 """ - if (len(rodrigues) != 3): - raise ValueError('Input is not a Rodrigues-Frank vector.\n') - R = rodrigues + if(rho.shape[-1] != 3): + raise ValueError('Input is not a Rodrigues-Frank vector.') - epsilon = 0.0 - if self.system == 'cubic': - return R[0] >= R[1]+epsilon and R[1] >= R[2]+epsilon and R[2] >= epsilon - elif self.system == 'hexagonal': - return R[0] >= np.sqrt(3)*(R[1]-epsilon) and R[1] >= epsilon and R[2] >= epsilon - elif self.system == 'tetragonal': - return R[0] >= R[1]-epsilon and R[1] >= epsilon and R[2] >= epsilon - elif self.system == 'orthorhombic': - return R[0] >= epsilon and R[1] >= epsilon and R[2] >= epsilon - else: - return True - - - def inSST(self, - vector, - proper = False, - color = False): - """ - Check whether given vector falls into standard stereographic triangle of own symmetry. - - proper considers only vectors with z >= 0, hence uses two neighboring SSTs. - Return inverse pole figure color if requested. - Bases are computed from - - >>> basis = {'cubic' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,1.]/np.sqrt(2.), # direction of green - ... [1.,1.,1.]/np.sqrt(3.)]).T), # direction of blue - ... 'hexagonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # direction of green - ... [np.sqrt(3.),1.,0.]/np.sqrt(4.)]).T), # direction of blue - ... 'tetragonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # direction of green - ... [1.,1.,0.]/np.sqrt(2.)]).T), # direction of blue - ... 'orthorhombic': np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # direction of green - ... [0.,1.,0.]]).T), # direction of blue - ... } - - """ - if self.system == 'cubic': - basis = {'improper':np.array([ [-1. , 0. , 1. ], - [ np.sqrt(2.) , -np.sqrt(2.) , 0. ], - [ 0. , np.sqrt(3.) , 0. ] ]), - 'proper':np.array([ [ 0. , -1. , 1. ], - [-np.sqrt(2.) , np.sqrt(2.) , 0. ], - [ np.sqrt(3.) , 0. , 0. ] ]), - } - elif self.system == 'hexagonal': - basis = {'improper':np.array([ [ 0. , 0. , 1. ], - [ 1. , -np.sqrt(3.) , 0. ], - [ 0. , 2. , 0. ] ]), - 'proper':np.array([ [ 0. , 0. , 1. ], - [-1. , np.sqrt(3.) , 0. ], - [ np.sqrt(3.) , -1. , 0. ] ]), - } - elif self.system == 'tetragonal': - basis = {'improper':np.array([ [ 0. , 0. , 1. ], - [ 1. , -1. , 0. ], - [ 0. , np.sqrt(2.) , 0. ] ]), - 'proper':np.array([ [ 0. , 0. , 1. ], - [-1. , 1. , 0. ], - [ np.sqrt(2.) , 0. , 0. ] ]), - } - elif self.system == 'orthorhombic': - basis = {'improper':np.array([ [ 0., 0., 1.], - [ 1., 0., 0.], - [ 0., 1., 0.] ]), - 'proper':np.array([ [ 0., 0., 1.], - [-1., 0., 0.], - [ 0., 1., 0.] ]), - } - else: # direct exit for unspecified symmetry - if color: - return (True,np.zeros(3,'d')) + with np.errstate(invalid='ignore'): + # using '*' for 'and' + if self.system == 'cubic': + return np.where((rho[...,0] >= rho[...,1]) * \ + (rho[...,1] >= rho[...,2]) * \ + (rho[...,2] >= 0),True,False) + elif self.system == 'hexagonal': + return np.where((rho[...,0] >= rho[...,1]*np.sqrt(3)) * \ + (rho[...,1] >= 0) * \ + (rho[...,2] >= 0),True,False) + elif self.system == 'tetragonal': + return np.where((rho[...,0] >= rho[...,1]) * \ + (rho[...,1] >= 0) * \ + (rho[...,2] >= 0),True,False) + elif self.system == 'orthorhombic': + return np.where((rho[...,0] >= 0) * \ + (rho[...,1] >= 0) * \ + (rho[...,2] >= 0),True,False) else: - return True - - v = np.array(vector,dtype=float) - if proper: # check both improper ... - theComponents = np.around(np.dot(basis['improper'],v),12) - inSST = np.all(theComponents >= 0.0) - if not inSST: # ... and proper SST - theComponents = np.around(np.dot(basis['proper'],v),12) - inSST = np.all(theComponents >= 0.0) - else: - v[2] = abs(v[2]) # z component projects identical - theComponents = np.around(np.dot(basis['improper'],v),12) # for positive and negative values - inSST = np.all(theComponents >= 0.0) - - if color: # have to return color array - if inSST: - rgb = np.power(theComponents/np.linalg.norm(theComponents),0.5) # smoothen color ramps - rgb = np.minimum(np.ones(3,dtype=float),rgb) # limit to maximum intensity - rgb /= max(rgb) # normalize to (HS)V = 1 - else: - rgb = np.zeros(3,dtype=float) - return (inSST,rgb) - else: - return inSST + return np.ones_like(rho[...,0],dtype=bool) - 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. @@ -503,7 +343,7 @@ class Lattice: # ToDo: Make a subclass of Symmetry! # transition to subclass self.system = self.symmetry.system self.in_SST = self.symmetry.in_SST - self.inFZ = self.symmetry.inFZ + self.in_FZ = self.symmetry.in_FZ def __repr__(self): diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 7d0f11c49..b6b24e951 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -40,8 +40,6 @@ class Orientation: # ToDo: make subclass of lattice and Rotation self.rotation = Rotation.from_quaternion(rotation) # assume quaternion def __getitem__(self,item): - if isinstance(item,tuple) and len(item) >= len(self): - raise IndexError('Too many indices') return self.__class__(self.rotation[item],self.lattice) @@ -61,8 +59,8 @@ class Orientation: # ToDo: make subclass of lattice and Rotation 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() + mySymEqs = self.equivalent if SST else self.equivalent[0] # take all or only first sym operation + otherSymEqs = other.equivalent for i,sA in enumerate(mySymEqs): aInv = sA.rotation.inversed() @@ -71,7 +69,7 @@ class Orientation: # ToDo: make subclass of lattice and Rotation r = b*aInv for k in range(2): r.inverse() - breaker = self.lattice.inFZ(r.as_Rodrigues(vector=True)) \ + breaker = self.in_FZ \ and (not SST or other.lattice.symmetry.inDisorientationSST(r.as_Rodrigues(vector=True))) if breaker: break if breaker: break @@ -81,17 +79,10 @@ class Orientation: # ToDo: make subclass of lattice and Rotation # ... own sym, other sym, # self-->other: True, self<--other: False - def inFZ_vec(self): + + def in_FZ(self): """Check if orientations fall into Fundamental Zone.""" - if not self.rotation.shape: - return self.lattice.inFZ(self.rotation.as_Rodrigues(vector=True)) - else: - return [self.lattice.inFZ(\ - self.rotation.as_Rodrigues(vector=True)[l]) for l in range(self.rotation.shape[0])] - - - def inFZ(self): - return self.lattice.inFZ(self.rotation.as_Rodrigues(vector=True)) + return self.lattice.in_FZ(self.rotation.as_Rodrigues(vector=True)) @property def equivalent(self): @@ -112,16 +103,6 @@ class Orientation: # ToDo: make subclass of lattice and Rotation return self.__class__(s@r,self.lattice) - 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 - def relatedOrientations_vec(self,model): """List of orientations related by the given orientation relationship.""" h = self.lattice.relationOperations(model) @@ -149,7 +130,7 @@ class Orientation: # ToDo: make subclass of lattice and Rotation r= 1 if not self.rotation.shape else equi.shape[1] #number of rotations num_equi=equi.shape[0] #number of equivalente orientations quat= np.reshape( equi.as_quaternion(), (r*num_equi,4) ,order='F') #equivalents are listed in intiuitive order - boolean=Orientation(quat, self.lattice).inFZ_vec() #check which ones are in FZ + boolean=Orientation(quat, self.lattice).in_FZ() #check which ones are in FZ if sum(boolean) == r: return self.__class__(quat[boolean],self.lattice) @@ -162,13 +143,10 @@ class Orientation: # ToDo: make subclass of lattice and Rotation return self.__class__(quat[index],self.lattice) - - - def reduced(self): """Transform orientation to fall into fundamental zone according to symmetry.""" - for me in self.equivalentOrientations(): - if self.lattice.inFZ(me.rotation.as_Rodrigues(vector=True)): break + for me in self.equivalent: + if self.lattice.in_FZ(me.rotation.as_Rodrigues(vector=True)): break return self.__class__(me.rotation,self.lattice) @@ -179,35 +157,23 @@ class Orientation: # ToDo: make subclass of lattice and Rotation 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 + for i,o in enumerate(self.equivalent): # 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 + if self.lattice.in_SST(pole,proper): break # found SST version else: pole = self.rotation@axis # align crystal direction to axis return (pole,i if SST else 0) - def IPFcolor(self,axis): + def IPF_color(self,axis): #ToDo axis or direction? """TSL color of inverse pole figure for given axis.""" - color = np.zeros(3,'d') - - for o in self.equivalent: - pole = o.rotation@axis # align crystal direction to axis - inSST,color = self.lattice.symmetry.inSST(pole,color=True) - if inSST: break - - return color - - - def IPF_color(self,axis): - """TSL color of inverse pole figure for given axis. Not for hex or triclinic lattices.""" eq = self.equivalent pole = eq.rotation @ np.broadcast_to(axis/np.linalg.norm(axis),eq.rotation.shape+(3,)) in_SST, color = self.lattice.in_SST(pole,color=True) # ignore duplicates (occur for highly symmetric orientations) - found = np.zeros_like(in_SST[1],dtype=bool) + found = np.zeros_like(in_SST[0],dtype=bool) c = np.empty(color.shape[1:]) 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) @@ -220,17 +186,18 @@ class Orientation: # ToDo: make subclass of lattice and Rotation def fromAverage(orientations, weights = []): """Create orientation from average of list of orientations.""" - # further read: Orientation distribution analysis in deformed grains, https://doi.org/10.1107/S0021889801003077 + # further read: Orientation distribution analysis in deformed grains + # https://doi.org/10.1107/S0021889801003077 if not all(isinstance(item, Orientation) for item in orientations): raise TypeError("Only instances of Orientation can be averaged.") closest = [] ref = orientations[0] for o in orientations: - closest.append(o.equivalentOrientations( + closest.append(o.equivalent[ ref.disorientation(o, SST = False, # select (o[ther]'s) sym orientation - symmetries = True)[2]).rotation) # with lowest misorientation + symmetries = True)[2]].rotation) # with lowest misorientation return Orientation(Rotation.fromAverage(closest,weights),ref.lattice) diff --git a/python/damask/_result.py b/python/damask/_result.py index e6dac9370..1396b560d 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -733,7 +733,7 @@ class Result: @staticmethod - def _add_IPFcolor(q,l): + def _add_IPF_color(q,l): m = util.scale_to_coprime(np.array(l)) o = Orientation(Rotation(rfn.structured_to_unstructured(q['data'])), @@ -749,7 +749,7 @@ class Result: 'Creator': inspect.stack()[0][3][1:] } } - def add_IPFcolor(self,q,l): + def add_IPF_color(self,q,l): """ Add RGB color tuple of inverse pole figure (IPF) color. @@ -761,7 +761,7 @@ class Result: Lab frame direction for inverse pole figure. """ - self._add_generic_pointwise(self._add_IPFcolor,{'q':q},{'l':l}) + self._add_generic_pointwise(self._add_IPF_color,{'q':q},{'l':l}) @staticmethod diff --git a/python/damask/_rotation.py b/python/damask/_rotation.py index 686b144ae..accd453cc 100644 --- a/python/damask/_rotation.py +++ b/python/damask/_rotation.py @@ -470,7 +470,7 @@ class Rotation: elif hasattr(shape, '__iter__'): r = np.random.random(tuple(shape)+(3,)) else: - r = np.random.random((shape,3)) + r = np.random.rand(shape,3) A = np.sqrt(r[...,2]) B = np.sqrt(1.0-r[...,2]) diff --git a/python/tests/test_Lattice.py b/python/tests/test_Lattice.py index 3d9c35706..8e4616660 100644 --- a/python/tests/test_Lattice.py +++ b/python/tests/test_Lattice.py @@ -3,10 +3,118 @@ import random import pytest import numpy as np +from damask import Orientation +from damask import Rotation from damask import Symmetry +def in_FZ(system,rho): + """Non-vectorized version of 'in_FZ'.""" + rho_abs = abs(rho) + + if system == 'cubic': + return np.sqrt(2.0)-1.0 >= rho_abs[0] \ + and np.sqrt(2.0)-1.0 >= rho_abs[1] \ + and np.sqrt(2.0)-1.0 >= rho_abs[2] \ + and 1.0 >= rho_abs[0] + rho_abs[1] + rho_abs[2] + elif system == 'hexagonal': + return 1.0 >= rho_abs[0] and 1.0 >= rho_abs[1] and 1.0 >= rho_abs[2] \ + and 2.0 >= np.sqrt(3)*rho_abs[0] + rho_abs[1] \ + and 2.0 >= np.sqrt(3)*rho_abs[1] + rho_abs[0] \ + and 2.0 >= np.sqrt(3) + rho_abs[2] + elif system == 'tetragonal': + return 1.0 >= rho_abs[0] and 1.0 >= rho_abs[1] \ + and np.sqrt(2.0) >= rho_abs[0] + rho_abs[1] \ + and np.sqrt(2.0) >= rho_abs[2] + 1.0 + elif system == 'orthorhombic': + return 1.0 >= rho_abs[0] and 1.0 >= rho_abs[1] and 1.0 >= rho_abs[2] + else: + return np.all(np.isfinite(rho_abs)) + + +def in_disorientation_SST(system,rho): + """Non-vectorized version of 'in_Disorientation_SST'.""" + epsilon = 0.0 + if system == 'cubic': + return rho[0] >= rho[1]+epsilon and rho[1] >= rho[2]+epsilon and rho[2] >= epsilon + elif system == 'hexagonal': + return rho[0] >= np.sqrt(3)*(rho[1]-epsilon) and rho[1] >= epsilon and rho[2] >= epsilon + elif system == 'tetragonal': + return rho[0] >= rho[1]-epsilon and rho[1] >= epsilon and rho[2] >= epsilon + elif system == 'orthorhombic': + return rho[0] >= epsilon and rho[1] >= epsilon and rho[2] >= epsilon + else: + return True + + +def in_SST(system,vector,proper = False): + """Non-vectorized version of 'in_SST'.""" + if system == 'cubic': + basis = {'improper':np.array([ [-1. , 0. , 1. ], + [ np.sqrt(2.) , -np.sqrt(2.) , 0. ], + [ 0. , np.sqrt(3.) , 0. ] ]), + 'proper':np.array([ [ 0. , -1. , 1. ], + [-np.sqrt(2.) , np.sqrt(2.) , 0. ], + [ np.sqrt(3.) , 0. , 0. ] ]), + } + elif system == 'hexagonal': + basis = {'improper':np.array([ [ 0. , 0. , 1. ], + [ 1. , -np.sqrt(3.) , 0. ], + [ 0. , 2. , 0. ] ]), + 'proper':np.array([ [ 0. , 0. , 1. ], + [-1. , np.sqrt(3.) , 0. ], + [ np.sqrt(3.) , -1. , 0. ] ]), + } + elif system == 'tetragonal': + basis = {'improper':np.array([ [ 0. , 0. , 1. ], + [ 1. , -1. , 0. ], + [ 0. , np.sqrt(2.) , 0. ] ]), + 'proper':np.array([ [ 0. , 0. , 1. ], + [-1. , 1. , 0. ], + [ np.sqrt(2.) , 0. , 0. ] ]), + } + elif system == 'orthorhombic': + basis = {'improper':np.array([ [ 0., 0., 1.], + [ 1., 0., 0.], + [ 0., 1., 0.] ]), + 'proper':np.array([ [ 0., 0., 1.], + [-1., 0., 0.], + [ 0., 1., 0.] ]), + } + else: + return True + + v = np.array(vector,dtype=float) + if proper: + theComponents = np.around(np.dot(basis['improper'],v),12) + inSST = np.all(theComponents >= 0.0) + if not inSST: + theComponents = np.around(np.dot(basis['proper'],v),12) + inSST = np.all(theComponents >= 0.0) + else: + v[2] = abs(v[2]) + theComponents = np.around(np.dot(basis['improper'],v),12) + inSST = np.all(theComponents >= 0.0) + + return inSST + + +@pytest.fixture +def set_of_rodrigues(set_of_quaternions): + return Rotation(set_of_quaternions).as_Rodrigues(vector=True)[:200] + class TestSymmetry: + @pytest.mark.parametrize('system',Symmetry.crystal_systems) + def test_in_FZ_vectorize(self,set_of_rodrigues,system): + for i,in_FZ_ in enumerate(Symmetry(system).in_FZ(set_of_rodrigues)): + assert in_FZ_ == in_FZ(system,set_of_rodrigues[i]) + + @pytest.mark.parametrize('system',Symmetry.crystal_systems) + def test_in_disorientation_SST_vectorize(self,set_of_rodrigues,system): + for i,in_disorientation_SST_ in enumerate(Symmetry(system).in_disorientation_SST(set_of_rodrigues)): + assert in_disorientation_SST_ == in_disorientation_SST(system,set_of_rodrigues[i]) + + @pytest.mark.parametrize('invalid_symmetry',['fcc','bcc','hello']) def test_invalid_symmetry(self,invalid_symmetry): with pytest.raises(KeyError): @@ -22,19 +130,19 @@ class TestSymmetry: assert Symmetry(symmetries[0]) != Symmetry(symmetries[1]) @pytest.mark.parametrize('system',Symmetry.crystal_systems) - def test_inFZ(self,system): - assert Symmetry(system).inFZ(np.zeros(3)) + def test_in_FZ(self,system): + assert Symmetry(system).in_FZ(np.zeros(3)) @pytest.mark.parametrize('system',Symmetry.crystal_systems) - def test_inDisorientationSST(self,system): - assert Symmetry(system).inDisorientationSST(np.zeros(3)) + def test_in_disorientation_SST(self,system): + assert Symmetry(system).in_disorientation_SST(np.zeros(3)) @pytest.mark.parametrize('system',Symmetry.crystal_systems) @pytest.mark.parametrize('proper',[True,False]) - def test_inSST(self,system,proper): - assert Symmetry(system).inSST(np.zeros(3),proper) + def test_in_SST(self,system,proper): + assert Symmetry(system).in_SST(np.zeros(3),proper) - @pytest.mark.parametrize('function',['inFZ','inDisorientationSST']) + @pytest.mark.parametrize('function',['in_FZ','in_disorientation_SST']) def test_invalid_argument(self,function): s = Symmetry() # noqa with pytest.raises(ValueError): diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index 750f6176d..f6f25e0a7 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -40,7 +40,7 @@ class TestOrientation: @pytest.mark.parametrize('lattice',Lattice.lattices) def test_IPF_equivalent(self,set_of_quaternions,lattice): direction = np.random.random(3)*2.0-1 - for ori in Orientation(Rotation(set_of_quaternions),lattice)[200]: + for ori in Orientation(Rotation(set_of_quaternions),lattice)[:200]: color = ori.IPF_color(direction) for equivalent in ori.equivalent: assert np.allclose(color,equivalent.IPF_color(direction)) @@ -48,9 +48,10 @@ class TestOrientation: @pytest.mark.parametrize('lattice',Lattice.lattices) def test_IPF_vectorize(self,set_of_quaternions,lattice): - for ori in Orientation(Rotation(set_of_quaternions),lattice)[200]: - direction = np.random.random(3)*2.0-1 - assert np.allclose(ori.IPF_color(direction),IPF_color(ori,direction)) + 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('model',['Bain','KS','GT','GT_prime','NW','Pitsch']) diff --git a/python/tests/test_Result.py b/python/tests/test_Result.py index d7946e5e0..aec91db9f 100644 --- a/python/tests/test_Result.py +++ b/python/tests/test_Result.py @@ -153,8 +153,8 @@ class TestResult: assert np.allclose(in_memory,in_file) @pytest.mark.parametrize('d',[[1,0,0],[0,1,0],[0,0,1]]) - def test_add_IPFcolor(self,default,d): - default.add_IPFcolor('orientation',d) + def test_add_IPF_color(self,default,d): + default.add_IPF_color('orientation',d) loc = {'orientation': default.get_dataset_location('orientation'), 'color': default.get_dataset_location('IPFcolor_[{} {} {}]'.format(*d))} qu = default.read_dataset(loc['orientation']).view(np.double).reshape(-1,4) @@ -162,7 +162,7 @@ class TestResult: in_memory = np.empty((qu.shape[0],3),np.uint8) for i,q in enumerate(qu): o = damask.Orientation(q,crystal_structure).reduced() - in_memory[i] = np.uint8(o.IPFcolor(np.array(d))*255) + in_memory[i] = np.uint8(o.IPF_color(np.array(d))*255) in_file = default.read_dataset(loc['color']) assert np.allclose(in_memory,in_file) diff --git a/python/tests/test_ori_vec.py b/python/tests/test_ori_vec.py index 772096201..7cb465046 100644 --- a/python/tests/test_ori_vec.py +++ b/python/tests/test_ori_vec.py @@ -16,44 +16,6 @@ rot3= Rotation.from_random() #average class TestOrientation_vec: - #@pytest.mark.xfail - @pytest.mark.parametrize('lattice',Lattice.lattices) - def test_equivalent_vec(self,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(ori_vec.lattice.symmetry.symmetryOperations())): - assert all(ori_vec.equivalent.rotation.as_Eulers()[s,0] == \ - ori0.equivalent[s].rotation.as_Eulers()) - assert all(ori_vec.equivalent.rotation.as_quaternion()[s,1] == \ - ori1.equivalent[s].rotation.as_quaternion()) - assert all(ori_vec.equivalent.rotation.as_Rodrigues()[s,2] == \ - ori2.equivalent[s].rotation.as_Rodrigues()) - assert all(ori_vec.equivalent.rotation.as_cubochoric()[s,3] == \ - ori3.equivalent[s].rotation.as_cubochoric()) - - @pytest.mark.parametrize('lattice',Lattice.lattices) - def test_inFZ_vec(self,lattice): - ori0=Orientation(rot0,lattice) - ori1=Orientation(rot1,lattice) - ori2=Orientation(rot2,lattice) - ori3=Orientation(rot3,lattice) - ori4=ori0.reduced() ; rot4=ori4.rotation #ensure 1 of them is in FZ - - quat=np.array([rot0.as_quaternion(),rot1.as_quaternion(),\ - rot2.as_quaternion(),rot3.as_quaternion(), rot4.as_quaternion()]) - ori_vec=Orientation(quat,lattice) - - assert ori_vec.inFZ_vec()[0] == ori0.inFZ() - assert ori_vec.inFZ_vec()[1] == ori1.inFZ() - assert ori_vec.inFZ_vec()[2] == ori2.inFZ() - assert ori_vec.inFZ_vec()[3] == ori3.inFZ() - assert ori_vec.inFZ_vec()[4] == ori4.inFZ() @pytest.mark.parametrize('model',['Bain','KS','GT','GT_prime','NW','Pitsch'])