Skip to content

CMT: Intro

In this chapter (as well as the next two chapters) we develop semi-analytical models based on coupled mode theory discussed in this paper: Design Space Exploration of Microring Resonators in Silicon Photonic Interconnects: Impact of the Ring Curvature

(Another good read for a simpler case is Evanescent waveguide couplers)

The main idea is to discretize the coupling area into many sections, each approximated as a simple directional coupler with a fixed gap:

Imports

```python import doModels.RefractiveIndex as ri import doModels.fem as fem import doModels.SemiAnalyticalCouplers as analyze

import matplotlib.pyplot as plt import numpy as np import xarray as xr from datetime import datetime from tqdm.notebook import tqdm ```

Define Stack

python stack = fem.CoupledWaveguides(gap=0.2, w_core_1=0.45, w_core_2=0.45, h_core=0.22, n_clad_func=ri.silica, n_core_func=ri.silicon)

Database of Coupled Waveguides

First, we create a database of super-modes for two adjacent waveguides whilst sweeping gap and wavelength. We use FEMWELL as described in Chapter 5. We store the results in an xarray netcdf file.

```python num_modes = 2 _gap = np.linspace(0.1, 0.9, 25) _wavelength = np.arange(1.5, 1.6, 0.005)

n = np.zeros((num_modes, len(_gap), len(_wavelength)))

for i, gap in enumerate(tqdm(_gap)): for j, wavelength in enumerate(_wavelength): stack.gap = gap modes = fem.supermode_solver( coupled_waveguides=stack, wavelength=wavelength, num_modes=num_modes, resolution=0.02, ) n[:, i, j] = modes

ds = xr.Dataset( { f"n{k+1}": (["gap", "wavelength"], n[k, :, :]) for k in range(num_modes) }, coords={"gap": _gap, "wavelength": _wavelength}, )

ds_sorted = analyze.adiabatic_mode_tracker1(ds) ds_sorted.to_netcdf("data/coupled_wgs.nc")

print(ds) ```

Load Database

python ds = xr.load_dataset("data/coupled_wgs.nc") analyze.plot_interpolated_modes(ds, wl=1.55, gap=0.3)

Exponential Fit

In the paper we cited above, the authors make a few geometrical arguments and apply some approximations to come up with a closed form expression for coupling coefficients of the directional coupler (See Eq. 11). One is to assume the indices of these supermodes follow an exponential as a function of gap:

```python aE, gammaE, aO, gammaO = analyze.fit_supermodes(ds, interp_at={"wavelength":1.55}, plot=True)

```

Compute Coupling Coefficient

Following Eq. 11, we compute the coupling coefficient \(\kappa\) as a function of gap, wavelength, and length.

```python

--- Plot κ vs gap ---

lambda_µm = 1.55 gaps_µm = np.linspace(0.250, 0.9, 100) lengths_µm = [1, 10, 50] kappa_results = np.zeros((len(lengths_µm), len(gaps_µm)))

fit_params = {} fit_params['aE'], fit_params['gammaE'], fit_params['aO'], fit_params['gammaO'] = analyze.fit_supermodes(ds, interp_at={"wavelength":lambda_µm})

for i, length_x_µm in enumerate(lengths_µm): for j, d in enumerate(gaps_µm): kappa_results[i, j] = analyze.kappa_directional_coupler(gap_µm=d, R_µm=20, lambda_µm=lambda_µm, length_x_µm=length_x_µm, v_offset_µm=15, fit_params=fit_params, wg_width_µm=stack.w_core_1)

plt.figure() for i, length_x_µm in enumerate(lengths_µm): plt.plot(gaps_µm, kappa_results[i], label=f'l_x = {length_x_µm} µm') plt.xlabel('Gap (µm)') plt.ylabel('κ') plt.grid(True) plt.legend()

--- Plot κ vs wavelength for fixed gaps ---

wavelengths_µm = np.linspace(1.50, 1.599, 100) fixed_gaps = [0.3, 0.4, 0.5]

plt.figure() for d in fixed_gaps: kappa_vals = [] for λ in wavelengths_µm: params = analyze.fit_supermodes(ds, interp_at={"wavelength": λ}, plot=False) kappa = analyze.kappa_directional_coupler(gap_µm=d, R_µm=20, lambda_µm=λ, length_x_µm=10, # or any fixed length v_offset_µm=15, fit_params=dict(zip(['aE', 'gammaE', 'aO', 'gammaO'], params)), wg_width_µm=stack.w_core_1) kappa_vals.append(kappa) plt.plot(wavelengths_µm, kappa_vals, label=f'gap = {d} µm')

plt.xlabel('Wavelength (µm)') plt.ylabel('κ') plt.grid(True) plt.legend() ```

To better visualize the coupling coefficient, we precomputed it for a range of parameters (gap, radius, length_x, and v_offset, wavelength) and store them in a look-up table. This is the simplest way to create an interactive plot shown in the next page.

```python

Define ranges

gap_vals = np.arange(0.25, 0.9, 0.04) # gaps in µm radius_vals = np.arange(5, 100, 20) # radii in µm wavelengths = np.arange(1.5, 1.6 , 0.005) # wavelengths in µm length_x_vals = np.arange(0, 100, 8) v_offset_vals = np.arange(5, 100, 40)

Initialize array

kappa_array = np.zeros((len(wavelengths), len(gap_vals), len(radius_vals), len(length_x_vals), len(v_offset_vals)))

for i0, wl in enumerate(wavelengths): fit_params = {} fit_params['aE'], fit_params['gammaE'], fit_params['aO'], fit_params['gammaO'] = analyze.fit_supermodes(ds_sorted, interp_at={"wavelength":wl})

for i1, gap in enumerate(gap_vals):
    for i2, radius in enumerate(radius_vals):
        for i3, length_x in enumerate(length_x_vals):
            for i4, v_offset in enumerate(v_offset_vals):
                kappa_array[i0, i1, i2, i3, i4] = analyze.kappa_directional_coupler(gap_µm=gap, 
                                        R_µm=radius, 
                                        lambda_µm=wl, 
                                        length_x_µm=length_x, 
                                        v_offset_µm=v_offset, 
                                        fit_params=fit_params, 
                                        wg_width_µm=stack.w_core_1)
                if np.isnan(kappa_array[i0, i1, i2, i3, i4]):
                    print(gap, radius, wl, length_x)

xarr_out = xr.DataArray( data=kappa_array, coords={ "wavelength": wavelengths, "gap": gap_vals, "radius": radius_vals, "length_x": length_x_vals, "v_offset": v_offset_vals, }, dims=["wavelength", "gap", "radius", "length_x", "v_offset"], name="kappa" )

xarr_out.to_netcdf("data/directional_coupler.nc")

xarr_out.to_dataframe().reset_index().to_json("data/directional_coupler.json", orient="records") ```