Multilayers and Nexus references

This chapter deals with multilayers but it also serves as an example how to deal with the pass by object reference strategy of Python and Nexus. Remember the Introduction on how objects are referenced.

For multilayers there are mainly two different ways to set them up. Either define every layer on its own or define only the layers of the unit cell. The main difference is that, in the first case, all layer are independent objects. During a fit, each layer’s density, thickness, roughness and so on are fit individually as long as no common Var objects are used. In the second case, all layers share the same properties of the unit cell.

Let’s create a multilayer made of a unit cell of Ta and SiC layer with 10 repetitions. We want to have 20 independent layers. The simplest method would be to simply define 20 layers by hand. However, it is a lot of work and not very clear. Therefore, we use a python function to create the unit cell layers for us and return them as individual objects in a list.

import nexus as nx

# function to create a Ta and SiC layer
# the function returns two new layers each time it is called
def create_layers(number):
    # for each material and layer we use number for indexing the id
    # this helps to find the layers in the outputs from Nexus

    # the instances mat_ta, layer_ta, mat_sic and layer_sic cannot be referenced
    # outside the function as the names only exist in the function

    mat_ta = nx.Material.Template(nx.lib.material.Ta)
    mat_ta.id = "mat Ta "+ str(number)

    layer_ta = nx.Layer(id = "lay Ta "+str(number),
                        thickness = nx.Var(2, min = 1, max = 3, fit = True, id = "ta thickness "+str(i)),  # nm
                        material = mat_ta,
                        roughness = 0.2)

    mat_sic = nx.Material.Template(nx.lib.material.SiC)
    mat_sic.id = "mat SiC "+ str(number)

    layer_sic = nx.Layer(id = "lay SiC "+str(number),
                         thickness = nx.Var(3, min = 2, max = 4, fit = True, id = "sic thickness "+str(i)),  # nm
                         material = mat_sic,
                         roughness = 0.1)

    return [layer_ta, layer_sic]

# we have to pass the complete list of objects to the Sample
# so we create an empty list called multilayer
multilayer = []

# and add the two layers 10 times
# index starts with 1
for i in range(1, 11, 1):
    # use list.extend function and not .append here because
    # the create function returns a list of layer objects
    multilayer.extend(create_layers(i))

# define the substrate
substrate = nx.Layer(id = "substrate",
                     material = nx.Material.Template(nx.lib.material.Si),
                     thickness = nx.inf,
                     roughness = 0.1)

# add the substrate
# append a single layer object
multilayer.append(substrate)

# define the sample
sample = nx.Sample(id = "my multilayer",
                   layers = multilayer)

print(sample)

# you get an index for each layer, which can be used to reference that layers in the sample
# now we can access the layers in the sample like this
# change the thickness and density of the second Ta layer "lay Ta 2"
sample.layers[2].thickness = 7

sample.layers[2].material.density = 13

print(sample)

# only the thickness and density of the second Ta layer has changed.

This method is useful when you want to fit all the layers properties individually.

The second way is to define only the unit cell and pass the same unit cell layers to a list multiple times.

import nexus as nx

mat_ta = nx.Material.Template(nx.lib.material.Ta)

layer_ta = nx.Layer(id = "lay Ta ",
                    thickness = nx.Var(2, min = 1, max = 3, fit = True, id = "ta thickness "),  # nm
                    material = mat_ta,
                    roughness = 0.2)

mat_sic = nx.Material.Template(nx.lib.material.SiC)

layer_sic = nx.Layer(id = "lay SiC ",
                     thickness = nx.Var(3, min = 2, max = 4, fit = True, id = "sic thickness "),  # nm
                     material = mat_sic,
                     roughness = 0.1)

# define the multilayer
multilayer = 10 * [layer_ta, layer_sic]
# is the same as writing
#      multilayer = [layer_ta, layer_sic, layer_ta, layer_sic, ..., layer_ta, layer_sic]
# the same object reference is passed again and again to the list
# so each Ta and SiC are exactly the same layer

# define the substrate
substrate = nx.Layer(id = "substrate",
                     material = nx.Material.Template(nx.lib.material.Si),
                     thickness = nx.inf,
                     roughness = 0.1)

# append the substrate
multilayer.append(substrate)

# define the sample
sample = nx.Sample(id = "my multilayer",
                   layers = multilayer)

print(sample)

# now we change the thickness of all Ta layers
# it works because every Ta layer in the sample references to the same objects layer_ta
layer_ta.thickness = 4

print(sample)

This is very helpful when using theoretical calculations to design multilayers or for a fit where all unit cell repetitions are assumed to be equal. It also has the benefit that the hyperfine properties of the layers just have to be calculated once, which makes this method faster for nuclear measurements.

Note

Working with references gives a lot of possibilities for combined parameter changes and fitting. Be sure to understand this feature in order to use Nexus full potential. If you are unsure how references will affect your experiment, the best option is to create each object individually.

Please note that how you create the multilayer strongly depends on how the parameters of the layers should be connected. Always remember that you pass references to Nexus objects.

It is also possible to combine the two methods when setting up multilayers. For example, you can create a multilayer where the material is exactly the same in each layer but the layer thickness and roughness are independent.

import nexus as nx

# define the materials
mat_ta = nx.Material.Template(nx.lib.material.Ta)

mat_sic = nx.Material.Template(nx.lib.material.SiC)

# now we pass the same material to all layers in the function
def create_layers(number):

    layer_ta = nx.Layer(id = "lay Ta "+str(i),
                        thickness = nx.Var(2, min = 1, max = 3, fit = True, id = "ta thickness "+str(i)),  # nm
                        material = mat_ta,
                        roughness = 0.2)

    layer_sic = nx.Layer(id = "lay SiC "+str(i),
                         thickness = nx.Var(3, min = 2, max = 4, fit = True, id = "sic thickness "+str(i)),  # nm
                         material = mat_sic,
                         roughness = 0.1)

    return [layer_ta, layer_sic]

multilayer = []

for i in range(1, 11, 1):
    multilayer.extend(create_layers(i))

substrate = nx.Layer(id = "substrate",
                     material = nx.Material.Template(nx.lib.material.Si),
                     thickness = nx.inf,
                     roughness = 0.1)

multilayer.append(substrate)

# define the sample
sample = nx.Sample(id = "my multilayer",
                   layers = multilayer)

print(sample)

# change the density of all tantalum layers
mat_ta.density = 14

# and the thickness of only the second ta_layer
sample.layers[2].thickness = 7

print(sample)

# the density has changed in all tantalum layers but
# only the thickness of the second Ta layer has changed.

Notebooks

multilayer 1 - nb_multilayer_1.ipynb.

multilayer 2 - nb_multilayer_2.ipynb.

multilayer 3 - nb_multilayer_3.ipynb.