Chapter 2: Diffraction
2.4. Basic Crystallography#
part of
MSE672: Introduction to Transmission Electron Microscopy
Spring 2024
Gerd Duscher | Khalid Hattar |
Microscopy Facilities | Tennessee Ion Beam Materials Laboratory |
Materials Science & Engineering | Nuclear Engineering |
Institute of Advanced Materials & Manufacturing | |
Background and methods to analysis and quantification of data acquired with transmission electron microscopes.
2.4.1. Load relevant python packages#
2.4.1.1. Check Installed Packages#
import sys
import importlib.metadata
def test_package(package_name):
"""Test if package exists and returns version or -1"""
try:
version = importlib.metadata.version(package_name)
except importlib.metadata.PackageNotFoundError:
version = '-1'
return version
if test_package('pyTEMlib') < '0.2024.1.0':
print('installing pyTEMlib')
!{sys.executable} -m pip install --upgrade pyTEMlib -q
if 'google.colab' in sys.modules:
!{sys.executable} -m pip install numpy==1.24.4
print('done')
done
2.4.1.2. Load the plotting and figure packages#
Import the python packages that we will use:
Beside the basic numerical (numpy) and plotting (pylab of matplotlib) libraries,
three dimensional plotting and some libraries from the book
kinematic scattering library.
%matplotlib widget
import matplotlib.pylab as plt
import numpy as np
import sys
if 'google.colab' in sys.modules:
from google.colab import output
output.enable_custom_widget_manager()
# 3D plotting package
from mpl_toolkits.mplot3d import Axes3D # 3D plotting
import ase
# Import libraries from the pyTEMlib
import pyTEMlib
import pyTEMlib.kinematic_scattering as ks # kinematic scattering Library
# with atomic form factors from Kirkland's book
# it is a good idea to show the version numbers at this point for archiving reasons.
print('pyTEM version: ',pyTEMlib.__version__)
pyTEM version: 0.2023.8.0
2.4.2. Define Crystal#
A crystal is well defined by its unit cell and the atom positions within, the so called base. The base consists of which element sits where within the unit cell
The unit cell fills the volume completely when translated in all three directions. Placing the unit cell in a global carthesian coordination system, we need the length of the sides and their angles for a complete description. This is depicted in the graph below.
Figure taken from the wikipedia page on lattice constants.
Mathematically it is more advantageous to describe the unit cell as matrix, the
2.4.2.1. Structure Matrix#
This matrix consists of rows of vectors that span the unit cell: \(\begin{bmatrix} a_1 & a_2 & a_3 \\ b_1 & b_2 & b_3 \\ c_1 & c_2 & c_3 \\ \end{bmatrix} =\left[\vec{a},\vec{b},\vec{c}\right]\).
This structure matrix is also used to describe the super cells in materials simulations for example density functional theory.
The representation of unit cells as structure matrices allows also for easy conversions as we will see in the following.
# Create graphite unit cell (or structure matrix)
a = b = 2.46 # Angstrom
c = 6.71 # Angstrom
gamma = 120
alpha = beta = 90
## Create the structure matrix for a hexagonal system explicitly:
structure_matrix = np.array([[a,0.,0.], ## also called the structure matrix
[np.cos(np.radians(gamma))*a,np.sin(np.radians(gamma))*a,0. ],
[0.,0.,c]
])
print('structure matrix \n', np.round(structure_matrix,3))
elements = ['C']*4
base = [[0, 0, 0], [0, 0, 1/2], [1/3, 2/3, 0], [2/3, 1/3, 1/2]]
print('elements:', elements)
print('base \n',np.round(base,3))
structure matrix
[[ 2.46 0. 0. ]
[-1.23 2.13 0. ]
[ 0. 0. 6.71]]
elements: ['C', 'C', 'C', 'C']
base
[[0. 0. 0. ]
[0. 0. 0.5 ]
[0.333 0.667 0. ]
[0.667 0.333 0.5 ]]
2.4.2.2. Store Information in ASE (atomic simulation environment) format#
atoms = ase.Atoms(elements, cell=structure_matrix, scaled_positions=base)
atoms
Atoms(symbols='C4', pbc=False, cell=[[2.46, 0.0, 0.0], [-1.2299999999999995, 2.130422493309719, 0.0], [0.0, 0.0, 6.71]])
We can retrieve the information stored
print('structure matrix [nm]\n',np.round(atoms.cell.array,3))
print('elements \n',atoms.get_chemical_formula())
print('base \n',np.round(atoms.get_scaled_positions(), 3))
structure matrix [nm]
[[ 2.46 0. 0. ]
[-1.23 2.13 0. ]
[ 0. 0. 6.71]]
elements
C4
base
[[0. 0. 0. ]
[0. 0. 0.5 ]
[0.333 0.667 0. ]
[0.667 0.333 0.5 ]]
A convenient function is provided by the kinematic_scttering library (loaded with name ks)
atoms = ks.structure_by_name('Graphite')
atoms.cell, atoms.positions
(Cell([[2.46772414, 0.0, 0.0], [-1.2338620699999996, 2.1371117947721068, 0.0], [0.0, 0.0, 6.711]]),
array([[0.00000000e+00, 0.00000000e+00, 0.00000000e+00],
[0.00000000e+00, 0.00000000e+00, 3.35550000e+00],
[1.23386207e+00, 7.12370598e-01, 0.00000000e+00],
[3.04714108e-16, 1.42474120e+00, 3.35550000e+00]]))
2.4.2.3. Volume of Unit Cell#
We will need the volume of the unit cell for unit conversions later.
Volume of the parallelepiped (https://en.wikipedia.org/wiki/Triple_product) : \(\vec{a} \cdot \vec{b} \times \vec{c} = \det \begin{bmatrix} a_1 & a_2 & a_3 \\ b_1 & b_2 & b_3 \\ c_1 & c_2 & c_3 \\ \end{bmatrix} ={\rm det}\left(\vec{a},\vec{b},\vec{c}\right)\)
We see that the structure matrix comes in handy for that calculation.
volume = v = np.linalg.det(structure_matrix)
print(f"volume of unit cell: {volume:.4f} Ang^3")
volume of unit cell: 35.1660 Ang^3
The same procedure is provided by ase
print(f"volume of unit cell: {atoms.cell.volume:.4f} Ang^3")
volume of unit cell: 35.3925 Ang^3
2.4.2.4. Vector Algebra in Unit Cell#
We will use the linear algebra package of numpy (np.linalg) for our vector calculations.
The length of a vector is called its norm.
And the angle between two vectors is calculated by the dot product: \(\vec{a} \cdot \vec{b} = \left\| \vec{a} \right\| \left\| \vec{b} \right\| \cos (\theta) \)
Note that python starts couting at 0 and so the second vector has index 1
length_b = np.linalg.norm(structure_matrix[1])
print(f'length of second unit cell vector is {length_b:.3f} Ang' )
gamma = np.arccos(np.dot(structure_matrix[0]/length_b, structure_matrix[1]/length_b))
print(f'angle between a and b is {np.degrees(gamma):.1f} degree')
length of second unit cell vector is 2.460 Ang
angle between a and b is 120.0 degree
2.4.2.5. Plot the unit cell#
We use the visualization library of ase to plot structures.
from ase.visualize.plot import plot_atoms
plot_atoms(atoms, radii=0.3, rotation=('0x,1y,0z'))
<Axes: >
# This does not work in Google Colab
from ase.visualize import view
view(atoms*(4,4,1))
<Popen: returncode: None args: ['C:\\Users\\gduscher\\AppData\\Local\\anacon...>
from ase.visualize import view
view(atoms*(3,3,1), viewer = 'x3d')
2.4.3. Reciprocal Lattice#
The unit cell in reciprocal space
reciprocal_lattice = np.linalg.inv(atoms.cell.array).T # transposed of inverted unit_cell
print('reciprocal lattice [1/Ang.]:')
print(np.round(reciprocal_lattice,4))
reciprocal lattice [1/Ang.]:
[[0.4052 0.234 0. ]
[0. 0.4679 0. ]
[0. 0. 0.149 ]]
The same function is provided in ase package of Cell.
print('reciprocal lattice [1/Ang.]:')
print(np.round(atoms.cell.reciprocal(),4))
reciprocal lattice [1/Ang.]:
[[ 0.4052 0.234 0. ]
[-0. 0.4679 0. ]
[ 0. 0. 0.149 ]]
2.4.3.1. Reciprocal Lattice Vectors#
From your crystallography book and lecture you are probably used to the following expression for the reciprocal lattice vectors (\(\vec{a}^*, \vec{b}^*, \vec{c}^*\))
$ \begin{align} \vec{a}^* &= \frac{\vec{b} \times \vec{c}}{\vec{a} \cdot \left(\vec{b} \times \vec{c}\right)} \ \vec{b}^* &= \frac{\vec{c} \times \vec{a}}{\vec{b} \cdot \left(\vec{c} \times \vec{a}\right)} \ \vec{c}^* &= \frac{\vec{a} \times \vec{b}}{\vec{c} \cdot \left(\vec{a} \times \vec{b}\right)} \end{align}$\
Where we see that the denominators of the above vector equations are the volume of the unit cell.
In physics book, you will see an additional factor of 2\(\pi\), which is generally omitted in materials science and microscopy.
## Now let's test whether this is really equivalent to the matrix expression above.
a,b,c = atoms.cell
a_recip = np.cross(b, c)/np.dot(a, np.cross(b, c))
print (np.round(a_recip, 3))
b_recip = np.cross(c, a)/np.dot(a, np.cross(b, c))
print (np.round(b_recip, 3))
c_recip = np.cross(a, b)/np.dot(a, np.cross(b, c))
print (np.round(c_recip, 3))
print('Compare to:')
print(np.round(reciprocal_lattice, 3))
[ 0.405 0.234 -0. ]
[0. 0.468 0. ]
[ 0. -0. 0.149]
Compare to:
[[0.405 0.234 0. ]
[0. 0.468 0. ]
[0. 0. 0.149]]
2.4.4. Conclusion#
With these definitions we have everything to define a crystal and to analyse diffraction and imaging data of crystalline specimens.
Crystallography deals with the application of symmetry and group theory of symmetry to crystal structures. If you want to play around with symmetry and space groups, you can install the spglib. The spglib is especially helpfull for determination of reduced unit cells (the smallest possible ones, instead of the ones with the full symmetry).
A number of common crystal structures are defined in the kinematic_scattering libary of the pyTEMlib package under the function ‘’structure_by_name’’. Try them out in this notebook.
# As ususal the help function will show you the usage of a function:
help(ks.structure_by_name)
Help on function structure_by_name in module pyTEMlib.crystal_tools:
structure_by_name(crystal_name)
Provides crystal structure in ase.Atoms format.
Additional information is stored in the info attribute as a dictionary
Parameter
---------
crystal_name: str
Please note that the chemical expressions are not case-sensitive.
Returns
-------
atoms: ase.Atoms
structure
Example
-------
>> # for a list of pre-defined crystal structures
>> import pyTEMlib.crystal_tools
>> print(pyTEMlib.crystal_tools.crystal_data_base.keys())
>>
>> atoms = pyTEMlib.crystal_tools.structure_by_name('Silicon')
>> print(atoms)
>> print(atoms.info)
Here are all the predifined crystal structures.
Check out the building tutorial of ase for more fun structures like nanotubes
print(ks.crystal_data_base.keys())
dict_keys(['aluminum', 'al', 'aluminium', 'gold', 'au', 'silver', 'ag', 'copper', 'cu', 'diamond', 'germanium', 'ge', 'silicon', 'si', 'gaas', 'fcc fe', 'iron', 'bcc fe', 'alpha iron', 'srtio3', 'strontium titanate', 'graphite', 'cscl', 'cesium chlorid', 'mgo', 'titanium nitride', 'zno wurzite', 'zno', 'wzno', 'gan', 'gan wurzite', 'wgan', 'gallium nitride', 'tio2', 'mos2', 'ws2', 'wse2', 'mose2', 'zno hexagonal', 'pdse2'])
Now use one name of above structures and redo this notebook
2.4.6. Appendix: Read POSCAR#
Load and draw a crystal structure from a POSCAR file
2.4.6.1. The function#
from ase.io import read, write
import pyTEMlib.file_tools as ft
import os
def read_poscar(): # open file dialog to select poscar file
file_name = ft.open_file_dialog_qt('POSCAR (POSCAR*.txt);;All files (*)')
#use ase package to read file
base = os.path.basename(file_name)
base_name = os.path.splitext(base)[0]
crystal = read(file_name, format='vasp', parallel=False)
return crystal
atoms = read_poscar()
atoms
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[22], line 1
----> 1 atoms = read_poscar()
2 atoms
Cell In[21], line 9, in read_poscar()
6 file_name = ft.open_file_dialog_qt('POSCAR (POSCAR*.txt);;All files (*)')
7 #use ase package to read file
----> 9 base = os.path.basename(file_name)
10 base_name = os.path.splitext(base)[0]
11 crystal = read(file_name, format='vasp', parallel=False)
File <frozen ntpath>:270, in basename(p)
File <frozen ntpath>:241, in split(p)
TypeError: expected str, bytes or os.PathLike object, not NoneType