import numpy as np
import escape as esc
esc.require('0.9.7')
from escape.utils.widgets import show
import matplotlib.pyplot as plt
Loading material database from /home/dkor/Data/Development/workspace_escape/escape/python/src/escape/scattering/../data/mdb/materials.db
PNR. Spin-flip scattering from a magnetic non-collinear resonator¶
Author: Yury Khaydukov
Neutron spin-flip (SF) scattering is a purely magnetic channel resulting from the scattering of neutrons on non-collinear magnetization. The SF scattering is an important channel in study of magnetic heterostructures with nontrivial magnetic ordering, such as helicoids, magnetic vortices, skyrmions, etc.
For precise quantitative analysis of SF data, it is necessary to take into account the characteristics of the polarization devices, such as polarizing efficiencies of the polarizer and analyzer, as well as the efficiencies of spin flippers. Non-100% efficiency of these devices leads to the fact that the real SF signal is polluted by the so-called "spin leakage". Fit of the SF data without taking into account the spin-leakage will lead to a systematic error in determining the non-collinearity parameters.
In this notebook, we consider the simultaneous data fit of non-SF and SF channels from a magnetic non-collinear resonator of nominal structure Pt(3nm)/Nb(25nm)/Co(3nm)/Nb(25nm)//Al2O3. The system was used in study the hydrogenation kinetics of thin films through the analysis of the resonance position in the SF channel, details can be found in L. Guasco et al, Nat. Commun. 2022 .
Next we create materials objects, layers and sample.
Pt_sld=esc.par("Pt SLD", 6.360, scale=1e-4, units="1/nm^2", userlim=[4, 6.4])
Nb_sld=esc.par("Nb SLD", 3.606, scale=1e-4, units="1/nm^2", userlim=[3, 5])
Co_sld=esc.par("Co SLD", 2.131, scale=1e-4, units="1/nm^2", userlim=[1, 3])
#Co_sldm=esc.par("Co SLDM", 4.12, scale=1e-4, units="1/nm^2", userlim=[0, 5])
Co_M=esc.par("Co Magnetization", 18.0, scale=1, units="kG", userlim=[0, 19])
Co_angl=esc.par("Co angle", 45, units="deg", userlim=[0, 180])
#Co_angl is defined as an angle between magnetization and external field, which was along y-axis in the experiment
Sb_sld=esc.par("Al2O3 SLD", 5.717, scale=1e-4, units="1/nm^2", userlim=[3, 6])
#Thickness and roughness
thknPt = esc.par("Pt Thkn", 3.0, units="nm", userlim=[2, 5])
roughPt = esc.par("Pt Roughness", 0.5, units="nm", userlim=[0, 2])
#
thknNb1 = esc.par("Nb1 Thkn", 25.0, units="nm", userlim=[20, 30])
roughNb1 = esc.par("Nb1 Roughness", 0.5, units="nm", userlim=[0, 2])
#
thknCo = esc.par("Co Thkn", 3.0, units="nm", userlim=[2, 4])
roughCo = esc.par("Co Roughness", 0.0, units="nm", userlim=[0, 2])
#
thknNb2 = esc.par("Nb2 Thkn", 25.0, units="nm", userlim=[20, 30])
roughNb2 = esc.par("Nb2 Roughness", 0.5, units="nm", userlim=[0, 2])
roughSb = esc.par("Sub Roughness", 0.5, units="nm", userlim=[0, 2])
pt = esc.generic_amorphous("Pt", Pt_sld, -1e-20)
nb = esc.generic_amorphous("Nb", Nb_sld, -1e-20)
co = esc.generic_amorphous("Co", Co_sld, -1e-20, sldm=Co_M*2.32e-5)
sb = esc.generic_amorphous("Al2O3", Sb_sld, -1e-20)
Pt_layer = esc.layer("Layer: Pt", pt, thknPt, roughPt)
Nb_layer1 = esc.layer("Layer: Nb1", nb, thknNb1, roughNb1)
#Co_layer = esc.magnetic_layer("Layer: Co", co, Co_angl, 90, thknCo, roughCo)
Co_layer = esc.magnetic_layer("Layer: Co", co, 90-Co_angl, 90, thknCo, roughCo)
Nb_layer2 = esc.layer("Layer: Nb2", nb, thknNb2, roughNb2)
sub = esc.substrate("Substrate: Al2O3", sb, roughSb)
sample = esc.multilayer("Pt/Nb/Co/Nb", frgr=esc.air("Air"), bkgr=sub)
sample.add(Pt_layer)
sample.add(Nb_layer1)
sample.add(Co_layer)
sample.add(Nb_layer2)
#let's have a look on the profile
show(sample, xlabel="Z[nm]", ylabel="Neutron SLD [1/nm^2]", yaxis="sld0re")
Now we create calculation kernel for polarized neutron reflectivity. We use four parameters for efficiencies of neutrons polarizer (PolEf), analyzer (AnEf) and flippers 1 and 2 (SF1, and SF2, respectively). The sign of the final efficiency parameters define the direction of neutron's polarization state: up (+) or down (-). Note that in this experiment transmission polarizer and analyzer was used. It means that default spin state with both spin-flippers off is (--).
# PNR
Qz=esc.var("qz")
Qz0=esc.var("qz0")
fwhm=esc.par("FWHM", 2.0e-2, userlim=[1e-3, 1e-1])
PolEf=esc.par("Polarizer Eff", 1, userlim=[0.9, 1])
AnEf=esc.par("Analyzer Eff", 1, userlim=[0.9, 1])
SF1=1
SF2=1
#SF1=esc.par("Flipper 1 Eff", 1, userlim=[0.9, 1])
#SF2=esc.par("Flipper 2 Eff", 1, userlim=[0.9, 1])
Rmm = esc.pnrspec("Model --", Qz, sample,[0, 1, 0], -PolEf, -AnEf)
Rpm = esc.pnrspec("Model +-", Qz, sample,[0, 1, 0], PolEf*SF1, -AnEf)
Rmp = esc.pnrspec("Model -+", Qz, sample,[0, 1, 0], -PolEf, AnEf*SF2)
Rpp = esc.pnrspec("Model ++", Qz, sample,[0, 1, 0], PolEf*SF1, AnEf*SF2)
#Next we take into account experimental resolution of the experiment via effective dQ defined as fwhm
numpoints=15
Rpp = esc.average_normal(Rpp, fwhm, Qz, Qz0, numpoints=numpoints)
Rmm = esc.average_normal(Rmm, fwhm, Qz, Qz0, numpoints=numpoints)
Rpm = esc.average_normal(Rpm, fwhm, Qz, Qz0, numpoints=numpoints)
Rmp = esc.average_normal(Rmp, fwhm, Qz, Qz0, numpoints=numpoints)
Note that efficiencies of spin-flippers 1 and 2 (SF1, SF2) do not appear in the fit independently, but always multiplied with PolEf or AnEf. In this regard it makes no sense to fit them independently. We set these efficiencies to 1, which is quite justified assumption for a state-of-art PNR instrument with well-tuned spin-flipers.
We will use the PNR data of the NREX reflectometer, provided by Laura Guasco (Max-Planck Institute for Solid State Research).
#
qz, rmm, err_mm,rpp,err_pp,rpm,err_pm,rmp,err_mp = np.loadtxt("data/NbCoNb/RemReflectivity.dat",unpack=True,skiprows=1)
qz=qz*10 #1/A to 1/nm
I0=esc.par("I0", 1.0, scale=1e0, userlim=[0.95, 1.05])
B=esc.par("Bgr", 1e-6,scale=1e-6, userlim=[0.1, 10])
Ipp=I0*Rpp+B
Imm=I0*Rmm+B
Ipm=I0*Rpm+B
Imp=I0*Rmp+B
dobj_pp = esc.data("Experiment ++", qz, rpp, err_pp, copy=True)
dobj_mm = esc.data("Experiment --", qz, rmm, err_mm, copy=True)
dobj_pm = esc.data("Experiment +-", qz, rpm, err_pm, copy=True)
dobj_mp = esc.data("Experiment -+", qz, rmp, err_mp, copy=True)
Next we create models object for every polarization-flipping state.
Lets see how the data and preliminary model looks like. The peak in the SF channels (-+) and (+-) at Q$_{res}$=0.15 nm$^{-1}$ corresponds to the resonance mode. By changing the SLD of the Nb layers (Nb SLD parameter) one can check the linear dependence of the peak position on SLD of the Nb layer, which can be used to precisely determine concentration of hydrogen or other gases in the layer (zoom in closer to the peak for better visibility).
Also one can observe the spin-leakage at Q< Q$_{res}$ of order of 0.14% which is not described in case of ideally polarized neutron beam. By playing with effieincy of polarizer and/or analyzer one can see how these parameters influence of spin-leakage.
ResScale="q2" # none, log, q2 or q4
mobj_pp = esc.model("Model ++", Ipp, dobj_pp, residuals_scale=ResScale, weight_type="data")
mobj_mm = esc.model("Model --", Imm, dobj_mm, residuals_scale=ResScale, weight_type="data")
mobj_pm = esc.model("Model +-", Ipm, dobj_pm, residuals_scale=ResScale, weight_type="data")
mobj_mp = esc.model("Model -+", Imp, dobj_mp, residuals_scale=ResScale, weight_type="data")
show([mobj_mp, mobj_pm,mobj_mm,mobj_pp], ylog=True, xlog=False, xlabel="Q[1/nm]", ylabel="Intensity")
Optimizing now with Differential Evolutionary algorithm
opt = esc.diffevol("DiffEvol", [mobj_mp, mobj_pm,mobj_mm,mobj_pp], popsize=15, maxiter=50,
mutation=0.5, crossover=0.5, minconv=1e-3, nupdate=10,
polish_final_maxiter=25, polish_candidate_maxiter=0)
opt()
show(opt, ylog=True, xlog=False, xlabel="Q[1/nm]", ylabel="Reflectivity")
#Let's print parameters and their errors after fit
show(sample, xlabel="z[nm]", ylabel="Neutron SLD [1/nm^2]", yaxis="sld0re")
opt
Name: DiffEvol Parameters number: 20 Parameter Value +- Error Units Fixed I0 0.98398 +- 0.0226 0 FWHM 0.010927 +- 0.00026188 0 Pt SLD 5.6636 +- 0.29936 x0.0001 1/nm^2 0 Pt Thkn 4.2594 +- 0.27388 nm 0 Pt Roughness 1.287 +- 0.12322 nm 0 Nb SLD 3.6658 +- 0.021089 x0.0001 1/nm^2 0 Nb1 Thkn 27.727 +- 0.22579 nm 0 Nb1 Roughness 0.60811 +- 0.40359 nm 0 Co SLD 2.0591 +- 0.11581 x0.0001 1/nm^2 0 Co Magnetization 10.354 +- 0.69282 kG 0 Co angle 127.34 +- 1.4572 deg 0 Co Thkn 3.5532 +- 0.21316 nm 0 Co Roughness 1.159 +- 0.21935 nm 0 Nb2 Thkn 26.791 +- 0.13649 nm 0 Nb2 Roughness 1.3053e-08 +- 0 nm 0 Al2O3 SLD 5.4017 +- 0.0073051 x0.0001 1/nm^2 0 Sub Roughness 0.92474 +- 0.1348 nm 0 Polarizer Eff 0.99847 +- 0.018626 0 Analyzer Eff 0.99886 +- 0.018624 0 Bgr 4.3092 +- 0.50399 x1e-06 0