MPB mode-solver#

MPB is a free open source software to compute:

  • electro-magnetic modes

  • band structures

supported by a waveguide with periodic boundaries.

Find modes waveguide#

Lets find the modes supported by a waveguide for a particular waveguide geometry and wavelength.

A waveguide is like a pipe to guide the light and is made of a higher refractive index core core_material surrounded by a lower refractive index cladding clad_material

          __________________________
          |
          |
          |         width
          |     <---------->
          |      ___________   _ _ _
          |     |           |       |
        sz|_____|           |_______|
          |                         | core_thickness
          |slab_thickness           |
          |_________________________|
          |
          |
          |__________________________
          <------------------------>
                        sy

Silicon is yellow and opaque at visible wavelengths (380 to 700nm). This is the reason why CMOS cameras can be made of Silicon.

At Infra-red wavelengths used for communications (1300 or 1550nm) Silicon is transparent and has a high refractive index 3.47. So making a Silicon waveguide is quite easy, where the Silicon is the guiding material, and Silicon oxide n=1.45 makes a great low index material for the cladding of the waveguide.

This video explains how Silicon Photonic waveguides guide light in Photonic integrated circuits.

Strip waveguides#

Strip waveguides are fully etch and don’t have a slab. slab_thickness = 0

          __________________________
          |
          |
          |         width
          |     <---------->
          |      ___________   _ _ _
          |     |           |       |
        sz|     |           |       |
          |     |  core_material    | core_thickness
          |     |           |       |
          |     |___________|  _ _ _|
          |
          |        clad_material
          |__________________________
          <------------------------>
                        sy
import matplotlib.pyplot as plt
import meep as mp
import numpy as np

import gplugins.modes as gm
Using MPI version 4.1, 1 processes
2024-08-16 06:16:29.042 | INFO     | gplugins.gmeep:<module>:39 - Meep '1.29.0' installed at ['/home/runner/micromamba/lib/python3.11/site-packages/meep']
modes = gm.find_modes_waveguide(
    parity=mp.NO_PARITY,
    core_width=0.4,
    core_material=3.47,
    clad_material=1.44,
    core_thickness=0.22,
    resolution=40,
    sy=3,
    sz=3,
    nmodes=4,
)
m1 = modes[1]
m2 = modes[2]
m3 = modes[3]

As you can see the refractive index goes from 1.44 SiO2 Silicon dioxide to 3.47 Silicon.

m1.plot_eps()
../_images/c472ae66378a4ee53de75c7b8618be18c47be685093f0fcd9a8885848905e9dd.png
m1.neff
2.217943250808032
m1.plot_ey()
../_images/03e22b98b80689dc24cc03c18c0ade3f43739d1c078950185b208390d01b4e9a.png
m2.plot_e_all()
../_images/55e2d3a40bac069d859c491eaf77364206532ec1b75ac1e3bd7af5a92d190868.png
m1.plot_e()
../_images/9e07ea6c18e7a504b6ec25e5fb26a2f2a11debbc05b009e9cbe516a75b5a9f05.png

As you can see the first order mode has most power in y-direction Ey. This type of mode is called TE (transverse-electric)

On the other hand the second order mode has most of the light in the Ex. This mode is called TM (transverse-magnetic)

m2.plot_e_all()
../_images/55e2d3a40bac069d859c491eaf77364206532ec1b75ac1e3bd7af5a92d190868.png
m3.plot_e()  # not guided
../_images/512a3e4a09f95b1d681a1b476b7bb34af3ab8cb9c41b89f7309c4542397dd540.png
m1.neff
2.217943250808032
m2.neff
1.6821915242947407
# the third mode does not propagate and its neff is below the cladding index
m3.neff
1.4306970394767435

Sidewall angle#

You can also specify the sidewall angle.

modes = gm.find_modes_waveguide(
    parity=mp.NO_PARITY,
    core_width=0.4,
    core_material=3.47,
    clad_material=1.44,
    core_thickness=0.22,
    resolution=40,
    sidewall_angle=10,
)
m1 = modes[1]
m2 = modes[2]
m3 = modes[3]
m1.plot_eps()
../_images/1d484af7318123de3065d6b666a98314e461720342052b1585a7e8c8de04ae99.png
modes = gm.find_modes_waveguide(
    parity=mp.NO_PARITY,
    core_width=0.4,
    core_material=3.47,
    clad_material=1.44,
    core_thickness=0.22,
    resolution=60,
    sidewall_angle=10,
    slab_thickness=90e-3,
)
m1 = modes[1]
m2 = modes[2]
m3 = modes[3]
m1.plot_eps()
../_images/e9528bfc80fe5e6a57040ccdf86d8ba6a8b60b00dc89e97d6f47600f5266e9f1.png

Rib waveguides#

Rib waveguides have a slab (not fully etched)

modes = gm.find_modes_waveguide(
    mode_number=1, nmodes=2, slab_thickness=90e-3, resolution=40
)
m1 = modes[1]
m2 = modes[2]
m1.plot_eps()
../_images/d727d73df46755fca66fe870ffd235fd48d385c9c62af43f7cb9748d8f98c84a.png
m1.plot_e_all()
../_images/c778a88c68f7b4a4c63b08f5b93eaa1c13e6eef7fc919d45c13ae009a55c42b9.png

Symmetries#

You can exploit symmetries to reduce computation time as well as finding only (TE or TM) modes

MPB assumes propagation in the X direction

  • TE: mp.ODD_Y + mp.EVEN_Z

  • TM: mp.EVEN+Y + mp.ODD_Z, all energy in z component

TM: mp.ODD_Y + mp.EVEN_Z#

You can define an even Y parity to find only the TM modes

modes = gm.find_modes_waveguide(
    mode_number=1,
    parity=mp.EVEN_Y + mp.ODD_Z,
    nmodes=2,
    core_width=1.0,
    core_material=3.47,
    clad_material=1.44,
    core_thickness=0.22,
    resolution=32,
    sy=6,
    sz=6,
)
m1 = modes[1]
m2 = modes[2]
m1.plot_e()
../_images/8426fd71d7abf35e6a05c649e6b43f4d31f8a942cd96db9aca319452f6117e04.png

ODD_Y (TE)#

modes = gm.find_modes_waveguide(
    mode_number=1,
    parity=mp.ODD_Y,
    nmodes=2,
    core_width=0.20,
    core_material=3.47,
    clad_material=1.44,
    core_thickness=0.22,
    resolution=20,
    sy=5,
    sz=5,
)
m1 = modes[1]
m2 = modes[2]
m1.plot_e()
../_images/e55cb494fb38b4ec4255b93adbb79bd909ef9210da8e1b9aecf56983effb8bb3.png

Sweep waveguide width#

Strip#

df = gm.find_neff_vs_width(filepath="data/mpb_neff_vs_width.csv")
df
1 2 3 4 width
0 1.499770 1.488664 1.328191 1.316624 0.200000
1 1.683443 1.556811 1.384970 1.358053 0.272727
2 2.031569 1.632182 1.406476 1.375044 0.345455
3 2.261402 1.692583 1.413844 1.400262 0.418182
4 2.410539 1.744925 1.418374 1.418373 0.490909
5 2.508256 1.788094 1.580987 1.422537 0.563636
6 2.577841 1.823844 1.767065 1.428408 0.636364
7 2.627689 1.958723 1.853650 1.438660 0.709091
8 2.665757 2.122754 1.880209 1.489468 0.781818
9 2.691444 2.236792 1.898304 1.540170 0.854545
10 2.711755 2.326842 1.913680 1.649241 0.927273
11 2.727891 2.397685 1.926424 1.792733 1.000000
gm.plot_neff_vs_width(df)
../_images/215103069084c2f77bfad80a2aaf3aa76cac475467229f67884de1680c12f3e7.png

Rib#

modes = gm.find_modes_waveguide(
    core_width=0.4,
    core_material=3.47,
    clad_material=1.44,
    core_thickness=220e-3,
    resolution=20,
    sz=6,
    sy=6,
    nmodes=4,
    slab_thickness=90e-3,
)
m1 = modes[1]
m2 = modes[2]
m3 = modes[3]
m1.plot_eps()
m1.neff
../_images/31891af3ae085fc0e5724ab070bf62ee53cfdddbd0816522c9cae2af06b1d972.png
2.44604980493456
m1.plot_e()
m1.neff
../_images/ca63fdc63881c59e0b87689ba31dd665a890b554b817c27a14a568568c8734d3.png
2.44604980493456
m2.plot_e()
m2.neff
../_images/caddcaea144698e4a656df6567e7bc89c37080af86c3438ca04205789cf1b28d.png
2.059679301823549
df = gm.find_neff_vs_width(
    slab_thickness=90e-3, filepath="data/mpb_neff_vs_width_rib.csv"
)
gm.plot_neff_vs_width(df)
../_images/a5a677ce771c74b01f029163f899429bbaebbef19fa6e61cce31e4ac802e0804.png

Nitride#

modes = gm.find_modes_waveguide(
    core_width=1.0,
    core_material=2.0,
    clad_material=1.44,
    core_thickness=400e-3,
    sz=6,
    sy=10,
    nmodes=4,
    resolution=10,
)
m1 = modes[1]
m2 = modes[2]
m3 = modes[3]
m1.plot_eps()
../_images/de4fbfa87cd137a58b9649efe206e89ba63de33cec57863f46d9535913641d76.png
m1.plot_ey()
../_images/a40cb7b8779ff923080311b9069ee4ecb13395178dedd1c01452b75bc91e8a0e.png
m1.plot_e_all()
../_images/4ca3e8c9b8d34bd642fa0aaa513dea5398c1f50b1618385742975357efe61ab5.png
m2.plot_ex()
../_images/4d0383c4f5820de53aa73ff5ac178297a7ceaa3c3e83663ea890fad34cce49a5.png
m3.plot_ey()
../_images/69c5e7b50e8b39f6fe04b9730205042a5001699a1223c12f423dbe08b609ca7e.png
df = gm.find_neff_vs_width(
    width1=0.5,
    width2=1.2,
    core_thickness=0.4,
    core_material=2.0,
    sy=10.0,
    resolution=15,
    filepath="data/mpb_neff_vs_width_nitride.csv",
)
gm.plot_neff_vs_width(df)
../_images/6c4f216fbe93f224e9eb9e39430b81fd59376a147fddda55968cf27f97aec799.png

Dispersion#

To get the effective index we only need to compute the mode propagation constant at a single frequency.

However, to compute the dispersion (group delay) we need to compute the effective index for at least 3 wavelengths.

The effective index neff relates to the speed of the phase evolution of the light, while the group index ng relates to the group velocity of the light.

To compute the resonances in MZI interferometers or ring resonators you need to use ng

help(gm.find_mode_dispersion)
Help on function find_mode_dispersion in module gplugins.modes.find_mode_dispersion:

find_mode_dispersion(wavelength: 'float' = 1.55, wavelength_step: 'float' = 0.01, core: 'str' = 'Si', clad: 'str' = 'SiO2', mode_number: 'int' = 1, **kwargs) -> 'Mode'
    Returns Mode with correct dispersion (ng).
    
    group index comes from a finite difference approximation at 3 wavelengths
    
    Args:
        wavelength: center wavelength (um).
        wavelength_step: in um.
        core: core material name.
        clad: clad material name.
        mode_number: mode index to compute (1: fundamental mode).
    
    Keyword Args:
        core_thickness: wg height (um).
        sx: supercell width (um).
        sy: supercell height (um).
        resolution: (pixels/um).
        wavelength: wavelength.
        num_bands: mode order.
        plot: if True plots mode.
        logscale: plots in logscale.
        plotH: plot magnetic field.
        cache: path to save the modes.
        polarization: prefix when saving the modes.
        parity: symmetries mp.ODD_Y mp.EVEN_X for TE, mp.EVEN_Y for TM.
m = gm.find_mode_dispersion()
m.ng
4.276256834772698

Convergence tests#

Before launching a set of simulations you need to make sure you have the correct simulation settings:

  • resolution: resolution

  • sx: Size of the simulation region in the x-direction (default=4.0)

  • sy: Size of the simulation region in the y-direction (default=4.0)

resolutions = np.linspace(10, 50, 5)
neffs = []

for resolution in resolutions:
    modes = gm.find_modes_waveguide(
        core_width=0.5,
        core_material=3.5,
        clad_material=1.44,
        core_thickness=0.22,
        resolution=resolution,
    )
    mode = modes[1]
    neffs.append(mode.neff)
plt.plot(resolutions, neffs, "o-")
plt.ylabel("neff")
plt.xlabel("resolution (pixels/um)")
Text(0.5, 0, 'resolution (pixels/um)')
../_images/77a41bfad147a54208591816c379b181eedf14def9b41abb34b3a6713808f4e7.png
szs = np.linspace(4, 6, 6)
neffs = []

for sz in szs:
    modes = gm.find_modes_waveguide(
        core_width=0.5,
        core_material=3.5,
        clad_material=1.44,
        core_thickness=0.22,
        resolution=20,
        sz=sz,
    )
    mode = modes[1]
    neffs.append(mode.neff)
plt.plot(szs, neffs, "o-")
plt.ylabel("neff")
plt.xlabel("simulation size in z(um)")
Text(0.5, 0, 'simulation size in z(um)')
../_images/3faae4a11269320fe52bda2d3350a00af331cdec87f60097e46a63e545ed4aa4.png
sys = np.linspace(2, 6, 6)
neffs = []

for sy in sys:
    modes = gm.find_modes_waveguide(
        core_width=0.5,
        core_material=3.5,
        clad_material=1.44,
        core_thickness=0.22,
        resolution=20,
        sy=sy,
    )
    mode = modes[1]
    neffs.append(mode.neff)
plt.plot(sys, neffs, "o-")
plt.ylabel("neff")
plt.xlabel("simulation size in y (um)")
Text(0.5, 0, 'simulation size in y (um)')
../_images/e2ca8ad366af4c72d19b018c41093d151d96e13221c7043996f7475b0277b365.png

Find modes coupler#

When two waveguides are close to each other, they support modes that travel with different index (speed). One of the modes is an even mode, while the other one is an odd mode.

Light will couple from one waveguide to another because the even and odd modes travel at different speeds and they interfere with each other. Creating a periodically back and forth coupling between both waveguides.

Depending on the length of the coupling region and the gap there will be a different percentage of the light coupled from one to another

          _____________________________________________________
          |
          |
          |         widths[0]                 widths[1]
          |     <---------->     gaps[0]    <---------->
          |      ___________ <-------------> ___________      _
          |     |           |               |           |     |
        sz|_____|           |_______________|           |_____|
          |    core_material                                  | core_thickness
          |slab_thickness        nslab                        |
          |___________________________________________________|
          |
          |<--->                                         <--->
          |ymargin               clad_material                   ymargin
          |____________________________________________________
          <--------------------------------------------------->
                                   sy
modes = gm.find_modes_coupler(
    core_widths=(0.5, 0.5),
    gaps=(0.2,),
    core_material=3.47,
    clad_material=1.44,
    core_thickness=0.22,
    resolution=20,
    sz=6,
    nmodes=4,
)
m1 = modes[1]
m2 = modes[2]
m3 = modes[3]
m1.plot_eps()
../_images/25266e3337e3a6a2fe6d8a988b73744d27a92d353a31f887324fbb8295c2419d.png
m1.plot_ey()  # even mode
../_images/3b93eef3d265bae701cc84b3e1f1bc7ecb288b96b5d7d5933a8f2d831ff273e4.png
m2.plot_ey()  # odd mode
../_images/5f503f149af7742edba7cb6e353ecfe90f8ce3e9278bb7e6b56fa71c22c042fa.png

Find coupling vs gap#

gm.find_coupling_vs_gap?
df = gm.coupler.find_coupling_vs_gap(
    gap1=0.2,
    gap2=0.4,
    steps=12,
    nmodes=4,
    wavelength=1.55,
    filepath="data/mpb_find_coupling_vs_gap_strip.csv",
)

plt.title("strip 500x200 coupling")
gm.plot_coupling_vs_gap(df)
../_images/10eae5298607bbf683fe8069d63128b180866d58d035151031374d722cab5772.png
df = gm.coupler.find_coupling_vs_gap_nitride(
    filepath="data/mpb_find_coupling_vs_gap_nitride.csv"
)

plt.title("nitride 1000x400 nitride")
gm.plot_coupling_vs_gap(df)
../_images/c390035321e16696351a542f4ff4cba4408c9571dca0b15866f82b647a623b47.png
ne = []
no = []
gaps = [0.2, 0.25, 0.3]

for gap in gaps:
    modes = gm.find_modes_coupler(
        core_widths=(0.5, 0.5),
        gaps=(gap,),
        core_material=3.47,
        clad_material=1.44,
        core_thickness=0.22,
        resolution=20,
        sz=6,
        nmodes=4,
    )
    ne.append(modes[1].neff)
    no.append(modes[2].neff)
def coupling_length(
    neff1: float,
    neff2: float,
    power_ratio: float = 1.0,
    wavelength: float = 1.55,
) -> float:
    """Returns the coupling length (um) of the directional coupler
    to achieve power_ratio.

    Args:
        neff1: even supermode of the directional coupler.
        neff2: odd supermode of the directional coupler.
        power_ratio: p2/p1, where 1 means 100% power transfer.
        wavelength: in um.

    """
    dneff = (neff1 - neff2).real
    return wavelength / (np.pi * dneff) * np.arcsin(np.sqrt(power_ratio))
lc = [
    coupling_length(neff1=neff1, neff2=neff2) for gap, neff1, neff2 in zip(gaps, ne, no)
]
plt.plot(gaps, lc, ".-")
plt.ylabel("100% coupling length (um)")
plt.xlabel("gap (um)")
Text(0.5, 0, 'gap (um)')
../_images/54de8c23d9ae041034b323b34187dbbaf6a41dafa4c3e47f76bd4bddf95ac9ad.png