MMI 1 x 4 power splitter#

Note: the cost of running the entire notebook is larger than 1 FlexUnit.

Optical power splitters are essential components in integrated photonics. Power splitters based on multimode interference (MMI) device are easy to fabricate and can achieve low excess loss as well as large bandwidth. Although the design of a MMI power splitter is based on the self-imaging principle, fine-tuning the geometric parameters with accurate and fast numerical simulations is crucial to achieving optimal device performance.

This example aims to demonstrate the design and optimization of 1 to 4 MMI device at telecom wavelength for power splitting applications. The initial design is adapted from D. Malka, Y. Danan, Y. Ramon, Z. Zalevsky, A Photonic 1 x 4 Power Splitter Based on Multimode Interference in Silicon–Gallium-Nitride Slot Waveguide Structures. Materials. 9, 516 (2016).

The device uses a Si-GaN-Si slot waveguide strcuture as schematically shown below.

a76704c755704cdca3641ccb271917b0

Simulation Setup#

[1]:
import numpy as np
import matplotlib.pyplot as plt
import tidy3d as td
import tidy3d.web as web
from tidy3d.plugins import ModeSolver

[21:05:31] WARNING  This version of Tidy3D was pip installed from the 'tidy3d-beta' repository on   __init__.py:103
                    PyPI. Future releases will be uploaded to the 'tidy3d' repository. From now on,                
                    please use 'pip install tidy3d' instead.                                                       
           INFO     Using client version: 1.9.0rc1                                                  __init__.py:121

Define materials. There are three materials involved in this model. The SiO2 cladding and the Si waveguide with GaN slot. All materials are modeled as lossless and dispersionless in this particular case.

[2]:
si = td.Medium(permittivity=3.48**2)
gan = td.Medium(permittivity=2.305**2)
sio2 = td.Medium(permittivity=1.444**2)

Define initial design parameters and wrap simulation setup in a function. The arguments of the function are the paremeters we want to optimize later. In this example, we aim to optimize the length and width of the MMI section.

[3]:
W_MMI = 5  # width of the MMI section
L_MMI = 11.2  # length of the MMI section
g1 = 0.9  # gap between the output waveguides
W1 = 0.4  # width of the waveguide
W2 = 0.8  # width of the tapper
L1 = 2  # length of the input tapper
L2 = 5  # length of the output tapper
H_Si = 0.3  # thickness of the Si layer
H_GaN = 0.1  # thickness of the GaN layer
g3 = (W2 - W1) / 2  # auxilary parameter defined for easier geometry building
g2 = g1 - 2 * g3  # gap between the output tapers
lda0 = 1.55  # central wavelength
freq0 = td.C_0 / lda0  # central frequency
ldas = np.linspace(1.45, 1.65, 101)  # wavelength range
freqs = td.C_0 / ldas  # frequency range

# buffer spacings in the x and y directions.
buffer_x = 1
buffer_y = 1.5

# define a function that takes the geometric parameters as input arguments and return a Simulation object
def make_sim(L_MMI, W_MMI):
    # the whole device is defined as a PolySlab with vertices given by the following
    vertices = np.array(
        [
            (-W1 / 2, -100),
            (-W1 / 2, 0),
            (-W2 / 2, L1),
            (-W_MMI / 2, L1),
            (-W_MMI / 2, L1 + L_MMI),
            (-g2 / 2 - W2 - g2 - 2 * g3 - W1, L1 + L_MMI),
            (-g2 / 2 - W2 - g2 - g3 - W1, L1 + L_MMI + L2),
            (-g2 / 2 - W2 - g2 - g3 - W1, 100),
            (-g2 / 2 - W2 - g2 - g3, 100),
            (-g2 / 2 - W2 - g2 - g3, L1 + L_MMI + L2),
            (-g2 / 2 - W2 - g2, L1 + L_MMI),
            (-g2 / 2 - W2, L1 + L_MMI),
            (-g1 / 2 - W1, L1 + L_MMI + L2),
            (-g1 / 2 - W1, 100),
            (-g1 / 2, 100),
            (-g1 / 2, L1 + L_MMI + L2),
            (-g2 / 2, L1 + L_MMI),
            (g2 / 2, L1 + L_MMI),
            (g1 / 2, L1 + L_MMI + L2),
            (g1 / 2, 100),
            (g1 / 2 + W1, 100),
            (g1 / 2 + W1, L1 + L_MMI + L2),
            (g2 / 2 + W2, L1 + L_MMI),
            (g2 / 2 + W2 + g2, L1 + L_MMI),
            (g2 / 2 + W2 + g2 + g3, L1 + L_MMI + L2),
            (g1 / 2 + W1 + g1, 100),
            (g1 / 2 + W1 + g1 + W1, 100),
            (g2 / 2 + W2 + g2 + g3 + W1, L1 + L_MMI + L2),
            (g2 / 2 + W2 + g2 + 2 * g3 + W1, L1 + L_MMI),
            (W_MMI / 2, L1 + L_MMI),
            (W_MMI / 2, L1),
            (W2 / 2, L1),
            (W1 / 2, 0),
            (W1 / 2, -100),
        ]
    )

    mmi_layer1 = td.Structure(
        geometry=td.PolySlab(
            vertices=vertices,
            axis=2,
            slab_bounds=(-H_Si - 0.5 * H_GaN, H_Si + 0.5 * H_GaN),
        ),
        medium=si,
    )
    mmi_layer2 = td.Structure(
        geometry=td.PolySlab(
            vertices=vertices, axis=2, slab_bounds=(-0.5 * H_GaN, 0.5 * H_GaN)
        ),
        medium=gan,
    )

    # simulation domain size
    Lx = W_MMI + 2 * buffer_x
    Ly = L1 + L_MMI + L2 + 2 * buffer_y
    Lz = 5 * (H_GaN * 2 + H_Si)
    sim_size = (Lx, Ly, Lz)

    mode_spec = td.ModeSpec(num_modes=5, target_neff=3)

    # add a mode source for excitation
    mode_source = td.ModeSource(
        center=(0, -buffer_y / 3, 0),
        size=(3 * W1, 0, 3 * (H_GaN * 2 + H_Si)),
        source_time=td.GaussianPulse(freq0=freq0, fwidth=freq0 / 10),
        direction="+",
        mode_spec=mode_spec,
        mode_index=0,
    )

    # add two flux monitors to monitor the transmission power at output waveguides
    flux_monitor1 = td.FluxMonitor(
        center=((g1 + W1) / 2, Ly - buffer_y, 0),
        size=(2 * W1, 0, 2 * W1),
        freqs=freqs,
        name="flux1",
    )

    flux_monitor2 = td.FluxMonitor(
        center=(3 * (g1 + W1) / 2, Ly - buffer_y, 0),
        size=(2 * W1, 0, 2 * W1),
        freqs=freqs,
        name="flux2",
    )

    # add two mode monitor to monitor the mode profiles at output waveguides
    mode_monitor1 = td.ModeMonitor(
        center=((g1 + W1) / 2, Ly - buffer_y, 0),
        size=(2 * W1, 0, 2 * W1),
        freqs=freqs,
        mode_spec=mode_spec,
        name="mode1",
    )

    mode_monitor2 = td.ModeMonitor(
        center=(3 * (g1 + W1) / 2, Ly - buffer_y, 0),
        size=(2 * W1, 0, 2 * W1),
        freqs=freqs,
        mode_spec=mode_spec,
        name="mode2",
    )

    # add a field monitor to monitor the field distribution
    field_monitor = td.FieldMonitor(
        center=(0, 0, 0), size=(td.inf, td.inf, 0), freqs=[freq0], name="field"
    )

    sim = td.Simulation(
        center=(0, Ly / 2 - buffer_y / 2, 0),
        size=sim_size,
        grid_spec=td.GridSpec.auto(min_steps_per_wvl=20, wavelength=td.C_0 / freq0),
        structures=[mmi_layer1, mmi_layer2],
        sources=[mode_source],
        monitors=[
            field_monitor,
            flux_monitor1,
            flux_monitor2,
            mode_monitor1,
            mode_monitor2,
        ],
        run_time=1e-11,
        boundary_spec=td.BoundarySpec.all_sides(boundary=td.PML()),
        medium=sio2,
    )
    return sim

Initial Design#

First, we simulate an initial design using the previously defined design parameters.

[4]:
f, (ax1, ax2) = plt.subplots(1, 2)
sim = make_sim(L_MMI, W_MMI)
sim.plot(z=0, ax=ax1)
sim.plot(y=-buffer_y / 3, ax=ax2)

[4]:
<AxesSubplot: title={'center': 'cross section at y=-0.50'}, xlabel='x', ylabel='z'>
../_images/notebooks_MMI_1x4_10_1.png

Before simulation, let’s inspect the waveguide modes supported in the slot waveguide to make sure we are using the correct excitation source.

[5]:
mode_spec = td.ModeSpec(num_modes=5, target_neff=3)
mode_solver = ModeSolver(
    simulation=sim,
    plane=td.Box(
        center=(0, -buffer_y / 3, 0), size=(3 * W1, 0, 3 * (H_GaN * 2 + H_Si))
    ),
    mode_spec=mode_spec,
    freqs=[freq0],
)
mode_data = mode_solver.solve()

/Users/twhughes/.pyenv/versions/3.10.9/lib/python3.10/site-packages/numpy/linalg/linalg.py:2139: RuntimeWarning: divide by zero encountered in det
  r = _umath_linalg.det(a, signature=signature)
/Users/twhughes/.pyenv/versions/3.10.9/lib/python3.10/site-packages/numpy/linalg/linalg.py:2139: RuntimeWarning: invalid value encountered in det
  r = _umath_linalg.det(a, signature=signature)

The lowest order mode shows a strong field confinement between the Si area. This is the mode we want to use as excitation.

[6]:
mode_index = 0

f, (ax1, ax2, ax3) = plt.subplots(1, 3, tight_layout=True, figsize=(10, 3))
abs(mode_data.Ex.isel(mode_index=mode_index)).plot(x="x", y="z", ax=ax1, cmap="magma")
abs(mode_data.Ey.isel(mode_index=mode_index)).plot(x="x", y="z", ax=ax2, cmap="magma")
abs(mode_data.Ez.isel(mode_index=mode_index)).plot(x="x", y="z", ax=ax3, cmap="magma")

ax1.set_title("|Ex(x, y)|")
ax1.set_aspect("equal")
ax2.set_title("|Ey(x, y)|")
ax2.set_aspect("equal")
ax3.set_title("|Ez(x, y)|")
ax3.set_aspect("equal")
plt.show()

../_images/notebooks_MMI_1x4_14_0.png

Submit simulation job to the server.

[7]:
job = web.Job(simulation=sim, task_name="mmi")
sim_data = job.run(path="data/simulation_data.hdf5")

[21:05:34] INFO     Created task 'mmi' with task_id '92ed5c27-eeda-471e-bbc9-e9c7b1727684'.           webapi.py:120
[21:05:36] INFO     status = queued                                                                   webapi.py:262
[21:05:44] INFO     Maximum FlexUnit cost: 1.956                                                      webapi.py:253
[21:06:00] INFO     status = preprocess                                                               webapi.py:274
[21:06:07] INFO     starting up solver                                                                webapi.py:278
[21:06:17] INFO     running solver                                                                    webapi.py:284
[21:08:05] INFO     early shutoff detected, exiting.                                                  webapi.py:295
           INFO     status = postprocess                                                              webapi.py:301
[21:08:20] INFO     status = success                                                                  webapi.py:307
[21:08:21] INFO     Billed FlexUnit cost: 0.476                                                       webapi.py:311
           INFO     downloading file "output/monitor_data.hdf5" to "data/simulation_data.hdf5"        webapi.py:593
[21:08:25] INFO     loading SimulationData from data/simulation_data.hdf5                             webapi.py:415

Visualize the field distribution.

[8]:
f, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 8))
sim_data.plot_field("field", "int", ax=ax1, f=freq0)
sim_data.plot_field("field", "Ez", ax=ax2, f=freq0)

[8]:
<AxesSubplot: title={'center': 'cross section at z=0.00'}, xlabel='x', ylabel='y'>
../_images/notebooks_MMI_1x4_18_1.png

Plot transmission on each output waveguide as well as the total excess loss. At the central wavelength of 1550 nm, the transimission power at the inner waveguide and outer waveguide differs by about 1%. The excess loss is about 0.4 dB.

[9]:
f, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True, figsize=(10, 5))
T1 = sim_data["flux1"].flux
T2 = sim_data["flux2"].flux

plt.sca(ax1)
plt.plot(ldas, T1, ldas, T2)
plt.vlines(x=1.55, ymin=0.1, ymax=0.3, colors="black", ls="--")
plt.xlabel("Wavelength ($\mu m$)")
plt.ylabel("Transmission to output waveguide")
plt.legend(("Inner waveguide", "Outer waveguide"))

plt.sca(ax2)
excess_loss = -10 * np.log10(2 * (T1 + T2))
plt.plot(ldas, excess_loss)
plt.vlines(x=1.55, ymin=0, ymax=1, colors="black", ls="--")
plt.hlines(y=excess_loss[50], xmin=1.45, xmax=1.65, colors="black", ls="--")
plt.xlabel("Wavelength ($\mu m$)")
plt.ylabel("Excess loss (dB)")

[9]:
Text(0, 0.5, 'Excess loss (dB)')
../_images/notebooks_MMI_1x4_20_1.png

We can use the mode monitor to inspect the composition of each mode at the output. For the inner waveguide, we can see that the fundamental mode is dominant and a small amount of Mode 4 is excited.

[10]:
mode_amp = sim_data["mode1"].amps.sel(direction="+")
mode_power = np.abs(mode_amp) ** 2 / T1
plt.plot(ldas, mode_power)
plt.xlabel("Wavelength (nm)")
plt.ylabel("Power share (%)")
plt.legend(["Mode 0", "Mode 1", "Mode 2", "Mode 3", "Mode 4"])

[10]:
<matplotlib.legend.Legend at 0x16763b0d0>
../_images/notebooks_MMI_1x4_22_1.png

Optimization#

Further tuning of the geometric parameters is likely to improve the device’s performance further. We will perform a parameter sweep on two parameters to see if we can optimize the MMI. The goal is to achieve even power splitting among output waveguides while keeping the excess loss low.

The length of the MMI is swept from 11.1 to 11.2 \(\mu m\) in 50 nm step and the width from 4.8 to 5 \(\mu m\) in 100 nm step. This results in a total of 9 simulations. Since this is just a demonstration model, we limit the total number of simulations for the sake of time. In practice, one can perform much larger parameter sweeps to cover a larger parameter space.

[11]:
L_MMIs = np.linspace(11.1, 11.2, 3)  # MMI length varies from 11.1 to 11.2 um
W_MMIs = np.linspace(4.8, 5, 3)  # MMI width varies from 4.8 to 5 um

sims = {
    f"L_MMI={L_MMI:.2f};W_MMI={W_MMI:.2f}": make_sim(L_MMI, W_MMI)
    for L_MMI in L_MMIs
    for W_MMI in W_MMIs
}
batch = web.Batch(simulations=sims)
batch_results = batch.run(path_dir="data")

[21:08:31] INFO     Created task 'L_MMI=11.10;W_MMI=4.80' with task_id                                webapi.py:120
                    '4f07d447-4c0a-4ceb-95eb-03d92ec02b58'.                                                        
[21:08:33] INFO     Created task 'L_MMI=11.10;W_MMI=4.90' with task_id                                webapi.py:120
                    'ef22c9b9-aaf0-402d-bbb4-cb03b34fa3ec'.                                                        
[21:08:34] INFO     Created task 'L_MMI=11.10;W_MMI=5.00' with task_id                                webapi.py:120
                    '7e7cec74-207b-42f5-a043-fb0e6c21bb6c'.                                                        
[21:08:36] INFO     Created task 'L_MMI=11.15;W_MMI=4.80' with task_id                                webapi.py:120
                    '1879e527-cae3-4a4d-9ba9-381349efe157'.                                                        
[21:08:38] INFO     Created task 'L_MMI=11.15;W_MMI=4.90' with task_id                                webapi.py:120
                    'c9b4da3c-55e5-4924-bb1f-bedb89e63b4c'.                                                        
[21:08:40] INFO     Created task 'L_MMI=11.15;W_MMI=5.00' with task_id                                webapi.py:120
                    '4394aa32-5b3b-493d-808f-1d7015961768'.                                                        
[21:08:41] INFO     Created task 'L_MMI=11.20;W_MMI=4.80' with task_id                                webapi.py:120
                    '4e28b4ef-710b-4a95-9444-70692ada9dae'.                                                        
[21:08:43] INFO     Created task 'L_MMI=11.20;W_MMI=4.90' with task_id                                webapi.py:120
                    '9f170e64-8a59-489e-bb8f-5e2a15cb1e5a'.                                                        
[21:08:44] INFO     Created task 'L_MMI=11.20;W_MMI=5.00' with task_id                                webapi.py:120
                    'cce44530-421b-4ee4-ad6b-ed23f514892f'.                                                        
[21:08:51] Started working on Batch.                                                               container.py:361

Parse flux data into numpy arrays.

[12]:
T1 = np.zeros((len(L_MMIs), len(W_MMIs)))
T2 = np.zeros((len(L_MMIs), len(W_MMIs)))
for i, L_MMI in enumerate(L_MMIs):
    for j, W_MMI in enumerate(W_MMIs):
        sim_data = batch_results[f"L_MMI={L_MMI:.2f};W_MMI={W_MMI:.2f}"]
        t1 = sim_data["flux1"].flux
        T1[i, j] = t1[50]  # the index 50 corresponds to the wavelength of 1550 nm
        t2 = sim_data["flux2"].flux
        T2[i, j] = t2[50]

[21:29:50] INFO     downloading file "output/monitor_data.hdf5" to                                    webapi.py:593
                    "data/4f07d447-4c0a-4ceb-95eb-03d92ec02b58.hdf5"                                               
[21:29:54] INFO     loading SimulationData from data/4f07d447-4c0a-4ceb-95eb-03d92ec02b58.hdf5        webapi.py:415
           INFO     downloading file "output/monitor_data.hdf5" to                                    webapi.py:593
                    "data/ef22c9b9-aaf0-402d-bbb4-cb03b34fa3ec.hdf5"                                               
[21:29:58] INFO     loading SimulationData from data/ef22c9b9-aaf0-402d-bbb4-cb03b34fa3ec.hdf5        webapi.py:415
           INFO     downloading file "output/monitor_data.hdf5" to                                    webapi.py:593
                    "data/7e7cec74-207b-42f5-a043-fb0e6c21bb6c.hdf5"                                               
[21:30:02] INFO     loading SimulationData from data/7e7cec74-207b-42f5-a043-fb0e6c21bb6c.hdf5        webapi.py:415
           INFO     downloading file "output/monitor_data.hdf5" to                                    webapi.py:593
                    "data/1879e527-cae3-4a4d-9ba9-381349efe157.hdf5"                                               
[21:30:09] INFO     loading SimulationData from data/1879e527-cae3-4a4d-9ba9-381349efe157.hdf5        webapi.py:415
           INFO     downloading file "output/monitor_data.hdf5" to                                    webapi.py:593
                    "data/c9b4da3c-55e5-4924-bb1f-bedb89e63b4c.hdf5"                                               
[21:30:14] INFO     loading SimulationData from data/c9b4da3c-55e5-4924-bb1f-bedb89e63b4c.hdf5        webapi.py:415
           INFO     downloading file "output/monitor_data.hdf5" to                                    webapi.py:593
                    "data/4394aa32-5b3b-493d-808f-1d7015961768.hdf5"                                               
[21:30:17] INFO     loading SimulationData from data/4394aa32-5b3b-493d-808f-1d7015961768.hdf5        webapi.py:415
           INFO     downloading file "output/monitor_data.hdf5" to                                    webapi.py:593
                    "data/4e28b4ef-710b-4a95-9444-70692ada9dae.hdf5"                                               
[21:30:21] INFO     loading SimulationData from data/4e28b4ef-710b-4a95-9444-70692ada9dae.hdf5        webapi.py:415
           INFO     downloading file "output/monitor_data.hdf5" to                                    webapi.py:593
                    "data/9f170e64-8a59-489e-bb8f-5e2a15cb1e5a.hdf5"                                               
[21:30:24] INFO     loading SimulationData from data/9f170e64-8a59-489e-bb8f-5e2a15cb1e5a.hdf5        webapi.py:415
           INFO     downloading file "output/monitor_data.hdf5" to                                    webapi.py:593
                    "data/cce44530-421b-4ee4-ad6b-ed23f514892f.hdf5"                                               
[21:30:28] INFO     loading SimulationData from data/cce44530-421b-4ee4-ad6b-ed23f514892f.hdf5        webapi.py:415

Visualize power difference between outputs as well as the excess loss. The optimal design would have both values as close to 0 as possible. From the plots, we can see that in this parameter range, the smallest power different does not coincide with the lowest excess loss. If we prioritize small power difference, for example, L_MMI = 11.15 \(\mu m\) and W_MMI = 4.9 \(\mu m\) would be a good design choice.

[13]:
f, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True, figsize=(10, 5))
plt.sca(ax1)
plt.pcolor(W_MMIs, L_MMIs, np.abs(T1 - T2), vmin=0, vmax=0.02, cmap="binary")
plt.colorbar()
plt.title("Power difference between inner and outer waveguides")
plt.xlabel("W_MMI ($\mu m$)")
plt.ylabel("L_MMI ($\mu m$)")
plt.sca(ax2)
plt.pcolor(
    W_MMIs, L_MMIs, -10 * np.log10(2 * (T1 + T2)), vmin=0, vmax=0.25, cmap="binary"
)
plt.colorbar()
plt.title("Excess loss (dB)")
plt.xlabel("W_MMI ($\mu m$)")
plt.ylabel("L_MMI ($\mu m$)")

[13]:
Text(0, 0.5, 'L_MMI ($\\mu m$)')
../_images/notebooks_MMI_1x4_29_1.png

Plot field intensity for the optimal design.

[14]:
sim_data = batch_results["L_MMI=11.15;W_MMI=4.90"]
f, ax = plt.subplots(1, 1, figsize=(10, 10))
sim_data.plot_field("field", "int", ax=ax, f=freq0)

[21:30:29] INFO     loading SimulationData from data/c9b4da3c-55e5-4924-bb1f-bedb89e63b4c.hdf5        webapi.py:415
[14]:
<AxesSubplot: title={'center': 'cross section at z=0.00'}, xlabel='x', ylabel='y'>
../_images/notebooks_MMI_1x4_31_2.png

Plot transmission on each output waveguide as well as the total excess loss. For this design, at the central wavelength of 1.55 \(\mu m\), the power on each power is roughly equal. The total excess loss is below 0.2 dB.

[15]:
f, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True, figsize=(10, 5))
T1 = sim_data["flux1"].flux
T2 = sim_data["flux2"].flux

plt.sca(ax1)
plt.plot(ldas, T1, ldas, T2)
plt.vlines(x=1.55, ymin=0.1, ymax=0.3, colors="black", ls="--")
plt.xlabel("Wavelength ($\mu m$)")
plt.ylabel("Transmission to output")
plt.legend(("Inner output", "Outer outport"))

plt.sca(ax2)
excess_loss = -10 * np.log10(2 * (T1 + T2))
plt.plot(ldas, excess_loss)
plt.vlines(x=1.55, ymin=0, ymax=1, colors="black", ls="--")
plt.hlines(y=excess_loss[50], xmin=1.45, xmax=1.65, colors="black", ls="--")
plt.xlabel("Wavelength ($\mu m$)")
plt.ylabel("Excess loss (dB)")

[15]:
Text(0, 0.5, 'Excess loss (dB)')
../_images/notebooks_MMI_1x4_33_1.png

In principle, the design can be further optimized by tuning other parameters such as gap size, tapper width, etc. if even lower excess loss is required. Finally, we can see the mode decomposition at the inner output waveguide. The fundamental mode is still dominant.

[16]:
mode_amp = sim_data["mode1"].amps.sel(direction="+")
mode_power = np.abs(mode_amp) ** 2 / T1
plt.plot(ldas, mode_power)
plt.xlabel("Wavelength ($\mu m$)")
plt.ylabel("Power fraction of the modes (%)")
plt.legend(["Mode 0", "Mode 1", "Mode 2", "Mode 3", "Mode 4"])

[16]:
<matplotlib.legend.Legend at 0x16ac137f0>
../_images/notebooks_MMI_1x4_35_1.png
[ ]: