Skip to main content

Microring Resonator

Introduction

Microring resonator is important filtering device in photonic integrated circuits, the high performance filter require a wide free spectrum range(FSR) and high quality factor. Obtaining a large spectral range for microring resonator requires the use of a small radius, but too small radius can cause bending loss and reduce the quality factor. In order to meet the requirements of wide free spectrum range and high quality factor, it is necessary to carefully design the size of the ring and coupling efficiency.

In this example, we demonstrate the use of finite difference eigenmode(FDE) solver to calculate the group refractive index of an optical waveguide, then calculate the effective refractive index of symmetric and anti symmetric modes at the coupling region between the straight waveguide and the ring, and finally use 3D FDTD simulation to calculate the transmittance of the drop port.

Simulation

1 Code Description

1.1 Import Toolkit

First, we need to import "maxoptics_sdk" and third-party package of Python. The import module for FDE and FDTD simulation as follows.

from typing import List, Literal, NamedTuple
import time
import maxoptics_sdk.all as mo
from maxoptics_sdk.helper import timed, with_path

1.2 Define Simulation Function and parameters

To facilitate parameter changes, we can define function to encapsulate the entire simulation project. Before starting the simulation, you can define variables to control the parameters. As shown below.

def simulation(*, wavelength, grid, number_of_trial_modes, run_options: "RunOptions", **kwargs):
# region --- 0. General Parameters ---
width = 0.4
radius = 2.8
waveform_name = f"wv{wavelength*1e3}"

path = kwargs["path"]
simu_name = f"Microring_resonator"
time_str = time.strftime("%Y%m%d_%H%M%S", time.localtime())
project_name = f"{simu_name}_local_{time_str}"
plot_path = f"{path}/plots/{project_name}/"
kL = [f"0{k}" for k in range(5)]
export_options = {"export_csv": True, "export_mat": True, "export_zbf": True}
# endregion

1.3 Create project

We create an empty project by instantiating "Project()" in the simulation environment and define the name of project.

# region --- 1. Project ---
pj = mo.Project(name=project_name)
# endregion

1.4 Add Material

Here we demonstrate using "Material()" to instance the material module into the project, "add_nondispersion" and "add_lib" functions are available to create materials. You can refer to the following script to set materials.

# region --- 2. Material ---
mt = pj.Material()
mt.add_lib(name="Si", data=mo.Material.Si_Palik, order=2)
mt.add_lib(name="SiO2", data=mo.Material.SiO2_Palik, order=2)
# endregion

The "name" defines the name of the added material.
The "data" specifies the real and imaginary parts of the refractive index of the material.
The "order" specifies the mesh order of the material.

1.5 Add waveform

Adding a light source for simulating in 3D FDTD, and we use Waveform to set the waveform parameters of the light source.

# region --- 3. Waveform ---
wv = pj.Waveform()
wv.add(name=waveform_name, type='gaussian_waveform',
property={'set': 'frequency_wavelength', # selections are ['frequency_wavelength','time_domain']
'set_frequency_wavelength': {
'range_type': 'wavelength', # selections are ['frequency','wavelength']
'range_limit': 'center_span', # selections are ['min_max','center_span']
'wavelength_center': wavelength,
'wavelength_span': 0.1,},
}
)
wv_id = wv[waveform_name]
# endregion

The "name" sets the name of the waveform.
The "wavelength_center" sets the center wavelength of the light source. The "wavelength_span" sets the wavelength range of the light source.

1.6 Add Structure

The microring resonator is typical filter for SOI waveguide, including two straight optical waveguides and a ring-shaped waveguide. We use "Structure()" to instance the structure module to the project, "add_geometry" function is available for adding structures.
# region --- 4. Structure ---
st = pj.Structure()

st.add_geometry(name="ring", type="Ring",
property={ "geometry": { "x": 0, "y": 0, "z": 0, "z_span": 0.22,
"inner_radius": radius - width/2, "outer_radius": radius + width/2},
"material": {"material": mt["Si"], "mesh_order": 3 }})
st.add_geometry(name="wg_1", type="Rectangle",
property={"geometry": {"x": 0, "x_span": 15, "y": 3.3, "y_span": width, "z": 0, "z_span": 0.22},
"material": {"material": mt["Si"], "mesh_order": 3}})
st.add_geometry(name="wg_2", type="Rectangle",
property={"geometry": {"x": 0, "x_span": 15, "y": -3.3, "y_span": width, "z": 0, "z_span": 0.22},
"material": {"material": mt["Si"], "mesh_order": 3}})
# endregion
KeyValueTypeDescription
x0floatcenter position in the x-direction of the geometric structure  
materialmt["Si"]materialselect the material added Materials
mesh_order3integerset the priority of the material
x0floatthe center coordinate of the ring in the x-direction
y0floatthe center coordinate of the ring in the y-direction
z0floatthe center coordinate of the ring in the z-direction
z_span0floatthe thickness of the ring in the z-direction
inner_radius2.6floatthe size of the inner radius of the ring
outer_radius3floatthe size of the outer radius of the ring

The properties of the ring structure are shown in the table above, properties of Rectangle can refer to the settings of the ring. Select simulation material by using mesh_order in areas where geometry overlaps, the priority of structural materials needs to be higher than that of background material.

1.9 Add Solver

We use the "Simulation()" to instance the simulation module into the project and the "add" function to add an EME solver for the simulation. The properties settings of FDE and FDTD solvers as follows.
# region --- 7. Simulation
simu = pj.Simulation()
simu.add(name=simu_name, type="FDTD",
property={"background_material": mt["SiO2"],
"geometry": {"x": 0, "x_span": 9, "y": 0, "y_span": 9, "z": 0, "z_span": 2},
"boundary_conditions": {
"x_min_bc": "PML", "x_max_bc": "PML", "y_min_bc": "PML", "y_max_bc": "PML", "z_min_bc": "PML", "z_max_bc": "PML",
"pml_settings": {"all_pml": {"layers": 8, "kappa": 2, "sigma": 0.8, "polynomial": 3, "alpha": 0, "alpha_polynomial": 1}}},
"general": {"simulation_time": 4000},
"mesh_settings": {"mesh_factor": 1.2, "mesh_type": "auto_non_uniform",
"mesh_accuracy": {"cells_per_wavelength": grids_per_lambda},
"minimum_mesh_step_settings": {"min_mesh_step": 1e-4},
"mesh_refinement": {"mesh_refinement": "curve_mesh"}},
"advanced_options": {"auto_shutoff": {"auto_shutoff_min": 1.00e-4, "down_sample_time": 200}},
})
# endregion

The properties settings for the FDE solver as follows.

KeyValueTypeDescription
solver_type2d_x_normalstringselect the solution plane
dygridfloatgrid size along the y-direction
dzgridfloatgrid size along the z-direction
calculate_modesrun_options.run_fdeboolchoose whether to calculate the mode of the waveguide
mesh_structureFalseboolchoose whether to calculate the refractive index distribution of the structure
wavelengthwavelengthfloatcalculate the wavelength of the mode
number_of_trial_modesnumber_of_trial_modesintegernumber of solving modes
searchmax_indexstringchoose "max_index" or "near_n" for finding modes
calculate_group_indexTrueboolchoose whether to calculate the group refractive index

The properties settings for the FDTD solver as follows.

KeyValueTypeDescription
simulation_time5000floatset the maximum simulation time
mesh_typeauto_non_uniformstringselect 'auto_non_uniform' or 'uniform' to set the type of grid
cells_per_wavelengthgrids_per_lambdaintegerset the size of the grid
min_mesh_step1e-4floatset the minimum grid size
auto_shutoff_min1.e-4floatset the energy threshold for terminating the simulation
down_sample_time200floatset additional simulation duration
thread4integernumber of threads allocated to run the program

In the settings of the FDE solver, use "calculatemodes" controls whether to calculate the mode. Note that we need to calculate the group refractive index of the waveguide, so set the "calculate group_index" to True.

In the setting of the FDTD solver, "simulation_time" is used to control the simulation time. We set the simulation time to 4000 fs, which is greater than the default value of 1000 fs. The micro ring resonator has a high quality factor, its simulation requires longer time. If the simulation time is set too small and the simulation stops before the field decays, the results obtained are incorrect.

1.8 Add FDTD Port

In the FDTD simulation, at least a light source is required. We use the "Port()" to instance the port object into the project and the "add" function to add FDTD ports for the simulation. A port can serve not only as a source but also as a monitor. If there are multiple ports, it is necessary to specify which port is the source in the port group. The settings for the FDTD ports as follows.

# region --- 6. Source ---
pt = pj.Port(property={"waveform_id": wv_struct, "source_port": "port_1","monitor_frequency_points":9})

pt.add(name="port_1", type="fdtd_port",
property={"geometry": {"x": -4.25, "x_span": 0, "y": 3.3, "y_span": 2.5, "z": 0, "z_span": 2},
"modal_properties": {"general": {"inject_axis": "x_axis", "direction": "forward", "mode_selection": "fundamental"}}})
pt.add(name="port_2", type="fdtd_port",
property={"geometry": {"x": -4.25, "x_span": 0, "y": -3.3, "y_span": 2.5, "z": 0, "z_span": 2},
"modal_properties": {"general": {"inject_axis": "x_axis", "direction": "forward", "mode_selection": "fundamental"}}})
pt.add(name="port_3", type="fdtd_port",
property={"geometry": {"x": 4.25, "x_span": 0, "y": 3.3, "y_span": 2.5, "z": 0, "z_span": 2},
"modal_properties": {"general": {"inject_axis": "x_axis", "direction": "backward", "mode_selection": "fundamental"}}})
pt.add(name="port_4", type="fdtd_port",
property={"geometry": {"x": 4.25, "x_span": 0, "y": -3.3, "y_span": 2.5, "z": 0, "z_span": 2},
"modal_properties": {"general": {"inject_axis": "x_axis", "direction": "backward", "mode_selection": "fundamental"}}})
# endregion

endregion

1.10 Add Monitor

We use the "Monitor()" to instance the monitor object into the project and the "add" function to add monitors. The port can automatically calculate the transmittance and field profile of the cross-section. In time-domain simulation, it is necessary to add a time monitor at the end of the simulation to check the field strength to judge the accuracy of the simulation results.

# region --- 8. Monitor ---  
""" 6.0 GlobalMonitor """
mn = pj.Monitor()
mn.add(name="Global Option", type="global_option", property={
"frequency_power": { # "sample_spacing": "uniform", "use_wavelength_spacing": True, ["min_max","center_span"]
"spacing_type": "wavelength", "spacing_limit": "center_span", "wavelength_center": wavelength, "wavelength_span": 0.1, "frequency_points": 11 } }, )
mn.add(name="time_monitor_point", type="time_monitor",
property={ "general": { "stop_method": "end_of_simulation", "start_time": 0, "stop_time": 100, "number_of_snapshots": 0 },
"geometry": { "monitor_type": "point", "x": 0, "x_span": 0, "y": 2.8, "y_span": 0, "z": 0, "z_span": 0 },
"advanced": {"sampling_rate": {"min_sampling_per_cycle": 10}}})

mn.add(name="z_normal", type="power_monitor",
property={ "general": {
"frequency_profile": { "wavelength_center": wavelength, "wavelength_span": 0.1, "frequency_points": 11}},
"geometry": { "monitor_type": "2d_z_normal",
"x": 0, "x_span": 9, "y": 0, "y_span": 9, "z": 0, "z_span": 0 } })
#endregion

1.11 View Structure

You can use the "structure_show" function to view the top view of the structure, or use the "show3d()" call gui to check simulation settings.

# region --- 9. Structure Show ---
st.structure_show(fig_type="png", show=False, savepath=f"{plot_path}00_{simu_name}")
# simu[simu_name].show3d()
# endregion

1.12 Run

Pass in the name of the simulation and use "run" function to run the simulation.

# region --- 10. Run ---
eme_res = simu[simu_name].run()
# endregion

1.13 Run and Extract Results

The extract function is used to extract data from the result data, where "data" is the calculation result data, "savepath" is the storage path, "target" is the classification of the data, and "monitor_name" is the name of the monitor. The data extraction reference is as follows.. The script demonstration is as follows.
# region --- 11. See Results ---
if run_options.extract:

if run_options.run_fde:
k = kL[1]
res = results.extract(data="calculate_modes", savepath=f"{plot_path}{k}_neff_table", export_csv=True)
print(res.to_string(index=False))
for m in range(len(res)):
k = kL[2]
results.extract(data="calculate_modes", savepath=f"{plot_path}{k}_mode{m}",
attribute="Ey", mode=m, real=True, imag=False, **export_options, show=False)

if run_options.run_fdtd:

""" 01_time monitor """
fdtd_res.extract(data='fdtd:time_monitor', savepath=f"{plot_path}{kL[1]}_TransVstime",
monitor_name='time_monitor_point', target='line', attribute='E',plot_x='time', real=True,
imag=True, export_csv=True, show=False)

""" 02_top_profile """
fdtd_res.extract(data="fdtd:power_monitor", savepath=f"{plot_path}{kL[2]}_profile", monitor_name="z_normal",
target="intensity", attribute="E", wavelength=f"{wavelength}", export_csv=True)

""" 03port1_modeprofile """
fdtd_res.extract(data="fdtd:port_mode_info", savepath=f"{plot_path}{kL[3]}_port_1_profile",
target="intensity", attribute="E", port_name="port_1", mode=0, export_csv=True)

""" 031_port1_Lambda_power """
fdtd_res.extract(data="fdtd:power_monitor", savepath=f"{plot_path}{kL[3]}_port_1_T",
target="line", attribute="T", port_name="port_1", plot_x="wavelength", export_csv=True)

""" 032_port2_Lambda_power """
fdtd_res.extract(data="fdtd:power_monitor", savepath=f"{plot_path}{kL[3]}_port_2_T",
target="line", attribute="T", port_name="port_2", plot_x="wavelength", export_csv=True)

""" 033_port3_Lambda_power """
fdtd_res.extract(data="fdtd:power_monitor", savepath=f"{plot_path}{kL[3]}_port_3_T",
target="line", attribute="T", port_name="port_3", plot_x="wavelength", export_csv=True)

""" 034_port4_Lambda_power """
fdtd_res.extract(data="fdtd:power_monitor", savepath=f"{plot_path}{kL[3]}_port_4_T",
target="line", attribute="T", port_name="port_4", plot_x="wavelength", export_csv=True)

# endregion

1.14 Control Switch

We can control the operation of the simulation by passing in bool values through tuple, as shown in the following code. In each simulation, only one solver will be enabled. When using the FDE solver, set "run_fde" to True and "run_fdtd" to False; When using the FDTD solver, set "run_fdtd" to True and "run_fde" to False.

class RunOptions(NamedTuple):
index_preview: bool
run_fde: bool
run_fdtd: bool
extract: bool

if __name__ == "__main__":
simulation(wavelength=1.55, grids_per_lambda=6, run_options=RunOptions(index_preview=False, run=True, extract=True))

2. Output results

Length of ring

The light traveling in the waveguide generates evanescent field on its surface, so energy couples into a nearby waveguide as the waveguide approaches. We design a microring resonant filter with an FSR of 30nm, and we can calculate the effective circumference of the microring according to the following formula. The FDE solver can be used to calculate the group refractive index of the waveguide. The following table shows the group refractive index of the waveguide cross-section in the first three modes with a wavelength of 1.55um.

2πR=λ2/(FSRng) 2{\pi}R= {\lambda}^2/(FSR*n_g)
orderTErationeff_realneff_imagng_realng_imagwavelength_nmloss_dBpcm
00.9666272.2275030.04.4345940.01550.00
10.0467591.6927110.03.4678100.01550.00
20.5997481.4304060.01.6838880.01550.00

Considering the transmission of the fundamental mode of TE polarization, a group refractive index of 4.43 can be obtained. Therefore, selecting a ring with an effective radius of 2.87um can meet the free spectrum range of 30nm.

Couple gap and length

The length of the coupling region can be calculated based on the effective refractive index difference between symmetric and antisymmetric modes, as shown in the following formula. Setting the gap between the straight waveguide and the ring to 100nm can generate strong mutual coupling. Using the FDE solver to calculate the effective refractive index of the coupling region between the waveguide and the microring, the corresponding mode distribution is as follows.

The coupling distance calculated at wavelength of 1.55um more than 1.4 um. You can choose to increase the radius of the ring to reduce the coupling length. We choose to set the coupling length to zero and obtain sufficient coupling length and expected loop length by increasing the radius.

L=λ/(πΔn)sin1(t12)L={\lambda}/({\pi}{\Delta}n)sin^{-1}(|t12|)

Transmission

Calculate the resonant response of a straight waveguide coupled to a microring with a radius of 2.8um using 3D FDTD. Adding a mode light source as an excitation for straight waveguide, and use a power monitor to view the field distribution and transmittance.

When the light field coupled into the microring meets the resonance condition, it will output from the dorp port. As shown in the figure below, there is the maximum electric field at the drop port at the resonant wavelength.

The transmittance of the drop end is shown in the following figure.

References

Hammer, M. and Hiremath, K.R. and Stoffer, R. (2004) Analytical approaches to the description of optical microresonator devices. (Invited) In: Microresonators as Building Blocks for VLSI Photonics, 18-25 October 2003, Erice, Italy. pp. 48-71. AIP Conference Proceedings 709. Springer. ISSN 0094-243X ISBN 978-0-7354-0184-6.