Chapter 2: Diffraction
2.12. HOLZ Lines#
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.12.1. Load relevant python packages#
2.12.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 git+https://github.com/pycroscopy/pyTEMlib.git@main -q --upgrade
if 'google.colab' in sys.modules:
!{sys.executable} -m pip install numpy==1.24.4
print('done')
installing pyTEMlib
done
2.12.1.2. Import numerical and plotting python packages#
Import the python packages that we will use:
Beside the basic numerical (numpy) and plotting (pylab of matplotlib) libraries,
and some libraries from the book
kinematic scattering library.
%matplotlib widget
import matplotlib.pyplot as plt
import numpy as np
import sys
if 'google.colab' in sys.modules:
from google.colab import output
output.enable_custom_widget_manager()
# additional package
import itertools
import scipy.constants as const
import ipywidgets as ipyw
# Import libraries from pyTEMlib
import pyTEMlib
import pyTEMlib.kinematic_scattering as ks # Kinematic scattering Library
# Atomic form factors from Kirklands book
### And we use the image tool library of Quantifit
import pyTEMlib.file_tools as ft
import pyTEMlib
__notebook_version__ = '2024.02.21'
print('pyTEM version: ', pyTEMlib.__version__)
print('notebook version: ', __notebook_version__)
You don't have igor2 installed. If you wish to open igor files, you will need to install it (pip install igor2) before attempting.
You don't have gwyfile installed. If you wish to open .gwy files, you will need to install it (pip install gwyfile) before attempting.
Symmetry functions of spglib enabled
Using kinematic_scattering library version {_version_ } by G.Duscher
pyTEM version: 0.2024.01.1
notebook version: 2024.02.21
2.12.2. Define crystal#
### Please choose another crystal like: Silicon, Aluminium, GaAs , ZnO
atoms = ks.structure_by_name('silicon')
atoms
Lattice(symbols='Si8', pbc=True, cell=[5.43088, 5.43088, 5.43088])
2.12.3. Plot the unit cell#
from ase.visualize.plot import plot_atoms
plot_atoms(atoms, radii=0.3, rotation=('0x,4y,0z'))
<Axes: >
2.12.4. Parameters for Diffraction Calculation#
tags = {}
atoms.info['experimental'] = tags
tags['acceleration_voltage_V'] = 99 *1000.0 #V
tags['convergence_angle_mrad'] = 0
tags['zone_hkl'] = np.array([2,2,1]) # incident neares zone axis: defines Laue Zones!!!!
tags['mistilt'] = np.array([0,0,0]) # mistilt in degrees
tags['Sg_max'] = .01 # 1/Ang maximum allowed excitation error ; This parameter is related to the thickness
tags['hkl_max'] = 36 # Highest evaluated Miller indices
2.12.5. Kinematic Scattering Calculation#
ks.kinematic_scattering(atoms, False)
2.12.5.1. Plot Selected Area Electron Diffraction Pattern#
#####################
# Plot ZOLZ SAED Pattern #
#####################
## Get information as dictionary
tagsD = atoms.info['diffraction']
#We plot only the allowed diffraction spots
points = tagsD['allowed']['g']
# we sort them by order of Laue zone
ZOLZ = tagsD['allowed']['ZOLZ']
HOLZ = tagsD['allowed']['HOLZ']
# Plot
fig = plt.figure()
ax = fig.add_subplot(111)
# We plot the x,y axis only; the z -direction is set to zero - this is our projection
ax.scatter(points[ZOLZ,0], points[ZOLZ,1], c='red', s=20)
ax.scatter(points[HOLZ,0], points[HOLZ,1], c='green', s=20)
# zero spot plotting
ax.scatter(0,0, c='red', s=100)
ax.scatter(0,0, c='white', s=40)
ax.axis('equal')
FOV = 18
plt.ylim(-FOV,FOV); plt.xlim(-FOV,FOV); plt.show()
import pyTEMlib.animation as animate
plt.figure()
animate.deficient_holz_line(exact_bragg=False, laue_zone=1)
#animate.deficient_holz_line(exact_bragg='True', laue_zone=1, color='blue')
animate.deficient_holz_line(exact_bragg='True', laue_zone=1, color='red', shift=True)
2.12.6. HOLZ Line Construction#
Position of deficient HOLZ line
2.12.6.1. What is \(d\theta\):#
\begin{eqnarray*} d\theta &+&(90-\theta_B)+ \varphi = 90\ d\theta &=& \theta_B -\varphi\ &&\ \sin(\theta_B) &=&|\vec{g}/2|/ |\vec{K}0| \ \tan(\phi) &=& |\vec{g}{HOLZ}|/|\vec{g}-\vec{g}{HOLZ}|\ &&\ |\vec{g}{deficient}| &=& 2\sin( d\theta/ 2 )* |\vec{K}_0|\ \end{eqnarray*}
For exact Bragg position in ZOLZ
\(d\theta = \theta_B\) then \(\vec{g}_{deficient} = -\vec{g}/2 \)
\(d\theta = \theta - \varphi\)
with:
\(\sin(\theta) =\frac{|\vec{g}|/2}{|\vec{K}_0| }\)
\(\sin(\varphi) = \frac{|\vec{g}_{\rm HOLZ}|}{|\vec{g}|}\)
Because \(d\theta\) is the same as \(d \varphi\) we can now calculate the deficient HOLZ lines
\begin{eqnarray*} \vec{K}{Bg} &=& ( (\vec{g}-\vec{g}{HOLZ})/ |\vec{g}-\vec{g}{HOLZ}|*|\vec{g}{HOLZ}| \ && + \vec{K}{0}/|\vec{K}{0}||\vec{g}-\vec{g}{HOLZ}|)\ \vec{K}{Bg} &=&(\vec{g}{ZOLZ}/|\vec{g}{ZOLZ}||\vec{g}{HOLZ}|) \ &+&\vec{K}{0}/|\vec{K}{0}|*|\vec{g}-\vec{g}{HOLZ}|)\ \vec{K}{B} &=& \vec{K}{bg}/|\vec{K}{Bg}|*\sqrt{|\lambda^2-g^2/4)}\ \vec{K}d &=& -(\vec{g}/2+\vec{K}B)\ \vec{g}{deficient} &=& \vec{K}{0}-\vec{K}{d}\ \end{eqnarray*}
For exact Bragg position in ZOLZ \(\vec{B}=\vec{K}_{0}\) then \(\vec{g}_{deficient} = \vec{g}/2\)
This is our Kikuchi line equation
ks.get_rotation_matrix(atoms.info['experimental'])
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[10], line 1
----> 1 ks.get_rotation_matrix(atoms.info['experimental'])
File ~\AppData\Local\anaconda3\envs\py11\Lib\site-packages\pyTEMlib\kinematic_scattering.py:168, in get_rotation_matrix(angles, in_radians)
152 """ Rotation of zone axis by mistilt
153
154 Parameters
(...)
164 rotation matrix in 3d
165 """
167 if not isinstance(angles, (np.ndarray, list)):
--> 168 raise TypeError('angles must be a list of float of length 3')
169 if len(angles) != 3:
170 raise TypeError('angles must be a list of float of length 3')
TypeError: angles must be a list of float of length 3
atoms.info['experimental']['nearest_zone_axes']
nearest = atoms.info['experimental']['nearest_zone_axes']
print('Next nearest zone axes are:')
for i in range(1, nearest['amount']):
print(f" {nearest[str(i)]['hkl']}: mistilt: {np.rad2deg(nearest[str(i)]['mistilt_alpha']):6.2f}, "
f"{np.rad2deg(nearest[str(i)]['mistilt_beta']):6.2f}")
Next nearest zone axes are:
[5. 5. 3.]: mistilt: 1.20, 4.40
[5. 5. 2.]: mistilt: -1.07, -4.76
#Calculate angle between K0 and deficient cone vector
#For dynamic calculations K0 is replaced by Kg
K0 = np.linalg.norm(atoms.info['experimental']['incident_wave_vector'])
g_allowed = atoms.info['diffraction']['allowed']['g']
g_norm_allowed = np.linalg.norm(g_allowed, axis = 1)
dtheta = np.arcsin(g_norm_allowed/K0/2.)
np.arcsin(np.abs(g_allowed[:,2])/g_norm_allowed)
#calculate length of distance of deficient cone to K0 in ZOLZ plane
gd_length =2*np.sin((dtheta)/2 )*K0
#Calculate nearest point of HOLZ and Kikuchi lines
gd = g_allowed.copy()
gd[:,0] = -gd[:,0]*gd_length/g_norm_allowed
gd[:,1] = -gd[:,1]*gd_length/g_norm_allowed
gd[:,2] = 0.
###calculate and save line in Hough space coordinates (distance and theta)
slope = gd[:,0]/(gd[:,1]+1e-20)
distance = gd_length
theta = np.arctan(slope)
2.12.7. Now everything together in a single cell#
We change the lattice parameter (Vegard’s law of alloys) by a few pm and observe the effect on the pattern.
# ----- Input -----------
unit_cell_change_pm = -1.0
# -----------------------
atoms = ks.structure_by_name('silicon')
cell = atoms.cell.lengths()
atoms.set_cell(cell+unit_cell_change_pm/100, scale_atoms=True)
atoms
atoms.info['experimental'] = {'crystal_name': 'silicon',
'acceleration_voltage_V': 100.8*1000.0, #V
'convergence_angle_mrad': 5.,
'Sg_max': .03, # 1/Ang maximum allowed excitation error ; This parameter is related to the thickness
'hkl_max': 14, # Highest evaluated Miller indices
'zone_hkl': np.array([1, 2, -2]),
'mistilt_alpha degree': 0., # -45#-35-0.28-1+2.42
'mistilt_beta degree': 0.,
'plot_FOV': .5}
ks.kinematic_scattering(atoms)
atoms.info['output']=(ks.plotHOLZ_parameter())
atoms.info['output']['plot_reflections']=False
atoms.info['output']['plot_Kikuchi']=False
atoms.info['output']['linewidth_HOLZ'] = -1
atoms.info['output']['plot_HOLZ']=True
ks.plot_diffraction_pattern(atoms)
plt.gca().set_xlim(-.2,.2)
plt.gca().set_ylim(-.2,.2)
(-0.2, 0.2)
atoms = ks.structure_by_name('graphite')
atoms
Atoms(symbols='C4', pbc=False, cell=[[2.46772414, 0.0, 0.0], [-1.2338620699999996, 2.1371117947721068, 0.0], [0.0, 0.0, 6.711]])
2.12.8. Now for graphite and low acceleration voltages.#
Change the acceleration voltage and see what happens.
Tip: 60.49keV is especially interesting
### Please choose another crystal like: Silicon, Aluminium, GaAs , ZnO
# ----- Input -----------
crystal_name = 'graphite'
acceleration_voltage_V = 60.49 * 1000.0
# -----------------------
atoms = ks.structure_by_name(crystal_name)
atoms.info['experimental'] = {'crystal_name': crystal_name,
'acceleration_voltage_V': acceleration_voltage_V, #V
'convergence_angle_mrad': 7.,
'Sg_max': .03, # 1/Ang maximum allowed excitation error ; This parameter is related to the thickness
'hkl_max': 9, # Highest evaluated Miller indices
'zone_hkl': np.array([0, 0, 1]),
'mistilt_alpha degree': .0, # -45#-35-0.28-1+2.42
'mistilt_beta degree': 0.,
'plot_FOV': .5}
ks.kinematic_scattering(atoms, verbose=False)
atoms.info['output']=(ks.plotHOLZ_parameter())
atoms.info['output']['plot_reflections']=True
atoms.info['output']['plot_Kikuchi']=False
atoms.info['output']['linewidth_HOLZ'] = 1
atoms.info['output']['plot_HOLZ']=True
ks.plot_diffraction_pattern(atoms)
plt.title(crystal_name + ': ' + str(atoms.info['experimental']['zone_hkl']))
plt.gca().set_xlim(-.17,.17)
plt.gca().set_ylim(-.17,.17)
(-0.17, 0.17)