diff --git a/processing/legacy/addSchmidfactors.py b/processing/legacy/addSchmidfactors.py deleted file mode 100755 index 8e1e4ffb7..000000000 --- a/processing/legacy/addSchmidfactors.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env python3 - -import os -import sys -from io import StringIO -from optparse import OptionParser - -import numpy as np - -import damask - - -scriptName = os.path.splitext(os.path.basename(__file__))[0] -scriptID = ' '.join([scriptName,damask.version]) - -slipSystems = { -'fcc': damask.lattice.kinematics['cF']['slip'][:12], -'bcc': damask.lattice.kinematics['cI']['slip'], -'hex': damask.lattice.kinematics['hP']['slip'], -} - -# -------------------------------------------------------------------- -# MAIN -# -------------------------------------------------------------------- - -parser = OptionParser(usage='%prog options [ASCIItable(s)]', description = """ -Add columns listing Schmid factors (and optional trace vector of selected system) for given Euler angles. - -""", version = scriptID) - -lattice_choices = list(slipSystems.keys()) -parser.add_option('-l', - '--lattice', - dest = 'lattice', type = 'choice', choices = lattice_choices, metavar='string', - help = 'type of lattice structure [%default] {}'.format(lattice_choices)) -parser.add_option('--covera', - dest = 'CoverA', type = 'float', metavar = 'float', - help = 'C over A ratio for hexagonal systems [%default]') -parser.add_option('-f', - '--force', - dest = 'force', - type = 'float', nargs = 3, metavar = 'float float float', - help = 'force direction in lab frame [%default]') -parser.add_option('-n', - '--normal', - dest = 'normal', - type = 'float', nargs = 3, metavar = 'float float float', - help = 'stress plane normal in lab frame, per default perpendicular to the force') -parser.add_option('-o', - '--orientation', - dest = 'quaternion', - metavar = 'string', - help = 'label of crystal orientation given as unit quaternion [%default]') - -parser.set_defaults(force = (0.0,0.0,1.0), - quaternion='orientation', - normal = None, - lattice = lattice_choices[0], - CoverA = np.sqrt(8./3.), - ) - -(options, filenames) = parser.parse_args() -if filenames == []: filenames = [None] - -force = np.array(options.force)/np.linalg.norm(options.force) - -if options.normal is not None: - normal = np.array(options.normal)/np.linalg.norm(options.ormal) - if abs(np.dot(force,normal)) > 1e-3: - parser.error('stress plane normal not orthogonal to force direction') -else: - normal = force - - -if options.lattice in ['bcc','fcc']: - slip_direction = slipSystems[options.lattice][:,:3] - slip_normal = slipSystems[options.lattice][:,3:] -elif options.lattice == 'hex': - slip_direction = np.zeros((len(slipSystems['hex']),3),'d') - slip_normal = np.zeros_like(slip_direction) - # convert 4 Miller index notation of hex to orthogonal 3 Miller index notation - for i in range(len(slip_direction)): - slip_direction[i] = np.array([slipSystems['hex'][i,0]*1.5, - (slipSystems['hex'][i,0] + 2.*slipSystems['hex'][i,1])*0.5*np.sqrt(3), - slipSystems['hex'][i,3]*options.CoverA, - ]) - slip_normal[i] = np.array([slipSystems['hex'][i,4], - (slipSystems['hex'][i,4] + 2.*slipSystems['hex'][i,5])/np.sqrt(3), - slipSystems['hex'][i,7]/options.CoverA, - ]) - -slip_direction /= np.linalg.norm(slip_direction,axis=1,keepdims=True) -slip_normal /= np.linalg.norm(slip_normal, axis=1,keepdims=True) - -labels = ['S[{direction[0]:.1g}_{direction[1]:.1g}_{direction[2]:.1g}]' - '({normal[0]:.1g}_{normal[1]:.1g}_{normal[2]:.1g})'\ - .format(normal = theNormal, direction = theDirection, - ) for theNormal,theDirection in zip(slip_normal,slip_direction)] - -for name in filenames: - damask.util.report(scriptName,name) - - table = damask.Table.load(StringIO(''.join(sys.stdin.read())) if name is None else name) - - o = damask.Rotation.from_quaternion(table.get(options.quaternion)) - - force = np.broadcast_to(force, o.shape+(3,)) - normal = np.broadcast_to(normal,o.shape+(3,)) - slip_direction = np.broadcast_to(slip_direction,o.shape+slip_direction.shape) - slip_normal = np.broadcast_to(slip_normal, o.shape+slip_normal.shape) - S = np.abs(np.einsum('ijk,ik->ij',slip_direction,(o@force))* - np.einsum('ijk,ik->ij',slip_normal, (o@normal))) - - for i,label in enumerate(labels): - table = table.add(label,S[:,i],scriptID+' '+' '.join(sys.argv[1:])) - - table.save((sys.stdout if name is None else name)) diff --git a/python/damask/__init__.py b/python/damask/__init__.py index ad46d454f..761fd5621 100644 --- a/python/damask/__init__.py +++ b/python/damask/__init__.py @@ -14,10 +14,10 @@ from . import tensor # noqa from . import mechanics # noqa from . import solver # noqa from . import grid_filters # noqa -from . import lattice # noqa #Modules that contain only one class (of the same name), are prefixed by a '_'. #For example, '_colormap' containsa class called 'Colormap' which is imported as 'damask.Colormap'. from ._rotation import Rotation # noqa +from ._crystal import Crystal # noqa from ._orientation import Orientation # noqa from ._table import Table # noqa from ._vtk import VTK # noqa diff --git a/python/damask/_crystal.py b/python/damask/_crystal.py new file mode 100644 index 000000000..73d9f031c --- /dev/null +++ b/python/damask/_crystal.py @@ -0,0 +1,849 @@ +import numpy as np + +from . import util +from . import Rotation + +lattice_symmetries = { + 'aP': 'triclinic', + + 'mP': 'monoclinic', + 'mS': 'monoclinic', + + 'oP': 'orthorhombic', + 'oS': 'orthorhombic', + 'oI': 'orthorhombic', + 'oF': 'orthorhombic', + + 'tP': 'tetragonal', + 'tI': 'tetragonal', + + 'hP': 'hexagonal', + + 'cP': 'cubic', + 'cI': 'cubic', + 'cF': 'cubic', + } + + +class Crystal(): + """Crystal lattice.""" + + def __init__(self,*, + family = None, + lattice = None, + a = None,b = None,c = None, + alpha = None,beta = None,gamma = None, + degrees = False): + """ + Representation of crystal in terms of crystal family or Bravais lattice. + + Parameters + ---------- + family : {'triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic'}, optional. + Name of the crystal family. + Will be inferred if 'lattice' is given. + lattice : {'aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF'}, optional. + Name of the Bravais lattice in Pearson notation. + a : float, optional + Length of lattice parameter 'a'. + b : float, optional + Length of lattice parameter 'b'. + c : float, optional + Length of lattice parameter 'c'. + alpha : float, optional + Angle between b and c lattice basis. + beta : float, optional + Angle between c and a lattice basis. + gamma : float, optional + Angle between a and b lattice basis. + degrees : bool, optional + Angles are given in degrees. Defaults to False. + + """ + if family not in [None] + list(lattice_symmetries.values()): + raise KeyError(f'invalid crystal family "{family}"') + if lattice is not None and family is not None and family != lattice_symmetries[lattice]: + raise KeyError(f'incompatible family "{family}" for lattice "{lattice}"') + + self.family = lattice_symmetries[lattice] if family is None else family + self.lattice = lattice + + if self.lattice is not None: + self.a = 1 if a is None else a + self.b = b + self.c = c + self.a = float(self.a) if self.a is not None else \ + (self.b / self.ratio['b'] if self.b is not None and self.ratio['b'] is not None else + self.c / self.ratio['c'] if self.c is not None and self.ratio['c'] is not None else None) + self.b = float(self.b) if self.b is not None else \ + (self.a * self.ratio['b'] if self.a is not None and self.ratio['b'] is not None else + self.c / self.ratio['c'] * self.ratio['b'] + if self.c is not None and self.ratio['b'] is not None and self.ratio['c'] is not None else None) + self.c = float(self.c) if self.c is not None else \ + (self.a * self.ratio['c'] if self.a is not None and self.ratio['c'] is not None else + self.b / self.ratio['b'] * self.ratio['c'] + if self.c is not None and self.ratio['b'] is not None and self.ratio['c'] is not None else None) + + self.alpha = np.radians(alpha) if degrees and alpha is not None else alpha + self.beta = np.radians(beta) if degrees and beta is not None else beta + self.gamma = np.radians(gamma) if degrees and gamma is not None else gamma + if self.alpha is None and 'alpha' in self.immutable: self.alpha = self.immutable['alpha'] + if self.beta is None and 'beta' in self.immutable: self.beta = self.immutable['beta'] + if self.gamma is None and 'gamma' in self.immutable: self.gamma = self.immutable['gamma'] + + if \ + (self.a is None) \ + or (self.b is None or ('b' in self.immutable and self.b != self.immutable['b'] * self.a)) \ + or (self.c is None or ('c' in self.immutable and self.c != self.immutable['c'] * self.b)) \ + or (self.alpha is None or ('alpha' in self.immutable and self.alpha != self.immutable['alpha'])) \ + or (self.beta is None or ('beta' in self.immutable and self.beta != self.immutable['beta'])) \ + or (self.gamma is None or ('gamma' in self.immutable and self.gamma != self.immutable['gamma'])): + raise ValueError (f'Incompatible parameters {self.parameters} for crystal family {self.family}') + + if np.any(np.array([self.alpha,self.beta,self.gamma]) <= 0): + raise ValueError ('Lattice angles must be positive') + if np.any([np.roll([self.alpha,self.beta,self.gamma],r)[0] + > np.sum(np.roll([self.alpha,self.beta,self.gamma],r)[1:]) for r in range(3)]): + raise ValueError ('Each lattice angle must be less than sum of others') + else: + self.a = self.b = self.c = None + self.alpha = self.beta = self.gamma = None + + + def __repr__(self): + """Represent.""" + return '\n'.join([f'Crystal family {self.family}'] + + ([] if self.lattice is None else [f'Bravais lattice {self.lattice}']+ + list(map(lambda x:f'{x[0]}:{x[1]:.5g}', + zip(['a','b','c','alpha','beta','gamma',], + self.parameters)))) + ) + + def __eq__(self,other): + """ + Equal to other. + + Parameters + ---------- + other : Crystal + Crystal to check for equality. + + """ + return self.lattice == other.lattice and \ + self.parameters == other.parameters and \ + self.family == other.family + + @property + def parameters(self): + """Return lattice parameters a, b, c, alpha, beta, gamma.""" + return (self.a,self.b,self.c,self.alpha,self.beta,self.gamma) + + + @property + def immutable(self): + """Return immutable lattice parameters.""" + _immutable = { + 'cubic': { + 'b': 1.0, + 'c': 1.0, + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': np.pi/2., + }, + 'hexagonal': { + 'b': 1.0, + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': 2.*np.pi/3., + }, + 'tetragonal': { + 'b': 1.0, + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': np.pi/2., + }, + 'orthorhombic': { + 'alpha': np.pi/2., + 'beta': np.pi/2., + 'gamma': np.pi/2., + }, + 'monoclinic': { + 'alpha': np.pi/2., + 'gamma': np.pi/2., + }, + 'triclinic': {} + } + return _immutable[self.family] + + + @property + def standard_triangle(self): + """ + Corners of the standard triangle. + + Notes + ----- + Not yet defined for monoclinic. + + + References + ---------- + Bases are computed from + + >>> basis = { + ... 'cubic' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,1.]/np.sqrt(2.), # green + ... [1.,1.,1.]/np.sqrt(3.)]).T), # blue + ... 'hexagonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [np.sqrt(3.),1.,0.]/np.sqrt(4.)]).T), # blue + ... 'tetragonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [1.,1.,0.]/np.sqrt(2.)]).T), # blue + ... 'orthorhombic': np.linalg.inv(np.array([[0.,0.,1.], # direction of red + ... [1.,0.,0.], # green + ... [0.,1.,0.]]).T), # blue + ... } + + """ + _basis = { + 'cubic': {'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. ] ]), + }, + 'hexagonal': + {'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. ] ]), + }, + 'tetragonal': + {'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. ] ]), + }, + 'orthorhombic': + {'improper':np.array([ [ 0., 0., 1.], + [ 1., 0., 0.], + [ 0., 1., 0.] ]), + 'proper':np.array([ [ 0., 0., 1.], + [-1., 0., 0.], + [ 0., 1., 0.] ]), + }} + return _basis.get(self.family,None) + + + @property + def ratio(self): + """Return axes ratios of own lattice.""" + _ratio = { 'hexagonal': {'c': np.sqrt(8./3.)}} + + return dict(b = self.immutable['b'] + if 'b' in self.immutable else + _ratio[self.family]['b'] if self.family in _ratio and 'b' in _ratio[self.family] else None, + c = self.immutable['c'] + if 'c' in self.immutable else + _ratio[self.family]['c'] if self.family in _ratio and 'c' in _ratio[self.family] else None, + ) + + + @property + def basis_real(self): + """ + Return orthogonal real space crystal basis. + + References + ---------- + C.T. Young and J.L. Lytton, Journal of Applied Physics 43:1408–1417, 1972 + https://doi.org/10.1063/1.1661333 + + """ + if None in self.parameters: + raise KeyError('missing crystal lattice parameters') + return np.array([ + [1,0,0], + [np.cos(self.gamma),np.sin(self.gamma),0], + [np.cos(self.beta), + (np.cos(self.alpha)-np.cos(self.beta)*np.cos(self.gamma)) /np.sin(self.gamma), + np.sqrt(1 - np.cos(self.alpha)**2 - np.cos(self.beta)**2 - np.cos(self.gamma)**2 + + 2 * np.cos(self.alpha) * np.cos(self.beta) * np.cos(self.gamma))/np.sin(self.gamma)], + ],dtype=float).T \ + * np.array([self.a,self.b,self.c]) + + + @property + def basis_reciprocal(self): + """Return reciprocal (dual) crystal basis.""" + return np.linalg.inv(self.basis_real.T) + + + def to_lattice(self,*,direction=None,plane=None): + """ + Calculate lattice vector corresponding to crystal frame direction or plane normal. + + Parameters + ---------- + direction|plane : numpy.ndarray of shape (...,3) + Vector along direction or plane normal. + + Returns + ------- + Miller : numpy.ndarray of shape (...,3) + Lattice vector of direction or plane. + Use util.scale_to_coprime to convert to (integer) Miller indices. + + """ + if (direction is not None) ^ (plane is None): + raise KeyError('Specify either "direction" or "plane"') + axis,basis = (np.array(direction),self.basis_reciprocal.T) \ + if plane is None else \ + (np.array(plane),self.basis_real.T) + return np.einsum('il,...l',basis,axis) + + + def to_frame(self,*,uvw=None,hkl=None): + """ + Calculate crystal frame vector along lattice direction [uvw] or plane normal (hkl). + + Parameters + ---------- + uvw|hkl : numpy.ndarray of shape (...,3) + Miller indices of crystallographic direction or plane normal. + + Returns + ------- + vector : numpy.ndarray of shape (...,3) or (N,...,3) + Crystal frame vector (or vectors if with_symmetry) along [uvw] direction or (hkl) plane normal. + + """ + if (uvw is not None) ^ (hkl is None): + raise KeyError('Specify either "uvw" or "hkl"') + axis,basis = (np.array(uvw),self.basis_real) \ + if hkl is None else \ + (np.array(hkl),self.basis_reciprocal) + return np.einsum('il,...l',basis,axis) + + + def kinematics(self,mode): + """ + Return crystal kinematics systems. + + Parameters + ---------- + mode : {'slip','twin'} + Deformation mode. + + Returns + ------- + direction_plane : dictionary + Directions and planes of deformation mode families. + + """ + _kinematics = { + 'cF': { + 'slip' :[np.array([ + [+0,+1,-1, +1,+1,+1], + [-1,+0,+1, +1,+1,+1], + [+1,-1,+0, +1,+1,+1], + [+0,-1,-1, -1,-1,+1], + [+1,+0,+1, -1,-1,+1], + [-1,+1,+0, -1,-1,+1], + [+0,-1,+1, +1,-1,-1], + [-1,+0,-1, +1,-1,-1], + [+1,+1,+0, +1,-1,-1], + [+0,+1,+1, -1,+1,-1], + [+1,+0,-1, -1,+1,-1], + [-1,-1,+0, -1,+1,-1]]), + np.array([ + [+1,+1,+0, +1,-1,+0], + [+1,-1,+0, +1,+1,+0], + [+1,+0,+1, +1,+0,-1], + [+1,+0,-1, +1,+0,+1], + [+0,+1,+1, +0,+1,-1], + [+0,+1,-1, +0,+1,+1]])], + 'twin' :[np.array([ + [-2, 1, 1, 1, 1, 1], + [ 1,-2, 1, 1, 1, 1], + [ 1, 1,-2, 1, 1, 1], + [ 2,-1, 1, -1,-1, 1], + [-1, 2, 1, -1,-1, 1], + [-1,-1,-2, -1,-1, 1], + [-2,-1,-1, 1,-1,-1], + [ 1, 2,-1, 1,-1,-1], + [ 1,-1, 2, 1,-1,-1], + [ 2, 1,-1, -1, 1,-1], + [-1,-2,-1, -1, 1,-1], + [-1, 1, 2, -1, 1,-1]])] + }, + 'cI': { + 'slip' :[np.array([ + [+1,-1,+1, +0,+1,+1], + [-1,-1,+1, +0,+1,+1], + [+1,+1,+1, +0,-1,+1], + [-1,+1,+1, +0,-1,+1], + [-1,+1,+1, +1,+0,+1], + [-1,-1,+1, +1,+0,+1], + [+1,+1,+1, -1,+0,+1], + [+1,-1,+1, -1,+0,+1], + [-1,+1,+1, +1,+1,+0], + [-1,+1,-1, +1,+1,+0], + [+1,+1,+1, -1,+1,+0], + [+1,+1,-1, -1,+1,+0]]), + np.array([ + [-1,+1,+1, +2,+1,+1], + [+1,+1,+1, -2,+1,+1], + [+1,+1,-1, +2,-1,+1], + [+1,-1,+1, +2,+1,-1], + [+1,-1,+1, +1,+2,+1], + [+1,+1,-1, -1,+2,+1], + [+1,+1,+1, +1,-2,+1], + [-1,+1,+1, +1,+2,-1], + [+1,+1,-1, +1,+1,+2], + [+1,-1,+1, -1,+1,+2], + [-1,+1,+1, +1,-1,+2], + [+1,+1,+1, +1,+1,-2]]), + np.array([ + [+1,+1,-1, +1,+2,+3], + [+1,-1,+1, -1,+2,+3], + [-1,+1,+1, +1,-2,+3], + [+1,+1,+1, +1,+2,-3], + [+1,-1,+1, +1,+3,+2], + [+1,+1,-1, -1,+3,+2], + [+1,+1,+1, +1,-3,+2], + [-1,+1,+1, +1,+3,-2], + [+1,+1,-1, +2,+1,+3], + [+1,-1,+1, -2,+1,+3], + [-1,+1,+1, +2,-1,+3], + [+1,+1,+1, +2,+1,-3], + [+1,-1,+1, +2,+3,+1], + [+1,+1,-1, -2,+3,+1], + [+1,+1,+1, +2,-3,+1], + [-1,+1,+1, +2,+3,-1], + [-1,+1,+1, +3,+1,+2], + [+1,+1,+1, -3,+1,+2], + [+1,+1,-1, +3,-1,+2], + [+1,-1,+1, +3,+1,-2], + [-1,+1,+1, +3,+2,+1], + [+1,+1,+1, -3,+2,+1], + [+1,+1,-1, +3,-2,+1], + [+1,-1,+1, +3,+2,-1]])], + 'twin' :[np.array([ + [-1, 1, 1, 2, 1, 1], + [ 1, 1, 1, -2, 1, 1], + [ 1, 1,-1, 2,-1, 1], + [ 1,-1, 1, 2, 1,-1], + [ 1,-1, 1, 1, 2, 1], + [ 1, 1,-1, -1, 2, 1], + [ 1, 1, 1, 1,-2, 1], + [-1, 1, 1, 1, 2,-1], + [ 1, 1,-1, 1, 1, 2], + [ 1,-1, 1, -1, 1, 2], + [-1, 1, 1, 1,-1, 2], + [ 1, 1, 1, 1, 1,-2]])] + }, + 'hP': { + 'slip' :[np.array([ + [+2,-1,-1,+0, +0,+0,+0,+1], + [-1,+2,-1,+0, +0,+0,+0,+1], + [-1,-1,+2,+0, +0,+0,+0,+1]]), + np.array([ + [+2,-1,-1,+0, +0,+1,-1,+0], + [-1,+2,-1,+0, -1,+0,+1,+0], + [-1,-1,+2,+0, +1,-1,+0,+0]]), + np.array([ + [-1,+1,+0,+0, +1,+1,-2,+0], + [+0,-1,+1,+0, -2,+1,+1,+0], + [+1,+0,-1,+0, +1,-2,+1,+0]]), + np.array([ + [-1,+2,-1,+0, +1,+0,-1,+1], + [-2,+1,+1,+0, +0,+1,-1,+1], + [-1,-1,+2,+0, -1,+1,+0,+1], + [+1,-2,+1,+0, -1,+0,+1,+1], + [+2,-1,-1,+0, +0,-1,+1,+1], + [+1,+1,-2,+0, +1,-1,+0,+1]]), + np.array([ + [-2,+1,+1,+3, +1,+0,-1,+1], + [-1,-1,+2,+3, +1,+0,-1,+1], + [-1,-1,+2,+3, +0,+1,-1,+1], + [+1,-2,+1,+3, +0,+1,-1,+1], + [+1,-2,+1,+3, -1,+1,+0,+1], + [+2,-1,-1,+3, -1,+1,+0,+1], + [+2,-1,-1,+3, -1,+0,+1,+1], + [+1,+1,-2,+3, -1,+0,+1,+1], + [+1,+1,-2,+3, +0,-1,+1,+1], + [-1,+2,-1,+3, +0,-1,+1,+1], + [-1,+2,-1,+3, +1,-1,+0,+1], + [-2,+1,+1,+3, +1,-1,+0,+1]]), + np.array([ + [-1,-1,+2,+3, +1,+1,-2,+2], + [+1,-2,+1,+3, -1,+2,-1,+2], + [+2,-1,-1,+3, -2,+1,+1,+2], + [+1,+1,-2,+3, -1,-1,+2,+2], + [-1,+2,-1,+3, +1,-2,+1,+2], + [-2,+1,+1,+3, +2,-1,-1,+2]])], + 'twin' :[np.array([ + [-1, 0, 1, 1, 1, 0,-1, 2], # shear = (3-(c/a)^2)/(sqrt(3) c/a) <-10.1>{10.2} + [ 0,-1, 1, 1, 0, 1,-1, 2], + [ 1,-1, 0, 1, -1, 1, 0, 2], + [ 1, 0,-1, 1, -1, 0, 1, 2], + [ 0, 1,-1, 1, 0,-1, 1, 2], + [-1, 1, 0, 1, 1,-1, 0, 2]]), + np.array([ + [-1,-1, 2, 6, 1, 1,-2, 1], # shear = 1/(c/a) <11.6>{-1-1.1} + [ 1,-2, 1, 6, -1, 2,-1, 1], + [ 2,-1,-1, 6, -2, 1, 1, 1], + [ 1, 1,-2, 6, -1,-1, 2, 1], + [-1, 2,-1, 6, 1,-2, 1, 1], + [-2, 1, 1, 6, 2,-1,-1, 1]]), + np.array([ + [ 1, 0,-1,-2, 1, 0,-1, 1], # shear = (4(c/a)^2-9)/(4 sqrt(3) c/a) <10.-2>{10.1} + [ 0, 1,-1,-2, 0, 1,-1, 1], + [-1, 1, 0,-2, -1, 1, 0, 1], + [-1, 0, 1,-2, -1, 0, 1, 1], + [ 0,-1, 1,-2, 0,-1, 1, 1], + [ 1,-1, 0,-2, 1,-1, 0, 1]]), + np.array([ + [ 1, 1,-2,-3, 1, 1,-2, 2], # shear = 2((c/a)^2-2)/(3 c/a) <11.-3>{11.2} + [-1, 2,-1,-3, -1, 2,-1, 2], + [-2, 1, 1,-3, -2, 1, 1, 2], + [-1,-1, 2,-3, -1,-1, 2, 2], + [ 1,-2, 1,-3, 1,-2, 1, 2], + [ 2,-1,-1,-3, 2,-1,-1, 2]])] + }, + } + master = _kinematics[self.lattice][mode] + if self.lattice == 'hP': + return {'direction':[util.Bravais_to_Miller(uvtw=m[:,0:4]) for m in master], + 'plane': [util.Bravais_to_Miller(hkil=m[:,4:8]) for m in master]} + else: + return {'direction':[m[:,0:3] for m in master], + 'plane': [m[:,3:6] for m in master]} + + + def relation_operations(self,model): + """ + Crystallographic orientation relationships for phase transformations. + + Parameters + ---------- + model : str + Name of orientation relationship. + + Returns + ------- + operations : (string, damask.Rotation) + Resulting lattice and rotations characterizing the orientation relationship. + + References + ---------- + S. Morito et al., Journal of Alloys and Compounds 577:s587-s592, 2013 + https://doi.org/10.1016/j.jallcom.2012.02.004 + + K. Kitahara et al., Acta Materialia 54(5):1279-1288, 2006 + https://doi.org/10.1016/j.actamat.2005.11.001 + + Y. He et al., Journal of Applied Crystallography 39:72-81, 2006 + https://doi.org/10.1107/S0021889805038276 + + H. Kitahara et al., Materials Characterization 54(4-5):378-386, 2005 + https://doi.org/10.1016/j.matchar.2004.12.015 + + Y. He et al., Acta Materialia 53(4):1179-1190, 2005 + https://doi.org/10.1016/j.actamat.2004.11.021 + + """ + _orientation_relationships = { + 'KS': { + 'cF' : np.array([ + [[-1, 0, 1],[ 1, 1, 1]], + [[-1, 0, 1],[ 1, 1, 1]], + [[ 0, 1,-1],[ 1, 1, 1]], + [[ 0, 1,-1],[ 1, 1, 1]], + [[ 1,-1, 0],[ 1, 1, 1]], + [[ 1,-1, 0],[ 1, 1, 1]], + [[ 1, 0,-1],[ 1,-1, 1]], + [[ 1, 0,-1],[ 1,-1, 1]], + [[-1,-1, 0],[ 1,-1, 1]], + [[-1,-1, 0],[ 1,-1, 1]], + [[ 0, 1, 1],[ 1,-1, 1]], + [[ 0, 1, 1],[ 1,-1, 1]], + [[ 0,-1, 1],[-1, 1, 1]], + [[ 0,-1, 1],[-1, 1, 1]], + [[-1, 0,-1],[-1, 1, 1]], + [[-1, 0,-1],[-1, 1, 1]], + [[ 1, 1, 0],[-1, 1, 1]], + [[ 1, 1, 0],[-1, 1, 1]], + [[-1, 1, 0],[ 1, 1,-1]], + [[-1, 1, 0],[ 1, 1,-1]], + [[ 0,-1,-1],[ 1, 1,-1]], + [[ 0,-1,-1],[ 1, 1,-1]], + [[ 1, 0, 1],[ 1, 1,-1]], + [[ 1, 0, 1],[ 1, 1,-1]], + ],dtype=float), + 'cI' : np.array([ + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + [[-1,-1, 1],[ 0, 1, 1]], + [[-1, 1,-1],[ 0, 1, 1]], + ],dtype=float), + }, + 'GT': { + 'cF' : np.array([ + [[ -5,-12, 17],[ 1, 1, 1]], + [[ 17, -5,-12],[ 1, 1, 1]], + [[-12, 17, -5],[ 1, 1, 1]], + [[ 5, 12, 17],[ -1, -1, 1]], + [[-17, 5,-12],[ -1, -1, 1]], + [[ 12,-17, -5],[ -1, -1, 1]], + [[ -5, 12,-17],[ -1, 1, 1]], + [[ 17, 5, 12],[ -1, 1, 1]], + [[-12,-17, 5],[ -1, 1, 1]], + [[ 5,-12,-17],[ 1, -1, 1]], + [[-17, -5, 12],[ 1, -1, 1]], + [[ 12, 17, 5],[ 1, -1, 1]], + [[ -5, 17,-12],[ 1, 1, 1]], + [[-12, -5, 17],[ 1, 1, 1]], + [[ 17,-12, -5],[ 1, 1, 1]], + [[ 5,-17,-12],[ -1, -1, 1]], + [[ 12, 5, 17],[ -1, -1, 1]], + [[-17, 12, -5],[ -1, -1, 1]], + [[ -5,-17, 12],[ -1, 1, 1]], + [[-12, 5,-17],[ -1, 1, 1]], + [[ 17, 12, 5],[ -1, 1, 1]], + [[ 5, 17, 12],[ 1, -1, 1]], + [[ 12, -5,-17],[ 1, -1, 1]], + [[-17,-12, 5],[ 1, -1, 1]], + ],dtype=float), + 'cI' : np.array([ + [[-17, -7, 17],[ 1, 0, 1]], + [[ 17,-17, -7],[ 1, 1, 0]], + [[ -7, 17,-17],[ 0, 1, 1]], + [[ 17, 7, 17],[ -1, 0, 1]], + [[-17, 17, -7],[ -1, -1, 0]], + [[ 7,-17,-17],[ 0, -1, 1]], + [[-17, 7,-17],[ -1, 0, 1]], + [[ 17, 17, 7],[ -1, 1, 0]], + [[ -7,-17, 17],[ 0, 1, 1]], + [[ 17, -7,-17],[ 1, 0, 1]], + [[-17,-17, 7],[ 1, -1, 0]], + [[ 7, 17, 17],[ 0, -1, 1]], + [[-17, 17, -7],[ 1, 1, 0]], + [[ -7,-17, 17],[ 0, 1, 1]], + [[ 17, -7,-17],[ 1, 0, 1]], + [[ 17,-17, -7],[ -1, -1, 0]], + [[ 7, 17, 17],[ 0, -1, 1]], + [[-17, 7,-17],[ -1, 0, 1]], + [[-17,-17, 7],[ -1, 1, 0]], + [[ -7, 17,-17],[ 0, 1, 1]], + [[ 17, 7, 17],[ -1, 0, 1]], + [[ 17, 17, 7],[ 1, -1, 0]], + [[ 7,-17,-17],[ 0, -1, 1]], + [[-17, -7, 17],[ 1, 0, 1]], + ],dtype=float), + }, + 'GT_prime': { + 'cF' : np.array([ + [[ 0, 1, -1],[ 7, 17, 17]], + [[ -1, 0, 1],[ 17, 7, 17]], + [[ 1, -1, 0],[ 17, 17, 7]], + [[ 0, -1, -1],[ -7,-17, 17]], + [[ 1, 0, 1],[-17, -7, 17]], + [[ 1, -1, 0],[-17,-17, 7]], + [[ 0, 1, -1],[ 7,-17,-17]], + [[ 1, 0, 1],[ 17, -7,-17]], + [[ -1, -1, 0],[ 17,-17, -7]], + [[ 0, -1, -1],[ -7, 17,-17]], + [[ -1, 0, 1],[-17, 7,-17]], + [[ -1, -1, 0],[-17, 17, -7]], + [[ 0, -1, 1],[ 7, 17, 17]], + [[ 1, 0, -1],[ 17, 7, 17]], + [[ -1, 1, 0],[ 17, 17, 7]], + [[ 0, 1, 1],[ -7,-17, 17]], + [[ -1, 0, -1],[-17, -7, 17]], + [[ -1, 1, 0],[-17,-17, 7]], + [[ 0, -1, 1],[ 7,-17,-17]], + [[ -1, 0, -1],[ 17, -7,-17]], + [[ 1, 1, 0],[ 17,-17, -7]], + [[ 0, 1, 1],[ -7, 17,-17]], + [[ 1, 0, -1],[-17, 7,-17]], + [[ 1, 1, 0],[-17, 17, -7]], + ],dtype=float), + 'cI' : np.array([ + [[ 1, 1, -1],[ 12, 5, 17]], + [[ -1, 1, 1],[ 17, 12, 5]], + [[ 1, -1, 1],[ 5, 17, 12]], + [[ -1, -1, -1],[-12, -5, 17]], + [[ 1, -1, 1],[-17,-12, 5]], + [[ 1, -1, -1],[ -5,-17, 12]], + [[ -1, 1, -1],[ 12, -5,-17]], + [[ 1, 1, 1],[ 17,-12, -5]], + [[ -1, -1, 1],[ 5,-17,-12]], + [[ 1, -1, -1],[-12, 5,-17]], + [[ -1, -1, 1],[-17, 12, -5]], + [[ -1, -1, -1],[ -5, 17,-12]], + [[ 1, -1, 1],[ 12, 17, 5]], + [[ 1, 1, -1],[ 5, 12, 17]], + [[ -1, 1, 1],[ 17, 5, 12]], + [[ -1, 1, 1],[-12,-17, 5]], + [[ -1, -1, -1],[ -5,-12, 17]], + [[ -1, 1, -1],[-17, -5, 12]], + [[ -1, -1, 1],[ 12,-17, -5]], + [[ -1, 1, -1],[ 5,-12,-17]], + [[ 1, 1, 1],[ 17, -5,-12]], + [[ 1, 1, 1],[-12, 17, -5]], + [[ 1, -1, -1],[ -5, 12,-17]], + [[ 1, 1, -1],[-17, 5,-12]], + ],dtype=float), + }, + 'NW': { + 'cF' : np.array([ + [[ 2, -1, -1],[ 1, 1, 1]], + [[ -1, 2, -1],[ 1, 1, 1]], + [[ -1, -1, 2],[ 1, 1, 1]], + [[ -2, -1, -1],[ -1, 1, 1]], + [[ 1, 2, -1],[ -1, 1, 1]], + [[ 1, -1, 2],[ -1, 1, 1]], + [[ 2, 1, -1],[ 1, -1, 1]], + [[ -1, -2, -1],[ 1, -1, 1]], + [[ -1, 1, 2],[ 1, -1, 1]], + [[ 2, -1, 1],[ -1, -1, 1]], + [[ -1, 2, 1],[ -1, -1, 1]], + [[ -1, -1, -2],[ -1, -1, 1]], + ],dtype=float), + 'cI' : np.array([ + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + [[ 0, -1, 1],[ 0, 1, 1]], + ],dtype=float), + }, + 'Pitsch': { + 'cF' : np.array([ + [[ 1, 0, 1],[ 0, 1, 0]], + [[ 1, 1, 0],[ 0, 0, 1]], + [[ 0, 1, 1],[ 1, 0, 0]], + [[ 0, 1, -1],[ 1, 0, 0]], + [[ -1, 0, 1],[ 0, 1, 0]], + [[ 1, -1, 0],[ 0, 0, 1]], + [[ 1, 0, -1],[ 0, 1, 0]], + [[ -1, 1, 0],[ 0, 0, 1]], + [[ 0, -1, 1],[ 1, 0, 0]], + [[ 0, 1, 1],[ 1, 0, 0]], + [[ 1, 0, 1],[ 0, 1, 0]], + [[ 1, 1, 0],[ 0, 0, 1]], + ],dtype=float), + 'cI' : np.array([ + [[ 1, -1, 1],[ -1, 0, 1]], + [[ 1, 1, -1],[ 1, -1, 0]], + [[ -1, 1, 1],[ 0, 1, -1]], + [[ -1, 1, -1],[ 0, -1, -1]], + [[ -1, -1, 1],[ -1, 0, -1]], + [[ 1, -1, -1],[ -1, -1, 0]], + [[ 1, -1, -1],[ -1, 0, -1]], + [[ -1, 1, -1],[ -1, -1, 0]], + [[ -1, -1, 1],[ 0, -1, -1]], + [[ -1, 1, 1],[ 0, -1, 1]], + [[ 1, -1, 1],[ 1, 0, -1]], + [[ 1, 1, -1],[ -1, 1, 0]], + ],dtype=float), + }, + 'Bain': { + 'cF' : np.array([ + [[ 0, 1, 0],[ 1, 0, 0]], + [[ 0, 0, 1],[ 0, 1, 0]], + [[ 1, 0, 0],[ 0, 0, 1]], + ],dtype=float), + 'cI' : np.array([ + [[ 0, 1, 1],[ 1, 0, 0]], + [[ 1, 0, 1],[ 0, 1, 0]], + [[ 1, 1, 0],[ 0, 0, 1]], + ],dtype=float), + }, + 'Burgers' : { + 'cI' : np.array([ + [[ -1, 1, 1],[ 1, 1, 0]], + [[ -1, 1, -1],[ 1, 1, 0]], + [[ 1, 1, 1],[ 1, -1, 0]], + [[ 1, 1, -1],[ 1, -1, 0]], + + [[ 1, 1, -1],[ 1, 0, 1]], + [[ -1, 1, 1],[ 1, 0, 1]], + [[ 1, 1, 1],[ -1, 0, 1]], + [[ 1, -1, 1],[ -1, 0, 1]], + + [[ -1, 1, -1],[ 0, 1, 1]], + [[ 1, 1, -1],[ 0, 1, 1]], + [[ -1, 1, 1],[ 0, -1, 1]], + [[ 1, 1, 1],[ 0, -1, 1]], + ],dtype=float), + 'hP' : np.array([ + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], + [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], + ],dtype=float), + }, + } + orientation_relationships = {k:v for k,v in _orientation_relationships.items() if self.lattice in v} + if model not in orientation_relationships: + raise KeyError(f'unknown orientation relationship "{model}"') + r = orientation_relationships[model] + + sl = self.lattice + ol = (set(r)-{sl}).pop() + m = r[sl] + o = r[ol] + + p_,_p = np.zeros(m.shape[:-1]+(3,)),np.zeros(o.shape[:-1]+(3,)) + p_[...,0,:] = m[...,0,:] if m.shape[-1] == 3 else util.Bravais_to_Miller(uvtw=m[...,0,0:4]) + p_[...,1,:] = m[...,1,:] if m.shape[-1] == 3 else util.Bravais_to_Miller(hkil=m[...,1,0:4]) + _p[...,0,:] = o[...,0,:] if o.shape[-1] == 3 else util.Bravais_to_Miller(uvtw=o[...,0,0:4]) + _p[...,1,:] = o[...,1,:] if o.shape[-1] == 3 else util.Bravais_to_Miller(hkil=o[...,1,0:4]) + + return (ol,Rotation.from_parallel(p_,_p)) diff --git a/python/damask/_orientation.py b/python/damask/_orientation.py index 72bc8a4d8..7007951da 100644 --- a/python/damask/_orientation.py +++ b/python/damask/_orientation.py @@ -1,11 +1,12 @@ import inspect +import copy import numpy as np from . import Rotation +from . import Crystal from . import util from . import tensor -from . import lattice as lattice_ lattice_symmetries = { @@ -30,10 +31,12 @@ lattice_symmetries = { } _parameter_doc = \ - """lattice : str - Either a crystal family out of {triclinic, monoclinic, orthorhombic, tetragonal, hexagonal, cubic} - or a Bravais lattice out of {aP, mP, mS, oP, oS, oI, oF, tP, tI, hP, cP, cI, cF}. - When specifying a Bravais lattice, additional lattice parameters might be required. + """ + family : {'triclinic', 'monoclinic', 'orthorhombic', 'tetragonal', 'hexagonal', 'cubic'}, optional. + Name of the crystal family. + Will be infered if 'lattice' is given. + lattice : {'aP', 'mP', 'mS', 'oP', 'oS', 'oI', 'oF', 'tP', 'tI', 'hP', 'cP', 'cI', 'cF'}, optional. + Name of the Bravais lattice in Pearson notation. a : float, optional Length of lattice parameter 'a'. b : float, optional @@ -52,7 +55,7 @@ _parameter_doc = \ """ -class Orientation(Rotation): +class Orientation(Rotation,Crystal): """ Representation of crystallographic orientation as combination of rotation and either crystal family or Bravais lattice. @@ -73,7 +76,7 @@ class Orientation(Rotation): - triclinic - aP : primitive - - monoclininic + - monoclinic - mP : primitive - mS : base-centered @@ -104,13 +107,15 @@ class Orientation(Rotation): -------- An array of 3 x 5 random orientations reduced to the fundamental zone of tetragonal symmetry: - >>> damask.Orientation.from_random(shape=(3,5),lattice='tetragonal').reduced + >>> import damask + >>> o=damask.Orientation.from_random(shape=(3,5),family='tetragonal').reduced """ @util.extend_docstring(_parameter_doc) def __init__(self, - rotation = None, + rotation = np.array([1.0,0.0,0.0,0.0]), *, + family = None, lattice = None, a = None,b = None,c = None, alpha = None,beta = None,gamma = None, @@ -126,79 +131,23 @@ class Orientation(Rotation): Defaults to no rotation. """ - Rotation.__init__(self) if rotation is None else Rotation.__init__(self,rotation=rotation) - - if lattice in lattice_symmetries: - self.family = lattice_symmetries[lattice] - self.lattice = lattice - - self.a = 1 if a is None else a - self.b = b - self.c = c - self.a = float(self.a) if self.a is not None else \ - (self.b / self.ratio['b'] if self.b is not None and self.ratio['b'] is not None else - self.c / self.ratio['c'] if self.c is not None and self.ratio['c'] is not None else None) - self.b = float(self.b) if self.b is not None else \ - (self.a * self.ratio['b'] if self.a is not None and self.ratio['b'] is not None else - self.c / self.ratio['c'] * self.ratio['b'] - if self.c is not None and self.ratio['b'] is not None and self.ratio['c'] is not None else None) - self.c = float(self.c) if self.c is not None else \ - (self.a * self.ratio['c'] if self.a is not None and self.ratio['c'] is not None else - self.b / self.ratio['b'] * self.ratio['c'] - if self.c is not None and self.ratio['b'] is not None and self.ratio['c'] is not None else None) - - self.alpha = np.radians(alpha) if degrees and alpha is not None else alpha - self.beta = np.radians(beta) if degrees and beta is not None else beta - self.gamma = np.radians(gamma) if degrees and gamma is not None else gamma - if self.alpha is None and 'alpha' in self.immutable: self.alpha = self.immutable['alpha'] - if self.beta is None and 'beta' in self.immutable: self.beta = self.immutable['beta'] - if self.gamma is None and 'gamma' in self.immutable: self.gamma = self.immutable['gamma'] - - if \ - (self.a is None) \ - or (self.b is None or ('b' in self.immutable and self.b != self.immutable['b'] * self.a)) \ - or (self.c is None or ('c' in self.immutable and self.c != self.immutable['c'] * self.b)) \ - or (self.alpha is None or ('alpha' in self.immutable and self.alpha != self.immutable['alpha'])) \ - or (self.beta is None or ( 'beta' in self.immutable and self.beta != self.immutable['beta'])) \ - or (self.gamma is None or ('gamma' in self.immutable and self.gamma != self.immutable['gamma'])): - raise ValueError (f'Incompatible parameters {self.parameters} for crystal family {self.family}') - - if np.any(np.array([self.alpha,self.beta,self.gamma]) <= 0): - raise ValueError ('Lattice angles must be positive') - if np.any([np.roll([self.alpha,self.beta,self.gamma],r)[0] - > np.sum(np.roll([self.alpha,self.beta,self.gamma],r)[1:]) for r in range(3)]): - raise ValueError ('Each lattice angle must be less than sum of others') - - elif lattice in set(lattice_symmetries.values()): - self.family = lattice - self.lattice = None - - self.a = self.b = self.c = None - self.alpha = self.beta = self.gamma = None - else: - raise KeyError(f'Lattice "{lattice}" is unknown') + Rotation.__init__(self,rotation) + Crystal.__init__(self,family=family, lattice=lattice, + a=a,b=b,c=c, alpha=alpha,beta=beta,gamma=gamma, degrees=degrees) def __repr__(self): """Represent.""" - return '\n'.join(([] if self.lattice is None else [f'Bravais lattice {self.lattice}']) - + ([f'Crystal family {self.family}']) - + [super().__repr__()]) + return '\n'.join([Crystal.__repr__(self), + Rotation.__repr__(self)]) - def __copy__(self,**kwargs): + def __copy__(self,rotation=None): """Create deep copy.""" - return self.__class__(rotation=kwargs['rotation'] if 'rotation' in kwargs else self.quaternion, - lattice =kwargs['lattice'] if 'lattice' in kwargs else self.lattice - if self.lattice is not None else self.family, - a =kwargs['a'] if 'a' in kwargs else self.a, - b =kwargs['b'] if 'b' in kwargs else self.b, - c =kwargs['c'] if 'c' in kwargs else self.c, - alpha =kwargs['alpha'] if 'alpha' in kwargs else self.alpha, - beta =kwargs['beta'] if 'beta' in kwargs else self.beta, - gamma =kwargs['gamma'] if 'gamma' in kwargs else self.gamma, - degrees =kwargs['degrees'] if 'degrees' in kwargs else None, - ) + dup = copy.deepcopy(self) + if rotation is not None: + dup.quaternion = Orientation(rotation,family='cubic').quaternion + return dup copy = __copy__ @@ -213,8 +162,9 @@ class Orientation(Rotation): Orientation to check for equality. """ - matching_type = all([hasattr(other,attr) and getattr(self,attr) == getattr(other,attr) - for attr in ['family','lattice','parameters']]) + matching_type = self.family == other.family and \ + self.lattice == other.lattice and \ + self.parameters == other.parameters return np.logical_and(matching_type,super(self.__class__,self.reduced).__eq__(other.reduced)) def __ne__(self,other): @@ -251,8 +201,9 @@ class Orientation(Rotation): Mask indicating where corresponding orientations are close. """ - matching_type = all([hasattr(other,attr) and getattr(self,attr) == getattr(other,attr) - for attr in ['family','lattice','parameters']]) + matching_type = self.family == other.family and \ + self.lattice == other.lattice and \ + self.parameters == other.parameters return np.logical_and(matching_type,super(self.__class__,self.reduced).isclose(other.reduced)) @@ -431,84 +382,6 @@ class Orientation(Rotation): return o.copy(rotation=Rotation.from_matrix(tensor.transpose(om/np.linalg.norm(om,axis=-1,keepdims=True)))) - @property - def symmetry_operations(self): - """Symmetry operations as Rotations.""" - if self.family == 'cubic': - sym_quats = [ - [ 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.family == 'hexagonal': - sym_quats = [ - [ 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.family == 'tetragonal': - sym_quats = [ - [ 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.family == 'orthorhombic': - sym_quats = [ - [ 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 ], - ] - elif self.family == 'monoclinic': - sym_quats = [ - [ 1.0,0.0,0.0,0.0 ], - [ 0.0,0.0,1.0,0.0 ], - ] - elif self.family == 'triclinic': - sym_quats = [ - [ 1.0,0.0,0.0,0.0 ], - ] - else: - raise KeyError(f'unknown crystal family "{self.family}"') - - return Rotation.from_quaternion(sym_quats,accept_homomorph=True) - - @property def equivalent(self): """ @@ -518,7 +391,8 @@ class Orientation(Rotation): is added to the left of the Rotation array. """ - o = self.symmetry_operations.broadcast_to(self.symmetry_operations.shape+self.shape,mode='right') + sym_ops = self.symmetry_operations + o = sym_ops.broadcast_to(sym_ops.shape+self.shape,mode='right') return self.copy(rotation=o*Rotation(self.quaternion).broadcast_to(o.shape,mode='left')) @@ -619,381 +493,6 @@ class Orientation(Rotation): return np.ones_like(rho[...,0],dtype=bool) - def relation_operations(self,model,return_lattice=False): - """ - Crystallographic orientation relationships for phase transformations. - - Parameters - ---------- - model : str - Name of orientation relationship. - return_lattice : bool, optional - Return the target lattice in addition. - - Returns - ------- - operations : Rotations - Rotations characterizing the orientation relationship. - - References - ---------- - S. Morito et al., Journal of Alloys and Compounds 577:s587-s592, 2013 - https://doi.org/10.1016/j.jallcom.2012.02.004 - - K. Kitahara et al., Acta Materialia 54(5):1279-1288, 2006 - https://doi.org/10.1016/j.actamat.2005.11.001 - - Y. He et al., Journal of Applied Crystallography 39:72-81, 2006 - https://doi.org/10.1107/S0021889805038276 - - H. Kitahara et al., Materials Characterization 54(4-5):378-386, 2005 - https://doi.org/10.1016/j.matchar.2004.12.015 - - Y. He et al., Acta Materialia 53(4):1179-1190, 2005 - https://doi.org/10.1016/j.actamat.2004.11.021 - - """ - if model not in lattice_.relations: - raise KeyError(f'unknown orientation relationship "{model}"') - r = lattice_.relations[model] - - if self.lattice not in r: - raise KeyError(f'relationship "{model}" not supported for lattice "{self.lattice}"') - - sl = self.lattice - ol = (set(r)-{sl}).pop() - m = r[sl] - o = r[ol] - - p_,_p = np.zeros(m.shape[:-1]+(3,)),np.zeros(o.shape[:-1]+(3,)) - p_[...,0,:] = m[...,0,:] if m.shape[-1] == 3 else lattice_.Bravais_to_Miller(uvtw=m[...,0,0:4]) - p_[...,1,:] = m[...,1,:] if m.shape[-1] == 3 else lattice_.Bravais_to_Miller(hkil=m[...,1,0:4]) - _p[...,0,:] = o[...,0,:] if o.shape[-1] == 3 else lattice_.Bravais_to_Miller(uvtw=o[...,0,0:4]) - _p[...,1,:] = o[...,1,:] if o.shape[-1] == 3 else lattice_.Bravais_to_Miller(hkil=o[...,1,0:4]) - - return (Rotation.from_parallel(p_,_p),ol) \ - if return_lattice else \ - Rotation.from_parallel(p_,_p) - - - def related(self,model): - """ - Orientations derived from the given relationship. - - One dimension (length according to number of related orientations) - is added to the left of the Rotation array. - - """ - o,lattice = self.relation_operations(model,return_lattice=True) - target = Orientation(lattice=lattice) - o = o.broadcast_to(o.shape+self.shape,mode='right') - return self.copy(rotation=o*Rotation(self.quaternion).broadcast_to(o.shape,mode='left'), - lattice=lattice, - b = self.b if target.ratio['b'] is None else self.a*target.ratio['b'], - c = self.c if target.ratio['c'] is None else self.a*target.ratio['c'], - alpha = None if 'alpha' in target.immutable else self.alpha, - beta = None if 'beta' in target.immutable else self.beta, - gamma = None if 'gamma' in target.immutable else self.gamma, - ) - - - @property - def parameters(self): - """Return lattice parameters a, b, c, alpha, beta, gamma.""" - return (self.a,self.b,self.c,self.alpha,self.beta,self.gamma) - - - @property - def immutable(self): - """Return immutable parameters of own lattice.""" - if self.family == 'triclinic': - return {} - if self.family == 'monoclinic': - return { - 'alpha': np.pi/2., - 'gamma': np.pi/2., - } - if self.family == 'orthorhombic': - return { - 'alpha': np.pi/2., - 'beta': np.pi/2., - 'gamma': np.pi/2., - } - if self.family == 'tetragonal': - return { - 'b': 1.0, - 'alpha': np.pi/2., - 'beta': np.pi/2., - 'gamma': np.pi/2., - } - if self.family == 'hexagonal': - return { - 'b': 1.0, - 'alpha': np.pi/2., - 'beta': np.pi/2., - 'gamma': 2.*np.pi/3., - } - if self.family == 'cubic': - return { - 'b': 1.0, - 'c': 1.0, - 'alpha': np.pi/2., - 'beta': np.pi/2., - 'gamma': np.pi/2., - } - - - @property - def ratio(self): - """Return axes ratios of own lattice.""" - _ratio = { 'hexagonal': {'c': np.sqrt(8./3.)}} - - return dict(b = self.immutable['b'] - if 'b' in self.immutable else - _ratio[self.family]['b'] if self.family in _ratio and 'b' in _ratio[self.family] else None, - c = self.immutable['c'] - if 'c' in self.immutable else - _ratio[self.family]['c'] if self.family in _ratio and 'c' in _ratio[self.family] else None, - ) - - - @property - def basis_real(self): - """ - Calculate orthogonal real space crystal basis. - - References - ---------- - C.T. Young and J.L. Lytton, Journal of Applied Physics 43:1408–1417, 1972 - https://doi.org/10.1063/1.1661333 - - """ - if None in self.parameters: - raise KeyError('missing crystal lattice parameters') - return np.array([ - [1,0,0], - [np.cos(self.gamma),np.sin(self.gamma),0], - [np.cos(self.beta), - (np.cos(self.alpha)-np.cos(self.beta)*np.cos(self.gamma)) /np.sin(self.gamma), - np.sqrt(1 - np.cos(self.alpha)**2 - np.cos(self.beta)**2 - np.cos(self.gamma)**2 - + 2 * np.cos(self.alpha) * np.cos(self.beta) * np.cos(self.gamma))/np.sin(self.gamma)], - ],dtype=float).T \ - * np.array([self.a,self.b,self.c]) - - - @property - def basis_reciprocal(self): - """Calculate reciprocal (dual) crystal basis.""" - return np.linalg.inv(self.basis_real.T) - - - def in_SST(self,vector,proper=False): - """ - Check whether given crystal frame vector falls into standard stereographic triangle of own symmetry. - - Parameters - ---------- - vector : numpy.ndarray of shape (...,3) - Vector to check. - proper : bool, optional - Consider only vectors with z >= 0, hence combine two neighboring SSTs. - Defaults to False. - - Returns - ------- - in : numpy.ndarray of shape (...) - Boolean array indicating whether vector falls into SST. - - References - ---------- - Bases are computed from - - >>> basis = { - ... 'cubic' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,1.]/np.sqrt(2.), # green - ... [1.,1.,1.]/np.sqrt(3.)]).T), # blue - ... 'hexagonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [np.sqrt(3.),1.,0.]/np.sqrt(4.)]).T), # blue - ... 'tetragonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [1.,1.,0.]/np.sqrt(2.)]).T), # blue - ... 'orthorhombic': np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [0.,1.,0.]]).T), # blue - ... } - - """ - if not isinstance(vector,np.ndarray) or vector.shape[-1] != 3: - raise ValueError('input is not a field of three-dimensional vectors') - - if self.family == '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.family == '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.family == '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.family == '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 - return np.ones_like(vector[...,0],bool) - - if proper: - components_proper = np.around(np.einsum('...ji,...i', - np.broadcast_to(basis['proper'], vector.shape+(3,)), - vector), 12) - components_improper = np.around(np.einsum('...ji,...i', - np.broadcast_to(basis['improper'], vector.shape+(3,)), - vector), 12) - return np.all(components_proper >= 0.0,axis=-1) \ - | np.all(components_improper >= 0.0,axis=-1) - else: - components = np.around(np.einsum('...ji,...i', - np.broadcast_to(basis['improper'], vector.shape+(3,)), - np.block([vector[...,:2],np.abs(vector[...,2:3])])), 12) - - return np.all(components >= 0.0,axis=-1) - - - def IPF_color(self,vector,in_SST=True,proper=False): - """ - Map vector to RGB color within standard stereographic triangle of own symmetry. - - Parameters - ---------- - vector : numpy.ndarray of shape (...,3) - Vector to colorize. - in_SST : bool, optional - Consider symmetrically equivalent orientations such that poles are located in SST. - Defaults to True. - proper : bool, optional - Consider only vectors with z >= 0, hence combine two neighboring SSTs (with mirrored colors). - Defaults to False. - - Returns - ------- - rgb : numpy.ndarray of shape (...,3) - RGB array of IPF colors. - - Examples - -------- - Inverse pole figure color of the e_3 direction for a crystal in "Cube" orientation with cubic symmetry: - - >>> o = damask.Orientation(lattice='cubic') - >>> o.IPF_color([0,0,1]) - array([1., 0., 0.]) - - References - ---------- - Bases are computed from - - >>> basis = { - ... 'cubic' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,1.]/np.sqrt(2.), # green - ... [1.,1.,1.]/np.sqrt(3.)]).T), # blue - ... 'hexagonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [np.sqrt(3.),1.,0.]/np.sqrt(4.)]).T), # blue - ... 'tetragonal' : np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [1.,1.,0.]/np.sqrt(2.)]).T), # blue - ... 'orthorhombic': np.linalg.inv(np.array([[0.,0.,1.], # direction of red - ... [1.,0.,0.], # green - ... [0.,1.,0.]]).T), # blue - ... } - - """ - if np.array(vector).shape[-1] != 3: - raise ValueError('input is not a field of three-dimensional vectors') - - vector_ = self.to_SST(vector,proper) if in_SST else \ - self @ np.broadcast_to(vector,self.shape+(3,)) - - if self.family == '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.family == '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.family == '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.family == '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 - return np.zeros_like(vector_) - - if proper: - components_proper = np.around(np.einsum('...ji,...i', - np.broadcast_to(basis['proper'], vector_.shape+(3,)), - vector_), 12) - components_improper = np.around(np.einsum('...ji,...i', - np.broadcast_to(basis['improper'], vector_.shape+(3,)), - vector_), 12) - in_SST = np.all(components_proper >= 0.0,axis=-1) \ - | np.all(components_improper >= 0.0,axis=-1) - components = np.where((in_SST & np.all(components_proper >= 0.0,axis=-1))[...,np.newaxis], - components_proper,components_improper) - else: - components = np.around(np.einsum('...ji,...i', - np.broadcast_to(basis['improper'], vector_.shape+(3,)), - np.block([vector_[...,:2],np.abs(vector_[...,2:3])])), 12) - - in_SST = np.all(components >= 0.0,axis=-1) - - with np.errstate(invalid='ignore',divide='ignore'): - rgb = (components/np.linalg.norm(components,axis=-1,keepdims=True))**0.5 # smoothen color ramps - rgb = np.clip(rgb,0.,1.) # clip intensity - rgb /= np.max(rgb,axis=-1,keepdims=True) # normalize to (HS)V = 1 - rgb[np.broadcast_to(~in_SST[...,np.newaxis],rgb.shape)] = 0.0 - return rgb - - def disorientation(self,other,return_operators=False): """ Calculate disorientation between myself and given other orientation. @@ -1148,57 +647,185 @@ class Orientation(Rotation): ) - def to_lattice(self,*,direction=None,plane=None): + def in_SST(self,vector,proper=False): """ - Calculate lattice vector corresponding to crystal frame direction or plane normal. + Check whether given crystal frame vector falls into standard stereographic triangle of own symmetry. Parameters ---------- - direction|normal : numpy.ndarray of shape (...,3) - Vector along direction or plane normal. + vector : numpy.ndarray of shape (...,3) + Vector to check. + proper : bool, optional + Consider only vectors with z >= 0, hence combine two neighboring SSTs. + Defaults to False. Returns ------- - Miller : numpy.ndarray of shape (...,3) - lattice vector of direction or plane. - Use util.scale_to_coprime to convert to (integer) Miller indices. + in : numpy.ndarray of shape (...) + Boolean array indicating whether vector falls into SST. """ - if (direction is not None) ^ (plane is None): - raise KeyError('specify either "direction" or "plane"') - axis,basis = (np.array(direction),self.basis_reciprocal.T) \ - if plane is None else \ - (np.array(plane),self.basis_real.T) - return np.einsum('il,...l',basis,axis) + if not isinstance(vector,np.ndarray) or vector.shape[-1] != 3: + raise ValueError('input is not a field of three-dimensional vectors') + + if self.standard_triangle is None: # direct exit for no symmetry + return np.ones_like(vector[...,0],bool) + + if proper: + components_proper = np.around(np.einsum('...ji,...i', + np.broadcast_to(self.standard_triangle['proper'], vector.shape+(3,)), + vector), 12) + components_improper = np.around(np.einsum('...ji,...i', + np.broadcast_to(self.standard_triangle['improper'], vector.shape+(3,)), + vector), 12) + return np.all(components_proper >= 0.0,axis=-1) \ + | np.all(components_improper >= 0.0,axis=-1) + else: + components = np.around(np.einsum('...ji,...i', + np.broadcast_to(self.standard_triangle['improper'], vector.shape+(3,)), + np.block([vector[...,:2],np.abs(vector[...,2:3])])), 12) + + return np.all(components >= 0.0,axis=-1) - def to_frame(self,*,uvw=None,hkl=None,with_symmetry=False): + def IPF_color(self,vector,in_SST=True,proper=False): """ - Calculate crystal frame vector along lattice direction [uvw] or plane normal (hkl). + Map vector to RGB color within standard stereographic triangle of own symmetry. Parameters ---------- - uvw|hkl : numpy.ndarray of shape (...,3) - Miller indices of crystallographic direction or plane normal. - with_symmetry : bool, optional - Calculate all N symmetrically equivalent vectors. + vector : numpy.ndarray of shape (...,3) + Vector to colorize. + in_SST : bool, optional + Consider symmetrically equivalent orientations such that poles are located in SST. + Defaults to True. + proper : bool, optional + Consider only vectors with z >= 0, hence combine two neighboring SSTs (with mirrored colors). + Defaults to False. Returns ------- - vector : numpy.ndarray of shape (...,3) or (N,...,3) - Crystal frame vector (or vectors if with_symmetry) along [uvw] direction or (hkl) plane normal. + rgb : numpy.ndarray of shape (...,3) + RGB array of IPF colors. + + Examples + -------- + Inverse pole figure color of the e_3 direction for a crystal in "Cube" orientation with cubic symmetry: + + >>> import damask + >>> o = damask.Orientation(lattice='cubic') + >>> o.IPF_color([0,0,1]) + array([1., 0., 0.]) """ - if (uvw is not None) ^ (hkl is None): - raise KeyError('specify either "uvw" or "hkl"') - axis,basis = (np.array(uvw),self.basis_real) \ - if hkl is None else \ - (np.array(hkl),self.basis_reciprocal) - return (self.symmetry_operations.broadcast_to(self.symmetry_operations.shape+axis.shape[:-1],mode='right') - @ np.broadcast_to(np.einsum('il,...l',basis,axis),self.symmetry_operations.shape+axis.shape) - if with_symmetry else - np.einsum('il,...l',basis,axis)) + if np.array(vector).shape[-1] != 3: + raise ValueError('input is not a field of three-dimensional vectors') + vector_ = self.to_SST(vector,proper) if in_SST else \ + self @ np.broadcast_to(vector,self.shape+(3,)) + + if self.standard_triangle is None: # direct exit for no symmetry + return np.zeros_like(vector_) + + if proper: + components_proper = np.around(np.einsum('...ji,...i', + np.broadcast_to(self.standard_triangle['proper'], vector_.shape+(3,)), + vector_), 12) + components_improper = np.around(np.einsum('...ji,...i', + np.broadcast_to(self.standard_triangle['improper'], vector_.shape+(3,)), + vector_), 12) + in_SST = np.all(components_proper >= 0.0,axis=-1) \ + | np.all(components_improper >= 0.0,axis=-1) + components = np.where((in_SST & np.all(components_proper >= 0.0,axis=-1))[...,np.newaxis], + components_proper,components_improper) + else: + components = np.around(np.einsum('...ji,...i', + np.broadcast_to(self .standard_triangle['improper'], vector_.shape+(3,)), + np.block([vector_[...,:2],np.abs(vector_[...,2:3])])), 12) + + in_SST = np.all(components >= 0.0,axis=-1) + + with np.errstate(invalid='ignore',divide='ignore'): + rgb = (components/np.linalg.norm(components,axis=-1,keepdims=True))**0.5 # smoothen color ramps + rgb = np.clip(rgb,0.,1.) # clip intensity + rgb /= np.max(rgb,axis=-1,keepdims=True) # normalize to (HS)V = 1 + rgb[np.broadcast_to(~in_SST[...,np.newaxis],rgb.shape)] = 0.0 + + return rgb + + + @property + def symmetry_operations(self): + """Symmetry operations as Rotations.""" + _symmetry_operations = { + 'cubic': [ + [ 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 ], + ], + 'hexagonal': [ + [ 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 ], + ], + 'tetragonal': [ + [ 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) ], + ], + 'orthorhombic': [ + [ 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 ], + ], + 'monoclinic': [ + [ 1.0,0.0,0.0,0.0 ], + [ 0.0,0.0,1.0,0.0 ], + ], + 'triclinic': [ + [ 1.0,0.0,0.0,0.0 ], + ]} + return Rotation.from_quaternion(_symmetry_operations[self.family],accept_homomorph=True) + + +#################################################################################################### + # functions that require lattice, not just family def to_pole(self,*,uvw=None,hkl=None,with_symmetry=False): """ @@ -1217,51 +844,78 @@ class Orientation(Rotation): Lab frame vector (or vectors if with_symmetry) along [uvw] direction or (hkl) plane normal. """ - v = self.to_frame(uvw=uvw,hkl=hkl,with_symmetry=with_symmetry) + v = self.to_frame(uvw=uvw,hkl=hkl) + if with_symmetry: + sym_ops = self.symmetry_operations + v = sym_ops.broadcast_to(sym_ops.shape+v.shape[:-1],mode='right') \ + @ np.broadcast_to(v,sym_ops.shape+v.shape) return ~(self if self.shape+v.shape[:-1] == () else self.broadcast_to(self.shape+v.shape[:-1],mode='right')) \ @ np.broadcast_to(v,self.shape+v.shape) - def Schmid(self,mode): + def Schmid(self,*,N_slip=None,N_twin=None): u""" - Calculate Schmid matrix P = d ⨂ n in the lab frame for given lattice shear kinematics. + Calculate Schmid matrix P = d ⨂ n in the lab frame for selected deformation systems. Parameters ---------- - mode : {'slip', 'twin'} - Type of kinematics. + N_slip|N_twin : iterable of int + Number of deformation systems per family of the deformation system. + Use '*' to select all. Returns ------- - P : numpy.ndarray of shape (...,N,3,3) + P : numpy.ndarray of shape (N,...,3,3) Schmid matrix for each of the N deformation systems. Examples -------- - Schmid matrix (in lab frame) of slip systems of a face-centered + Schmid matrix (in lab frame) of first octahedral slip system of a face-centered cubic crystal in "Goss" orientation. >>> import damask >>> import numpy as np >>> np.set_printoptions(3,suppress=True,floatmode='fixed') - >>> damask.Orientation.from_Eulers(phi=[0,45,0],degrees=True,lattice='cF').Schmid('slip')[0] + >>> O = damask.Orientation.from_Euler_angles(phi=[0,45,0],degrees=True,lattice='cF') + >>> O.Schmid(N_slip=[1]) array([[ 0.000, 0.000, 0.000], [ 0.577, -0.000, 0.816], [ 0.000, 0.000, 0.000]]) """ - try: - master = lattice_.kinematics[self.lattice][mode] - kinematics = {'direction':master[:,0:3],'plane':master[:,3:6]} \ - if master.shape[-1] == 6 else \ - {'direction':lattice_.Bravais_to_Miller(uvtw=master[:,0:4]), - 'plane': lattice_.Bravais_to_Miller(hkil=master[:,4:8])} - except KeyError: - raise (f'"{mode}" not defined for lattice "{self.lattice}"') - d = self.to_frame(uvw=kinematics['direction'],with_symmetry=False) - p = self.to_frame(hkl=kinematics['plane'] ,with_symmetry=False) - P = np.einsum('...i,...j',d/np.linalg.norm(d,axis=-1,keepdims=True), - p/np.linalg.norm(p,axis=-1,keepdims=True)) + if (N_slip is not None) ^ (N_twin is None): + raise KeyError('Specify either "N_slip" or "N_twin"') - return ~self.broadcast_to( self.shape+P.shape[:-2],mode='right') \ - @ np.broadcast_to(P,self.shape+P.shape) + kinematics,active = (self.kinematics('slip'),N_slip) if N_twin is None else \ + (self.kinematics('twin'),N_twin) + if active == '*': active = [len(a) for a in kinematics['direction']] + + d = self.to_frame(uvw=np.vstack([kinematics['direction'][i][:n] for i,n in enumerate(active)])) + p = self.to_frame(hkl=np.vstack([kinematics['plane'][i][:n] for i,n in enumerate(active)])) + P = np.einsum('...i,...j',d/np.linalg.norm(d,axis=1,keepdims=True), + p/np.linalg.norm(p,axis=1,keepdims=True)) + + shape = P.shape[0:1]+self.shape+(3,3) + return ~self.broadcast_to(shape[:-2]) \ + @ np.broadcast_to(P.reshape(util.shapeshifter(P.shape,shape)),shape) + + + def related(self,model): + """ + Orientations derived from the given relationship. + + One dimension (length according to number of related orientations) + is added to the left of the Rotation array. + + """ + lattice,o = self.relation_operations(model) + target = Crystal(lattice=lattice) + o = o.broadcast_to(o.shape+self.shape,mode='right') + return Orientation(rotation=o*Rotation(self.quaternion).broadcast_to(o.shape,mode='left'), + lattice=lattice, + b = self.b if target.ratio['b'] is None else self.a*target.ratio['b'], + c = self.c if target.ratio['c'] is None else self.a*target.ratio['c'], + alpha = None if 'alpha' in target.immutable else self.alpha, + beta = None if 'beta' in target.immutable else self.beta, + gamma = None if 'gamma' in target.immutable else self.gamma, + ) diff --git a/python/damask/_result.py b/python/damask/_result.py index d502969a1..09e0b2507 100644 --- a/python/damask/_result.py +++ b/python/damask/_result.py @@ -981,47 +981,35 @@ class Result: self._add_generic_pointwise(self._add_stress_second_Piola_Kirchhoff,{'P':P,'F':F}) -# The add_pole functionality needs discussion. -# The new Crystal object can perform such a calculation but the outcome depends on the lattice parameters -# as well as on whether a direction or plane is concerned (see the DAMASK_examples/pole_figure notebook). -# Below code appears to be too simplistic. - # @staticmethod - # def _add_pole(q,p,polar): - # pole = np.array(p) - # unit_pole = pole/np.linalg.norm(pole) - # m = util.scale_to_coprime(pole) - # rot = Rotation(q['data'].view(np.double).reshape(-1,4)) - # - # rotatedPole = rot @ np.broadcast_to(unit_pole,rot.shape+(3,)) # rotate pole according to crystal orientation - # xy = rotatedPole[:,0:2]/(1.+abs(unit_pole[2])) # stereographic projection - # coords = xy if not polar else \ - # np.block([np.sqrt(xy[:,0:1]*xy[:,0:1]+xy[:,1:2]*xy[:,1:2]),np.arctan2(xy[:,1:2],xy[:,0:1])]) - # return { - # 'data': coords, - # 'label': 'p^{}_[{} {} {})'.format(u'rφ' if polar else 'xy',*m), - # 'meta' : { - # 'unit': '1', - # 'description': '{} coordinates of stereographic projection of pole (direction/plane) in crystal frame'\ - # .format('Polar' if polar else 'Cartesian'), - # 'creator': 'add_pole' - # } - # } - # def add_pole(self,q,p,polar=False): - # """ - # Add coordinates of stereographic projection of given pole in crystal frame. - # - # Parameters - # ---------- - # q : str - # Name of the dataset containing the crystallographic orientation as quaternions. - # p : numpy.array of shape (3) - # Crystallographic direction or plane. - # polar : bool, optional - # Give pole in polar coordinates. Defaults to False. - # - # """ - # self._add_generic_pointwise(self._add_pole,{'q':q},{'p':p,'polar':polar}) + @staticmethod + def _add_pole(q,uvw,hkl): + c = q['meta']['c/a'] if 'c/a' in q['meta'] else 1 + pole = Orientation(q['data'], lattice=q['meta']['lattice'], a=1, c=c).to_pole(uvw=uvw,hkl=hkl) + + return { + 'data': pole, + 'label': 'p^[{} {} {}]'.format(*uvw) if uvw else 'p^({} {} {})'.format(*hkl), + 'meta' : { + 'unit': '1', + 'description': f"lab frame vector along lattice {'direction' if uvw else 'plane'}", + 'creator': 'add_pole' + } + } + def add_pole(self,q='O',*,uvw=None,hkl=None): + """ + Add lab frame vector along lattice direction [uvw] or plane normal (hkl). + + Parameters + ---------- + q : str + Name of the dataset containing the crystallographic orientation as quaternions. + Defaults to 'O'. + uvw|hkl : numpy.ndarray of shape (...,3) + Miller indices of crystallographic direction or plane normal. + + """ + self._add_generic_pointwise(self._add_pole,{'q':q},{'uvw':uvw,'hkl':hkl}) @staticmethod diff --git a/python/damask/lattice.py b/python/damask/lattice.py deleted file mode 100644 index e335fc85b..000000000 --- a/python/damask/lattice.py +++ /dev/null @@ -1,501 +0,0 @@ -import numpy as _np - - -def Bravais_to_Miller(*,uvtw=None,hkil=None): - """ - Transform 4 Miller–Bravais indices to 3 Miller indices of crystal direction [uvw] or plane normal (hkl). - - Parameters - ---------- - uvtw|hkil : numpy.ndarray of shape (...,4) - Miller–Bravais indices of crystallographic direction [uvtw] or plane normal (hkil). - - Returns - ------- - uvw|hkl : numpy.ndarray of shape (...,3) - Miller indices of [uvw] direction or (hkl) plane normal. - - """ - if (uvtw is not None) ^ (hkil is None): - raise KeyError('Specify either "uvtw" or "hkil"') - axis,basis = (_np.array(uvtw),_np.array([[1,0,-1,0], - [0,1,-1,0], - [0,0, 0,1]])) \ - if hkil is None else \ - (_np.array(hkil),_np.array([[1,0,0,0], - [0,1,0,0], - [0,0,0,1]])) - return _np.einsum('il,...l',basis,axis) - - -def Miller_to_Bravais(*,uvw=None,hkl=None): - """ - Transform 3 Miller indices to 4 Miller–Bravais indices of crystal direction [uvtw] or plane normal (hkil). - - Parameters - ---------- - uvw|hkl : numpy.ndarray of shape (...,3) - Miller indices of crystallographic direction [uvw] or plane normal (hkl). - - Returns - ------- - uvtw|hkil : numpy.ndarray of shape (...,4) - Miller–Bravais indices of [uvtw] direction or (hkil) plane normal. - - """ - if (uvw is not None) ^ (hkl is None): - raise KeyError('Specify either "uvw" or "hkl"') - axis,basis = (_np.array(uvw),_np.array([[ 2,-1, 0], - [-1, 2, 0], - [-1,-1, 0], - [ 0, 0, 3]])/3) \ - if hkl is None else \ - (_np.array(hkl),_np.array([[ 1, 0, 0], - [ 0, 1, 0], - [-1,-1, 0], - [ 0, 0, 1]])) - return _np.einsum('il,...l',basis,axis) - - -kinematics = { - 'cF': { - 'slip' : _np.array([ - [+0,+1,-1 , +1,+1,+1], - [-1,+0,+1 , +1,+1,+1], - [+1,-1,+0 , +1,+1,+1], - [+0,-1,-1 , -1,-1,+1], - [+1,+0,+1 , -1,-1,+1], - [-1,+1,+0 , -1,-1,+1], - [+0,-1,+1 , +1,-1,-1], - [-1,+0,-1 , +1,-1,-1], - [+1,+1,+0 , +1,-1,-1], - [+0,+1,+1 , -1,+1,-1], - [+1,+0,-1 , -1,+1,-1], - [-1,-1,+0 , -1,+1,-1], - [+1,+1,+0 , +1,-1,+0], - [+1,-1,+0 , +1,+1,+0], - [+1,+0,+1 , +1,+0,-1], - [+1,+0,-1 , +1,+0,+1], - [+0,+1,+1 , +0,+1,-1], - [+0,+1,-1 , +0,+1,+1], - ],'d'), - 'twin' : _np.array([ - [-2, 1, 1, 1, 1, 1], - [ 1,-2, 1, 1, 1, 1], - [ 1, 1,-2, 1, 1, 1], - [ 2,-1, 1, -1,-1, 1], - [-1, 2, 1, -1,-1, 1], - [-1,-1,-2, -1,-1, 1], - [-2,-1,-1, 1,-1,-1], - [ 1, 2,-1, 1,-1,-1], - [ 1,-1, 2, 1,-1,-1], - [ 2, 1,-1, -1, 1,-1], - [-1,-2,-1, -1, 1,-1], - [-1, 1, 2, -1, 1,-1], - ],dtype=float), - }, - 'cI': { - 'slip' : _np.array([ - [+1,-1,+1 , +0,+1,+1], - [-1,-1,+1 , +0,+1,+1], - [+1,+1,+1 , +0,-1,+1], - [-1,+1,+1 , +0,-1,+1], - [-1,+1,+1 , +1,+0,+1], - [-1,-1,+1 , +1,+0,+1], - [+1,+1,+1 , -1,+0,+1], - [+1,-1,+1 , -1,+0,+1], - [-1,+1,+1 , +1,+1,+0], - [-1,+1,-1 , +1,+1,+0], - [+1,+1,+1 , -1,+1,+0], - [+1,+1,-1 , -1,+1,+0], - [-1,+1,+1 , +2,+1,+1], - [+1,+1,+1 , -2,+1,+1], - [+1,+1,-1 , +2,-1,+1], - [+1,-1,+1 , +2,+1,-1], - [+1,-1,+1 , +1,+2,+1], - [+1,+1,-1 , -1,+2,+1], - [+1,+1,+1 , +1,-2,+1], - [-1,+1,+1 , +1,+2,-1], - [+1,+1,-1 , +1,+1,+2], - [+1,-1,+1 , -1,+1,+2], - [-1,+1,+1 , +1,-1,+2], - [+1,+1,+1 , +1,+1,-2], - [+1,+1,-1 , +1,+2,+3], - [+1,-1,+1 , -1,+2,+3], - [-1,+1,+1 , +1,-2,+3], - [+1,+1,+1 , +1,+2,-3], - [+1,-1,+1 , +1,+3,+2], - [+1,+1,-1 , -1,+3,+2], - [+1,+1,+1 , +1,-3,+2], - [-1,+1,+1 , +1,+3,-2], - [+1,+1,-1 , +2,+1,+3], - [+1,-1,+1 , -2,+1,+3], - [-1,+1,+1 , +2,-1,+3], - [+1,+1,+1 , +2,+1,-3], - [+1,-1,+1 , +2,+3,+1], - [+1,+1,-1 , -2,+3,+1], - [+1,+1,+1 , +2,-3,+1], - [-1,+1,+1 , +2,+3,-1], - [-1,+1,+1 , +3,+1,+2], - [+1,+1,+1 , -3,+1,+2], - [+1,+1,-1 , +3,-1,+2], - [+1,-1,+1 , +3,+1,-2], - [-1,+1,+1 , +3,+2,+1], - [+1,+1,+1 , -3,+2,+1], - [+1,+1,-1 , +3,-2,+1], - [+1,-1,+1 , +3,+2,-1], - ],'d'), - 'twin' : _np.array([ - [-1, 1, 1, 2, 1, 1], - [ 1, 1, 1, -2, 1, 1], - [ 1, 1,-1, 2,-1, 1], - [ 1,-1, 1, 2, 1,-1], - [ 1,-1, 1, 1, 2, 1], - [ 1, 1,-1, -1, 2, 1], - [ 1, 1, 1, 1,-2, 1], - [-1, 1, 1, 1, 2,-1], - [ 1, 1,-1, 1, 1, 2], - [ 1,-1, 1, -1, 1, 2], - [-1, 1, 1, 1,-1, 2], - [ 1, 1, 1, 1, 1,-2], - ],dtype=float), - }, - 'hP': { - 'slip' : _np.array([ - [+2,-1,-1,+0 , +0,+0,+0,+1], - [-1,+2,-1,+0 , +0,+0,+0,+1], - [-1,-1,+2,+0 , +0,+0,+0,+1], - [+2,-1,-1,+0 , +0,+1,-1,+0], - [-1,+2,-1,+0 , -1,+0,+1,+0], - [-1,-1,+2,+0 , +1,-1,+0,+0], - [-1,+1,+0,+0 , +1,+1,-2,+0], - [+0,-1,+1,+0 , -2,+1,+1,+0], - [+1,+0,-1,+0 , +1,-2,+1,+0], - [-1,+2,-1,+0 , +1,+0,-1,+1], - [-2,+1,+1,+0 , +0,+1,-1,+1], - [-1,-1,+2,+0 , -1,+1,+0,+1], - [+1,-2,+1,+0 , -1,+0,+1,+1], - [+2,-1,-1,+0 , +0,-1,+1,+1], - [+1,+1,-2,+0 , +1,-1,+0,+1], - [-2,+1,+1,+3 , +1,+0,-1,+1], - [-1,-1,+2,+3 , +1,+0,-1,+1], - [-1,-1,+2,+3 , +0,+1,-1,+1], - [+1,-2,+1,+3 , +0,+1,-1,+1], - [+1,-2,+1,+3 , -1,+1,+0,+1], - [+2,-1,-1,+3 , -1,+1,+0,+1], - [+2,-1,-1,+3 , -1,+0,+1,+1], - [+1,+1,-2,+3 , -1,+0,+1,+1], - [+1,+1,-2,+3 , +0,-1,+1,+1], - [-1,+2,-1,+3 , +0,-1,+1,+1], - [-1,+2,-1,+3 , +1,-1,+0,+1], - [-2,+1,+1,+3 , +1,-1,+0,+1], - [-1,-1,+2,+3 , +1,+1,-2,+2], - [+1,-2,+1,+3 , -1,+2,-1,+2], - [+2,-1,-1,+3 , -2,+1,+1,+2], - [+1,+1,-2,+3 , -1,-1,+2,+2], - [-1,+2,-1,+3 , +1,-2,+1,+2], - [-2,+1,+1,+3 , +2,-1,-1,+2], - ],'d'), - 'twin' : _np.array([ - [-1, 0, 1, 1, 1, 0, -1, 2], # shear = (3-(c/a)^2)/(sqrt(3) c/a) <-10.1>{10.2} - [ 0, -1, 1, 1, 0, 1, -1, 2], - [ 1, -1, 0, 1, -1, 1, 0, 2], - [ 1, 0, -1, 1, -1, 0, 1, 2], - [ 0, 1, -1, 1, 0, -1, 1, 2], - [-1, 1, 0, 1, 1, -1, 0, 2], - [-1, -1, 2, 6, 1, 1, -2, 1], # shear = 1/(c/a) <11.6>{-1-1.1} - [ 1, -2, 1, 6, -1, 2, -1, 1], - [ 2, -1, -1, 6, -2, 1, 1, 1], - [ 1, 1, -2, 6, -1, -1, 2, 1], - [-1, 2, -1, 6, 1, -2, 1, 1], - [-2, 1, 1, 6, 2, -1, -1, 1], - [ 1, 0, -1, -2, 1, 0, -1, 1], # shear = (4(c/a)^2-9)/(4 sqrt(3) c/a) <10.-2>{10.1} - [ 0, 1, -1, -2, 0, 1, -1, 1], - [-1, 1, 0, -2, -1, 1, 0, 1], - [-1, 0, 1, -2, -1, 0, 1, 1], - [ 0, -1, 1, -2, 0, -1, 1, 1], - [ 1, -1, 0, -2, 1, -1, 0, 1], - [ 1, 1, -2, -3, 1, 1, -2, 2], # shear = 2((c/a)^2-2)/(3 c/a) <11.-3>{11.2} - [-1, 2, -1, -3, -1, 2, -1, 2], - [-2, 1, 1, -3, -2, 1, 1, 2], - [-1, -1, 2, -3, -1, -1, 2, 2], - [ 1, -2, 1, -3, 1, -2, 1, 2], - [ 2, -1, -1, -3, 2, -1, -1, 2], - ],dtype=float), - }, -} - -# 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 - -relations = { - 'KS': { - 'cF' : _np.array([ - [[ -1, 0, 1],[ 1, 1, 1]], - [[ -1, 0, 1],[ 1, 1, 1]], - [[ 0, 1, -1],[ 1, 1, 1]], - [[ 0, 1, -1],[ 1, 1, 1]], - [[ 1, -1, 0],[ 1, 1, 1]], - [[ 1, -1, 0],[ 1, 1, 1]], - [[ 1, 0, -1],[ 1, -1, 1]], - [[ 1, 0, -1],[ 1, -1, 1]], - [[ -1, -1, 0],[ 1, -1, 1]], - [[ -1, -1, 0],[ 1, -1, 1]], - [[ 0, 1, 1],[ 1, -1, 1]], - [[ 0, 1, 1],[ 1, -1, 1]], - [[ 0, -1, 1],[ -1, 1, 1]], - [[ 0, -1, 1],[ -1, 1, 1]], - [[ -1, 0, -1],[ -1, 1, 1]], - [[ -1, 0, -1],[ -1, 1, 1]], - [[ 1, 1, 0],[ -1, 1, 1]], - [[ 1, 1, 0],[ -1, 1, 1]], - [[ -1, 1, 0],[ 1, 1, -1]], - [[ -1, 1, 0],[ 1, 1, -1]], - [[ 0, -1, -1],[ 1, 1, -1]], - [[ 0, -1, -1],[ 1, 1, -1]], - [[ 1, 0, 1],[ 1, 1, -1]], - [[ 1, 0, 1],[ 1, 1, -1]], - ],dtype=float), - 'cI' : _np.array([ - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - [[ -1, -1, 1],[ 0, 1, 1]], - [[ -1, 1, -1],[ 0, 1, 1]], - ],dtype=float), - }, - 'GT': { - 'cF' : _np.array([ - [[ -5,-12, 17],[ 1, 1, 1]], - [[ 17, -5,-12],[ 1, 1, 1]], - [[-12, 17, -5],[ 1, 1, 1]], - [[ 5, 12, 17],[ -1, -1, 1]], - [[-17, 5,-12],[ -1, -1, 1]], - [[ 12,-17, -5],[ -1, -1, 1]], - [[ -5, 12,-17],[ -1, 1, 1]], - [[ 17, 5, 12],[ -1, 1, 1]], - [[-12,-17, 5],[ -1, 1, 1]], - [[ 5,-12,-17],[ 1, -1, 1]], - [[-17, -5, 12],[ 1, -1, 1]], - [[ 12, 17, 5],[ 1, -1, 1]], - [[ -5, 17,-12],[ 1, 1, 1]], - [[-12, -5, 17],[ 1, 1, 1]], - [[ 17,-12, -5],[ 1, 1, 1]], - [[ 5,-17,-12],[ -1, -1, 1]], - [[ 12, 5, 17],[ -1, -1, 1]], - [[-17, 12, -5],[ -1, -1, 1]], - [[ -5,-17, 12],[ -1, 1, 1]], - [[-12, 5,-17],[ -1, 1, 1]], - [[ 17, 12, 5],[ -1, 1, 1]], - [[ 5, 17, 12],[ 1, -1, 1]], - [[ 12, -5,-17],[ 1, -1, 1]], - [[-17,-12, 5],[ 1, -1, 1]], - ],dtype=float), - 'cI' : _np.array([ - [[-17, -7, 17],[ 1, 0, 1]], - [[ 17,-17, -7],[ 1, 1, 0]], - [[ -7, 17,-17],[ 0, 1, 1]], - [[ 17, 7, 17],[ -1, 0, 1]], - [[-17, 17, -7],[ -1, -1, 0]], - [[ 7,-17,-17],[ 0, -1, 1]], - [[-17, 7,-17],[ -1, 0, 1]], - [[ 17, 17, 7],[ -1, 1, 0]], - [[ -7,-17, 17],[ 0, 1, 1]], - [[ 17, -7,-17],[ 1, 0, 1]], - [[-17,-17, 7],[ 1, -1, 0]], - [[ 7, 17, 17],[ 0, -1, 1]], - [[-17, 17, -7],[ 1, 1, 0]], - [[ -7,-17, 17],[ 0, 1, 1]], - [[ 17, -7,-17],[ 1, 0, 1]], - [[ 17,-17, -7],[ -1, -1, 0]], - [[ 7, 17, 17],[ 0, -1, 1]], - [[-17, 7,-17],[ -1, 0, 1]], - [[-17,-17, 7],[ -1, 1, 0]], - [[ -7, 17,-17],[ 0, 1, 1]], - [[ 17, 7, 17],[ -1, 0, 1]], - [[ 17, 17, 7],[ 1, -1, 0]], - [[ 7,-17,-17],[ 0, -1, 1]], - [[-17, -7, 17],[ 1, 0, 1]], - ],dtype=float), - }, - 'GT_prime': { - 'cF' : _np.array([ - [[ 0, 1, -1],[ 7, 17, 17]], - [[ -1, 0, 1],[ 17, 7, 17]], - [[ 1, -1, 0],[ 17, 17, 7]], - [[ 0, -1, -1],[ -7,-17, 17]], - [[ 1, 0, 1],[-17, -7, 17]], - [[ 1, -1, 0],[-17,-17, 7]], - [[ 0, 1, -1],[ 7,-17,-17]], - [[ 1, 0, 1],[ 17, -7,-17]], - [[ -1, -1, 0],[ 17,-17, -7]], - [[ 0, -1, -1],[ -7, 17,-17]], - [[ -1, 0, 1],[-17, 7,-17]], - [[ -1, -1, 0],[-17, 17, -7]], - [[ 0, -1, 1],[ 7, 17, 17]], - [[ 1, 0, -1],[ 17, 7, 17]], - [[ -1, 1, 0],[ 17, 17, 7]], - [[ 0, 1, 1],[ -7,-17, 17]], - [[ -1, 0, -1],[-17, -7, 17]], - [[ -1, 1, 0],[-17,-17, 7]], - [[ 0, -1, 1],[ 7,-17,-17]], - [[ -1, 0, -1],[ 17, -7,-17]], - [[ 1, 1, 0],[ 17,-17, -7]], - [[ 0, 1, 1],[ -7, 17,-17]], - [[ 1, 0, -1],[-17, 7,-17]], - [[ 1, 1, 0],[-17, 17, -7]], - ],dtype=float), - 'cI' : _np.array([ - [[ 1, 1, -1],[ 12, 5, 17]], - [[ -1, 1, 1],[ 17, 12, 5]], - [[ 1, -1, 1],[ 5, 17, 12]], - [[ -1, -1, -1],[-12, -5, 17]], - [[ 1, -1, 1],[-17,-12, 5]], - [[ 1, -1, -1],[ -5,-17, 12]], - [[ -1, 1, -1],[ 12, -5,-17]], - [[ 1, 1, 1],[ 17,-12, -5]], - [[ -1, -1, 1],[ 5,-17,-12]], - [[ 1, -1, -1],[-12, 5,-17]], - [[ -1, -1, 1],[-17, 12, -5]], - [[ -1, -1, -1],[ -5, 17,-12]], - [[ 1, -1, 1],[ 12, 17, 5]], - [[ 1, 1, -1],[ 5, 12, 17]], - [[ -1, 1, 1],[ 17, 5, 12]], - [[ -1, 1, 1],[-12,-17, 5]], - [[ -1, -1, -1],[ -5,-12, 17]], - [[ -1, 1, -1],[-17, -5, 12]], - [[ -1, -1, 1],[ 12,-17, -5]], - [[ -1, 1, -1],[ 5,-12,-17]], - [[ 1, 1, 1],[ 17, -5,-12]], - [[ 1, 1, 1],[-12, 17, -5]], - [[ 1, -1, -1],[ -5, 12,-17]], - [[ 1, 1, -1],[-17, 5,-12]], - ],dtype=float), - }, - 'NW': { - 'cF' : _np.array([ - [[ 2, -1, -1],[ 1, 1, 1]], - [[ -1, 2, -1],[ 1, 1, 1]], - [[ -1, -1, 2],[ 1, 1, 1]], - [[ -2, -1, -1],[ -1, 1, 1]], - [[ 1, 2, -1],[ -1, 1, 1]], - [[ 1, -1, 2],[ -1, 1, 1]], - [[ 2, 1, -1],[ 1, -1, 1]], - [[ -1, -2, -1],[ 1, -1, 1]], - [[ -1, 1, 2],[ 1, -1, 1]], - [[ 2, -1, 1],[ -1, -1, 1]], - [[ -1, 2, 1],[ -1, -1, 1]], - [[ -1, -1, -2],[ -1, -1, 1]], - ],dtype=float), - 'cI' : _np.array([ - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - [[ 0, -1, 1],[ 0, 1, 1]], - ],dtype=float), - }, - 'Pitsch': { - 'cF' : _np.array([ - [[ 1, 0, 1],[ 0, 1, 0]], - [[ 1, 1, 0],[ 0, 0, 1]], - [[ 0, 1, 1],[ 1, 0, 0]], - [[ 0, 1, -1],[ 1, 0, 0]], - [[ -1, 0, 1],[ 0, 1, 0]], - [[ 1, -1, 0],[ 0, 0, 1]], - [[ 1, 0, -1],[ 0, 1, 0]], - [[ -1, 1, 0],[ 0, 0, 1]], - [[ 0, -1, 1],[ 1, 0, 0]], - [[ 0, 1, 1],[ 1, 0, 0]], - [[ 1, 0, 1],[ 0, 1, 0]], - [[ 1, 1, 0],[ 0, 0, 1]], - ],dtype=float), - 'cI' : _np.array([ - [[ 1, -1, 1],[ -1, 0, 1]], - [[ 1, 1, -1],[ 1, -1, 0]], - [[ -1, 1, 1],[ 0, 1, -1]], - [[ -1, 1, -1],[ 0, -1, -1]], - [[ -1, -1, 1],[ -1, 0, -1]], - [[ 1, -1, -1],[ -1, -1, 0]], - [[ 1, -1, -1],[ -1, 0, -1]], - [[ -1, 1, -1],[ -1, -1, 0]], - [[ -1, -1, 1],[ 0, -1, -1]], - [[ -1, 1, 1],[ 0, -1, 1]], - [[ 1, -1, 1],[ 1, 0, -1]], - [[ 1, 1, -1],[ -1, 1, 0]], - ],dtype=float), - }, - 'Bain': { - 'cF' : _np.array([ - [[ 0, 1, 0],[ 1, 0, 0]], - [[ 0, 0, 1],[ 0, 1, 0]], - [[ 1, 0, 0],[ 0, 0, 1]], - ],dtype=float), - 'cI' : _np.array([ - [[ 0, 1, 1],[ 1, 0, 0]], - [[ 1, 0, 1],[ 0, 1, 0]], - [[ 1, 1, 0],[ 0, 0, 1]], - ],dtype=float), - }, - 'Burgers' : { - 'cI' : _np.array([ - [[ -1, 1, 1],[ 1, 1, 0]], - [[ -1, 1, -1],[ 1, 1, 0]], - [[ 1, 1, 1],[ 1, -1, 0]], - [[ 1, 1, -1],[ 1, -1, 0]], - - [[ 1, 1, -1],[ 1, 0, 1]], - [[ -1, 1, 1],[ 1, 0, 1]], - [[ 1, 1, 1],[ -1, 0, 1]], - [[ 1, -1, 1],[ -1, 0, 1]], - - [[ -1, 1, -1],[ 0, 1, 1]], - [[ 1, 1, -1],[ 0, 1, 1]], - [[ -1, 1, 1],[ 0, -1, 1]], - [[ 1, 1, 1],[ 0, -1, 1]], - ],dtype=float), - 'hP' : _np.array([ - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - [[ -1, 2, -1, 0],[ 0, 0, 0, 1]], - [[ -1, -1, 2, 0],[ 0, 0, 0, 1]], - ],dtype=float), - }, -} diff --git a/python/damask/util.py b/python/damask/util.py index ab204905b..d59b41b16 100644 --- a/python/damask/util.py +++ b/python/damask/util.py @@ -27,6 +27,7 @@ __all__=[ 'execution_stamp', 'shapeshifter', 'shapeblender', 'extend_docstring', 'extended_docstring', + 'Bravais_to_Miller', 'Miller_to_Bravais', 'DREAM3D_base_group', 'DREAM3D_cell_data_group', 'dict_prune', 'dict_flatten' ] @@ -286,6 +287,8 @@ def project_stereographic(vector,direction='z',normalize=True,keepdims=False): Examples -------- + >>> import damask + >>> import numpy as np >>> project_stereographic(np.ones(3)) [0.3660254, 0.3660254] >>> project_stereographic(np.ones(3),direction='x',normalize=False,keepdims=True) @@ -338,7 +341,7 @@ def hybrid_IA(dist,N,rng_seed=None): def shapeshifter(fro,to,mode='left',keep_ones=False): """ - Return a tuple that reshapes 'fro' to become broadcastable to 'to'. + Return dimensions that reshape 'fro' to become broadcastable to 'to'. Parameters ---------- @@ -355,6 +358,22 @@ def shapeshifter(fro,to,mode='left',keep_ones=False): Treat '1' in fro as literal value instead of dimensional placeholder. Defaults to False. + Returns + ------- + new_dims : tuple + Dimensions for reshape. + + Example + ------- + >>> import numpy as np + >>> from damask import util + >>> a = np.ones((3,4,2)) + >>> b = np.ones(4) + >>> b_extended = b.reshape(util.shapeshifter(b.shape,a.shape)) + >>> (a * np.broadcast_to(b_extended,a.shape)).shape + (3,4,2) + + """ beg = dict(left ='(^.*\\b)', right='(^.*?\\b)') @@ -499,6 +518,62 @@ def DREAM3D_cell_data_group(fname): return cell_data_group +def Bravais_to_Miller(*,uvtw=None,hkil=None): + """ + Transform 4 Miller–Bravais indices to 3 Miller indices of crystal direction [uvw] or plane normal (hkl). + + Parameters + ---------- + uvtw|hkil : numpy.ndarray of shape (...,4) + Miller–Bravais indices of crystallographic direction [uvtw] or plane normal (hkil). + + Returns + ------- + uvw|hkl : numpy.ndarray of shape (...,3) + Miller indices of [uvw] direction or (hkl) plane normal. + + """ + if (uvtw is not None) ^ (hkil is None): + raise KeyError('Specify either "uvtw" or "hkil"') + axis,basis = (np.array(uvtw),np.array([[1,0,-1,0], + [0,1,-1,0], + [0,0, 0,1]])) \ + if hkil is None else \ + (np.array(hkil),np.array([[1,0,0,0], + [0,1,0,0], + [0,0,0,1]])) + return np.einsum('il,...l',basis,axis) + + +def Miller_to_Bravais(*,uvw=None,hkl=None): + """ + Transform 3 Miller indices to 4 Miller–Bravais indices of crystal direction [uvtw] or plane normal (hkil). + + Parameters + ---------- + uvw|hkl : numpy.ndarray of shape (...,3) + Miller indices of crystallographic direction [uvw] or plane normal (hkl). + + Returns + ------- + uvtw|hkil : numpy.ndarray of shape (...,4) + Miller–Bravais indices of [uvtw] direction or (hkil) plane normal. + + """ + if (uvw is not None) ^ (hkl is None): + raise KeyError('Specify either "uvw" or "hkl"') + axis,basis = (np.array(uvw),np.array([[ 2,-1, 0], + [-1, 2, 0], + [-1,-1, 0], + [ 0, 0, 3]])/3) \ + if hkl is None else \ + (np.array(hkl),np.array([[ 1, 0, 0], + [ 0, 1, 0], + [-1,-1, 0], + [ 0, 0, 1]])) + return np.einsum('il,...l',basis,axis) + + def dict_prune(d): """ Recursively remove empty dictionaries. diff --git a/python/tests/test_Crystal.py b/python/tests/test_Crystal.py new file mode 100644 index 000000000..5481003c9 --- /dev/null +++ b/python/tests/test_Crystal.py @@ -0,0 +1,57 @@ +import pytest +import numpy as np + +from damask import Crystal + +class TestCrystal: + + def test_double_to_lattice(self): + c = Crystal(lattice='cF') + with pytest.raises(KeyError): + c.to_lattice(direction=np.ones(3),plane=np.ones(3)) + + def test_double_to_frame(self): + c = Crystal(lattice='cF') + with pytest.raises(KeyError): + c.to_frame(uvw=np.ones(3),hkl=np.ones(3)) + + @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', + [ + ('aP',0.5,2.0,3.0,0.8,0.5,1.2), + ('mP',1.0,2.0,3.0,np.pi/2,0.5,np.pi/2), + ('oI',0.5,1.5,3.0,np.pi/2,np.pi/2,np.pi/2), + ('tP',0.5,0.5,3.0,np.pi/2,np.pi/2,np.pi/2), + ('hP',1.0,None,1.6,np.pi/2,np.pi/2,2*np.pi/3), + ('cF',1.0,1.0,None,np.pi/2,np.pi/2,np.pi/2), + ]) + def test_bases_contraction(self,lattice,a,b,c,alpha,beta,gamma): + c = Crystal(lattice=lattice, + a=a,b=b,c=c, + alpha=alpha,beta=beta,gamma=gamma) + assert np.allclose(np.eye(3),np.einsum('ik,jk',c.basis_real,c.basis_reciprocal)) + + + @pytest.mark.parametrize('keyFrame,keyLattice',[('uvw','direction'),('hkl','plane'),]) + @pytest.mark.parametrize('vector',np.array([ + [1.,1.,1.], + [-2.,3.,0.5], + [0.,0.,1.], + [1.,1.,1.], + [2.,2.,2.], + [0.,1.,1.], + ])) + @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', + [ + ('aP',0.5,2.0,3.0,0.8,0.5,1.2), + ('mP',1.0,2.0,3.0,np.pi/2,0.5,np.pi/2), + ('oI',0.5,1.5,3.0,np.pi/2,np.pi/2,np.pi/2), + ('tP',0.5,0.5,3.0,np.pi/2,np.pi/2,np.pi/2), + ('hP',1.0,1.0,1.6,np.pi/2,np.pi/2,2*np.pi/3), + ('cF',1.0,1.0,1.0,np.pi/2,np.pi/2,np.pi/2), + ]) + def test_to_frame_to_lattice(self,lattice,a,b,c,alpha,beta,gamma,vector,keyFrame,keyLattice): + c = Crystal(lattice=lattice, + a=a,b=b,c=c, + alpha=alpha,beta=beta,gamma=gamma) + assert np.allclose(vector, + c.to_frame(**{keyFrame:c.to_lattice(**{keyLattice:vector})})) diff --git a/python/tests/test_Orientation.py b/python/tests/test_Orientation.py index 63caca34e..0c735c20f 100644 --- a/python/tests/test_Orientation.py +++ b/python/tests/test_Orientation.py @@ -7,7 +7,6 @@ from damask import Orientation from damask import Table from damask import util from damask import grid_filters -from damask import lattice from damask import _orientation crystal_families = set(_orientation.lattice_symmetries.values()) @@ -25,38 +24,42 @@ def set_of_rodrigues(set_of_quaternions): class TestOrientation: - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('shape',[None,5,(4,6)]) - def test_equal(self,lattice,shape): + def test_equal(self,family,shape): R = Rotation.from_random(shape) - assert Orientation(R,lattice) == Orientation(R,lattice) if shape is None else \ - (Orientation(R,lattice) == Orientation(R,lattice)).all() + assert Orientation(R,family=family) == Orientation(R,family=family) if shape is None else \ + (Orientation(R,family=family) == Orientation(R,family=family)).all() - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('shape',[None,5,(4,6)]) - def test_unequal(self,lattice,shape): + def test_unequal(self,family,shape): R = Rotation.from_random(shape) - assert not ( Orientation(R,lattice) != Orientation(R,lattice) if shape is None else \ - (Orientation(R,lattice) != Orientation(R,lattice)).any()) + assert not ( Orientation(R,family=family) != Orientation(R,family=family) if shape is None else \ + (Orientation(R,family=family) != Orientation(R,family=family)).any()) - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('shape',[None,5,(4,6)]) - def test_close(self,lattice,shape): - R = Orientation.from_random(lattice=lattice,shape=shape) + def test_close(self,family,shape): + R = Orientation.from_random(family=family,shape=shape) assert R.isclose(R.reduced).all() and R.allclose(R.reduced) @pytest.mark.parametrize('a,b',[ - (dict(rotation=[1,0,0,0],lattice='triclinic'), - dict(rotation=[0.5,0.5,0.5,0.5],lattice='triclinic')), + (dict(rotation=[1,0,0,0],family='triclinic'), + dict(rotation=[0.5,0.5,0.5,0.5],family='triclinic')), - (dict(rotation=[1,0,0,0],lattice='cubic'), - dict(rotation=[1,0,0,0],lattice='hexagonal')), + (dict(rotation=[1,0,0,0],family='cubic'), + dict(rotation=[1,0,0,0],family='hexagonal')), + ]) + def test_unequal_family(self,a,b): + assert Orientation(**a) != Orientation(**b) + @pytest.mark.parametrize('a,b',[ (dict(rotation=[1,0,0,0],lattice='cF',a=1), dict(rotation=[1,0,0,0],lattice='cF',a=2)), ]) - def test_nonequal(self,a,b): + def test_unequal_lattice(self,a,b): assert Orientation(**a) != Orientation(**b) @pytest.mark.parametrize('kwargs',[ @@ -72,7 +75,7 @@ class TestOrientation: ]) def test_invalid_init(self,kwargs): with pytest.raises(ValueError): - Orientation(**kwargs).parameters # noqa + Orientation(**kwargs) @pytest.mark.parametrize('kwargs',[ dict(lattice='aP',a=1.0,b=1.1,c=1.2,alpha=np.pi/4,beta=np.pi/3,gamma=np.pi/2), @@ -100,47 +103,47 @@ class TestOrientation: assert o != p def test_from_quaternion(self): - assert np.all(Orientation.from_quaternion(q=np.array([1,0,0,0]),lattice='triclinic').as_matrix() + assert np.all(Orientation.from_quaternion(q=np.array([1,0,0,0]),family='triclinic').as_matrix() == np.eye(3)) def test_from_Euler_angles(self): - assert np.all(Orientation.from_Euler_angles(phi=np.zeros(3),lattice='triclinic').as_matrix() + assert np.all(Orientation.from_Euler_angles(phi=np.zeros(3),family='triclinic').as_matrix() == np.eye(3)) def test_from_axis_angle(self): - assert np.all(Orientation.from_axis_angle(axis_angle=[1,0,0,0],lattice='triclinic').as_matrix() + assert np.all(Orientation.from_axis_angle(axis_angle=[1,0,0,0],family='triclinic').as_matrix() == np.eye(3)) def test_from_basis(self): - assert np.all(Orientation.from_basis(basis=np.eye(3),lattice='triclinic').as_matrix() + assert np.all(Orientation.from_basis(basis=np.eye(3),family='triclinic').as_matrix() == np.eye(3)) def test_from_matrix(self): - assert np.all(Orientation.from_matrix(R=np.eye(3),lattice='triclinic').as_matrix() + assert np.all(Orientation.from_matrix(R=np.eye(3),family='triclinic').as_matrix() == np.eye(3)) def test_from_Rodrigues_vector(self): - assert np.all(Orientation.from_Rodrigues_vector(rho=np.array([0,0,1,0]),lattice='triclinic').as_matrix() + assert np.all(Orientation.from_Rodrigues_vector(rho=np.array([0,0,1,0]),family='triclinic').as_matrix() == np.eye(3)) def test_from_homochoric(self): - assert np.all(Orientation.from_homochoric(h=np.zeros(3),lattice='triclinic').as_matrix() + assert np.all(Orientation.from_homochoric(h=np.zeros(3),family='triclinic').as_matrix() == np.eye(3)) def test_from_cubochoric(self): - assert np.all(Orientation.from_cubochoric(x=np.zeros(3),lattice='triclinic').as_matrix() + assert np.all(Orientation.from_cubochoric(x=np.zeros(3),family='triclinic').as_matrix() == np.eye(3)) def test_from_spherical_component(self): assert np.all(Orientation.from_spherical_component(center=Rotation(), - sigma=0.0,N=1,lattice='triclinic').as_matrix() + sigma=0.0,N=1,family='triclinic').as_matrix() == np.eye(3)) def test_from_fiber_component(self): r = Rotation.from_fiber_component(alpha=np.zeros(2),beta=np.zeros(2), sigma=0.0,N=1,rng_seed=0) assert np.all(Orientation.from_fiber_component(alpha=np.zeros(2),beta=np.zeros(2), - sigma=0.0,N=1,rng_seed=0,lattice='triclinic').quaternion + sigma=0.0,N=1,rng_seed=0,family='triclinic').quaternion == r.quaternion) @pytest.mark.parametrize('kwargs',[ @@ -185,26 +188,26 @@ class TestOrientation: with pytest.raises(ValueError): Orientation(lattice='aP',a=1,b=2,c=3,alpha=45,beta=45,gamma=90.0001,degrees=True) # noqa - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('angle',[10,20,30,40]) - def test_average(self,angle,lattice): - o = Orientation.from_axis_angle(lattice=lattice,axis_angle=[[0,0,1,10],[0,0,1,angle]],degrees=True) + def test_average(self,angle,family): + o = Orientation.from_axis_angle(family=family,axis_angle=[[0,0,1,10],[0,0,1,angle]],degrees=True) avg_angle = o.average().as_axis_angle(degrees=True,pair=True)[1] assert np.isclose(avg_angle,10+(angle-10)/2.) - @pytest.mark.parametrize('lattice',crystal_families) - def test_reduced_equivalent(self,lattice): - i = Orientation(lattice=lattice) - o = Orientation.from_random(lattice=lattice) + @pytest.mark.parametrize('family',crystal_families) + def test_reduced_equivalent(self,family): + i = Orientation(family=family) + o = Orientation.from_random(family=family) eq = o.equivalent FZ = np.argmin(abs(eq.misorientation(i.broadcast_to(len(eq))).as_axis_angle(pair=True)[1])) assert o.reduced == eq[FZ] - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('N',[1,8,32]) - def test_disorientation(self,lattice,N): - o = Orientation.from_random(lattice=lattice,shape=N) - p = Orientation.from_random(lattice=lattice,shape=N) + def test_disorientation(self,family,N): + o = Orientation.from_random(family=family,shape=N) + p = Orientation.from_random(family=family,shape=N) d,ops = o.disorientation(p,return_operators=True) @@ -218,72 +221,72 @@ class TestOrientation: .misorientation(p[n].equivalent[ops[n][1]]) .as_quaternion()) - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('a,b',[ ((2,3,2),(2,3,2)), ((2,2),(4,4)), ((3,1),(1,3)), (None,None), ]) - def test_disorientation_blending(self,lattice,a,b): - o = Orientation.from_random(lattice=lattice,shape=a) - p = Orientation.from_random(lattice=lattice,shape=b) + def test_disorientation_blending(self,family,a,b): + o = Orientation.from_random(family=family,shape=a) + p = Orientation.from_random(family=family,shape=b) blend = util.shapeblender(o.shape,p.shape) for loc in np.random.randint(0,blend,(10,len(blend))): assert o[tuple(loc[:len(o.shape)])].disorientation(p[tuple(loc[-len(p.shape):])]) \ .isclose(o.disorientation(p)[tuple(loc)]) - @pytest.mark.parametrize('lattice',crystal_families) - def test_disorientation360(self,lattice): - o_1 = Orientation(Rotation(),lattice) - o_2 = Orientation.from_Euler_angles(lattice=lattice,phi=[360,0,0],degrees=True) + @pytest.mark.parametrize('family',crystal_families) + def test_disorientation360(self,family): + o_1 = Orientation(Rotation(),family=family) + o_2 = Orientation.from_Euler_angles(family=family,phi=[360,0,0],degrees=True) assert np.allclose((o_1.disorientation(o_2)).as_matrix(),np.eye(3)) - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('shape',[(1),(2,3),(4,3,2)]) - def test_reduced_vectorization(self,lattice,shape): - o = Orientation.from_random(lattice=lattice,shape=shape) + def test_reduced_vectorization(self,family,shape): + o = Orientation.from_random(family=family,shape=shape) for r, theO in zip(o.reduced.flatten(),o.flatten()): assert r == theO.reduced - @pytest.mark.parametrize('lattice',crystal_families) - def test_reduced_corner_cases(self,lattice): + @pytest.mark.parametrize('family',crystal_families) + def test_reduced_corner_cases(self,family): # test whether there is always a sym-eq rotation that falls into the FZ N = np.random.randint(10,40) size = np.ones(3)*np.pi**(2./3.) grid = grid_filters.coordinates0_node([N+1,N+1,N+1],size,-size*.5) - evenly_distributed = Orientation.from_cubochoric(x=grid[:-2,:-2,:-2],lattice=lattice) + evenly_distributed = Orientation.from_cubochoric(x=grid[:-2,:-2,:-2],family=family) assert evenly_distributed.shape == evenly_distributed.reduced.shape - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('shape',[(1),(2,3),(4,3,2)]) @pytest.mark.parametrize('vector',np.array([[1,0,0],[1,2,3],[-1,1,-1]])) @pytest.mark.parametrize('proper',[True,False]) - def test_to_SST_vectorization(self,lattice,shape,vector,proper): - o = Orientation.from_random(lattice=lattice,shape=shape) + def test_to_SST_vectorization(self,family,shape,vector,proper): + o = Orientation.from_random(family=family,shape=shape) for r, theO in zip(o.to_SST(vector=vector,proper=proper).reshape((-1,3)),o.flatten()): assert np.allclose(r,theO.to_SST(vector=vector,proper=proper)) - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('shape',[(1),(2,3),(4,3,2)]) @pytest.mark.parametrize('vector',np.array([[1,0,0],[1,2,3],[-1,1,-1]])) @pytest.mark.parametrize('proper',[True,False]) @pytest.mark.parametrize('in_SST',[True,False]) - def test_IPF_color_vectorization(self,lattice,shape,vector,proper,in_SST): - o = Orientation.from_random(lattice=lattice,shape=shape) + def test_IPF_color_vectorization(self,family,shape,vector,proper,in_SST): + o = Orientation.from_random(family=family,shape=shape) for r, theO in zip(o.IPF_color(vector,in_SST=in_SST,proper=proper).reshape((-1,3)),o.flatten()): assert np.allclose(r,theO.IPF_color(vector,in_SST=in_SST,proper=proper)) - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('a,b',[ ((2,3,2),(2,3,2)), ((2,2),(4,4)), ((3,1),(1,3)), (None,(3,)), ]) - def test_to_SST_blending(self,lattice,a,b): - o = Orientation.from_random(lattice=lattice,shape=a) + def test_to_SST_blending(self,family,a,b): + o = Orientation.from_random(family=family,shape=a) v = np.random.random(b+(3,)) blend = util.shapeblender(o.shape,b) for loc in np.random.randint(0,blend,(10,len(blend))): @@ -298,75 +301,45 @@ class TestOrientation: {'label':'blue', 'RGB':[0,0,1],'direction':[1,1,1]}]) @pytest.mark.parametrize('proper',[True,False]) def test_IPF_cubic(self,color,proper): - cube = Orientation(lattice='cubic') + cube = Orientation(family='cubic') for direction in set(permutations(np.array(color['direction']))): assert np.allclose(np.array(color['RGB']), cube.IPF_color(vector=np.array(direction),proper=proper)) - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('proper',[True,False]) - def test_IPF_equivalent(self,set_of_quaternions,lattice,proper): + def test_IPF_equivalent(self,set_of_quaternions,family,proper): direction = np.random.random(3)*2.0-1.0 - o = Orientation(rotation=set_of_quaternions,lattice=lattice).equivalent + o = Orientation(rotation=set_of_quaternions,family=family).equivalent color = o.IPF_color(vector=direction,proper=proper) assert np.allclose(np.broadcast_to(color[0,...],color.shape),color) - @pytest.mark.parametrize('lattice',crystal_families) - def test_in_FZ_vectorization(self,set_of_rodrigues,lattice): - result = Orientation.from_Rodrigues_vector(rho=set_of_rodrigues.reshape((-1,4,4)),lattice=lattice).in_FZ.reshape(-1) + @pytest.mark.parametrize('family',crystal_families) + def test_in_FZ_vectorization(self,set_of_rodrigues,family): + result = Orientation.from_Rodrigues_vector(rho=set_of_rodrigues.reshape((-1,4,4)),family=family).in_FZ.reshape(-1) for r,rho in zip(result,set_of_rodrigues[:len(result)]): - assert r == Orientation.from_Rodrigues_vector(rho=rho,lattice=lattice).in_FZ + assert r == Orientation.from_Rodrigues_vector(rho=rho,family=family).in_FZ - @pytest.mark.parametrize('lattice',crystal_families) - def test_in_disorientation_FZ_vectorization(self,set_of_rodrigues,lattice): + @pytest.mark.parametrize('family',crystal_families) + def test_in_disorientation_FZ_vectorization(self,set_of_rodrigues,family): result = Orientation.from_Rodrigues_vector(rho=set_of_rodrigues.reshape((-1,4,4)), - lattice=lattice).in_disorientation_FZ.reshape(-1) + family=family).in_disorientation_FZ.reshape(-1) for r,rho in zip(result,set_of_rodrigues[:len(result)]): - assert r == Orientation.from_Rodrigues_vector(rho=rho,lattice=lattice).in_disorientation_FZ + assert r == Orientation.from_Rodrigues_vector(rho=rho,family=family).in_disorientation_FZ @pytest.mark.parametrize('proper',[True,False]) - @pytest.mark.parametrize('lattice',crystal_families) - def test_in_SST_vectorization(self,lattice,proper): + @pytest.mark.parametrize('family',crystal_families) + def test_in_SST_vectorization(self,family,proper): vecs = np.random.rand(20,4,3) - result = Orientation(lattice=lattice).in_SST(vecs,proper).flatten() + result = Orientation(family=family).in_SST(vecs,proper).flatten() for r,v in zip(result,vecs.reshape((-1,3))): - assert np.all(r == Orientation(lattice=lattice).in_SST(v,proper)) - - @pytest.mark.parametrize('invalid_lattice',['fcc','bcc','hello']) - def test_invalid_lattice_init(self,invalid_lattice): - with pytest.raises(KeyError): - Orientation(lattice=invalid_lattice) # noqa + assert np.all(r == Orientation(family=family).in_SST(v,proper)) @pytest.mark.parametrize('invalid_family',[None,'fcc','bcc','hello']) - def test_invalid_symmetry_family(self,invalid_family): + def test_invalid_lattice_init(self,invalid_family): with pytest.raises(KeyError): - o = Orientation(lattice='cubic') - o.family = invalid_family - o.symmetry_operations # noqa + Orientation(family=invalid_family) - def test_invalid_rot(self): - with pytest.raises(TypeError): - Orientation.from_random(lattice='cubic') * np.ones(3) - - def test_missing_symmetry_immutable(self): - with pytest.raises(KeyError): - Orientation(lattice=None).immutable # noqa - - def test_missing_symmetry_basis_real(self): - with pytest.raises(KeyError): - Orientation(lattice=None).basis_real # noqa - - def test_missing_symmetry_basis_reciprocal(self): - with pytest.raises(KeyError): - Orientation(lattice=None).basis_reciprocal # noqa - - def test_double_to_lattice(self): - with pytest.raises(KeyError): - Orientation().to_lattice(direction=np.ones(3),plane=np.ones(3)) # noqa - - def test_double_to_frame(self): - with pytest.raises(KeyError): - Orientation().to_frame(uvw=np.ones(3),hkl=np.ones(3)) # noqa @pytest.mark.parametrize('relation',[None,'Peter','Paul']) def test_unknown_relation(self,relation): @@ -388,28 +361,22 @@ class TestOrientation: a=a,b=b,c=c, alpha=alpha,beta=beta,gamma=gamma).related(relation) # noqa - @pytest.mark.parametrize('lattice',crystal_families) + @pytest.mark.parametrize('family',crystal_families) @pytest.mark.parametrize('proper',[True,False]) - def test_in_SST(self,lattice,proper): - assert Orientation(lattice=lattice).in_SST(np.zeros(3),proper) + def test_in_SST(self,family,proper): + assert Orientation(family=family).in_SST(np.zeros(3),proper) @pytest.mark.parametrize('function',['in_SST','IPF_color']) def test_invalid_argument(self,function): - o = Orientation(lattice='cubic') # noqa + o = Orientation(family='cubic') # noqa with pytest.raises(ValueError): eval(f'o.{function}(np.ones(4))') - - @pytest.mark.parametrize('model',lattice.relations) - def test_relationship_definition(self,model): - m,o = list(lattice.relations[model]) - assert lattice.relations[model][m].shape[:-1] == lattice.relations[model][o].shape[:-1] - @pytest.mark.parametrize('model',['Bain','KS','GT','GT_prime','NW','Pitsch']) @pytest.mark.parametrize('lattice',['cF','cI']) def test_relationship_vectorize(self,set_of_quaternions,lattice,model): r = Orientation(rotation=set_of_quaternions[:200].reshape((50,4,4)),lattice=lattice).related(model) for i in range(200): - assert (r.reshape((-1,200))[:,i] == Orientation(set_of_quaternions[i],lattice).related(model)).all() + assert (r.reshape((-1,200))[:,i] == Orientation(set_of_quaternions[i],lattice=lattice).related(model)).all() @pytest.mark.parametrize('model',['Bain','KS','GT','GT_prime','NW','Pitsch']) @pytest.mark.parametrize('lattice',['cF','cI']) @@ -444,45 +411,6 @@ class TestOrientation: ) assert np.allclose(o.to_frame(uvw=np.eye(3)),basis), 'Lattice basis disagrees with initialization' - @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', - [ - ('aP',0.5,2.0,3.0,0.8,0.5,1.2), - ('mP',1.0,2.0,3.0,np.pi/2,0.5,np.pi/2), - ('oI',0.5,1.5,3.0,np.pi/2,np.pi/2,np.pi/2), - ('tP',0.5,0.5,3.0,np.pi/2,np.pi/2,np.pi/2), - ('hP',1.0,None,1.6,np.pi/2,np.pi/2,2*np.pi/3), - ('cF',1.0,1.0,None,np.pi/2,np.pi/2,np.pi/2), - ]) - def test_bases_contraction(self,lattice,a,b,c,alpha,beta,gamma): - L = Orientation(lattice=lattice, - a=a,b=b,c=c, - alpha=alpha,beta=beta,gamma=gamma) - assert np.allclose(np.eye(3),np.einsum('ik,jk',L.basis_real,L.basis_reciprocal)) - - @pytest.mark.parametrize('keyFrame,keyLattice',[('uvw','direction'),('hkl','plane'),]) - @pytest.mark.parametrize('vector',np.array([ - [1.,1.,1.], - [-2.,3.,0.5], - [0.,0.,1.], - [1.,1.,1.], - [2.,2.,2.], - [0.,1.,1.], - ])) - @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', - [ - ('aP',0.5,2.0,3.0,0.8,0.5,1.2), - ('mP',1.0,2.0,3.0,np.pi/2,0.5,np.pi/2), - ('oI',0.5,1.5,3.0,np.pi/2,np.pi/2,np.pi/2), - ('tP',0.5,0.5,3.0,np.pi/2,np.pi/2,np.pi/2), - ('hP',1.0,1.0,1.6,np.pi/2,np.pi/2,2*np.pi/3), - ('cF',1.0,1.0,1.0,np.pi/2,np.pi/2,np.pi/2), - ]) - def test_to_frame_to_lattice(self,lattice,a,b,c,alpha,beta,gamma,vector,keyFrame,keyLattice): - L = Orientation(lattice=lattice, - a=a,b=b,c=c, - alpha=alpha,beta=beta,gamma=gamma) - assert np.allclose(vector, - L.to_frame(**{keyFrame:L.to_lattice(**{keyLattice:vector})})) @pytest.mark.parametrize('lattice,a,b,c,alpha,beta,gamma', [ @@ -510,13 +438,22 @@ class TestOrientation: assert o.to_pole(**{kw:vector,'with_symmetry':with_symmetry}).shape \ == o.shape + (o.symmetry_operations.shape if with_symmetry else ()) + vector.shape - @pytest.mark.parametrize('lattice',['hP','cI','cF']) - @pytest.mark.parametrize('mode',['slip','twin']) - def test_Schmid(self,update,ref_path,lattice,mode): - L = Orientation(lattice=lattice) - reference = ref_path/f'{lattice}_{mode}.txt' - P = L.Schmid(mode) - if update: - table = Table(P.reshape(-1,9),{'Schmid':(3,3,)}) - table.save(reference) - assert np.allclose(P,Table.load(reference).get('Schmid')) + @pytest.mark.parametrize('lattice',['hP','cI','cF']) #tI not included yet + def test_Schmid(self,update,ref_path,lattice): + O = Orientation(lattice=lattice) # noqa + for mode in ['slip','twin']: + reference = ref_path/f'{lattice}_{mode}.txt' + P = O.Schmid(N_slip='*') if mode == 'slip' else O.Schmid(N_twin='*') + if update: + table = Table(P.reshape(-1,9),{'Schmid':(3,3,)}) + table.save(reference) + assert np.allclose(P,Table.load(reference).get('Schmid')) + + @pytest.mark.parametrize('lattice',['hP','cI','cF']) # tI not included yet + def test_Schmid_vectorize(self,lattice): + O = Orientation.from_random(shape=4,lattice=lattice) # noqa + for mode in ['slip','twin']: + Ps = O.Schmid(N_slip='*') if mode == 'slip' else O.Schmid(N_twin='*') + for i in range(4): + P = O[i].Schmid(N_slip='*') if mode == 'slip' else O[i].Schmid(N_twin='*') + assert np.allclose(P,Ps[:,i]) diff --git a/python/tests/test_Result.py b/python/tests/test_Result.py index 5f543861d..7126b4ab0 100644 --- a/python/tests/test_Result.py +++ b/python/tests/test_Result.py @@ -12,7 +12,6 @@ import vtk import numpy as np from damask import Result -from damask import Rotation from damask import Orientation from damask import tensor from damask import mechanics @@ -220,17 +219,15 @@ class TestResult: in_file = default.place('S') assert np.allclose(in_memory,in_file) - @pytest.mark.skip(reason='requires rework of lattice.f90') - @pytest.mark.parametrize('polar',[True,False]) - def test_add_pole(self,default,polar): - pole = np.array([1.,0.,0.]) - default.add_pole('O',pole,polar) - rot = Rotation(default.place('O')) - rotated_pole = rot * np.broadcast_to(pole,rot.shape+(3,)) - xy = rotated_pole[:,0:2]/(1.+abs(pole[2])) - in_memory = xy if not polar else \ - np.block([np.sqrt(xy[:,0:1]*xy[:,0:1]+xy[:,1:2]*xy[:,1:2]),np.arctan2(xy[:,1:2],xy[:,0:1])]) - in_file = default.place('p^{}_[1 0 0)'.format(u'rφ' if polar else 'xy')) + @pytest.mark.parametrize('options',[{'uvw':[1,0,0]},{'hkl':[0,1,1]}]) + def test_add_pole(self,default,options): + default.add_pole(**options) + rot = default.place('O') + in_memory = Orientation(rot,lattice=rot.dtype.metadata['lattice']).to_pole(**options) + brackets = ['[[]','[]]'] if 'uvw' in options.keys() else ['(',')'] # escape fnmatch + label = '{}{} {} {}{}'.format(brackets[0],*(list(options.values())[0]),brackets[1]) + in_file = default.place(f'p^{label}') + print(in_file - in_memory) assert np.allclose(in_memory,in_file) def test_add_rotation(self,default): diff --git a/python/tests/test_lattice.py b/python/tests/test_lattice.py deleted file mode 100644 index 4060025a1..000000000 --- a/python/tests/test_lattice.py +++ /dev/null @@ -1,34 +0,0 @@ -import pytest -import numpy as np - -from damask import lattice - -class TestLattice: - - def test_double_Bravais_to_Miller(self): - with pytest.raises(KeyError): - lattice.Bravais_to_Miller(uvtw=np.ones(4),hkil=np.ones(4)) # noqa - - def test_double_Miller_to_Bravais(self): - with pytest.raises(KeyError): - lattice.Miller_to_Bravais(uvw=np.ones(4),hkl=np.ones(4)) # noqa - - @pytest.mark.parametrize('vector',np.array([ - [1,0,0], - [1,1,0], - [1,1,1], - [1,0,-2], - ])) - @pytest.mark.parametrize('kw_Miller,kw_Bravais',[('uvw','uvtw'),('hkl','hkil')]) - def test_Miller_Bravais_Miller(self,vector,kw_Miller,kw_Bravais): - assert np.all(vector == lattice.Bravais_to_Miller(**{kw_Bravais:lattice.Miller_to_Bravais(**{kw_Miller:vector})})) - - @pytest.mark.parametrize('vector',np.array([ - [1,0,-1,2], - [1,-1,0,3], - [1,1,-2,-3], - [0,0,0,1], - ])) - @pytest.mark.parametrize('kw_Miller,kw_Bravais',[('uvw','uvtw'),('hkl','hkil')]) - def test_Bravais_Miller_Bravais(self,vector,kw_Miller,kw_Bravais): - assert np.all(vector == lattice.Miller_to_Bravais(**{kw_Miller:lattice.Bravais_to_Miller(**{kw_Bravais:vector})})) diff --git a/python/tests/test_util.py b/python/tests/test_util.py index 3ed3bf908..9af609ad0 100644 --- a/python/tests/test_util.py +++ b/python/tests/test_util.py @@ -158,3 +158,33 @@ class TestUtil: ({'A':{'B':{},'C':'D'}}, {'B':{},'C':'D'})]) def test_flatten(self,full,reduced): assert util.dict_flatten(full) == reduced + + + def test_double_Bravais_to_Miller(self): + with pytest.raises(KeyError): + util.Bravais_to_Miller(uvtw=np.ones(4),hkil=np.ones(4)) + + def test_double_Miller_to_Bravais(self): + with pytest.raises(KeyError): + util.Miller_to_Bravais(uvw=np.ones(4),hkl=np.ones(4)) + + + @pytest.mark.parametrize('vector',np.array([ + [1,0,0], + [1,1,0], + [1,1,1], + [1,0,-2], + ])) + @pytest.mark.parametrize('kw_Miller,kw_Bravais',[('uvw','uvtw'),('hkl','hkil')]) + def test_Miller_Bravais_Miller(self,vector,kw_Miller,kw_Bravais): + assert np.all(vector == util.Bravais_to_Miller(**{kw_Bravais:util.Miller_to_Bravais(**{kw_Miller:vector})})) + + @pytest.mark.parametrize('vector',np.array([ + [1,0,-1,2], + [1,-1,0,3], + [1,1,-2,-3], + [0,0,0,1], + ])) + @pytest.mark.parametrize('kw_Miller,kw_Bravais',[('uvw','uvtw'),('hkl','hkil')]) + def test_Bravais_Miller_Bravais(self,vector,kw_Miller,kw_Bravais): + assert np.all(vector == util.Miller_to_Bravais(**{kw_Miller:util.Bravais_to_Miller(**{kw_Bravais:vector})}))