Skip to content

Commit 9201373

Browse files
committed
simple fit tutorial
1 parent 8e716f0 commit 9201373

File tree

2 files changed

+87
-24
lines changed

2 files changed

+87
-24
lines changed

docs/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ It serves as a system for the day to day ellipsometry task at hand and aims to m
88
PyElli can be installed directly from [PyPI](https://pypi.org/project/pyElli/).
99
Installation is as simple as
1010

11-
```
11+
```sh
1212
pip install pyelli
1313
```
1414

@@ -18,6 +18,6 @@ PyElli has the optional dependency `fitting` to install dependencies to use addi
1818
fitting capabilities and interactive widgets with jupyter notebooks.
1919
If you want to use those you can directly install the dependencies along with pyElli:
2020

21-
```
21+
```sh
2222
pip install 'pyelli[fitting]'
2323
```

docs/tutorial/simple_fit.md

Lines changed: 85 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,98 @@
11
# Performing a Simple Fit
22

3+
In this tutorial we will perform a simple fit with pyElli.
4+
We determine the thickness of a silicon dioxide layer on top of silicon.
5+
This fit is often carried out as calibration and benchmark fit for ellipsometers and
6+
is widely used for quality control of silicon wafers.
7+
Here, we will use it to make our first steps with pyElli.
8+
9+
Please make sure you meet the following prerequesits:
10+
11+
- You are in a python environment with pyElli [installed](../index.md).
12+
- We recommend to run the code in a jupyter or jupyter compatible environment.
13+
However, you _can_ run this code in a repl or python file directly as well.
14+
It will just not display the fitting widgets.
15+
- An understanding of ellipsometry, in particular Psi/Delta measurement data.
16+
17+
## Importing the packages
18+
19+
First, we need to import pyElli itself.
20+
Additionally, we import the `ParamsHist` class, which is a small wrapper around lmfit's `Parameter` class.
21+
It stores a parameter history to retrieve previous fit configurations.
22+
We'll also import the function `fit` from pyEllis fitting module.
23+
This is a function decorator, which we will use to display our interactive fitting widget.
24+
325
```python
426
import elli
527
from elli.fitting import ParamsHist, fit
628
```
729

830
## Reading data
931

32+
We need some measurement data to perform a fit on and load an example [data set](https://github.com/PyEllips/pyElli/blob/master/examples/Basic%20Usage/SiO2onSi.ellips.nxs) of a psi delta measurement in the standardized [NeXus format](https://manual.nexusformat.org/classes/contributed_definitions/NXellipsometry.html#nxellipsometry) with pyElli's included `read_nexus_psi_delta` function.
33+
Lets select a measurement angle of 70 degrees (`.loc[ANGLE]`) from the data and
34+
restrict the wavelength range in between 210 nm and 800 nm (`.loc[210:800]`).
35+
The restriction is necessary because we're using tabulated literature values for silicon, which are only provided in this wavelength range.
36+
1037
```python
1138
ANGLE = 70
12-
psi_delta = elli.read_nexus_psi_delta("SiO2onSi.ellips.nxs").loc[ANGLE].loc[210:800]
39+
psi_delta = (
40+
elli.read_nexus_psi_delta("SiO2onSi.ellips.nxs")
41+
.loc[ANGLE]
42+
.loc[210:800]
43+
)
1344
```
1445

1546
## Setting parameters
1647

48+
To fit our data we need some actual fitting parameters.
49+
We'll use our `ParamsHist` class which is a wrapper around lmfit's Parameters class.
50+
The names of the paramters are freely choosable but we recommend to use some material and parameter name combination.
51+
We'll use standard literature values for the silicon dioxide dispersion parameters $n_0$ (`SiO2_n0`) and $n_1$ (`SiO2_n1`).
52+
For now, we won't vary these parameters in the fit, hence we set `vary=False` in the parameter definition.
53+
Last but not least, we'll add a thickness parameter of the silicon dioxide layer with an initial guess of the materials thickness.
54+
We set the `min` and `max` values to reasonable values and `vary=True` since we want to fit the thickness of the layer.
55+
1756
```python
1857
params = ParamsHist()
19-
params.add("SiO2_n0", value=1.452, min=-100, max=100, vary=False)
20-
params.add("SiO2_n1", value=36.0, min=-40000, max=40000, vary=False)
21-
params.add("SiO2_n2", value=0, min=-40000, max=40000, vary=False)
22-
params.add("SiO2_k0", value=0, min=-100, max=100, vary=False)
23-
params.add("SiO2_k1", value=0, min=-40000, max=40000, vary=False)
24-
params.add("SiO2_k2", value=0, min=-40000, max=40000, vary=False)
25-
params.add("SiO2_d", value=20, min=0, max=40000, vary=True)
58+
params.add("SiO2_n0", value=1.452, vary=False)
59+
params.add("SiO2_n1", value=36.0, vary=False)
60+
params.add("SiO2_d", value=20, min=1, max=1000, vary=True)
2661
```
2762

63+
## Building the Fit Model
64+
65+
We're ready to build a model!
66+
First we need to construct our materials.
67+
We have a silicon substrate which we load from the included [refractiveindex.info](https://refractiveindex.info) material database.
68+
We are going to use the tabulated values from [Aspnes et al.](https://refractiveindex.info/?shelf=main&book=Si&page=Aspnes).
69+
2870
```python
2971
rii_db = elli.db.RII()
3072
Si = rii_db.get_mat("Si", "Aspnes")
3173
```
3274

33-
## Building the Fitting Model
75+
For the silicon dioxide layer we need a formula to insert our fitting values.
76+
Here, we could also use tabulated values since we won't vary the parameters of the Cauchy model but in general it's a good practice to fit the material parameters, too.
3477

3578
```python
3679
SiO2_dispersion = elli.Cauchy(
37-
params["SiO2_n0"],
38-
params["SiO2_n1"],
39-
params["SiO2_n2"],
40-
params["SiO2_k0"],
41-
params["SiO2_k1"],
42-
params["SiO2_k2"],
80+
params["SiO2_n0"], params["SiO2_n1"], 0, 0, 0, 0,
4381
)
4482
```
4583

84+
The above snippet created a dispersion and now we need to generate a material from it. For anisotropic materials this can be done by calling `get_mat()`.
85+
4686
```python
4787
SiO2 = SiO2_dispersion.get_mat()
4888
```
4989

90+
Now, we we put our materials together in a `Structure`.
91+
It consists of a front-layer, which is air (`elli.AIR`) in this case (but will be air in most experiments you do) and a back layer which is silicon here.
92+
In between is the most important part: the array of layers.
93+
Here, we only have a single layer of silicon dioxide.
94+
In the `Layer` specify the material and the thickness (`SiO2_d`).
95+
5096
```python
5197
structure = elli.Structure(
5298
elli.AIR,
@@ -55,22 +101,25 @@ structure = elli.Structure(
55101
)
56102
```
57103

104+
In the last step we just need to evaluate our model for a set of wavelengths (`lbda`) at a given angle (`ANGLE`).
105+
Additionally, we need the specify the solver (`elli.Solver2x2` in this case).
106+
58107
```python
59108
structure.evaluate(lbda, ANGLE, solver=elli.Solver2x2)
60109
```
61110

62111
## Putting it all together
63112

113+
In this section we simply put the parts together: building the materials, the structure and evaluating it with the appropriate parameters.
114+
We use the `@fit` decorator here, which takes the measurement data `psi_delta` and our fit parameter class `params`.
115+
The `@fit` decorator generates a fit class to perform fitting on.
116+
If this code snippet is executed in a jupyter environment it will display a fit widget in which you can alter the parameter and see the direct feedback as well as performing the fit.
117+
64118
```python
65119
@fit(psi_delta, params)
66120
def model(lbda, params):
67121
SiO2 = elli.Cauchy(
68-
params["SiO2_n0"],
69-
params["SiO2_n1"],
70-
params["SiO2_n2"],
71-
params["SiO2_k0"],
72-
params["SiO2_k1"],
73-
params["SiO2_k2"],
122+
params["SiO2_n0"], params["SiO2_n1"], 0, 0, 0, 0,
74123
).get_mat()
75124

76125
structure = elli.Structure(
@@ -82,7 +131,21 @@ def model(lbda, params):
82131
return structure.evaluate(lbda, ANGLE, solver=elli.Solver2x2)
83132
```
84133

134+
By calling `fit()` we perform the fit and with `plot()` we can generate a plot of the resulting fit.
135+
This part is optional if you used the fitting widget of the `@fit` decorator, since the widget will call these for you.
85136
```python
86137
fit_stats = model.fit()
87138
model.plot()
88-
```
139+
```
140+
141+
Finally, we extract the actual thickness of the silicon dioxide layer by extracting the parameter from `fit_stats`.
142+
```
143+
fit_stats.params["SiO2_d].value
144+
```
145+
146+
It should return a value $\approx 2.066\,\text{nm}$.
147+
148+
## Congrats 🎉
149+
150+
You have sucessfully performed a simple fit with pyElli.
151+
Feel free to explore with this code, e.g., you may want to try varying the material parameters during the fit or using different angles from the measurement (50 or 60 degrees is also available).

0 commit comments

Comments
 (0)