Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ok-landscape
GitHub Repository: Ok-landscape/computational-pipeline
Path: blob/main/latex-templates/templates/optics/thin_film.tex
51 views
unlisted
1
\documentclass[a4paper, 11pt]{article}
2
\usepackage[utf8]{inputenc}
3
\usepackage[T1]{fontenc}
4
\usepackage{amsmath, amssymb}
5
\usepackage{graphicx}
6
\usepackage{siunitx}
7
\usepackage{booktabs}
8
\usepackage[makestderr]{pythontex}
9
10
\title{Optics: Thin Film Interference and Coatings}
11
\author{Computational Science Templates}
12
\date{\today}
13
14
\begin{document}
15
\maketitle
16
17
\section{Introduction}
18
Thin film interference creates colors in soap bubbles and enables anti-reflection coatings, high reflectors, and narrow-band filters. This analysis computes reflectance and transmittance spectra for single-layer and multi-layer thin film structures using Fresnel equations and the transfer matrix method, with applications in optical coating design.
19
20
\section{Mathematical Framework}
21
22
\subsection{Fresnel Equations}
23
At normal incidence, the amplitude reflection coefficient:
24
\begin{equation}
25
r_{ij} = \frac{n_i - n_j}{n_i + n_j}
26
\end{equation}
27
28
For oblique incidence:
29
\begin{align}
30
r_s &= \frac{n_i\cos\theta_i - n_t\cos\theta_t}{n_i\cos\theta_i + n_t\cos\theta_t} \\
31
r_p &= \frac{n_t\cos\theta_i - n_i\cos\theta_t}{n_t\cos\theta_i + n_i\cos\theta_t}
32
\end{align}
33
34
\subsection{Single Layer Reflectance}
35
For a single layer on a substrate:
36
\begin{equation}
37
R = \left|\frac{r_{01} + r_{12}e^{2i\delta}}{1 + r_{01}r_{12}e^{2i\delta}}\right|^2
38
\end{equation}
39
where $\delta = \frac{2\pi n_1 d \cos\theta_1}{\lambda}$ is the optical phase thickness.
40
41
\subsection{Transfer Matrix Method}
42
For a layer with refractive index $n$ and thickness $d$:
43
\begin{equation}
44
M = \begin{pmatrix} \cos\delta & \frac{i\sin\delta}{\eta} \\ i\eta\sin\delta & \cos\delta \end{pmatrix}
45
\end{equation}
46
where $\eta = n\cos\theta$ for s-polarization and $\eta = n/\cos\theta$ for p-polarization.
47
48
\section{Environment Setup}
49
\begin{pycode}
50
import numpy as np
51
import matplotlib.pyplot as plt
52
plt.rc('text', usetex=True)
53
plt.rc('font', family='serif')
54
55
def save_plot(filename, caption=""):
56
plt.savefig(filename, bbox_inches='tight', dpi=150)
57
print(r'\begin{figure}[htbp]')
58
print(r'\centering')
59
print(r'\includegraphics[width=0.95\textwidth]{' + filename + '}')
60
if caption:
61
print(r'\caption{' + caption + '}')
62
print(r'\end{figure}')
63
plt.close()
64
\end{pycode}
65
66
\section{Single Layer Anti-Reflection Coating}
67
\begin{pycode}
68
def fresnel_r(n1, n2):
69
"""Fresnel reflection coefficient at normal incidence."""
70
return (n1 - n2) / (n1 + n2)
71
72
def single_layer_reflectance(wavelength, d, n0, n1, n2):
73
"""Reflectance of single layer thin film."""
74
# Phase thickness
75
delta = 2 * np.pi * n1 * d / wavelength
76
77
# Fresnel coefficients
78
r01 = fresnel_r(n0, n1)
79
r12 = fresnel_r(n1, n2)
80
81
# Total reflection coefficient
82
r = (r01 + r12 * np.exp(2j * delta)) / (1 + r01 * r12 * np.exp(2j * delta))
83
84
return np.abs(r)**2
85
86
# Wavelength range
87
wavelengths = np.linspace(400e-9, 800e-9, 500)
88
89
# Single layer anti-reflection coating (MgF2 on glass)
90
n_air = 1.0
91
n_MgF2 = 1.38
92
n_glass = 1.52
93
d_AR = 550e-9 / (4 * n_MgF2) # Quarter-wave at 550 nm
94
95
R_AR = [single_layer_reflectance(lam, d_AR, n_air, n_MgF2, n_glass) for lam in wavelengths]
96
97
# Uncoated glass
98
R_glass = fresnel_r(n_air, n_glass)**2 * np.ones_like(wavelengths)
99
100
# Optimal coating: n_coating = sqrt(n_substrate)
101
n_optimal = np.sqrt(n_glass)
102
d_optimal = 550e-9 / (4 * n_optimal)
103
R_optimal = [single_layer_reflectance(lam, d_optimal, n_air, n_optimal, n_glass) for lam in wavelengths]
104
105
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
106
107
# Plot 1: Anti-reflection coating
108
axes[0, 0].plot(wavelengths*1e9, np.array(R_AR)*100, 'b-', linewidth=2, label='MgF$_2$ ($n = 1.38$)')
109
axes[0, 0].plot(wavelengths*1e9, np.array(R_optimal)*100, 'g--', linewidth=2, label=f'Optimal ($n = {n_optimal:.2f}$)')
110
axes[0, 0].plot(wavelengths*1e9, R_glass*100, 'r--', linewidth=1.5, label='Uncoated')
111
axes[0, 0].set_xlabel('Wavelength (nm)')
112
axes[0, 0].set_ylabel('Reflectance (\\%)')
113
axes[0, 0].set_title('Single Layer Anti-Reflection Coating')
114
axes[0, 0].legend(fontsize=8)
115
axes[0, 0].grid(True, alpha=0.3)
116
117
# Plot 2: Reflectance vs film thickness
118
thicknesses = np.linspace(0, 1000e-9, 200)
119
wavelength_plot = 550e-9
120
R_vs_d = [single_layer_reflectance(wavelength_plot, d, n_air, n_MgF2, n_glass) for d in thicknesses]
121
122
axes[0, 1].plot(thicknesses*1e9, np.array(R_vs_d)*100, 'b-', linewidth=2)
123
axes[0, 1].axvline(x=d_AR*1e9, color='red', linestyle='--', alpha=0.7, label='QW thickness')
124
axes[0, 1].axhline(y=0, color='gray', linestyle='-', alpha=0.3)
125
axes[0, 1].set_xlabel('Film Thickness (nm)')
126
axes[0, 1].set_ylabel('Reflectance (\\%)')
127
axes[0, 1].set_title(f'Reflectance vs Thickness ($\\lambda = 550$ nm)')
128
axes[0, 1].legend()
129
axes[0, 1].grid(True, alpha=0.3)
130
131
# Plot 3: Different coating materials
132
coating_materials = [
133
('MgF$_2$', 1.38, 'blue'),
134
('SiO$_2$', 1.46, 'green'),
135
('Al$_2$O$_3$', 1.76, 'orange'),
136
('ZrO$_2$', 2.0, 'red')
137
]
138
139
for name, n_coat, color in coating_materials:
140
d_coat = 550e-9 / (4 * n_coat)
141
R_coat = [single_layer_reflectance(lam, d_coat, n_air, n_coat, n_glass) for lam in wavelengths]
142
axes[1, 0].plot(wavelengths*1e9, np.array(R_coat)*100, color=color, linewidth=2, label=name)
143
144
axes[1, 0].axhline(y=R_glass[0]*100, color='gray', linestyle='--', alpha=0.5, label='Uncoated')
145
axes[1, 0].set_xlabel('Wavelength (nm)')
146
axes[1, 0].set_ylabel('Reflectance (\\%)')
147
axes[1, 0].set_title('Different Coating Materials')
148
axes[1, 0].legend(fontsize=8)
149
axes[1, 0].grid(True, alpha=0.3)
150
151
# Plot 4: V-coating (two-layer AR)
152
n_L = 1.38 # MgF2
153
n_H = 2.0 # ZrO2
154
155
# V-coating: QWOT of each material
156
d_L = 550e-9 / (4 * n_L)
157
d_H = 550e-9 / (4 * n_H)
158
159
# Two-layer calculation
160
def two_layer_reflectance(wavelength, d1, n1, d2, n2, n0, n_sub):
161
delta1 = 2 * np.pi * n1 * d1 / wavelength
162
delta2 = 2 * np.pi * n2 * d2 / wavelength
163
164
r01 = fresnel_r(n0, n1)
165
r12 = fresnel_r(n1, n2)
166
r23 = fresnel_r(n2, n_sub)
167
168
# Calculate using characteristic matrix method
169
M1 = np.array([[np.cos(delta1), 1j*np.sin(delta1)/n1],
170
[1j*n1*np.sin(delta1), np.cos(delta1)]])
171
M2 = np.array([[np.cos(delta2), 1j*np.sin(delta2)/n2],
172
[1j*n2*np.sin(delta2), np.cos(delta2)]])
173
174
M = M1 @ M2
175
176
# Reflection coefficient
177
r = (n0*M[0,0] + n0*n_sub*M[0,1] - M[1,0] - n_sub*M[1,1]) / \
178
(n0*M[0,0] + n0*n_sub*M[0,1] + M[1,0] + n_sub*M[1,1])
179
180
return np.abs(r)**2
181
182
R_V = [two_layer_reflectance(lam, d_L, n_L, d_H, n_H, n_air, n_glass) for lam in wavelengths]
183
184
axes[1, 1].plot(wavelengths*1e9, np.array(R_AR)*100, 'b-', linewidth=2, label='Single MgF$_2$')
185
axes[1, 1].plot(wavelengths*1e9, np.array(R_V)*100, 'g-', linewidth=2, label='V-coating (MgF$_2$/ZrO$_2$)')
186
axes[1, 1].axhline(y=R_glass[0]*100, color='gray', linestyle='--', alpha=0.5, label='Uncoated')
187
axes[1, 1].set_xlabel('Wavelength (nm)')
188
axes[1, 1].set_ylabel('Reflectance (\\%)')
189
axes[1, 1].set_title('Single vs Two-Layer AR Coating')
190
axes[1, 1].legend(fontsize=8)
191
axes[1, 1].grid(True, alpha=0.3)
192
193
plt.tight_layout()
194
save_plot('ar_coating.pdf', 'Anti-reflection coatings: single layer, material comparison, and V-coating.')
195
196
# Calculate minimum reflectance
197
R_min = min(R_AR)
198
\end{pycode}
199
200
\section{High Reflector (Dielectric Mirror)}
201
\begin{pycode}
202
def multi_layer_reflectance(wavelength, layers, n_substrate, n_ambient=1.0):
203
"""Reflectance of multi-layer stack using transfer matrix method."""
204
# Initialize transfer matrix
205
M = np.eye(2, dtype=complex)
206
207
for n, d in layers:
208
# Phase thickness
209
delta = 2 * np.pi * n * d / wavelength
210
211
# Layer matrix
212
M_layer = np.array([
213
[np.cos(delta), 1j * np.sin(delta) / n],
214
[1j * n * np.sin(delta), np.cos(delta)]
215
])
216
M = M @ M_layer
217
218
# Calculate reflection coefficient
219
eta0 = n_ambient
220
etas = n_substrate
221
222
num = eta0 * M[0, 0] + eta0 * etas * M[0, 1] - M[1, 0] - etas * M[1, 1]
223
den = eta0 * M[0, 0] + eta0 * etas * M[0, 1] + M[1, 0] + etas * M[1, 1]
224
225
r = num / den
226
return np.abs(r)**2
227
228
# High/low index materials
229
n_H = 2.3 # TiO2
230
n_L = 1.38 # MgF2
231
lambda_0 = 550e-9
232
d_H = lambda_0 / (4 * n_H)
233
d_L = lambda_0 / (4 * n_L)
234
235
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
236
237
# Plot 1: HR mirror with different number of pairs
238
pair_counts = [2, 4, 8, 16]
239
colors_pairs = ['blue', 'green', 'orange', 'red']
240
241
for n_pairs, color in zip(pair_counts, colors_pairs):
242
layers_HR = []
243
for i in range(n_pairs):
244
layers_HR.append((n_H, d_H))
245
layers_HR.append((n_L, d_L))
246
247
R_HR = [multi_layer_reflectance(lam, layers_HR, n_glass) for lam in wavelengths]
248
axes[0, 0].plot(wavelengths*1e9, np.array(R_HR)*100, color=color, linewidth=2,
249
label=f'{n_pairs} pairs')
250
251
axes[0, 0].set_xlabel('Wavelength (nm)')
252
axes[0, 0].set_ylabel('Reflectance (\\%)')
253
axes[0, 0].set_title('High Reflector: (HL)$^N$ Stack')
254
axes[0, 0].legend(fontsize=8)
255
axes[0, 0].grid(True, alpha=0.3)
256
axes[0, 0].set_ylim([0, 105])
257
258
# Plot 2: Peak reflectance vs number of pairs
259
pair_range = np.arange(1, 21)
260
peak_reflectances = []
261
262
for n_pairs in pair_range:
263
layers = []
264
for i in range(n_pairs):
265
layers.append((n_H, d_H))
266
layers.append((n_L, d_L))
267
268
R_peak = multi_layer_reflectance(lambda_0, layers, n_glass)
269
peak_reflectances.append(R_peak * 100)
270
271
axes[0, 1].plot(pair_range, peak_reflectances, 'b-', linewidth=2)
272
axes[0, 1].axhline(y=99.9, color='gray', linestyle='--', alpha=0.7, label='99.9\\%')
273
axes[0, 1].set_xlabel('Number of H/L Pairs')
274
axes[0, 1].set_ylabel('Peak Reflectance (\\%)')
275
axes[0, 1].set_title('Peak Reflectance vs Stack Layers')
276
axes[0, 1].legend()
277
axes[0, 1].grid(True, alpha=0.3)
278
279
# Plot 3: Bandwidth of HR mirror
280
# Theoretical bandwidth: Delta_lambda/lambda_0 = (4/pi) * arcsin((n_H - n_L)/(n_H + n_L))
281
contrast = (n_H - n_L) / (n_H + n_L)
282
bandwidth_theory = (4/np.pi) * np.arcsin(contrast)
283
284
# Measure bandwidth from 16-pair stack
285
n_pairs = 16
286
layers_16 = []
287
for i in range(n_pairs):
288
layers_16.append((n_H, d_H))
289
layers_16.append((n_L, d_L))
290
291
R_16 = np.array([multi_layer_reflectance(lam, layers_16, n_glass) for lam in wavelengths])
292
T_16 = 1 - R_16
293
294
axes[1, 0].semilogy(wavelengths*1e9, T_16*100, 'b-', linewidth=2)
295
axes[1, 0].axhline(y=0.1, color='gray', linestyle='--', alpha=0.7, label='T = 0.1\\%')
296
axes[1, 0].set_xlabel('Wavelength (nm)')
297
axes[1, 0].set_ylabel('Transmittance (\\%)')
298
axes[1, 0].set_title(f'HR Mirror Stop Band ({n_pairs} pairs)')
299
axes[1, 0].legend()
300
axes[1, 0].grid(True, alpha=0.3, which='both')
301
302
# Plot 4: Effect of index contrast
303
contrasts = [
304
(1.8, 1.38, 'Low contrast'),
305
(2.0, 1.38, 'Medium contrast'),
306
(2.3, 1.38, 'High contrast'),
307
(2.5, 1.38, 'Very high contrast')
308
]
309
colors_contrast = ['blue', 'green', 'orange', 'red']
310
311
n_pairs = 8
312
for (nH, nL, label), color in zip(contrasts, colors_contrast):
313
dH = lambda_0 / (4 * nH)
314
dL = lambda_0 / (4 * nL)
315
layers = []
316
for i in range(n_pairs):
317
layers.append((nH, dH))
318
layers.append((nL, dL))
319
320
R = [multi_layer_reflectance(lam, layers, n_glass) for lam in wavelengths]
321
axes[1, 1].plot(wavelengths*1e9, np.array(R)*100, color=color, linewidth=2, label=label)
322
323
axes[1, 1].set_xlabel('Wavelength (nm)')
324
axes[1, 1].set_ylabel('Reflectance (\\%)')
325
axes[1, 1].set_title('Effect of Index Contrast')
326
axes[1, 1].legend(fontsize=8)
327
axes[1, 1].grid(True, alpha=0.3)
328
329
plt.tight_layout()
330
save_plot('hr_mirror.pdf', 'High reflector mirrors: layer count, peak reflectance, stop band, and index contrast.')
331
332
# Calculate max reflectance for results
333
R_max_HR = max(peak_reflectances)
334
\end{pycode}
335
336
\section{Narrow-Band Filters}
337
\begin{pycode}
338
# Fabry-Perot interference filter
339
def fabry_perot_filter(wavelength, d_cavity, n_cavity, layers_mirror, n_sub):
340
"""
341
Fabry-Perot filter: mirror / cavity / mirror on substrate.
342
"""
343
# Build full stack: front mirror + cavity + back mirror
344
full_stack = layers_mirror + [(n_cavity, d_cavity)] + layers_mirror[::-1]
345
return multi_layer_reflectance(wavelength, full_stack, n_sub)
346
347
# Design filter centered at 550 nm
348
n_cavity = 1.38 # MgF2 spacer
349
d_cavity = 550e-9 / (2 * n_cavity) # Half-wave spacer
350
351
# Mirror layers
352
mirror_pairs = 5
353
mirror_layers = []
354
for i in range(mirror_pairs):
355
mirror_layers.append((n_H, d_H))
356
mirror_layers.append((n_L, d_L))
357
358
# Fine wavelength grid for filter
359
wavelengths_fine = np.linspace(500e-9, 600e-9, 1000)
360
361
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
362
363
# Plot 1: Narrow-band filter transmission
364
T_filter = []
365
for lam in wavelengths_fine:
366
R = fabry_perot_filter(lam, d_cavity, n_cavity, mirror_layers, n_glass)
367
T_filter.append((1 - R) * 100)
368
369
axes[0, 0].plot(wavelengths_fine*1e9, T_filter, 'b-', linewidth=2)
370
axes[0, 0].set_xlabel('Wavelength (nm)')
371
axes[0, 0].set_ylabel('Transmittance (\\%)')
372
axes[0, 0].set_title('Narrow-Band Filter (5 pairs each mirror)')
373
axes[0, 0].grid(True, alpha=0.3)
374
375
# Plot 2: Filter with different mirror reflectivity
376
mirror_pair_counts = [3, 5, 7, 9]
377
colors_filter = ['blue', 'green', 'orange', 'red']
378
379
for pairs, color in zip(mirror_pair_counts, colors_filter):
380
m_layers = []
381
for i in range(pairs):
382
m_layers.append((n_H, d_H))
383
m_layers.append((n_L, d_L))
384
385
T = []
386
for lam in wavelengths_fine:
387
R = fabry_perot_filter(lam, d_cavity, n_cavity, m_layers, n_glass)
388
T.append((1 - R) * 100)
389
390
axes[0, 1].plot(wavelengths_fine*1e9, T, color=color, linewidth=2, label=f'{pairs} pairs')
391
392
axes[0, 1].set_xlabel('Wavelength (nm)')
393
axes[0, 1].set_ylabel('Transmittance (\\%)')
394
axes[0, 1].set_title('Filter Bandwidth vs Mirror Reflectivity')
395
axes[0, 1].legend(fontsize=8)
396
axes[0, 1].grid(True, alpha=0.3)
397
398
# Plot 3: Edge filter (long-pass)
399
def edge_filter(wavelength, layers, n_sub, n_ambient=1.0):
400
"""Edge filter design."""
401
return multi_layer_reflectance(wavelength, layers, n_sub, n_ambient)
402
403
# Design long-pass at 500 nm
404
lambda_edge = 500e-9
405
d_H_edge = lambda_edge / (4 * n_H)
406
d_L_edge = lambda_edge / (4 * n_L)
407
408
edge_pairs = 20
409
edge_layers = []
410
for i in range(edge_pairs):
411
edge_layers.append((n_H, d_H_edge))
412
edge_layers.append((n_L, d_L_edge))
413
414
T_edge = []
415
wavelengths_edge = np.linspace(400e-9, 700e-9, 500)
416
for lam in wavelengths_edge:
417
R = edge_filter(lam, edge_layers, n_glass)
418
T_edge.append((1 - R) * 100)
419
420
axes[1, 0].plot(wavelengths_edge*1e9, T_edge, 'b-', linewidth=2)
421
axes[1, 0].axvline(x=500, color='red', linestyle='--', alpha=0.7, label='Design $\\lambda$')
422
axes[1, 0].set_xlabel('Wavelength (nm)')
423
axes[1, 0].set_ylabel('Transmittance (\\%)')
424
axes[1, 0].set_title('Edge Filter (Long-Pass)')
425
axes[1, 0].legend()
426
axes[1, 0].grid(True, alpha=0.3)
427
428
# Plot 4: Bandpass filter using two edge filters
429
lambda_short = 450e-9 # Short-pass edge
430
lambda_long = 550e-9 # Long-pass edge
431
432
# Short-pass
433
d_H_short = lambda_short / (4 * n_H)
434
d_L_short = lambda_short / (4 * n_L)
435
short_layers = []
436
for i in range(15):
437
short_layers.append((n_H, d_H_short))
438
short_layers.append((n_L, d_L_short))
439
440
# Long-pass
441
d_H_long = lambda_long / (4 * n_H)
442
d_L_long = lambda_long / (4 * n_L)
443
long_layers = []
444
for i in range(15):
445
long_layers.append((n_H, d_H_long))
446
long_layers.append((n_L, d_L_long))
447
448
T_bandpass = []
449
for lam in wavelengths_edge:
450
R_short = edge_filter(lam, short_layers, n_glass)
451
R_long = edge_filter(lam, long_layers, n_glass)
452
# Combined transmission
453
T = (1 - R_short) * (1 - R_long) * 100
454
T_bandpass.append(T)
455
456
axes[1, 1].plot(wavelengths_edge*1e9, T_bandpass, 'purple', linewidth=2)
457
axes[1, 1].axvline(x=450, color='blue', linestyle='--', alpha=0.5)
458
axes[1, 1].axvline(x=550, color='red', linestyle='--', alpha=0.5)
459
axes[1, 1].set_xlabel('Wavelength (nm)')
460
axes[1, 1].set_ylabel('Transmittance (\\%)')
461
axes[1, 1].set_title('Bandpass Filter (Edge Filter Combination)')
462
axes[1, 1].grid(True, alpha=0.3)
463
464
plt.tight_layout()
465
save_plot('narrow_band.pdf', 'Narrow-band filters: Fabry-P\\'erot, bandwidth control, and bandpass design.')
466
\end{pycode}
467
468
\section{Angle Dependence and Polarization}
469
\begin{pycode}
470
# Oblique incidence calculations
471
def snell_angle(n1, n2, theta1):
472
"""Calculate refracted angle using Snell's law."""
473
sin_theta2 = n1 * np.sin(theta1) / n2
474
if np.abs(sin_theta2) > 1:
475
return np.pi/2 # Total internal reflection
476
return np.arcsin(sin_theta2)
477
478
def oblique_layer_matrix(n, d, wavelength, theta, polarization='s'):
479
"""Transfer matrix for layer at oblique incidence."""
480
theta_in_layer = snell_angle(1.0, n, theta)
481
delta = 2 * np.pi * n * d * np.cos(theta_in_layer) / wavelength
482
483
if polarization == 's':
484
eta = n * np.cos(theta_in_layer)
485
else: # p-polarization
486
eta = n / np.cos(theta_in_layer)
487
488
M = np.array([
489
[np.cos(delta), 1j * np.sin(delta) / eta],
490
[1j * eta * np.sin(delta), np.cos(delta)]
491
])
492
return M
493
494
def oblique_multilayer_reflectance(wavelength, layers, n_sub, theta, polarization='s'):
495
"""Reflectance at oblique incidence."""
496
M = np.eye(2, dtype=complex)
497
498
for n, d in layers:
499
M_layer = oblique_layer_matrix(n, d, wavelength, theta, polarization)
500
M = M @ M_layer
501
502
# Calculate terminal admittances
503
theta_sub = snell_angle(1.0, n_sub, theta)
504
505
if polarization == 's':
506
eta0 = np.cos(theta)
507
etas = n_sub * np.cos(theta_sub)
508
else:
509
eta0 = 1.0 / np.cos(theta)
510
etas = n_sub / np.cos(theta_sub)
511
512
num = eta0 * M[0, 0] + eta0 * etas * M[0, 1] - M[1, 0] - etas * M[1, 1]
513
den = eta0 * M[0, 0] + eta0 * etas * M[0, 1] + M[1, 0] + etas * M[1, 1]
514
515
r = num / den
516
return np.abs(r)**2
517
518
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
519
520
# Plot 1: AR coating at different angles
521
angles = [0, 15, 30, 45]
522
colors_angle = ['blue', 'green', 'orange', 'red']
523
524
for angle, color in zip(angles, colors_angle):
525
theta = np.deg2rad(angle)
526
R_s = []
527
R_p = []
528
for lam in wavelengths:
529
d_AR_eff = d_AR # Could adjust for angle
530
Rs = single_layer_reflectance(lam, d_AR_eff, n_air, n_MgF2, n_glass) # Simplified
531
R_s.append(Rs * 100)
532
R_p.append(Rs * 100)
533
534
# More accurate calculation for one wavelength
535
layers_AR = [(n_MgF2, d_AR)]
536
Rs_accurate = [oblique_multilayer_reflectance(lam, layers_AR, n_glass, theta, 's') * 100 for lam in wavelengths]
537
538
axes[0, 0].plot(wavelengths*1e9, Rs_accurate, color=color, linewidth=2, label=f'{angle}$^\\circ$')
539
540
axes[0, 0].set_xlabel('Wavelength (nm)')
541
axes[0, 0].set_ylabel('Reflectance (\\%)')
542
axes[0, 0].set_title('AR Coating: Angle Dependence (s-pol)')
543
axes[0, 0].legend(fontsize=8)
544
axes[0, 0].grid(True, alpha=0.3)
545
546
# Plot 2: s vs p polarization
547
theta_45 = np.deg2rad(45)
548
layers_test = [(n_MgF2, d_AR)]
549
550
R_s_45 = [oblique_multilayer_reflectance(lam, layers_test, n_glass, theta_45, 's') * 100 for lam in wavelengths]
551
R_p_45 = [oblique_multilayer_reflectance(lam, layers_test, n_glass, theta_45, 'p') * 100 for lam in wavelengths]
552
553
axes[0, 1].plot(wavelengths*1e9, R_s_45, 'b-', linewidth=2, label='s-polarization')
554
axes[0, 1].plot(wavelengths*1e9, R_p_45, 'r--', linewidth=2, label='p-polarization')
555
axes[0, 1].set_xlabel('Wavelength (nm)')
556
axes[0, 1].set_ylabel('Reflectance (\\%)')
557
axes[0, 1].set_title('Polarization Dependence at 45$^\\circ$')
558
axes[0, 1].legend()
559
axes[0, 1].grid(True, alpha=0.3)
560
561
# Plot 3: Blue shift with angle (HR mirror)
562
angles_shift = np.linspace(0, 60, 50)
563
layers_HR_8 = []
564
for i in range(8):
565
layers_HR_8.append((n_H, d_H))
566
layers_HR_8.append((n_L, d_L))
567
568
# Find peak wavelength at each angle
569
peak_wavelengths = []
570
for angle in angles_shift:
571
theta = np.deg2rad(angle)
572
# Search for peak
573
max_R = 0
574
peak_lam = lambda_0
575
for lam in wavelengths:
576
R = oblique_multilayer_reflectance(lam, layers_HR_8, n_glass, theta, 's')
577
if R > max_R:
578
max_R = R
579
peak_lam = lam
580
peak_wavelengths.append(peak_lam * 1e9)
581
582
axes[1, 0].plot(angles_shift, peak_wavelengths, 'b-', linewidth=2)
583
axes[1, 0].axhline(y=550, color='gray', linestyle='--', alpha=0.5, label='Normal incidence')
584
axes[1, 0].set_xlabel('Angle of incidence (degrees)')
585
axes[1, 0].set_ylabel('Peak wavelength (nm)')
586
axes[1, 0].set_title('Blue Shift of HR Mirror with Angle')
587
axes[1, 0].legend()
588
axes[1, 0].grid(True, alpha=0.3)
589
590
# Plot 4: Polarizing beam splitter concept
591
# At Brewster angle, p-pol has minimum reflection
592
angles_brewster = np.linspace(0, 85, 100)
593
R_glass_s = []
594
R_glass_p = []
595
596
for angle in angles_brewster:
597
theta = np.deg2rad(angle)
598
theta_t = snell_angle(1.0, n_glass, theta)
599
600
# Fresnel coefficients
601
rs = (np.cos(theta) - n_glass * np.cos(theta_t)) / (np.cos(theta) + n_glass * np.cos(theta_t))
602
rp = (n_glass * np.cos(theta) - np.cos(theta_t)) / (n_glass * np.cos(theta) + np.cos(theta_t))
603
604
R_glass_s.append(np.abs(rs)**2 * 100)
605
R_glass_p.append(np.abs(rp)**2 * 100)
606
607
axes[1, 1].plot(angles_brewster, R_glass_s, 'b-', linewidth=2, label='s-polarization')
608
axes[1, 1].plot(angles_brewster, R_glass_p, 'r-', linewidth=2, label='p-polarization')
609
610
# Brewster angle
611
theta_B = np.rad2deg(np.arctan(n_glass))
612
axes[1, 1].axvline(x=theta_B, color='green', linestyle='--', alpha=0.7, label=f'Brewster: {theta_B:.1f}$^\\circ$')
613
614
axes[1, 1].set_xlabel('Angle of incidence (degrees)')
615
axes[1, 1].set_ylabel('Reflectance (\\%)')
616
axes[1, 1].set_title('Glass Surface: s vs p Polarization')
617
axes[1, 1].legend(fontsize=8)
618
axes[1, 1].grid(True, alpha=0.3)
619
620
plt.tight_layout()
621
save_plot('angle_dependence.pdf', 'Angle and polarization effects: AR coating, s/p splitting, blue shift, and Brewster angle.')
622
\end{pycode}
623
624
\section{Soap Bubbles and Natural Thin Films}
625
\begin{pycode}
626
# Soap bubble (thin water film in air)
627
n_water = 1.33
628
d_bubble = 300e-9
629
R_bubble = [single_layer_reflectance(lam, d_bubble, n_air, n_water, n_air) for lam in wavelengths]
630
631
# Oil slick on water
632
n_oil = 1.5
633
d_oil = 400e-9
634
R_oil = [single_layer_reflectance(lam, d_oil, n_air, n_oil, n_water) for lam in wavelengths]
635
636
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
637
638
# Plot 1: Soap bubble colors
639
axes[0, 0].plot(wavelengths*1e9, np.array(R_bubble)*100, 'b-', linewidth=2)
640
axes[0, 0].set_xlabel('Wavelength (nm)')
641
axes[0, 0].set_ylabel('Reflectance (\\%)')
642
axes[0, 0].set_title(f'Soap Bubble ($d = {d_bubble*1e9:.0f}$ nm)')
643
axes[0, 0].grid(True, alpha=0.3)
644
645
# Plot 2: Different bubble thicknesses
646
bubble_thicknesses = [100e-9, 200e-9, 300e-9, 400e-9, 500e-9]
647
colors_bubble = plt.cm.rainbow(np.linspace(0, 1, len(bubble_thicknesses)))
648
649
for d, color in zip(bubble_thicknesses, colors_bubble):
650
R = [single_layer_reflectance(lam, d, n_air, n_water, n_air) for lam in wavelengths]
651
axes[0, 1].plot(wavelengths*1e9, np.array(R)*100, color=color, linewidth=2,
652
label=f'{d*1e9:.0f} nm')
653
654
axes[0, 1].set_xlabel('Wavelength (nm)')
655
axes[0, 1].set_ylabel('Reflectance (\\%)')
656
axes[0, 1].set_title('Soap Bubble Thickness Variation')
657
axes[0, 1].legend(fontsize=8)
658
axes[0, 1].grid(True, alpha=0.3)
659
660
# Plot 3: Oil slick colors
661
axes[1, 0].plot(wavelengths*1e9, np.array(R_oil)*100, 'purple', linewidth=2)
662
axes[1, 0].set_xlabel('Wavelength (nm)')
663
axes[1, 0].set_ylabel('Reflectance (\\%)')
664
axes[1, 0].set_title(f'Oil Slick on Water ($d = {d_oil*1e9:.0f}$ nm)')
665
axes[1, 0].grid(True, alpha=0.3)
666
667
# Plot 4: Color map vs thickness
668
thicknesses_color = np.linspace(50e-9, 600e-9, 100)
669
wavelengths_vis = np.linspace(400e-9, 700e-9, 100)
670
671
T_color = np.zeros((len(thicknesses_color), len(wavelengths_vis)))
672
673
for i, d in enumerate(thicknesses_color):
674
for j, lam in enumerate(wavelengths_vis):
675
R = single_layer_reflectance(lam, d, n_air, n_water, n_air)
676
T_color[i, j] = R
677
678
im = axes[1, 1].pcolormesh(wavelengths_vis*1e9, thicknesses_color*1e9, T_color,
679
cmap='hot', shading='auto')
680
axes[1, 1].set_xlabel('Wavelength (nm)')
681
axes[1, 1].set_ylabel('Film thickness (nm)')
682
axes[1, 1].set_title('Reflectance Color Map (Soap Film)')
683
plt.colorbar(im, ax=axes[1, 1], label='Reflectance')
684
685
plt.tight_layout()
686
save_plot('natural_films.pdf', 'Natural thin films: soap bubbles, oil slicks, and thickness-dependent colors.')
687
\end{pycode}
688
689
\section{Results Summary}
690
\begin{pycode}
691
# Generate results table
692
print(r'\begin{table}[htbp]')
693
print(r'\centering')
694
print(r'\caption{Summary of Thin Film Parameters}')
695
print(r'\begin{tabular}{lll}')
696
print(r'\toprule')
697
print(r'Configuration & Parameter & Value \\')
698
print(r'\midrule')
699
print(r'\multicolumn{3}{c}{\textit{Anti-Reflection Coating}} \\')
700
print(f'Uncoated glass & Reflectance & {R_glass[0]*100:.2f}\\% \\\\')
701
print(f'MgF$_2$ coating & Min reflectance & {R_min*100:.3f}\\% \\\\')
702
print(f'QW thickness & $d$ & {d_AR*1e9:.1f} nm \\\\')
703
print(f'Optimal $n$ & $\\sqrt{{n_{{sub}}}}$ & {n_optimal:.2f} \\\\')
704
print(r'\midrule')
705
print(r'\multicolumn{3}{c}{\textit{High Reflector}} \\')
706
print(f'Peak reflectance & 16 pairs & {R_max_HR:.1f}\\% \\\\')
707
print(f'TiO$_2$ index & $n_H$ & {n_H:.2f} \\\\')
708
print(f'MgF$_2$ index & $n_L$ & {n_L:.2f} \\\\')
709
print(f'Stop band & Theory & {bandwidth_theory*100:.0f}\\% of $\\lambda_0$ \\\\')
710
print(r'\midrule')
711
print(r'\multicolumn{3}{c}{\textit{Brewster Angle}} \\')
712
print(f'Glass & $\\theta_B$ & {theta_B:.1f}$^\\circ$ \\\\')
713
print(r'\bottomrule')
714
print(r'\end{tabular}')
715
print(r'\end{table}')
716
\end{pycode}
717
718
\section{Statistical Summary}
719
\begin{itemize}
720
\item \textbf{Uncoated glass reflectance}: \py{f"{R_glass[0]*100:.2f}"}\%
721
\item \textbf{Minimum AR coating reflectance}: \py{f"{R_min*100:.3f}"}\%
722
\item \textbf{Quarter-wave AR thickness}: \py{f"{d_AR*1e9:.1f}"} nm
723
\item \textbf{Optimal coating index}: $n = \sqrt{n_{glass}} = $ \py{f"{n_optimal:.3f}"}
724
\item \textbf{High reflector peak (16 pairs)}: \py{f"{R_max_HR:.1f}"}\%
725
\item \textbf{Stop band width}: $\approx$ \py{f"{bandwidth_theory*100:.0f}"}\% of $\lambda_0$
726
\item \textbf{Brewster angle (glass)}: \py{f"{theta_B:.1f}"}$^\circ$
727
\end{itemize}
728
729
\section{Conclusion}
730
Thin film interference enables precise control of optical properties through constructive and destructive interference. A single quarter-wave MgF$_2$ layer reduces glass reflectance from 4\% to below 1\% at the design wavelength. Multi-layer stacks can achieve reflectances exceeding 99.99\% for high-power laser mirrors. The transfer matrix method provides an efficient algorithm for analyzing arbitrary layer structures. Angular dependence causes blue-shifting of spectral features and polarization splitting, exploited in polarizing beam splitters. These principles underpin technologies from camera lenses to laser mirrors, interference filters, and optical telecommunications components.
731
732
\end{document}
733
734