import numpy as np
import escape as esc
esc.require("0.9.7")
from escape.utils.widgets import show
Loading material database from /home/dkor/Data/Development/workspace_escape/escape/python/src/escape/scattering/../data/mdb/materials.db
PNR. General¶
Polarized neutron reflectometry is a scattering method, which is well-suited for determining the structural parameters of magnetic thin films and multilayers. The specular reflection of polarized neutrons can be analyzed to yield the in-plane average of the vector magnetization depth profile of a magnetic sample along the surface normal. For the analysis of the polarization state of neutrons the PNR experimental setup has a polarizer, analyzer and two filppers after the polarizer and before anaylzer. The polarizer, analyzer and flippers are characterized by their efficiency, which one can define in ESCAPE as model parameters.
If one is not familiar with polarized neutron relfectivity, there is a good introduction to this method and its theory [https://www.ncnr.nist.gov/staff/hammouda/distance_learning/pnrchapti.pdf]
Below we demonstrate how to create a model for a single magnetic Fe layer on Si substrate. Magnetization of this layer has an angle of 45 degrees towards the direction of external magnetic field.
In this example for the parametrization of the nuclear scattering we use scattering length density. The strength of magnetic scattering is described by magnetic scattering length density.
Fe_sld=esc.par("Fe SLD", 8.024, scale=1e-4, units="1/nm^2")
Fe_sldm=esc.par("Fe SLDM", 5.00, scale=1e-4, units="1/nm^2")
Si_sld=esc.par("Si SLD", 2.074, scale=1e-4, units="1/nm^2")
thknFe = esc.par("Fe Thkn", 10, units="nm", userlim=[5, 15])
bphiFe=esc.par("Fe1 Bphi", 45, units="deg", userlim=[-90, 90])
Next we create materials objects, layers and sample.
Fe = esc.generic_amorphous("Fe", Fe_sld, 0.0, sldm=Fe_sldm)
Si = esc.generic_amorphous("Si", Si_sld, 0.0)
Fe_layer = esc.magnetic_layer("Layer: Fe", Fe, bphiFe, 90, thknFe, 0.0)
sub = esc.substrate("Substrate: Si", Si, 0.0) # roughness is zero
sample = esc.multilayer("Fe/Si/Fe", frgr=esc.air("Air"), bkgr=sub)
sample.add(Fe_layer)
#let's have a look on the profile
show(sample, xlabel="Z[nm]", ylabel="SLDRe[1/nm^2]", yaxis="sld0re")
Now we create calculation kernel for polarized neutron reflectivity. We use four parameters for efficiency of neutrons polarizers and flippers. The sign of the final efficiency parameters define the direction of neutron's polarization state: up (+) or down (-).
Qz=esc.var("qz")
poleffi=esc.par("Polarization Effi", 1, userlim=[0, 1])
polefff=esc.par("Polarization Efff", 1, userlim=[0, 1])
flipeffi=esc.par("Flip Effi", 1, userlim=[0, 1])
flipefff=esc.par("Flip Efff", 1, userlim=[0, 1])
B=[0, 1, 0]# external magnetic field componenets in the sample coordinate system
Rpp = esc.pnrspec("Specrefl ++", Qz, sample, B, poleffi, polefff)
Rpm = esc.pnrspec("Specrefl +-", Qz, sample, B, poleffi, -polefff*flipefff)
Rmp = esc.pnrspec("Specrefl -+", Qz, sample, B, -poleffi*flipeffi, polefff)
Rmm = esc.pnrspec("Specrefl --", Qz, sample, B, -poleffi*flipeffi, -polefff*flipefff)
We read the data from the file, provided by Dr. Yury Khaydukov. The data is generated in the software package and normalized. The errors are undefined. In order to obtain realistic fit errors for our fit parameters, we multiply the data by I0, apply poisson noise and find errors for every point folllowed by normalizing the data again. In general case, the I0 can be also a parameter.
qz, rpp, rpm, rmp, rmm = np.loadtxt("data/fe-si.dat", unpack=True)
qz=qz*10 #1/A to 1/nm
I0 = 1e8
rpp = np.random.poisson(rpp*I0)/I0
err_pp = np.sqrt(rpp)/np.sqrt(I0)
rmm = np.random.poisson(rmm*I0)/I0
err_mm = np.sqrt(rmm)/np.sqrt(I0)
rpm = np.random.poisson(rpm*I0)/I0
err_pm = np.sqrt(rpm)/np.sqrt(I0)
rmp = np.random.poisson(rmp*I0)/I0
err_mp = np.sqrt(rmp)/np.sqrt(I0)
dobj_pp = esc.data("++", qz, rpp, err_pp, copy=True)
dobj_pm = esc.data("+-", qz, rpm, err_pm, copy=True)
dobj_mp = esc.data("-+", qz, rmp, err_mp, copy=True)
dobj_mm = esc.data("--", qz, rmm, err_mm, copy=True)
Next we create four models object, each for every polarization-flipping state.
mobj_pp = esc.model("R++", Rpp, dobj_pp, residuals_scale="q4", weight_type="data")
mobj_mm = esc.model("R--", Rmm, dobj_mm, residuals_scale="q4", weight_type="data")
mobj_pm = esc.model("R+-", Rpm, dobj_pm, residuals_scale="none", weight_type="data")
mobj_mp = esc.model("R-+", Rmp, dobj_mp, residuals_scale="none", weight_type="data")
#show([mobj_pp, mobj_mm, mobj_pm, mobj_mp], ylog=True, xlog=False, xlabel="Q[1/nm]", ylabel="Intensity")
Optimizing now with Levenberg-Marquardt algorithm
#opt = esc.levmar("LM", [mobj_pp, mobj_mm, mobj_pm, mobj_mp],
# maxiter=1000, xtol=1e-15, ftol=1e-15, gtol=1e-15, nupdate=1)
opt = esc.diffevol("DiffEvol", [mobj_pp, mobj_mm, mobj_pm, mobj_mp], popsize=15, maxiter=150,
mutation=0.5, crossover=0.5, minconv=1e-3, nupdate=5,
polish_final_maxiter=50, polish_candidate_maxiter=0)
opt.shake()
opt()
show(opt, ylog=True, xlog=False, xlabel="Q[1/nm]", ylabel="|R|^2")
#Let's print parameters and their errors after fit
opt
Name: DiffEvol Parameters number: 9 Parameter Value +- Error Units Fixed Fe SLD 8.046 +- 0.00026248 x0.0001 1/nm^2 0 Fe SLDM 5.0098 +- 0.00038636 x0.0001 1/nm^2 0 Fe1 Bphi 45.053 +- 0.0037963 deg 0 Fe Thkn 9.9999 +- 6.0886e-05 nm 0 Si SLD 2.0829 +- 6.1279e-05 x0.0001 1/nm^2 0 Polarization Effi 1 +- 0 0 Polarization Efff 1 +- 0 0 Flip Effi 1 +- 0 0 Flip Efff 1 +- 0 0