Depth-probe simulation tutorial

Depth-probe simulation is an auxiliary simulation type, which helps to visualize the total intensity in dependence on the beam incidence angle and the position in the sample.

Here we will consider the intensity map produced by a neutron resonator, composed of one Ti/Pt bilayer.

Used conventions

Depth-probe simulation takes into account the position across the surface of the sample. This position will be denoted as “z” and measured in nanometers. The surface of the sample will be assigned to $z = 0$, while the direction of z-axis will be from the bulk of the sample out to the ambient. That is, $z < 0$ corresponds to the region from the substrate to the surface of the sample, and $z > 0$ corresponds to the ambient media.

Sample description

The layout of the sample is presented in the figure above. A Ti-Pt sample on a silicon substrate is placed in $D_2 O$ environment and is irradiated by a diagnostic beam. Note that in this layout the beam enters the sample from the substrate side. For this reason the sample has reversed structure, with silicon being defined as the “ambient media” and $D_2 O$ being defined as the “substrate”.

Before constructing the sample we will import bornagain library and units which will be used throughout our tutorial:

import bornagain as ba
from bornagain import deg, angstrom, nm

The process of constructing the sample is the same as the one described in
GISAS and reflectometry tutorials. First of all we will define the get_sample function:

def get_sample():
    """
    Constructs a sample with one resonating Ti/Pt bilayer
    """

Then we will create the materials of the sample, substrate and the ambient media:

    # define materials
    m_Si = ba.HomogeneousMaterial("Si", 3.3009e-05, 0.0)
    m_Ti = ba.HomogeneousMaterial("Ti", -3.0637e-05, 1.5278e-08)
    m_TiO2 = ba.HomogeneousMaterial("TiO2", 4.1921e-05, 8.1293e-09)
    m_Pt = ba.HomogeneousMaterial("Pt", 1.0117e-04, 3.01822e-08)
    m_D2O = ba.HomogeneousMaterial("D2O", 1.0116e-04, 1.8090e-12)

Note, that here we set the attenuation factor for silicon to zero. It is necessary, since this material will serve as the media of the beam propagation and is assumed to be semi-infinite. Thus setting a non-zero attenuation factor will lead to complete dissipation of the beam even before its entering the sample.

After the materials are defined, it is necessary to create the layers and the MultiLayer object representing the whole sample:

    # create layers
    l_Si = ba.Layer(m_Si)
    l_Ti = ba.Layer(m_Ti, 130.0 * angstrom)
    l_Pt = ba.Layer(m_Pt, 320.0 * angstrom)
    l_Ti_top = ba.Layer(m_Ti, 100.0 * angstrom)
    l_TiO2 = ba.Layer(m_TiO2, 30.0 * angstrom)
    l_D2O = ba.Layer(m_D2O)

    # construct sample from top to bottom
    sample = ba.MultiLayer()
    sample.addLayer(l_Si)

    sample.addLayer(l_Ti)
    sample.addLayer(l_Pt)

    sample.addLayer(l_Ti_top)
    sample.addLayer(l_TiO2)
    sample.addLayer(l_D2O)

    return sample

Setting up the depth-probe simulation

Depth-probe simulation can be created with the command DepthProbeSimulation(), while its parameters are set with setBeamParameters and setZSpan. The signature of setBeamParameters coincides completely with the signature of analogous method in SpecularSimulation:

<simulation>.setBeamParameters(wavelength, n_bins, angle_min, angle_max)

Here <simulation> is the simulation object created with DepthProbeSimulation command, wavelength is the wavelength of the incident beam (in nanometers by default), angle_min and angle_max — minimum and maximum incident angles (in radians by default). Units of the input arguments can be adjusted by multiplying factors deg, angstrom, etc. n_bins defines the number of points uniformly distributed from angle_min to angle_max.

setZSpan allows to define the coordinates across the sample surface, at which the signal intensities should be calculated. The signature of the command is

<simulation>.setZSpan(n_z_bins, z_min, z_max)

Again, <simulation> is the object created with DepthProbeSimulation command, z_min and z_max denote the limits of the z-axis (in nanometers by default), while n_z_bins defines the number of points uniformly distributed between z_min and z_max.

Let us write down get_simulation function, which creates a depth-probe simulation with incident angle range from $0^{\circ}$ to $1^{\circ}$ and 5000 points in between. The z-axis will be defined as the range $[-100, 100]$ nm with 500 points.

def get_simulation():
    """
    Returns a depth-probe simulation.
    """
    simulation = ba.DepthProbeSimulation()
    simulation.setBeamParameters(10 * angstrom, 5000, 0.0 * deg, 1.0 * deg)
    simulation.setZSpan(500, -100 * nm, 100 * nm)
    return simulation

Running the simulation and plotting the results

Running the simulation does not differ from doing it with any other type of simulation in BornAgain:

def run_simulation():
    """
    Runs simulation and returns its result.
    """
    sample = get_sample()
    simulation = get_simulation()
    simulation.setSample(sample)
    simulation.runSimulation()
    return simulation.result()

Plotting can be done with built-in BornAgain function plot_simulation_result:

if __name__ == '__main__':
    result = run_simulation()
    ba.plot_simulation_result(result)

As a result of executing the script, the following image should be displayed on the screen

In this figure the y-axis corresponds to the position across the sample surface (in nanometers), while the x-axis corresponds to the incident angle values $\alpha_i$.

Complete script

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
"""
Basic example of depth-probe simulation with BornAgain.
"""
import bornagain as ba
from bornagain import deg, angstrom, nm


def get_sample():
    """
    Constructs a sample with one resonating Ti/Pt bilayer
    """

    # define materials
    m_Si = ba.HomogeneousMaterial("Si", 3.3009e-05, 0.0)
    m_Ti = ba.HomogeneousMaterial("Ti", -3.0637e-05, 1.5278e-08)
    m_TiO2 = ba.HomogeneousMaterial("TiO2", 4.1921e-05, 8.1293e-09)
    m_Pt = ba.HomogeneousMaterial("Pt", 1.0117e-04, 3.01822e-08)
    m_D2O = ba.HomogeneousMaterial("D2O", 1.0116e-04, 1.8090e-12)

    # create layers
    l_Si = ba.Layer(m_Si)
    l_Ti = ba.Layer(m_Ti, 130.0 * angstrom)
    l_Pt = ba.Layer(m_Pt, 320.0 * angstrom)
    l_Ti_top = ba.Layer(m_Ti, 100.0 * angstrom)
    l_TiO2 = ba.Layer(m_TiO2, 30.0 * angstrom)
    l_D2O = ba.Layer(m_D2O)

    # construct sample from top to bottom
    sample = ba.MultiLayer()
    sample.addLayer(l_Si)

    sample.addLayer(l_Ti)
    sample.addLayer(l_Pt)

    sample.addLayer(l_Ti_top)
    sample.addLayer(l_TiO2)
    sample.addLayer(l_D2O)

    return sample


def get_simulation():
    """
    Returns a depth-probe simulation.
    """
    simulation = ba.DepthProbeSimulation()
    simulation.setBeamParameters(10 * angstrom, 5000, 0.0 * deg, 1.0 * deg)
    simulation.setZSpan(500, -100 * nm, 100 * nm)
    return simulation


def run_simulation():
    """
    Runs simulation and returns its result.
    """
    sample = get_sample()
    simulation = get_simulation()
    simulation.setSample(sample)
    simulation.runSimulation()
    return simulation.result()


if __name__ == '__main__':
    result = run_simulation()
    ba.plot_simulation_result(result)
DepthProbe.py