FDTD tidy3d#

tidy3D is a fast GPU based FDTD tool developed by flexcompute.

To run, you need to create an account and add credits. The number of credits that each simulation takes depends on the simulation size and computation time.

cloud_model

Materials#

To use gdsfactory LayerStack for different PDKs into tidy3d you have to create a mapping between each material name from the LayerStack into a tidy3d Medim

Tidy3d provides you with a material database that also includes dispersive materials.

import gdsfactory as gf
import matplotlib.pyplot as plt
import numpy as np
import tidy3d as td

import gplugins as gp
import gplugins.tidy3d as gt
from gplugins import plot
from gplugins.common.config import PATH
gt.material_name_to_medium
{'si': Medium(attrs={}, name='Si', frequency_range=None, allow_gain=False, nonlinear_spec=None, modulation_spec=None, heat_spec=None, type='Medium', permittivity=12.0409, conductivity=0.0),
 'sio2': Medium(attrs={}, name='SiO2', frequency_range=None, allow_gain=False, nonlinear_spec=None, modulation_spec=None, heat_spec=None, type='Medium', permittivity=2.1609, conductivity=0.0),
 'sin': Medium(attrs={}, name='SiN', frequency_range=None, allow_gain=False, nonlinear_spec=None, modulation_spec=None, heat_spec=None, type='Medium', permittivity=4.0, conductivity=0.0)}
nm = 1e-3
wavelength = np.linspace(1500, 1600) * nm
f = td.C_0 / wavelength
eps_complex = td.material_library["cSi"]["Li1993_293K"].eps_model(f)
n, k = td.Medium.eps_complex_to_nk(eps_complex)
plt.plot(wavelength, n)
plt.title("cSi crystalline silicon")
plt.xlabel("wavelength")
plt.ylabel("n")
Text(0, 0.5, 'n')
../_images/8536a5673e74e0b6adb6181de09cd88e8272e542400f1e8eb4588b7d34fe5eed.png
eps_complex = td.material_library["Si3N4"]["Luke2015PMLStable"].eps_model(f)
n, k = td.Medium.eps_complex_to_nk(eps_complex)
plt.plot(wavelength, n)
plt.title("SiN")
plt.xlabel("wavelength")
plt.ylabel("n")
Text(0, 0.5, 'n')
../_images/9d6e5b39555502aa088d74f1d7ca4df3d7b610c7dab433c6e136510feb1a077a.png
eps_complex = td.material_library["SiO2"]["Horiba"].eps_model(f)
n, k = td.Medium.eps_complex_to_nk(eps_complex)
plt.plot(wavelength, n)
plt.title("SiO2")
plt.xlabel("wavelength")
plt.ylabel("n")
Text(0, 0.5, 'n')
../_images/6f949805668923d880525e12d8b2c9a17fdc56615253e4f2ed30eb1944a1243f.png

Component Modeler#

You can easily convert a gdsfactory planar Component into a tidy3d simulation and make sure the simulation looks correct before running it

from gdsfactory.generic_tech import LAYER_STACK, get_generic_pdk

pdk = get_generic_pdk()
pdk.activate()

component = gf.components.coupler_ring()
component.plot()
../_images/a4d494fe240b19ecf58888a8a4f7f9c108caccb0a763a7f3b1af31c411d8a9bb.png
# define a mapping of pdk material names to tidy3d medium objects
mapping = {
    "si": td.Medium(name="Si", permittivity=3.47**2),
    "sio2": td.Medium(name="SiO2", permittivity=1.47**2),
}

# setup the tidy3d component
c = gt.Tidy3DComponent(
    component=component,
    layer_stack=LAYER_STACK,
    material_mapping=mapping,
    pad_xy_inner=2.0,
    pad_xy_outer=2.0,
    pad_z_inner=0,
    pad_z_outer=0,
    extend_ports=2.0,
)

# plot the component and the layerstack
fig = plt.figure(constrained_layout=True)
gs = fig.add_gridspec(ncols=2, nrows=3, width_ratios=(3, 1))
ax0 = fig.add_subplot(gs[0, 0])
ax1 = fig.add_subplot(gs[1, 0])
ax2 = fig.add_subplot(gs[2, 0])
axl = fig.add_subplot(gs[1, 1])
c.plot_slice(x="core", ax=ax0)
c.plot_slice(y="core", ax=ax1)
c.plot_slice(z="core", ax=ax2)
axl.legend(*ax0.get_legend_handles_labels(), loc="center")
axl.axis("off")
plt.show()
../_images/1181b6510d5612e6e4e5afb607ff36e6e4dd4f2c00f37bc1eb408a89c8342a62.png
LAYER_STACK.layers.pop("substrate", None)

# setup the tidy3d component
c = gt.Tidy3DComponent(
    component=component,
    layer_stack=LAYER_STACK,
    material_mapping=mapping,
    pad_xy_inner=2.0,
    pad_xy_outer=2.0,
    pad_z_inner=0,
    pad_z_outer=0,
    extend_ports=2.0,
)

# plot the component and the layerstack
fig = plt.figure(constrained_layout=True)
gs = fig.add_gridspec(ncols=2, nrows=3, width_ratios=(3, 1))
ax0 = fig.add_subplot(gs[0, 0])
ax1 = fig.add_subplot(gs[1, 0])
ax2 = fig.add_subplot(gs[2, 0])
axl = fig.add_subplot(gs[1, 1])
c.plot_slice(x="core", ax=ax0)
c.plot_slice(y="core", ax=ax1)
c.plot_slice(z="core", ax=ax2)
axl.legend(*ax0.get_legend_handles_labels(), loc="center")
axl.axis("off")
plt.show()
../_images/3b4e45ead266be70ac2f1a3d9ce28b02c66a7a7b776f1f27e77d97bcfc04fcd4.png
c.plot_slice(x="core")
<Axes: title={'center': 'cross section at x=-2.00'}, xlabel='y', ylabel='z'>
../_images/9aee3d0723ff80048dc98565637d001f5341fec2fcc4331cbd50362f77191dc5.png
# initialize the tidy3d ComponentModeler
modeler = c.get_component_modeler(
    center_z="core", port_size_mult=(6, 4), sim_size_z=3.0
)

# we can plot the tidy3d simulation setup
fig, ax = plt.subplots(2, 1)
modeler.plot_sim(z=c.get_layer_center("core")[2], ax=ax[0])
modeler.plot_sim(x=c.ports[0].center[0], ax=ax[1])
fig.tight_layout()
plt.show()
../_images/54d1b92fe357d81dc8d2869c7a3d304b093f2d9f8dcfe05f3d7e6115a6c552d6.png

Write S-parameters#

The most useful function is gt.write_sparameters which allows you to:

  • leverage a file cache to avoid running the same simulation twice.

  • plot_simulation and modes before running

  • run in 2D or 3D

file cache#

write_sparameters automatically will write your Sparameters in your home directory. By default uses a hidden folder in your home directory dirpath=~/.gdsfactory/sparameters. If simulation is found it will load it automatically, if not it will run it and write the Sparameters to filepath You can also specify a particular filepath to store simulations and load Sparameters from:

sp = gt.write_sparameters(
    component,
    filepath=PATH.sparameters_repo / "coupler_ring_2d.npz",
    sim_size_z=0,
    center_z="core",
)
01:23:01 UTC WARNING: 'simulation.structures[1]' is outside of the simulation   
             domain.                                                            
             WARNING: 'simulation.structures[1]' is outside of the simulation   
             domain.                                                            
Simulation loaded from PosixPath('/home/runner/work/gplugins/gplugins/test-data/sp/coupler_ring_2d.npz')
gp.plot.plot_sparameters(sp)
../_images/5bb9c47cfaa185dfd8c96f904c6f73f9ebb4b3aafe2c3d980d996cb2039ccdd6.png

Plot Simulations#

When setting up a simulation it’s important to check if simulation and modes looks correct.

c = gf.components.taper_sc_nc()
c.plot()
../_images/865610aea8ef9c46274535edd0eb24ae3fc5cd8fea138aa3acf9d5a9e3d6f204.png
sp = gt.write_sparameters(c, plot_simulation_layer_name="core", layer_stack=LAYER_STACK)
../_images/9c93ef4bda808c5d417ec55d9684d20558e0cc816226aedf305f9ea39a00bde2.png
# lets plot the fundamental input mode
sp = gt.write_sparameters(
    c, plot_mode_port_name="o1", plot_mode_index=0, layer_stack=LAYER_STACK
)
01:23:02 UTC WARNING: Use the remote mode solver with subpixel averaging for    
             better accuracy through 'tidy3d.plugins.mode.web.run(...)'.        
../_images/ce14bbe86d36fd414f5bcd72f1b9bb7e0f045a927b5d9752b38504657cd72a82.png
# lets plot the second order input mode
mode_spec = td.ModeSpec(num_modes=2, filter_pol="te")
sp = gt.write_sparameters(
    c,
    plot_mode_port_name="o1",
    plot_mode_index=1,
    layer_stack=LAYER_STACK,
    mode_spec=mode_spec,
)
../_images/08bbdb00497656c8ff4ea916c2210ccb266e1d5c22d8849756c304503392d00f.png
sp = gt.write_sparameters(
    c,
    plot_simulation_layer_name="nitride",
    plot_simulation_port_index=1,
    layer_stack=LAYER_STACK,
)
../_images/8a3336b5ac543d9e79bc72ac8237d7f7a84101430735f7a40b480e91f2a33be8.png
# lets plot the output mode
sp = gt.write_sparameters(
    c, plot_mode_port_name="o2", plot_mode_index=0, layer_stack=LAYER_STACK
)
../_images/df3cba8b17b1e1a2512ff4b9dc672fd736e2eb16ad385d30a4d212499af57b4f.png

2D#

2D simulations take less credits but are also less accurate than 3D. When running in 2D we don’t consider the component thickness in the z dimension.

For running simulations in 2D you need to set sim_size_z = 0.

It is also important that you choose center_z correctly.

sp = gt.write_sparameters(
    component,
    sim_size_z=0,
    layer_stack=LAYER_STACK,
    plot_simulation_layer_name="core",
    center_z="core",
)
01:23:25 UTC WARNING: 'simulation.structures[1]' is outside of the simulation   
             domain.                                                            
             WARNING: 'simulation.structures[1]' is outside of the simulation   
             domain.                                                            
             WARNING: 'simulation.structures[1]' is outside of the simulation   
             domain.                                                            
             WARNING: 'simulation.structures[1]' is outside of the simulation   
             domain.                                                            
             WARNING: 'simulation.structures[1]' is outside of the simulation   
             domain.                                                            
/home/runner/work/gplugins/gplugins/.venv/lib/python3.11/site-packages/tidy3d/components/scene.py:497: UserWarning: Attempting to set identical low and high ylims makes transformation singular; automatically expanding.
  ax.set_ylim(vlim)
../_images/9e11669a215d936f03df96f2ab9705f0f6ca401dcdf92730f52751eedef727db.png
component = gf.components.straight()
sp = gt.write_sparameters(
    component,
    filepath=PATH.sparameters_repo / "straight_2d.npz",
    sim_size_z=0,
    center_z="core",
)
01:23:26 UTC WARNING: 'simulation.structures[1]' is outside of the simulation   
             domain.                                                            
             WARNING: 'simulation.structures[1]' is outside of the simulation   
             domain.                                                            
Simulation loaded from PosixPath('/home/runner/work/gplugins/gplugins/test-data/sp/straight_2d.npz')
gp.plot.plot_sparameters(sp)
../_images/b95eef622d72a9bef2e6ab2f18ad99f9cde55fff830654c63faa49385c6afcb6.png

3D#

By default all simulations run in 3D unless indicated otherwise. 3D simulations run quite fast thanks to the GPU solver on the server side hosted by tidy3d cloud.

c = gf.components.straight(length=2)
sp = gt.write_sparameters(
    c, filepath=PATH.sparameters_repo / "straight_3d.npz", sim_size_z=4
)
gp.plot.plot_sparameters(sp)
Simulation loaded from PosixPath('/home/runner/work/gplugins/gplugins/test-data/sp/straight_3d.npz')
../_images/3cf7eac3f97f4ad26c53e107783a765ee6eef6aa41e7517b83c84ac9ff260b4e.png

Erosion / dilation#

component = gf.components.straight(length=0.1)
sp = gt.write_sparameters(
    component, layer_stack=LAYER_STACK, plot_simulation_layer_name="core", dilation=0
)
../_images/6458610f24fc4220c4571125c7bf3b16ea131f1ddc217014b26c58fd2464dd90.png

A dilation = 0.5 makes a 0.5um waveguide 0.75um

0.5 * 0.8
0.4
component = gf.components.straight(length=0.1)
sp = gt.write_sparameters(
    component, layer_stack=LAYER_STACK, plot_simulation_layer_name="core", dilation=0.5
)
../_images/bca544fb9a48d219d97c375bd9b9c50203bc78bc292c8e8e24378c34692e8b22.png

A dilation = -0.2 makes a 0.5um eroded down to 0.1um

0.2 * 0.5
0.1
component = gf.components.straight(length=0.1)
sp = gt.write_sparameters(
    component, layer_stack=LAYER_STACK, plot_simulation_layer_name="core", dilation=-0.2
)
../_images/0b6ed9291f27c93c556fd65ae2c522d45381474b145636c72137423edfb0c558.png

Plot monitors#

component = gf.components.taper_strip_to_ridge(length=10)
component.plot()
../_images/037d1d9c476343eaf70f94b7ada2e103888366d9ee385b60ad2a5d253004ea14.png
sp = gt.write_sparameters(
    c,
    plot_simulation_layer_name="core",
    plot_simulation_port_index=0,
    layer_stack=LAYER_STACK,
)
../_images/80127c290c5b2804d8488eec32315ef26def896aafa7067f2e45b31076fbe7e7.png
sp = gt.write_sparameters(
    c,
    plot_simulation_layer_name="core",
    plot_simulation_port_index=1,
    layer_stack=LAYER_STACK,
)
sp = gt.write_sparameters(
    c,
    plot_simulation_layer_name="slab90",
    plot_simulation_port_index=1,
    layer_stack=LAYER_STACK,
)
../_images/7638c71126011c52d1721fddd797415c86bad98d1972a2750f9ef86be3ae44e3.png ../_images/c31c65b9944ab7406d8b903a9931f85222a6737648d424a70866b3d943fb1f09.png
components = [
    "bend_euler",
    "bend_s",
    "coupler",
    "coupler_ring",
    "crossing",
    "mmi1x2",
    "mmi2x2",
    "taper",
    "straight",
]

for component_name in components:
    print(component_name)
    component = gf.get_component(component_name)
    gt.write_sparameters(
        component=component,
        layer_stack=LAYER_STACK,
        plot_simulation_layer_name="core",
        plot_simulation_port_index=0,
    )
bend_euler
../_images/65e6f18226f6e02c8cb900ebf757f3a65e2b7a98c6e06762f4e8b0a04d953349.png
bend_s
../_images/ef4392adb011c2b0b05c0d900c19b3aa3fe8acae7ba3767b198db5fb2701e02a.png
coupler
../_images/6aa1665fec6ff6c4bc59e1fe3fa4e0aa854b552f232e4511e92ece7c0be0213b.png
coupler_ring
../_images/3e6cee5e8783c73eb35edbfae444960cfc7f78546c37c956405f84583dc5d628.png
crossing
../_images/f62b2adda62c8b1232e19920a21f811d7310443f120ab687830aafd24ba6f48a.png
mmi1x2
../_images/82a08c77d806b022f8c3610d2a0331c6d03c42677b08c151279743369cad94a3.png
mmi2x2
../_images/16ca32965030bb0238048e5fb0749cd0b79b13246a893f405eb8488d9af80922.png
taper
../_images/5fa63dfed9886f0ceffd79e3c992cb0ecf308cb15bec925efc1e7a09868106fd.png
straight
../_images/5fa63dfed9886f0ceffd79e3c992cb0ecf308cb15bec925efc1e7a09868106fd.png
c = gf.components.bend_circular(radius=5)
s = gt.write_sparameters(
    c,
    layer_stack=LAYER_STACK,
    plot_simulation_layer_name="core",
    plot_simulation_port_index=0,
)
../_images/f97d5bb56da5334cfd76697712db6260b81bf97312c6c6606fc026d8c9543321.png

For a 2 port reciprocal passive component you can always assume s21 = s12

Another approximation you can make for planar devices is that s11 = s22, which saves 1 extra simulation. This approximation only works well for straight and bends. We call this 1x1 port symmetry

sp = np.load(
    PATH.sparameters_repo / "bend_circular_radius2_9d7742b34c224827aeae808dc986308e.npz"
)
plot.plot_sparameters(sp)
../_images/b88b3abfe7b3fe2eeb3c43011b2d5f8706ea4597acbdf730844f8219dd189123.png
plot.plot_sparameters(sp, keys=("o2@0,o1@0",))
../_images/abbac8d95359188cb871838d6faba781ea2888e2a736afa90fddf6cde588b9fb.png
c = gf.components.mmi1x2()
s = gt.write_sparameters(
    c,
    layer_stack=LAYER_STACK,
    plot_simulation_layer_name="core",
    plot_simulation_port_index=0,
)
../_images/82a08c77d806b022f8c3610d2a0331c6d03c42677b08c151279743369cad94a3.png
# sp = gt.write_sparameters(c)
sp = np.load(PATH.sparameters_repo / "mmi1x2_507de731d50770de9096ac9f23321daa.npz")
plot.plot_sparameters(sp)
../_images/881d015ced1127e87d03a5eae22773e15afef975c589f0517b05f3eacf09473b.png
plot.plot_sparameters(sp, keys=("o1@0,o2@0", "o1@0,o3@0"))
../_images/e447b2986790e4427d30ca7eb6e78d4851b8cbb72f9ae567a04b04727c74b2eb.png
plot.plot_loss1x2(sp)
../_images/04eff7eb42bb59b8a13d502628a494b114520cd176988236ab5ade8209d00678.png
plot.plot_imbalance1x2(sp)
../_images/0a8bdc50c6eb179ede4d2b942276850f6f1c5a5b0560f925c8bda216651197b8.png
c = gf.components.mmi2x2_with_sbend(with_sbend=False)
c.plot()
../_images/4e6a85a8566844f7c10e3fc6d3626d6925eb771c8abd016565ede65f51df6d65.png
sp = gt.write_sparameters(
    c,
    layer_stack=LAYER_STACK,
    plot_simulation_layer_name="core",
    plot_simulation_port_index=0,
)
../_images/07b027fb80978c529b37dfdb43d863d888c58dc5ce02cfc11153d36a994840a3.png
# sp = gt.write_sparameters(c, filepath=PATH.sparameters_repo / 'mmi2x2_without_sbend.npz')
sp = np.load(PATH.sparameters_repo / "mmi2x2_without_sbend.npz")
plot.plot_loss2x2(sp)
../_images/052e788f1d62be72d78f40da105531e5ffd10946e97b6730f3e0801e089ab322.png
plot.plot_imbalance2x2(sp)
../_images/53fd6d668f63dad79fe0db0eede8ef845433c4021fcc15cfa1027eb8f62c201b.png

get_simulation_grating_coupler#

You can also expand the planar component simulations to simulate an out-of-plane grating coupler.

The following simulations run in 2D but can also run in 3D.

help(gt.get_simulation_grating_coupler)
Help on function get_simulation_grating_coupler in module gplugins.tidy3d.get_simulation_grating_coupler:

get_simulation_grating_coupler(component: 'Component', port_extension: 'float' = 10.0, layer_stack: 'LayerStack | None' = None, thickness_pml: 'float' = 1.0, xmargin: 'float' = 0, ymargin: 'float' = 0, xmargin_left: 'float' = 0, xmargin_right: 'float' = 0, ymargin_top: 'float' = 0, ymargin_bot: 'float' = 0, zmargin: 'float' = 1.0, clad_material: 'str' = 'sio2', box_material: 'str' = 'sio2', box_thickness: 'float' = 3.0, substrate_material: 'str' = 'si', port_waveguide_name: 'str' = 'o1', port_margin: 'float' = 0.5, port_waveguide_offset: 'float' = 0.1, wavelength: 'float' = 1.55, wavelength_start: 'float' = 1.2, wavelength_stop: 'float' = 1.8, wavelength_points: 'int' = 256, plot_modes: 'bool' = False, num_modes: 'int' = 2, run_time_ps: 'float' = 10.0, fiber_port_prefix: 'str' = 'o2', fiber_xoffset: 'float' = -7, fiber_z: 'float' = 2, fiber_mfd: 'float' = 10.4, fiber_angle_deg: 'float' = 20.0, material_name_to_tidy3d: 'None | dict[str, str]' = None, is_3d: 'bool' = True, with_all_monitors: 'bool' = False, boundary_spec: 'td.BoundarySpec | None' = None, grid_spec: 'td.GridSpec | None' = None, sidewall_angle_deg: 'float' = 0, dilation: 'float' = 0.0, padding_layer: 'tuple[int, int]' = (67, 0), cross_section: 'CrossSectionSpec | None' = None, **kwargs) -> 'td.Simulation'
    Returns Simulation object from a gdsfactory grating coupler component.
    
    injects a Gaussian beam from above and monitors the transmission into the waveguide.
    
    based on grating coupler example
    https://docs.simulation.cloud/projects/tidy3d/en/latest/notebooks/GratingCoupler.html
    
    .. code::
    
         top view
              ________________________________
             |                               |
             | xmargin_left                  |
             |<------>                       |
             |           ________________    |
             |          /   |  |  |  |  |    |
             |         /    |  |  |  |  |    |
             |=========     |  |  |  |  |    |
             |         \    |  |  |  |  |    |
             |   _ _ _ _\___|__|__|__|__|    |
             |   |                       <-->|
             |   |ymargin_bot   xmargin_right|
             |   |                           |
             |___|___________________________|
    
    
        side view
    
              fiber_xoffset
                 |<--->|
            fiber_port_name
                 |
                         fiber_angle_deg > 0
                        |  /
                        | /
                        |/
                 /              /       |
                /  fiber_mfd   /        |
               /<------------>/    _ _ _| _ _ _ _ _ _ _
                                        |
                   clad_material        | fiber_z
                    _   _   _      _ _ _|_ _ _ _ _ _ _
                   | | | | | |          ||core_thickness
                  _| |_| |_| |__________||___
                                        || |
        waveguide            |          || | slab_thickness
              ____________________ _ _ _||_|_
                             |          |
                   box_material         |box_thickness
              _______________|____ _ _ _|_ _ _ _ _ _ _
                             |          |
                 substrate_material     |substrate_thickness
             ________________|____ _ _ _|_ _ _ _ _ _ _
                             |
        |--------------------|<-------->
                                xmargin
    
    Args:
        component: gdsfactory Component.
        port_extension: extend ports beyond the PML.
        layer_stack: contains layer to thickness, zmin and material.
            Defaults to active pdk.layer_stack.
        thickness_pml: PML thickness (um).
        xmargin: left/right distance from component to PML.
        xmargin_left: left distance from component to PML.
        xmargin_right: right distance from component to PML.
        ymargin: left/right distance from component to PML.
        ymargin_top: top distance from component to PML.
        ymargin_bot: bottom distance from component to PML.
        zmargin: thickness for cladding above and below core.
        clad_material: material for cladding.
        box_material: material for box.
        substrate_material: material for substrate.
        box_thickness: (um).
        substrate_thickness: (um).
        port_waveguide_name: input port name.
        port_margin: margin on each side of the port.
        port_waveguide_offset: mode solver workaround.
            positive moves source forward, negative moves source backward.
        wavelength: source center wavelength (um).
            if None takes mean between wavelength_start, wavelength_stop
        wavelength_start: in (um).
        wavelength_stop: in (um).
        wavelength_points: number of wavelengths.
        plot_modes: plot source modes.
        num_modes: number of modes to plot.
        run_time_ps: make sure it's sufficient for the fields to decay.
            defaults to 10ps and automatic shutoff stops earlier if needed.
        fiber_port_prefix: port prefix to place fiber source.
        fiber_xoffset: fiber center xoffset to fiber_port_name.
        fiber_z: fiber zoffset from grating zmax.
        fiber_mfd: fiber mode field diameter (um). 10.4 for Cband and 9.2um for Oband.
        fiber_angle_deg: fiber_angle in degrees with respect to normal.
            Positive for west facing, Negative for east facing sources.
        material_name_to_tidy3d: dispersive materials have a wavelength
            dependent index. Maps layer_stack names with tidy3d material database names.
        is_3d: if False collapses the Y direction for a 2D simulation.
        with_all_monitors: True includes field monitors which increase results filesize.
        grid_spec: defaults to automatic td.GridSpec.auto(wavelength=wavelength)
            td.GridSpec.uniform(dl=20*nm)
            td.GridSpec(
                grid_x = td.UniformGrid(dl=0.04),
                grid_y = td.AutoGrid(min_steps_per_wvl=20),
                grid_z = td.AutoGrid(min_steps_per_wvl=20),
                wavelength=wavelength,
                override_structures=[refine_box]
            )
        boundary_spec: Specification of boundary conditions along each dimension.
            Defaults to td.BoundarySpec.all_sides(boundary=td.PML())
        sidewall_angle_deg : float = 0
            Angle of the sidewall.
            ``sidewall_angle=0`` (default) specifies vertical wall,
            while ``0<sidewall_angle_deg<90`` for the base to be larger than the top.
        padding_layer: layer to use for padding.
        dilation: float = 0.0
            Dilation of the polygon in the base by shifting each edge along its
            normal outwards direction by a distance;
            a negative value corresponds to erosion.
        cross_section: optional cross_section to extend ports beyond PML.
        kwargs: Additional keyword arguments to pass to the Simulation constructor.
    
    Keyword Args:
        symmetry: Define Symmetries.
            Tuple of integers defining reflection symmetry across a plane
            bisecting the simulation domain normal to the x-, y-, and z-axis
            at the simulation center of each axis, respectively.
            Each element can be ``0`` (no symmetry), ``1`` (even, i.e. 'PMC' symmetry) or
            ``-1`` (odd, i.e. 'PEC' symmetry).
            Note that the vectorial nature of the fields must be taken into account to correctly
            determine the symmetry value.
        medium: Background medium of simulation, defaults to vacuum if not specified.
        shutoff: shutoff condition
            Ratio of the instantaneous integrated E-field intensity to the maximum value
            at which the simulation will automatically terminate time stepping.
            Used to prevent extraneous run time of simulations with fully decayed fields.
            Set to ``0`` to disable this feature.
        subpixel: subpixel averaging.If ``True``, uses subpixel averaging of the permittivity
        based on structure definition, resulting in much higher accuracy for a given grid size.
        courant: courant factor.
            Courant stability factor, controls time step to spatial step ratio.
            Lower values lead to more stable simulations for dispersive materials,
            but result in longer simulation times.
        version: String specifying the front end version number.
    
    .. code::
    
        import matplotlib.pyplot as plt
        import gdsfactory as gf
        import gplugins.tidy3d as gt
    
        c = gf.components.grating_coupler_elliptical_arbitrary(
            widths=[0.343] * 25, gaps=[0.345] * 25
        )
        sim = gt.get_simulation(c)
        gt.plot_simulation(sim)
c = (
    gf.components.grating_coupler_elliptical_lumerical()
)  # inverse design grating apodized
fiber_angle_deg = 5
s = gt.get_simulation_grating_coupler(
    c, is_3d=False, fiber_angle_deg=fiber_angle_deg, fiber_xoffset=0
)
f = gt.plot_simulation(s)
Skipping ((WG - DEEP_ETCH) - SHALLOW_ETCH)
Skipping (SHALLOW_ETCH & WG)
Adding layer=SLAB150, layer_tuple=(2, 0) zmin=0.0, zmax=0.15 material_name='si'
01:23:34 UTC WARNING: The medium associated with structures[1] has a frequency  
             range: (1.692592e+14, 1.208995e+15) (Hz) that does not fully cover 
             the frequencies contained in monitors[0]. This can cause           
             inaccuracies in the recorded results.                              
             WARNING: Suppressed 1 WARNING message.                             
../_images/1aaf85406ad82f73eb2ef8e173835532bd4e8c378da69a03e1849b01b8501dcf.png
f = c.plot()
../_images/18889fce7dd2e55e7bceba5dd18b25ecd960f9b52125cf108951d50044e6b121.png

Lets compare the xtolerance of a constant pitch vs an apodized grating.

We run simulations in 2D for faster.

Lets simulate 2 different grating couplers:

  • apodized inverse design example from lumerical website (5 degrees fiber angle)

  • constant pitch grating from gdsfactory generic PDK (20 degrees fiber angle)

sim = gt.get_simulation_grating_coupler(
    c, is_3d=False, fiber_angle_deg=fiber_angle_deg, fiber_xoffset=-5
)
f = gt.plot_simulation(sim)
Skipping ((WG - DEEP_ETCH) - SHALLOW_ETCH)
Skipping (SHALLOW_ETCH & WG)
Adding layer=SLAB150, layer_tuple=(2, 0) zmin=0.0, zmax=0.15 material_name='si'
             WARNING: The medium associated with structures[1] has a frequency  
             range: (1.692592e+14, 1.208995e+15) (Hz) that does not fully cover 
             the frequencies contained in monitors[0]. This can cause           
             inaccuracies in the recorded results.                              
             WARNING: Suppressed 1 WARNING message.                             
../_images/3acd85ef18782fd7c490ea1ec0c1d6e5ec8a4c78a2e5cb9c432497ac3003e17f.png
sim = gt.get_simulation_grating_coupler(
    c, is_3d=False, fiber_angle_deg=fiber_angle_deg, fiber_xoffset=+5
)
f = gt.plot_simulation(sim)
Skipping ((WG - DEEP_ETCH) - SHALLOW_ETCH)
Skipping (SHALLOW_ETCH & WG)
Adding layer=SLAB150, layer_tuple=(2, 0) zmin=0.0, zmax=0.15 material_name='si'
             WARNING: The medium associated with structures[1] has a frequency  
             range: (1.692592e+14, 1.208995e+15) (Hz) that does not fully cover 
             the frequencies contained in monitors[0]. This can cause           
             inaccuracies in the recorded results.                              
             WARNING: Suppressed 1 WARNING message.                             
../_images/757bff0f01bb84b387efd585c7a7f7b52634d2a3ddb4004801709bbdcdeda8ca.png
offsets = np.arange(-5, 6, 5)
offsets = [-10, -5, 0]
offsets = [0]
dfs = [
    gt.write_sparameters_grating_coupler(
        component=c,
        is_3d=False,
        fiber_angle_deg=fiber_angle_deg,
        fiber_xoffset=fiber_xoffset,
        filepath=PATH.sparameters_repo / f"gc_offset{fiber_xoffset}",
    )
    for fiber_xoffset in offsets
]
def log(x):
    return 20 * np.log10(x)
for offset in offsets:
    sp = gt.write_sparameters_grating_coupler(
        c,
        is_3d=False,
        fiber_angle_deg=fiber_angle_deg,
        fiber_xoffset=offset,
        filepath=PATH.sparameters_repo / f"gc_offset{offset}",
    )
    plt.plot(
        sp["wavelengths"], 20 * np.log10(np.abs(sp["o2@0,o1@0"])), label=str(offset)
    )

plt.xlabel("wavelength (um")
plt.ylabel("Transmission (dB)")
plt.title("transmission vs fiber xoffset (um)")
plt.legend()
<matplotlib.legend.Legend at 0x7f46c8e19fd0>
../_images/8cc81232ff781a75444438fe95d1f3c9f297dad804563b7b94186e88ec64722c.png
sp.keys()
dict_keys(['wavelengths', 'o1@0,o1@0', 'o2@0,o2@0', 'o1@0,o2@0', 'o2@0,o1@0'])
fiber_angles = [3, 5, 7]
dfs = [
    gt.write_sparameters_grating_coupler(
        component=c,
        is_3d=False,
        fiber_angle_deg=fiber_angle_deg,
        filepath=PATH.sparameters_repo / f"gc_angle{fiber_angle_deg}",
    )
    for fiber_angle_deg in fiber_angles
]
for fiber_angle_deg in fiber_angles:
    sp = gt.write_sparameters_grating_coupler(
        c,
        is_3d=False,
        fiber_angle_deg=fiber_angle_deg,
        filepath=PATH.sparameters_repo / f"gc_angle{fiber_angle_deg}",
    )
    plt.plot(
        sp["wavelengths"],
        20 * np.log10(np.abs(sp["o2@0,o1@0"])),
        label=str(fiber_angle_deg),
    )

plt.xlabel("wavelength (um")
plt.ylabel("Transmission (dB)")
plt.title("transmission vs fiber angle (degrees)")
plt.legend()
<matplotlib.legend.Legend at 0x7f46c8bc1fd0>
../_images/40033974a9c7a75f1b8883e30a7f88e10172b1069ac5d90b9ae02cf951c206da.png
c = gf.components.grating_coupler_elliptical_arbitrary(
    widths=(0.343,) * 25, gaps=(0.345,) * 25
)
f = c.plot()
../_images/2c1b01351fc3e915e92e1527eb3a944c3f5711d4f9fe1992142560150e486735.png
fiber_angle_deg = 20
sim = gt.get_simulation_grating_coupler(
    c, is_3d=False, fiber_angle_deg=fiber_angle_deg, fiber_xoffset=0
)
f = gt.plot_simulation(sim, figsize=(22, 8))
Skipping ((WG - DEEP_ETCH) - SHALLOW_ETCH)
Skipping (SHALLOW_ETCH & WG)
Adding layer=SLAB150, layer_tuple=(2, 0) zmin=0.0, zmax=0.15 material_name='si'
01:23:35 UTC WARNING: The medium associated with structures[1] has a frequency  
             range: (1.692592e+14, 1.208995e+15) (Hz) that does not fully cover 
             the frequencies contained in monitors[0]. This can cause           
             inaccuracies in the recorded results.                              
             WARNING: Suppressed 1 WARNING message.                             
../_images/2864060dbeaeceb523f0267e4e3ef2a404c0dca2dacec69139e30e7bcdd337e6.png
offsets = [0]
offsets
[0]
dfs = [
    gt.write_sparameters_grating_coupler(
        component=c,
        is_3d=False,
        fiber_angle_deg=fiber_angle_deg,
        fiber_xoffset=fiber_xoffset,
        filepath=PATH.sparameters_repo / f"gc_offset{offset}",
    )
    for fiber_xoffset in offsets
]
port_name = c.ports[1].name

for offset in offsets:
    sp = gt.write_sparameters_grating_coupler(
        c,
        is_3d=False,
        fiber_angle_deg=fiber_angle_deg,
        fiber_xoffset=offset,
        filepath=PATH.sparameters_repo / f"gc_offset{offset}",
    )
    plt.plot(
        sp["wavelengths"],
        20 * np.log10(np.abs(sp["o2@0,o1@0"])),
        label=str(offset),
    )

plt.xlabel("wavelength (um")
plt.ylabel("Transmission (dB)")
plt.title("transmission vs xoffset")
plt.legend()
<matplotlib.legend.Legend at 0x7f46c8ae1cd0>
../_images/f8f763b33dd17512b0dc48aa3d1723c606ca8b4b1933d9c5ad3613a8219f6853.png

Run jobs in parallel#

You can run multiple simulations in parallel on separate threads.

Only when you sp.result() you will wait for the simulations to finish.

c = gf.components.grating_coupler_elliptical_lumerical()
fiber_angles = [3, 5, 7]
jobs = [
    dict(
        component=c,
        is_3d=False,
        fiber_angle_deg=fiber_angle_deg,
        filepath=PATH.sparameters_repo / f"gc_angle{fiber_angle_deg}",
    )
    for fiber_angle_deg in fiber_angles
]
sps = gt.write_sparameters_grating_coupler_batch(jobs)
for sp, fiber_angle_deg in zip(sps, fiber_angles):
    sp = sp.result()
    plt.plot(
        sp["wavelengths"],
        20 * np.log10(np.abs(sp["o2@0,o1@0"])),
        label=str(fiber_angle_deg),
    )

plt.xlabel("wavelength (um")
plt.ylabel("Transmission (dB)")
plt.title("transmission vs fiber angle (degrees)")
plt.legend()
<matplotlib.legend.Legend at 0x7f46c89f3590>
../_images/40033974a9c7a75f1b8883e30a7f88e10172b1069ac5d90b9ae02cf951c206da.png