import numpy as np
import escape as esc
esc.require("0.9.8")
Loading material database from C:\dev\escape-core\python\src\escape\scattering\..\data\mdb\materials.db
Specular reflectivity. (Ta/Cu/Nb/Gd/Nb/Al2O3). Example with mass density fit.¶
This notebook shows a multilayer X-ray reflectivity fit for a Nb/Gd/Nb structure on Al2O3. The example is useful for understanding how density-based parameters and layer interfaces shape the measured reflectivity curve.
In this notebook we are going to fit the data published in the following article [https://doi.org/10.1103/PhysRevB.97.144511]. The given sample is a Nb(25nm)/Gd(7nm)/Nb(25nm) trilayer on a Al2O3 substrate and covered with Ta/Cu thin films. In the previous notebook about specular reflectivity we have demonstarted how the module works, where as an experimental data we took the curve generated with Parratt32 program. The idea of this notebook is to demonstrate how to work with real experimental data in terms of ESCAPE. The result obtained here could be slightly different from what is published, because we do not have all details about published model.
We first define our parameters, which we are going to fit. The names of these parameters are self-explanatory.
thknTa = esc.par("Ta Thkn", 3.0, units="nm", userlim=[1, 5])
roughTa = esc.par("Ta Roughness", 0.1, units="nm", userlim=[0, 3])
thknCu = esc.par("Cu Thkn", 4.0, units="nm", userlim=[3, 7])
roughCu = esc.par("Cu Roughness", 0.5, units="nm", userlim=[0, 4])
thknGd = esc.par("Gd Thkn", 7.6, units="nm", userlim=[0, 10])
roughGd = esc.par("Gd Roughness", 0.5, units="nm", userlim=[0, 5])
thknNb1 = esc.par("Nb1 Thkn", 25.0, units="nm", userlim=[20, 30])
roughNb1 = esc.par("Nb1 Roughness", 1.0, units="nm", userlim=[0, 5])
thknNb2 = esc.par("Nb2 Thkn", 25.0, units="nm", userlim=[20, 30])
roughNb2 = esc.par("Nb2 Roughness", 1.0, units="nm", userlim=[0, 5])
roughAl2O3 = esc.par("Al2O3 Roughness", 1.0, units="nm", userlim=[0, 1])
Next we create materials, layers and finally the sample objects.
Al2O3 = esc.amorphous(
"Al2O3", formula="Al2O3", density=esc.par(value="3.95g/cm^3", fixed=True)
)
ta_layer = esc.layer("Ta", thknTa, roughTa, bydensity=True)
cu_layer = esc.layer("Cu", thknCu, roughCu, bydensity=True)
gd_layer = esc.layer("Gd", thknGd, roughGd, bydensity=True)
nb1_layer = esc.layer("Nb", thknNb1, roughNb1, bydensity=True)
nb2_layer = esc.layer("Nb", thknNb2, roughNb2, bydensity=True)
sub = esc.substrate(Al2O3, roughAl2O3, bydensity=True)
sample = esc.multilayer(name="Ta/Cu/Nb1/Gd/Nb2/Al2O3", frgr=esc.air("Air"), bkgr=sub)
sample.add(ta_layer)
sample.add(cu_layer)
sample.add(nb1_layer)
sample.add(gd_layer)
sample.add(nb2_layer)
wl = 0.229 # we use it later
src = esc.xrays(wavelength=wl, units="nm")
sample.show(source=src)
The contribution of resolution function is negligible, but as a demonstration we will take it into account. The beam FWHM can be also defined as a parameter. This can help, if resolution function cannot be estimated properly. Normally this paramater is fixed.
Qz = esc.var("qz")
fwhm = esc.par("FWHM", 2.0e-2, userlim=[0.001, 0.02], fixed=True)
R = esc.specrefl(Qz, sample, "matrix", source=src)
# we integrate over the variable Qz, thus intensity is a function of I(Qz0)
R = esc.average_normal(R, fwhm, Qz)
Below we take into account sample size. At low angles the X-rays or neutrons beam doesn't cover the whole sample leading to the reduction of intensity. Usually this correction is done for the data, but can be also added to the model as a fit parameter in the case if some characteristics are not known.
I0 = esc.par("I0", 1, scale=1e7, userlim=[0.1, 10], units="Cnt")
B = esc.par("Bgr", 10, userlim=[0, 30], units="Cnt")
h = 0.05 # size of a beam
L = 5 # size of a sample
Qmax = esc.par(
"Qmax", 4 * np.pi / wl * h / L, fixed=True, userlim=[0, 1.5], units="1/nm"
)
I0f = esc.conditional(Qz > Qmax, esc.func(Qz, I0), I0 / (Qmax / Qz))
I = I0f * R + B
# Opening the experimental data and creating the data object
theta, y = np.loadtxt("data/GdNb/TriLayer/XRR/xrr2.dat", unpack=True)
qz = 4 * np.pi / wl * np.sin((theta / 2) * np.pi / 180.0)
qz_r = qz[(qz >= 0.15) & (qz <= 3)]
y_r = y[(qz >= 0.15) & (qz <= 3)]
print(qz_r.size, y_r.size)
dobj = esc.data(qz_r, y_r, copy=True)
596 596
# Creating the model
mobj = esc.model(I, dobj, residuals_scale="log", weight_type="none")
# Now we can perform optimization with DE algorithm
opt = esc.diffevol(
mobj,
popsize=6,
maxiter=150,
ftol=1e-5,
nupdate=5,
crossover=0.7,
maxfev=10000,
#strategy="best1bin",
polish_final_maxiter=50,
polish_candidate_maxiter=0,
)
opt()
opt.show().config_model(ylog=True, xlog=False, xlabel="Q[1/nm]", ylabel="R")
sample.show(source=src)
opt
Name: Differential Evolution Parameters number: 21 Parameter Value +- Error Units Fixed Qmax 0.54875 +- 0 1/nm 1 I0 1.3557 +- 0.00019993 x1e+07 Cnt 0 FWHM 0.02 +- 0 1 Ta Density 16.663 +- 0.0083433 0 Ta Thkn 3.9509 +- 0.0046787 nm 0 Ta Roughness 2.3442 +- 0.0018266 nm 0 Cu Density 4.8391 +- 0.0044598 0 Cu Thkn 4.9888 +- 0.01838 nm 0 Cu Roughness 0.83511 +- 0.00068212 nm 0 Nb Density 4.285 +- 0 0 Nb1 Thkn 25.903 +- 0.019642 nm 0 Nb1 Roughness 1.689 +- 0.021744 nm 0 Gd Density 7.5076 +- 0.0017482 0 Gd Thkn 9.6568 +- 0.0024783 nm 0 Gd Roughness 1.0817 +- 0.00242 nm 0 Nb Density 8.6878 +- 0.0018635 0 Nb2 Thkn 26.746 +- 0.0024649 nm 0 Nb2 Roughness 0.87448 +- 0.0021615 nm 0 Parameter 3.95 +- 0 g/cm^3 1 Al2O3 Roughness 0.69889 +- 0.001659 nm 0 Bgr 6.3855 +- 0.4075 Cnt 0