Skip to article frontmatterSkip to article content

Thermal-Diffuse Scattering

Chapter 3: Imaging


Thermal-Diffuse Scattering

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.

Note: This notebook needs a Linux environment and is most easily run in Colab with the button above

Load important packages

In Colab, the next code cell must be run first

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.2.3':
    print('installing pyTEMlib')
    !{sys.executable} -m pip install  --upgrade pyTEMlib -q
if test_package('abtem') < '1.0.0b17':
    print('installing abtem')
    !{sys.executable} -m pip install  --upgrade abtem -q

print('done')
installing pyTEMlib
installing abtem
done

Load Packages

We will use

  • numpy and matplotlib (installed with magic comand %pylab.)

  • physical constants from scipy

  • The pyTEMlib kinematic scattering librarty is only used to determine the wavelength.

%matplotlib widget
import matplotlib.pyplot as plt
import numpy as np
import sys
import os
if 'google.colab' in sys.modules:
    from google.colab import output
    output.enable_custom_widget_manager()
    
# import atomic simulation environment
import ase
import ase.spacegroup
import ase.visualize

# import abintio-tem library
import abtem

__notebook__ = 'CH3-08-Thermal_Diffuse_Scattering'
__notebook_version__ = '2021_03_29'

Multislice Algorithm

As in the Dynamic Diffraction part in the Multislice notebook, we first define the potential of the slices.

However, here we do this with the abtem package and the structure is made with the ase package

Defining the structure

Here we make a SrTiO3_3 crystal again

atom_pos = [(0.0, 0.0, 0.0), (0.5, 0.5, 0.5), (0.5, 0.5, 0.0)]
srtio3 = ase.spacegroup.crystal(['Sr','Ti','O'], atom_pos, spacegroup=221, cellpar=3.905, size=(4, 4, 16))
srtio3.center()
ase.visualize.view(srtio3, viewer='x3d')  
Loading...

However, as before we need more atoms laterally and more layers.

Please note:

all length in abTEM are in Angstrom

atom_pos = [(0.0, 0.0, 0.0), (0.5, 0.5, 0.5), (0.5, 0.5, 0.0)]
srtio3 = ase.spacegroup.crystal(['Sr','Ti','O'], atom_pos, spacegroup=221, cellpar=3.905, size=(8, 8, 180))

srtio3.center()
print(f"Simulation cell: {srtio3.cell}")
abtem.show_atoms(srtio3);
Loading...

Defining the potential

potential = abtem.Potential(srtio3, 
                      gpts=512, 
                      slice_thickness=3.904/2, 
                      parametrization='kirkland', 
                      projection='infinite')

potential.sampling
wave = abtem.PlaneWave(energy=200e3, sampling=.05)

Multislice

exit_wave = wave.multislice(potential)
exit_wave.compute()
exit_wave.show()
Loading...

Plot Diffraction Pattern


diffraction_pattern = exit_wave.diffraction_patterns()
diffraction_pattern.crop(60).block_direct()
view = diffraction_pattern.show(power=.2, figsize=(6,6), cmap='jet')
Loading...

Thermal Diffuse Scattering

The atoms in any real material are not exactly located at their symmetrical lattice points due to thermal and zero-point vibrations. The frozen phonon approximation is a simple to simulate the effects of thermal vibrations.

The scattering that is associated with thermal lattice vibrations is called thermal diffuse scattering (TDS). In the following, we investigate the effect of thermal diffuse scattering in the frozen phonon approximation on diffraction pattern.

Structure and Debey Waller Factor

Each atom is allowed to deviate from its symmetrical lattice position with a Gaussian distribution and an root mean square rms deviation of xRMSx_{RMS} in each of three directions. This is equivalent to the Einstein model of the density of states for phonons.

This standard deviation of this Gaussiam distribution xRMSx_{RMS} for most materials is about 0.1Å:

The relation of the Debey-Waller Factors in the literature to the deviation of position xRMS=B/(8π2)x_{RMS} = \sqrt{B/(8\pi^2)}.

The FrozenPhonon class of the abtem package generates offsets from such a Gaussian distribution using a random number generator with a given seed.

The standard deviation of the Gaussian distribution is provided for each element as a dictionary.

# ------ Input ------ #
number_of_frozen_phonon_runs = 12
# --------------------#
frozen_phonons = abtem.FrozenPhonons(srtio3, number_of_frozen_phonon_runs, {'Sr' : .1, 'Ti' : .1, 'O' : .1}, seed=1)
atoms_conf = next(iter(frozen_phonons))

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10,5))

abtem.show_atoms(srtio3, ax=ax1)

abtem.show_atoms(atoms_conf, ax=ax2);
ax1.set_title('Symmetric ensemble')
ax2.set_title('Thermal ensemble');
Loading...
atoms_conf = next(iter(frozen_phonons))

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10,5))

abtem.show_atoms(srtio3, ax=ax1)

abtem.show_atoms(atoms_conf, ax=ax2);
ax1.set_title('Symmetric ensemble')
ax2.set_title('Thermal ensemble');
Loading...

Building a potential

tds_potential = abtem.Potential(frozen_phonons, gpts=512, slice_thickness=3.905/2, 
                                projection='infinite', parametrization='kirkland')
print(f"Real space sampling: {tds_potential.sampling} Angstrom ")
Real space sampling: (0.061015625, 0.061015625) Angstrom 
tds_exit_waves = wave.multislice(tds_potential).compute()
[########################################] | 100% Completed | 118.36 s

Averaging thermal diffuse scattering calculations.

The need for averaging of the thermal diffuse scattering becomes clear in the following figure.

Each atom position has to have many deviations to be averaged out in the center. This is waht makes this process so long and what makes the process obvious for (embarrassingly) parallel methods.

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10,5), sharex=True, sharey=True)

tds_exit_waves[0].show(ax=ax1)
ax1.set_title('Single configuration')
tds_exit_waves.intensity().mean(0).show(ax=ax2)
ax2.set_title('Thermal ensemble')
Loading...

Plot Diffraction Pattern

The Kikuchi notebook explained that the source for the Kikuchi pattern formation is inside the sample. Here we have the origin of this source. The thermal diffuse scattering makes the electrons scatter into high angles and that is needed to capture these features in a simulation.

So when you see Kikuchi bands you actually see the effect of phonons.

diffraction_patterns_static = exit_wave.diffraction_patterns(
    max_angle="valid", block_direct=True)
tds_diffraction_pattern = tds_exit_waves.diffraction_patterns(
    max_angle="valid", block_direct=True)

fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(10,5), sharex=True, sharey=True)

diffraction_pattern.show(power=.2, cmap='jet', ax=ax1)
ax1.set_title('Symmetric ensemble')
tds_diffraction_pattern[0].show(power=.2, cmap='jet', ax=ax2)
ax2.set_title('Single thermal ensemble')

tds_diffraction_pattern.mean(0).show(ax=ax3, cmap='jet', power=.2, )
ax3.set_title('Averaged thermal ensemble')
Loading...

CBED and Thermal Diffuse Scattering

Make the probe

The probe has to be on the same grid (matrix, pixels) as the potential, which is ensured with the grid.match function.

# ---- Input ----- #
convergence_angle = 2  # in mrad of half angle
acceleration_voltage = 200e3 # in V
defocus = 40  # in nm
# -----------------#
probe = abtem.Probe(energy=acceleration_voltage, semiangle_cutoff=convergence_angle,
                     aberrations = {'C10': defocus, 'C30':3e6})# , focal_spread=20)
probe.grid.match(potential)
waves = probe.build().compute()
probe.profiles().show();
waves.show()
Loading...

Symmetric ensemble


cbed = probe.multislice(potential).compute()
cbed.show(power=0.25)
Loading...

TDS

For the thermal diffuse scattering like above for the symetric ensemble we reuse the potential from the plane wave calculation and just change the probe. The probe was definded in the symmetric enseble calculation above.

The calculation takes about 1 min on my laptop.

center = (tds_potential.extent[0] / 2, tds_potential.extent[1] / 2)

probe.grid.match(tds_potential)
tds_cbed = probe.multislice(potential=tds_potential).compute()
tds_cbed.mean(0).show(power=0.5)
Loading...

Comparison

plt.close('all')
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10,5), sharex=True, sharey=True)

cbed.show(power=.5, cmap='jet', ax=ax1)
ax1.set_title('Symmetric ensemble')
tds_cbed.mean(0).show(power=.5, cmap='jet', ax=ax2)
ax2.set_title('Thermal ensemble')
Loading...

Redo this calculation with non overlapping disks in the convergence angle (let’s say 7 mrad at 80keV) You can jump to Make the probe

Summary

For a quantitative image simulation we need to do dynamic scattering theory.

The dynamic scattering theory is done within the multislice algorithm that treats each slice like a weak phase object.

Thermal defuse scattering needs to be included into the multislice calculations for a good simulation

The thermal diffuse scattering can be approximated by the frozen phonon approximation but it is computationally intensive.