Waveguide crossing based on cosine tapers#

To achieve a high integration density on a photonic chip, efficient routing of light with low loss using compact junctions is necessary. Therefore, waveguide crossings are crucial building blocks in high performance integrated circuits.

This example model demonstrates the simulation of a waveguide crossing based on cosine tapers. The convex cosine taper focuses the guided mode at the center of the crossing junction. This ensures the light is efficiently transmitted into the through port instead of scattered into the cross ports. The device achieves an insertion loss of ~0.2 dB and crosstalk ~-30 dB in the O-band (1260 nm -1360 nm). The design is adapted from Sujith Chandran, et al. “Beam shaping for ultra-compact waveguide crossings on monolithic silicon photonics platform,” Opt. Lett. 45, 6230-6233 (2020).

a7ab60c6b77b4453aa80a9d513354d60

[1]:
import numpy as np
import matplotlib.pyplot as plt
import tidy3d as td
import tidy3d.web as web
from scipy.optimize import fsolve

[21:42:50] 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

Simulation Setup#

Define geometric parameters and materials. In this device, the Si waveguide has a thickness of 161 nm and a width of 350 nm.

[2]:
h = 0.161  # waveguide thickness
w_in = 0.35  # input taper width
w_out = 1.1  # output taper width
w_m = 0.75  # amplitude of the cos function
l_t = 5.3  # taper length
inf_eff = (
    1000  # effective infinity used to make sure the waveguides extend into the pml
)

[3]:
si = td.Medium(permittivity=3.67**2)
sio2 = td.Medium(permittivity=1.45**2)

The taper width is described by a cosine function \(w(x)=w_m cos(ax+b)\). To determine the parameters \(a\) and \(b\), we solve a system of equations to ensure \(w(x)=w_{in}/2\) at the beginning of the taper and \(w(x)=w_{out}/2\) at the end of the taper. This can be easily done using fsolve from Scipy.

After we obtain \(w(x)\), vertices can be generated and the taper can be made using PolySlab. Once the first taper is made, the rest three tapers can be made by manipulating the vertices with the symmetry relation.

[4]:
# numerically solve for the cos function that describes the taper shape
def equations(x0):
    a, b = x0
    return (
        w_m * np.cos(a * (-w_out / 2) + b) - w_out / 2,
        w_m * np.cos(a * (-w_out / 2 - l_t) + b) - w_in / 2,
    )


a, b = fsolve(equations, (0.5, 2))

x = np.linspace(-w_out / 2 - l_t, -w_out / 2, 30)
w = w_m * np.cos(a * x + b)

# using the calculated taper shape to construct the taper as a PolySlab
vertices = np.zeros((2 * len(x), 2))
vertices[:, 0] = np.concatenate((x, np.flipud(x)))
vertices[:, 1] = np.concatenate((w, -np.flipud(w)))
taper_1 = td.Structure(
    geometry=td.PolySlab(vertices=vertices, axis=2, slab_bounds=(-h / 2, h / 2)),
    medium=si,
)

# creating the other four tapers by manipulating the vertices of the first taper
vertices[:, 0] = -vertices[:, 0]
taper_2 = td.Structure(
    geometry=td.PolySlab(vertices=vertices, axis=2, slab_bounds=(-h / 2, h / 2)),
    medium=si,
)

vertices[:, [1, 0]] = vertices[:, [0, 1]]
taper_3 = td.Structure(
    geometry=td.PolySlab(vertices=vertices, axis=2, slab_bounds=(-h / 2, h / 2)),
    medium=si,
)

vertices[:, 1] = -vertices[:, 1]
taper_4 = td.Structure(
    geometry=td.PolySlab(vertices=vertices, axis=2, slab_bounds=(-h / 2, h / 2)),
    medium=si,
)

# creating the center crossing junction using a Box
corner = 0
center = td.Structure(
    geometry=td.Box.from_bounds(
        rmin=(-w_out / 2 - corner, -w_out / 2 - corner, -h / 2),
        rmax=(w_out / 2 + corner, w_out / 2 + corner, h / 2),
    ),
    medium=si,
)

# creating the input port and through port
horizontal_wg = td.Structure(
    geometry=td.Box.from_bounds(
        rmin=(-inf_eff, -w_in / 2, -h / 2), rmax=(inf_eff, w_in / 2, h / 2)
    ),
    medium=si,
)

# creating the cross ports
vertical_wg = td.Structure(
    geometry=td.Box.from_bounds(
        rmin=(-w_in / 2, -inf_eff, -h / 2), rmax=(w_in / 2, inf_eff, h / 2)
    ),
    medium=si,
)

Set up simulation domain, source, and monitors. A mode source is used to excite the input waveguide. A field monitor at \(z=0\) plane is added to monitor the field propagation. Two flux monitors are added at the through port and cross port to monitor the transmission and crosstalk levels.

Before running the simulation, we can use the plot method to ensure the geometry, source, and monitors are set up correctly.

[5]:
lda0 = 1.31  # central wavelength
freq0 = td.C_0 / lda0  # operation frequency
ldas = np.linspace(1.26, 1.36, 101)  # wavelength range of interest
freqs = td.C_0 / ldas  # frequency range of interest

l_wg = 1  # input/output waveguide length
Lx = 2 * l_t + w_out + 2 * l_wg
Ly = 2 * l_t + w_out + 2 * l_wg
Lz = 1.5 * lda0
sim_size = (Lx, Ly, Lz)

# define a mode source for excitation using the lowest order mode
mode_source = td.ModeSource(
    center=(-Lx / 2 + l_wg / 2, 0, 0),
    size=(0, 4 * w_in, 4 * h),
    source_time=td.GaussianPulse(freq0=freq0, fwidth=freq0 / 10),
    direction="+",
    mode_spec=td.ModeSpec(num_modes=1, target_neff=3.455, filter_pol="te"),
    mode_index=0,
)

# add a field monitor at z=0 for field visualization
field_monitor = td.FieldMonitor(
    center=(0, 0, 0), size=(td.inf, td.inf, 0), freqs=[freq0], name="field"
)

# define a flux monitor to detect transmission to the through port
flux_monitor_through = td.FluxMonitor(
    center=(Lx / 2 - l_wg / 2, 0, 0),
    size=(0, 4 * w_in, 4 * h),
    freqs=freqs,
    name="flux_through",
)

# define a flux monitor to detect transmission to the cross port
flux_monitor_cross = td.FluxMonitor(
    center=(0, Ly / 2 - l_wg / 2, 0),
    size=(4 * w_in, 0, 4 * h),
    freqs=freqs,
    name="flux_cross",
)

sim = td.Simulation(
    center=(0, 0, 0),
    size=sim_size,
    grid_spec=td.GridSpec.auto(min_steps_per_wvl=20, wavelength=lda0),
    structures=[taper_1, taper_2, taper_3, taper_4, center, horizontal_wg, vertical_wg],
    sources=[mode_source],
    monitors=[field_monitor, flux_monitor_through, flux_monitor_cross],
    run_time=1e-12,
    boundary_spec=td.BoundarySpec.all_sides(boundary=td.PML()),
    medium=sio2,
)

sim.plot(z=0)

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

Once the simulation set up is verified, submit job to the server.

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

[21:42:52] INFO     Created task 'waveguide_crossing' with task_id                                    webapi.py:120
                    '8f6a1c6c-9692-4fba-9cb3-c65a8ca615c3'.                                                        
[21:42:54] INFO     Maximum FlexUnit cost: 0.488                                                      webapi.py:253
           INFO     status = queued                                                                   webapi.py:262
[21:42:57] INFO     status = preprocess                                                               webapi.py:274
[21:43:03] INFO     starting up solver                                                                webapi.py:278
[21:43:14] INFO     running solver                                                                    webapi.py:284
[21:43:54] INFO     early shutoff detected, exiting.                                                  webapi.py:295
           INFO     status = postprocess                                                              webapi.py:301
[21:44:07] INFO     status = success                                                                  webapi.py:307
[21:44:08] INFO     Billed FlexUnit cost: 0.274                                                       webapi.py:311
           INFO     downloading file "output/monitor_data.hdf5" to "data/simulation_data.hdf5"        webapi.py:593
[21:44:13] INFO     loading SimulationData from data/simulation_data.hdf5                             webapi.py:415

Result Visualization#

After the simulation is complete, first plot the field intensity distribution.

From the figure below, a good transmission to the through port is observed. In the crossing junction, a strong field focus is formed due to the focusing property of the cosine taper.

[7]:
sim_data.plot_field("field", "int", f=freq0, vmin=0, vmax=3000)

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

Finally, to quantify the designed waveguide crossing’s performance, plot insertion loss and crosstalk level.

[8]:
f, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True, figsize=(10, 5))

T_through = sim_data["flux_through"].flux
T_cross = sim_data["flux_cross"].flux

ax1.plot(ldas, -10 * np.log10(T_through))
ax1.set_xlabel("Wavelength ($\mu m$)")
ax1.set_ylabel("Insertion loss (dB)")
ax1.set_ylim((0, 0.3))

ax2.plot(ldas, 10 * np.log10(T_cross))
ax2.set_xlabel("Wavelength ($\mu m$)")
ax2.set_ylabel("Crosstalk (dB)")
ax2.set_ylim((-30, -25))

[8]:
(-30.0, -25.0)
../_images/notebooks_WaveguideCrossing_17_1.png
[ ]: