Chapter 2: Diffraction
HOLZ Lines¶
part of
MSE672: Introduction to Transmission Electron Microscopy
Spring 2026
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
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.2026.1.0':
print('installing pyTEMlib')
!{sys.executable} -m pip install pyTEMlib -q --upgrade
print('done')done
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()
# Import libraries from pyTEMlib
import pyTEMlib
__notebook_version__ = '2026.01.11'
print('pyTEM version: ', pyTEMlib.__version__)
print('notebook version: ', __notebook_version__)pyTEM version: 0.2026.1.0
notebook version: 2026.01.11
Define crystal¶
### Please choose another crystal like: Silicon, Aluminium, GaAs , ZnO
atoms = pyTEMlib.crystal_tools.structure_by_name('silicon')
atomsLattice(symbols='Si8', pbc=True, cell=[5.43088, 5.43088, 5.43088])Plot the unit cell¶
from ase.visualize.plot import plot_atoms
plot_atoms(atoms, radii=0.3, rotation=('0x,4y,0z'))<Axes: >Parameters for Diffraction Calculation¶
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['Sg_max'] = .01 # 1/Ang maximum allowed excitation error ; This parameter is related to the thickness
tags['hkl_max'] = 36 # Highest evaluated Miller indices
Kinematic Scattering Calculation¶
diff_dict = pyTEMlib.diffraction_tools.get_bragg_reflections(atoms, tags, verbose=True)Of the 724 possible reflection 723 are allowed.
Of those, there are 12 in ZOLZ and 711 in HOLZ
Of the 0 forbidden reflection in ZOLZ 0 can be dynamically activated.
Plot Selected Area Electron Diffraction Pattern¶
#####################
# Plot ZOLZ SAED Pattern #
#####################
#We plot only the allowed diffraction spots
g = diff_dict['allowed']['g']
# we sort them by order of Laue zone
ZOLZ = diff_dict['allowed']['ZOLZ']
HOLZ = diff_dict['allowed']['HOLZ']
rotation = 0
x = g[:, 0] * np.cos(g[:, 1]+np.pi+rotation)*10
y = g[:, 0] * np.sin(g[:, 1]+np.pi+rotation)*10
slope = np.tan(g[:, 1]+rotation-np.pi/2)
# 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(x[ZOLZ], y[ZOLZ], c='red', s=20)
ax.scatter(x[HOLZ], y[HOLZ], c='green', s=20)
# zero spot plotting
ax.scatter(0,0, c='red', s=100)
ax.scatter(0,0, c='white', s=40)
ax.set_aspect('equal')
FOV = 16
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)
HOLZ Line Construction¶
Position of deficient HOLZ line
What is :¶
No such environment: eqnarray* at position 7: \begin{̲e̲q̲n̲a̲r̲r̲a̲y̲*̲}̲
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(\varphi) &=& |\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 is the same as we can now calculate the deficient HOLZ lines
No such environment: eqnarray* at position 7: \begin{̲e̲q̲n̲a̲r̲r̲a̲y̲*̲}̲
\vec{K}_{B…
\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
#Calculate angle between K0 and deficient cone vector
#For dynamic calculations K0 is replaced by Kg
K0 = diff_dict['K_0']
g_allowed = diff_dict['allowed']['g']
g_norm_allowed = g_allowed[:, 0]
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)
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 = pyTEMlib.crystal_tools.structure_by_name('Silicon')
cell = atoms.cell.lengths()
atoms.set_cell(cell+unit_cell_change_pm/100, scale_atoms=True)
atoms
tags = {'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': 9, # 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}
diff_dict ={}
diff_dict = pyTEMlib.diffraction_tools.get_bragg_reflections(atoms, tags, verbose=True)
diff_dict['output']=pyTEMlib.diffraction_tools.plot_holz_parameter()
diff_dict['output']['plot_reflections']=False
diff_dict['output']['plot_Kikuchi']=False
diff_dict['output']['linewidth_HOLZ'] = -1
diff_dict['output']['plot_HOLZ']=True
pyTEMlib.diffraction_tools.plot_diffraction_pattern(diff_dict)
plt.gca().set_xlim(-2,2)
plt.gca().set_ylim(-2,2)Of the 156 possible reflection 156 are allowed.
Of those, there are 50 in ZOLZ and 106 in HOLZ
Of the 0 forbidden reflection in ZOLZ 0 can be dynamically activated.
(-2.0, 2.0)def calculate_holz(dif, tags):
""" Calculate HOLZ lines (of allowed reflections)"""
# dif = atoms.info['diffraction']
intensities = dif['allowed']['intensities']
k_0 = dif['K_0']
g_norm_allowed = dif['allowed']['g'][:, 0]
# Dynamic Correction
# Equation Spence+Zuo 3.86a
gamma_1 = - 1./(2.*k_0) * (intensities / (2.*k_0*dif['allowed']['excitation_error'])).sum()
# Equation Spence+Zuo 3.84
kg = k_0 - k_0*gamma_1/(dif['allowed']['g'][:, 2]+1e-15)
kg[dif['allowed']['ZOLZ']] = k_0
# Calculate angle between K0 and deficient cone vector
# For dynamic calculations K0 is replaced by kg
kg[:] = k_0
d_theta = np.arcsin(g_norm_allowed/kg/2.) - (np.arcsin(np.abs(dif['allowed']['g'][:, 2])
/g_norm_allowed))
# calculate length of distance of deficient cone to K0 in ZOLZ plane
gd_length = 2*np.sin(d_theta/2) * k_0
# Calculate nearest point of HOLZ and Kikuchi lines
g_closest = dif['allowed']['g'].copy()
g_closest = g_closest*(gd_length/np.linalg.norm(g_closest, axis=1))[:, np.newaxis]
g_closest[:, 2] = 0.
# calculate and save line in Hough space coordinates (distance and theta)
slope = g_closest[:, 0]/(g_closest[:, 1]+1e-10)
distance = gd_length
theta = dif['allowed']['g'][:, 1]
dif['HOLZ'] = {}
dif['HOLZ']['slope'] = slope
return distance, theta
r, phi = calculate_holz(diff_dict, tags)
x = r * np.cos(phi)*10
y = r * np.sin(phi)*10
slope = np.tan(phi+np.pi/2)
plt.figure()
for i in range(len(x)):
plt.axline((x[i], y[i]), slope=slope[i], c='r')
plt.gca().set_aspect('equal')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 = 61.49 * 1000.0
# -----------------------
atoms = pyTEMlib.crystal_tools.structure_by_name(crystal_name)
tags = {'crystal_name': crystal_name,
'acceleration_voltage': acceleration_voltage, #V
'convergence_angle': 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': .0, # -45#-35-0.28-1+2.42
'mistilt_beta': 0.,
'plot_FOV': .5}
diff_dict ={}
diff_dict = pyTEMlib.diffraction_tools.get_bragg_reflections(atoms, tags, verbose=True)
diff_dict['output']=pyTEMlib.diffraction_tools.plot_holz_parameter()
diff_dict['output']['plot_reflections']=True
diff_dict['output']['plot_Kikuchi']=False
diff_dict['output']['linewidth_HOLZ'] = 2
diff_dict['output']['plot_HOLZ']=True
pyTEMlib.diffraction_tools.plot_diffraction_pattern(diff_dict)
plt.title(crystal_name + ': ' + str(tags['zone_hkl']))
plt.gca().set_xlim(-1.7, 1.7)
plt.gca().set_ylim(-1.7, 1.7)
plt.gca().set_aspect('equal')
Of the 138 possible reflection 118 are allowed.
Of those, there are 18 in ZOLZ and 100 in HOLZ
Of the 0 forbidden reflection in ZOLZ 0 can be dynamically activated.
Navigation¶
Back: Kikuchi Lines
Chapter 2: Diffraction
List of Content: Front