Skip to article frontmatterSkip to article content

HOLZ Lines

Chapter 2: Diffraction


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

Load relevant python packages

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

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

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])

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]'}>

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

Kinematic Scattering Calculation

ks.kinematic_scattering(atoms, False)

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()
Loading...
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)
Loading...

HOLZ Line Construction

Position of deficient HOLZ line

What is dθd\theta:

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(\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θ=θBd\theta = \theta_B then gdeficient=g/2\vec{g}_{deficient} = -\vec{g}/2

dθ=θφd\theta = \theta - \varphi

with:

sin(θ)=g/2K0\sin(\theta) =\frac{|\vec{g}|/2}{|\vec{K}_0| }

sin(φ)=gHOLZg\sin(\varphi) = \frac{|\vec{g}_{\rm HOLZ}|}{|\vec{g}|}

Because dθd\theta is the same as dφd \varphi 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 B=K0\vec{B}=\vec{K}_{0} then gdeficient=g/2\vec{g}_{deficient} = \vec{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)

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)
Loading...
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]])

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)
Loading...