Building Circuit Models from Layout Files

In this notebook, we will see how to load GDSII layouts and run both component- and circuit-level simulations. This allows designers to import GDSII files generated from other software tools and evaluate their optical responses.
With a simulation model, we can see how fabrication variations (such as sidewall angles, layer thicknesses, refractive indices, etc.) impact circuit-level performance.
In this demo, we’ll learn how to:
- Import hierarchical designs from GDSII files;
- Detect ports and assign component models;
- Create circuit models; and
- Update the fabrication parameters to see impact on the circuit level S matrix.
We begin by first loading libraries for PhotonForge, Tidy3D and the open-source SiEPIC OpenEBL PDK, as well as and others we will require. To install the SiEPIC PDK please follow these steps.
import matplotlib.pyplot as pltimport numpy as npimport photonforge as pfimport photonforge.live_viewer as live_viewerimport siepic_forge as siepic_pdkimport tidy3d as tdAssign open-source Siepic PDK as our default technolgy and set some configurations.
tech = siepic_pdk.ebeam()pf.config.default_technology = tech
pf.config.default_mesh_refinement = 12 # Decreased accuracy only to run the example
pf.config.svg_labels = FalseLet’s begin by loading the GDSII file external_mzi.gds using the load_layout function. This file was created using an open-source PDK software package for fabrication in SiEPIC foundry, meaning the layer-stacks and PDK components adhere to the specifications provided by the foundry.
We can inspect the data, which is loaded as a dictionary of components (GDSII cells) indexed by name.
gds_data = pf.load_layout("misc/external_mzi.gds")
for component_name in gds_data.keys(): print(component_name)straight_L100_N2_CSstripdelayline_DL0delayline_DL200bend_euler_CSstrip_A180bend_euler_CSstripstraight_L20p358_N2_CSx_23d87c0cstraight_L58p878_N2_CSx_33fc2739straight_L17p043_N2_CSx_3c6d3aa8ebeam_y_1550gc_te1550_broadbandmzi_DL200mzi_with_gratings_DL200We are interested in creating a simulation model for the unbalanced MZI with grating couplers.
top_level = pf.find_top_level(*gds_data.values())
for component in top_level: print(component.name)mzi_with_gratings_DL200mzi_circuit = top_level[0]mzi_circuittype(mzi_circuit)photonforge.ComponentIf we want to inspect the device geometry carefully, we can use the browser-based layout viewer.
from photonforge.live_viewer import LiveViewer
viewer = LiveViewer()LiveViewer started at http://127.0.0.1:44191. Call its 'stop()' method to free the port.viewer.display(mzi_circuit)We can also visualize all hierarchical nature of how this GDSII file was created.
Feel free to set interactive = True to visualize the components in this heirarchy.
We see several independent components such as ebeam_y_1550, gc_te1550_broadband, and bend_euler_CSstrip that make up the circuit.
mzi_circuit.tree_view(interactive=False)├─ mzi_DL200
│ ├─ ebeam_y_1550
│ ├─ delayline_DL200
│ │ ├─ bend_euler_CSstrip
│ │ ├─ straight_L100_N2_CSstrip
│ │ └─ bend_euler_CSstrip_A180
│ └─ delayline_DL0
│ ├─ bend_euler_CSstrip
│ └─ bend_euler_CSstrip_A180
├─ gc_te1550_broadband
├─ bend_euler_CSstrip
├─ straight_L17p043_N2_CSx_3c6d3aa8
├─ straight_L58p878_N2_CSx_33fc2739
└─ straight_L20p358_N2_CSx_23d87c0c
GDSII files contains only layout information. In order to build a circuit model, we will identify all components with no dependencies (leaves in the dependency tree) and specify their ports and models to calculate their individual S parameters. PhotonForge will then use the physical connections between them to build the circuit model.
Let us start by looking at all the dependencies of the mzi_circuit component.
To identify independent components we need to find the ones that have no further references (references in PhotonForge are like instances of components).
# Gather all components with no dependencies (no references to other components)independent_components = [ c for c in mzi_circuit.dependencies() if len(c.references) == 0]
# Sort the list by nameindependent_components.sort(key=lambda c: c.name)
print("Independent components:")for i, component in enumerate(independent_components): print(f"- [{i}] {component.name}")Independent components:- [0] bend_euler_CSstrip- [1] bend_euler_CSstrip_A180- [2] ebeam_y_1550- [3] gc_te1550_broadband- [4] straight_L100_N2_CSstrip- [5] straight_L17p043_N2_CSx_3c6d3aa8- [6] straight_L20p358_N2_CSx_23d87c0c- [7] straight_L58p878_N2_CSx_33fc2739Sub-component setup
Section titled “Sub-component setup”Now depending on the type of the component we are looking at we might have to add different ports and models.
For example, for gc_te1550_broadband, we want to add a data model and an additional Gaussian port.
For ebeam_y_1550 we might want to run FDTD simulations.
For straight waveguides (or bends with insignificant loss) we might want a semi-analytical waveguide model.
We will add ports and models to the independent components one by one to show a few details in the process. Later, this process can be completely automated, if needed, based on the expected GDSII contents.
Component 0
Section titled “Component 0”component = independent_components[0]viewer.display(component)By inspecting the component in the live viewer, we see that the routes correspond to a 500 nm wide silicon strip waveguide. The PDK provides the default port specification “TE_1550_500” that matches this profile, so we will be using that to find ports. Note that we can easily inspect the ports of any technology.
port_spec = tech.ports["TE_1550_500"]wg_width, _ = port_spec.path_profile_for((1, 0))wg_width0.5# Automatically detect portsedge_ports = component.detect_ports([port_spec], on_boundary="xy")for port in edge_ports: print(port)Port at (0, 0) at 0 deg with spec "Strip TE 1550 nm, w=500 nm"Port at (10, 10) at 270 deg with spec "Strip TE 1550 nm, w=500 nm"# Add portscomponent.add_port(edge_ports)viewer.display(component)For 2-port routes where we don’t need to include bend losses, the waveguide model can be used to efficiently compute the S parameters from a single mode-solving calculation and the route length. Otherwise, full FDTD models can always be used to completely characterize the geometry.
# Use the area of the waveguide polygon in the layer (1,0) and waveguide width to estimate the route lengtharea = sum(s.area() for s in component.structures[1, 0])length = area / wg_widthcomponent.add_model(pf.WaveguideModel(length=length), "Waveguide")print(f"The length of this path is {length:.3f} μm")The length of this path is 16.637 μmComponent 1
Section titled “Component 1”The second component is very similar to the first we processed.
component = independent_components[1]viewer.display(component)# We can limit the search for port to a single boundaryedge_ports = component.detect_ports([port_spec], on_boundary="-x")for port in edge_ports: print(port)Port at (0, 0) at 0 deg with spec "Strip TE 1550 nm, w=500 nm"Port at (0, 20) at 0 deg with spec "Strip TE 1550 nm, w=500 nm"# Add portscomponent.add_port(edge_ports)
# Add modellength = sum(s.area() for s in component.structures[1, 0]) / wg_widthcomponent.add_model(pf.WaveguideModel(length=length), "Waveguide")print(f"The length of this route is {length:.3f} μm")
viewer.display(component)The length of this route is 42.817 μmComponent 2
Section titled “Component 2”component = independent_components[2]viewer.display(component)Because the port on the -x side has a rectangular box around the actual port edge, looking for ports at the exact boundaries will not work, so we add a 200 nm margin to the search:
edge_ports = component.detect_ports([port_spec], on_boundary="x", boundary_margin=0.2)for port in edge_ports: print(port)Port at (-7.4, 0) at 0 deg with spec "Strip TE 1550 nm, w=500 nm"Port at (7.4, -2.75) at 180 deg with spec "Strip TE 1550 nm, w=500 nm"Port at (7.4, 2.75) at 180 deg with spec "Strip TE 1550 nm, w=500 nm"# Add portscomponent.add_port(edge_ports)
# Add Tidy3D modelcomponent.add_model(pf.Tidy3DModel(port_symmetries=[("P0", "P2", "P1")]), "Tidy3D")
viewer.display(component)Component 3
Section titled “Component 3”component = independent_components[3]viewer.display(component)edge_ports = component.detect_ports([port_spec], on_boundary="x", boundary_margin=0.2)component.add_port(edge_ports)viewer.display(component)The free-space port for the grating coupler is best modeled by a Gaussian port, which can be used to accurately compute the S matrix based on an Tidy3D model. Alternatively, if we have the data available, a data model can be used as in the foundry PIC design example.
If we are not really interested in adding the effects of the grating coupler, a simpler alternative is to add another waveguide port (even if the geometry does not match it) and an analytical 2-port model, as we do below.
# Dummy waveguide portcomponent.add_port( pf.Port(component["P0"].center + (30, 0), input_direction=180, spec=port_spec))
component.add_model(pf.TwoPortModel(), "Analytical")
viewer.display(component)Components 4 – 7
Section titled “Components 4 – 7”For the rest of the components which are simply straight waveguides, we add semi-analytical models again.
For straight waveguides, PhotonForge automatically calculates the length so we do not need to explicitly specific length argument when specifying WaveguideModel.
for component in independent_components[4:]: edge_ports = component.detect_ports([port_spec], on_boundary="x") component.add_port(edge_ports) component.add_model(pf.WaveguideModel(), "WaveguideModel")viewer.display(independent_components[7])Now let’s look at the overall circuit. We can enable reference port drawing to see that all connections are accounted for.
pf.config.svg_reference_ports = True
viewer.display(mzi_circuit)We can see that, once ports are defined for individual components, PhotonForge detects the connections, which we can see by inspecting the netlist. The warnings tell us that there are still dangling ports, but that’s expected, because we are not done yet: we did not add ports to the intermediate-level components.
mzi_circuit.get_netlist()["connections"][((6, 'P1', 1), (7, 'P0', 1)), ((6, 'P0', 1), (8, 'P0', 1)), ((3, 'P1', 1), (4, 'P0', 1)), ((3, 'P0', 1), (5, 'P0', 1)), ((2, 'P0', 1), (8, 'P1', 1)), ((1, 'P0', 1), (5, 'P1', 1))]Circuit ports and models
Section titled “Circuit ports and models”In our case we want the ports for the grating coupler references to be the final ports.
Because the component has a hierarchical structure, we also need to add reference ports and models to each sub-component like mzi_DL200, delayline_DL200 and so on.
The add_reference_ports function allows us to recursively add ports and circuit models to these hierarchical sub-components.
mzi_circuit.add_reference_ports(include_dependencies=True, add_model=pf.CircuitModel())
pf.config.svg_reference_ports = False
viewer.display(mzi_circuit)S-matrix computation
Section titled “S-matrix computation”Now that ports and models are defined, all we need to do is to call s_matrix on the top-level component.
wavelengths = np.linspace(1.545, 1.55, 101)
s_matrix_initial = mzi_circuit.s_matrix(pf.C_0 / wavelengths)Uploading task 'P0@0…'Uploading task 'P1@0…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'P0@0…'Uploading task 'P1@0…'Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-55c90426-56b4-475d-8e9d-e1aab7c3438cStarting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-06601855-496f-46bf-85fd-1b950e9281d9Starting task 'P1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-26f5c0df-5b8f-4e9d-82bc-175dd575b117Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-32db3a51-1206-4040-aac3-8e40184a3188Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-e575ce76-7e26-4582-be04-4ca134115989Starting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-113c33fe-7104-42dc-bc80-464bcb5c79c6Downloading data from 'Mode-StripTE1550nmw500nm'…Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-f68d08cb-cd09-4da1-9c43-48ad873ba97bDownloading data from 'Mode-StripTE1550nmw500nm'…Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-6879780d-b590-4aa5-b6e7-aad9bc662dc0Downloading data from 'Mode-StripTE1550nmw500nm'…Starting task 'P1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-0c41eb4f-cb53-4fec-b33e-20dea801874cDownloading data from 'P1@0'…Downloading data from 'Mode-StripTE1550nmw500nm'…Starting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-104727ba-ce17-4c44-9d89-31e85ea58c5fStarting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-51fd6a8d-bf0f-49c9-bb46-fcf7f6a2e206Downloading data from 'P0@0'…Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-1535faa9-c80c-40f4-9601-1e1112435a0fDownloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'P1@0'…Downloading data from 'P0@0'…Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'Mode-StripTE1550nmw500nm'…_ = pf.plot_s_matrix(s_matrix_initial, input_ports=["P0"])
Let us calculate the FSR for the MZI based on the group index of the routing waveguide and compare to the value we can extract from the S parameters.
lda = 1.55
mode_solver = pf.port_modes( port_spec, frequencies=[pf.C_0 / lda], mesh_refinement=40, group_index=True)mode_solver.data.to_dataframe()14:31:54 -03 Loading simulation from local cache. View cached task using web UI at 'https://tidy3d.simulation.cloud/workbench?taskId=mo-2448784a-3cd1- 44ad-ab25-363b07beb31d'.
| wavelength | n eff | k eff | loss (dB/cm) | TE (Ey) fraction | wg TE fraction | wg TM fraction | mode area | group index | dispersion (ps/(nm km)) | ||
|---|---|---|---|---|---|---|---|---|---|---|---|
| f | mode_index | ||||||||||
| 1.934145e+14 | 0 | 1.55 | 2.442662 | 0.0 | 0.0 | 0.983483 | 0.763836 | 0.817709 | 0.190931 | 4.186253 | 508.873435 |
dl = 200ng = mode_solver.data.n_group.isel(mode_index=0, f=0).item()fsr = lda**2 / (ng * dl)
print( f"Estimated FSR from group index calculation using mode solver is {fsr * 1e3:.2f} nm")Estimated FSR from group index calculation using mode solver is 2.87 nmWe can get the FSR from the S parameters using scipy to find the transmission dips:
from scipy.signal import find_peaks
# We can calculate FSR from the datatransmission = np.abs(s_matrix_initial["P0@0", "P1@0"]) ** 2
# Find dips by inverting the signal: adjust prominence and distance as neededdips, properties = find_peaks(-transmission, prominence=0.01, distance=5)
# Plot original data_, ax = plt.subplots(1, 1)ax.plot(wavelengths, transmission, label="Transmission")
# Plot identified dipsfor dip in dips: ax.axvline(wavelengths[dip], ls="--", color="tab:gray")
ax.plot(wavelengths[dips], transmission[dips], "o", label="Identified dips")
ax.set(xlabel="Wavelength (µm)", ylabel="Transmission")ax.legend()
fsr_s = wavelengths[dips[1]] - wavelengths[dips[0]]print(f"FSR from S-matrix is: {fsr_s * 1e3:.2f} nm")FSR from S-matrix is: 2.85 nm
Technology variations
Section titled “Technology variations”During fabrication a number of parameters like the thickness of silicon, sidewall angle, and even material parameters may vary across the wafer. In order to study the impact of these parameters on the performance of the circuit, we can simply update the technology and re-run S-matrix computation.
# Since the technology itself is parametric we can inspect what parameters we can updatefor k, v in tech.parametric_kwargs.items(): s = str(v) if len(s) > 16: s = f"{v.__class__.__name__}(…)" print(f"- {k} = {s}")- si_thickness = 0.22- si_slab_thickness = 0.09- si_mask_dilation = 0.0- si_slab_mask_dilation = 0.0- sidewall_angle = 0.0- heater_thickness = 0.2- router_thickness = 0.6- bottom_oxide_thickness = 2.0- top_oxide_thickness = 2.2- passivation_oxide_thickness = 0.3- sio2 = dict(…)- si = dict(…)- router_metal = dict(…)- heater_metal = dict(…)- opening = Medium(…)Now, let’s update the silicon thickness and see the impact of that on our s-matrix.
_ = tech.update(si_thickness=0.215)
s_matrix_thin = mzi_circuit.s_matrix(pf.C_0 / wavelengths)
# It's a good idea to reset the value to the original, in case we forget later_ = tech.update(si_thickness=0.2)Uploading task 'P0@0…'Uploading task 'P1@0…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'P0@0…'Uploading task 'P1@0…'Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-3b37cb35-74f3-49ac-b2a2-05d8bee31b93Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-5dce6654-ee85-405f-8fcb-c7cd9884e1b7Starting task 'P1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-d0419e02-8b7f-47cb-a623-4cb052d7a7afStarting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-8b80c01c-2037-4bf3-aae8-db8395366215Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-2f3635c3-dc52-4ff3-b215-3ef48823de92Starting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-9eb8b919-6796-4c70-a911-aace01187e54Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-217c83a9-a0ad-415f-9410-6d4b8947685cStarting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-5b5d5be1-a1a4-45f2-9817-9c73ff31966cStarting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-531bc797-3e2a-42f0-a8d3-6632457c9f44Downloading data from 'Mode-StripTE1550nmw500nm'…Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-79e1e211-2cd0-4c57-8616-a1b6981b8903Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'P0@0'…Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'P1@0'…Starting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-45070993-be79-4a1c-980c-1719d5af0691Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'Mode-StripTE1550nmw500nm'…Starting task 'P1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-6229f9ea-1941-4b67-a66b-750c783e71d7Downloading data from 'P0@0'…Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'P1@0'…transmission2 = np.abs(s_matrix_thin["P0@0", "P1@0"]) ** 2
_, ax = plt.subplots(1, 1)ax.plot(wavelengths, transmission, label="Si thickness: 220 nm")ax.plot(wavelengths, transmission2, label="Si thickness: 200 nm")
ax.set(xlabel="Wavelength (µm)", ylabel="Transmission")_ = ax.legend()
As expected, changing silicon waveguide thinkness across the wafer even by 5 nm will dramatically impact the unbalanced MZM response. One can also perform Monte-Carlo simulations to look at how fabrication variations impact overall circuit performance.
Removing a delay arm
Section titled “Removing a delay arm”Now, let’s see what happens to the circuit performance if we delete one of the delay arms. We can use the tree view to get the reference numbers within each component:
mzi_circuit.tree_view(by_reference=True)├─[0] mzi_DL200
│ ├─[0] ebeam_y_1550
│ ├─[1] delayline_DL200
│ │ ├─[0] bend_euler_CSstrip
│ │ ├─[1] straight_L100_N2_CSstrip
│ │ ├─[2] bend_euler_CSstrip_A180
│ │ ├─[3] straight_L100_N2_CSstrip
│ │ └─[4] bend_euler_CSstrip
│ ├─[2] delayline_DL0
│ │ ├─[0] bend_euler_CSstrip
│ │ ├─[1] bend_euler_CSstrip_A180
│ │ └─[2] bend_euler_CSstrip
│ └─[3] ebeam_y_1550
├─[1] gc_te1550_broadband
├─[2] gc_te1550_broadband
├─[3] bend_euler_CSstrip
├─[4] straight_L17p043_N2_CSx_3c6d3aa8
├─[5] straight_L58p878_N2_CSx_33fc2739
├─[6] bend_euler_CSstrip
├─[7] straight_L20p358_N2_CSx_23d87c0c
└─[8] straight_L58p878_N2_CSx_33fc2739
# from the tree view, remove delayline_DL0 from mzi_DL200mzi_circuit.references[0].component.remove( mzi_circuit.references[0].component.references[2])viewer.display(mzi_circuit)s_matrix_new = mzi_circuit.s_matrix(pf.C_0 / wavelengths)Uploading task 'P0@0…'Uploading task 'P1@0…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'Mode-StripTE1550nmw500nm…'Uploading task 'P0@0…'Uploading task 'P1@0…'Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-642db40a-0f94-4951-b072-1301570fb8c0Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-65db9baf-49e4-47ce-a9a4-777d8265e787Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-3de121ba-db93-41c4-8f31-3854aee91c85Starting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-124632b1-9fea-4c9b-8d25-722f73b0a8c5Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-3dad7df3-df96-4024-935a-73e28d4de1e6Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-300a567a-9548-407d-949d-45e6c7714a54Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-1725176a-878b-41e5-a2db-39dc1efc7f07Starting task 'P1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-bbbb78bb-2ad1-4ae2-81ea-cf10e923db96Downloading data from 'Mode-StripTE1550nmw500nm'…Starting task 'Mode-StripTE1550nmw500nm': https://tidy3d.simulation.cloud/workbench?taskId=mo-856cf2f8-88f2-4d86-bb7c-ef60e10e7bceStarting task 'P0@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-5993f438-cbea-435e-918b-ec0ca825ebfcDownloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'P0@0'…Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'Mode-StripTE1550nmw500nm'…Starting task 'P1@0': https://tidy3d.simulation.cloud/workbench?taskId=fdve-94932686-724f-467b-97a5-96c12d0805c5Downloading data from 'P1@0'…Downloading data from 'P0@0'…Downloading data from 'Mode-StripTE1550nmw500nm'…Downloading data from 'P1@0'…transmission3 = np.abs(s_matrix_new["P0@0", "P1@0"]) ** 2
_, ax = plt.subplots(1, 1)ax.plot(wavelengths, 10 * np.log10(transmission), label="Original")ax.plot(wavelengths, 10 * np.log10(transmission3), label="Disconnected")
ax.set(xlabel="Wavelength (µm)", ylabel="Transmission (dB)")_ = ax.legend()
As expected, we see about 6 dB loss from the disconnected splitter.