Chapter 2: Diffraction


2.12. HOLZ Lines#

Download

Open In Colab

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