A multilayer fitting model#
One of the main tools in easyreflectometry
is the assemblies library. This allows the user to define their model, using specific parameters for their system of interest (if it is included in the assemblies library). These assemblies will impose necessary constraints and computational efficiencies based on the assembly that is used.
In this tutorial, we will look at one of these assemblies, that of a RepeatingMultilayer
(documented here). This tutorial is based on an example from the BornAgain documentation looking at specular reflectivity analysis. Before performing analysis, we should import the packages that we need.
First configure matplotlib to place figures in notebook and import needed modules
[1]:
%matplotlib inline
import numpy as np
import scipp as sc
import pooch
import refl1d
import easyreflectometry
from easyreflectometry.data import load
from easyreflectometry.sample import Layer
from easyreflectometry.sample import Sample
from easyreflectometry.sample import Material
from easyreflectometry.sample import RepeatingMultilayer
from easyreflectometry.sample import Multilayer
from easyreflectometry.model import Model
from easyreflectometry.model import PercentageFwhm
from easyreflectometry.calculators import CalculatorFactory
from easyreflectometry.fitting import MultiFitter
from easyreflectometry.plot import plot
from easyscience.fitting import AvailableMinimizers
As mentioned in the previous tutorial, we share the version of the software packages we will use.
[2]:
print(f'numpy: {np.__version__}')
print(f'scipp: {sc.__version__}')
print(f'easyreflectometry: {easyreflectometry.__version__}')
print(f'Refl1D: {refl1d.__version__}')
numpy: 1.26.0
scipp: 24.02.0
easyreflectometry: 1.3.0
Refl1D: 0.8.16
Reading in experimental data#
The data that we will investigate in this tutorial was generated with GenX and is stored in an .ort
format file. We use pooch
to fetch the file from the repository.
[3]:
file_path = pooch.retrieve(
# URL to one of Pooch's test files
url="https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/fitting/repeating_layers.ort",
known_hash="a5ffca9fd24f1d362266251723aec7ce9f34f123e39a38dfc4d829c758e6bf90",
)
data = load(file_path)
Downloading data from 'https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/fitting/repeating_layers.ort' to file '/home/runner/.cache/pooch/13c1608abd74b50f2c27c72af9126344-repeating_layers.ort'.
This data is very featureful, with many fringes present (arising from the multilayer structure)
[4]:
plot(data)
Building our model#
The system that was used to produce the data shown above is based on a silicon subphase, with a repeating multilayer of nickel and titanium grown upon it. Typcially, under experimental conditions, the producer of the sample will know how many repeats there will be of the multilayer system (as these are grown using some vapour disposition or sputtering method that the producer controls). We show the model that will be used graphically below.
A slab model description of the repeating multilayer, showing the four layers of vacuum, titanium, nickel and silicon, with the titanium/nickel layers being repeated 10 times.
To construct such a layer structure, first we create each of the materials and associated layers
[5]:
vacuum = Material(sld=0, isld=0, name='Vacuum')
ti = Material(sld=-1.9493, isld=0, name='Ti')
ni = Material(sld=9.4245, isld=0, name='Ni')
si = Material(sld=2.0704, isld=0, name='Si')
[6]:
superphase = Layer(material=vacuum, thickness=0, roughness=0, name='Vacuum Superphase')
ti_layer = Layer(material=ti, thickness=40, roughness=0, name='Ti Layer')
ni_layer = Layer(material=ni, thickness=70, roughness=0, name='Ni Layer')
subphase = Layer(material=si, thickness=0, roughness=0, name='Si Subphase')
Then, to produce the repeating multilayer, we use the RepeatingMultilayer
assembly type. This can be constructed in a range of different ways, however here we pass a list of Layer
type objects and a number of repetitions.
[7]:
rep_multilayer = RepeatingMultilayer([ti_layer, ni_layer], repetitions=10, name='NiTi Multilayer')
rep_multilayer
[7]:
NiTi Multilayer:
Ti Layer/Ni Layer:
- Ti Layer:
material:
Ti:
sld: -1.949e-6 1/Å^2
isld: 0.000e-6 1/Å^2
thickness: 40.000 Å
roughness: 0.000 Å
- Ni Layer:
material:
Ni:
sld: 9.425e-6 1/Å^2
isld: 0.000e-6 1/Å^2
thickness: 70.000 Å
roughness: 0.000 Å
repetitions: 10.0
From these objects, we can construct our structure and combine this with a scaling, background and resolution (since this data is simulated there is no background or resolution smearing).
[8]:
resolution_function = PercentageFwhm(0)
sample = Sample(Multilayer(superphase), rep_multilayer, Multilayer(subphase), name='Multilayer Structure')
model = Model(
sample=sample,
scale=1,
background=0,
resolution_function=resolution_function,
name='Multilayer Model'
)
In the analysis, we will only vary a single parameter, the thickness of titanium layer.
[9]:
ti_layer.thickness.bounds = (10, 60)
Choosing our calculation engine#
In the previous tutorial, we used the default refnx engine for our analysis. Here, we will change our engine to be Refl1D. This is achieved with the interface.switch('refl1d')
method below.
[10]:
interface = CalculatorFactory()
interface.switch('refl1d')
model.interface = interface
print(interface.current_interface.name)
refl1d
Performing an optimisation#
The easyScience framework allows us to access a broad range of optimisation methods. Below, we have selected the differential evolution method from lmfit.
[11]:
fitter = MultiFitter(model)
fitter.switch_minimizer(AvailableMinimizers.LMFit_differential_evolution)
analysed = fitter.fit(data)
analysed
[11]:
- datadict(){'R_0': <scipp.Variable> (Qz_0: 400) float64 [dimensionless] [1, 1, ..., 8....
- coordsdict(){'Qz_0': <scipp.Variable> (Qz_0: 400) float64 [1/Å] [0.000712093, ...
- attrsdict(){'R_0': {'orso_header': <scipp.Variable> () PyObject <no unit> {'data_...
- R_0_modelscippVariable(Qz_0: 400)float64𝟙1.000, 1.000, ..., 9.474e-08, 4.953e-09
- SLD_0scippVariable(z_0: 10200)float641/Å^28.882e-22, 8.882e-22, ..., 2.070e-06, 2.070e-06
We can visualise the analysed model and SLD profile with the plot
function.
[12]:
plot(analysed)
The value of the titanium layer thickness that gives this best fit can be found from the relavant object. Note that the uncertainty of 0 is due to the use of the lmfit differential evolution algorithm, which does not include uncertainty analysis.
[13]:
ti_layer.thickness
[13]:
<Parameter 'thickness': 29.9879 Å, bounds=[10.0:60.0]>
This result of a thickness of 30 Å is the same as that which is used to produce the data.