Multislice simulations (ED and CBED)¶
The multislice algorithm works by propagating the 2D lateral part of the wave function slice-by-slice forward along the optical axis (which in abTEM is by definition the positive z
direction). A forward step through a slice of the potential consists of a transmission and a propagation. The Wave
objects can be transmitted through a potential slice, which in the weak-phase approximation is just a phase shift. To complete one forward step, the transmitted wave is then propagated by a
distance corresponding to the thickness of the slice using the Fresnel free-space approximation.
We import the potential from the previous tutorial. We tile the potential to get a better Fourier space sampling in the diffraction patterns. We also set up a plane wave with an energy of 300 keV.
[1]:
from abtem.potentials import PotentialArray
from abtem.waves import PlaneWave
from ase.io import read
atoms = read('data/srtio3_110.cif')
wave = PlaneWave(energy=300e3, sampling=.05)
To propagate the wave function through the potential we use the .multislice
method.
[2]:
from abtem.potentials import Potential
pw_exit_wave = wave.multislice(atoms)
pw_exit_wave.write('data/srtio3_110_exit_wave.hdf5')
We show the intensity of the resulting exit wave.
[3]:
pw_exit_wave.show();

The electron diffraction pattern can be obtained using the .diffraction_pattern
method.
[4]:
pw_diffraction_pattern = pw_exit_wave.diffraction_pattern()
The be able to show the diffraction spots, we have to block the zeroth order spot. We also plot the diffraction pattern on a power scale to reveal slightly more higher order reflections, we set the power to 0.5
.
[5]:
from abtem.measure import block_zeroth_order_spot
pw_diffraction_pattern = block_zeroth_order_spot(pw_diffraction_pattern)
ax, im = pw_diffraction_pattern.show(power=.5, figsize=(6,6), cmap='jet')
ax.set_xlim([-40,40])
ax.set_ylim([-40,40]);

Note:
You may have noted that the grid of the wave function was not given above. Using the multislice function, the wave function just adopts the grid of the potential before starting the multislice propagation. On the other hand, the following will result in a runtime error because the sampling cannot be inferred from the information given.
wave = PlaneWave(energy=300e3)
potential = Potential(atoms)
exit_wave = wave.multislice(potential)
The next bit of code will also result in an error because the sampling of the wave function and the potential is different.
wave = PlaneWave(sampling=.1, energy=300e3)
potential = Potential(atoms, sampling=.05)
exit_wave = wave.multislice(potential)
Multislice simulation with probe¶
For a probe, we additionally define the probe convergence semiangle (in mrad) via semiangle_cutoff
, with the parameter rolloff
smoothing the cutoff, emulating a finite source size. Imaging modes utilizing an electron probe requires us to provide a positions for the probe (here a single location at 5 Å both in \(x\) and \(y\)).
[6]:
from abtem.waves import Probe
probe = Probe(energy=300e3, semiangle_cutoff=20, rolloff=.1, sampling=.05)
We can position the probe close to an atom by reloading our SrTiO\(_3\) model, and reading the xy
position of one of the Sr atoms.
[7]:
from ase.io import read
srtio3_110 = read('data/srtio3_110.cif')
pos = srtio3_110[1].position[:2]
We then simulate the exit wave via a multislice simulation of the probe placed at this position.
[8]:
probe_exit_wave = probe.multislice(positions=pos, potential=srtio3_110)
[9]:
probe_exit_wave.show(cmap='gray');

The convergent-beam electron diffraction (CBED) pattern can be calculated in the same manner as the ED pattern. We again display the result on a log scale. Note that to get more useful results, we should go back and increase our sampling of the potential.
[10]:
cbed_diffraction_pattern = probe_exit_wave.diffraction_pattern()
ax, im = cbed_diffraction_pattern.show(cmap='inferno')
ax.set_xlim([-60,60])
ax.set_ylim([-60,60]);

The multislice algorithm using the low-level interface¶
If additional control is necessary, it is possible to run the multislice algorithm using a lower-level interface. We start by building a plane wave wavefunction. We ensure that the wave function matches the potential by using .match
method of the wave function grid wave.grid
(note: the required potential was written to disk in Walkthrough #2).
[14]:
potential = PotentialArray.read('data/srtio3_110_potential.hdf5')
potential[4].project().show();

[15]:
wave = PlaneWave(energy=300e3)
wave.grid.match(potential)
wave = wave.build()
We then complete one step of the multislice algorithm by transmitting and then propagating the wave function. This requires importing the FresnelPropagator
class and the transmit
function.
[16]:
from abtem.waves import FresnelPropagator
potential_slice = potential[4]
propagator = FresnelPropagator()
wave = potential_slice.transmit(wave)
wave = propagator.propagate(wave, potential_slice.thickness)
wave.show();

The complete multislice algorithm simply repeats this for all slices sequentially, and hence it can be implemented as the following loop.
[17]:
for potential_slice in potential:
potential_slice.transmit(wave)
propagator.propagate(wave, potential_slice.thickness)
wave.show();

[ ]: