FDTD 101: Prelude to Integrated Photonics Simulation: Mode Injection
Lecture 4: Prelude to Integrated Photonics Simulation: Mode Injection
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.
For Python setup used in this lecture, please see related FDTD Python Tutorial below.
Tutorial 4: Mode source¶
In this tutorial, we illustrate the usage and the importance of mode sources in integrated photonics simulations.
# standard python imports
import numpy as np
import matplotlib.pyplot as plt
import tidy3d as td
from tidy3d.plugins.mode import ModeSolver
from tidy3d import web
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 (Detals can be found here). The procedue 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.566749 2.0084913]]
- 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]})
Visulization¶
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.
sim_data = web.run(sim, task_name='lecture04_mode_source', path=f'data/data_mode.hdf5')
sim_point_data = web.run(sim_point, task_name='lecture04_point_source', path=f'data/data_point.hdf5')
Post Run Analysis¶
Once the simulation has completed, we can download the results and load them into the simulation object.
Now, Let's visulize 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 sigle mode features; while the one by the point dipole (right figure) leaks to the free space. To quantatatively 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
flux_point_list[i] = sim_point_data['flux'+str(i)].flux
# 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()