Skip to content

Example: Fit two layers reflectance spectrum (demo)

Leandro Acquaroli edited this page Nov 28, 2019 · 3 revisions

Here we will demonstrate the capabilities of the fit function to perform over every layer with type ModelFit. We will create a system with two active layers: the incident medium is air, then a porous silicon layer of 0.7 porosity and 300 nm thick, then a layer of glass with 250 nanometer thickness, and the substrate made of crystalline silicon.

As usual, the light hits the air medium first, goes through the porous layer, then the glass layer and reflect off on the substrate.

The complete code can be found here.

Load modules

using Plots
pyplot()
closeall()
using Optim
using ThinFilmsTools

Create the reflectance spectrum

Since we do not have a two layer system available, and for simplicity, we create the reflectance spectrum using the TMMOptics function.

function bilayer_reflectance(beam, incident, emergent)
    layers2 = [ LayerTMMO(incident),
                LayerTMMO(RI.looyenga([0.70 1.0-0.70],[incident emergent]); d=300.),
                LayerTMMO(RIdb.glass(beam.λ./1e3); d=250.),
                LayerTMMO(emergent) ]
    sol = tmm_optics(beam, layers2)
    return vec(beam.p.*sol.Spectra.Rp .+ (1.0 - beam.p).*sol.Spectra.Rs)
end

Define the beam

# Wavelength range [nm]
λ = 250:900
# Angle of incidence [degrees]
θ = [5.]
# Polarisation (1.0 = p, 0.0 = s, between 0.0 and 1.0 = average)
pol = 0.5
beam = PlaneWave(λ, θ; p=pol)

Create the absolute reflectance spectrum to fit

Rexp_norm = bilayer_reflectance(beam, incident, emergent)

System of layers to fit

We use the same configuration of layers as those created. Notice that we want to fit the porous and glass layers parameters, so we set the second and third layers as ModelFit, using the Looyenga EMA for the porous layer and the Sellmeier equation to model the glass index of refraction. The thickness of each layer is fitted by default as well.

incident = RIdb.air(beam.λ)
emergent = RIdb.silicon(beam.λ)

layers = [
   LayerTMMO(incident),
   ModelFit(:looyenga; N=(ninc=incident, nhost=emergent)),
   ModelFit(:sellmeier),
   LayerTMMO(emergent),
]

Fit and compare results

Notice the seed parameter wraps in an array, the two array initial guesses for the two layers of interest. The first one corresponds to the porous layer and the second to the glass layer. The Sellmeier model has six parameters plus the thickness (check RefractiveIndicesModels.jl).

options = Optim.Options(
   g_abstol=1e-8, g_reltol=1e-8, iterations=10^6, show_trace=true, store_trace=false,
);

seed = [
   [300, 0.7],
   vcat(250.0, [1.0, 0.23, 1.0, 6e-3, 2e-2, 1e-2]),
]

solOptim = fit_tmm_optics(
    Reflectance(Rexp_norm), seed, beam, layers;
    options=options, alg=SAMIN(), lb=0.5.*seed, ub=1.5.*seed,
)

plot(FitSpectrum(), solOptim.beam.λ, solOptim.spectrumExp, solOptim.spectrumFit)
gui()

Fit results

The results we got are:

julia> solOptim.objfunMin
6.158481787202754e-9

julia> solOptim.optParams
2-element Array{Array{Float64,1},1}:
 [299.688596366821, 0.7000206437221619]
 [249.54056092622545, 0.626152750665722, 0.11815261899269534, 0.5184304810189039, 0.008015953858193134, 0.010645912702937665, 0.011978284930304835]

Where the first elements of solOptim.optParams are the thickness and porosity of the Looyenga model for the porous layer, respectively, and the second element corresponds to the thickness and Sellmeier parameters for the glass layer, respectively.

Clone this wiki locally