Skip to content

Calculate GVD of waveguide

Reproduce figure 2e from {cite}Klenner2016

# !uv add femwell  # uncomment to install
#! uvx inc run gmsh
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()

png

png

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()

png

Bibliography

:style: unsrt
:filter: docname in docnames