# How to inject modes in bent and angled waveguides in Tidy3D FDTD¶

Here, we illustrate how we can use the `ModeSource`

and `ModeMonitor`

objects to study modes in bent waveguides.

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

### Bent waveguide setup¶

First, we will study mode injection and decomposition in a microring. We start by defining various simulation parameters, and the structures that enter the simulation. We simulate a silicon ring on a silicon oxide substrate, and the ring is defined using two Cylinders.

```
# Unit length is micron.
wg_height = 0.22
wg_width = 0.9
# Radius of the simulated ring
radius = 2
# Waveguide and substrate materials
mat_wg = td.Medium(permittivity=3.48**2)
mat_sub = td.Medium(permittivity=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 = radius + 1.5
# Simulation domain size, resolution and total run time
sim_size = [sim_length, 2 * (radius + 1.5), 3]
run_time = 20 / fwidth
grid_spec = td.GridSpec.auto(min_steps_per_wvl=20, wavelength=lambda0)
# 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,
)
# The ring is made by two cylinders
cyl1 = td.Structure(
geometry=td.Cylinder(
center=[0, 0, 0],
radius=radius - wg_width / 2,
length=wg_height,
axis=2,
),
medium=td.Medium(),
)
cyl2 = td.Structure(
geometry=td.Cylinder(
center=[0, 0, 0],
radius=radius + wg_width / 2,
length=wg_height,
axis=2,
),
medium=mat_wg,
)
```

### Modal planes in bent waveguides¶

As usual, when initializing `ModeSource`

and `ModeMonitor`

objects, one of the three values of the `size`

parameter must be zero. In this example, we also need to define the axis of the bend and the radius of the curvature. The definitions are schematically illustrated in the image below. The bend axis is the axis normal to the plane in which the bend lies, (`'z'`

in the diagram below). In the mode specification, it is defined *locally* for the mode plane as one of the two axes tangential to the plane. In the case of bends that lie in the `xy`

-plane, the mode plane would be either in `xz`

or in `yz`

, so in both cases the correct setting is `bend_axis=1`

, selecting the global `z`

. The bend radius is counted from the center of the mode plane to the center of the curvature, along the tangential axis perpendicular to the bend axis. This radius can also be negative, if the center of the mode plane is smaller than the center of the bend, which is what we will encounter in this example. Finally, we note that the `'forward'`

and `'backward'`

direction parameter can still be used to distinguish between the two propagation directions as in regular modal sources and monitors.

```
# xy-plane frequency-domain field monitor; slightly offset in z for better structure viz below
field_mnt = td.FieldMonitor(
center=[0, 0, 0.05], size=[td.inf, td.inf, 0], freqs=[freq0], name="field"
)
# Flux monitor along the ring propagation direction
flux_mnt = td.FluxMonitor(
center=[0, radius, 0], size=[0, 3, 2], freqs=[freq0], name="flux"
)
```

### Running the simulation¶

First, we visualize the simulation to make sure we have set up the device correctly. We will use `'absorber'`

boundaries along the x-direction, because these boundaries work better than PML for structures which are not translationally invariant along the boundary normal direction.

```
# Simulation
sim = td.Simulation(
center=[sim_length / 2 - 0.2, 0, 0],
size=sim_size,
grid_spec=grid_spec,
structures=[substrate, cyl2, cyl1],
sources=[],
monitors=[field_mnt, flux_mnt],
run_time=run_time,
boundary_spec=td.BoundarySpec(
x=td.Boundary.absorber(), y=td.Boundary.pml(), z=td.Boundary.pml()
),
)
fig = plt.figure(figsize=(11, 4))
gs = mpl.gridspec.GridSpec(1, 2, figure=fig, width_ratios=[1, 2])
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1])
sim.plot(z=0, ax=ax1)
sim.plot(x=0, ax=ax2)
plt.show()
```

Note that Tidy3D is warning us that the simulation does not contain a source. However, since this simulation is used to construct the mode solver and will not be run directly, we can ignore this warning.

Next, we can compute the source modes to make sure that we inject the desired mode. When a bend radius $R$ is used, the effective index $n$ returned by the solver is such that the field evolves as $e^{i n k_0 R \phi}$, with $\phi$ the polar angle and $k_0 = \omega/c$. This definition is such that in the limit of infinite $R$, the effective index approaches that of a straight waveguide with the same cross-section. Based on our discussion and diagram above, we set the `bend_axis`

to `1`

, and the `bend_radius`

at the position of the source is negative.

```
# Modal source plane
source_plane = td.Box(center=[0, -radius, 0], size=[0, 3, 2])
num_modes = 4
# NB: negative radius since the plane position is at y=-radius
mode_spec = td.ModeSpec(num_modes=num_modes, bend_radius=-radius, bend_axis=1)
ms = ModeSolver(simulation=sim, plane=source_plane, freqs=[freq0], mode_spec=mode_spec)
modes = ms.solve()
f, axes = plt.subplots(num_modes, 3, tight_layout=True, figsize=(15, 12))
for axe, mode_index in zip(axes, range(num_modes)):
for ax, field_name in zip(axe, ("Ex", "Ey", "Ez")):
ms.plot_field(field_name, "abs", f=freq0, mode_index=mode_index, ax=ax)
plt.show()
```

Note that the last two of the computed modes are unphysical and for such modes that do not decay to zero at the plane boundary Tidy3D issues warnings. The fundamental mode looks like what we would expect, and we will use that mode for injection. Below, we also define a mode monitor, which is situated radially from the mode source, and so we use a positive value for the bend radius.

```
# Mode source directly exported from the mode solver above
source_time = td.GaussianPulse(freq0=freq0, fwidth=fwidth)
mode_src = ms.to_source(source_time=source_time, mode_index=0, direction="+")
# Mode monitor after one-half round-trip around the ring; NB: positive radius
mode_mnt = td.ModeMonitor(
center=[0, radius, 0],
size=[0, 3, 2],
freqs=[freq0],
mode_spec=td.ModeSpec(num_modes=2, bend_radius=radius, bend_axis=1),
name="modes",
)
sim = sim.copy(update=dict(sources=[mode_src]))
sim = sim.copy(update=dict(monitors=[field_mnt, flux_mnt, mode_mnt]))
```

```
sim_data = web.run(sim, task_name="ring_mode", path="data/sim_data.hdf5", verbose=True)
```

Finally, we visualize the results and verify that we get very close to unity transmission through the half-circle, and all the power is in the fundamental ring mode.

```
print("Transmission flux: ", abs(sim_data["flux"].flux.data))
# note: 'backward' mode amplitude
mode_flux = abs(sim_data["modes"].amps.sel(direction="-")) ** 2
print("Flux in first two modes: ", np.array(mode_flux).ravel())
f, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True, figsize=(15, 5))
ax1 = sim_data.plot_field("field", "Ex", z=0.05, f=freq0, val="real", ax=ax1)
ax2 = sim_data.plot_field("field", "E", "abs^2", z=0.05, f=freq0, ax=ax2)
plt.show()
```

Transmission flux: [0.99816906] Flux in first two modes: [0.99704065 0.00301029]

### Angled waveguide setup¶

`Mode`

objects can also be set to inject and record propagation at a given angle with respect to the axis normal to the mode plane. The `angle_theta`

and `angle_phi`

parameters of `ModeSource`

and `ModeMonitor`

objects define the injection axis as illustrated in the figure below, with respect to the axis normal to the mode plane (`x`

in the figure). Note that `angle_theta`

must be smaller than $\pi/2$. To inject in the backward direction, we can still use the `direction`

parameter as also shown in the figure. Similarly, the mode amplitudes computed in mode monitors are defined w.r.t. the `forward`

and `backward`

directions as illustrated.