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()

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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88  #!/usr/bin/env python3 """ Basic example of depth-probe simulation with BornAgain. Sample layers are Si | Ti | Pt | Ti | TiO2 | D2O. Beam comes from Si side. Therefore we model the stack with Si on top. The z axis points from D2O to Si; z=0 is at the Si/Ti interface. """ import bornagain as ba from bornagain import angstrom, deg, nm, nm2, kvector_t # layer thicknesses in angstroms t_Ti = 130*angstrom t_Pt = 320*angstrom t_Ti_top = 100*angstrom t_TiO2 = 30*angstrom # beam data ai_min = 0 # minimum incident angle ai_max = 1*deg # maximum incident angle n_ai_bins = 5000 # number of bins in incident angle axis beam_sample_ratio = 0.01 # beam-to-sample size ratio wl = 10*angstrom # wavelength in angstroms # convolution parameters d_ang = 0.01*ba.deg # spread width for incident angle n_points = 25 # number of points to convolve over n_sig = 3 # number of sigmas to convolve over # depth position span z_min = -100*nm z_max = 100*nm n_z_bins = 500 def get_sample(): """ Constructs a sample with one resonating Ti/Pt layer """ # Define materials material_D2O = ba.HomogeneousMaterial("D2O", 0.00010116, 1.809e-12) material_Pt = ba.HomogeneousMaterial("Pt", 0.00010117, 3.01822e-08) material_Si = ba.HomogeneousMaterial("Si", 3.3009e-05, 0) material_Ti = ba.HomogeneousMaterial("Ti", -3.0637e-05, 1.5278e-08) material_TiO2 = ba.HomogeneousMaterial("TiO2", 4.1921e-05, 8.1293e-09) # Define layers layer_1 = ba.Layer(material_Si) layer_2 = ba.Layer(material_Ti, 13*nm) layer_3 = ba.Layer(material_Pt, 32*nm) layer_4 = ba.Layer(material_Ti, 10*nm) layer_5 = ba.Layer(material_TiO2, 3*nm) layer_6 = ba.Layer(material_D2O) # Define sample sample = ba.MultiLayer() sample.addLayer(layer_1) sample.addLayer(layer_2) sample.addLayer(layer_3) sample.addLayer(layer_4) sample.addLayer(layer_5) sample.addLayer(layer_6) return sample def get_simulation(sample): """ Returns a depth-probe simulation. """ alpha_distr = ba.DistributionGaussian(0, d_ang) footprint = ba.FootprintSquare(beam_sample_ratio) simulation = ba.DepthProbeSimulation() simulation.setBeamParameters(wl, n_ai_bins, ai_min, ai_max, footprint) simulation.setZSpan(n_z_bins, z_min, z_max) simulation.addParameterDistribution("*/Beam/InclinationAngle", alpha_distr, n_points, n_sig) simulation.setSample(sample) return simulation if __name__ == '__main__': import ba_plot sample = get_sample() simulation = get_simulation(sample) ba_plot.run_and_plot(simulation, aspect='auto') 
DepthProbe.py