Back to home

Quickstart: 2x2 MMI with S-Bends

Simulate a 2x2 multimode interference coupler using gdsfactory for layout and Hyperwave for cloud-accelerated 3D FDTD.

What you will learn

  • 1.Convert a gdsfactory component to a simulation-ready structure
  • 2.Set up mode sources, monitors, and absorbing boundaries
  • 3.Run a cloud GPU simulation and analyze transmission
1

Create Your Account

To get started with Hyperwave, you need a free account on the SPINS Photonics platform.

1
Go to spinsphotonics.com/signup

Fill in your name, email, and a password.

2
Check your email

Click the verification link to activate your account.

3
Log in at spinsphotonics.com/login

You will land on your personal dashboard with simulation history, credits, and settings.

Sign Up Free

Interested in trying Hyperwave? and we'll find a time to walk you through it.

2

Get Your API Key

Hyperwave uses API keys to authenticate simulation requests from your scripts and notebooks.

1
Go to your Dashboard

After logging in, click Overview in the left sidebar. Your API Key card is on the main dashboard.

2
Click "Generate API Key"

Your key is created instantly and auto-copied to your clipboard.

3
Save your key securely

Store it in a .env file or password manager. You can click the eye icon to reveal it, or the copy icon to copy it again.

Your API key is shown only once when generated. For security, you will not be able to view the full key again after leaving the page. Copy it immediately and store it somewhere safe.

3

Set Up Your Environment

We recommend using a Python virtual environment to keep dependencies isolated.

terminal
python -m venv hyperwave-env && source hyperwave-env/bin/activate

Prefer zero setup? Open in Google Colab. Want to run locally without copying each cell? Download the notebook.

4

Prerequisites

Before you begin, make sure you have the following ready.

1
Python 3.9 or later

Verify with python --version. Hyperwave supports Python 3.9 through 3.12.

2
Hyperwave API key

Sign up at spinsphotonics.com/signup if you do not have one yet.

3
No GPU required locally

Your machine only handles problem setup. The FDTD computation runs on cloud GPUs.

No special hardware is required on your local machine. The actual FDTD computation happens on our cloud infrastructure.

5

Installation

Install the two main packages for this tutorial. hyperwave-community is the Hyperwave FDTD simulation SDK. gdsfactory is a widely used open-source library for designing photonic components using the GDSII layout format.

JAX is installed automatically as a dependency. You do not need to configure it separately.

In Google Colab, prefix the command with % to install packages directly in a notebook cell.

terminal
pip install hyperwave-community gdsfactory
6

Imports and Configuration

Import the core libraries. hyperwave_community (imported as hwc) is the main SDK. gdsfactory (imported as gf) provides the photonic component library. numpy and jax.numpy handle numerical operations.

hwc.set_device("auto") auto-detects GPU/CPU and installs the correct JAX variant. hwc.set_verbose(True) enables detailed logging so you can see what happens at each stage.

The PDK (Process Design Kit) is gdsfactory's library of pre-built standard photonic components. Activating the generic PDK gives you access to silicon photonics building blocks with sensible defaults.

You can also set resolution by cells per wavelength: CELLS_PER_WL = 20 then RESOLUTION_UM = WL_UM / (N_CORE * CELLS_PER_WL). More cells means higher accuracy but slower simulation.

quickstart.ipynb
import hyperwave_community as hwc
import gdsfactory as gf
import numpy as np
import jax.numpy as jnp

hwc.set_device("auto")
hwc.set_verbose(True)

PDK = gf.gpdk.get_generic_pdk()
PDK.activate()

WL_UM = 1.55                  # Wavelength
N_CORE = 3.48                 # Silicon refractive index at 1550 nm
N_CLAD = 1.45                 # SiO2 cladding
RESOLUTION_UM = 0.02          # 20 nm grid spacing
WG_HEIGHT_UM = 0.22           # Waveguide core height
TOTAL_HEIGHT_UM = 4.0         # Total stack height
PADDING = (100, 100, 0, 0)   # (left, right, top, bottom) in theta pixels
7

Step 1: Load Component

A 2x2 MMI coupler is one of the most fundamental building blocks in integrated photonics. It uses the self-imaging property of multimode waveguides to split light into two output ports.

The mmi2x2_with_sbend variant from gdsfactory connects waveguides using S-shaped bends for smoother transitions that reduce back-reflections.

We extend the ports by 2 um using gf.c.extend_ports() to ensure mode sources and monitors are in straight waveguide sections. hwc.component_to_theta() converts the GDS geometry into a 2D density array called theta.

For GDS files: theta, device_info = hwc.gds_to_theta(gds_filepath="your_device.gds", resolution=RESOLUTION_UM)

quickstart.ipynb
EXTENSION_LENGTH = 2.0        # Extend ports by 2 um

gf_device = gf.components.mmi2x2_with_sbend()
gf_extended = gf.c.extend_ports(gf_device, length=EXTENSION_LENGTH)

theta, device_info = hwc.component_to_theta(
    component=gf_extended,
    resolution=RESOLUTION_UM,
)

hwc.plot_theta(theta)
8

Step 2: Build 3D Structure

Convert the 2D layout into a full 3D electromagnetic structure. FDTD simulates Maxwell's equations on a 3D grid of permittivity values, where epsilon = n^2 for each material.

The layer stack follows standard silicon-on-insulator (SOI): bottom SiO2 cladding, thin silicon core (220 nm), and top SiO2 cladding. hwc.density() applies density filtering to smooth sharp edges, improving simulation stability.

hwc.create_structure() stacks the layers vertically. The vertical_radius parameter smooths transitions between layers. z_wg_center marks the waveguide core center for plotting.

The density radius controls how sharp waveguide edges are. Larger radius = smoother edges, which can improve simulation stability.

quickstart.ipynb
eps_core = N_CORE ** 2
eps_clad = N_CLAD ** 2

density_core = hwc.density(theta=theta, pad_width=PADDING, radius=3)
density_clad = hwc.density(theta=jnp.zeros_like(theta), pad_width=PADDING, radius=5)

wg_cells = max(1, int(np.round(WG_HEIGHT_UM / RESOLUTION_UM)))
clad_cells = int(np.round((TOTAL_HEIGHT_UM - WG_HEIGHT_UM) / 2 / RESOLUTION_UM))

structure = hwc.create_structure(
    layers=[
        hwc.Layer(density_pattern=density_clad, permittivity_values=eps_clad, layer_thickness=clad_cells),
        hwc.Layer(density_pattern=density_core, permittivity_values=(eps_clad, eps_core), layer_thickness=wg_cells),
        hwc.Layer(density_pattern=density_clad, permittivity_values=eps_clad, layer_thickness=clad_cells),
    ],
    vertical_radius=2,
)

z_wg_center = clad_cells + wg_cells // 2
hwc.plot_structure(structure, axis="z", position=z_wg_center)
hwc.plot_structure(structure, view_mode="3d")
9

Step 3: Absorbing Boundaries

FDTD simulations need absorbing boundaries to prevent outgoing waves from reflecting back into the simulation. Hyperwave uses adiabatic absorbers where conductivity gradually increases toward the grid edges.

hwc.get_optimized_absorber_params() calculates optimal absorber width and conductivity for your grid. hwc.create_absorption_mask() generates the 3D conductivity array.

The absorber width is automatically calculated to be thick enough for less than -40 dB reflection.

quickstart.ipynb
_, Lx, Ly, Lz = structure.permittivity.shape

abs_params = hwc.get_optimized_absorber_params(
    resolution_nm=RESOLUTION_UM * 1000,
    structure_dimensions=(Lx, Ly, Lz),
)
abs_widths = abs_params["absorption_widths"]
abs_coeff = abs_params["absorber_coeff"]

absorber = hwc.create_absorption_mask(
    grid_shape=(Lx, Ly, Lz),
    absorption_widths=abs_widths,
    absorption_coeff=abs_coeff,
)
structure.conductivity = jnp.zeros_like(structure.conductivity) + absorber

hwc.plot_absorption_mask(absorber)
10

Step 4: Mode Source

To simulate how a device processes light, we inject the exact guided mode of the input waveguide. This is called a mode source, the standard approach in photonic simulation.

A guided mode is a self-consistent field pattern that propagates without changing shape. We select mode_num=0 (fundamental TE mode), the most commonly used in silicon photonics.

hwc.create_mode_source() identifies the waveguide cross-section, solves for guided modes, and returns the source field pattern, offset, and mode info including the propagation constant beta.

The propagation constant beta gives the effective index: n_eff = beta * wavelength / (2 * pi). Expect n_eff around 2.4 for standard SOI.

quickstart.ipynb
wl_cells = WL_UM / RESOLUTION_UM
freq_band = (2 * jnp.pi / wl_cells, 2 * jnp.pi / wl_cells, 1)

source_field, source_offset, mode_info = hwc.create_mode_source(
    structure=structure,
    freq_band=freq_band,
    mode_num=0,
    propagation_axis="x",
    source_position=abs_widths[0],
)

hwc.plot_mode(
    mode_field=mode_info["field"],
    beta=mode_info["beta"],
    mode_num=0,
    propagation_axis="x",
)
11

Step 5: Monitors

Monitors are virtual measurement probes placed inside the simulation to record electromagnetic fields at specific locations. They capture the field passing through a cross-section so you can extract power and transmission.

Place one monitor at the input port and additional monitors at each output port. The ratio of output to input power gives you the transmission coefficient for each port.

create_port_monitors() automates placement by reading port metadata from the gdsfactory component. Input-facing ports get an "Input_" prefix and output-facing ports get an "Output_" prefix.

Monitor names starting with "Input_" and "Output_" are recognized by analyze_transmission(). Follow this convention for custom monitors.

quickstart.ipynb
monitors = hwc.create_port_monitors(
    component=gf_device,
    structure=structure,
    device_info=device_info,
    padding=PADDING,
    absorption_widths=abs_widths,
)

hwc.plot_monitor_layout(
    structure.permittivity, monitors,
    axis="z", position=z_wg_center, source_position=abs_widths[0],
)
12

Step 6: Run Simulation

Configure your API key first. The code checks for a key in Google Colab secrets, then falls back to the HYPERWAVE_API_KEY environment variable.

When you call simulate(), your structure and source data are sent to cloud GPUs (NVIDIA B200). A simulation with ~125 million cells that might take hours locally completes in under two minutes on the cloud.

simulation_steps=20000 runs enough time steps for light to propagate through the device and reach steady state. hwc.save_results() caches results locally so you can reload them without re-running.

Simulation time depends on grid size. This MMI (~125 million cells) typically completes in under 2 minutes on cloud GPU.

quickstart.ipynb
try:
    from google.colab import userdata
    hwc.configure_api(api_key=userdata.get("HYPERWAVE_API_KEY"))
except ImportError:
    import os
    hwc.configure_api(api_key=os.environ.get("HYPERWAVE_API_KEY"))

results = hwc.simulate(
    structure_recipe=structure.extract_recipe(),
    source_field=source_field,
    source_offset=source_offset,
    freq_band=freq_band,
    monitors_recipe=monitors.recipe,
    mode_info=mode_info,
    simulation_steps=20000,
    absorption_widths=abs_widths,
    absorption_coeff=abs_coeff,
)

hwc.save_results(results, "quickstart_results.npz")
13

Step 7: Analyze Results

analyze_transmission() computes how much optical power reaches each output port by calculating the Poynting vector at each monitor, integrating power flux, and normalizing by the input.

For the default 2x2 MMI coupler, expect roughly equal power splitting between outputs (T around 0.48, or -3 dB each). Total transmission should be close to 1.0.

plot_monitors() visualizes the Hz field at each monitor. The xy_mid plane shows the characteristic MMI self-imaging pattern inside the device.

Transmission values slightly above 1.0 can occur in simulations without perfect absorbing boundaries, due to small reflections from domain edges.

quickstart.ipynb
transmission = hwc.analyze_transmission(
    results, input_monitor="Input_o1", direction="x",
)

hwc.plot_monitors(results, component="Hz")

Next steps