Chapter 2: Diffraction
2.12. HOLZ Lines#
part of
MSE672: Introduction to Transmission Electron Microscopy
Spring 2025
by Gerd Duscher
Microscopy Facilities
Institute of Advanced Materials & Manufacturing
Materials Science & Engineering
The University of Tennessee, Knoxville
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__)
pyTEM version: 0.2024.09.0
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: title={'center': 'graphite: [0 0 1]'}>
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 :#
\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
then
with:
Because
\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
then This is our Kikuchi line equation
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)