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 so-called indepedent atom model (IAM) is used, which neglects any bonding effects and treats the sample as an array of atomic potentials.

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.

Simulation codes include a parametrization of the atomic potentials, with a table of parameters for each element fitted to Hartree-Fock calculations. Below we show the radial depedence of the electrostatic potential of five selected elements, using the default setting in abTEM. This parametrization by Lobato et. al. is one of the latest and most accurate (doi:10.1107/S205327331401643X).

The radial dependence of the atomic potential for isolated single atoms is shown below.

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.

[4]:
from ase.io import read
from abtem.potentials import Potential

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

potential = Potential(atoms, sampling=.05, parametrization='lobato')

We can show the projected potential (this requires building the potential).

[5]:
potential.project().show();
../_images/walkthrough_02_potentials_8_0.png

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

[6]:
import matplotlib.pyplot as plt

fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(10, 3))
potential[3].project().show(ax=ax1)
potential[5].project().show(ax=ax2)
potential[7].project().show(ax=ax3);
../_images/walkthrough_02_potentials_10_0.png

Building 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 precalculates the potential whenever it has to be used more than once. The potential can also be precalculated manually using the .build method.

[5]:
precalculated_potential = potential.build(pbar=True)

This returns an ArrayPotential 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.

[6]:
precalculated_potential, type(precalculated_potential.array), precalculated_potential.array.shape
[6]:
(<abtem.potentials.PotentialArray at 0x15117153a90>,
 numpy.ndarray,
 (54, 221, 235))

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

[7]:
precalculated_potential.write('data/srtio3_110_potential.hdf5')
[8]:
from abtem.potentials import PotentialArray

PotentialArray.read('data/srtio3_110_potential.hdf5')
[8]:
<abtem.potentials.PotentialArray at 0x150e22997c0>

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.

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

\[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 object. abTEM uses a tapering cutoff starting at \(85 \ \%\) of the full cutoff.

[9]:
potential = Potential(atoms, cutoff_tolerance=1e-3)

print('Oxygen cutoff:', potential.get_cutoff(8))
print('Strontium cutoff:', potential.get_cutoff(38))
Oxygen cutoff: 1.818317912643183
Strontium cutoff: 4.141957979160105

Cache

Once a projection with some limits is calculated, it will be cached, by default to within 0.01 Å of either limit. The number of decimals used in the cache keys can be modified using the cache_decimals argument, the cache can be disabled by setting cache_decimals to None.

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 (this currently requires using the Kirkland parametrization):

[10]:
potential = Potential(atoms, projection='infinite', parametrization='kirkland', sampling=.05)

potential.build()
[10]:
<abtem.potentials.PotentialArray at 0x15117153d60>