Edge Coupler

05af1c1d46bf4df3a2fe62ef01846e2d

In this example, we’ll explore the simulation of edge couplers using PhotonForge. Edge couplers are essential components for efficiently coupling light into and out of photonic chips. Typically, optimized edge coupler designs minimize both the insertion loss and the return loss. The geometry of the edge coupler and the medium between the fiber and chip facet significantly impact these performance metrics.

This notebook demonstrates how to use 3D Gaussian Ports in to simulate light emission from a fiber, providing an intuitive approach to simulating scattering matrices for edge couplers. To see how they can also be used with vertical coupling, please refer to the grating coupler example.

We first begin by showing how to set up a Gaussian port to compute the scattering matrix for a simple inverse taper edge coupler. After that, we also look at designing an improved version with an angled edge coupler.

[1]:
import matplotlib.pyplot as plt
import numpy as np
import photonforge as pf
import tidy3d as td
import tidy3d.web as web

We will use the basic technology for Silicon photonics that comes with PhotonForge. One parameter that we will require for positioning our Gaussian port is the thickness of the core layer of our device, so we set it here as well.

[2]:
core_thickness = 0.22

tech = pf.basic_technology(core_thickness=0.22)
pf.config.default_technology = tech
pf.config.default_kwargs["radius"] = 5

Simple Inverse Taper

Let’s build a parametric component to specify our edge coupler, including its ports and model.

We will use the default strip waveguide as base for the inverse taper, so we get a few dimensions from it, knowing that in the basic technology, waveguides are always surrounded by a cladding region.

[3]:
tech
[3]:
Name: Basic Technology
Version: 1.4.3
Layers
NameLayerDescriptionColorPattern
WG_CLAD(1, 0)Waveguide clad#9da6a218.
WG_CORE(2, 0)Waveguide core#6db5dd18/
SLAB(3, 0)Slab region#8851ad18:
TRENCH(4, 0)
Deep-etched trench for chip…… facets
#535e5918+
METAL(5, 0)Metal layer#b8a18b18\
Extrusion Specs
#MaskLimits (μm)Sidewal (°)Opt. MediumElec. Medium
0()-inf, 00cSi_Li1993_293KMedium(permittivity=12.3)
1()-2, 1.720SiO2_Palik_LosslessMedium(permittivity=4.2)
2'WG_CORE'0, 0.220cSi_Li1993_293KMedium(permittivity=12.3)
3'SLAB'0, 0.070cSi_Li1993_293KMedium(permittivity=12.3)
4'METAL'1.72, 2.220Cu_JohnsonChristy1972PEC
5'TRENCH'-inf, inf0Medium()Medium()
Ports
NameClassificationDescriptionWidth (μm)Limits (μm)Radius (μm)ModesTarget n_effPath profiles (μm)Voltage pathCurrent path
CPWelectricalCPW transmission line18.7398-6.46388, 10.4039014
'gnd0'@……'METAL': 6.8199 (-6.20995), 'gnd1'@'METAL': 6.8199 (+6.20995), 'signal'@'METAL': 3.6
(2.8, 1.97) (1.8, 1.97)
(2.3, 2.72) (-2.3, 2.72) (-2.3,…… 1.22) (2.3, 1.22)
RibopticalRib waveguide2.16-1, 1.22014
'WG_CORE': 0.4,…… 'SLAB': 2.4, 'WG_CLAD': 2.4
StripopticalStrip waveguide2.25-1, 1.22014
'WG_CORE': 0.5,…… 'WG_CLAD': 2.5
Background medium
  • Optical: Medium()
  • Electrical: Medium()
Connections: []
[4]:
port_spec = tech.ports["Strip"]
core_width, _ = port_spec.path_profile_for("WG_CORE")
clad_width, _ = port_spec.path_profile_for("WG_CLAD")
[5]:
@pf.parametric_component
def create_edge_coupler(
    *,
    width_tip=0.1,
    length_taper=10,
    waist_radius=2.0,
    fiber_distance=3.0,
    trench_layer="TRENCH"
):
    component = pf.Component()
    component.add(
        "WG_CORE",
        pf.stencil.linear_taper(length_taper, (width_tip, core_width)),
        "WG_CLAD",
        pf.stencil.linear_taper(length_taper, (4 * waist_radius, clad_width)),
    )

    # The Gaussian port must be placed over homogeneous medium, so we add a trench where the
    # port will be placed
    component.add(
        trench_layer,
        pf.Rectangle(
            (-2 * fiber_distance, -3 * waist_radius),
            (0, 3 * waist_radius),
        ),
    )

    port_fiber = pf.GaussianPort(
        center=(-fiber_distance, 0, core_thickness / 2),
        input_vector=(1, 0, 0),
        waist_radius=waist_radius,
        polarization_angle=0,  # for TE mode, in this case
    )
    port_waveguide = pf.Port((length_taper, 0), 180, port_spec)

    component.add_port([port_fiber, port_waveguide])

    model = pf.Tidy3DModel(symmetry=(0, -1, 0))
    component.add_model(model, "Tidy3D")
    return component


create_edge_coupler()
[5]:
../_images/examples_Edge_Coupler_7_0.svg

Note that we have defined Gaussian port as a source to inject light directly into the waveguide, which is not realistic. We will improve on that later.

For now, let’s create an inverse taper edge coupler with a 150 nm tip and a taper length of 25 μm. We will assume that we are using a lensed fiber of 2.5 μm spot size to couple light into the chip.

[6]:
edge_coupler = create_edge_coupler(
    width_tip=0.15,
    length_taper=25,
    waist_radius=2.5 / 2,
)
edge_coupler
[6]:
../_images/examples_Edge_Coupler_9_0.svg

Let’s plot the simulation domain to make sure the Gaussian port is setup properly.

[7]:
wavelengths = np.linspace(1.5, 1.6, 21)

_, ax = plt.subplots(1, 2, figsize=(10, 3), tight_layout=True)

sims = edge_coupler.models["Tidy3D"].get_simulations(edge_coupler, pf.C_0 / wavelengths)
_ = sims["P0@0"].plot(z=0, ax=ax[0])
_ = sims["P0@0"].plot(y=0, ax=ax[1])
07:34:19 -03 WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 2 WARNING messages.                            
             WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 2 WARNING messages.                            
../_images/examples_Edge_Coupler_11_4.png

Next, we compute and plot the S matrix (limited to a single input, to limit the number of simulations run).

[8]:
s_matrix = edge_coupler.s_matrix(pf.C_0 / wavelengths, model_kwargs={"inputs": ["P0"]})
_ = pf.plot_s_matrix(s_matrix, y="dB", threshold=0.2, input_ports=["P0"])
Starting…
07:34:20 -03 WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 2 WARNING messages.                            
             WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
07:34:21 -03 WARNING: Suppressed 2 WARNING messages.                            
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-dd2ce122-28e
             a-4ac0-85bd-1922da66fa3d'.
             WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 2 WARNING messages.                            
             WARNING: Warning messages were found in the solver log. For more   
             information, check 'SimulationData.log' or use                     
             'web.download_log(task_id)'.                                       
             WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 2 WARNING messages.                            
             WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 2 WARNING messages.                            
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=mo-cd3beb88-f927-
             46b6-a23b-ecbd7b614bb4'.
Progress: 100%
../_images/examples_Edge_Coupler_13_15.png

We see an insertion loss above 2.7 dB in the whole wavelength range, which includes both modal mismatch and reflections from the chip facet.

Index-Matched Gap

Index matching fluid or packaging epoxy can be used to not only improve the coupling efficiency but also to provide structural support post packaging. In order to see how adding an index matching layer affects the coupling losses while keeping the fiber at a fixed distance away from the chip we will define a new layer and an extrusion specification.

We could, in this case, set the basic technology parameter trench_medium to the index matching fluid to the same effect, but this is a good example of technology customization that might be useful in cases where the technology function does not provide enough flexibility.

First, we define and add the new layer:

[9]:
matching_layer = pf.LayerSpec(
    (5, 0), "Index matching region", color=(30, 30, 155, 100), pattern="x"
)
tech.add_layer("INDEX_MATCHING", matching_layer)
tech.layers
[9]:
NameLayerDescriptionColorPattern
WG_CLAD(1, 0)Waveguide clad#9da6a218.
WG_CORE(2, 0)Waveguide core#6db5dd18/
SLAB(3, 0)Slab region#8851ad18:
TRENCH(4, 0)
Deep-etched trench for chip…… facets
#535e5918+
INDEX_MATCHING(5, 0)Index matching region#1e1e9b64x
METAL(5, 0)Metal layer#b8a18b18\

Then, we create a new extrusion specification to extrude the structures in our new layer with the appropriate dimensions and medium. Typically, the index of the index matching layer is close to but lower than the index of the SiO₂ cladding, so we will use refractive index of 1.4 for this simulation.

Note that this extrusion will be inserted after the core and slab extrusion rules, because we might want to override the structures from those steps with the index matching region.

[10]:
medium_index_matching = td.Medium(permittivity=1.4**2, name="INDEX_MATCHING")
matching_extrusion = pf.ExtrusionSpec(
    pf.MaskSpec("INDEX_MATCHING"), medium_index_matching, (-pf.Z_INF, pf.Z_INF)
)
tech.insert_extrusion_spec(len(tech.extrusion_specs), matching_extrusion)
tech.extrusion_specs
[10]:
#MaskLimits (μm)Sidewal (°)Opt. MediumElec. Medium
0()-inf, 00cSi_Li1993_293KMedium(permittivity=12.3)
1()-2, 1.720SiO2_Palik_LosslessMedium(permittivity=4.2)
2'WG_CORE'0, 0.220cSi_Li1993_293KMedium(permittivity=12.3)
3'SLAB'0, 0.070cSi_Li1993_293KMedium(permittivity=12.3)
4'INDEX_MATCHING'1.72, 2.220Cu_JohnsonChristy1972PEC
5'TRENCH'-inf, inf0Medium()Medium()
6'INDEX_MATCHING'-inf, inf0INDEX_MATCHINGINDEX_MATCHING
[11]:
edge_coupler = create_edge_coupler(
    width_tip=0.15,
    length_taper=25,
    waist_radius=2.5 / 2,
    fiber_distance=3,
    trench_layer="INDEX_MATCHING",
)
edge_coupler
[11]:
../_images/examples_Edge_Coupler_19_0.svg
[12]:
_, ax = plt.subplots(1, 2, figsize=(10, 3), tight_layout=True)

sims = edge_coupler.models["Tidy3D"].get_simulations(edge_coupler, pf.C_0 / wavelengths)
_ = sims["P0@0"].plot(z=0, ax=ax[0])
_ = sims["P0@0"].plot(y=0, ax=ax[1])
07:34:22 -03 WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 5 WARNING messages.                            
             WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 5 WARNING messages.                            
../_images/examples_Edge_Coupler_20_4.png

Next, we calculate the S parameters and plot it.

[13]:
s_matrix_matched = edge_coupler.s_matrix(
    pf.C_0 / wavelengths, model_kwargs={"inputs": ["P0"]}
)
_ = pf.plot_s_matrix(s_matrix_matched, y="dB", input_ports=["P0"])
Starting…
07:34:23 -03 WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 5 WARNING messages.                            
07:34:24 -03 WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 5 WARNING messages.                            
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-98762173-4bb
             a-4430-810f-007d6c4dec9f'.
             WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 5 WARNING messages.                            
             WARNING: Warning messages were found in the solver log. For more   
             information, check 'SimulationData.log' or use                     
             'web.download_log(task_id)'.                                       
             WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 5 WARNING messages.                            
             WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 5 WARNING messages.                            
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=mo-cd6cb820-129b-
             4ba4-bff6-5be519a152c9'.
Progress: 100%
../_images/examples_Edge_Coupler_22_15.png

By introducing the index matching region, we are able to increase the coupling efficiency by almost 1 dB, as well as reduce the reflections to less than -35 dB. From this point, we could further characterize how the position and angular alignment of the fiber affects the insertion and return losses.

Another interesting possibility is to use an angled taper to minimize return losses, which is critical in some applications and offers us the opportunity to demonstrate angled waveguide and Gaussian ports.

Angled Inverse Taper

We will begin by creating a parametric function that allows us to create an angled edge coupler. At the output of the edge coupler, we will create an angled waveguide. A small bend is also added to guarantee a grid_aligned port.

[14]:
@pf.parametric_component
def create_angled_edge_coupler(
    *,
    width_tip=0.1,
    length_taper=10,
    angle_taper=7,
    waist_radius=2.0,
    fiber_distance=3.0,
    angle_fiber=10.2,
    trench_size_factor=3.0,
    trench_layer="TRENCH"
):

    v_taper = np.array(
        (np.cos(angle_taper / 180 * np.pi), np.sin(angle_taper / 180 * np.pi))
    )
    v_fiber = np.array(
        (np.cos(angle_fiber / 180 * np.pi), np.sin(angle_fiber / 180 * np.pi))
    )

    # Build the silicon inverse taper
    endpoint = pf.snap_to_grid(length_taper * v_taper)
    taper_core = (
        pf.Path(-waist_radius * v_taper, width_tip)
        .segment((0, 0))
        .segment(endpoint, core_width)
    )
    taper_clad = (
        pf.Path(-waist_radius * v_taper, 4 * waist_radius)
        .segment((0, 0))
        .segment(length_taper * v_taper, clad_width)
    )

    # Taper endpoint after a small bend to guarantee that the waveguide port
    # grid-aligned
    if angle_taper > 0:
        radius = pf.config.default_kwargs["radius"]
        endpoint = pf.snap_to_grid(
            endpoint + radius * np.array((abs(v_taper[1]), 1 - v_taper[0]))
        )
        base = 90 if angle_taper > 0 else -90
        taper_core.arc(90 + angle_taper, 90, radius, endpoint=endpoint)
        taper_clad.arc(90 + angle_taper, 90, radius, endpoint=endpoint)

    component = pf.Component()
    component.add(
        "WG_CORE",
        taper_core,
        "WG_CLAD",
        taper_clad,
        trench_layer,
        pf.Rectangle(
            (-2 * fiber_distance, -trench_size_factor * waist_radius),
            (0, trench_size_factor * waist_radius),
        ),
    )

    fiber_center = -fiber_distance * v_fiber
    port_fiber = pf.GaussianPort(
        center=(fiber_center[0], fiber_center[1], core_thickness / 2),
        input_vector=(v_fiber[0], v_fiber[1], 0),
        waist_radius=waist_radius,
        polarization_angle=0,  # for TE mode, in this case
    )
    port_waveguide = pf.Port(endpoint, 180, port_spec)

    component.add_port([port_fiber, port_waveguide])

    model = pf.Tidy3DModel()
    component.add_model(model, "Tidy3D")
    return component


create_angled_edge_coupler()
[14]:
../_images/examples_Edge_Coupler_25_0.svg
[15]:
angle_fiber = 15  # in degrees
# Fresnel diffraction
angle_taper = np.arcsin(np.sin(angle_fiber / 180 * np.pi) / 1.45) / np.pi * 180
print(f"Taper angle: {angle_taper:.1f}°")

edge_coupler = create_angled_edge_coupler(
    width_tip=0.15,
    length_taper=25,
    angle_taper=angle_taper,
    waist_radius=2.5 / 2,
    fiber_distance=3,
    angle_fiber=angle_fiber,
    trench_size_factor=4,
    trench_layer="TRENCH",
)
edge_coupler
Taper angle: 10.3°
[15]:
../_images/examples_Edge_Coupler_26_1.svg
[16]:
_, ax = plt.subplots(1, 2, figsize=(10, 3), tight_layout=True)

sims = edge_coupler.models["Tidy3D"].get_simulations(edge_coupler, pf.C_0 / wavelengths)
_ = sims["P0@0"].plot(z=0, ax=ax[0])
_ = sims["P0@0"].plot(y=0, ax=ax[1])
07:34:25 -03 WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 1 WARNING message.                             
07:34:26 -03 WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 1 WARNING message.                             
../_images/examples_Edge_Coupler_27_4.png
[17]:
s_matrix_tilted = edge_coupler.s_matrix(
    pf.C_0 / wavelengths, model_kwargs={"inputs": ["P0"]}
)
_ = pf.plot_s_matrix(s_matrix_tilted, y="dB", input_ports=["P0"])
Starting…
             WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 1 WARNING message.                             
             WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 1 WARNING message.                             
             Loading simulation from local cache. View cached task using web UI
             at
             'https://tidy3d.simulation.cloud/workbench?taskId=fdve-b46ef32f-df3
             5-43c3-b489-2a2efde0a3b2'.
             WARNING: Structure at 'structures[3]' has bounds that extend       
             exactly to simulation edges. This can cause unexpected behavior. If
             intending to extend the structure to infinity along one dimension, 
             use td.inf as a size variable instead to make this explicit.       
             WARNING: Suppressed 1 WARNING message.                             
07:34:27 -03 WARNING: Warning messages were found in the solver log. For more   
             information, check 'SimulationData.log' or use                     
             'web.download_log(task_id)'.                                       
Progress: 100%
../_images/examples_Edge_Coupler_28_10.png

We see that the insertion loss has essentially remained the same when compared to the straight edge coupler with air gap, but the reflection is more than 6 dB lower.