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();
../_images/walkthrough_04_multislice_6_0.png

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]);
../_images/walkthrough_04_multislice_10_0.png

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');
../_images/walkthrough_04_multislice_19_0.png

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]);
../_images/walkthrough_04_multislice_21_0.png

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();
../_images/walkthrough_04_multislice_24_0.png
[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();
../_images/walkthrough_04_multislice_27_0.png

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();
../_images/walkthrough_04_multislice_29_0.png
[ ]: