SpecFitter example

This example shows how you can use the pre-trained neural network model to fit a single layer of and organic thin film on a Si/SiOx substrate by directly reading the data from a SPEC file.

[1]:
import mlreflect
print('This example was tested with mlreflect version ' + mlreflect.__version__)
This example was tested with mlreflect version 0.19.0

The SpecFitter class

First we have to import the SpecFitter class as well as the path to the example SPEC file. The DefaultSpecFitter class is a conventient way to import a SpecFitter that already initializes with the aforementioned pre-trained model.

[2]:
from mlreflect.curve_fitter import DefaultSpecFitter, example_spec_file_path
[3]:
spec_fitter = DefaultSpecFitter()

We can inspect the sample structure that the model was trained on via the following property:

[4]:
print(spec_fitter.trained_model.sample)
Air (ambient):
        sld: 0 [1e-6 1/Å^2]
[1] Film:
        thickness: (20, 1000) [Å]
        roughness: (0, 100) [Å]
        sld: (1, 14) [1e-6 1/Å^2]
[0] SiOx:
        thickness: 10.0 [Å]
        roughness: 2.5 [Å]
        sld: (17.7735+0.4048j) [1e-6 1/Å^2]
Si (substrate):
        roughness: 1 [Å]
        sld: (20.0701+0.4571j) [1e-6 1/Å^2]

It is important to note that the q range that is used for the prediction is fixed after training. The pre-trained model used here was trained for a q range from 0.02 to 0.15 1/Å due to a Bragg peak around 0.3 1/Å.

Next, we must define two dictionaries with important information about the data and the experiment and pass that information to the SpecFitter object.

The first dict contains the column names of all columns in the SPEC file that together form the full scattering angle (2 theta) and the name of the column where the reflected intensity is saved. You can provide the column name of the attenuator counter with the keyword attenuator_column.

The second dict contains all parameters that are necessary to apply a basic footprint correction. The wavelength is provided in units of Å. The beam width and sample length along the beam direction are provided in units of mm. By default, the footprint correction assumes a Gaussian profile.

[5]:
spec_params = {
    'angle_columns': ['tt'],
    'intensity_column': "p100k_roi2",
    'attenuator_column': 'atten_position'
}

spec_fitter.set_import_params(**spec_params)
[6]:
footprint_params = {
    'wavelength': 0.6889,
    'beam_width': 0.072,
    'sample_length': 10
}

spec_fitter.set_footprint_params(**footprint_params)

Lastly, we need to provide the path the SPEC file that we want to extract the data from. The example dataset provided by the package contains a reflectivity data series from a real-time annealing experiment of a DNTT:PDIF thin film on a Si/SiOx substrate.

[7]:
spec_fitter.set_file(example_spec_file_path)
38 scans found in /home/greco/git_repos/mlreflect/mlreflect/resources/example/example.spec

Fitting a single curve

The fit of a particular scan on the spec file can be executed via the fit() method by providing the scan number. When this method is executed, several things happen automatically:

  • The data is read from the SPEC file

  • Footprint correction is applied

  • The data is normalized to 1

  • The data is interpolated to the q values required by the neural network input

  • The neural network predicts the three thin film parameters (thickness, roughness, SLD)

  • A “fit” curve is simulated and plotted with the original data

The fit method takes several optional keywords, some important examples are:

  • trim_front: This can be important in cases where the direct beam is part of the dataset. In such a case it is recommended to use a value fortrim_front so that the normalization of the curve is not affected by the direct beam.

  • polish: If polish=True, the result obtained from the neural network is refined by a simple LMS minimization using scipy.optimize.minimize. This can sometimes yield a nicer fit, but there is a risk of running into unphysical minima for the roughness or SLD.

  • plot: If plot=True a plot of the curve fit and the SLD profile are shown.

The fit() method yields a FitResult object that contains all the important results.

[8]:
fit_result = spec_fitter.fit(1046, trim_front=3, plot=True, reload=False)
_images/spec_fitter_example_18_0.png
_images/spec_fitter_example_18_1.png

The results are obtained within less than a second and we can see that the guess of the neural network shows a reasonable fit of the data within the given q range.

Accessing and saving the results

The fit results can also be accessed via the predicted_parameters property. This shows the parameter values of the whole box model (including the ones that weren’t predicted).

[9]:
fit_result.predicted_parameters
[9]:
SiOx_thickness Film_thickness Si_roughness SiOx_roughness Film_roughness Si_sld SiOx_sld Film_sld Air_sld
scan
1046 10.0 202.783216 1 2.5 13.718066 20.070100+0.457100j 17.773500+0.404800j 9.64782 0

The FitResult class has several useful methods to save the results as well as the original curve (including footprint correction).

[10]:
fit_result.save_predicted_parameters('parameters.dat')
fit_result.save_predicted_reflectivity('predicted_reflectivity.dat')
fit_result.save_corrected_reflectivity('corrected_reflectivity.dat')

Identifying reflectivity scans

A quick overview of the available scans in the SPEC file can be achieved with the method SpecFitter.show_scans(). This is useful to find the scan numbers of reflectivity scans for the fit() method.

[11]:
spec_fitter.show_scans(min_scan=1046)
38 scans found in /home/greco/git_repos/mlreflect/mlreflect/resources/example/example.spec
scan #1046
        command: a2scan om 0.06 0.5 tt 0.12 1.0 79 1.0
        time: Sun Jul 04 21:18:52 2021
scan #1047
        command: dscan hz -0.2 0.2 20 0.1
        time: Sun Jul 04 21:25:42 2021
scan #1048
        command: dscan hz -0.2 0.2 20 0.1
        time: Sun Jul 04 21:30:19 2021
scan #1049
        command: dscan hry -0.05 0.05 20 1.0
        time: Sun Jul 04 21:31:24 2021
scan #1050
        command: dscan hz -0.2 0.2 20 0.1
        time: Sun Jul 04 21:32:52 2021
scan #1051
        command: a2scan om 0.06 0.5 tt 0.12 1.0 79 1.0
        time: Sun Jul 04 21:33:58 2021
scan #1052
        command: dscan hz -0.2 0.2 20 0.1
        time: Sun Jul 04 21:40:59 2021
scan #1053
        command: dscan hry -0.05 0.05 20 1.0
        time: Sun Jul 04 21:42:29 2021
scan #1054
        command: a2scan om 0.06 0.5 tt 0.12 1.0 79 1.0
        time: Sun Jul 04 21:44:38 2021
scan #1055
        command: a2scan om 0.06 0.5 tt 0.12 1.0 79 1.0
        time: Sun Jul 04 21:50:18 2021
scan #1056
        command: a2scan om 0.06 0.5 tt 0.12 1.0 79 1.0
        time: Sun Jul 04 21:55:52 2021
scan #1057
        command: a2scan om 0.06 0.5 tt 0.12 1.0 79 1.0
        time: Sun Jul 04 22:01:18 2021
scan #1058
        command: a2scan om 0.06 0.5 tt 0.12 1.0 79 1.0
        time: Sun Jul 04 22:06:46 2021
scan #1059
        command: dscan hz -0.2 0.2 20 0.1
        time: Sun Jul 04 22:15:56 2021

Fitting a range of curves

In order to analyze a whole series of scans at once (which is useful for a temperature series such as this one) we can use the fit_range() method. It works almost the exact same as the fit() method except that we have to provide a list of scan numbers and that it yields a FitResultSeries object.

[12]:
scan_numbers = [1046, 1051] + list(range(1054, 1059))
print(scan_numbers)
[1046, 1051, 1054, 1055, 1056, 1057, 1058]
[13]:
fit_result_series = spec_fitter.fit_range(scan_numbers, trim_front=3, plot=True, reload=False)
_images/spec_fitter_example_31_0.png
_images/spec_fitter_example_31_1.png

From the plot output we can a nice trend in the data which shows that the film becomes thinner and a lot rougher during the temperature ramp, indicating the expected desorption of the film.

If we want to inspect every single fit of the neural network, we can use the fit_results_list property to access every individual FitResult object.

[14]:
for result in fit_result_series.fit_results_list:
    result.plot_prediction(['Film_thickness', 'Film_roughness', 'Film_sld'])
_images/spec_fitter_example_33_0.png
_images/spec_fitter_example_33_1.png
_images/spec_fitter_example_33_2.png
_images/spec_fitter_example_33_3.png
_images/spec_fitter_example_33_4.png
_images/spec_fitter_example_33_5.png
_images/spec_fitter_example_33_6.png

Again, we can see that all of the fits are quite good. However, in terms of the thickness trend, the last curve seems to be an outlier. This can be explained by the fact that the last curve has almost no features anymore, which makes it very difficult to extract any information (especially about the thickness) from it.