Mueller matrix

Mueller matrix fit to a SiO2 on Si measurement.

import elli
from elli.fitting import ParamsHist, fit_mueller_matrix

Read data

We load the data from an ascii file containing each of the mueller matrix elements. The wavelength range is cut to be in between 210 nm and 820 nm, to stay in the range of the provided literature values for Si. The data is expected to be in a pandas dataframe containing the columns Mxy, where x and y refer to the matrix element inside the mueller matrix. The data is scaled by the M11 element, such that \(M_{11} = 1\) for all wavelengths. To show the structure we print the MM dataframe. If you load your data from another source make sure it adheres to this form.

MM = elli.read_spectraray_mmatrix("Wafer_MM_70.txt").loc[210:820]
print(MM)
            M11      M12      M13      M14  ...      M41      M42      M43      M44
Wavelength                                  ...
210.30366   1.0 -0.09147 -0.01241 -0.00614  ...  0.00986  0.00048 -0.95574  0.25821
210.76102   1.0 -0.10879 -0.01151 -0.00901  ...  0.00917  0.01319 -0.96899  0.24432
211.21834   1.0 -0.10639 -0.00694 -0.00475  ...  0.00712  0.00891 -0.96191  0.24986
211.67561   1.0 -0.11544 -0.02094 -0.00348  ...  0.01877  0.01726 -0.95951  0.25408
212.13284   1.0 -0.12199  0.02182  0.00480  ... -0.01790 -0.01957 -0.95493  0.28308
...         ...      ...      ...      ...  ...      ...      ...      ...      ...
818.08934   1.0 -0.44285 -0.00048 -0.00288  ...  0.00292 -0.00276 -0.87949  0.15713
818.50589   1.0 -0.44301  0.00376  0.00118  ... -0.00159 -0.01090 -0.87876  0.16422
818.92240   1.0 -0.44245  0.00887  0.00626  ... -0.00585 -0.01388 -0.87960  0.16743
819.33886   1.0 -0.44406 -0.00646 -0.00610  ...  0.00882  0.00424 -0.87907  0.15105
819.75528   1.0 -0.44381  0.01170  0.00550  ... -0.00692 -0.01260 -0.87849  0.16637

[1396 rows x 16 columns]

Setting start parameters

Here we set the start parameters for the SiO2 cauchy dispersion and thickness of the layer.

params = ParamsHist()
params.add("SiO2_n0", value=1.452, min=-100, max=100, vary=True)
params.add("SiO2_n1", value=36.0, min=-40000, max=40000, vary=True)
params.add("SiO2_n2", value=0, min=-40000, max=40000, vary=True)
params.add("SiO2_k0", value=0, min=-100, max=100, vary=True)
params.add("SiO2_k1", value=0, min=-40000, max=40000, vary=True)
params.add("SiO2_k2", value=0, min=-40000, max=40000, vary=True)
params.add("SiO2_d", value=120, min=0, max=40000, vary=True)

Building the model

Here the model is build and the experimental structure is returned. For details on this process please refer to the Basic usage example. When executed in an jupyter notebook this displays an interactive graph with which you can select the start parameters before fitting the data.

@fit_mueller_matrix(MM, params, display_single=False, sharex=True, full_scale=False)
def model(lbda, params):
    sr = elli.TableSpectraRay("./")
    Si = elli.IsotropicMaterial(sr.load_dispersion_table("Si_Aspnes.mat"))

    SiO2 = elli.Cauchy(
        params["SiO2_n0"],
        params["SiO2_n1"],
        params["SiO2_n2"],
        params["SiO2_k0"],
        params["SiO2_k1"],
        params["SiO2_k2"],
    ).get_mat()

    Layer = [elli.Layer(SiO2, params["SiO2_d"])]

    return elli.Structure(elli.AIR, Layer, Si).evaluate(
        lbda, 70, solver=elli.Solver4x4, propagator=elli.PropagatorExpm()
    )

Plot & Fit the model

Here we plot the model at the initial parameter set vs. the experimental data.

model.plot()


We can also plot the residual between measurement and model.

model.plot_residual()


Now we execute a fit and plot the model afterwards.

fit_stats = model.fit()
model.plot(full_scale=False)


For comparison we plot the residual again to have a figure of merit for the fit quality

model.plot_residual()


We may also print the fit statistics.

fit_stats

Fit Statistics

fitting methodleastsq
# function evals107
# data points22336
# variables7
chi-square 3.00724125
reduced chi-square 1.3468e-04
Akaike info crit.-199065.246
Bayesian info crit.-199009.149

Variables

name value standard error relative error initial value min max vary
SiO2_n0 1.45271090 5.7283e-04 (0.04%) 1.452 -100.000000 100.000000 True
SiO2_n1 30.7219302 0.55071732 (1.79%) 36.0 -40000.0000 40000.0000 True
SiO2_n2 3.41045196 0.25310586 (7.42%) 0 -40000.0000 40000.0000 True
SiO2_k0 0.00393831 5.7844e-04 (14.69%) 0 -100.000000 100.000000 True
SiO2_k1 -6.96289983 0.91851787 (13.19%) 0 -40000.0000 40000.0000 True
SiO2_k2 3.16088573 0.36347412 (11.50%) 0 -40000.0000 40000.0000 True
SiO2_d 103.560411 0.08979903 (0.09%) 120 0.00000000 40000.0000 True

Correlations (unreported correlations are < 0.100)

SiO2_k1SiO2_k2-0.9755
SiO2_k0SiO2_k1-0.9639
SiO2_n1SiO2_n2-0.9509
SiO2_k0SiO2_d0.9506
SiO2_n0SiO2_d-0.9496
SiO2_n0SiO2_k0-0.9026
SiO2_k0SiO2_k20.8907
SiO2_k1SiO2_d-0.8461
SiO2_n0SiO2_k10.8035
SiO2_k2SiO2_d0.7348
SiO2_n0SiO2_k2-0.6978
SiO2_n1SiO2_d-0.4582
SiO2_n1SiO2_k0-0.4355
SiO2_n1SiO2_k10.3877
SiO2_n1SiO2_k2-0.3367
SiO2_n2SiO2_d0.2265
SiO2_n2SiO2_k00.2153
SiO2_n2SiO2_k1-0.1916
SiO2_n2SiO2_k20.1664
SiO2_n0SiO2_n10.1663


References

Here you can find the latest jupyter notebook and data files of this example.

Total running time of the script: ( 0 minutes 17.315 seconds)