<jupyter_start><jupyter_text>Rotating Analyzer Ellipsometry**<NAME>****May 2020**<jupyter_code>import numpy as np
import matplotlib.pyplot as plt

import pypolar.fresnel as fresnel
import pypolar.ellipsometry as ell<jupyter_output><empty_output><jupyter_text>Ellipsometer LayoutA basic ellipsometer configuration is shown below.  Typically the incident light is linearly polarized but the reflected light is, in general, elliptically polarized. $$E_{rs} = r_s e^{j\delta_s} E_{is} $$and$$E_{rp} = r_p e^{j\delta_p}E_{ip} $$The parameter $\Delta$ describes the change in phase from parallel polarization after reflection (because $E_p$ and $E_s$ are in phase before incidence).  The amplitude ratio (parallel vs perpendicular reflected light) is represented by $\tan\psi$.    Rotating EllipsometerAn ellipsometer is usually used at a single angle of incidence with linear or circularly polarized light.  The reflected light passes through a rotating analyzer before hitting the detector. The reflected light is monitored over 360° with each rotation.  This produces a sinusoidal signal that looks like$$I(\phi) = I_\mathrm{DC} + I_C \cos 2\phi + I_S \sin 2\phi$$where $\phi$ is the angle that the analyzer makes with the plane of incidence.  To extract the index of refraction of a substrate three things must be done1. fit the ellipsometer signal to obtain $I_\mathrm{DC}$, $I_S$, and $I_C$2. calculate $\alpha =I_C/I_\mathrm{DC}$ and $\beta =I_S/I_\mathrm{DC}$2. calculate $\rho =\tan\psi\cdot\exp(j\Delta)$ from $\alpha$ and $\beta$3. calculate the index of refraction using $\rho$  Fitting to a sinusoidWe want to fit the detected signal $I(\phi)$ to find average value $I_\mathrm{DC}$ as well as the two Fourier coefficients $I_S$ and $I_C$ $$I(\phi) = I_\mathrm{DC} + I_C \cos 2\phi + I_S \sin 2\phi$$Our ellipsometer digitizes the signal every 5 degrees to produce an array of 72 elements, $I_i$.  The first challenge is to determine the coefficients $I_\mathrm{DC}$, $I_S$, and $I_C$ using these discrete data points$$I_i = I_\mathrm{DC}  + I_C \cos2\phi_i + I_S \sin2\phi_i$$where, $\phi_i=2\pi i/N$.The DC offset $I_\mathrm{DC}$ is found by averaging over one analyzer rotation ($0\le\phi\le2\pi$)$$I_\mathrm{DC} = {1\over N}\sum_{i=0}^{N-1} I_i$$The Fourier coefficients are given by$$  I_S = {1\over \pi} \int_0^{2\pi} I(\phi)\sin(2\phi) \,{\rm d} \phi \qquad\mbox{and}\qquad I_C = {1\over \pi} \int_0^{2\pi} I(\phi)\cos(2\phi) \,{\rm d} \phi$$For the discrete case this becomes$$  I_S = {1\over \pi} \sum_{i=0}^{N-1} I_i\sin2\phi_i  \cdot \Delta\phi\qquad\mbox{and}\qquad I_C = {1\over \pi} \sum_{i=0}^{N-1} I_i\cos2\phi_i \cdot \Delta\phi$$where $\Delta\phi=2\pi/N$.  If we substitute for for $\Delta\phi$, then we recognize that $I_C$ and $I_S$ are just the weighted averages of $I_i$ over one drum rotation$$ I_S = {1\over N} \sum_{i=0}^{N-1} I_i\cdot 2\sin 2\phi_i\qquad\mbox{and}\qquad I_C = {1\over N} \sum_{i=0}^{N-1} I_i\cdot 2\cos 2\phi_i$$where the quantities in brackets need only be calculated once at the beginning of the analysis.  Every rotation of the drum requires three averages to be calculated.Below is a test with random noise added to a known sinsoidal signal.<jupyter_code>degrees = np.linspace(0,360,num=72,endpoint=False)
phi = degrees*np.pi/180

# this is the signal we will try to recover
a=4.1
b=0.8
c=0.5
error=0.1
signal=ell.rotating_analyzer_signal(phi,a,b,c,error)

# finding the offset and coefficients of the sin() and cos() terms
# np.average sums the array and divides by the number of elements N
I_DC = np.average(signal)
I_S = 2*np.average(signal*np.sin(2*phi))
I_C = 2*np.average(signal*np.cos(2*phi))

plt.scatter(degrees,signal,s=5,color='red')
plt.plot(degrees,I_DC + I_S*np.sin(2*phi) + I_C*np.cos(2*phi))
plt.xlabel("Analyzer Angle (degrees)")
plt.ylabel("Ellipsometer Intensity")
plt.xlim(-10,370)
plt.show()

print("I_DC expected=%.3f obtained=%.3f" % (a,I_DC))
print("I_S  expected=%.3f obtained=%.3f" % (b,I_S))
print("I_C  expected=%.3f obtained=%.3f" % (c,I_C))<jupyter_output><empty_output><jupyter_text>Isotropic, Homogeneous MaterialsConverting $\alpha$ and $\beta$ to surface properties requires a physical model for the surface.  The simplest model is that of an isotropic flat material with reflection determined by the Fresnel reflection properties.  Tompkins 2005, page 282, writes>The major problem with this approach is that it ignores the surface layer of the material. All materials have this surface overlayer, which may due to surface roughness, surface oxide, surface reconstruction, etc. Therefore, any realistic model of the sample is more complicated than a simple air/material, and [this analysis] is not valid for any model involving a surface overlayer. However, [it] is quite useful as a limiting case ...The expression for the electric field at the detector can be found using Jones matrices.  The incident light passes through a linear polarizer at an angle $\theta_p$ relative to the plane of incidence.  The light is reflected off the surface and then passes through a linear analyzer at an angle $\phi$.  The electric field is$$E_D=\left[\begin{array}{cc}\cos^2\phi & \sin \phi\cos\phi\\\sin\phi\cos\phi & \sin^2\phi\\\end{array}\right]\cdot\left[\begin{array}{cc}r_p(\theta_i) & 0\\0 & r_s(\theta_i)\\\end{array}\right]\cdot\left[\begin{array}{cc}\cos^2\theta_p & \sin \theta_p\cos \theta_p\\\sin \theta_p\cos \theta_p & \sin^2 \theta_p\\\end{array}\right]\cdot\left[\begin{array}{c}1\\0\\\end{array}\right]$$Therefore$$E_D=\left[\begin{array}{cc}r_p(\theta_i)\cos^2\theta_p\cos^2\phi+r_s(\theta_i)\cos \theta_p\cos\phi\sin \theta_p \sin\phi\\r_p(\theta_i)\cos^2 \theta_p\cos\phi\sin\phi+r_s(\theta_i)\cos P\sin \theta_p \sin^2\phi\\\end{array}\right]$$and since the intensity is product of $E_D$ with its conjugate transpose $I=E_D\cdot E_D^T$ with a bit of algebra we find that$$I_\mathrm{measured} = \cos^2\theta_p \cdot\left|r_p(\theta_i)\cos\theta_p \cos\phi+r_s(\theta_i)\sin\theta_p \sin\phi\right|^2$$and with even more algebra this can be related back to the sinusoidal function$$I(\phi) = I_\mathrm{DC} + I_C \cos 2\phi + I_S \sin 2\phi$$which leads to$$\alpha = {I_C\over I_\mathrm{DC}} = {\tan^2\psi -\tan^2 \theta_p \over \tan^2\psi+\tan^2 \theta_p}\qquad\mbox{and}\qquad\beta = {I_S\over I_\mathrm{DC}} = {2\tan\psi \cos\Delta \tan \theta_p \over \tan^2\psi+\tan^2 \theta_p}$$<jupyter_code>degrees = np.linspace(0,360,num=72,endpoint=False)
phi = degrees*np.pi/180

# create signal
m=3-0.2j                # sample index of refraction
P=30                    # incident polarization azimuth (degrees)
theta_p = np.radians(P) # incident polarization azimuth (radians)
th=70                   # angle of incidence (degrees)
theta_i = np.radians(th)  # angle of incidence (radians)

# Generate 72 intensities based on experimental conditions
# On reflected intensity for each angle of the rotating analyzer
phi_deg = np.linspace(0,360,num=72,endpoint=False)
phi = np.radians(phi_deg)
signal = ell.rotating_analyzer_signal_from_m(phi, m, theta_i, theta_p, noise=0.0003)

# analyze signal
rho, fit = ell.rho_from_rotating_analyzer_data(phi, signal, theta_p)
m2 = ell.m_from_rho(rho,theta_i)

# display data and fit
plt.plot(degrees,signal,'xr')
plt.plot(degrees,fit)

plt.xlabel("Analyzer Angle (degrees)")
plt.ylabel("Ellipsometer Measurement")
plt.title("%.1f° Linear Polarized Light, No QWP" % np.degrees(theta_p))
plt.xlim(-10,370)
plt.show()


print("m=%.3f%+.3fj (expected)"%(m.real,m.imag))
print("m=%.3f%+.3fj"%(m2.real,m2.imag))

#rho2 = ell.rho_from_m(m,theta_i)
#print("rho=%.3f%+.3fj (expected)"%(rho2.real,rho2.imag))
#print("rho=%.3f%+.3fj"%(rho.real,rho.imag))<jupyter_output><empty_output><jupyter_text>Ellipsometry ParametersIf linearly polarized light is incident with an azimuthal angle $\theta_p$ (where $\theta_p=0^\circ$ is in the plane of incidence) thenthe normalized intensity is $${I(\phi)\over I_\mathrm{DC}} = 1 + \alpha \cos 2\phi + \beta\sin 2\phi$$ No quarter wave plate in the incident beam ($0\le\theta_p\le90°$)The parameters $\psi$ and $\Delta$ can now be calculated from $\alpha$ and $\beta$$$\tan\psi = \sqrt{1+\alpha\over 1-\alpha}\cdot |\tan \theta_p| \qquad\mbox{and}\qquad\cos\Delta = {\beta\over\sqrt{1-\alpha^2}}\cdot{\tan \theta_p\over |\tan \theta_p|} $$Or in terms of $I_\mathrm{DC}$, $I_S$, and $I_C$$$\tan\psi = \sqrt{I_\mathrm{DC}+I_C\over I_\mathrm{DC}-I_C}\cdot |\tan \theta_p|\qquad\mbox{and}\qquad\cos\Delta = {I_S\over\sqrt{I_\mathrm{DC}^2-I_C^2}}\cdot{\tan \theta_p\over |\tan \theta_p|}$$  Example* generate a theoretical ellipsometer signal* fit the signal to determine m<jupyter_code># Experimental Conditions
m=1.5-0.0j              # sample index of refraction
P=45                    # incident polarization azimuth (degrees)
theta_p = np.radians(P) # incident polarization azimuth (radians)
th=70                   # angle of incidence (degrees)
theta_i = np.radians(th)  # angle of incidence (radians)

# Generate 72 intensities based on experimental conditions
# On reflected intensity for each angle of the rotating analyzer
phiD = np.linspace(0,360,num=72,endpoint=False)
phi = np.radians(phiD)
signal = ell.rotating_analyzer_signal_from_m(phi, m, theta_i, theta_p, noise=0.005)

# Calculate Delta, tanpsi, and index from 72 intensities
rho, fit = ell.rho_from_rotating_analyzer_data(phi, signal, theta_p)
m2 = ell.m_from_rho(rho, theta_i)
tanpsi, Delta = ell.tanpsi_Delta_from_rho(rho)

# Show the results
plt.plot(phiD, signal, 'or')
plt.plot(phiD, fit, color="blue")
plt.title(r"P=%d°, $\theta_i=$%d, m=%.1f-%.1fj" % (P,th,m.real,-m.imag))
plt.xlabel("Analyzer Angle (degrees)")
plt.ylabel("Ellipsometer Intensity")
plt.xlim(-10,370)
plt.show()

print("Fitted Delta=%.1f°, tanpsi=%.3f" % (np.degrees(Delta),tanpsi))
print("Original  refractive index = %.3f%+.3fj" % (m.real,m.imag))
print("Recovered refractive index = %.3f%+.3fj" % (m2.real,m2.imag))<jupyter_output><empty_output><jupyter_text>Sensitivity to random gaussian noise<jupyter_code># Experimental Conditions
m=3.0-0.2j                # sample index of refraction
P=20                    # incident linear polarization angle (degrees)
theta_p = np.radians(P) # incident linear polarization angle (radians)
th=30                   # angle of incidence (degrees)
theta_i = np.radians(th)# angle of incidence (radians)

# Generate 72 intensities based on experimental conditions
phi = np.radians(np.linspace(0,360,num=72,endpoint=False))

# error free signal needed to scale the plots
signal = ell.rotating_analyzer_signal_from_m(phi, m, theta_i, theta_p)
scale = signal.mean()
ymax = signal.max()

N=16
dev = np.zeros(N)
err = np.zeros(N)
print("m=%.3f%+.3f  expected" % (m.real,m.imag))

# create fit plots for each error
plt.subplots(4,4,figsize=(10,10))
for i in range(N):
    error = scale*2**(-i-2)
    signal = ell.rotating_analyzer_signal_from_m(phi, m, theta_i, theta_p, noise=error)
    rho, fit = ell.rho_from_rotating_analyzer_data(phi, signal, theta_p)
    m2 = ell.m_from_rho(rho, theta_i)
    dev[i]=abs(m2-m)
    rel_error = 100*error/ymax
    err[i]=rel_error
    plt.subplot(4,4,i+1)
    plt.plot(np.degrees(phi), signal, 'ob')
    plt.plot(np.degrees(phi), fit, 'k')
    plt.ylim(-0.1*ymax,1.1*ymax)
    plt.text(0,0.99*ymax,'%.4f%%'%rel_error)
    plt.yticks([])
    plt.xticks([])
    
    print("m=%.3f%+.3f  error=%.3f%%" % (m2.real,m2.imag,rel_error))

plt.show()
plt.scatter(err,dev)
plt.xlim(1e-4,100)
plt.ylim(5e-4,20)
plt.xscale('log')
plt.yscale('log')
plt.xlabel('Relative Noise Added to Signal (%)')
plt.ylabel('Absolute Error in Measured Index')
plt.title("Expected m=%.3f%+.3f" % (m.real,m.imag))
plt.show()<jupyter_output>m=3.000-0.200  expected
m=0.610-0.968  error=12.505%
m=2.197-0.821  error=6.253%
m=1.713-1.200  error=3.126%
m=11.331+0.000  error=1.563%
m=2.751-0.797  error=0.782%
m=4.636+0.000  error=0.391%
m=2.941-0.412  error=0.195%
m=3.702+0.000  error=0.098%
m=2.989-0.295  error=0.049%
m=3.014-0.068  error=0.024%
m=3.002-0.188  error=0.012%
m=3.000-0.200  error=0.006%
m=3.005-0.165  error=0.003%
m=3.002-0.186  error=0.002%
m=3.001-0.196  error=0.001%
m=2.999-0.204  error=0.000%<jupyter_text>Not all measurements give equally good results<jupyter_code># Experimental Conditions
phi = np.radians(np.linspace(0,360,num=72,endpoint=False))
tanpsi = 0.5

print("   P     Delta  tanpsi     Delta  tanpsi  Delta tanpsi")
for P in [1, 45, 89, 91, 178, -10, -89]:
    for Del in [10, 67, 120, 178]:

        Delta   = np.radians(Del)
        theta_p = np.radians(P)
        rho0 = tanpsi*np.exp(1j*Delta)
        
        print("%6.1f° %6.1f° %6.3f:   " % (P,Del,tanpsi), end='')
#        print(" [%.2f° %.2f] " % (np.degrees(np.angle(rho0)),rho.imag),end='')

        signal = ell.rotating_analyzer_signal_from_rho(phi, rho0, theta_p, noise=0.0002)

        rho, fit = ell.rho_from_rotating_analyzer_data(phi, signal, theta_p)
        print("%8.3f° %6.3f" % (np.degrees(np.angle(rho)),np.abs(rho)))<jupyter_output>P     Delta  tanpsi     Delta  tanpsi  Delta tanpsi
   1.0°   10.0°  0.500:      6.748°  0.504
   1.0°   67.0°  0.500:     67.123°  0.498
   1.0°  120.0°  0.500:    120.367°  0.506
   1.0°  178.0°  0.500:    173.541°  0.497
  45.0°   10.0°  0.500:     10.051°  0.500
  45.0°   67.0°  0.500:     67.000°  0.500
  45.0°  120.0°  0.500:    119.997°  0.500
  45.0°  178.0°  0.500:    177.919°  0.500
  89.0°   10.0°  0.500:      0.000°  0.255
  89.0°   67.0°  0.500:     62.731°  0.424
  89.0°  120.0°  0.500:    126.181°  0.422
  89.0°  178.0°  0.500:    142.567°  0.630
  91.0°   10.0°  0.500:     33.703°  0.592
  91.0°   67.0°  0.500:     67.805°  0.519
  91.0°  120.0°  0.500:    129.220°  0.396
  91.0°  178.0°  0.500:   -180.000°  0.375
 178.0°   10.0°  0.500:     10.602°  0.499
 178.0°   67.0°  0.500:     66.955°  0.501
 178.0°  120.0°  0.500:    120.087°  0.502
 178.0°  178.0°  0.500:    177.213°  0.500
 -10.0°   10.0°  0.500:      9.935°  0.500
 -10.0°   67.0°  0.500:     67.001°  0.500[...]
{'fraction_non_alphanumeric': 0.15828984056624007, 'fraction_numerical': 0.06927861585758203, 'mean_word_length': 3.942756183745583, 'pattern_counts': {'":': 0, '<': 24, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '599911', 'license': 'MIT', 'n_tokens_mistral': 6002, 'n_tokens_neox': 5367, 'n_words': 1356, 'path': 'docs/10c-Ellipsometry.ipynb', 'repo_name': 'gyger/pypolar', 'size': '13992'}