Calculate GVD of waveguide¶
Reproduce figure 2e from {cite}Klenner2016
import math
from collections import OrderedDict
import matplotlib.pyplot as plt
import numpy as np
import shapely
from femwell.maxwell.waveguide import compute_modes
from femwell.mesh import mesh_from_OrderedDict
from femwell.visualization import plot_domains
from scipy.interpolate import UnivariateSpline
from skfem import Basis, ElementTriP0
from skfem.io import from_meshio
from tqdm import tqdm
First, construct the geometry of waveguide according to figure 2d of the paper.
width = 0.88 # um
height = 0.69 # um
wavelength_range = [500, 2500]
wavelegnth_step = 50
# Construct waveguide geometry
core = shapely.geometry.box(-width / 2, 0, +width / 2, height)
cladding = shapely.geometry.box(-width * 2, 0, width * 2, height * 3)
buried_oxide = shapely.geometry.box(-width * 2, -height * 2, width * 2, 0)
polygon = OrderedDict(
core=core,
cladding=cladding,
buried_oxide=buried_oxide,
)
# Define material property and resolution of waveguide
resolutions = dict(
core={"resolution": 0.02, "distance": 0.3},
cladding={"resolution": 0.05, "distance": 0.3},
buried_oxide={"resolution": 0.05, "distance": 0.3},
)
mesh = from_meshio(
mesh_from_OrderedDict(polygon, resolutions, default_resolution_max=2)
)
mesh.draw().show()
plot_domains(mesh)
plt.show()


Use sellmeier equation to determine the refractive index of material.
def n_Si3N4(wavelength, fit=False):
if wavelength >= 0.31 and wavelength <= 5.507:
return math.sqrt(
(3.0249 * wavelength**2) / (wavelength**2 - 0.1353406**2)
+ (40314 * wavelength**2) / (wavelength**2 - 1239.842**2)
+ 1
)
else:
raise ValueError(
f"wavelength provided is {wavelength}um, is out of the range for Si3N4"
)
def n_SiO2(wavelength):
if wavelength < 0.21 or wavelength > 6.7:
raise ValueError(
f"wavelength provided is {wavelength}um, is out of the range for {type}"
)
return np.sqrt(
0.6961663 * wavelength**2 / (wavelength**2 - 0.0684043**2)
+ (0.4079426 * wavelength**2 / (wavelength**2 - 0.1162414**2))
+ (0.8974794 * wavelength**2 / (wavelength**2 - 9.896161**2))
+ 1
)
n_dict = {"core": n_Si3N4, "cladding": n_SiO2, "buried_oxide": n_SiO2}
Create the mesh, and sweep wavelength using the same mesh. The target mode is te mode, so the mode is selected by highest te fraction
neff_list = []
aeff_list = []
basis0 = Basis(mesh, ElementTriP0())
epsilon = basis0.zeros()
wavelength_list = np.linspace(wavelength_range[0], wavelength_range[1], wavelegnth_step)
for wavelength in tqdm(wavelength_list):
wavelength = wavelength * 1e-3
for subdomain, n in n_dict.items():
epsilon[basis0.get_dofs(elements=subdomain)] = n(wavelength) ** 2
modes = compute_modes(basis0, epsilon, wavelength=wavelength, num_modes=3, order=1)
modes_sorted = modes.sorted(key=lambda mode: -np.real(mode.te_fraction))
mode = modes_sorted[0]
neff_list.append(np.real(mode.n_eff))
aeff_list.append(np.real(mode.calculate_effective_area()))
0%| | 0/50 [00:00<?, ?it/s]
2%|▏ | 1/50 [00:03<03:04, 3.77s/it]
4%|▍ | 2/50 [00:07<03:07, 3.92s/it]
6%|▌ | 3/50 [00:11<03:00, 3.85s/it]
8%|▊ | 4/50 [00:15<02:55, 3.81s/it]
10%|█ | 5/50 [00:19<02:50, 3.79s/it]
12%|█▏ | 6/50 [00:22<02:46, 3.78s/it]
14%|█▍ | 7/50 [00:26<02:41, 3.77s/it]
16%|█▌ | 8/50 [00:30<02:37, 3.75s/it]
18%|█▊ | 9/50 [00:34<02:33, 3.75s/it]
20%|██ | 10/50 [00:37<02:29, 3.75s/it]
22%|██▏ | 11/50 [00:41<02:25, 3.74s/it]
24%|██▍ | 12/50 [00:45<02:22, 3.74s/it]
26%|██▌ | 13/50 [00:48<02:18, 3.74s/it]
28%|██▊ | 14/50 [00:52<02:14, 3.74s/it]
30%|███ | 15/50 [00:56<02:11, 3.75s/it]
32%|███▏ | 16/50 [01:00<02:07, 3.74s/it]
34%|███▍ | 17/50 [01:04<02:05, 3.81s/it]
36%|███▌ | 18/50 [01:08<02:03, 3.86s/it]
38%|███▊ | 19/50 [01:12<02:01, 3.92s/it]
40%|████ | 20/50 [01:16<01:58, 3.95s/it]
42%|████▏ | 21/50 [01:20<01:55, 3.98s/it]
44%|████▍ | 22/50 [01:24<01:54, 4.09s/it]
46%|████▌ | 23/50 [01:28<01:51, 4.14s/it]
48%|████▊ | 24/50 [01:33<01:48, 4.19s/it]
50%|█████ | 25/50 [01:37<01:47, 4.30s/it]
52%|█████▏ | 26/50 [01:42<01:44, 4.36s/it]
54%|█████▍ | 27/50 [01:47<01:43, 4.49s/it]
56%|█████▌ | 28/50 [01:51<01:40, 4.58s/it]
58%|█████▊ | 29/50 [01:56<01:37, 4.65s/it]
60%|██████ | 30/50 [02:01<01:35, 4.77s/it]
62%|██████▏ | 31/50 [02:06<01:32, 4.87s/it]
64%|██████▍ | 32/50 [02:11<01:28, 4.93s/it]
66%|██████▌ | 33/50 [02:16<01:24, 4.98s/it]
68%|██████▊ | 34/50 [02:22<01:20, 5.01s/it]
70%|███████ | 35/50 [02:26<01:14, 4.95s/it]
72%|███████▏ | 36/50 [02:31<01:08, 4.90s/it]
74%|███████▍ | 37/50 [02:36<01:04, 4.96s/it]
76%|███████▌ | 38/50 [02:41<00:59, 4.99s/it]
78%|███████▊ | 39/50 [02:46<00:53, 4.86s/it]
80%|████████ | 40/50 [02:51<00:48, 4.86s/it]
82%|████████▏ | 41/50 [02:56<00:43, 4.85s/it]
84%|████████▍ | 42/50 [03:00<00:38, 4.85s/it]
86%|████████▌ | 43/50 [03:05<00:34, 4.86s/it]
88%|████████▊ | 44/50 [03:10<00:29, 4.86s/it]
90%|█████████ | 45/50 [03:15<00:24, 4.85s/it]
92%|█████████▏| 46/50 [03:20<00:19, 4.86s/it]
94%|█████████▍| 47/50 [03:24<00:14, 4.78s/it]
96%|█████████▌| 48/50 [03:29<00:09, 4.80s/it]
98%|█████████▊| 49/50 [03:34<00:04, 4.74s/it]
100%|██████████| 50/50 [03:38<00:00, 4.70s/it]
100%|██████████| 50/50 [03:38<00:00, 4.38s/it]
Calculate the GVD by fitting a curve for wavelength vs neff. Then take second derivative of the curve
y_spl = UnivariateSpline(wavelength_list, neff_list, s=0, k=3)
x_range = np.linspace(wavelength_list[0], wavelength_list[-1], 1000)
y_spl_2d = y_spl.derivative(n=2)
# Plot the result
fig, axs = plt.subplots(3, 1, figsize=(9, 20))
axs[0].set_xlabel("Wavelength / nm")
axs[0].set_ylabel("neff")
axs[0].set_title(" neff vs wavelength fit")
axs[0].semilogy(x_range, y_spl(x_range))
axs[0].semilogy(wavelength_list, neff_list, "ro", label="data")
axs[0].legend()
axs[0].set_xlim(500, 2200)
axs[1].set_xlabel("Wavelength / nm")
axs[1].set_ylabel("neff''")
axs[1].set_title("wavelength vs second derivative of neff")
axs[1].plot(x_range, y_spl_2d(x_range))
axs[1].set_xlim(500, 2200)
# ----Calculate and plot GVD
GVD = -wavelength_list / (2.99792e-7) * y_spl_2d(wavelength_list)
axs[2].scatter(wavelength_list, GVD, label="calculated", c="red")
axs[2].set_ylabel("GVD")
axs[2].set_xlabel("Wavelength / nm")
axs[2].set_ylim(-1000, 200)
axs[2].set_xlim(500, 2200)
axs[2].set_title("GVD parameter")
axs[2].legend()
plt.tight_layout()
plt.show()
