Chapter 2: Diffraction


2.12. HOLZ Lines#

Download

Open In Colab

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 dθ:#

\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θ=θB then gdeficient=g/2

dθ=θφ

with:

sin(θ)=|g|/2|K0|

sin(φ)=|gHOLZ||g|

Because dθ is the same as dφ 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 B=K0 then gdeficient=g/2

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)