TIDY3D
LEARNING CENTER

Complete this course to earn 50 FlexCredits Credits are added to your account when all course items are checked in green.

Prelude to Integrated Photonics Simulation: Mode Injection

By Weiliang Jin, Zongfu Yu and Shanhui Fan

In this lecture, we show how to perfectly inject a specific mode into a straight waveguide. This is helpful in decreasing computational cost, and having a clean simulation without unwanted electromagnetic interference in the computational domain.

- Describe how to use the eigenmode solver to solve for guided modes.
- Describe how to setup mode sources to selectively excite the target mode along a specific propagating direction.
- As a comparison, we also illustrate the field profile when excited by a dipole source.

Share On:
Download .ipynb View Presentation Slides
Additional information: This was updated in Apr 13, 2022

Tutorial 4: mode source

In this tutorial, we illustrate the usage and the importance of mode sources in integrated photonics simulations.

Setup

First we import Tidy3D and the other packages needed. If it is not installed, please perform the installation and account authentication by following the detailed instructions here.

# standard python imports
import matplotlib.pyplot as plt
import numpy as np
import tidy3d as td
from tidy3d import web
from tidy3d.plugins.mode import ModeSolver

td.config.local_cache.enabled = False

Straight Waveguide Simulation

We will do a simulation of a straight waveguide, using a silicon ridge waveguide on a silicon oxide substrate. We begin by defining some general parameters.

# Unit length is micron.
wg_height = 0.25
wg_width = 0.5

# Permittivity of waveguide and substrate
si_eps = 3.48**2
sio_eps = 1.45**2

# Free-space wavelength (in um) and frequency (in Hz)
lambda0 = 1.55
freq0 = td.C_0 / lambda0
fwidth = freq0 / 10

# Simulation size inside the PML along propagation direction
sim_length = 20  # um

# space between waveguide and PML
spacing = 1.5  # um

# Simulation domain size and total run time
sim_size = [sim_length, wg_width + spacing * 2, wg_height + spacing * 2]
run_time = 20 / fwidth

# Grid specification
grid_spec = td.GridSpec.auto(min_steps_per_wvl=20, wavelength=lambda0)

# monitor and modal plane size
sz_plane = 2  # um

# Modal source position (x coordinate)
src_pos = -sim_size[0] / 2 + spacing

Create Simulation

Now we set up almost everything (structures, monitors, simulation) to run the example. Sources will be set up in the next section.

First, we define the substrate and the waveguide. Note that they both need to be extended into the PML layers. In tidy3d, the PML layers extend beyond the simulation domain (more details on setting up PML layers can be found here). So when defining the substrate and the waveguide, it is best to extend them well beyond the simulation domain size to make sure they truly extend into the PML layers.

# Waveguide and substrate materials
mat_wg = td.Medium(permittivity=si_eps, name="silicon")
mat_sub = td.Medium(permittivity=sio_eps, name="silica")

# Substrate
substrate = td.Structure(
    geometry=td.Box(
        center=[0, 0, -sim_size[2]],
        size=[td.inf, td.inf, 2 * sim_size[2] - wg_height],
    ),
    medium=mat_sub,
    name="substrate",
)

# Waveguide
waveguide = td.Structure(
    geometry=td.Box(
        center=[0, 0, 0],
        size=[td.inf, wg_width, wg_height],
    ),
    medium=mat_wg,
    name="waveguide",
)

Next, we define two types of monitors: * FieldMonitor to visualize the fields in xy plane at central frequency; * A set of FluxMonitor to measure the flux as a function of distance away from the source.

mnt_list = []

# xy-plane frequency-domain field monitor at central frequency
freq_mnt = td.FieldMonitor(
    center=[0, 0, 0], size=[np.inf, np.inf, 0], freqs=[freq0], name="field"
)
mnt_list.append(freq_mnt)

# A series of flux monitors
offset_from_source = 0.05  # the space between the 1st monitor and the source

pos_start = src_pos + offset_from_source  # define the positions of the monitor
pos_end = sim_size[0] / 2 - spacing
num = 100
pos_list = np.linspace(pos_start, pos_end, num)

for i in range(num):
    flux_mnt = td.FluxMonitor(
        center=[pos_list[i], 0, 0],
        size=[0, sz_plane, sz_plane],
        freqs=[freq0],
        name="flux" + str(i),
    )

    mnt_list.append(flux_mnt)

Now it is time to define the simulation object.

# Simulation
sim = td.Simulation(
    size=sim_size,
    grid_spec=grid_spec,
    structures=[substrate, waveguide],
    sources=[],
    monitors=mnt_list,
    run_time=run_time,
    boundary_spec=td.BoundarySpec.all_sides(boundary=td.PML()),
)

Note: Tidy3D is warning us that our Simulation does not contain sources. In this case, since we are using the simulation as a demonstration and are not running any simulations, we may safely ignore this error.

Setup Source

In this waveguide example, we are interested in exciting a selected mode. This can be achieved with our in-built Mode Source feature (Details can be found here). The procedure is as follows,

  • First, we solve for eigenmodes with the in-built solver. The modes are computed at the central frequency of the source, and in order of decreasing effective index n, such that the modes that are fully below light-line (if any) should appear first.
# position and size of source plane
src_plane = td.Box(center=[src_pos, 0, 0], size=[0, sz_plane, sz_plane])

# number of modes to compute
num_modes = 2

# setup and solve for modes
mode_spec = td.ModeSpec(num_modes=num_modes)
ms = ModeSolver(simulation=sim, plane=src_plane, mode_spec=mode_spec, freqs=[freq0])
modes = ms.solve()

print("Effective index of computed modes: ", np.array(modes.n_eff))
Effective index of computed modes:  [[2.49630979 1.849733  ]]
  • Next, we visualize the modes using the in-built plotting functions.
fig, axs = plt.subplots(num_modes, 2, figsize=(10, 8), tight_layout=True)
for mode_ind in range(num_modes):
    ms.plot_field("Ey", "real", f=freq0, mode_index=mode_ind, ax=axs[mode_ind, 0])
    ms.plot_field("Ez", "real", f=freq0, mode_index=mode_ind, ax=axs[mode_ind, 1])
plt.show()

  • Finally, we select the mode to excite. In this example, we choose the fundamental TE mode, or Mode 0.
source_time = td.GaussianPulse(freq0=freq0, fwidth=fwidth)
mode_source = ms.to_source(mode_index=0, direction="+", source_time=source_time)
sim = sim.copy(update={"sources": [mode_source]})

As a comparison, here we setup another simulation whose source is simply a dipole source of the same polarization as the TE mode. We place the dipole source off the symmetry plane, since in general the waveguide might not have a symmetry plane.

point_source = td.PointDipole(
    center=(src_pos, wg_width / 4, wg_height / 4),
    source_time=source_time,
    polarization="Ey",
    name="point source",
)
sim_point = sim.copy(update={"sources": [point_source]})

Visualization

Let’s visualize the simulation with the modal source or the point source.

fig, ax = plt.subplots(1, 2, tight_layout=True, figsize=(8, 4))
sim.plot(x=src_pos, ax=ax[0])
sim_point.plot(x=src_pos, ax=ax[1])
plt.show()

Running Simulation

We will submit the simulation to run as a new project.

batch = {"lecture04_mode_source": sim, "lecture04_point_source": sim_point}

batch_data = web.run_async(simulations=batch, path_dir="data")

sim_data = batch_data["lecture04_mode_source"]
sim_point_data = batch_data["lecture04_point_source"]
Uploading data for 2 tasks ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:33

00:28:20 UTC Started working on Batch containing 2 tasks.                       
00:28:24 UTC Maximum FlexCredit cost: 0.141 for the whole batch.                
             Use 'Batch.real_cost()' to get the billed FlexCredit cost after    
             completion.                                                        
00:29:31 UTC Batch complete.                                                    
lecture04_mode_sour... → success ━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:01:06
lecture04_point_sou... → success ━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:35

Post Run Analysis

Once the simulation has completed, we can download the results and load them into the simulation object.

Now, Let’s visualize the field xy plane at the central frequency

fig, ax = plt.subplots(1, 2, figsize=(12, 3), tight_layout=True)

sim_data.plot_field("field", "Ey", z=0, f=freq0, val="real", ax=ax[0])
sim_point_data.plot_field("field", "Ey", z=0, f=freq0, val="real", ax=ax[1])
ax[0].set_xlim([-10, 0])
ax[1].set_xlim([-10, 0])
plt.show()

The field excited by the modal source (left figure) shows single mode features; while the one by the point dipole (right figure) leaks to the free space. To quantitatively illustrate that, we plot the flux as a function of distance away from the source:

flux_list = np.zeros_like(pos_list)
flux_point_list = np.zeros_like(pos_list)
for i in range(num):
    flux_list[i] = sim_data["flux" + str(i)].flux.item()
    flux_point_list[i] = sim_point_data["flux" + str(i)].flux.item()

# normalize the dipole one
flux_point_list /= flux_point_list[0]

fig, ax = plt.subplots(1, figsize=(5, 4), tight_layout=True)
ax.plot(pos_list - src_pos, flux_list, "-r", label="Modal source")
ax.plot(pos_list - src_pos, flux_point_list, "--k", label="Point source")
ax.set_xlabel("Distance to the source")
ax.set_ylabel("Flux")
ax.legend()
plt.show()