#%matplotlib qt
from ase.io import read
import matplotlib.pyplot as plt

import abtem


An electron beam interacts with a specimen through the Coulomb potential of its electrons and nuclei. Thus the total electrostatic potential of the sample is required for quantitative image simulations. Typically, the so-called indepedent atom model (IAM) is used, which neglects any bonding effects and treats the sample as an array of atomic potentials.

Atomic potential parametrization

The electron charge distribution of an atom can be calculated from a first-principles electronic structure calculation, while the atomic nuclei are point charges at any realistic spatial resolution. Given a charge distribution, the potential can be obtained via Poisson’s equation. Most multislice simulation codes include a parametrization of the atomic potentials, with a table of parameters for each element fitted to Hartree-Fock calculations.

The radial dependence of the electrostatic potential and (electron) scattering factor of five selected elements is shown below.

symbols = ['C', 'N', 'Si', 'Au', 'U']

potentials = abtem.concatenate([abtem.AtomicPotential(symbol).line_profiles(name='potential', cutoff=2) for symbol in symbols])
scattering_factors = abtem.concatenate([abtem.AtomicPotential(symbol).line_profiles(name='scattering_factor', cutoff=3) for symbol in symbols])

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10,5))
ax1 = scattering_factors.show(ax=ax1)
ax2 = potentials.show(ax=ax2)

ax2.set_ylim([0, 800]);

The default parametrization in abTEM is created by Lobato et. al. (doi:10.1107/S205327331401643X). We also implement the parametrization by Kirkland and the parametrization by Peng. The differences between the parametrizations are generally negligible at low scattering angles, however, the parametrization by Lobato has the most accurate behavior at high scattering angles.

lobato_scattering_factors = abtem.concatenate([abtem.AtomicPotential(symbol, parametrization='lobato').line_profiles(name='scattering_factor', cutoff=3) for symbol in symbols])
kirkland_scattering_factors = abtem.concatenate([abtem.AtomicPotential(symbol, parametrization='kirkland').line_profiles(name='scattering_factor', cutoff=3) for symbol in symbols])
peng_scattering_factors = abtem.concatenate([abtem.AtomicPotential(symbol, parametrization='peng').line_profiles(name='scattering_factor', cutoff=3) for symbol in symbols])

fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15,5))

Independent atom model

The full specimen potential, \(V(r)\), is usually obtained as a linear superposition of atomic potentials

\[V(r) = \sum_i V_i(r-r_i) \quad,\]

where \(V_i(r)\) is the atomic potential of the \(i\)’th atom. This is known as the Indenpendent Atom Model (IAM).

Below we create a Potential object, which represents a sliced IAM potential using a given parametrization. The parameter sampling denotes the spacing of the \(xy\)-sampling of the potential.

atoms = read('data/srtio3_110.cif')

potential = abtem.Potential(atoms, sampling=.05, parametrization='lobato', slice_thickness=1)
potential_array = potential.build().compute()

Indexing a Potential returns the individual slices. Below we show three slices from the SrTiO3 potential.

potential_array.images()[1:7:1].show(explode=True, figsize=(16,5))

Hyperspy: Interactive visualizations


Building and saving the potential

The Potential object does not store the calculated potential slices. Hence, if a simulation, such as STEM, requires multiple propagations, each slice have to be calculated multiple times. For this reason, abTEM often precalculates the potential, whenever it has to be used more than once.

The potential can be precalculated manually using the .build method. Note that it should typically be preferred to let abTEM precalculate the potential.

potential_array = potential.build(lazy=True)

This returns an PotentialArray object, which stores each 2D potential slice in a 3D array. The first dimension is the slice index and the last two are the spatial dimensions.

Array Chunk
Bytes 5.35 MiB 5.35 MiB
Shape (27, 221, 235) (27, 221, 235)
Count 4 Tasks 1 Chunks
Type float32 numpy.ndarray
235 221 27

The calculated potential can be stored in the zarr file format and read back.

potential_array.to_zarr('data/srtio3_110_potential.zarr', overwrite=True)
<abtem.potentials.potentials.PotentialArray at 0x16ce1db2b50>

Slicing of the potential

The multislice method requires a mathematical slicing of the potential. The multislice algorithm is only correct in the limit of thin slices, however, more slices increases the computational cost. A reasonable value for slice thickness is generally between \(0.5 \ Å\) and \(2 \ Å\). The default is \(0.5 \ Å\).

Finite projections

abTEM implements an accurate finite potential projection method. Numerical integration is used to calculate the integrals of the form

\[V_{proj}^{(i)}(x, y) = \int_{z_i}^{z_i+\Delta z} V(x,y,z) dz \quad ,\]

where \(z_i\) is the \(z\)-position at the entrance of the \(i\)’th slice and \(\Delta z\) is the slice thickness. The numerical integrals are efficiently handled by the double exponential Tanh–Sinh quadrature, which is designed for accurate results using a minimum number of evaluations for functions with singularities.


The potential of a single atom is very localized, but in principle infinite in extent, hence we need to set a reasonable cutoff. The cutoff is calculated by solving the equation

\[V(r) = V_{tol} \quad ,\]

where \(V_{tol}\) is the error at the cut-off. The equation is solved for each species. The use of the cut-off radius creates a discontinuity; hence, abTEM uses a tapering near the cut-off. \(V_{cut}\) can be modified using the cutoff_tolerance argument of the Potential or AtomicPotential objects. abTEM uses a tapering cutoff starting at \(85 \ \%\) of the full cutoff.

print('Oxygen cutoff:', abtem.AtomicPotential('O', cutoff_tolerance=1e-3).cutoff)
print('Strontium cutoff:', abtem.AtomicPotential('Sr', cutoff_tolerance=1e-3).cutoff)
Oxygen cutoff: 2.63498745959192
Strontium cutoff: 6.346935466282788

Infinite projections

Since the finite projection method can be computationally demanding, abTEM also implements the infinite projection scheme. The finite integrals are replaced by infinite integrals, which may be evaluated analytically

\[\int_{-\infty}^{\infty} V(x,y,z) dz \approx \int_{z_i}^{z_i+\Delta z} V(x,y,z) dz\]

The infinite projection of the atomic potential for each atom is assigned to a single slice. The implementation uses the hybrid real-space/Fourier-space approach by W. van den Broek et. al. (https://doi.org/10.1016/j.ultramic.2015.07.005). Using infinite projections is up to 10 times faster on CPU and up to 100 times faster on GPU, especially for potentials with a large numbers of atoms.

Below we create a potential with infinite projections:

potential = abtem.Potential(atoms, projection='infinite', sampling=.05)

<abtem.potentials.potentials.PotentialArray at 0x16cdf3ef7c0>

Crystal potentials


[ ]: