[1]:
#%matplotlib qt
from ase.io import read
import matplotlib.pyplot as plt
import abtem
Potential¶
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 socalled 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 firstprinciples 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 HartreeFock calculations.
The radial dependence of the electrostatic potential and (electron) scattering factor of five selected elements is shown below.
[428]:
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.
[429]:
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))
lobato_scattering_factors.show(ax=ax1)
kirkland_scattering_factors.show(ax=ax2)
peng_scattering_factors.show(ax=ax3);
Independent atom model¶
The full specimen potential, \(V(r)\), is usually obtained as a linear superposition of atomic potentials
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.
[79]:
atoms = read('data/srtio3_110.cif')
potential = abtem.Potential(atoms, sampling=.05, parametrization='lobato', slice_thickness=1)
[77]:
potential_array = potential.build().compute()
Indexing a Potential
returns the individual slices. Below we show three slices from the SrTiO3 potential.
[78]:
potential_array.images()[1:7:1].show(explode=True, figsize=(16,5))
Hyperspy: Interactive visualizations
potential.build(lazy=False).to_hyperspy().plot()
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.
[24]:
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.
[25]:
potential_array.array
[25]:

The calculated potential can be stored in the zarr
file format and read back.
[26]:
potential_array.to_zarr('data/srtio3_110_potential.zarr', overwrite=True)
[27]:
abtem.from_zarr('data/srtio3_110_potential.zarr')
[27]:
<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
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.
Cutoff¶
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
where \(V_{tol}\) is the error at the cutoff. The equation is solved for each species. The use of the cutoff radius creates a discontinuity; hence, abTEM uses a tapering near the cutoff. \(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.
[29]:
print('Oxygen cutoff:', abtem.AtomicPotential('O', cutoff_tolerance=1e3).cutoff)
print('Strontium cutoff:', abtem.AtomicPotential('Sr', cutoff_tolerance=1e3).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
The infinite projection of the atomic potential for each atom is assigned to a single slice. The implementation uses the hybrid realspace/Fourierspace 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:
[31]:
potential = abtem.Potential(atoms, projection='infinite', sampling=.05)
potential.build()
[31]:
<abtem.potentials.potentials.PotentialArray at 0x16cdf3ef7c0>
Crystal potentials¶
Simulations
[ ]:
potential_unit
crystal_potential