3. How to find the CSL/DSC lattices of a $\Sigma$-rotation using GBpy:

First we need to import all the required packages that we need for completing this tutorial.

In [1]:
import GBpy
import numpy as np
import os
import inspect
import pickle

3.1. Finding the $\Sigma$-rotations:

While $\Sigma$-rotations are widely known and used in crystallography they are not trivial to build. Therefore we have computed a wide range of $\Sigma$-rotations and have included them inside the GBpy package in a separate folder named pkl_files. If you have installed the GBpy package using the pip install GBpy command or if you have cloned the GBpy github repository on Github you have received the pkl_files folder. To access the pkl_files folder, you need to know where the GBpy package resides on your system. The following steps will help you navigate through the GBpy package folder structure. If you already have the rotation matrix (3x3 matrix) associated with the $\Sigma$-rotation igonre the following steps.

In [2]:
gbpy_dir = os.path.dirname((inspect.getfile(GBpy)))
In [3]:
print gbpy_dir
/media/valuedc/lin/prog/github/GBpy/GBpy

The variable gbpy_dir is a string contaning the path to where GBpy resides on your system. pkl_files is a subfolder of the gbpy_dir. You can get the list of the files that are included in the pkl_files folder using the following command:

In [4]:
pkl_dir = gbpy_dir + '/pkl_files'
In [5]:
os.listdir(pkl_dir)
Out[5]:
['__init__.py',
 'symm_quats_Cs.pkl',
 'symm_quats_Oh.pkl',
 'symm_quats_D6.pkl',
 'symm_mats_D4h.pkl',
 'symm_mats_D6.pkl',
 'hP_Id_csl_common_rotations.pkl',
 'symm_mats_C2h.pkl',
 'hR_Id_csl_common_rotations.pkl',
 'symm_mats_D3d.pkl',
 'symm_mats_D6h.pkl',
 'symm_quats_O.pkl',
 'symm_mats_D2h.pkl',
 'cF_Id_csl_common_rotations.pkl',
 'cI_Id_csl_common_rotations.pkl',
 'symm_mats_D8h.pkl',
 'symm_mats_O.pkl',
 '__init__.pyc',
 'symm_mats_Cs.pkl',
 'tP_Id_exp_csl_common_rotations.pkl',
 'symm_quats_D4.pkl',
 'symm_quats_C2h.pkl',
 'symm_quats_D8h.pkl',
 'symm_mats_Oh.pkl',
 'tP_ca_csl_specific_tau_1_9_rotations.pkl',
 'symm_quats_D6h.pkl',
 'symm_quats_D3.pkl',
 'tP_Id_csl_common_rotations.pkl']

The common $\Sigma$-rotations of cubic lattices are saved in a file named "cF_Id_csl_common_rotations.pkl". You can find $\Sigma$-rotations of other lattices in the same folder similarly (i.e. hexagnal(hP), tetragonal(tP), etc.), however in this tutorial the focus is on face-centered cubic (f.c.c) lattice. To access the contents of "cF_Id_csl_common_rotations.pkl" use the following commands:

In [6]:
pkl_file = pkl_dir + '/cF_Id_csl_common_rotations.pkl'
pkl_content = pickle.load(open(pkl_file))

The variable "pkl_content" contains first 200 common $\Sigma$-rotations of cubic lattices, stored as a dictionary data structure. Each entry in the dictionary can be access by specifying the key to that entry. The key for each sigma rotation is a string with the $\Sigma$-number. We would like to emphasize that the key to each entry has to be a string. For instance for $\Sigma$3 you need to pass '3' to the pkl_content as the key.

It is very important to note that all the rotation matrices are expressed in primitive bases of the associated lattices. To use the rotation matrices in an orthogonal basis one needs to perform similarity transformation on the provided rotation matrices. We will demonstrate an example of such transformation later in this tutorial. Luckily for cubic lattices the $\Sigma$-rotations remain constant in both primitive and orthogonal bases.

To avoid the truncation error we have stored the Numerator (saved as 'N') and the Denominator (saved as 'D') of each $\Sigma$-rotation separately. To construct the rotation matrix associated with each $\Sigma$-rotation you need to divide the elements of N by elements of D. The result will be a 3x3 rotation matrix which will be a numpy array. The following steps shows how you can construct the rotation matrix associated with the $\Sigma$3 rotation:

In [7]:
pkl_content['3']
Out[7]:
{'D': array([[[ 3.,  3.,  3.],
         [ 3.,  3.,  3.],
         [ 3.,  3.,  3.]]]), 'N': array([[[ 2., -1.,  2.],
         [ 2.,  2., -1.],
         [-1.,  2.,  2.]]])}
In [8]:
N3 = pkl_content['3']['N'][0]
D3 = pkl_content['3']['D'][0]
Sigma3 = N3/D3
print Sigma3
[[ 0.66666667 -0.33333333  0.66666667]
 [ 0.66666667  0.66666667 -0.33333333]
 [-0.33333333  0.66666667  0.66666667]]

Note that some of the Sigmas have more than one Sigma rotation. For instance $\Sigma$17 has two $\Sigma$-rotations often referred to as $\Sigma$17-a and $\Sigma$17-b. In such cases you will find two N matrices and two D matrices associated with each rotation. Use the following to access each N and D matrix.

In [9]:
pkl_content['17']
Out[9]:
{'D': array([[[ 17.,  17.,  17.],
         [ 17.,  17.,  17.],
         [ 17.,  17.,  17.]],
 
        [[ 17.,  17.,  17.],
         [ 17.,  17.,  17.],
         [ 17.,  17.,  17.]]]), 'N': array([[[ 15.,  -5.,   3.],
         [  8.,  20.,   5.],
         [ -8.,  -3.,  12.]],
 
        [[  7., -11.,   7.],
         [ 14.,  12.,  -3.],
         [ -3.,  12.,  14.]]])}
In [10]:
N17_a = pkl_content['17']['N'][0]
D17_a = pkl_content['17']['D'][0]
Sigma17_a = N17_a/D17_a
print 'Sigma17_a =', Sigma17_a, '\n'

N17_b = pkl_content['17']['N'][1]
D17_b = pkl_content['17']['D'][1]
Sigma17_b = N17_b/D17_b
print 'Sigma17_b =', Sigma17_b,
Sigma17_a = [[ 0.88235294 -0.29411765  0.17647059]
 [ 0.47058824  1.17647059  0.29411765]
 [-0.47058824 -0.17647059  0.70588235]] 

Sigma17_b = [[ 0.41176471 -0.64705882  0.41176471]
 [ 0.82352941  0.70588235 -0.17647059]
 [-0.17647059  0.70588235  0.82352941]]

3.2. Changing the Basis of a Transformation Matrix

So far we have been able to obtain the rotation matrix associated with the $\Sigma$3 rotation which is stored in the variable Sigma3. As mentioned in section 1.1, it is very important to recognize that the obtained rotation matrices are expressed in primitive basis of the lattice. To use the rotation matrices in an orthogonal basis one needs to perform similarity transformation on the provided rotation matrices.

By convention we use the p for primitive basis and po for orthogonal basis of a lattice.

If we define the transformation matrix of the $\Sigma$3 (that transforms crystal $p_{1}$ to $p_{2}$) in the primitive basis as $T_{p_{1} \rightarrow p_{2}}^{p}$, same transformation in orthogonal basis of the lattice (e.g. supercell lattice) will be defined as $T_{p_{1} \rightarrow p_{2}}^{po}$ and can be found using the similarity transformation as:

$T_{p_{1} \rightarrow p_{2}}^{po} = \Lambda_{p}^{po} \times T_{p_{1} \rightarrow p_{2}}^{p} \times \Lambda_{po}^{p}$

where $\Lambda_{p}^{po}$ is a 3x3 matrix which defines the components of the basis of the primitve lattice in an orthogonal reference frame attached to the same lattice. Therefore, $\Lambda_{p}^{po}$ is a constant for each lattice. $\Lambda_{p}^{po}$ for various lattices can be obtained from the lattice class in GBpy. For example, the components of a primitive f.c.c. lattice with lattice parameter $a = 1.0 \unicode{x212B} $, $\Lambda_{p}^{po}$, is defined as:

In [11]:
L_p_po = 1.0*np.array([[0.,0.5,0.5],[0.5,0.,0.5],[0.5,0.5,0.]])
L_p_po
Out[11]:
array([[ 0. ,  0.5,  0.5],
       [ 0.5,  0. ,  0.5],
       [ 0.5,  0.5,  0. ]])

and $\Lambda_{p}^{po}$ is the inverse of $\Lambda_{po}^{p}$:

In [12]:
L_po_p = np.linalg.inv(L_p_po)
L_po_p
Out[12]:
array([[-1.,  1.,  1.],
       [ 1., -1.,  1.],
       [ 1.,  1., -1.]])

Therefore using similarity transformation, $T_{p_{1} \rightarrow p_{2}}^{po}$ can be obtained as:

In [13]:
T_p1top2_po = np.dot(L_p_po, np.dot(Sigma3, L_po_p)).reshape(1,3,3)[0]
T_p1top2_po
Out[13]:
array([[ 0.66666667, -0.33333333,  0.66666667],
       [ 0.66666667,  0.66666667, -0.33333333],
       [-0.33333333,  0.66666667,  0.66666667]])

As mentioned in section 1.1. it can be observed that $T_{p_{1} \rightarrow p_{2}}^{po}$ and $T_{p_{1} \rightarrow p_{2}}^{p}$ are the same for cubic lattices.


3.3. Finding the CSL and DSC Lattices of a $\Sigma$3 rotation:

To compute the CSL and DSC lattices, GBpy only requires the $T_{p_{1}\rightarrow p_{2}}^{p}$ that can be directly accessed from the provided pkl files. It automatically performs the similarity transformation using the $\Lambda_{p}^{po}$ that can be obtained from the lattice class.

We have stored the $\Lambda_{p}^{po}$ in L_p_po and $T_{p_{1}\rightarrow p_{2}}^{p}$ in Sigma3 that are shown in the following:

In [14]:
L_p_po
Out[14]:
array([[ 0. ,  0.5,  0.5],
       [ 0.5,  0. ,  0.5],
       [ 0.5,  0.5,  0. ]])
In [15]:
Sigma3
Out[15]:
array([[ 0.66666667, -0.33333333,  0.66666667],
       [ 0.66666667,  0.66666667, -0.33333333],
       [-0.33333333,  0.66666667,  0.66666667]])

To find the CSL and DSC lattices the user should use the function find_csl_dsc. This function is available in a file with the same name. To use this function first you must import GBpy as a package or directly import the function from the file.

find_csl_dsc needs two inputs and provides the CSL and DSC lattices in primitive basis. The syntax is as follows:

$\Lambda_{CSL}^{p1}$, $\Lambda_{DSC}^{p1}$ = find_csl_dsc ($\Lambda_{p}^{po}$, $T_{p_{1}\rightarrow p_{2}}^{p}$)

Follow the steps below to obtain the CSL and DSC lattices using GBpy:

First import the appropriate module from GBpy. Next call the find_csl_dsc function with the following syntax:

In [16]:
import GBpy.find_csl_dsc as gb_csl
L_csl_p1, L_dsc_p1 = gb_csl.find_csl_dsc(L_p_po, Sigma3)

where $\Lambda_{CSL}^{p1}$ is obtained as:

In [17]:
print L_csl_p1
[[-1.  0.  1.]
 [ 0. -1.  1.]
 [ 1.  1.  1.]]

and $\Lambda_{DSC}^{p1}$ is obtained as:

In [18]:
print L_dsc_p1
[[-0.33333333 -0.33333333  0.33333333]
 [ 0.66666667 -0.33333333  0.33333333]
 [-0.33333333  0.66666667  0.33333333]]

To find the $\Lambda_{CSL}^{po1}$ and $\Lambda_{DSC}^{po1}$, one must change the reference frame to the orthogonal basis as follows:

$\Lambda_{CSL}^{po1}$ = $\Lambda_{p}^{po} \times \Lambda_{CSL}^{p1}$

Hence we can obtain the $\Lambda_{CSL}^{po1}$ as:

In [19]:
L_csl_po1 = np.dot(L_p_po, L_csl_p1)
print L_csl_po1
[[ 0.5  0.   1. ]
 [ 0.   0.5  1. ]
 [-0.5 -0.5  1. ]]

Also we have:

$\Lambda_{DSC}^{po1}$ = $\Lambda_{p}^{po} \times \Lambda_{DSC}^{p1}$

Therefore we can obtain the $\Lambda_{DSC}^{po1}$ as:

In [40]:
L_dsc_po1 = np.dot(L_p_po, L_dsc_p1)
print L_dsc_po1
[[ 0.16666667  0.16666667  0.33333333]
 [-0.33333333  0.16666667  0.33333333]
 [ 0.16666667 -0.33333333  0.33333333]]

The $\Lambda_{CSL}^{po1}$ provides a basis for the coincidence sites of a bicrystal that have a misorientation of the specified $\Sigma$ (e.g. $\Sigma3$ in this case). Note that the basis obtained $\Lambda_{CSL}^{po1}$ is in reduced form (using lll-reduction algorithm) and therefore the length of the basis vectors are as close as possible. Each column of the $\Lambda_{CSL}^{po1}$ contains one of the basis vectors of the CSL lattice. These basis vectors of CSL the lattice are of paramount importance in simulation of interfaces and are used to construct a periodic simulation cell.

The $\Lambda_{DSC}^{po1}$ provides a basis for the displacement shift lattice of a bicrystal that have a misorientation of the specified $\Sigma$ (e.g. $\Sigma3$ in this case). The basis vectors of a DSC lattice provide unique displacement intervals in space in which the coincidence pattern of the bicrystal remains preserved. In other words any displacement in one of the bicrystals along the DSC basis vectors and with a magnitude of integer multiples of the norm of the DSC basis vectors, preserves the CSL pattern. This property of the DSC lattice is of great importance in energy minimization of interfaces.


Copyright (c) 2015, Arash D. Banadaki, Srikanth Patala