Resolution functions#
The purpose of the resolution function is to enable the easyreflectometry
model to quantify the experimental uncertainties in wavelength and incident angle. When determining reflectivity the resolution function defines the smearing to apply. For a given Q-point such smearing is applied by determining an average of the neighboring Q-point weigthed by a normal distribution, which has a Q-point dependent Full Width at the Half Maximum (FWHM) that again is defined by the resolution function.
Often we rely on a resolution function that has a simple functional dependecy of the Q-point. By this is understood that the applied smearing in an Q point-has a FWHM that is given as a percentage of the value of the Q-point.
Alternatively the FWHM value might be determined and declared directly for each measured Q-point. When this is the case the provided Q-points and the corresponding FWHM values can be used to declare a linear spline function and thereby enable a determination of the reflectivity at an arbitrary point within the provided range of discrete Q-points.
Setup#
First configure matplotlib to place figures in notebook and import needed modules
[1]:
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import scipp as sc
import refnx
import pooch
import easyreflectometry
from easyreflectometry.calculators import CalculatorFactory
from easyreflectometry.data import load
from easyreflectometry.experiment import Model
from easyreflectometry.experiment import LinearSpline
from easyreflectometry.experiment import PercentageFhwm
from easyreflectometry.sample import Layer
from easyreflectometry.sample import Material
from easyreflectometry.sample import Multilayer
from easyreflectometry.sample import Sample
from easyreflectometry.plot import plot
For reference we fetch the version of the software packages we are using.
[2]:
print(f'numpy: {np.__version__}')
print(f'scipp: {sc.__version__}')
print(f'easyreflectometry: {easyreflectometry.__version__}')
print(f'refnx: {refnx.__version__}')
numpy: 1.26.0
scipp: 24.02.0
easyreflectometry: 1.1.1
refnx: 0.1.48
Reading in measured data#
The data that we will investigate in this tutorial was generated with Refnx
and are stored in .ort
format file files. In this tutorial we are investigation how we can include resolution effects when simulating and reproducing data measured in an experiment. For an .ort
file the resoultion data for reflectivity is stored in the fourth column.
IMPORTANT when using easyreflectometry
functionality for loading an .ort
file we store the resolution data as a variance (squared value). As a consequence one needs to take the squareroot of the loaded data to recover the raw values (fourth column). We use pooch
to fetch the file from the repository.
[3]:
file_path_0 = pooch.retrieve(
# URL to one of Pooch's test files
url="https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/sample/mod_pointwise_two_layer_sample_dq-0.0.ort",
known_hash="f8a3e7007b83f0de4e2c761134e7d1c55027f0099528bd56f746b50349369f50",
)
file_path_1 = pooch.retrieve(
# URL to one of Pooch's test files
url="https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/sample/mod_pointwise_two_layer_sample_dq-1.0.ort",
known_hash="9d81a512cbe45f923806ad307e476b27535614b2e08a2bf0f4559ab608a34f7a",
)
file_path_10 = pooch.retrieve(
# URL to one of Pooch's test files
url="https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/sample/mod_pointwise_two_layer_sample_dq-10.0.ort",
known_hash="991395c0b6a91bf60c12d234c645143dcac1cab929944fc4e452020d44b787ad",
)
dict_reference = {}
dict_reference['0'] = load(file_path_0)
dict_reference['1'] = load(file_path_1)
dict_reference['10'] = load(file_path_10)
Downloading data from 'https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/sample/mod_pointwise_two_layer_sample_dq-0.0.ort' to file '/home/runner/.cache/pooch/ae1ec5ab673e9a86d55627d3439d6ee1-mod_pointwise_two_layer_sample_dq-0.0.ort'.
Downloading data from 'https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/sample/mod_pointwise_two_layer_sample_dq-1.0.ort' to file '/home/runner/.cache/pooch/9264c15e11f33ec7c904eb17623a35e7-mod_pointwise_two_layer_sample_dq-1.0.ort'.
Downloading data from 'https://raw.githubusercontent.com/EasyScience/EasyReflectometryLib/master/docs/src/tutorials/sample/mod_pointwise_two_layer_sample_dq-10.0.ort' to file '/home/runner/.cache/pooch/df4a4308886f7f782000f60c6ab52691-mod_pointwise_two_layer_sample_dq-10.0.ort'.
As an example we can plot the reference data without any resolution effects.
[4]:
plot(dict_reference['0'])
Building our model#
The system that was used to produce the data shown above is based on a silicon subphase with two layers upon it. These two layers are charachterized by having a scattering length density (SLD) of respectively 4 and 8. Both layers have a rougness of 2 but their thicknesses are 100 and 150 angstrom respectively. We show the model that will be used graphically below.
A slab model description of the two layer.
To construct such a layer structure, first we create each of the materials, the associated layers, and the sub and super phases.
[5]:
sld_4 = Material(sld=4.0, isld=0, name='Sld 4')
sld_8 = Material(sld=8.0, isld=0, name='Sld 8')
vacuum = Material(sld=0, isld=0, name='Vacuum')
si = Material(sld=2.047, isld=0, name='Si')
[6]:
sld_4_layer = Layer(material=sld_4, thickness=100, roughness=2, name='SLD 4 Layer')
sld_8_layer = Layer(material=sld_8, thickness=150, roughness=2, name='SLD 8 Layer')
superphase = Layer(material=vacuum, thickness=0, roughness=0, name='Vacuum Superphase')
subphase = Layer(material=si, thickness=0, roughness=2, name='Si Subphase')
Then, to produce the two layered structure, we use the Multilayer
assembly type.
[7]:
two_layers = Multilayer([sld_4_layer, sld_8_layer], name='SLD 4/8 Layer')
two_layers
[7]:
SLD 4/8 Layer:
SLD 4 Layer/SLD 8 Layer:
- SLD 4 Layer:
material:
Sld 4:
sld: 4.000e-6 1/Å^2
isld: 0.000e-6 1/Å^2
thickness: 100.000 Å
roughness: 2.000 Å
- SLD 8 Layer:
material:
Sld 8:
sld: 8.000e-6 1/Å^2
isld: 0.000e-6 1/Å^2
thickness: 150.000 Å
roughness: 2.000 Å
From this, we can construct our structure and combine this with a scaling and background.
[8]:
sample = Sample(superphase, two_layers, subphase, name='Two Layer Sample')
model = Model(
sample=sample,
scale=1,
background=0,
name='Two Layer Model',
)
Set the calculation engine#
We will use the default Refnx calculator for our analysis.
[9]:
interface = CalculatorFactory()
model.interface = interface
print(interface.current_interface.name)
refnx
Resolution functions#
We now define the different resoultion functions.
[10]:
resolution_function_dict = {}
resolution_function_dict['0'] = LinearSpline(
q_data_points=dict_reference['0']['coords']['Qz_0'].values,
fwhm_values=np.sqrt(dict_reference['0']['coords']['Qz_0'].variances),
)
resolution_function_dict['1'] = LinearSpline(
q_data_points=dict_reference['1']['coords']['Qz_0'].values,
fwhm_values=np.sqrt(dict_reference['1']['coords']['Qz_0'].variances),
)
resolution_function_dict['10'] = LinearSpline(
q_data_points=dict_reference['10']['coords']['Qz_0'].values,
fwhm_values=np.sqrt(dict_reference['10']['coords']['Qz_0'].variances),
)
Simulations#
Refnx
(Reference).[11]:
for key in resolution_function_dict.keys():
reference_coords = dict_reference[key]['coords']['Qz_0'].values
reference_data = dict_reference[key]['data']['R_0'].values
model_coords = np.linspace(
start=min(reference_coords),
stop=max(reference_coords),
num=1000,
)
model.resolution_function = resolution_function_dict[key]
model_data = model.interface().fit_func(
model_coords,
model.unique_name,
)
plt.plot(model_coords, model_data, 'k-', label=f'Resolution: {key}%')
plt.plot(reference_coords, reference_data, 'rx', label=f'Reference')
ax = plt.gca()
ax.set_xlim([-0.01, 0.45])
ax.set_ylim([1e-10, 2.5])
plt.legend()
plt.yscale('log')
plt.show()
From the plots it is apparent that an increasing resolution flattens the reflectivity profile.
Afterthoughts#
As a last task we will compare the reflectivity determined using a percentage resolution function and a point-wise function. We should recall that the “experimental” data was generated using Refnx
. By comparing the reflectivities determined using a resolution function with a FWHM of 1.0% and the point-wise FHWN constructed from data in a .ort
file it is apparent that this reference data also was constructed using a resolution function of 1.0%.
[12]:
key = '1'
reference_coords = dict_reference[key]['coords']['Qz_0'].values
reference_data = dict_reference[key]['data']['R_0'].values
model_coords = np.linspace(
start=min(reference_coords),
stop=max(reference_coords),
num=1000,
)
model.resolution_function = resolution_function_dict[key]
model_data = model.interface().fit_func(
model_coords,
model.unique_name,
)
plt.plot(model_coords, model_data, 'k-', label=f'Variable', linewidth=5)
model.resolution_function = PercentageFhwm(1.0)
model_data = model.interface().fit_func(
model_coords,
model.unique_name,
)
plt.plot(model_coords, model_data, 'r-', label=f'Percentage')
ax = plt.gca()
ax.set_xlim([-0.01, 0.45])
ax.set_ylim([1e-10, 2.5])
plt.legend()
plt.yscale('log')
plt.show()