Tidy3D mode solver#

Tidy3d comes with an open source FDFD mode solver

Waveguides#

Guided Electromagnetic modes are the ones that have an effective index larger than the cladding of the waveguide

Here is a waveguide of Silicon (n=3.4) surrounded by SiO2 (n=1.44) cladding

For a 220 nm height x 450 nm width the effective index is 2.466

try:
  import google.colab
  is_running_on_colab = True
  !pip install gdsfactory gplugins[tidy3d] > /dev/null
  !apt install python3-gmsh gmsh > /dev/null
  
except ImportError:
  is_running_on_colab = False
import numpy as np
import gplugins.tidy3d as gt
import matplotlib.pyplot as plt
import gdsfactory as gf
from gdsfactory.generic_tech import get_generic_pdk

gf.config.rich_output()
PDK = get_generic_pdk()
PDK.activate()

nm = 1e-3
strip = gt.modes.Waveguide(
    wavelength=1.55,
    core_width=0.5,
    core_thickness=0.22,
    slab_thickness=0.0,
    core_material="si",
    clad_material="sio2",
)
strip.plot_index()

<matplotlib.collections.QuadMesh object at 0x7f9eaba59790>

../_images/3624380080a6dbb85589b918611924e3e791b83419439239bcfcd5ecada2c341.png
strip.plot_grid()

../_images/e5decd457058490752b57a1c738cadb411df1f2a5a6542f0a0785489a126ca5f.png
strip.plot_field(field_name="Ex", mode_index=0)  # TE
2024-03-09 00:45:00.582 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_952c1242c33b6ca3.npz.

<matplotlib.collections.QuadMesh object at 0x7f9ea998cd90>

../_images/375ac014b186ee082833f4e5e25337ca85f1b45693cf02219d37f9139338d300.png
strip.plot_field(field_name="Ex", mode_index=0, value="dB")  # TE

<matplotlib.collections.QuadMesh object at 0x7f9ea95eb910>

../_images/eaad0bc58544734fa60383570aac8a367c0a7d70cca812ea7fa7b0cb0f763864.png
strip.plot_field(field_name="Ey", mode_index=1)  # TM

<matplotlib.collections.QuadMesh object at 0x7f9ea9675110>

../_images/dc68c27d05f73dcef539e4c89202d24f20e6a6b8a7715b62c46f0b8fbf5b37dd.png
strip.n_eff

array([2.51134734+4.42777628e-05j, 1.86463643+2.09422901e-04j])
rib = gt.modes.Waveguide(
    wavelength=1.55,
    core_width=0.5,
    core_thickness=0.22,
    slab_thickness=0.15,
    core_material="si",
    clad_material="sio2",
)
rib.plot_index()
rib.n_eff
2024-03-09 00:45:01.211 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_726ead3c1d0a1bf3.npz.

array([2.67427418+3.10179974e-05j, 2.50854926+4.47861069e-05j])

../_images/4b5217de28af6d89e8f2eb13c96559639d72767c69631f7145778c9b5e325afa.png
rib.plot_field(field_name="Ex", mode_index=0)  # TE

<matplotlib.collections.QuadMesh object at 0x7f9ea943c110>

../_images/0e1b4cdf0b5d2e38ebc3dfd78f5f874310d058131610f4d632684f1bfd02b8e2.png
nitride = gt.modes.Waveguide(
    wavelength=1.55,
    core_width=1.0,
    core_thickness=400 * nm,
    slab_thickness=0.0,
    core_material="sin",
    clad_material="sio2",
)
nitride.plot_index()
nitride.n_eff
2024-03-09 00:45:01.613 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_93a010d591ee1827.npz.

array([1.64461788+8.05914278e-05j, 1.57796343+1.42713228e-04j])

../_images/bae009dc2d4f5bfd9db0ba80535df6f411797ae271f1d30df6afefbab404c7ba.png
nitride.plot_field(field_name="Ex", mode_index=0)  # TE

<matplotlib.collections.QuadMesh object at 0x7f9ea9659390>

../_images/77f2efc1338899f9c31f0a34bf81de5c5ea710a216c606bfd213d6fae2fd0c6a.png

Sweep width#

You can sweep the waveguide width and compute the modes.

By increasing the waveguide width, the waveguide supports many more TE and TM modes. Where TE modes have a dominant Ex field and TM modes have larger Ey fields.

Notice that waveguides wider than 0.450 um support more than one TE mode. Therefore the maximum width for single mode operation is 0.450 um.

strip = gt.modes.Waveguide(
    wavelength=1.55,
    core_width=1.0,
    slab_thickness=0.0,
    core_material="si",
    clad_material="sio2",
    core_thickness=220 * nm,
    num_modes=4,
)
w = np.linspace(400 * nm, 1000 * nm, 7)
n_eff = gt.modes.sweep_n_eff(strip, core_width=w)
fraction_te = gt.modes.sweep_fraction_te(strip, core_width=w)

for i in range(4):
    plt.plot(w, n_eff.sel(mode_index=i).real, c="k")
    plt.scatter(w, n_eff.sel(mode_index=i).real, c=fraction_te.sel(mode_index=i), vmin=0, vmax=1)
plt.axhline(y=1.44, color="k", ls="--")
plt.colorbar().set_label("TE fraction")
plt.xlabel("Width of waveguide µm")
plt.ylabel("Effective refractive index")
plt.title("Effective index sweep")
2024-03-09 00:45:01.984 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_d72800855e87d64d.npz.
2024-03-09 00:45:01.986 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_f6d805f8eed6e5e6.npz.
2024-03-09 00:45:01.987 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_5c1f9fd9c1dc2337.npz.
2024-03-09 00:45:01.989 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_b52e704c98787794.npz.
2024-03-09 00:45:01.990 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_64a02432ce60ee16.npz.
2024-03-09 00:45:01.992 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_e0d05a23783270d1.npz.
2024-03-09 00:45:01.993 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_ed43cf17cc242135.npz.
2024-03-09 00:45:02.001 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_d72800855e87d64d.npz.
2024-03-09 00:45:02.004 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_f6d805f8eed6e5e6.npz.
2024-03-09 00:45:02.005 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_5c1f9fd9c1dc2337.npz.
2024-03-09 00:45:02.006 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_b52e704c98787794.npz.
2024-03-09 00:45:02.008 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_64a02432ce60ee16.npz.
2024-03-09 00:45:02.010 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_e0d05a23783270d1.npz.
2024-03-09 00:45:02.144 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_ed43cf17cc242135.npz.

Text(0.5, 1.0, 'Effective index sweep')

../_images/07373d63496f355fa4cadac67077f6921458d78929804727f0b4e9ed870da64e.png

Exercises

  • What is the maximum width to support a single TE mode at 1310 nm?

  • For a Silicon Nitride (n=2) 400nm thick waveguide surrounded by SiO2 (n=1.44), what is the maximum width to support a single TE mode at 1550 nm?

  • For two 500x220nm Silicon waveguides surrounded by SiO2, what is the coupling length (100% coupling) for 200 nm gap?

Group index#

You can also compute the group index for a waveguide.

nm = 1e-3

strip = gt.modes.Waveguide(
    wavelength=1.55,
    core_width=500 * nm,
    slab_thickness=0.0,
    core_material="si",
    clad_material="sio2",
    core_thickness=220 * nm,
    num_modes=4,
    group_index_step=10 * nm,
)
print(strip.n_group)
2024-03-09 00:45:02.386 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_652749366badba01.npz.
[4.17803969 4.08299706 2.71577378 1.50332985]

Bend modes#

You can compute bend modes specifying the bend radius.

strip_bend = gt.modes.Waveguide(
    wavelength=1.55,
    core_width=500 * nm,
    core_thickness=220 * nm,
    slab_thickness=0.0,
    bend_radius=4,
    core_material="si",
    clad_material="sio2",
)
strip_bend.plot_field(field_name="Ex", mode_index=0)  # TE
2024-03-09 00:45:02.393 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_b5e165b81e641338.npz.

<matplotlib.collections.QuadMesh object at 0x7f9ea93f40d0>

../_images/34d64e0a7432e0eeb1d7516ac067f40247c8e598aa5026d23cae54d406877b80.png

Bend loss#

You can also compute the losses coming from the mode mismatch from the bend into a straight waveguide. To compute the bend loss due to mode mismatch you can calculate the mode overlap of the straight mode and the bent mode. Because there are two mode mismatch interfaces the total loss due to mode mismatch will be squared (from bend to straight and from straight to bend).

from paper

radii = np.arange(4, 7)
bend = gt.modes.Waveguide(
    wavelength=1.55,
    core_width=500 * nm,
    core_thickness=220 * nm,
    core_material="si",
    clad_material="sio2",
    num_modes=1,
    bend_radius=radii.min(),
)
mismatch = gt.modes.sweep_bend_mismatch(bend, radii)

plt.plot(radii, 10 * np.log10(mismatch))
plt.title("Strip waveguide bend")
plt.xlabel("Radius (μm)")
plt.ylabel("Mismatch (dB)")

Text(0, 0.5, 'Mismatch (dB)')

../_images/6137569c3be03394971b9a1862b5b68d9463b601ea4661774a1e61743a079384.png
dB_cm = 2  # dB/cm
length = 2 * np.pi * radii * 1e-6
propagation_loss = dB_cm * length * 1e2
propagation_loss

plt.title("Bend90 loss for TE polarization")
plt.plot(radii, -10 * np.log10(mismatch), ".", label="mode loss")
plt.plot(radii, propagation_loss, ".", label="propagation loss")
plt.xlabel("bend radius (um)")
plt.ylabel("Loss (dB)")
plt.legend()

<matplotlib.legend.Legend object at 0x7f9ea95441d0>

../_images/262199a7bf714a32139ca6c9b40181f03dd437d6999e4d7ea8e7ce6d653afb6b.png
rib = gt.modes.Waveguide(
    wavelength=1.55,
    core_width=1000 * nm,
    core_thickness=220 * nm,
    slab_thickness=110 * nm,
    bend_radius=15,
    core_material="si",
    clad_material="sio2",
)
rib.plot_field(field_name="Ex", mode_index=0)  # TE
2024-03-09 00:45:06.589 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_e1b8d2a571c11c59.npz.

<matplotlib.collections.QuadMesh object at 0x7f9ea3f062d0>

../_images/1c9dd7ff455dc64e57357d86643f7640d0f31db8444695c00f788eee2eb2bcdc.png
nitride_bend = gt.modes.Waveguide(
    wavelength=1.55,
    core_width=1000 * nm,
    core_thickness=400 * nm,
    slab_thickness=0.0,
    bend_radius=30,
    core_material="sin",
    clad_material="sio2",
)
nitride_bend.plot_field(field_name="Ex", mode_index=0, value="abs")  # TE
2024-03-09 00:45:06.759 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/Waveguide_64a4d7e824828167.npz.

<matplotlib.collections.QuadMesh object at 0x7f9ea3fd7090>

../_images/b7fde10c02f8306943b5d38f1bdd47c2a24de3068327f32170325e02c57344df.png
radii = np.array([30, 35, 40])
bend = gt.modes.Waveguide(
    wavelength=1.55,
    core_width=1000 * nm,
    core_thickness=400 * nm,
    core_material="sin",
    clad_material="sio2",
    num_modes=1,
    bend_radius=radii.min(),
)
mismatch = gt.modes.sweep_bend_mismatch(bend, radii)
dB_cm = 2  # dB/cm
length = 2 * np.pi * radii * 1e-6
propagation_loss = dB_cm * length * 1e2
propagation_loss

plt.title("Bend90 loss for TE polarization")
plt.plot(radii, -10 * np.log10(mismatch), ".", label="mode loss")
plt.plot(radii, propagation_loss, ".", label="propagation loss")
plt.xlabel("bend radius (um)")
plt.ylabel("Loss (dB)")
plt.legend()

<matplotlib.legend.Legend object at 0x7f9ea3e6f590>

../_images/0267636c1436bc1f07a3597192257928fd765effe12a71f758685c9ce350fe8f.png

Exercises

  • For a 500nm wide 220nm thick Silicon waveguide surrounded by SiO2, what is the minimum bend radius to have less than 0.04dB loss for TE polarization at 1550nm?

  • For a 500nm wide 220nm thick Silicon waveguide surrounded by SiO2, what is the minimum bend radius to have 99% power transmission for TM polarization at 1550nm?

Waveguide coupler#

You can also compute the modes of a waveguide coupler.

       ore_width[0]  core_width[1]
        <------->     <------->
         _______       _______   _
        |       |     |       | |
        |       |     |       |
        |       |_____|       | | core_thickness
        |slab_thickness       |
        |_____________________| |_
                <----->
                  gap


c = gt.modes.WaveguideCoupler(
    wavelength=1.55,
    core_width=(500 * nm, 500 * nm),
    gap=200 * nm,
    core_thickness=220 * nm,
    slab_thickness=100 * nm,
    core_material="si",
    clad_material="sio2",
)
c.plot_index()

<matplotlib.collections.QuadMesh object at 0x7f9ea363d5d0>

../_images/bb5cee3002972f61927ec8bd8a8de0012692c2c0b6f8471163b8dfb956d6e094.png
c.plot_field(field_name="Ex", mode_index=0)  # TE
2024-03-09 00:45:15.220 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/WaveguideCoupler_79c6a39de0ac47bc.npz.

<matplotlib.collections.QuadMesh object at 0x7f9ea38d69d0>

../_images/e7f629521cbe6ae11ca54d1cfee3279b92f12f38ebe7a8dd01d1342515e3ecbf.png
c.plot_field(field_name="Ex", mode_index=1)  # TE

<matplotlib.collections.QuadMesh object at 0x7f9ea38f7e50>

../_images/e12040de9a2616bcaff2a3c8d85411e6a25c09184851903616c5b7daed09f41c.png
coupler = gt.modes.WaveguideCoupler(
    wavelength=1.55,
    core_width=(450 * nm, 450 * nm),
    core_thickness=220 * nm,
    core_material="si",
    clad_material="sio2",
    num_modes=4,
    gap=0.1,
)

print("\nCoupler:", coupler)
print("Effective indices:", coupler.n_eff)
print("Mode areas:", coupler.mode_area)
print("Coupling length:", coupler.coupling_length())

gaps = np.linspace(0.05, 0.15, 11)
lengths = gt.modes.sweep_coupling_length(coupler, gaps)

_, ax = plt.subplots(1, 1)
ax.plot(gaps, lengths)
ax.set(xlabel="Gap (μm)", ylabel="Coupling length (μm)")
ax.legend(["TE", "TM"])
ax.grid()
Coupler: WaveguideCoupler(wavelength=array(1.55), core_width=['0.45', '0.45'], core_thickness='0.22', core_material='si', clad_material='sio2', box_material=None, slab_thickness='0.0', clad_thickness=None, box_thickness=None, side_margin=None, sidewall_angle='0.0', sidewall_thickness='0.0', sidewall_k='0.0', surface_thickness='0.0', surface_k='0.0', bend_radius=None, num_modes='4', group_index_step='False', precision='double', grid_resolution='20', max_grid_scaling='1.2', cache_path='/home/runner/.gdsfactory/modes', overwrite='False', model_config={'extra': 'forbid'}, gap='0.1')
2024-03-09 00:45:15.599 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/WaveguideCoupler_82b844a6e8c147c7.npz.
Effective indices: [2.4637647 +6.57552457e-05j 2.39007229+5.06214923e-05j
 1.9225165 +1.99036730e-04j 1.71420814+2.37015946e-04j]
Mode areas: [0.31003254 0.33258301 0.57286555 0.59002858]
Coupling length: [10.5166863   3.72044606]
2024-03-09 00:45:15.608 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/WaveguideCoupler_1cea3050b3d0c7ec.npz.
2024-03-09 00:45:15.610 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/WaveguideCoupler_f0bef047b52f4b28.npz.
2024-03-09 00:45:15.612 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/WaveguideCoupler_56b7c6012ee18b1b.npz.
2024-03-09 00:45:15.613 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/WaveguideCoupler_6c568eedf951f059.npz.
2024-03-09 00:45:15.615 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/WaveguideCoupler_e83519c7a06e4279.npz.
2024-03-09 00:45:15.617 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/WaveguideCoupler_82e63a3d868d244f.npz.
2024-03-09 00:45:15.618 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/WaveguideCoupler_ccf1ffc94f9670f3.npz.
2024-03-09 00:45:15.620 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/WaveguideCoupler_15448997f7363457.npz.
2024-03-09 00:45:15.622 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/WaveguideCoupler_4963b30bfc0685ac.npz.
2024-03-09 00:45:15.623 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/WaveguideCoupler_28607a1061ef8e2c.npz.
2024-03-09 00:45:15.625 | INFO     | gplugins.tidy3d.modes:_data:265 - load data from /home/runner/.gdsfactory/modes/WaveguideCoupler_f9023893ce41b2d6.npz.

../_images/4f93cac89bb450f5085324cce63fb7c800ba65c57e851b1c32b9278274605f12.png