from typing import Optional, Union, Dict, List, Tuple, Literal
import numpy as np
from ._typehints import FloatSequence, IntSequence, CrystalFamily, BravaisLattice, CrystalKinematics
from . import util
from . import Rotation
_kinematics: Dict[BravaisLattice, Dict[CrystalKinematics, List[np.ndarray]]] = {
'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, +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]]),
np.array([
[+1,-1,+1, +2,+1,-1],
[+1,-1,+1, -1,+1,+2],
[+1,-1,+1, +1,+2,+1],
[-1,-1,+1, +2,-1,+1],
[-1,-1,+1, +1,+1,+2],
[-1,-1,+1, -1,+2,+1],
[+1,+1,+1, +1,+1,-2],
[+1,+1,+1, +1,-2,+1],
[+1,+1,+1, -2,+1,+1],
[-1,+1,+1, +1,-1,+2],
[-1,+1,+1, +1,+2,-1],
[-1,+1,+1, +2,+1,+1]]),
np.array([
[+1,-1,+1, -1,+2,+3],
[+1,-1,+1, +1,+3,+2],
[+1,-1,+1, -2,+1,+3],
[+1,-1,+1, +2,+3,+1],
[+1,-1,+1, +3,+1,-2],
[+1,-1,+1, +3,+2,-1],
[-1,-1,+1, +1,+2,+3],
[-1,-1,+1, -1,+3,+2],
[-1,-1,+1, +2,+1,+3],
[-1,-1,+1, -2,+3,+1],
[-1,-1,+1, +3,-1,+2],
[-1,-1,+1, +3,-2,+1],
[+1,+1,+1, +1,+2,-3],
[+1,+1,+1, +1,-3,+2],
[+1,+1,+1, +2,+1,-3],
[+1,+1,+1, +2,-3,+1],
[+1,+1,+1, -3,+1,+2],
[+1,+1,+1, -3,+2,+1],
[-1,+1,+1, +1,-2, 3],
[-1,+1,+1, +1,+3,-2],
[-1,+1,+1, +2,-1,+3],
[-1,+1,+1, +2,+3,-1],
[-1,+1,+1, +3,+1,+2],
[-1,+1,+1, +3,+2,+1]])],
'twin': [np.array([
[+1,-1,+1, +2,+1,-1],
[+1,-1,+1, -1,+1,+2],
[+1,-1,+1, +1,+2,+1],
[-1,-1,+1, +2,-1,+1],
[-1,-1,+1, +1,+1,+2],
[-1,-1,+1, -1,+2,+1],
[+1,+1,+1, +1,+1,-2],
[+1,+1,+1, +1,-2,+1],
[+1,+1,+1, -2,+1,+1],
[-1,+1,+1, +1,-1,+2],
[-1,+1,+1, +1,+2,-1],
[-1,+1,+1, +2,+1,+1]])]
},
'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,+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], # <-10.1>{10.2}; shear = (3-(c/a)^2)/(sqrt(3) c/a)
[ 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], # <11.6>{-1-1.1}; shear = 1/(c/a)
[ 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], # <10.-2>{10.1}; shear = (9-4(c/a)^2)/(4 sqrt(3) c/a)
[ 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], # <11.-3>{11.2}; shear = 2(2-(c/a)^2)/(3 c/a)
[-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]])]
},
'tI': {
'slip': [np.array([
[ 0, 0,+1, +1, 0, 0],
[ 0, 0,+1, 0,+1, 0]]),
np.array([
[ 0, 0,+1, +1,+1, 0],
[ 0, 0,+1, -1,+1, 0]]),
np.array([
[ 0,+1, 0, +1, 0, 0],
[+1, 0, 0, 0,+1, 0]]),
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]]),
np.array([
[+1,-1, 0, +1,+1, 0],
[+1,+1, 0, +1,-1, 0]]),
np.array([
[ 0,+1,+1, +1, 0, 0],
[ 0,-1,+1, +1, 0, 0],
[-1, 0,+1, 0,+1, 0],
[+1, 0,+1, 0,+1, 0]]),
np.array([
[ 0,+1, 0, 0, 0,+1],
[+1, 0, 0, 0, 0,+1]]),
np.array([
[+1,+1, 0, 0, 0,+1],
[-1,+1, 0, 0, 0,+1]]),
np.array([
[ 0,+1,-1, 0,+1,+1],
[ 0,-1,-1, 0,-1,+1],
[-1, 0,-1, -1, 0,+1],
[+1, 0,-1, +1, 0,+1]]),
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]]),
np.array([
[+1, 0, 0, 0,+1,+1],
[+1, 0, 0, 0,+1,-1],
[ 0,+1, 0, +1, 0,+1],
[ 0,+1, 0, +1, 0,-1]]),
np.array([
[ 0,+1,-1, +2,+1,+1],
[ 0,-1,-1, +2,-1,+1],
[+1, 0,-1, +1,+2,+1],
[-1, 0,-1, -1,+2,+1],
[ 0,+1,-1, -2,+1,+1],
[ 0,-1,-1, -2,-1,+1],
[-1, 0,-1, -1,-2,+1],
[+1, 0,-1, +1,-2,+1]]),
np.array([
[-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, -2,+1,+1],
[+1,-1,+1, -2,-1,+1],
[-1,+1,+1, -1,-2,+1],
[+1,+1,+1, +1,-2,+1]])]
}
}
lattice_symmetries: Dict[Optional[BravaisLattice], CrystalFamily] = {
'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',
}
orientation_relationships: Dict[str, Dict[str,List[np.ndarray]]] = {
'KS': { # https://doi.org/10.1016/j.jallcom.2012.02.004
'cF-->cI' : [
np.repeat(np.array([
[[-1, 0, 1],[ 1, 1, 1]],
[[ 0, 1,-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],[-1, 1, 1]],
[[-1, 1, 0],[ 1, 1,-1]],
[[ 0,-1,-1],[ 1, 1,-1]],
[[ 1, 0, 1],[ 1, 1,-1]],
]),
2,axis=0),
np.tile(np.array([[[-1,-1, 1],[ 0, 1, 1]],
[[-1, 1,-1],[ 0, 1, 1]]]),
(12,1,1)),
],
'cI-->cF' : [
np.repeat(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]],
]),
2,axis=0),
np.tile(np.array([[[ 0, 1,-1],[ 1, 1, 1]],
[[ 0,-1, 1],[ 1, 1, 1]]]),
(12,1,1)),
],
},
'GT': { # https://doi.org/10.1107/S0021889805038276
'cF-->cI' : [
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]],
]),
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]],
]),
],
'cI-->cF' : [
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]],
]),
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]],
]),
],
},
'GT_prime': { # https://doi.org/10.1107/S0021889805038276
'cF-->cI' : [
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]],
]),
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]],
]),
],
'cI-->cF' : [
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]],
]),
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]],
]),
],
},
'NW': { # https://doi.org/10.1016/j.matchar.2004.12.015
'cF-->cI' : [
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]],
]),
np.broadcast_to(np.array([[ 0,-1, 1],[ 0, 1, 1]]),
(12,2,3)),
],
'cI-->cF' : [
np.repeat(np.array([
[[ 0, 1,-1],[ 0, 1, 1]],
[[ 0, 1, 1],[ 0, 1,-1]],
[[ 1, 0,-1],[ 1, 0, 1]],
[[ 1, 0, 1],[ 1, 0,-1]],
[[ 1,-1, 0],[ 1, 1, 0]],
[[ 1, 1, 0],[ 1,-1, 0]],
]),
2,axis=0),
np.tile(np.array([
[[ 2,-1,-1],[ 1, 1, 1]],
[[-2, 1, 1],[ 1, 1, 1]],
]),
(6,1,1)),
],
},
'Pitsch': { # https://doi.org/10.1080/14786435908238253
'cF-->cI' : [
np.repeat(np.array([
[[ 0, 1, 1],[ 1, 0, 0]],
[[ 0, 1,-1],[ 1, 0, 0]],
[[ 1, 0, 1],[ 0, 1, 0]],
[[ 1, 0,-1],[ 0, 1, 0]],
[[ 1, 1, 0],[ 0, 0, 1]],
[[ 1,-1, 0],[ 0, 0, 1]],
]),
2,axis=0),
np.tile(np.array([
[[ 1, 1,-1],[ 0, 1, 1]],
[[-1, 1,-1],[ 0, 1, 1]],
]),
(6,1,1)),
],
'cI-->cF' : [
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.broadcast_to(np.array([[ 1, 1, 0],[ 0, 0, 1]]),
(12,2,3)),
],
},
'Bain': { # https://doi.org/10.1107/S0021889805038276
'cF-->cI' : [
np.array([
[[ 0, 1, 0],[ 1, 0, 0]],
[[ 0, 0, 1],[ 0, 1, 0]],
[[ 1, 0, 0],[ 0, 0, 1]],
]),
np.broadcast_to(np.array([[ 1, 1, 0],[ 0, 0, 1]]),
(3,2,3)),
],
'cI-->cF' : [
np.array([
[[ 0, 1, 1],[ 1, 0, 0]],
[[ 1, 0, 1],[ 0, 1, 0]],
[[ 1, 1, 0],[ 0, 0, 1]],
]),
np.broadcast_to(np.array([[ 1, 0, 0],[ 0, 0, 1]]),
(3,2,3)),
]
},
'Burgers' : { # https://doi.org/10.1016/S0031-8914(34)80244-3
'cI-->hP' : [
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.broadcast_to(np.array([[ 2,-1,-1, 0],[ 0, 0, 0, 1]]),
(12,2,4)),
],
'hP-->cI' : [
np.repeat(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,axis=0),
np.tile(np.array([
[[ 1, 1,-1],[ 0, 1, 1]],
[[-1, 1,-1],[ 0, 1, 1]],
]),
(3,1,1)),
]
},
}
[docs]
class Crystal():
"""
Representation of a crystal as (general) crystal family or (more specific) as a scaled Bravais lattice.
Examples
--------
Cubic crystal family:
>>> import damask
>>> (cubic := damask.Crystal(family='cubic'))
Crystal family: cubic
Body-centered cubic Bravais lattice with parameters of iron:
>>> import damask
>>> (Fe := damask.Crystal(lattice='cI', a=287e-12))
Crystal family: cubic
Bravais lattice: cI
a=2.87e-10 m, b=2.87e-10 m, c=2.87e-10 m
α=90°, β=90°, γ=90°
"""
def __init__(self, *,
family: Optional[CrystalFamily] = None,
lattice: Optional[BravaisLattice] = None,
a: Optional[float] = None, b: Optional[float] = None, c: Optional[float] = None,
alpha: Optional[float] = None, beta: Optional[float] = None, gamma: Optional[float] = None,
degrees: bool = False):
"""
New representation of a crystal.
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 is not None and family not in 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')
def __repr__(self):
"""
Return repr(self).
Give short, human-readable summary.
"""
family = f'Crystal family: {self.family}'
return family if self.lattice is None else \
util.srepr([family,
f'Bravais lattice: {self.lattice}',
'a={a:.5g} m, b={b:.5g} m, c={c:.5g} m'.format(**self.parameters),
'α={alpha:.5g}°, β={beta:.5g}°, γ={gamma:.5g}°'
.format(**dict(map(lambda kv: (kv[0], np.degrees(kv[1])), self.parameters.items()))),
])
def __eq__(self,
other: object) -> bool:
"""
Return self==other.
Test equality of other.
Parameters
----------
other : Crystal
Crystal to check for equality.
"""
return (NotImplemented if not isinstance(other, Crystal) else
self.lattice == other.lattice and
self.parameters == other.parameters and
self.family == other.family) # type: ignore
@property
def parameters(self) -> Optional[Dict]:
"""Return lattice parameters a, b, c, alpha, beta, gamma."""
has_parameters = all([hasattr(self,p) for p in ['a','b','c','alpha','beta','gamma']])
return dict(a=self.a,b=self.b,c=self.c,
alpha=self.alpha,beta=self.beta,gamma=self.gamma) if has_parameters else None
@property
def immutable(self) -> Dict[str, float]:
"""Return immutable lattice parameters."""
# ToDo: use pattern matching in Python 3.10
_immutable: Dict[CrystalFamily, Dict[str,float]] = {
'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 orientation_relationships(self) -> List[str]:
"""Return labels of orientation relationships."""
return [k for k,v in orientation_relationships.items() if np.any([m.startswith(str(self.lattice)) for m in v])]
@property
def standard_triangle(self) -> Union[Dict[str, np.ndarray], None]:
"""
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: Dict[CrystalFamily, Dict[str, np.ndarray]] = {
'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 symmetry_operations(self) -> Rotation:
"""
Return symmetry operations.
Notes
-----
The symmetry operations defined here only consider Rotations.
More specifically, for each crystal family, an enantiomorphic
point symmetry is selected. In case that there are multiple
point groups with enantiomorphic point symmetry, the one with
the highest order is chosen:
Overview of crystal classes and point group in Hermann-Mauguin
notation used for definition of symmetry operations.
- tricinic: 1
- monoclinic: 2
- orthorhombic: 222
- tetragonal: 422
- hexagonal: 622
- cubic: 432
References
----------
U.F. Kocks et al.,
Texture and Anisotropy: Preferred Orientations in Polycrystals
and their Effect on Materials Properties.
Cambridge University Press 1998. Table II
https://en.wikipedia.org/wiki/Crystal_system#Crystal_classes
"""
_symmetry_operations: Dict[CrystalFamily, List] = {
'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 ],
], # 432
'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 ],
], # 622
'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) ],
], # 422
'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 ],
], # 222
'monoclinic': [
[ 1.0,0.0,0.0,0.0 ],
[ 0.0,0.0,1.0,0.0 ],
], # 2
'triclinic': [
[ 1.0,0.0,0.0,0.0 ],
]} # 1
return Rotation.from_quaternion(_symmetry_operations[self.family],accept_homomorph=True)
@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) -> np.ndarray:
"""
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 (p := self.parameters) is not None:
return np.array([
[1,0,0],
[np.cos(p['gamma']),np.sin(p['gamma']),0],
[np.cos(p['beta']),
(np.cos(p['alpha'])-np.cos(p['beta'])*np.cos(p['gamma'])) /np.sin(p['gamma']),
np.sqrt(1 - np.cos(p['alpha'])**2 - np.cos(p['beta'])**2 - np.cos(p['gamma'])**2
+ 2 * np.cos(p['alpha']) * np.cos(p['beta']) * np.cos(p['gamma']))/np.sin(p['gamma'])],
]).T \
* np.array([p['a'],p['b'],p['c']])
else:
raise KeyError('missing crystal lattice parameters')
@property
def basis_reciprocal(self) -> np.ndarray:
"""Return reciprocal (dual) crystal basis."""
return np.linalg.inv(self.basis_real.T)
@property
def lattice_points(self) -> np.ndarray:
"""Return lattice points."""
_lattice_points: Dict[str, List] = {
'P': [
],
'S': [
[0.5,0.5,0],
],
'I': [
[0.5,0.5,0.5],
],
'F': [
[0.0,0.5,0.5],
[0.5,0.0,0.5],
[0.5,0.5,0.0],
],
'hP': [
[2./3.,1./3.,0.5],
],
}
if self.lattice is None: raise KeyError('no lattice type specified')
return np.array([[0,0,0]]
+ _lattice_points.get(self.lattice if self.lattice == 'hP' else
self.lattice[-1],[]),dtype=float)
[docs]
def to_lattice(self, *,
direction: Optional[FloatSequence] = None,
plane: Optional[FloatSequence] = None) -> np.ndarray:
"""
Calculate lattice vector corresponding to crystal frame direction or plane normal.
Parameters
----------
direction|plane : numpy.ndarray, shape (...,3)
Real space vector along direction or
reciprocal space vector along plane normal.
Returns
-------
Miller : numpy.ndarray, 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"')
basis,axis = (self.basis_reciprocal,np.asarray(direction)) \
if plane is None else \
(self.basis_real,np.asarray(plane))
return np.einsum('li,...l',basis,axis)
[docs]
def to_frame(self, *,
uvw: Optional[IntSequence] = None,
hkl: Optional[IntSequence] = None,
uvtw: Optional[IntSequence] = None,
hkil: Optional[IntSequence] = None) -> np.ndarray:
"""
Calculate crystal frame vector corresponding to lattice direction [uvw]/[uvtw] or plane normal (hkl)/(hkil).
Parameters
----------
uvw|hkl|uvtw|hkil : numpy.ndarray, shape (...,3) or shape (...,4)
Miller(–Bravais) indices of crystallographic direction or plane normal.
Returns
-------
vector : numpy.ndarray, shape (...,3)
Crystal frame vector in real space along [uvw]/[uvtw] direction or
in reciprocal space along (hkl)/(hkil) plane normal.
Examples
--------
Crystal frame vector (real space) of Magnesium corresponding to [1,1,0] direction:
>>> import damask
>>> Mg = damask.Crystal(lattice='hP', a=321e-12, c=521e-12)
>>> Mg.to_frame(uvw=[1, 1, 0])
array([1.60500000e-10, 2.77994155e-10, 0.00000000e+00])
Crystal frame vector (reciprocal space) of Titanium along (1,0,0) plane normal:
>>> import damask
>>> Ti = damask.Crystal(lattice='hP', a=295e-12, c=469e-12)
>>> Ti.to_frame(hkl=(1, 0, 0))
array([ 3.38983051e+09, 1.95711956e+09, -4.15134508e-07])
"""
if sum(arg is not None for arg in (uvw,hkl, uvtw,hkil)) != 1:
raise KeyError('specify either "uvw", "hkl", "uvtw", or "hkil"')
basis,axis = (self.basis_real,np.asarray(uvw if uvtw is None else util.Bravais_to_Miller(uvtw=uvtw))) \
if hkl is None and hkil is None else \
(self.basis_reciprocal,np.asarray(hkl if hkil is None else util.Bravais_to_Miller(hkil=hkil)))
return np.einsum('il,...l',basis,axis)
[docs]
def kinematics(self,
mode: CrystalKinematics) -> Dict[str, List[np.ndarray]]:
"""
Return crystal kinematics systems.
Parameters
----------
mode : {'slip','twin'}
Deformation mode.
Returns
-------
direction_plane : dictionary
Directions and planes of deformation mode families.
Notes
-----
Kinematics of slip systems are bidirectional, i.e. the
shape change equals the kinematics multiplied by the (signed) shear.
In contrast, twin kinematics are unidirectional and already consider
the distinction between extension and compression twins such that the
shape change equals the kinematics multiplied by (absolute) twin shear.
"""
if self.lattice is None: raise KeyError('no lattice type specified')
master = _kinematics[self.lattice][mode]
kinematics = {'direction':[util.Bravais_to_Miller(uvtw=m[:,0:4]) if self.lattice == 'hP'
else m[:,0:3] for m in master],
'plane': [util.Bravais_to_Miller(hkil=m[:,4:8]) if self.lattice == 'hP'
else m[:,3:6] for m in master]}
if mode == 'twin':
gamma_char = self.characteristic_shear_twin()
kinematics['plane'] = [np.sign(gamma_char[i]).astype(int).reshape(-1,1)*k
for i,k in enumerate(kinematics['plane'])]
return kinematics
[docs]
def characteristic_shear_twin(self,
N_twin: Union[List[int], Literal['*']] = '*') -> np.ndarray:
"""
Return characteristic shear for twinning.
A positive value indicates a tension twin, a negative value a
compression twin.
Parameters
----------
N_twin : '*' or sequence of int
Number of twin systems per twin family.
Use '*' to select all.
Returns
-------
s : numpy.ndarray, shape (...)
Characteristic shear for twinning.
References
----------
J.W. Christian and S. Mahajan, Progress in Materials Science 39(1-2):1-157, 1995
https://doi.org/10.1016/0079-6425(94)00007-7
"""
if self.lattice in ['cI', 'cF']:
N_twin_ = [len(a) for a in _kinematics[self.lattice]['twin']] if N_twin == '*' else N_twin
return np.array([[0.5*np.sqrt(2.0)]*N_twin_[0]])
elif self.lattice == 'hP':
N_twin_ = [len(a) for a in _kinematics[self.lattice]['twin']] if N_twin == '*' else N_twin
c_a = self.c/self.a #type: ignore
return np.array([[(3.0-c_a**2)/np.sqrt(3.0)/c_a]*N_twin_[0],
[1.0/c_a]*N_twin_[1],
[(9.0-4.0*c_a**2)/np.sqrt(48.0)/c_a]*N_twin_[2],
[2.0*(2.0-c_a**2)/3.0/c_a]*N_twin_[3]]
)
else:
raise KeyError(f'twin systems not defined for lattice "{self.lattice}"')
[docs]
def relation_operations(self,
model: str,
target = None) -> Tuple[BravaisLattice, Rotation]:
"""
Crystallographic orientation relationships for phase transformations.
Parameters
----------
model : str
Name of orientation relationship.
target : Crystal, optional
Crystal to transform to.
Providing this parameter allows specification of non-standard lattice parameters.
Default is inferred from selected model and uses standard lattice parameters.
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
"""
m_l: BravaisLattice
o_l: BravaisLattice
if model not in self.orientation_relationships:
raise KeyError(f'unknown orientation relationship "{model}"')
sep = '-->'
search = self.lattice+sep+('' if target is None else target.lattice) # type: ignore
transform = [t for t in orientation_relationships[model].keys() if t.startswith(search)] # type: ignore
if len(transform) != 1:
raise ValueError(f'invalid target lattice "{search.split(sep)[1]}"')
m_l,o_l = transform[0].split(sep) # type: ignore
m_p,o_p = orientation_relationships[model][m_l+sep+o_l]
m = Crystal(lattice=m_l) if self.parameters is None else Crystal(lattice=m_l,**self.parameters) # type: ignore
o = Crystal(lattice=o_l) if target is None else target
m_p = np.stack((m.to_frame(uvw=m_p[:,0] if m_l != 'hP' else util.Bravais_to_Miller(uvtw=m_p[:,0])),
m.to_frame(hkl=m_p[:,1] if m_l != 'hP' else util.Bravais_to_Miller(hkil=m_p[:,1]))),
axis=-2)
o_p = np.stack((o.to_frame(uvw=o_p[:,0] if o_l != 'hP' else util.Bravais_to_Miller(uvtw=o_p[:,0])),
o.to_frame(hkl=o_p[:,1] if o_l != 'hP' else util.Bravais_to_Miller(hkil=o_p[:,1]))),
axis=-2)
return (o_l,Rotation.from_parallel(source=m_p,target=o_p,active=True))
[docs]
def Schmid(self, *,
N_slip: Optional[Union[IntSequence, Literal['*']]] = None,
N_twin: Optional[Union[IntSequence, Literal['*']]] = None) -> np.ndarray:
u"""
Calculate Schmid matrix P = d ⨂ n for selected deformation systems.
Parameters
----------
N_slip|N_twin : '*' or sequence of int
Number of deformation systems per family of the deformation system.
Use '*' to select all.
Returns
-------
P : numpy.ndarray, shape (N,3,3)
Schmid matrix for each of the N deformation systems.
Examples
--------
Schmid matrix of first octahedral slip system of a face-centered
cubic crystal.
>>> import numpy as np
>>> import damask
>>> C = damask.Crystal(lattice='cF')
>>> C.Schmid(N_slip=[12])[0]
array([[ 0. , 0. , 0. ],
[ 0.4082, 0.4082, 0.4082],
[-0.4082, -0.4082, -0.4082]])
"""
if (N_slip is not None) ^ (N_twin is None):
raise KeyError('specify either "N_slip" or "N_twin"')
kinematics,active = (self.kinematics('slip'),N_slip) if N_twin is None else \
(self.kinematics('twin'),N_twin)
everylen = list(map(len,kinematics['direction']))
if active == '*': active = everylen
if not active or (np.array(active) > everylen[:len(active)]).any():
raise ValueError('Invalid number of slip/twin systems')
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)]))
return np.einsum('...i,...j',d/np.linalg.norm(d,axis=-1,keepdims=True),
p/np.linalg.norm(p,axis=-1,keepdims=True))